commit 777ee9bad84d64138847c327b989830af64496e0 Author: achmad Date: Fri May 29 15:11:31 2026 +0700 initial commit diff --git a/addoninfo.txt b/addoninfo.txt new file mode 100644 index 0000000..716be12 --- /dev/null +++ b/addoninfo.txt @@ -0,0 +1,17 @@ +"AddonInfo" +{ + "maps" "invasion" + "IsPlayable" "1" + "MinimalPrecache" "1" + "HeroGuidesSupported" "1" + "ForceDefaultGuide" "0" + "EnablePickRules" "0" + "PenaltiesEnabled" "0" + "EventGame" "0" + "DrawCustomTeamHeroesOnMinimap" "1" + "CheckAFKPlayers" "0" + "invasion" + { + "MaxPlayers" "4" + } +} \ No newline at end of file diff --git a/itembuilds/default_axe.txt b/itembuilds/default_axe.txt new file mode 100644 index 0000000..7c2e8a3 --- /dev/null +++ b/itembuilds/default_axe.txt @@ -0,0 +1,38 @@ +"itembuilds" +{ + "author" "Nasqreal" + "hero" "npc_dota_hero_axe" + "Title" "Recommended items for Axe" + + "Items" + { + "#zinv_item_build_night_1" + { + "item" "item_boots" + "item" "item_bracer" + "item" "item_chainmail" + } + + "#zinv_item_build_night_2" + { + "item" "item_blink" + "item" "item_phase_boots" + "item" "item_blade_mail" + } + + "#zinv_item_build_night_3" + { + "item" "item_black_king_bar" + "item" "item_heart" + "item" "item_crimson_guard" + } + + "#zinv_item_build_night_4" + { + "item" "item_lotus_orb" + "item" "item_shivas_guard" + "item" "item_overwhelming_blink" + "item" "item_refresher" + } + } +} diff --git a/itembuilds/default_bloodhunter.txt b/itembuilds/default_bloodhunter.txt new file mode 100644 index 0000000..30fb71c --- /dev/null +++ b/itembuilds/default_bloodhunter.txt @@ -0,0 +1,38 @@ +"itembuilds" +{ + "author" "Nasqreal" + "hero" "npc_dota_hero_bloodhunter" + "Title" "Recommended items for Bloodhunter" + + "Items" + { + "#zinv_item_build_night_1" + { + "item" "item_boots" + "item" "item_wraith_band" + "item" "item_magic_stick" + } + + "#zinv_item_build_night_2" + { + "item" "item_power_treads" + "item" "item_desolator_custom" + "item" "item_mask_of_madness_custom" + } + + "#zinv_item_build_night_3" + { + "item" "item_black_king_bar" + "item" "item_manta" + "item" "item_satanic_custom" + } + + "#zinv_item_build_night_4" + { + "item" "item_battle_fury_custom" + "item" "item_skadi" + "item" "item_butterfly" + "item" "item_zombie_slayer" + } + } +} diff --git a/itembuilds/default_bristleback.txt b/itembuilds/default_bristleback.txt new file mode 100644 index 0000000..0729f71 --- /dev/null +++ b/itembuilds/default_bristleback.txt @@ -0,0 +1,38 @@ +"itembuilds" +{ + "author" "Nasqreal" + "hero" "npc_dota_hero_bristleback" + "Title" "Recommended items for Bristleback" + + "Items" + { + "#zinv_item_build_night_1" + { + "item" "item_boots" + "item" "item_bracer" + "item" "item_magic_wand" + } + + "#zinv_item_build_night_2" + { + "item" "item_phase_boots" + "item" "item_vanguard" + "item" "item_blade_mail" + } + + "#zinv_item_build_night_3" + { + "item" "item_heart" + "item" "item_shivas_guard" + "item" "item_lotus_orb" + } + + "#zinv_item_build_night_4" + { + "item" "item_eternal_shroud" + "item" "item_assault" + "item" "item_overwhelming_blink" + "item" "item_aeon_disk" + } + } +} diff --git a/itembuilds/default_crystal_maiden.txt b/itembuilds/default_crystal_maiden.txt new file mode 100644 index 0000000..066aa20 --- /dev/null +++ b/itembuilds/default_crystal_maiden.txt @@ -0,0 +1,40 @@ +"itembuilds" +{ + "author" "Nasqreal" + "hero" "npc_dota_hero_crystal_maiden" + "Title" "Recommended items for Crystal Maiden" + + "Items" + { + "#zinv_item_build_night_1" + { + "item" "item_boots" + "item" "item_magic_wand" + "item" "item_clarity" + } + + "#zinv_item_build_night_2" + { + "item" "item_arcane_boots" + "item" "item_wind_lace" + "item" "item_glimmer_cape" + } + + "#zinv_item_build_night_3" + { + "item" "item_force_staff" + "item" "item_aether_lens" + "item" "item_black_king_bar" + } + + "#zinv_item_build_night_4" + { + "item" "item_lotus_orb" + "item" "item_ghost" + "item" "item_wind_waker" + "item" "item_octarine_core" + } + } +} + + diff --git a/itembuilds/default_drow_ranger.txt b/itembuilds/default_drow_ranger.txt new file mode 100644 index 0000000..cd2a7e5 --- /dev/null +++ b/itembuilds/default_drow_ranger.txt @@ -0,0 +1,38 @@ +"itembuilds" +{ + "author" "Nasqreal" + "hero" "npc_dota_hero_drow_ranger" + "Title" "Recommended items for Drow Ranger" + + "Items" + { + "#zinv_item_build_night_1" + { + "item" "item_boots" + "item" "item_wraith_band" + "item" "item_magic_wand" + } + + "#zinv_item_build_night_2" + { + "item" "item_power_treads" + "item" "item_dragon_lance" + "item" "item_maelstrom" + } + + "#zinv_item_build_night_3" + { + "item" "item_hurricane_pike" + "item" "item_mjolnir_custom" + "item" "item_black_king_bar" + } + + "#zinv_item_build_night_4" + { + "item" "item_zombie_slayer" + "item" "item_skadi" + "item" "item_monkey_king_bar" + "item" "item_satanic_custom" + } + } +} diff --git a/itembuilds/default_elder_dragon_smaug.txt b/itembuilds/default_elder_dragon_smaug.txt new file mode 100644 index 0000000..5173a2c --- /dev/null +++ b/itembuilds/default_elder_dragon_smaug.txt @@ -0,0 +1,38 @@ +"itembuilds" +{ + "author" "Nasqreal" + "hero" "npc_dota_hero_elder_dragon_smaug" + "Title" "Recommended items for Elder Dragon Smaug" + + "Items" + { + "#zinv_item_build_night_1" + { + "item" "item_boots" + "item" "item_crown" + "item" "item_magic_wand" + } + + "#zinv_item_build_night_2" + { + "item" "item_power_treads" + "item" "item_kaya" + "item" "item_yasha" + } + + "#zinv_item_build_night_3" + { + "item" "item_octarine_core" + "item" "item_trident" + "item" "item_shivas_guard" + } + + "#zinv_item_build_night_4" + { + "item" "item_black_king_bar" + "item" "item_skadi" + "item" "item_refresher" + "item" "item_ultimate_scepter" + } + } +} diff --git a/itembuilds/default_hoodwink.txt b/itembuilds/default_hoodwink.txt new file mode 100644 index 0000000..a321b10 --- /dev/null +++ b/itembuilds/default_hoodwink.txt @@ -0,0 +1,38 @@ +"itembuilds" +{ + "author" "Nasqreal" + "hero" "npc_dota_hero_hoodwink" + "Title" "Recommended items for Hoodwink" + + "Items" + { + "#zinv_item_build_night_1" + { + "item" "item_boots" + "item" "item_wraith_band" + "item" "item_magic_wand" + } + + "#zinv_item_build_night_2" + { + "item" "item_power_treads" + "item" "item_maelstrom" + "item" "item_dragon_lance" + } + + "#zinv_item_build_night_3" + { + "item" "item_mjolnir_custom" + "item" "item_gungir" + "item" "item_black_king_bar" + } + + "#zinv_item_build_night_4" + { + "item" "item_skadi" + "item" "item_monkey_king_bar" + "item" "item_meteor_hammer" + "item" "item_shivas_guard" + } + } +} diff --git a/itembuilds/default_juggernaut.txt b/itembuilds/default_juggernaut.txt new file mode 100644 index 0000000..7e45df1 --- /dev/null +++ b/itembuilds/default_juggernaut.txt @@ -0,0 +1,38 @@ +"itembuilds" +{ + "author" "Nasqreal" + "hero" "npc_dota_hero_juggernaut" + "Title" "Recommended items for Juggernaut" + + "Items" + { + "#zinv_item_build_night_1" + { + "item" "item_boots" + "item" "item_wraith_band" + "item" "item_magic_wand" + } + + "#zinv_item_build_night_2" + { + "item" "item_power_treads" + "item" "item_yasha" + "item" "item_falcon_blade" + } + + "#zinv_item_build_night_3" + { + "item" "item_manta" + "item" "item_black_king_bar" + "item" "item_skadi" + } + + "#zinv_item_build_night_4" + { + "item" "item_butterfly" + "item" "item_monkey_king_bar" + "item" "item_satanic_custom" + "item" "item_mage_slayer" + } + } +} diff --git a/itembuilds/default_lina.txt b/itembuilds/default_lina.txt new file mode 100644 index 0000000..c9f8324 --- /dev/null +++ b/itembuilds/default_lina.txt @@ -0,0 +1,38 @@ +"itembuilds" +{ + "author" "Nasqreal" + "hero" "npc_dota_hero_lina" + "Title" "Recommended items for Lina" + + "Items" + { + "#zinv_item_build_night_1" + { + "item" "item_boots" + "item" "item_null_talisman" + "item" "item_magic_wand" + } + + "#zinv_item_build_night_2" + { + "item" "item_power_treads" + "item" "item_kaya" + "item" "item_clarity" + } + + "#zinv_item_build_night_3" + { + "item" "item_witch_blade" + "item" "item_black_king_bar" + "item" "item_octarine_core" + } + + "#zinv_item_build_night_4" + { + "item" "item_refresher" + "item" "item_ultimate_scepter" + "item" "item_shivas_guard" + "item" "item_phylactery" + } + } +} diff --git a/itembuilds/default_luna.txt b/itembuilds/default_luna.txt new file mode 100644 index 0000000..4710c31 --- /dev/null +++ b/itembuilds/default_luna.txt @@ -0,0 +1,38 @@ +"itembuilds" +{ + "author" "Nasqreal" + "hero" "npc_dota_hero_luna" + "Title" "Recommended items for Luna" + + "Items" + { + "#zinv_item_build_night_1" + { + "item" "item_boots" + "item" "item_wraith_band" + "item" "item_magic_wand" + } + + "#zinv_item_build_night_2" + { + "item" "item_power_treads" + "item" "item_yasha" + "item" "item_mask_of_madness_custom" + } + + "#zinv_item_build_night_3" + { + "item" "item_manta" + "item" "item_black_king_bar" + "item" "item_satanic_custom" + } + + "#zinv_item_build_night_4" + { + "item" "item_butterfly" + "item" "item_skadi" + "item" "item_monkey_king_bar" + "item" "item_zombie_slayer" + } + } +} diff --git a/itembuilds/default_medusa.txt b/itembuilds/default_medusa.txt new file mode 100644 index 0000000..72e9baf --- /dev/null +++ b/itembuilds/default_medusa.txt @@ -0,0 +1,38 @@ +"itembuilds" +{ + "author" "Nasqreal" + "hero" "npc_dota_hero_medusa" + "Title" "Recommended items for Medusa" + + "Items" + { + "#zinv_item_build_night_1" + { + "item" "item_boots" + "item" "item_magic_wand" + "item" "item_cloak" + } + + "#zinv_item_build_night_2" + { + "item" "item_mega_treads" + "item" "item_yasha" + "item" "item_maelstrom" + } + + "#zinv_item_build_night_3" + { + "item" "item_skadi" + "item" "item_manta" + "item" "item_mjolnir_custom" + } + + "#zinv_item_build_night_4" + { + "item" "item_butterfly" + "item" "item_monkey_king_bar" + "item" "item_hurricane_pike" + "item" "item_eternal_shroud" + } + } +} diff --git a/itembuilds/default_nagash.txt b/itembuilds/default_nagash.txt new file mode 100644 index 0000000..b5470df --- /dev/null +++ b/itembuilds/default_nagash.txt @@ -0,0 +1,38 @@ +"itembuilds" +{ + "author" "Nasqreal" + "hero" "npc_dota_hero_nagash" + "Title" "Recommended items for Nagash" + + "Items" + { + "#zinv_item_build_night_1" + { + "item" "item_boots" + "item" "item_bracer" + "item" "item_magic_wand" + } + + "#zinv_item_build_night_2" + { + "item" "item_power_treads" + "item" "item_vanguard" + "item" "item_blink" + } + + "#zinv_item_build_night_3" + { + "item" "item_eternal_shroud" + "item" "item_heart" + "item" "item_shivas_guard" + } + + "#zinv_item_build_night_4" + { + "item" "item_black_king_bar" + "item" "item_radiance" + "item" "item_lotus_orb" + "item" "item_assault" + } + } +} diff --git a/itembuilds/default_nevermore.txt b/itembuilds/default_nevermore.txt new file mode 100644 index 0000000..8296559 --- /dev/null +++ b/itembuilds/default_nevermore.txt @@ -0,0 +1,38 @@ +"itembuilds" +{ + "author" "Nasqreal" + "hero" "npc_dota_hero_nevermore" + "Title" "Recommended items for Shadow Fiend" + + "Items" + { + "#zinv_item_build_night_1" + { + "item" "item_boots" + "item" "item_wraith_band" + "item" "item_magic_wand" + } + + "#zinv_item_build_night_2" + { + "item" "item_power_treads" + "item" "item_mask_of_madness_custom" + "item" "item_maelstrom" + } + + "#zinv_item_build_night_3" + { + "item" "item_mjolnir_custom" + "item" "item_black_king_bar" + "item" "item_skadi" + } + + "#zinv_item_build_night_4" + { + "item" "item_butterfly" + "item" "item_satanic_custom" + "item" "item_hurricane_pike" + "item" "item_monkey_king_bar" + } + } +} diff --git a/itembuilds/default_phantom_assassin.txt b/itembuilds/default_phantom_assassin.txt new file mode 100644 index 0000000..1ddc16b --- /dev/null +++ b/itembuilds/default_phantom_assassin.txt @@ -0,0 +1,40 @@ +"itembuilds" +{ + "author" "Nasqreal" + "hero" "npc_dota_hero_phantom_assassin" + "Title" "Recommended items for Phantom Assassin" + + "Items" + { + "#zinv_item_build_night_1" + { + "item" "item_boots" + "item" "item_quelling_blade" + "item" "item_magic_wand" + } + + "#zinv_item_build_night_2" + { + "item" "item_mega_treads" + "item" "item_orb_of_corrosion" + "item" "item_vampire_claw" + } + + "#zinv_item_build_night_3" + { + "item" "item_battle_fury_custom" + "item" "item_black_king_bar" + "item" "item_desolator_custom" + } + + "#zinv_item_build_night_4" + { + "item" "item_mega_treads" + "item" "item_desolator_custom_2" + "item" "item_mega_fury" + "item" "item_skadi" + "item" "item_satanic_custom" + "item" "item_king_fly" + } + } +} diff --git a/itembuilds/default_pudge.txt b/itembuilds/default_pudge.txt new file mode 100644 index 0000000..d49511c --- /dev/null +++ b/itembuilds/default_pudge.txt @@ -0,0 +1,38 @@ +"itembuilds" +{ + "author" "Nasqreal" + "hero" "npc_dota_hero_pudge" + "Title" "Recommended items for Pudge" + + "Items" + { + "#zinv_item_build_night_1" + { + "item" "item_boots" + "item" "item_bracer" + "item" "item_magic_wand" + } + + "#zinv_item_build_night_2" + { + "item" "item_blink" + "item" "item_phase_boots" + "item" "item_vanguard" + } + + "#zinv_item_build_night_3" + { + "item" "item_heart" + "item" "item_eternal_shroud" + "item" "item_black_king_bar" + } + + "#zinv_item_build_night_4" + { + "item" "item_aeon_disk" + "item" "item_lotus_orb" + "item" "item_overwhelming_blink" + "item" "item_shivas_guard" + } + } +} diff --git a/itembuilds/default_rubick.txt b/itembuilds/default_rubick.txt new file mode 100644 index 0000000..19cf7ee --- /dev/null +++ b/itembuilds/default_rubick.txt @@ -0,0 +1,38 @@ +"itembuilds" +{ + "author" "Nasqreal" + "hero" "npc_dota_hero_rubick" + "Title" "Recommended items for Rubick" + + "Items" + { + "#zinv_item_build_night_1" + { + "item" "item_boots" + "item" "item_null_talisman" + "item" "item_magic_wand" + } + + "#zinv_item_build_night_2" + { + "item" "item_arcane_boots" + "item" "item_aether_lens" + "item" "item_blink" + } + + "#zinv_item_build_night_3" + { + "item" "item_octarine_core" + "item" "item_black_king_bar" + "item" "item_ultimate_scepter" + } + + "#zinv_item_build_night_4" + { + "item" "item_refresher" + "item" "item_arcane_blink" + "item" "item_meteor_hammer" + "item" "item_lotus_orb" + } + } +} diff --git a/itembuilds/default_sargatanas.txt b/itembuilds/default_sargatanas.txt new file mode 100644 index 0000000..23fbc8c --- /dev/null +++ b/itembuilds/default_sargatanas.txt @@ -0,0 +1,38 @@ +"itembuilds" +{ + "author" "Nasqreal" + "hero" "npc_dota_hero_sargatanas" + "Title" "Recommended items for Sargatanas" + + "Items" + { + "#zinv_item_build_night_1" + { + "item" "item_boots" + "item" "item_bracer" + "item" "item_magic_wand" + } + + "#zinv_item_build_night_2" + { + "item" "item_phase_boots" + "item" "item_echo_sabre" + "item" "item_desolator_custom" + } + + "#zinv_item_build_night_3" + { + "item" "item_blade_mail" + "item" "item_black_king_bar" + "item" "item_skadi" + } + + "#zinv_item_build_night_4" + { + "item" "item_harpoon" + "item" "item_heart" + "item" "item_assault" + "item" "item_radiance" + } + } +} diff --git a/itembuilds/default_sniper.txt b/itembuilds/default_sniper.txt new file mode 100644 index 0000000..558c0f0 --- /dev/null +++ b/itembuilds/default_sniper.txt @@ -0,0 +1,38 @@ +"itembuilds" +{ + "author" "Nasqreal" + "hero" "npc_dota_hero_sniper" + "Title" "Recommended items for Sniper" + + "Items" + { + "#zinv_item_build_night_1" + { + "item" "item_boots" + "item" "item_wraith_band" + "item" "item_magic_wand" + } + + "#zinv_item_build_night_2" + { + "item" "item_power_treads" + "item" "item_dragon_lance" + "item" "item_maelstrom" + } + + "#zinv_item_build_night_3" + { + "item" "item_hurricane_pike" + "item" "item_mjolnir_custom" + "item" "item_black_king_bar" + } + + "#zinv_item_build_night_4" + { + "item" "item_zombie_slayer" + "item" "item_skadi" + "item" "item_monkey_king_bar" + "item" "item_satanic_custom" + } + } +} diff --git a/itembuilds/default_sven.txt b/itembuilds/default_sven.txt new file mode 100644 index 0000000..598adda --- /dev/null +++ b/itembuilds/default_sven.txt @@ -0,0 +1,38 @@ +"itembuilds" +{ + "author" "Nasqreal" + "hero" "npc_dota_hero_sven" + "Title" "Recommended items for Sven" + + "Items" + { + "#zinv_item_build_night_1" + { + "item" "item_boots" + "item" "item_quelling_blade" + "item" "item_magic_wand" + } + + "#zinv_item_build_night_2" + { + "item" "item_power_treads" + "item" "item_echo_sabre" + "item" "item_lifesteal_custom" + } + + "#zinv_item_build_night_3" + { + "item" "item_blink" + "item" "item_black_king_bar" + "item" "item_satanic_custom" + } + + "#zinv_item_build_night_4" + { + "item" "item_harpoon" + "item" "item_skadi" + "item" "item_assault" + "item" "item_mega_fury" + } + } +} diff --git a/itembuilds/default_templar_assassin.txt b/itembuilds/default_templar_assassin.txt new file mode 100644 index 0000000..a3fc546 --- /dev/null +++ b/itembuilds/default_templar_assassin.txt @@ -0,0 +1,38 @@ +"itembuilds" +{ + "author" "Nasqreal" + "hero" "npc_dota_hero_templar_assassin" + "Title" "Recommended items for Templar Assassin" + + "Items" + { + "#zinv_item_build_night_1" + { + "item" "item_boots" + "item" "item_wraith_band" + "item" "item_magic_wand" + } + + "#zinv_item_build_night_2" + { + "item" "item_power_treads" + "item" "item_desolator_custom" + "item" "item_blink" + } + + "#zinv_item_build_night_3" + { + "item" "item_black_king_bar" + "item" "item_dark_crystalys" + "item" "item_swift_blink" + } + + "#zinv_item_build_night_4" + { + "item" "item_hurricane_pike" + "item" "item_assault" + "item" "item_monkey_king_bar" + "item" "item_skadi" + } + } +} diff --git a/itembuilds/default_yuki_onna.txt b/itembuilds/default_yuki_onna.txt new file mode 100644 index 0000000..1661816 --- /dev/null +++ b/itembuilds/default_yuki_onna.txt @@ -0,0 +1,38 @@ +"itembuilds" +{ + "author" "Nasqreal" + "hero" "npc_dota_hero_yuki_onna" + "Title" "Recommended items for Yuki-onna" + + "Items" + { + "#zinv_item_build_night_1" + { + "item" "item_boots" + "item" "item_null_talisman" + "item" "item_magic_wand" + } + + "#zinv_item_build_night_2" + { + "item" "item_arcane_boots" + "item" "item_kaya" + "item" "item_rod_of_atos" + } + + "#zinv_item_build_night_3" + { + "item" "item_witch_blade" + "item" "item_octarine_core" + "item" "item_shivas_guard" + } + + "#zinv_item_build_night_4" + { + "item" "item_black_king_bar" + "item" "item_refresher" + "item" "item_yasha_and_kaya" + "item" "item_ethereal_blade_custom" + } + } +} diff --git a/maps/effects/skin_effect_blue_gems.vpk b/maps/effects/skin_effect_blue_gems.vpk new file mode 100644 index 0000000..a34038b Binary files /dev/null and b/maps/effects/skin_effect_blue_gems.vpk differ diff --git a/maps/effects/skin_effect_bp_diretide_emblem.vpk b/maps/effects/skin_effect_bp_diretide_emblem.vpk new file mode 100644 index 0000000..7de5db3 Binary files /dev/null and b/maps/effects/skin_effect_bp_diretide_emblem.vpk differ diff --git a/maps/effects/skin_effect_bp_diretide_emblem_v1.vpk b/maps/effects/skin_effect_bp_diretide_emblem_v1.vpk new file mode 100644 index 0000000..3e2c796 Binary files /dev/null and b/maps/effects/skin_effect_bp_diretide_emblem_v1.vpk differ diff --git a/maps/effects/skin_effect_bp_diretide_emblem_v3.vpk b/maps/effects/skin_effect_bp_diretide_emblem_v3.vpk new file mode 100644 index 0000000..83c47b7 Binary files /dev/null and b/maps/effects/skin_effect_bp_diretide_emblem_v3.vpk differ diff --git a/maps/effects/skin_effect_bp_garden.vpk b/maps/effects/skin_effect_bp_garden.vpk new file mode 100644 index 0000000..cad222e Binary files /dev/null and b/maps/effects/skin_effect_bp_garden.vpk differ diff --git a/maps/effects/skin_effect_bp_golden.vpk b/maps/effects/skin_effect_bp_golden.vpk new file mode 100644 index 0000000..30deaf1 Binary files /dev/null and b/maps/effects/skin_effect_bp_golden.vpk differ diff --git a/maps/effects/skin_effect_bp_red.vpk b/maps/effects/skin_effect_bp_red.vpk new file mode 100644 index 0000000..1474d8c Binary files /dev/null and b/maps/effects/skin_effect_bp_red.vpk differ diff --git a/maps/effects/skin_effect_fall_2021_emblem.vpk b/maps/effects/skin_effect_fall_2021_emblem.vpk new file mode 100644 index 0000000..f1eb586 Binary files /dev/null and b/maps/effects/skin_effect_fall_2021_emblem.vpk differ diff --git a/maps/effects/skin_effect_fire.vpk b/maps/effects/skin_effect_fire.vpk new file mode 100644 index 0000000..a45250e Binary files /dev/null and b/maps/effects/skin_effect_fire.vpk differ diff --git a/maps/effects/skin_effect_heaven.vpk b/maps/effects/skin_effect_heaven.vpk new file mode 100644 index 0000000..272b105 Binary files /dev/null and b/maps/effects/skin_effect_heaven.vpk differ diff --git a/maps/effects/skin_effect_ice.vpk b/maps/effects/skin_effect_ice.vpk new file mode 100644 index 0000000..c993692 Binary files /dev/null and b/maps/effects/skin_effect_ice.vpk differ diff --git a/maps/effects/skin_effect_lotus.vpk b/maps/effects/skin_effect_lotus.vpk new file mode 100644 index 0000000..a2b56bb Binary files /dev/null and b/maps/effects/skin_effect_lotus.vpk differ diff --git a/maps/effects/skin_effect_sponsor.vpk b/maps/effects/skin_effect_sponsor.vpk new file mode 100644 index 0000000..e3653da Binary files /dev/null and b/maps/effects/skin_effect_sponsor.vpk differ diff --git a/maps/hero_selection/map_npc_dota_hero_bloodhunter.vpk b/maps/hero_selection/map_npc_dota_hero_bloodhunter.vpk new file mode 100644 index 0000000..9f55812 Binary files /dev/null and b/maps/hero_selection/map_npc_dota_hero_bloodhunter.vpk differ diff --git a/maps/hero_selection/map_npc_dota_hero_elder_dragon_smaug.vpk b/maps/hero_selection/map_npc_dota_hero_elder_dragon_smaug.vpk new file mode 100644 index 0000000..1683259 Binary files /dev/null and b/maps/hero_selection/map_npc_dota_hero_elder_dragon_smaug.vpk differ diff --git a/maps/hero_selection/map_npc_dota_hero_nagash.vpk b/maps/hero_selection/map_npc_dota_hero_nagash.vpk new file mode 100644 index 0000000..bf5d8e1 Binary files /dev/null and b/maps/hero_selection/map_npc_dota_hero_nagash.vpk differ diff --git a/maps/hero_selection/map_npc_dota_hero_sargatanas.vpk b/maps/hero_selection/map_npc_dota_hero_sargatanas.vpk new file mode 100644 index 0000000..d0d4b61 Binary files /dev/null and b/maps/hero_selection/map_npc_dota_hero_sargatanas.vpk differ diff --git a/maps/hero_selection/map_npc_dota_hero_yuki_onna.vpk b/maps/hero_selection/map_npc_dota_hero_yuki_onna.vpk new file mode 100644 index 0000000..d745c2d Binary files /dev/null and b/maps/hero_selection/map_npc_dota_hero_yuki_onna.vpk differ diff --git a/maps/invasion.vpk b/maps/invasion.vpk new file mode 100644 index 0000000..ba22e32 Binary files /dev/null and b/maps/invasion.vpk differ diff --git a/maps/light_fix.vpk b/maps/light_fix.vpk new file mode 100644 index 0000000..51aa158 Binary files /dev/null and b/maps/light_fix.vpk differ diff --git a/maps/test.vpk b/maps/test.vpk new file mode 100644 index 0000000..fec2201 Binary files /dev/null and b/maps/test.vpk differ diff --git a/materials/models/creeps/thief/drow_base_color_psd_67b932.vtex_c b/materials/models/creeps/thief/drow_base_color_psd_67b932.vtex_c new file mode 100644 index 0000000..1c0ffa2 Binary files /dev/null and b/materials/models/creeps/thief/drow_base_color_psd_67b932.vtex_c differ diff --git a/materials/models/creeps/thief/drow_bracer_color_psd_dcb8174b.vtex_c b/materials/models/creeps/thief/drow_bracer_color_psd_dcb8174b.vtex_c new file mode 100644 index 0000000..aa14428 Binary files /dev/null and b/materials/models/creeps/thief/drow_bracer_color_psd_dcb8174b.vtex_c differ diff --git a/materials/models/creeps/thief/drow_legarmor_color_psd_94fa217c.vtex_c b/materials/models/creeps/thief/drow_legarmor_color_psd_94fa217c.vtex_c new file mode 100644 index 0000000..3cf72d0 Binary files /dev/null and b/materials/models/creeps/thief/drow_legarmor_color_psd_94fa217c.vtex_c differ diff --git a/materials/models/creeps/thief/drow_shoulder_color_psd_cde15261.vtex_c b/materials/models/creeps/thief/drow_shoulder_color_psd_cde15261.vtex_c new file mode 100644 index 0000000..d868c49 Binary files /dev/null and b/materials/models/creeps/thief/drow_shoulder_color_psd_cde15261.vtex_c differ diff --git a/materials/models/creeps/thief/thief.vmat_c b/materials/models/creeps/thief/thief.vmat_c new file mode 100644 index 0000000..9d3b91e Binary files /dev/null and b/materials/models/creeps/thief/thief.vmat_c differ diff --git a/materials/models/creeps/thief/thief_archer_base.vmat_c b/materials/models/creeps/thief/thief_archer_base.vmat_c new file mode 100644 index 0000000..a14c54c Binary files /dev/null and b/materials/models/creeps/thief/thief_archer_base.vmat_c differ diff --git a/materials/models/creeps/thief/thief_archer_bow.vmat_c b/materials/models/creeps/thief/thief_archer_bow.vmat_c new file mode 100644 index 0000000..9b57127 Binary files /dev/null and b/materials/models/creeps/thief/thief_archer_bow.vmat_c differ diff --git a/materials/models/creeps/thief/thief_archer_bow_psd_4d94dc1c.vtex_c b/materials/models/creeps/thief/thief_archer_bow_psd_4d94dc1c.vtex_c new file mode 100644 index 0000000..630c731 Binary files /dev/null and b/materials/models/creeps/thief/thief_archer_bow_psd_4d94dc1c.vtex_c differ diff --git a/materials/models/creeps/thief/thief_archer_bracer.vmat_c b/materials/models/creeps/thief/thief_archer_bracer.vmat_c new file mode 100644 index 0000000..bf8944b Binary files /dev/null and b/materials/models/creeps/thief/thief_archer_bracer.vmat_c differ diff --git a/materials/models/creeps/thief/thief_archer_legarmor.vmat_c b/materials/models/creeps/thief/thief_archer_legarmor.vmat_c new file mode 100644 index 0000000..0a799e5 Binary files /dev/null and b/materials/models/creeps/thief/thief_archer_legarmor.vmat_c differ diff --git a/materials/models/creeps/thief/thief_archer_shoulder.vmat_c b/materials/models/creeps/thief/thief_archer_shoulder.vmat_c new file mode 100644 index 0000000..3b00aa4 Binary files /dev/null and b/materials/models/creeps/thief/thief_archer_shoulder.vmat_c differ diff --git a/materials/models/creeps/thief/thief_armor.vmat_c b/materials/models/creeps/thief/thief_armor.vmat_c new file mode 100644 index 0000000..b5d6992 Binary files /dev/null and b/materials/models/creeps/thief/thief_armor.vmat_c differ diff --git a/materials/models/creeps/thief/thief_armor_color_psd_29172a66.vtex_c b/materials/models/creeps/thief/thief_armor_color_psd_29172a66.vtex_c new file mode 100644 index 0000000..068c5e5 Binary files /dev/null and b/materials/models/creeps/thief/thief_armor_color_psd_29172a66.vtex_c differ diff --git a/materials/models/creeps/thief/thief_back.vmat_c b/materials/models/creeps/thief/thief_back.vmat_c new file mode 100644 index 0000000..428ae2f Binary files /dev/null and b/materials/models/creeps/thief/thief_back.vmat_c differ diff --git a/materials/models/creeps/thief/thief_back_color_psd_d2df393c.vtex_c b/materials/models/creeps/thief/thief_back_color_psd_d2df393c.vtex_c new file mode 100644 index 0000000..1e719d1 Binary files /dev/null and b/materials/models/creeps/thief/thief_back_color_psd_d2df393c.vtex_c differ diff --git a/materials/models/creeps/thief/thief_color_psd_553c3e27.vtex_c b/materials/models/creeps/thief/thief_color_psd_553c3e27.vtex_c new file mode 100644 index 0000000..7b9b0e2 Binary files /dev/null and b/materials/models/creeps/thief/thief_color_psd_553c3e27.vtex_c differ diff --git a/materials/models/creeps/thief/thief_hood.vmat_c b/materials/models/creeps/thief/thief_hood.vmat_c new file mode 100644 index 0000000..9ac180d Binary files /dev/null and b/materials/models/creeps/thief/thief_hood.vmat_c differ diff --git a/materials/models/creeps/thief/thief_hood_color_psd_b9752f2.vtex_c b/materials/models/creeps/thief/thief_hood_color_psd_b9752f2.vtex_c new file mode 100644 index 0000000..9f7bc4b Binary files /dev/null and b/materials/models/creeps/thief/thief_hood_color_psd_b9752f2.vtex_c differ diff --git a/materials/models/creeps/thief/thief_hood_normal_tga_3e8d2cb4.vtex_c b/materials/models/creeps/thief/thief_hood_normal_tga_3e8d2cb4.vtex_c new file mode 100644 index 0000000..45db981 Binary files /dev/null and b/materials/models/creeps/thief/thief_hood_normal_tga_3e8d2cb4.vtex_c differ diff --git a/materials/models/creeps/thief/thief_hood_specmask_tga_366db673.vtex_c b/materials/models/creeps/thief/thief_hood_specmask_tga_366db673.vtex_c new file mode 100644 index 0000000..3f751ca Binary files /dev/null and b/materials/models/creeps/thief/thief_hood_specmask_tga_366db673.vtex_c differ diff --git a/materials/models/creeps/thief/thief_hood_vmat_g_tmasks1_3fd6c990.vtex_c b/materials/models/creeps/thief/thief_hood_vmat_g_tmasks1_3fd6c990.vtex_c new file mode 100644 index 0000000..64fa7a1 Binary files /dev/null and b/materials/models/creeps/thief/thief_hood_vmat_g_tmasks1_3fd6c990.vtex_c differ diff --git a/materials/models/creeps/thief/thief_normal_psd_165860c4.vtex_c b/materials/models/creeps/thief/thief_normal_psd_165860c4.vtex_c new file mode 100644 index 0000000..6659fe6 Binary files /dev/null and b/materials/models/creeps/thief/thief_normal_psd_165860c4.vtex_c differ diff --git a/materials/models/creeps/thief/thief_vmat_g_tmasks1_3fd6c990.vtex_c b/materials/models/creeps/thief/thief_vmat_g_tmasks1_3fd6c990.vtex_c new file mode 100644 index 0000000..64fa7a1 Binary files /dev/null and b/materials/models/creeps/thief/thief_vmat_g_tmasks1_3fd6c990.vtex_c differ diff --git a/materials/models/creeps/thief/thiefdagger.vmat_c b/materials/models/creeps/thief/thiefdagger.vmat_c new file mode 100644 index 0000000..4b5ffa3 Binary files /dev/null and b/materials/models/creeps/thief/thiefdagger.vmat_c differ diff --git a/materials/models/creeps/thief/thiefdagger_color_psd_480d0dda.vtex_c b/materials/models/creeps/thief/thiefdagger_color_psd_480d0dda.vtex_c new file mode 100644 index 0000000..7bb8b6f Binary files /dev/null and b/materials/models/creeps/thief/thiefdagger_color_psd_480d0dda.vtex_c differ diff --git a/materials/models/creeps/thief/thiefdagger_normal_tga_75e671bc.vtex_c b/materials/models/creeps/thief/thiefdagger_normal_tga_75e671bc.vtex_c new file mode 100644 index 0000000..692434a Binary files /dev/null and b/materials/models/creeps/thief/thiefdagger_normal_tga_75e671bc.vtex_c differ diff --git a/materials/models/creeps/thief/thiefdagger_selfillummask_tga_ecc4e0d8.vtex_c b/materials/models/creeps/thief/thiefdagger_selfillummask_tga_ecc4e0d8.vtex_c new file mode 100644 index 0000000..d83288d Binary files /dev/null and b/materials/models/creeps/thief/thiefdagger_selfillummask_tga_ecc4e0d8.vtex_c differ diff --git a/materials/models/creeps/thief/thiefdagger_specmask_tga_32b3d5a2.vtex_c b/materials/models/creeps/thief/thiefdagger_specmask_tga_32b3d5a2.vtex_c new file mode 100644 index 0000000..59d78bf Binary files /dev/null and b/materials/models/creeps/thief/thiefdagger_specmask_tga_32b3d5a2.vtex_c differ diff --git a/materials/models/gameplay/meat/134.vmat_c b/materials/models/gameplay/meat/134.vmat_c new file mode 100644 index 0000000..3cd4818 Binary files /dev/null and b/materials/models/gameplay/meat/134.vmat_c differ diff --git a/materials/models/gameplay/meat/134_png_5f06e666.vtex_c b/materials/models/gameplay/meat/134_png_5f06e666.vtex_c new file mode 100644 index 0000000..7add2d7 Binary files /dev/null and b/materials/models/gameplay/meat/134_png_5f06e666.vtex_c differ diff --git a/materials/models/gameplay/meat/meat.vmesh_c b/materials/models/gameplay/meat/meat.vmesh_c new file mode 100644 index 0000000..407faff Binary files /dev/null and b/materials/models/gameplay/meat/meat.vmesh_c differ diff --git a/materials/overviews/invasion.vmat_c b/materials/overviews/invasion.vmat_c new file mode 100644 index 0000000..16b2410 Binary files /dev/null and b/materials/overviews/invasion.vmat_c differ diff --git a/materials/overviews/invasion_tga_1e5faf81.vtex_c b/materials/overviews/invasion_tga_1e5faf81.vtex_c new file mode 100644 index 0000000..8709c5c Binary files /dev/null and b/materials/overviews/invasion_tga_1e5faf81.vtex_c differ diff --git a/materials/overviews/test.vmat_c b/materials/overviews/test.vmat_c new file mode 100644 index 0000000..7b4ea56 Binary files /dev/null and b/materials/overviews/test.vmat_c differ diff --git a/materials/overviews/test_tga_ff89961b.vtex_c b/materials/overviews/test_tga_ff89961b.vtex_c new file mode 100644 index 0000000..dd2adbb Binary files /dev/null and b/materials/overviews/test_tga_ff89961b.vtex_c differ diff --git a/materials/overviews/zombie_invasion.vmat_c b/materials/overviews/zombie_invasion.vmat_c new file mode 100644 index 0000000..b2a3db6 Binary files /dev/null and b/materials/overviews/zombie_invasion.vmat_c differ diff --git a/materials/overviews/zombie_invasion_tga_2622dd27.vtex_c b/materials/overviews/zombie_invasion_tga_2622dd27.vtex_c new file mode 100644 index 0000000..07d6e90 Binary files /dev/null and b/materials/overviews/zombie_invasion_tga_2622dd27.vtex_c differ diff --git a/models/attack_box.vmdl_c b/models/attack_box.vmdl_c new file mode 100644 index 0000000..6c31d39 Binary files /dev/null and b/models/attack_box.vmdl_c differ diff --git a/models/campfire.vmdl_c b/models/campfire.vmdl_c new file mode 100644 index 0000000..bc25af1 Binary files /dev/null and b/models/campfire.vmdl_c differ diff --git a/models/creeps/thief_01.vmdl_c b/models/creeps/thief_01.vmdl_c new file mode 100644 index 0000000..a1f3617 Binary files /dev/null and b/models/creeps/thief_01.vmdl_c differ diff --git a/models/creeps/thief_01/thief_01_model_lod1_vmorf.vtex_c b/models/creeps/thief_01/thief_01_model_lod1_vmorf.vtex_c new file mode 100644 index 0000000..eb6ceba Binary files /dev/null and b/models/creeps/thief_01/thief_01_model_lod1_vmorf.vtex_c differ diff --git a/models/creeps/thief_01/thief_01_model_vmorf.vtex_c b/models/creeps/thief_01/thief_01_model_vmorf.vtex_c new file mode 100644 index 0000000..eb6ceba Binary files /dev/null and b/models/creeps/thief_01/thief_01_model_vmorf.vtex_c differ diff --git a/models/creeps/thief_01_archer.vmdl_c b/models/creeps/thief_01_archer.vmdl_c new file mode 100644 index 0000000..f3bfb9e Binary files /dev/null and b/models/creeps/thief_01_archer.vmdl_c differ diff --git a/models/creeps/thief_01_archer/drow_lod1_model_vmorf.vtex_c b/models/creeps/thief_01_archer/drow_lod1_model_vmorf.vtex_c new file mode 100644 index 0000000..7fabcaa Binary files /dev/null and b/models/creeps/thief_01_archer/drow_lod1_model_vmorf.vtex_c differ diff --git a/models/creeps/thief_01_archer/drow_model_vmorf.vtex_c b/models/creeps/thief_01_archer/drow_model_vmorf.vtex_c new file mode 100644 index 0000000..7fabcaa Binary files /dev/null and b/models/creeps/thief_01_archer/drow_model_vmorf.vtex_c differ diff --git a/models/creeps/thief_01_leader.vmdl_c b/models/creeps/thief_01_leader.vmdl_c new file mode 100644 index 0000000..f310993 Binary files /dev/null and b/models/creeps/thief_01_leader.vmdl_c differ diff --git a/models/creeps/thief_01_leader/thief_01_leader_model_lod1_vmorf.vtex_c b/models/creeps/thief_01_leader/thief_01_leader_model_lod1_vmorf.vtex_c new file mode 100644 index 0000000..86e54bf Binary files /dev/null and b/models/creeps/thief_01_leader/thief_01_leader_model_lod1_vmorf.vtex_c differ diff --git a/models/creeps/thief_01_leader/thief_01_leader_model_vmorf.vtex_c b/models/creeps/thief_01_leader/thief_01_leader_model_vmorf.vtex_c new file mode 100644 index 0000000..86e54bf Binary files /dev/null and b/models/creeps/thief_01_leader/thief_01_leader_model_vmorf.vtex_c differ diff --git a/models/gameplay/meat/6a2a8df7/meatidle.vanim_c b/models/gameplay/meat/6a2a8df7/meatidle.vanim_c new file mode 100644 index 0000000..5b82558 Binary files /dev/null and b/models/gameplay/meat/6a2a8df7/meatidle.vanim_c differ diff --git a/models/gameplay/meat/meat.vmdl_c b/models/gameplay/meat/meat.vmdl_c new file mode 100644 index 0000000..374a6a9 Binary files /dev/null and b/models/gameplay/meat/meat.vmdl_c differ diff --git a/models/gameplay/meat/meat.vmesh_c b/models/gameplay/meat/meat.vmesh_c new file mode 100644 index 0000000..407faff Binary files /dev/null and b/models/gameplay/meat/meat.vmesh_c differ diff --git a/models/gameplay/meat/meat.vmorf_c b/models/gameplay/meat/meat.vmorf_c new file mode 100644 index 0000000..7570c70 Binary files /dev/null and b/models/gameplay/meat/meat.vmorf_c differ diff --git a/models/gameplay/meat/meat_6a2a8df7.vagrp_c b/models/gameplay/meat/meat_6a2a8df7.vagrp_c new file mode 100644 index 0000000..496d4d5 Binary files /dev/null and b/models/gameplay/meat/meat_6a2a8df7.vagrp_c differ diff --git a/models/gameplay/meat/meat_vmorf.vtex_c b/models/gameplay/meat/meat_vmorf.vtex_c new file mode 100644 index 0000000..9910185 Binary files /dev/null and b/models/gameplay/meat/meat_vmorf.vtex_c differ diff --git a/models/meat.vmdl_c b/models/meat.vmdl_c new file mode 100644 index 0000000..c96dc82 Binary files /dev/null and b/models/meat.vmdl_c differ diff --git a/models/sakura_tree.vmdl_c b/models/sakura_tree.vmdl_c new file mode 100644 index 0000000..2f42696 Binary files /dev/null and b/models/sakura_tree.vmdl_c differ diff --git a/models/thief_01.vmdl_c b/models/thief_01.vmdl_c new file mode 100644 index 0000000..e291a79 Binary files /dev/null and b/models/thief_01.vmdl_c differ diff --git a/models/thief_01_archer.vmdl_c b/models/thief_01_archer.vmdl_c new file mode 100644 index 0000000..71ea68f Binary files /dev/null and b/models/thief_01_archer.vmdl_c differ diff --git a/models/thief_01_leader.vmdl_c b/models/thief_01_leader.vmdl_c new file mode 100644 index 0000000..4fd0062 Binary files /dev/null and b/models/thief_01_leader.vmdl_c differ diff --git a/panorama/images/custom_game/arsenal/amberpendant_png.vtex_c b/panorama/images/custom_game/arsenal/amberpendant_png.vtex_c new file mode 100644 index 0000000..c59e070 Binary files /dev/null and b/panorama/images/custom_game/arsenal/amberpendant_png.vtex_c differ diff --git a/panorama/images/custom_game/arsenal/arcanecirclet_png.vtex_c b/panorama/images/custom_game/arsenal/arcanecirclet_png.vtex_c new file mode 100644 index 0000000..07df064 Binary files /dev/null and b/panorama/images/custom_game/arsenal/arcanecirclet_png.vtex_c differ diff --git a/panorama/images/custom_game/arsenal/chainmail_png.vtex_c b/panorama/images/custom_game/arsenal/chainmail_png.vtex_c new file mode 100644 index 0000000..d19d4e5 Binary files /dev/null and b/panorama/images/custom_game/arsenal/chainmail_png.vtex_c differ diff --git a/panorama/images/custom_game/arsenal/copperband_png.vtex_c b/panorama/images/custom_game/arsenal/copperband_png.vtex_c new file mode 100644 index 0000000..6ae2462 Binary files /dev/null and b/panorama/images/custom_game/arsenal/copperband_png.vtex_c differ diff --git a/panorama/images/custom_game/arsenal/dragonplate_png.vtex_c b/panorama/images/custom_game/arsenal/dragonplate_png.vtex_c new file mode 100644 index 0000000..54abe66 Binary files /dev/null and b/panorama/images/custom_game/arsenal/dragonplate_png.vtex_c differ diff --git a/panorama/images/custom_game/arsenal/hermesgreaves_png.vtex_c b/panorama/images/custom_game/arsenal/hermesgreaves_png.vtex_c new file mode 100644 index 0000000..e6089f0 Binary files /dev/null and b/panorama/images/custom_game/arsenal/hermesgreaves_png.vtex_c differ diff --git a/panorama/images/custom_game/arsenal/ironblade_png.vtex_c b/panorama/images/custom_game/arsenal/ironblade_png.vtex_c new file mode 100644 index 0000000..19db097 Binary files /dev/null and b/panorama/images/custom_game/arsenal/ironblade_png.vtex_c differ diff --git a/panorama/images/custom_game/arsenal/leathercap_png.vtex_c b/panorama/images/custom_game/arsenal/leathercap_png.vtex_c new file mode 100644 index 0000000..ea41af1 Binary files /dev/null and b/panorama/images/custom_game/arsenal/leathercap_png.vtex_c differ diff --git a/panorama/images/custom_game/arsenal/phasetreads_png.vtex_c b/panorama/images/custom_game/arsenal/phasetreads_png.vtex_c new file mode 100644 index 0000000..c011d6a Binary files /dev/null and b/panorama/images/custom_game/arsenal/phasetreads_png.vtex_c differ diff --git a/panorama/images/custom_game/arsenal/prismchoker_png.vtex_c b/panorama/images/custom_game/arsenal/prismchoker_png.vtex_c new file mode 100644 index 0000000..98147ef Binary files /dev/null and b/panorama/images/custom_game/arsenal/prismchoker_png.vtex_c differ diff --git a/panorama/images/custom_game/arsenal/soullocket_png.vtex_c b/panorama/images/custom_game/arsenal/soullocket_png.vtex_c new file mode 100644 index 0000000..a55cbe7 Binary files /dev/null and b/panorama/images/custom_game/arsenal/soullocket_png.vtex_c differ diff --git a/panorama/images/custom_game/arsenal/stormedge_png.vtex_c b/panorama/images/custom_game/arsenal/stormedge_png.vtex_c new file mode 100644 index 0000000..d34c832 Binary files /dev/null and b/panorama/images/custom_game/arsenal/stormedge_png.vtex_c differ diff --git a/panorama/images/custom_game/arsenal/sunfangsaber_png.vtex_c b/panorama/images/custom_game/arsenal/sunfangsaber_png.vtex_c new file mode 100644 index 0000000..32fbc20 Binary files /dev/null and b/panorama/images/custom_game/arsenal/sunfangsaber_png.vtex_c differ diff --git a/panorama/images/custom_game/arsenal/swiftboots_png.vtex_c b/panorama/images/custom_game/arsenal/swiftboots_png.vtex_c new file mode 100644 index 0000000..af1b195 Binary files /dev/null and b/panorama/images/custom_game/arsenal/swiftboots_png.vtex_c differ diff --git a/panorama/images/custom_game/arsenal/titancircle_png.vtex_c b/panorama/images/custom_game/arsenal/titancircle_png.vtex_c new file mode 100644 index 0000000..e865b70 Binary files /dev/null and b/panorama/images/custom_game/arsenal/titancircle_png.vtex_c differ diff --git a/panorama/images/custom_game/arsenal/vanguardshell_png.vtex_c b/panorama/images/custom_game/arsenal/vanguardshell_png.vtex_c new file mode 100644 index 0000000..29e6a57 Binary files /dev/null and b/panorama/images/custom_game/arsenal/vanguardshell_png.vtex_c differ diff --git a/panorama/images/custom_game/arsenal/voidsignet_png.vtex_c b/panorama/images/custom_game/arsenal/voidsignet_png.vtex_c new file mode 100644 index 0000000..f760ac4 Binary files /dev/null and b/panorama/images/custom_game/arsenal/voidsignet_png.vtex_c differ diff --git a/panorama/images/custom_game/arsenal/warcrownmask_png.vtex_c b/panorama/images/custom_game/arsenal/warcrownmask_png.vtex_c new file mode 100644 index 0000000..63b5b41 Binary files /dev/null and b/panorama/images/custom_game/arsenal/warcrownmask_png.vtex_c differ diff --git a/panorama/images/custom_game/bg/bg_congrat_png.vtex_c b/panorama/images/custom_game/bg/bg_congrat_png.vtex_c new file mode 100644 index 0000000..6e01c5a Binary files /dev/null and b/panorama/images/custom_game/bg/bg_congrat_png.vtex_c differ diff --git a/panorama/images/custom_game/bg/card_pack_1_png.vtex_c b/panorama/images/custom_game/bg/card_pack_1_png.vtex_c new file mode 100644 index 0000000..c27b8bb Binary files /dev/null and b/panorama/images/custom_game/bg/card_pack_1_png.vtex_c differ diff --git a/panorama/images/custom_game/bg/card_pack_2_png.vtex_c b/panorama/images/custom_game/bg/card_pack_2_png.vtex_c new file mode 100644 index 0000000..70f0991 Binary files /dev/null and b/panorama/images/custom_game/bg/card_pack_2_png.vtex_c differ diff --git a/panorama/images/custom_game/bg/cook_bg_png.vtex_c b/panorama/images/custom_game/bg/cook_bg_png.vtex_c new file mode 100644 index 0000000..b30f311 Binary files /dev/null and b/panorama/images/custom_game/bg/cook_bg_png.vtex_c differ diff --git a/panorama/images/custom_game/bg/drow_png.vtex_c b/panorama/images/custom_game/bg/drow_png.vtex_c new file mode 100644 index 0000000..be52b12 Binary files /dev/null and b/panorama/images/custom_game/bg/drow_png.vtex_c differ diff --git a/panorama/images/custom_game/bg/hoodwink_png.vtex_c b/panorama/images/custom_game/bg/hoodwink_png.vtex_c new file mode 100644 index 0000000..5d6c9df Binary files /dev/null and b/panorama/images/custom_game/bg/hoodwink_png.vtex_c differ diff --git a/panorama/images/custom_game/bg/invasion_shop_png.vtex_c b/panorama/images/custom_game/bg/invasion_shop_png.vtex_c new file mode 100644 index 0000000..215f99b Binary files /dev/null and b/panorama/images/custom_game/bg/invasion_shop_png.vtex_c differ diff --git a/panorama/images/custom_game/bg/qop_png.vtex_c b/panorama/images/custom_game/bg/qop_png.vtex_c new file mode 100644 index 0000000..5a5e49a Binary files /dev/null and b/panorama/images/custom_game/bg/qop_png.vtex_c differ diff --git a/panorama/images/custom_game/cards/card_10_png.vtex_c b/panorama/images/custom_game/cards/card_10_png.vtex_c new file mode 100644 index 0000000..bc06f26 Binary files /dev/null and b/panorama/images/custom_game/cards/card_10_png.vtex_c differ diff --git a/panorama/images/custom_game/cards/card_11_png.vtex_c b/panorama/images/custom_game/cards/card_11_png.vtex_c new file mode 100644 index 0000000..4983b83 Binary files /dev/null and b/panorama/images/custom_game/cards/card_11_png.vtex_c differ diff --git a/panorama/images/custom_game/cards/card_12_png.vtex_c b/panorama/images/custom_game/cards/card_12_png.vtex_c new file mode 100644 index 0000000..8b85b2c Binary files /dev/null and b/panorama/images/custom_game/cards/card_12_png.vtex_c differ diff --git a/panorama/images/custom_game/cards/card_13_png.vtex_c b/panorama/images/custom_game/cards/card_13_png.vtex_c new file mode 100644 index 0000000..a40210d Binary files /dev/null and b/panorama/images/custom_game/cards/card_13_png.vtex_c differ diff --git a/panorama/images/custom_game/cards/card_14_png.vtex_c b/panorama/images/custom_game/cards/card_14_png.vtex_c new file mode 100644 index 0000000..16c6a9c Binary files /dev/null and b/panorama/images/custom_game/cards/card_14_png.vtex_c differ diff --git a/panorama/images/custom_game/cards/card_15_png.vtex_c b/panorama/images/custom_game/cards/card_15_png.vtex_c new file mode 100644 index 0000000..48f6ce8 Binary files /dev/null and b/panorama/images/custom_game/cards/card_15_png.vtex_c differ diff --git a/panorama/images/custom_game/cards/card_16_png.vtex_c b/panorama/images/custom_game/cards/card_16_png.vtex_c new file mode 100644 index 0000000..d406cb9 Binary files /dev/null and b/panorama/images/custom_game/cards/card_16_png.vtex_c differ diff --git a/panorama/images/custom_game/cards/card_17_png.vtex_c b/panorama/images/custom_game/cards/card_17_png.vtex_c new file mode 100644 index 0000000..f96adf2 Binary files /dev/null and b/panorama/images/custom_game/cards/card_17_png.vtex_c differ diff --git a/panorama/images/custom_game/cards/card_18_png.vtex_c b/panorama/images/custom_game/cards/card_18_png.vtex_c new file mode 100644 index 0000000..fcd6865 Binary files /dev/null and b/panorama/images/custom_game/cards/card_18_png.vtex_c differ diff --git a/panorama/images/custom_game/cards/card_19_png.vtex_c b/panorama/images/custom_game/cards/card_19_png.vtex_c new file mode 100644 index 0000000..1e2dafb Binary files /dev/null and b/panorama/images/custom_game/cards/card_19_png.vtex_c differ diff --git a/panorama/images/custom_game/cards/card_1_png.vtex_c b/panorama/images/custom_game/cards/card_1_png.vtex_c new file mode 100644 index 0000000..932f2ed Binary files /dev/null and b/panorama/images/custom_game/cards/card_1_png.vtex_c differ diff --git a/panorama/images/custom_game/cards/card_20_png.vtex_c b/panorama/images/custom_game/cards/card_20_png.vtex_c new file mode 100644 index 0000000..02ec236 Binary files /dev/null and b/panorama/images/custom_game/cards/card_20_png.vtex_c differ diff --git a/panorama/images/custom_game/cards/card_21_png.vtex_c b/panorama/images/custom_game/cards/card_21_png.vtex_c new file mode 100644 index 0000000..aedd0e0 Binary files /dev/null and b/panorama/images/custom_game/cards/card_21_png.vtex_c differ diff --git a/panorama/images/custom_game/cards/card_22_png.vtex_c b/panorama/images/custom_game/cards/card_22_png.vtex_c new file mode 100644 index 0000000..008b811 Binary files /dev/null and b/panorama/images/custom_game/cards/card_22_png.vtex_c differ diff --git a/panorama/images/custom_game/cards/card_23_png.vtex_c b/panorama/images/custom_game/cards/card_23_png.vtex_c new file mode 100644 index 0000000..5fc78c2 Binary files /dev/null and b/panorama/images/custom_game/cards/card_23_png.vtex_c differ diff --git a/panorama/images/custom_game/cards/card_24_png.vtex_c b/panorama/images/custom_game/cards/card_24_png.vtex_c new file mode 100644 index 0000000..d65c9e5 Binary files /dev/null and b/panorama/images/custom_game/cards/card_24_png.vtex_c differ diff --git a/panorama/images/custom_game/cards/card_25_png.vtex_c b/panorama/images/custom_game/cards/card_25_png.vtex_c new file mode 100644 index 0000000..e4b7561 Binary files /dev/null and b/panorama/images/custom_game/cards/card_25_png.vtex_c differ diff --git a/panorama/images/custom_game/cards/card_26_png.vtex_c b/panorama/images/custom_game/cards/card_26_png.vtex_c new file mode 100644 index 0000000..2fd4264 Binary files /dev/null and b/panorama/images/custom_game/cards/card_26_png.vtex_c differ diff --git a/panorama/images/custom_game/cards/card_27_png.vtex_c b/panorama/images/custom_game/cards/card_27_png.vtex_c new file mode 100644 index 0000000..e675172 Binary files /dev/null and b/panorama/images/custom_game/cards/card_27_png.vtex_c differ diff --git a/panorama/images/custom_game/cards/card_28_png.vtex_c b/panorama/images/custom_game/cards/card_28_png.vtex_c new file mode 100644 index 0000000..85c8259 Binary files /dev/null and b/panorama/images/custom_game/cards/card_28_png.vtex_c differ diff --git a/panorama/images/custom_game/cards/card_29_png.vtex_c b/panorama/images/custom_game/cards/card_29_png.vtex_c new file mode 100644 index 0000000..2526736 Binary files /dev/null and b/panorama/images/custom_game/cards/card_29_png.vtex_c differ diff --git a/panorama/images/custom_game/cards/card_2_png.vtex_c b/panorama/images/custom_game/cards/card_2_png.vtex_c new file mode 100644 index 0000000..df91599 Binary files /dev/null and b/panorama/images/custom_game/cards/card_2_png.vtex_c differ diff --git a/panorama/images/custom_game/cards/card_30_png.vtex_c b/panorama/images/custom_game/cards/card_30_png.vtex_c new file mode 100644 index 0000000..f5a4e3f Binary files /dev/null and b/panorama/images/custom_game/cards/card_30_png.vtex_c differ diff --git a/panorama/images/custom_game/cards/card_31_png.vtex_c b/panorama/images/custom_game/cards/card_31_png.vtex_c new file mode 100644 index 0000000..4c4d03f Binary files /dev/null and b/panorama/images/custom_game/cards/card_31_png.vtex_c differ diff --git a/panorama/images/custom_game/cards/card_32_png.vtex_c b/panorama/images/custom_game/cards/card_32_png.vtex_c new file mode 100644 index 0000000..88c9e52 Binary files /dev/null and b/panorama/images/custom_game/cards/card_32_png.vtex_c differ diff --git a/panorama/images/custom_game/cards/card_33_png.vtex_c b/panorama/images/custom_game/cards/card_33_png.vtex_c new file mode 100644 index 0000000..683bbf8 Binary files /dev/null and b/panorama/images/custom_game/cards/card_33_png.vtex_c differ diff --git a/panorama/images/custom_game/cards/card_34_png.vtex_c b/panorama/images/custom_game/cards/card_34_png.vtex_c new file mode 100644 index 0000000..6bec997 Binary files /dev/null and b/panorama/images/custom_game/cards/card_34_png.vtex_c differ diff --git a/panorama/images/custom_game/cards/card_35_png.vtex_c b/panorama/images/custom_game/cards/card_35_png.vtex_c new file mode 100644 index 0000000..f986aa9 Binary files /dev/null and b/panorama/images/custom_game/cards/card_35_png.vtex_c differ diff --git a/panorama/images/custom_game/cards/card_36_png.vtex_c b/panorama/images/custom_game/cards/card_36_png.vtex_c new file mode 100644 index 0000000..02abfe9 Binary files /dev/null and b/panorama/images/custom_game/cards/card_36_png.vtex_c differ diff --git a/panorama/images/custom_game/cards/card_37_png.vtex_c b/panorama/images/custom_game/cards/card_37_png.vtex_c new file mode 100644 index 0000000..8d71a4c Binary files /dev/null and b/panorama/images/custom_game/cards/card_37_png.vtex_c differ diff --git a/panorama/images/custom_game/cards/card_38_png.vtex_c b/panorama/images/custom_game/cards/card_38_png.vtex_c new file mode 100644 index 0000000..391cf5c Binary files /dev/null and b/panorama/images/custom_game/cards/card_38_png.vtex_c differ diff --git a/panorama/images/custom_game/cards/card_39_png.vtex_c b/panorama/images/custom_game/cards/card_39_png.vtex_c new file mode 100644 index 0000000..99c36da Binary files /dev/null and b/panorama/images/custom_game/cards/card_39_png.vtex_c differ diff --git a/panorama/images/custom_game/cards/card_3_png.vtex_c b/panorama/images/custom_game/cards/card_3_png.vtex_c new file mode 100644 index 0000000..4dc9714 Binary files /dev/null and b/panorama/images/custom_game/cards/card_3_png.vtex_c differ diff --git a/panorama/images/custom_game/cards/card_404_png.vtex_c b/panorama/images/custom_game/cards/card_404_png.vtex_c new file mode 100644 index 0000000..1b684e9 Binary files /dev/null and b/panorama/images/custom_game/cards/card_404_png.vtex_c differ diff --git a/panorama/images/custom_game/cards/card_40_png.vtex_c b/panorama/images/custom_game/cards/card_40_png.vtex_c new file mode 100644 index 0000000..dc32ce6 Binary files /dev/null and b/panorama/images/custom_game/cards/card_40_png.vtex_c differ diff --git a/panorama/images/custom_game/cards/card_41_png.vtex_c b/panorama/images/custom_game/cards/card_41_png.vtex_c new file mode 100644 index 0000000..6afd70e Binary files /dev/null and b/panorama/images/custom_game/cards/card_41_png.vtex_c differ diff --git a/panorama/images/custom_game/cards/card_42_png.vtex_c b/panorama/images/custom_game/cards/card_42_png.vtex_c new file mode 100644 index 0000000..f368148 Binary files /dev/null and b/panorama/images/custom_game/cards/card_42_png.vtex_c differ diff --git a/panorama/images/custom_game/cards/card_43_png.vtex_c b/panorama/images/custom_game/cards/card_43_png.vtex_c new file mode 100644 index 0000000..4f8fb73 Binary files /dev/null and b/panorama/images/custom_game/cards/card_43_png.vtex_c differ diff --git a/panorama/images/custom_game/cards/card_44_png.vtex_c b/panorama/images/custom_game/cards/card_44_png.vtex_c new file mode 100644 index 0000000..b0cf46f Binary files /dev/null and b/panorama/images/custom_game/cards/card_44_png.vtex_c differ diff --git a/panorama/images/custom_game/cards/card_45_png.vtex_c b/panorama/images/custom_game/cards/card_45_png.vtex_c new file mode 100644 index 0000000..18638fd Binary files /dev/null and b/panorama/images/custom_game/cards/card_45_png.vtex_c differ diff --git a/panorama/images/custom_game/cards/card_46_png.vtex_c b/panorama/images/custom_game/cards/card_46_png.vtex_c new file mode 100644 index 0000000..9c5ba03 Binary files /dev/null and b/panorama/images/custom_game/cards/card_46_png.vtex_c differ diff --git a/panorama/images/custom_game/cards/card_47_png.vtex_c b/panorama/images/custom_game/cards/card_47_png.vtex_c new file mode 100644 index 0000000..ba5c96b Binary files /dev/null and b/panorama/images/custom_game/cards/card_47_png.vtex_c differ diff --git a/panorama/images/custom_game/cards/card_48_png.vtex_c b/panorama/images/custom_game/cards/card_48_png.vtex_c new file mode 100644 index 0000000..9f2ecc4 Binary files /dev/null and b/panorama/images/custom_game/cards/card_48_png.vtex_c differ diff --git a/panorama/images/custom_game/cards/card_49_png.vtex_c b/panorama/images/custom_game/cards/card_49_png.vtex_c new file mode 100644 index 0000000..bbe33b4 Binary files /dev/null and b/panorama/images/custom_game/cards/card_49_png.vtex_c differ diff --git a/panorama/images/custom_game/cards/card_4_png.vtex_c b/panorama/images/custom_game/cards/card_4_png.vtex_c new file mode 100644 index 0000000..e681736 Binary files /dev/null and b/panorama/images/custom_game/cards/card_4_png.vtex_c differ diff --git a/panorama/images/custom_game/cards/card_50_png.vtex_c b/panorama/images/custom_game/cards/card_50_png.vtex_c new file mode 100644 index 0000000..4144e2e Binary files /dev/null and b/panorama/images/custom_game/cards/card_50_png.vtex_c differ diff --git a/panorama/images/custom_game/cards/card_51_png.vtex_c b/panorama/images/custom_game/cards/card_51_png.vtex_c new file mode 100644 index 0000000..6c4c85d Binary files /dev/null and b/panorama/images/custom_game/cards/card_51_png.vtex_c differ diff --git a/panorama/images/custom_game/cards/card_52_png.vtex_c b/panorama/images/custom_game/cards/card_52_png.vtex_c new file mode 100644 index 0000000..7bf3890 Binary files /dev/null and b/panorama/images/custom_game/cards/card_52_png.vtex_c differ diff --git a/panorama/images/custom_game/cards/card_53_png.vtex_c b/panorama/images/custom_game/cards/card_53_png.vtex_c new file mode 100644 index 0000000..fcb5049 Binary files /dev/null and b/panorama/images/custom_game/cards/card_53_png.vtex_c differ diff --git a/panorama/images/custom_game/cards/card_54_png.vtex_c b/panorama/images/custom_game/cards/card_54_png.vtex_c new file mode 100644 index 0000000..b92432e Binary files /dev/null and b/panorama/images/custom_game/cards/card_54_png.vtex_c differ diff --git a/panorama/images/custom_game/cards/card_55_png.vtex_c b/panorama/images/custom_game/cards/card_55_png.vtex_c new file mode 100644 index 0000000..fc6bde8 Binary files /dev/null and b/panorama/images/custom_game/cards/card_55_png.vtex_c differ diff --git a/panorama/images/custom_game/cards/card_56_png.vtex_c b/panorama/images/custom_game/cards/card_56_png.vtex_c new file mode 100644 index 0000000..866f80d Binary files /dev/null and b/panorama/images/custom_game/cards/card_56_png.vtex_c differ diff --git a/panorama/images/custom_game/cards/card_57_png.vtex_c b/panorama/images/custom_game/cards/card_57_png.vtex_c new file mode 100644 index 0000000..345a63c Binary files /dev/null and b/panorama/images/custom_game/cards/card_57_png.vtex_c differ diff --git a/panorama/images/custom_game/cards/card_58_png.vtex_c b/panorama/images/custom_game/cards/card_58_png.vtex_c new file mode 100644 index 0000000..f1dc6e8 Binary files /dev/null and b/panorama/images/custom_game/cards/card_58_png.vtex_c differ diff --git a/panorama/images/custom_game/cards/card_59_png.vtex_c b/panorama/images/custom_game/cards/card_59_png.vtex_c new file mode 100644 index 0000000..0e43c72 Binary files /dev/null and b/panorama/images/custom_game/cards/card_59_png.vtex_c differ diff --git a/panorama/images/custom_game/cards/card_5_png.vtex_c b/panorama/images/custom_game/cards/card_5_png.vtex_c new file mode 100644 index 0000000..8809829 Binary files /dev/null and b/panorama/images/custom_game/cards/card_5_png.vtex_c differ diff --git a/panorama/images/custom_game/cards/card_60_png.vtex_c b/panorama/images/custom_game/cards/card_60_png.vtex_c new file mode 100644 index 0000000..0220c25 Binary files /dev/null and b/panorama/images/custom_game/cards/card_60_png.vtex_c differ diff --git a/panorama/images/custom_game/cards/card_61_png.vtex_c b/panorama/images/custom_game/cards/card_61_png.vtex_c new file mode 100644 index 0000000..c6ff5ba Binary files /dev/null and b/panorama/images/custom_game/cards/card_61_png.vtex_c differ diff --git a/panorama/images/custom_game/cards/card_62_png.vtex_c b/panorama/images/custom_game/cards/card_62_png.vtex_c new file mode 100644 index 0000000..3c858b2 Binary files /dev/null and b/panorama/images/custom_game/cards/card_62_png.vtex_c differ diff --git a/panorama/images/custom_game/cards/card_63_png.vtex_c b/panorama/images/custom_game/cards/card_63_png.vtex_c new file mode 100644 index 0000000..9c25948 Binary files /dev/null and b/panorama/images/custom_game/cards/card_63_png.vtex_c differ diff --git a/panorama/images/custom_game/cards/card_64_png.vtex_c b/panorama/images/custom_game/cards/card_64_png.vtex_c new file mode 100644 index 0000000..5fc7d6a Binary files /dev/null and b/panorama/images/custom_game/cards/card_64_png.vtex_c differ diff --git a/panorama/images/custom_game/cards/card_65_png.vtex_c b/panorama/images/custom_game/cards/card_65_png.vtex_c new file mode 100644 index 0000000..7a0783c Binary files /dev/null and b/panorama/images/custom_game/cards/card_65_png.vtex_c differ diff --git a/panorama/images/custom_game/cards/card_66_png.vtex_c b/panorama/images/custom_game/cards/card_66_png.vtex_c new file mode 100644 index 0000000..30d8c31 Binary files /dev/null and b/panorama/images/custom_game/cards/card_66_png.vtex_c differ diff --git a/panorama/images/custom_game/cards/card_67_png.vtex_c b/panorama/images/custom_game/cards/card_67_png.vtex_c new file mode 100644 index 0000000..8c461e0 Binary files /dev/null and b/panorama/images/custom_game/cards/card_67_png.vtex_c differ diff --git a/panorama/images/custom_game/cards/card_68_png.vtex_c b/panorama/images/custom_game/cards/card_68_png.vtex_c new file mode 100644 index 0000000..58152f0 Binary files /dev/null and b/panorama/images/custom_game/cards/card_68_png.vtex_c differ diff --git a/panorama/images/custom_game/cards/card_69_png.vtex_c b/panorama/images/custom_game/cards/card_69_png.vtex_c new file mode 100644 index 0000000..e05630d Binary files /dev/null and b/panorama/images/custom_game/cards/card_69_png.vtex_c differ diff --git a/panorama/images/custom_game/cards/card_6_png.vtex_c b/panorama/images/custom_game/cards/card_6_png.vtex_c new file mode 100644 index 0000000..dddb6ae Binary files /dev/null and b/panorama/images/custom_game/cards/card_6_png.vtex_c differ diff --git a/panorama/images/custom_game/cards/card_70_png.vtex_c b/panorama/images/custom_game/cards/card_70_png.vtex_c new file mode 100644 index 0000000..5f1be3b Binary files /dev/null and b/panorama/images/custom_game/cards/card_70_png.vtex_c differ diff --git a/panorama/images/custom_game/cards/card_71_png.vtex_c b/panorama/images/custom_game/cards/card_71_png.vtex_c new file mode 100644 index 0000000..f997898 Binary files /dev/null and b/panorama/images/custom_game/cards/card_71_png.vtex_c differ diff --git a/panorama/images/custom_game/cards/card_72_png.vtex_c b/panorama/images/custom_game/cards/card_72_png.vtex_c new file mode 100644 index 0000000..429ebba Binary files /dev/null and b/panorama/images/custom_game/cards/card_72_png.vtex_c differ diff --git a/panorama/images/custom_game/cards/card_73_png.vtex_c b/panorama/images/custom_game/cards/card_73_png.vtex_c new file mode 100644 index 0000000..a3b619a Binary files /dev/null and b/panorama/images/custom_game/cards/card_73_png.vtex_c differ diff --git a/panorama/images/custom_game/cards/card_74_png.vtex_c b/panorama/images/custom_game/cards/card_74_png.vtex_c new file mode 100644 index 0000000..3fd6185 Binary files /dev/null and b/panorama/images/custom_game/cards/card_74_png.vtex_c differ diff --git a/panorama/images/custom_game/cards/card_75_png.vtex_c b/panorama/images/custom_game/cards/card_75_png.vtex_c new file mode 100644 index 0000000..b8e8a97 Binary files /dev/null and b/panorama/images/custom_game/cards/card_75_png.vtex_c differ diff --git a/panorama/images/custom_game/cards/card_76_png.vtex_c b/panorama/images/custom_game/cards/card_76_png.vtex_c new file mode 100644 index 0000000..49539c7 Binary files /dev/null and b/panorama/images/custom_game/cards/card_76_png.vtex_c differ diff --git a/panorama/images/custom_game/cards/card_77_png.vtex_c b/panorama/images/custom_game/cards/card_77_png.vtex_c new file mode 100644 index 0000000..846e2f9 Binary files /dev/null and b/panorama/images/custom_game/cards/card_77_png.vtex_c differ diff --git a/panorama/images/custom_game/cards/card_78_png.vtex_c b/panorama/images/custom_game/cards/card_78_png.vtex_c new file mode 100644 index 0000000..8f1e4b7 Binary files /dev/null and b/panorama/images/custom_game/cards/card_78_png.vtex_c differ diff --git a/panorama/images/custom_game/cards/card_79_png.vtex_c b/panorama/images/custom_game/cards/card_79_png.vtex_c new file mode 100644 index 0000000..ccb2166 Binary files /dev/null and b/panorama/images/custom_game/cards/card_79_png.vtex_c differ diff --git a/panorama/images/custom_game/cards/card_7_png.vtex_c b/panorama/images/custom_game/cards/card_7_png.vtex_c new file mode 100644 index 0000000..9c0d045 Binary files /dev/null and b/panorama/images/custom_game/cards/card_7_png.vtex_c differ diff --git a/panorama/images/custom_game/cards/card_80_png.vtex_c b/panorama/images/custom_game/cards/card_80_png.vtex_c new file mode 100644 index 0000000..fd39261 Binary files /dev/null and b/panorama/images/custom_game/cards/card_80_png.vtex_c differ diff --git a/panorama/images/custom_game/cards/card_81_png.vtex_c b/panorama/images/custom_game/cards/card_81_png.vtex_c new file mode 100644 index 0000000..4eb3e4d Binary files /dev/null and b/panorama/images/custom_game/cards/card_81_png.vtex_c differ diff --git a/panorama/images/custom_game/cards/card_82_png.vtex_c b/panorama/images/custom_game/cards/card_82_png.vtex_c new file mode 100644 index 0000000..8626510 Binary files /dev/null and b/panorama/images/custom_game/cards/card_82_png.vtex_c differ diff --git a/panorama/images/custom_game/cards/card_83_png.vtex_c b/panorama/images/custom_game/cards/card_83_png.vtex_c new file mode 100644 index 0000000..dd841e0 Binary files /dev/null and b/panorama/images/custom_game/cards/card_83_png.vtex_c differ diff --git a/panorama/images/custom_game/cards/card_84_png.vtex_c b/panorama/images/custom_game/cards/card_84_png.vtex_c new file mode 100644 index 0000000..9e67b64 Binary files /dev/null and b/panorama/images/custom_game/cards/card_84_png.vtex_c differ diff --git a/panorama/images/custom_game/cards/card_85_png.vtex_c b/panorama/images/custom_game/cards/card_85_png.vtex_c new file mode 100644 index 0000000..1f24ef4 Binary files /dev/null and b/panorama/images/custom_game/cards/card_85_png.vtex_c differ diff --git a/panorama/images/custom_game/cards/card_86_png.vtex_c b/panorama/images/custom_game/cards/card_86_png.vtex_c new file mode 100644 index 0000000..a3236a0 Binary files /dev/null and b/panorama/images/custom_game/cards/card_86_png.vtex_c differ diff --git a/panorama/images/custom_game/cards/card_87_png.vtex_c b/panorama/images/custom_game/cards/card_87_png.vtex_c new file mode 100644 index 0000000..93a2c34 Binary files /dev/null and b/panorama/images/custom_game/cards/card_87_png.vtex_c differ diff --git a/panorama/images/custom_game/cards/card_88_png.vtex_c b/panorama/images/custom_game/cards/card_88_png.vtex_c new file mode 100644 index 0000000..e173c82 Binary files /dev/null and b/panorama/images/custom_game/cards/card_88_png.vtex_c differ diff --git a/panorama/images/custom_game/cards/card_8_png.vtex_c b/panorama/images/custom_game/cards/card_8_png.vtex_c new file mode 100644 index 0000000..458ab45 Binary files /dev/null and b/panorama/images/custom_game/cards/card_8_png.vtex_c differ diff --git a/panorama/images/custom_game/cards/card_9_png.vtex_c b/panorama/images/custom_game/cards/card_9_png.vtex_c new file mode 100644 index 0000000..34606db Binary files /dev/null and b/panorama/images/custom_game/cards/card_9_png.vtex_c differ diff --git a/panorama/images/custom_game/cat_shnapy.webm b/panorama/images/custom_game/cat_shnapy.webm new file mode 100644 index 0000000..5e70341 Binary files /dev/null and b/panorama/images/custom_game/cat_shnapy.webm differ diff --git a/panorama/images/custom_game/cm.webm b/panorama/images/custom_game/cm.webm new file mode 100644 index 0000000..8a66211 Binary files /dev/null and b/panorama/images/custom_game/cm.webm differ diff --git a/panorama/images/custom_game/eblo_razraba.webm b/panorama/images/custom_game/eblo_razraba.webm new file mode 100644 index 0000000..deea433 Binary files /dev/null and b/panorama/images/custom_game/eblo_razraba.webm differ diff --git a/panorama/images/custom_game/items/agility_cape_png.vtex_c b/panorama/images/custom_game/items/agility_cape_png.vtex_c new file mode 100644 index 0000000..4d10f5c Binary files /dev/null and b/panorama/images/custom_game/items/agility_cape_png.vtex_c differ diff --git a/panorama/images/custom_game/items/armlet2_off_png.vtex_c b/panorama/images/custom_game/items/armlet2_off_png.vtex_c new file mode 100644 index 0000000..1d93cf0 Binary files /dev/null and b/panorama/images/custom_game/items/armlet2_off_png.vtex_c differ diff --git a/panorama/images/custom_game/items/armlet2_png.vtex_c b/panorama/images/custom_game/items/armlet2_png.vtex_c new file mode 100644 index 0000000..dc9a120 Binary files /dev/null and b/panorama/images/custom_game/items/armlet2_png.vtex_c differ diff --git a/panorama/images/custom_game/items/bearstbow_1_png.vtex_c b/panorama/images/custom_game/items/bearstbow_1_png.vtex_c new file mode 100644 index 0000000..2aeaadf Binary files /dev/null and b/panorama/images/custom_game/items/bearstbow_1_png.vtex_c differ diff --git a/panorama/images/custom_game/items/bearstbow_2_png.vtex_c b/panorama/images/custom_game/items/bearstbow_2_png.vtex_c new file mode 100644 index 0000000..5833ca8 Binary files /dev/null and b/panorama/images/custom_game/items/bearstbow_2_png.vtex_c differ diff --git a/panorama/images/custom_game/items/bloodstone2_png.vtex_c b/panorama/images/custom_game/items/bloodstone2_png.vtex_c new file mode 100644 index 0000000..53fcf20 Binary files /dev/null and b/panorama/images/custom_game/items/bloodstone2_png.vtex_c differ diff --git a/panorama/images/custom_game/items/boo_stuff_png.vtex_c b/panorama/images/custom_game/items/boo_stuff_png.vtex_c new file mode 100644 index 0000000..b2ab065 Binary files /dev/null and b/panorama/images/custom_game/items/boo_stuff_png.vtex_c differ diff --git a/panorama/images/custom_game/items/book_agi_png.vtex_c b/panorama/images/custom_game/items/book_agi_png.vtex_c new file mode 100644 index 0000000..bccfaf9 Binary files /dev/null and b/panorama/images/custom_game/items/book_agi_png.vtex_c differ diff --git a/panorama/images/custom_game/items/book_int_png.vtex_c b/panorama/images/custom_game/items/book_int_png.vtex_c new file mode 100644 index 0000000..0cfadac Binary files /dev/null and b/panorama/images/custom_game/items/book_int_png.vtex_c differ diff --git a/panorama/images/custom_game/items/book_str_png.vtex_c b/panorama/images/custom_game/items/book_str_png.vtex_c new file mode 100644 index 0000000..938539f Binary files /dev/null and b/panorama/images/custom_game/items/book_str_png.vtex_c differ diff --git a/panorama/images/custom_game/items/candy_png.vtex_c b/panorama/images/custom_game/items/candy_png.vtex_c new file mode 100644 index 0000000..0731b98 Binary files /dev/null and b/panorama/images/custom_game/items/candy_png.vtex_c differ diff --git a/panorama/images/custom_game/items/critical_havoc_png.vtex_c b/panorama/images/custom_game/items/critical_havoc_png.vtex_c new file mode 100644 index 0000000..d0513da Binary files /dev/null and b/panorama/images/custom_game/items/critical_havoc_png.vtex_c differ diff --git a/panorama/images/custom_game/items/crown_png.vtex_c b/panorama/images/custom_game/items/crown_png.vtex_c new file mode 100644 index 0000000..234013d Binary files /dev/null and b/panorama/images/custom_game/items/crown_png.vtex_c differ diff --git a/panorama/images/custom_game/items/damage_dagger_png.vtex_c b/panorama/images/custom_game/items/damage_dagger_png.vtex_c new file mode 100644 index 0000000..9104f1d Binary files /dev/null and b/panorama/images/custom_game/items/damage_dagger_png.vtex_c differ diff --git a/panorama/images/custom_game/items/dickpier_png.vtex_c b/panorama/images/custom_game/items/dickpier_png.vtex_c new file mode 100644 index 0000000..48cd4fe Binary files /dev/null and b/panorama/images/custom_game/items/dickpier_png.vtex_c differ diff --git a/panorama/images/custom_game/items/egg_of_death_png.vtex_c b/panorama/images/custom_game/items/egg_of_death_png.vtex_c new file mode 100644 index 0000000..91b3d5a Binary files /dev/null and b/panorama/images/custom_game/items/egg_of_death_png.vtex_c differ diff --git a/panorama/images/custom_game/items/eggs_png.vtex_c b/panorama/images/custom_game/items/eggs_png.vtex_c new file mode 100644 index 0000000..17620b2 Binary files /dev/null and b/panorama/images/custom_game/items/eggs_png.vtex_c differ diff --git a/panorama/images/custom_game/items/ent_heart_png.vtex_c b/panorama/images/custom_game/items/ent_heart_png.vtex_c new file mode 100644 index 0000000..e795ce5 Binary files /dev/null and b/panorama/images/custom_game/items/ent_heart_png.vtex_c differ diff --git a/panorama/images/custom_game/items/fire_cape_png.vtex_c b/panorama/images/custom_game/items/fire_cape_png.vtex_c new file mode 100644 index 0000000..cbbd9be Binary files /dev/null and b/panorama/images/custom_game/items/fire_cape_png.vtex_c differ diff --git a/panorama/images/custom_game/items/golden_bag_png.vtex_c b/panorama/images/custom_game/items/golden_bag_png.vtex_c new file mode 100644 index 0000000..b8e6c15 Binary files /dev/null and b/panorama/images/custom_game/items/golden_bag_png.vtex_c differ diff --git a/panorama/images/custom_game/items/ice_spine_png.vtex_c b/panorama/images/custom_game/items/ice_spine_png.vtex_c new file mode 100644 index 0000000..78f4de9 Binary files /dev/null and b/panorama/images/custom_game/items/ice_spine_png.vtex_c differ diff --git a/panorama/images/custom_game/items/item_mega_treads_0_png.vtex_c b/panorama/images/custom_game/items/item_mega_treads_0_png.vtex_c new file mode 100644 index 0000000..8ec9e5a Binary files /dev/null and b/panorama/images/custom_game/items/item_mega_treads_0_png.vtex_c differ diff --git a/panorama/images/custom_game/items/item_mega_treads_1_png.vtex_c b/panorama/images/custom_game/items/item_mega_treads_1_png.vtex_c new file mode 100644 index 0000000..b24b2f7 Binary files /dev/null and b/panorama/images/custom_game/items/item_mega_treads_1_png.vtex_c differ diff --git a/panorama/images/custom_game/items/item_mega_treads_2_png.vtex_c b/panorama/images/custom_game/items/item_mega_treads_2_png.vtex_c new file mode 100644 index 0000000..91ae7c4 Binary files /dev/null and b/panorama/images/custom_game/items/item_mega_treads_2_png.vtex_c differ diff --git a/panorama/images/custom_game/items/kunkka_sword_png.vtex_c b/panorama/images/custom_game/items/kunkka_sword_png.vtex_c new file mode 100644 index 0000000..4d17c9b Binary files /dev/null and b/panorama/images/custom_game/items/kunkka_sword_png.vtex_c differ diff --git a/panorama/images/custom_game/items/lycan_horn_png.vtex_c b/panorama/images/custom_game/items/lycan_horn_png.vtex_c new file mode 100644 index 0000000..9b5dc36 Binary files /dev/null and b/panorama/images/custom_game/items/lycan_horn_png.vtex_c differ diff --git a/panorama/images/custom_game/items/magic_mushroom_png.vtex_c b/panorama/images/custom_game/items/magic_mushroom_png.vtex_c new file mode 100644 index 0000000..8039020 Binary files /dev/null and b/panorama/images/custom_game/items/magic_mushroom_png.vtex_c differ diff --git a/panorama/images/custom_game/items/magicpier_png.vtex_c b/panorama/images/custom_game/items/magicpier_png.vtex_c new file mode 100644 index 0000000..3ee4a56 Binary files /dev/null and b/panorama/images/custom_game/items/magicpier_png.vtex_c differ diff --git a/panorama/images/custom_game/items/magicrapier_png.vtex_c b/panorama/images/custom_game/items/magicrapier_png.vtex_c new file mode 100644 index 0000000..9b63536 Binary files /dev/null and b/panorama/images/custom_game/items/magicrapier_png.vtex_c differ diff --git a/panorama/images/custom_game/items/manaflare_png.vtex_c b/panorama/images/custom_game/items/manaflare_png.vtex_c new file mode 100644 index 0000000..368f36c Binary files /dev/null and b/panorama/images/custom_game/items/manaflare_png.vtex_c differ diff --git a/panorama/images/custom_game/items/meat_png.vtex_c b/panorama/images/custom_game/items/meat_png.vtex_c new file mode 100644 index 0000000..542b552 Binary files /dev/null and b/panorama/images/custom_game/items/meat_png.vtex_c differ diff --git a/panorama/images/custom_game/items/medkit_png.vtex_c b/panorama/images/custom_game/items/medkit_png.vtex_c new file mode 100644 index 0000000..1838219 Binary files /dev/null and b/panorama/images/custom_game/items/medkit_png.vtex_c differ diff --git a/panorama/images/custom_game/items/mega_fury_png.vtex_c b/panorama/images/custom_game/items/mega_fury_png.vtex_c new file mode 100644 index 0000000..8d57a5e Binary files /dev/null and b/panorama/images/custom_game/items/mega_fury_png.vtex_c differ diff --git a/panorama/images/custom_game/items/milk_png.vtex_c b/panorama/images/custom_game/items/milk_png.vtex_c new file mode 100644 index 0000000..479d519 Binary files /dev/null and b/panorama/images/custom_game/items/milk_png.vtex_c differ diff --git a/panorama/images/custom_game/items/mini_bfury_png.vtex_c b/panorama/images/custom_game/items/mini_bfury_png.vtex_c new file mode 100644 index 0000000..2326859 Binary files /dev/null and b/panorama/images/custom_game/items/mini_bfury_png.vtex_c differ diff --git a/panorama/images/custom_game/items/oldmen_amulet_png.vtex_c b/panorama/images/custom_game/items/oldmen_amulet_png.vtex_c new file mode 100644 index 0000000..9acd1b7 Binary files /dev/null and b/panorama/images/custom_game/items/oldmen_amulet_png.vtex_c differ diff --git a/panorama/images/custom_game/items/orb_of_fire_png.vtex_c b/panorama/images/custom_game/items/orb_of_fire_png.vtex_c new file mode 100644 index 0000000..eef88fb Binary files /dev/null and b/panorama/images/custom_game/items/orb_of_fire_png.vtex_c differ diff --git a/panorama/images/custom_game/items/pet_png.vtex_c b/panorama/images/custom_game/items/pet_png.vtex_c new file mode 100644 index 0000000..f9bf5ed Binary files /dev/null and b/panorama/images/custom_game/items/pet_png.vtex_c differ diff --git a/panorama/images/custom_game/items/reset_to_zero_png.vtex_c b/panorama/images/custom_game/items/reset_to_zero_png.vtex_c new file mode 100644 index 0000000..a6f9198 Binary files /dev/null and b/panorama/images/custom_game/items/reset_to_zero_png.vtex_c differ diff --git a/panorama/images/custom_game/items/restock_png.vtex_c b/panorama/images/custom_game/items/restock_png.vtex_c new file mode 100644 index 0000000..b315dc4 Binary files /dev/null and b/panorama/images/custom_game/items/restock_png.vtex_c differ diff --git a/panorama/images/custom_game/items/rofl_for_kaban_pumba_png.vtex_c b/panorama/images/custom_game/items/rofl_for_kaban_pumba_png.vtex_c new file mode 100644 index 0000000..0a9fc18 Binary files /dev/null and b/panorama/images/custom_game/items/rofl_for_kaban_pumba_png.vtex_c differ diff --git a/panorama/images/custom_game/items/rom_png.vtex_c b/panorama/images/custom_game/items/rom_png.vtex_c new file mode 100644 index 0000000..8449e63 Binary files /dev/null and b/panorama/images/custom_game/items/rom_png.vtex_c differ diff --git a/panorama/images/custom_game/items/silver_eye_png.vtex_c b/panorama/images/custom_game/items/silver_eye_png.vtex_c new file mode 100644 index 0000000..7fa3cea Binary files /dev/null and b/panorama/images/custom_game/items/silver_eye_png.vtex_c differ diff --git a/panorama/images/custom_game/items/soldier_png.vtex_c b/panorama/images/custom_game/items/soldier_png.vtex_c new file mode 100644 index 0000000..6b5bc3c Binary files /dev/null and b/panorama/images/custom_game/items/soldier_png.vtex_c differ diff --git a/panorama/images/custom_game/items/soul_devourer_staff_png.vtex_c b/panorama/images/custom_game/items/soul_devourer_staff_png.vtex_c new file mode 100644 index 0000000..410673a Binary files /dev/null and b/panorama/images/custom_game/items/soul_devourer_staff_png.vtex_c differ diff --git a/panorama/images/custom_game/items/spell_mask_png.vtex_c b/panorama/images/custom_game/items/spell_mask_png.vtex_c new file mode 100644 index 0000000..703bf10 Binary files /dev/null and b/panorama/images/custom_game/items/spell_mask_png.vtex_c differ diff --git a/panorama/images/custom_game/items/stone_armor_png.vtex_c b/panorama/images/custom_game/items/stone_armor_png.vtex_c new file mode 100644 index 0000000..640713f Binary files /dev/null and b/panorama/images/custom_game/items/stone_armor_png.vtex_c differ diff --git a/panorama/images/custom_game/items/storm_png.vtex_c b/panorama/images/custom_game/items/storm_png.vtex_c new file mode 100644 index 0000000..a892499 Binary files /dev/null and b/panorama/images/custom_game/items/storm_png.vtex_c differ diff --git a/panorama/images/custom_game/items/the_hand_of_gluttony_png.vtex_c b/panorama/images/custom_game/items/the_hand_of_gluttony_png.vtex_c new file mode 100644 index 0000000..ffeca6d Binary files /dev/null and b/panorama/images/custom_game/items/the_hand_of_gluttony_png.vtex_c differ diff --git a/panorama/images/custom_game/items/vampire_claw_1_png.vtex_c b/panorama/images/custom_game/items/vampire_claw_1_png.vtex_c new file mode 100644 index 0000000..a1ea86f Binary files /dev/null and b/panorama/images/custom_game/items/vampire_claw_1_png.vtex_c differ diff --git a/panorama/images/custom_game/items/vampire_claw_2_png.vtex_c b/panorama/images/custom_game/items/vampire_claw_2_png.vtex_c new file mode 100644 index 0000000..adc75a1 Binary files /dev/null and b/panorama/images/custom_game/items/vampire_claw_2_png.vtex_c differ diff --git a/panorama/images/custom_game/items/vampire_claw_3_png.vtex_c b/panorama/images/custom_game/items/vampire_claw_3_png.vtex_c new file mode 100644 index 0000000..332a921 Binary files /dev/null and b/panorama/images/custom_game/items/vampire_claw_3_png.vtex_c differ diff --git a/panorama/images/custom_game/items/vampire_claw_4_png.vtex_c b/panorama/images/custom_game/items/vampire_claw_4_png.vtex_c new file mode 100644 index 0000000..80bbc93 Binary files /dev/null and b/panorama/images/custom_game/items/vampire_claw_4_png.vtex_c differ diff --git a/panorama/images/custom_game/items/wolf_claw_png.vtex_c b/panorama/images/custom_game/items/wolf_claw_png.vtex_c new file mode 100644 index 0000000..43476c0 Binary files /dev/null and b/panorama/images/custom_game/items/wolf_claw_png.vtex_c differ diff --git a/panorama/images/custom_game/items/zombie_slayer_png.vtex_c b/panorama/images/custom_game/items/zombie_slayer_png.vtex_c new file mode 100644 index 0000000..8b88bf9 Binary files /dev/null and b/panorama/images/custom_game/items/zombie_slayer_png.vtex_c differ diff --git a/panorama/images/custom_game/jump.webm b/panorama/images/custom_game/jump.webm new file mode 100644 index 0000000..de88a53 Binary files /dev/null and b/panorama/images/custom_game/jump.webm differ diff --git a/panorama/images/custom_game/kitty_flex.webm b/panorama/images/custom_game/kitty_flex.webm new file mode 100644 index 0000000..77a419f Binary files /dev/null and b/panorama/images/custom_game/kitty_flex.webm differ diff --git a/panorama/images/custom_game/loading_screen/loadscreen.vsvg_c b/panorama/images/custom_game/loading_screen/loadscreen.vsvg_c new file mode 100644 index 0000000..30b30ee Binary files /dev/null and b/panorama/images/custom_game/loading_screen/loadscreen.vsvg_c differ diff --git a/panorama/images/custom_game/loading_screen/loadscreen_16_9_png.vtex_c b/panorama/images/custom_game/loading_screen/loadscreen_16_9_png.vtex_c new file mode 100644 index 0000000..a77c49d Binary files /dev/null and b/panorama/images/custom_game/loading_screen/loadscreen_16_9_png.vtex_c differ diff --git a/panorama/images/custom_game/loading_screen/loadscreen_21_9_png.vtex_c b/panorama/images/custom_game/loading_screen/loadscreen_21_9_png.vtex_c new file mode 100644 index 0000000..4ff3e16 Binary files /dev/null and b/panorama/images/custom_game/loading_screen/loadscreen_21_9_png.vtex_c differ diff --git a/panorama/images/custom_game/loading_screen/loadscreen_new_16_9_png.vtex_c b/panorama/images/custom_game/loading_screen/loadscreen_new_16_9_png.vtex_c new file mode 100644 index 0000000..59ec932 Binary files /dev/null and b/panorama/images/custom_game/loading_screen/loadscreen_new_16_9_png.vtex_c differ diff --git a/panorama/images/custom_game/loading_screen/loadscreen_new_21_9_png.vtex_c b/panorama/images/custom_game/loading_screen/loadscreen_new_21_9_png.vtex_c new file mode 100644 index 0000000..acd6d1a Binary files /dev/null and b/panorama/images/custom_game/loading_screen/loadscreen_new_21_9_png.vtex_c differ diff --git a/panorama/images/custom_game/loading_screen/loadscreenold_png.vtex_c b/panorama/images/custom_game/loading_screen/loadscreenold_png.vtex_c new file mode 100644 index 0000000..83dc10f Binary files /dev/null and b/panorama/images/custom_game/loading_screen/loadscreenold_png.vtex_c differ diff --git a/panorama/images/custom_game/ne_ponimaiu_karina_strimersha_slozhno_slozhno.webm b/panorama/images/custom_game/ne_ponimaiu_karina_strimersha_slozhno_slozhno.webm new file mode 100644 index 0000000..dca2c57 Binary files /dev/null and b/panorama/images/custom_game/ne_ponimaiu_karina_strimersha_slozhno_slozhno.webm differ diff --git a/panorama/images/custom_game/oi_tak_nravitsa.webm b/panorama/images/custom_game/oi_tak_nravitsa.webm new file mode 100644 index 0000000..3b6d2a8 Binary files /dev/null and b/panorama/images/custom_game/oi_tak_nravitsa.webm differ diff --git a/panorama/images/custom_game/social_link/boosty_png.vtex_c b/panorama/images/custom_game/social_link/boosty_png.vtex_c new file mode 100644 index 0000000..ce8a022 Binary files /dev/null and b/panorama/images/custom_game/social_link/boosty_png.vtex_c differ diff --git a/panorama/images/custom_game/social_link/discord_png.vtex_c b/panorama/images/custom_game/social_link/discord_png.vtex_c new file mode 100644 index 0000000..c0f7467 Binary files /dev/null and b/panorama/images/custom_game/social_link/discord_png.vtex_c differ diff --git a/panorama/images/custom_game/zaika.webm b/panorama/images/custom_game/zaika.webm new file mode 100644 index 0000000..e2c7cf9 Binary files /dev/null and b/panorama/images/custom_game/zaika.webm differ diff --git a/panorama/images/heroes/alternative_abilities/crystal_maiden_brilliance_aura_alt_png.vtex_c b/panorama/images/heroes/alternative_abilities/crystal_maiden_brilliance_aura_alt_png.vtex_c new file mode 100644 index 0000000..7c2d311 Binary files /dev/null and b/panorama/images/heroes/alternative_abilities/crystal_maiden_brilliance_aura_alt_png.vtex_c differ diff --git a/panorama/images/heroes/alternative_abilities/crystal_maiden_brilliance_aura_png.vtex_c b/panorama/images/heroes/alternative_abilities/crystal_maiden_brilliance_aura_png.vtex_c new file mode 100644 index 0000000..d6319cf Binary files /dev/null and b/panorama/images/heroes/alternative_abilities/crystal_maiden_brilliance_aura_png.vtex_c differ diff --git a/panorama/images/heroes/alternative_abilities/crystal_maiden_frostbite_alt_png.vtex_c b/panorama/images/heroes/alternative_abilities/crystal_maiden_frostbite_alt_png.vtex_c new file mode 100644 index 0000000..4b69c5c Binary files /dev/null and b/panorama/images/heroes/alternative_abilities/crystal_maiden_frostbite_alt_png.vtex_c differ diff --git a/panorama/images/heroes/alternative_abilities/crystal_maiden_frostbite_png.vtex_c b/panorama/images/heroes/alternative_abilities/crystal_maiden_frostbite_png.vtex_c new file mode 100644 index 0000000..bfd947d Binary files /dev/null and b/panorama/images/heroes/alternative_abilities/crystal_maiden_frostbite_png.vtex_c differ diff --git a/panorama/images/heroes/alternative_abilities/juggernaut_blade_dance_alt_png.vtex_c b/panorama/images/heroes/alternative_abilities/juggernaut_blade_dance_alt_png.vtex_c new file mode 100644 index 0000000..ee2c5cd Binary files /dev/null and b/panorama/images/heroes/alternative_abilities/juggernaut_blade_dance_alt_png.vtex_c differ diff --git a/panorama/images/heroes/alternative_abilities/juggernaut_blade_dance_png.vtex_c b/panorama/images/heroes/alternative_abilities/juggernaut_blade_dance_png.vtex_c new file mode 100644 index 0000000..89aaeb1 Binary files /dev/null and b/panorama/images/heroes/alternative_abilities/juggernaut_blade_dance_png.vtex_c differ diff --git a/panorama/images/heroes/alternative_abilities/juggernaut_blade_fury_alt_png.vtex_c b/panorama/images/heroes/alternative_abilities/juggernaut_blade_fury_alt_png.vtex_c new file mode 100644 index 0000000..5962271 Binary files /dev/null and b/panorama/images/heroes/alternative_abilities/juggernaut_blade_fury_alt_png.vtex_c differ diff --git a/panorama/images/heroes/alternative_abilities/juggernaut_blade_fury_png.vtex_c b/panorama/images/heroes/alternative_abilities/juggernaut_blade_fury_png.vtex_c new file mode 100644 index 0000000..ee9957d Binary files /dev/null and b/panorama/images/heroes/alternative_abilities/juggernaut_blade_fury_png.vtex_c differ diff --git a/panorama/images/heroes/alternative_abilities/templar_assassin_meld_alt_png.vtex_c b/panorama/images/heroes/alternative_abilities/templar_assassin_meld_alt_png.vtex_c new file mode 100644 index 0000000..23ed8d2 Binary files /dev/null and b/panorama/images/heroes/alternative_abilities/templar_assassin_meld_alt_png.vtex_c differ diff --git a/panorama/images/heroes/alternative_abilities/templar_assassin_meld_png.vtex_c b/panorama/images/heroes/alternative_abilities/templar_assassin_meld_png.vtex_c new file mode 100644 index 0000000..78a7a04 Binary files /dev/null and b/panorama/images/heroes/alternative_abilities/templar_assassin_meld_png.vtex_c differ diff --git a/panorama/images/heroes/hero_portrait/npc_dota_hero_bloodhunter_png.vtex_c b/panorama/images/heroes/hero_portrait/npc_dota_hero_bloodhunter_png.vtex_c new file mode 100644 index 0000000..65b2881 Binary files /dev/null and b/panorama/images/heroes/hero_portrait/npc_dota_hero_bloodhunter_png.vtex_c differ diff --git a/panorama/images/heroes/hero_portrait/npc_dota_hero_elder_dragon_smaug_png.vtex_c b/panorama/images/heroes/hero_portrait/npc_dota_hero_elder_dragon_smaug_png.vtex_c new file mode 100644 index 0000000..6e1a95e Binary files /dev/null and b/panorama/images/heroes/hero_portrait/npc_dota_hero_elder_dragon_smaug_png.vtex_c differ diff --git a/panorama/images/heroes/hero_portrait/npc_dota_hero_nagash_png.vtex_c b/panorama/images/heroes/hero_portrait/npc_dota_hero_nagash_png.vtex_c new file mode 100644 index 0000000..045ccee Binary files /dev/null and b/panorama/images/heroes/hero_portrait/npc_dota_hero_nagash_png.vtex_c differ diff --git a/panorama/images/heroes/hero_portrait/npc_dota_hero_sargatanas_png.vtex_c b/panorama/images/heroes/hero_portrait/npc_dota_hero_sargatanas_png.vtex_c new file mode 100644 index 0000000..344a231 Binary files /dev/null and b/panorama/images/heroes/hero_portrait/npc_dota_hero_sargatanas_png.vtex_c differ diff --git a/panorama/images/heroes/hero_portrait/npc_dota_hero_yuki_onna_png.vtex_c b/panorama/images/heroes/hero_portrait/npc_dota_hero_yuki_onna_png.vtex_c new file mode 100644 index 0000000..cf1be80 Binary files /dev/null and b/panorama/images/heroes/hero_portrait/npc_dota_hero_yuki_onna_png.vtex_c differ diff --git a/panorama/images/heroes/npc_dota_hero_bloodhunter_png.vtex_c b/panorama/images/heroes/npc_dota_hero_bloodhunter_png.vtex_c new file mode 100644 index 0000000..c966c7b Binary files /dev/null and b/panorama/images/heroes/npc_dota_hero_bloodhunter_png.vtex_c differ diff --git a/panorama/images/heroes/npc_dota_hero_elder_dragon_smaug_png.vtex_c b/panorama/images/heroes/npc_dota_hero_elder_dragon_smaug_png.vtex_c new file mode 100644 index 0000000..b5a5837 Binary files /dev/null and b/panorama/images/heroes/npc_dota_hero_elder_dragon_smaug_png.vtex_c differ diff --git a/panorama/images/heroes/npc_dota_hero_nagash_png.vtex_c b/panorama/images/heroes/npc_dota_hero_nagash_png.vtex_c new file mode 100644 index 0000000..acf50c6 Binary files /dev/null and b/panorama/images/heroes/npc_dota_hero_nagash_png.vtex_c differ diff --git a/panorama/images/heroes/npc_dota_hero_sargatanas_png.vtex_c b/panorama/images/heroes/npc_dota_hero_sargatanas_png.vtex_c new file mode 100644 index 0000000..02945d8 Binary files /dev/null and b/panorama/images/heroes/npc_dota_hero_sargatanas_png.vtex_c differ diff --git a/panorama/images/heroes/npc_dota_hero_yuki_onna_png.vtex_c b/panorama/images/heroes/npc_dota_hero_yuki_onna_png.vtex_c new file mode 100644 index 0000000..8c28a73 Binary files /dev/null and b/panorama/images/heroes/npc_dota_hero_yuki_onna_png.vtex_c differ diff --git a/panorama/layout/custom_game/admin_menu.vxml_c b/panorama/layout/custom_game/admin_menu.vxml_c new file mode 100644 index 0000000..c56e95c Binary files /dev/null and b/panorama/layout/custom_game/admin_menu.vxml_c differ diff --git a/panorama/layout/custom_game/arsenal.vxml_c b/panorama/layout/custom_game/arsenal.vxml_c new file mode 100644 index 0000000..08af731 Binary files /dev/null and b/panorama/layout/custom_game/arsenal.vxml_c differ diff --git a/panorama/layout/custom_game/arsenal_item_tooltip.vxml_c b/panorama/layout/custom_game/arsenal_item_tooltip.vxml_c new file mode 100644 index 0000000..bbeea12 Binary files /dev/null and b/panorama/layout/custom_game/arsenal_item_tooltip.vxml_c differ diff --git a/panorama/layout/custom_game/battle_pass.vxml_c b/panorama/layout/custom_game/battle_pass.vxml_c new file mode 100644 index 0000000..2ea336b Binary files /dev/null and b/panorama/layout/custom_game/battle_pass.vxml_c differ diff --git a/panorama/layout/custom_game/black_shop.vxml_c b/panorama/layout/custom_game/black_shop.vxml_c new file mode 100644 index 0000000..fd81f9e Binary files /dev/null and b/panorama/layout/custom_game/black_shop.vxml_c differ diff --git a/panorama/layout/custom_game/boss_health_bar.vxml_c b/panorama/layout/custom_game/boss_health_bar.vxml_c new file mode 100644 index 0000000..2ca1ff8 Binary files /dev/null and b/panorama/layout/custom_game/boss_health_bar.vxml_c differ diff --git a/panorama/layout/custom_game/card_selection.vxml_c b/panorama/layout/custom_game/card_selection.vxml_c new file mode 100644 index 0000000..dcc3ab7 Binary files /dev/null and b/panorama/layout/custom_game/card_selection.vxml_c differ diff --git a/panorama/layout/custom_game/chat_wheel.vxml_c b/panorama/layout/custom_game/chat_wheel.vxml_c new file mode 100644 index 0000000..dce257b Binary files /dev/null and b/panorama/layout/custom_game/chat_wheel.vxml_c differ diff --git a/panorama/layout/custom_game/contracts_inventory.vxml_c b/panorama/layout/custom_game/contracts_inventory.vxml_c new file mode 100644 index 0000000..7fd986e Binary files /dev/null and b/panorama/layout/custom_game/contracts_inventory.vxml_c differ diff --git a/panorama/layout/custom_game/cooking_panel.vxml_c b/panorama/layout/custom_game/cooking_panel.vxml_c new file mode 100644 index 0000000..0a6fb05 Binary files /dev/null and b/panorama/layout/custom_game/cooking_panel.vxml_c differ diff --git a/panorama/layout/custom_game/crystal_display.vxml_c b/panorama/layout/custom_game/crystal_display.vxml_c new file mode 100644 index 0000000..d666bbe Binary files /dev/null and b/panorama/layout/custom_game/crystal_display.vxml_c differ diff --git a/panorama/layout/custom_game/custom_chat.vxml_c b/panorama/layout/custom_game/custom_chat.vxml_c new file mode 100644 index 0000000..d598620 Binary files /dev/null and b/panorama/layout/custom_game/custom_chat.vxml_c differ diff --git a/panorama/layout/custom_game/custom_loading_screen.vxml_c b/panorama/layout/custom_game/custom_loading_screen.vxml_c new file mode 100644 index 0000000..34bbf1c Binary files /dev/null and b/panorama/layout/custom_game/custom_loading_screen.vxml_c differ diff --git a/panorama/layout/custom_game/custom_ui_manifest.vxml_c b/panorama/layout/custom_game/custom_ui_manifest.vxml_c new file mode 100644 index 0000000..32652e1 Binary files /dev/null and b/panorama/layout/custom_game/custom_ui_manifest.vxml_c differ diff --git a/panorama/layout/custom_game/cutscenes/cutscene_main.vxml_c b/panorama/layout/custom_game/cutscenes/cutscene_main.vxml_c new file mode 100644 index 0000000..26cd172 Binary files /dev/null and b/panorama/layout/custom_game/cutscenes/cutscene_main.vxml_c differ diff --git a/panorama/layout/custom_game/death_sentence_contracts.vxml_c b/panorama/layout/custom_game/death_sentence_contracts.vxml_c new file mode 100644 index 0000000..f5d0ba7 Binary files /dev/null and b/panorama/layout/custom_game/death_sentence_contracts.vxml_c differ diff --git a/panorama/layout/custom_game/deck_builder.vxml_c b/panorama/layout/custom_game/deck_builder.vxml_c new file mode 100644 index 0000000..94c8037 Binary files /dev/null and b/panorama/layout/custom_game/deck_builder.vxml_c differ diff --git a/panorama/layout/custom_game/difficulty_select.vxml_c b/panorama/layout/custom_game/difficulty_select.vxml_c new file mode 100644 index 0000000..4abffe7 Binary files /dev/null and b/panorama/layout/custom_game/difficulty_select.vxml_c differ diff --git a/panorama/layout/custom_game/dps_display.vxml_c b/panorama/layout/custom_game/dps_display.vxml_c new file mode 100644 index 0000000..b238e22 Binary files /dev/null and b/panorama/layout/custom_game/dps_display.vxml_c differ diff --git a/panorama/layout/custom_game/end_game_summary.vxml_c b/panorama/layout/custom_game/end_game_summary.vxml_c new file mode 100644 index 0000000..1d308b1 Binary files /dev/null and b/panorama/layout/custom_game/end_game_summary.vxml_c differ diff --git a/panorama/layout/custom_game/equipment.vxml_c b/panorama/layout/custom_game/equipment.vxml_c new file mode 100644 index 0000000..bd77f75 Binary files /dev/null and b/panorama/layout/custom_game/equipment.vxml_c differ diff --git a/panorama/layout/custom_game/error_hud.vxml_c b/panorama/layout/custom_game/error_hud.vxml_c new file mode 100644 index 0000000..75e2b86 Binary files /dev/null and b/panorama/layout/custom_game/error_hud.vxml_c differ diff --git a/panorama/layout/custom_game/event_notification.vxml_c b/panorama/layout/custom_game/event_notification.vxml_c new file mode 100644 index 0000000..3d6d07d Binary files /dev/null and b/panorama/layout/custom_game/event_notification.vxml_c differ diff --git a/panorama/layout/custom_game/hero_icon_replacer.vxml_c b/panorama/layout/custom_game/hero_icon_replacer.vxml_c new file mode 100644 index 0000000..efd3011 Binary files /dev/null and b/panorama/layout/custom_game/hero_icon_replacer.vxml_c differ diff --git a/panorama/layout/custom_game/hero_inspect.vxml_c b/panorama/layout/custom_game/hero_inspect.vxml_c new file mode 100644 index 0000000..f6eb74c Binary files /dev/null and b/panorama/layout/custom_game/hero_inspect.vxml_c differ diff --git a/panorama/layout/custom_game/hero_rank_tooltip.vxml_c b/panorama/layout/custom_game/hero_rank_tooltip.vxml_c new file mode 100644 index 0000000..74980cc Binary files /dev/null and b/panorama/layout/custom_game/hero_rank_tooltip.vxml_c differ diff --git a/panorama/layout/custom_game/hero_selection_timer.vxml_c b/panorama/layout/custom_game/hero_selection_timer.vxml_c new file mode 100644 index 0000000..abe277c Binary files /dev/null and b/panorama/layout/custom_game/hero_selection_timer.vxml_c differ diff --git a/panorama/layout/custom_game/hero_selection_topbar.vxml_c b/panorama/layout/custom_game/hero_selection_topbar.vxml_c new file mode 100644 index 0000000..9b2752e Binary files /dev/null and b/panorama/layout/custom_game/hero_selection_topbar.vxml_c differ diff --git a/panorama/layout/custom_game/hud.vxml_c b/panorama/layout/custom_game/hud.vxml_c new file mode 100644 index 0000000..b9de69e Binary files /dev/null and b/panorama/layout/custom_game/hud.vxml_c differ diff --git a/panorama/layout/custom_game/leaderboard.vxml_c b/panorama/layout/custom_game/leaderboard.vxml_c new file mode 100644 index 0000000..9b88d1a Binary files /dev/null and b/panorama/layout/custom_game/leaderboard.vxml_c differ diff --git a/panorama/layout/custom_game/marketplace_item_tooltip.vxml_c b/panorama/layout/custom_game/marketplace_item_tooltip.vxml_c new file mode 100644 index 0000000..f5b24f0 Binary files /dev/null and b/panorama/layout/custom_game/marketplace_item_tooltip.vxml_c differ diff --git a/panorama/layout/custom_game/marketplace_item_tooltip_details.vxml_c b/panorama/layout/custom_game/marketplace_item_tooltip_details.vxml_c new file mode 100644 index 0000000..b0549ff Binary files /dev/null and b/panorama/layout/custom_game/marketplace_item_tooltip_details.vxml_c differ diff --git a/panorama/layout/custom_game/match_end_screen.vxml_c b/panorama/layout/custom_game/match_end_screen.vxml_c new file mode 100644 index 0000000..6fc7f8a Binary files /dev/null and b/panorama/layout/custom_game/match_end_screen.vxml_c differ diff --git a/panorama/layout/custom_game/mini_profile.vxml_c b/panorama/layout/custom_game/mini_profile.vxml_c new file mode 100644 index 0000000..3e03eb9 Binary files /dev/null and b/panorama/layout/custom_game/mini_profile.vxml_c differ diff --git a/panorama/layout/custom_game/nearby_item_notification.vxml_c b/panorama/layout/custom_game/nearby_item_notification.vxml_c new file mode 100644 index 0000000..b277100 Binary files /dev/null and b/panorama/layout/custom_game/nearby_item_notification.vxml_c differ diff --git a/panorama/layout/custom_game/news_feed.vxml_c b/panorama/layout/custom_game/news_feed.vxml_c new file mode 100644 index 0000000..0dd9146 Binary files /dev/null and b/panorama/layout/custom_game/news_feed.vxml_c differ diff --git a/panorama/layout/custom_game/player_tip.vxml_c b/panorama/layout/custom_game/player_tip.vxml_c new file mode 100644 index 0000000..3feb415 Binary files /dev/null and b/panorama/layout/custom_game/player_tip.vxml_c differ diff --git a/panorama/layout/custom_game/profile.vxml_c b/panorama/layout/custom_game/profile.vxml_c new file mode 100644 index 0000000..7b84763 Binary files /dev/null and b/panorama/layout/custom_game/profile.vxml_c differ diff --git a/panorama/layout/custom_game/progress_bar/rage_progress_bar.vxml_c b/panorama/layout/custom_game/progress_bar/rage_progress_bar.vxml_c new file mode 100644 index 0000000..71baf6d Binary files /dev/null and b/panorama/layout/custom_game/progress_bar/rage_progress_bar.vxml_c differ diff --git a/panorama/layout/custom_game/quests.vxml_c b/panorama/layout/custom_game/quests.vxml_c new file mode 100644 index 0000000..a58deaf Binary files /dev/null and b/panorama/layout/custom_game/quests.vxml_c differ diff --git a/panorama/layout/custom_game/rage_hud.vxml_c b/panorama/layout/custom_game/rage_hud.vxml_c new file mode 100644 index 0000000..fa55e35 Binary files /dev/null and b/panorama/layout/custom_game/rage_hud.vxml_c differ diff --git a/panorama/layout/custom_game/rage_hud_block.vxml_c b/panorama/layout/custom_game/rage_hud_block.vxml_c new file mode 100644 index 0000000..27ce47a Binary files /dev/null and b/panorama/layout/custom_game/rage_hud_block.vxml_c differ diff --git a/panorama/layout/custom_game/raid_boss_rewards.vxml_c b/panorama/layout/custom_game/raid_boss_rewards.vxml_c new file mode 100644 index 0000000..6317862 Binary files /dev/null and b/panorama/layout/custom_game/raid_boss_rewards.vxml_c differ diff --git a/panorama/layout/custom_game/setup_status.vxml_c b/panorama/layout/custom_game/setup_status.vxml_c new file mode 100644 index 0000000..73388f3 Binary files /dev/null and b/panorama/layout/custom_game/setup_status.vxml_c differ diff --git a/panorama/layout/custom_game/social_links.vxml_c b/panorama/layout/custom_game/social_links.vxml_c new file mode 100644 index 0000000..4feb0fb Binary files /dev/null and b/panorama/layout/custom_game/social_links.vxml_c differ diff --git a/panorama/layout/custom_game/store.vxml_c b/panorama/layout/custom_game/store.vxml_c new file mode 100644 index 0000000..655bb0b Binary files /dev/null and b/panorama/layout/custom_game/store.vxml_c differ diff --git a/panorama/scripts/custom_game/admin_menu.vjs_c b/panorama/scripts/custom_game/admin_menu.vjs_c new file mode 100644 index 0000000..2294417 Binary files /dev/null and b/panorama/scripts/custom_game/admin_menu.vjs_c differ diff --git a/panorama/scripts/custom_game/arsenal.vjs_c b/panorama/scripts/custom_game/arsenal.vjs_c new file mode 100644 index 0000000..fb8a521 Binary files /dev/null and b/panorama/scripts/custom_game/arsenal.vjs_c differ diff --git a/panorama/scripts/custom_game/arsenal_item_tooltip.vjs_c b/panorama/scripts/custom_game/arsenal_item_tooltip.vjs_c new file mode 100644 index 0000000..93a71c4 Binary files /dev/null and b/panorama/scripts/custom_game/arsenal_item_tooltip.vjs_c differ diff --git a/panorama/scripts/custom_game/battle_pass.vjs_c b/panorama/scripts/custom_game/battle_pass.vjs_c new file mode 100644 index 0000000..b45a842 Binary files /dev/null and b/panorama/scripts/custom_game/battle_pass.vjs_c differ diff --git a/panorama/scripts/custom_game/black_shop.vjs_c b/panorama/scripts/custom_game/black_shop.vjs_c new file mode 100644 index 0000000..b689af2 Binary files /dev/null and b/panorama/scripts/custom_game/black_shop.vjs_c differ diff --git a/panorama/scripts/custom_game/boss_health_bar.vjs_c b/panorama/scripts/custom_game/boss_health_bar.vjs_c new file mode 100644 index 0000000..e8d7884 Binary files /dev/null and b/panorama/scripts/custom_game/boss_health_bar.vjs_c differ diff --git a/panorama/scripts/custom_game/card_description_helpers.vjs_c b/panorama/scripts/custom_game/card_description_helpers.vjs_c new file mode 100644 index 0000000..b90c81f Binary files /dev/null and b/panorama/scripts/custom_game/card_description_helpers.vjs_c differ diff --git a/panorama/scripts/custom_game/card_selection.vjs_c b/panorama/scripts/custom_game/card_selection.vjs_c new file mode 100644 index 0000000..452e97e Binary files /dev/null and b/panorama/scripts/custom_game/card_selection.vjs_c differ diff --git a/panorama/scripts/custom_game/chat_wheel.vjs_c b/panorama/scripts/custom_game/chat_wheel.vjs_c new file mode 100644 index 0000000..0ef6fda Binary files /dev/null and b/panorama/scripts/custom_game/chat_wheel.vjs_c differ diff --git a/panorama/scripts/custom_game/contracts_inventory.vjs_c b/panorama/scripts/custom_game/contracts_inventory.vjs_c new file mode 100644 index 0000000..5059000 Binary files /dev/null and b/panorama/scripts/custom_game/contracts_inventory.vjs_c differ diff --git a/panorama/scripts/custom_game/cooking_panel.vjs_c b/panorama/scripts/custom_game/cooking_panel.vjs_c new file mode 100644 index 0000000..aaacb27 Binary files /dev/null and b/panorama/scripts/custom_game/cooking_panel.vjs_c differ diff --git a/panorama/scripts/custom_game/crystal_display.vjs_c b/panorama/scripts/custom_game/crystal_display.vjs_c new file mode 100644 index 0000000..da66157 Binary files /dev/null and b/panorama/scripts/custom_game/crystal_display.vjs_c differ diff --git a/panorama/scripts/custom_game/custom_chat.vjs_c b/panorama/scripts/custom_game/custom_chat.vjs_c new file mode 100644 index 0000000..d5375ed Binary files /dev/null and b/panorama/scripts/custom_game/custom_chat.vjs_c differ diff --git a/panorama/scripts/custom_game/custom_loading_screen.vjs_c b/panorama/scripts/custom_game/custom_loading_screen.vjs_c new file mode 100644 index 0000000..33989d1 Binary files /dev/null and b/panorama/scripts/custom_game/custom_loading_screen.vjs_c differ diff --git a/panorama/scripts/custom_game/cutscenes/cutscene_main.vjs_c b/panorama/scripts/custom_game/cutscenes/cutscene_main.vjs_c new file mode 100644 index 0000000..0c4a068 Binary files /dev/null and b/panorama/scripts/custom_game/cutscenes/cutscene_main.vjs_c differ diff --git a/panorama/scripts/custom_game/death_sentence/contracts_ui.vjs_c b/panorama/scripts/custom_game/death_sentence/contracts_ui.vjs_c new file mode 100644 index 0000000..bfd92a1 Binary files /dev/null and b/panorama/scripts/custom_game/death_sentence/contracts_ui.vjs_c differ diff --git a/panorama/scripts/custom_game/deck_builder.vjs_c b/panorama/scripts/custom_game/deck_builder.vjs_c new file mode 100644 index 0000000..b0fcc94 Binary files /dev/null and b/panorama/scripts/custom_game/deck_builder.vjs_c differ diff --git a/panorama/scripts/custom_game/difficulty_select.vjs_c b/panorama/scripts/custom_game/difficulty_select.vjs_c new file mode 100644 index 0000000..52e4267 Binary files /dev/null and b/panorama/scripts/custom_game/difficulty_select.vjs_c differ diff --git a/panorama/scripts/custom_game/dps_display.vjs_c b/panorama/scripts/custom_game/dps_display.vjs_c new file mode 100644 index 0000000..c431ff6 Binary files /dev/null and b/panorama/scripts/custom_game/dps_display.vjs_c differ diff --git a/panorama/scripts/custom_game/end_game_summary.vjs_c b/panorama/scripts/custom_game/end_game_summary.vjs_c new file mode 100644 index 0000000..57e8e21 Binary files /dev/null and b/panorama/scripts/custom_game/end_game_summary.vjs_c differ diff --git a/panorama/scripts/custom_game/equipment.vjs_c b/panorama/scripts/custom_game/equipment.vjs_c new file mode 100644 index 0000000..dfdc5f1 Binary files /dev/null and b/panorama/scripts/custom_game/equipment.vjs_c differ diff --git a/panorama/scripts/custom_game/error_hud.vjs_c b/panorama/scripts/custom_game/error_hud.vjs_c new file mode 100644 index 0000000..40955c6 Binary files /dev/null and b/panorama/scripts/custom_game/error_hud.vjs_c differ diff --git a/panorama/scripts/custom_game/event_notification.vjs_c b/panorama/scripts/custom_game/event_notification.vjs_c new file mode 100644 index 0000000..eee3a97 Binary files /dev/null and b/panorama/scripts/custom_game/event_notification.vjs_c differ diff --git a/panorama/scripts/custom_game/hero_icon_replacer.vjs_c b/panorama/scripts/custom_game/hero_icon_replacer.vjs_c new file mode 100644 index 0000000..ee17603 Binary files /dev/null and b/panorama/scripts/custom_game/hero_icon_replacer.vjs_c differ diff --git a/panorama/scripts/custom_game/hero_inspect.vjs_c b/panorama/scripts/custom_game/hero_inspect.vjs_c new file mode 100644 index 0000000..fc42e7f Binary files /dev/null and b/panorama/scripts/custom_game/hero_inspect.vjs_c differ diff --git a/panorama/scripts/custom_game/hero_rank_tooltip.vjs_c b/panorama/scripts/custom_game/hero_rank_tooltip.vjs_c new file mode 100644 index 0000000..33f1376 Binary files /dev/null and b/panorama/scripts/custom_game/hero_rank_tooltip.vjs_c differ diff --git a/panorama/scripts/custom_game/hero_scene_preview.vjs_c b/panorama/scripts/custom_game/hero_scene_preview.vjs_c new file mode 100644 index 0000000..f101fab Binary files /dev/null and b/panorama/scripts/custom_game/hero_scene_preview.vjs_c differ diff --git a/panorama/scripts/custom_game/hero_selection_icons.vjs_c b/panorama/scripts/custom_game/hero_selection_icons.vjs_c new file mode 100644 index 0000000..9cf5c42 Binary files /dev/null and b/panorama/scripts/custom_game/hero_selection_icons.vjs_c differ diff --git a/panorama/scripts/custom_game/hero_selection_timer.vjs_c b/panorama/scripts/custom_game/hero_selection_timer.vjs_c new file mode 100644 index 0000000..69193b1 Binary files /dev/null and b/panorama/scripts/custom_game/hero_selection_timer.vjs_c differ diff --git a/panorama/scripts/custom_game/hud.vjs_c b/panorama/scripts/custom_game/hud.vjs_c new file mode 100644 index 0000000..de75425 Binary files /dev/null and b/panorama/scripts/custom_game/hud.vjs_c differ diff --git a/panorama/scripts/custom_game/leaderboard.vjs_c b/panorama/scripts/custom_game/leaderboard.vjs_c new file mode 100644 index 0000000..267a46c Binary files /dev/null and b/panorama/scripts/custom_game/leaderboard.vjs_c differ diff --git a/panorama/scripts/custom_game/manifest.vjs_c b/panorama/scripts/custom_game/manifest.vjs_c new file mode 100644 index 0000000..25cf917 Binary files /dev/null and b/panorama/scripts/custom_game/manifest.vjs_c differ diff --git a/panorama/scripts/custom_game/marketplace_item_tooltip.vjs_c b/panorama/scripts/custom_game/marketplace_item_tooltip.vjs_c new file mode 100644 index 0000000..ff52b9b Binary files /dev/null and b/panorama/scripts/custom_game/marketplace_item_tooltip.vjs_c differ diff --git a/panorama/scripts/custom_game/marketplace_item_tooltip_details.vjs_c b/panorama/scripts/custom_game/marketplace_item_tooltip_details.vjs_c new file mode 100644 index 0000000..f5ad0bf Binary files /dev/null and b/panorama/scripts/custom_game/marketplace_item_tooltip_details.vjs_c differ diff --git a/panorama/scripts/custom_game/match_details.vjs_c b/panorama/scripts/custom_game/match_details.vjs_c new file mode 100644 index 0000000..8f1a60b Binary files /dev/null and b/panorama/scripts/custom_game/match_details.vjs_c differ diff --git a/panorama/scripts/custom_game/match_end_screen.vjs_c b/panorama/scripts/custom_game/match_end_screen.vjs_c new file mode 100644 index 0000000..378a597 Binary files /dev/null and b/panorama/scripts/custom_game/match_end_screen.vjs_c differ diff --git a/panorama/scripts/custom_game/mini_profile.vjs_c b/panorama/scripts/custom_game/mini_profile.vjs_c new file mode 100644 index 0000000..5a80e45 Binary files /dev/null and b/panorama/scripts/custom_game/mini_profile.vjs_c differ diff --git a/panorama/scripts/custom_game/nearby_item_notification.vjs_c b/panorama/scripts/custom_game/nearby_item_notification.vjs_c new file mode 100644 index 0000000..d72041d Binary files /dev/null and b/panorama/scripts/custom_game/nearby_item_notification.vjs_c differ diff --git a/panorama/scripts/custom_game/news_feed.vjs_c b/panorama/scripts/custom_game/news_feed.vjs_c new file mode 100644 index 0000000..219a9d0 Binary files /dev/null and b/panorama/scripts/custom_game/news_feed.vjs_c differ diff --git a/panorama/scripts/custom_game/notification.vjs_c b/panorama/scripts/custom_game/notification.vjs_c new file mode 100644 index 0000000..a83b1c5 Binary files /dev/null and b/panorama/scripts/custom_game/notification.vjs_c differ diff --git a/panorama/scripts/custom_game/player_ids.vjs_c b/panorama/scripts/custom_game/player_ids.vjs_c new file mode 100644 index 0000000..277a519 Binary files /dev/null and b/panorama/scripts/custom_game/player_ids.vjs_c differ diff --git a/panorama/scripts/custom_game/player_info.vjs_c b/panorama/scripts/custom_game/player_info.vjs_c new file mode 100644 index 0000000..2befb49 Binary files /dev/null and b/panorama/scripts/custom_game/player_info.vjs_c differ diff --git a/panorama/scripts/custom_game/player_tips.vjs_c b/panorama/scripts/custom_game/player_tips.vjs_c new file mode 100644 index 0000000..7a34ba8 Binary files /dev/null and b/panorama/scripts/custom_game/player_tips.vjs_c differ diff --git a/panorama/scripts/custom_game/profile.vjs_c b/panorama/scripts/custom_game/profile.vjs_c new file mode 100644 index 0000000..4ac64c8 Binary files /dev/null and b/panorama/scripts/custom_game/profile.vjs_c differ diff --git a/panorama/scripts/custom_game/quests.vjs_c b/panorama/scripts/custom_game/quests.vjs_c new file mode 100644 index 0000000..22b490d Binary files /dev/null and b/panorama/scripts/custom_game/quests.vjs_c differ diff --git a/panorama/scripts/custom_game/rage_hud.vjs_c b/panorama/scripts/custom_game/rage_hud.vjs_c new file mode 100644 index 0000000..670a9f8 Binary files /dev/null and b/panorama/scripts/custom_game/rage_hud.vjs_c differ diff --git a/panorama/scripts/custom_game/raid_boss_rewards.vjs_c b/panorama/scripts/custom_game/raid_boss_rewards.vjs_c new file mode 100644 index 0000000..4a2b5f2 Binary files /dev/null and b/panorama/scripts/custom_game/raid_boss_rewards.vjs_c differ diff --git a/panorama/scripts/custom_game/setup_status.vjs_c b/panorama/scripts/custom_game/setup_status.vjs_c new file mode 100644 index 0000000..314e8d4 Binary files /dev/null and b/panorama/scripts/custom_game/setup_status.vjs_c differ diff --git a/panorama/scripts/custom_game/social_links.vjs_c b/panorama/scripts/custom_game/social_links.vjs_c new file mode 100644 index 0000000..eb0e7ab Binary files /dev/null and b/panorama/scripts/custom_game/social_links.vjs_c differ diff --git a/panorama/scripts/custom_game/store.vjs_c b/panorama/scripts/custom_game/store.vjs_c new file mode 100644 index 0000000..0d483a1 Binary files /dev/null and b/panorama/scripts/custom_game/store.vjs_c differ diff --git a/panorama/scripts/custom_game/utils.vjs_c b/panorama/scripts/custom_game/utils.vjs_c new file mode 100644 index 0000000..57fe36e Binary files /dev/null and b/panorama/scripts/custom_game/utils.vjs_c differ diff --git a/panorama/scripts/custom_game/vector_targeting.vjs_c b/panorama/scripts/custom_game/vector_targeting.vjs_c new file mode 100644 index 0000000..90d5c4f Binary files /dev/null and b/panorama/scripts/custom_game/vector_targeting.vjs_c differ diff --git a/panorama/scripts/custom_game/world_light_recover.vjs_c b/panorama/scripts/custom_game/world_light_recover.vjs_c new file mode 100644 index 0000000..0360382 Binary files /dev/null and b/panorama/scripts/custom_game/world_light_recover.vjs_c differ diff --git a/panorama/styles/custom_game/admin_menu.vcss_c b/panorama/styles/custom_game/admin_menu.vcss_c new file mode 100644 index 0000000..a1db02e Binary files /dev/null and b/panorama/styles/custom_game/admin_menu.vcss_c differ diff --git a/panorama/styles/custom_game/arsenal.vcss_c b/panorama/styles/custom_game/arsenal.vcss_c new file mode 100644 index 0000000..9e394f4 Binary files /dev/null and b/panorama/styles/custom_game/arsenal.vcss_c differ diff --git a/panorama/styles/custom_game/arsenal_item_tooltip.vcss_c b/panorama/styles/custom_game/arsenal_item_tooltip.vcss_c new file mode 100644 index 0000000..868d090 Binary files /dev/null and b/panorama/styles/custom_game/arsenal_item_tooltip.vcss_c differ diff --git a/panorama/styles/custom_game/battle_pass.vcss_c b/panorama/styles/custom_game/battle_pass.vcss_c new file mode 100644 index 0000000..b1189c5 Binary files /dev/null and b/panorama/styles/custom_game/battle_pass.vcss_c differ diff --git a/panorama/styles/custom_game/black_shop.vcss_c b/panorama/styles/custom_game/black_shop.vcss_c new file mode 100644 index 0000000..47372d0 Binary files /dev/null and b/panorama/styles/custom_game/black_shop.vcss_c differ diff --git a/panorama/styles/custom_game/boss_health_bar.vcss_c b/panorama/styles/custom_game/boss_health_bar.vcss_c new file mode 100644 index 0000000..e811867 Binary files /dev/null and b/panorama/styles/custom_game/boss_health_bar.vcss_c differ diff --git a/panorama/styles/custom_game/card_selection.vcss_c b/panorama/styles/custom_game/card_selection.vcss_c new file mode 100644 index 0000000..e87e954 Binary files /dev/null and b/panorama/styles/custom_game/card_selection.vcss_c differ diff --git a/panorama/styles/custom_game/chat_wheel.vcss_c b/panorama/styles/custom_game/chat_wheel.vcss_c new file mode 100644 index 0000000..36c2730 Binary files /dev/null and b/panorama/styles/custom_game/chat_wheel.vcss_c differ diff --git a/panorama/styles/custom_game/contracts_inventory.vcss_c b/panorama/styles/custom_game/contracts_inventory.vcss_c new file mode 100644 index 0000000..4d2c068 Binary files /dev/null and b/panorama/styles/custom_game/contracts_inventory.vcss_c differ diff --git a/panorama/styles/custom_game/cooking_panel.vcss_c b/panorama/styles/custom_game/cooking_panel.vcss_c new file mode 100644 index 0000000..3cb90ee Binary files /dev/null and b/panorama/styles/custom_game/cooking_panel.vcss_c differ diff --git a/panorama/styles/custom_game/crystal_display.vcss_c b/panorama/styles/custom_game/crystal_display.vcss_c new file mode 100644 index 0000000..aa4fad5 Binary files /dev/null and b/panorama/styles/custom_game/crystal_display.vcss_c differ diff --git a/panorama/styles/custom_game/custom_loading_screen.vcss_c b/panorama/styles/custom_game/custom_loading_screen.vcss_c new file mode 100644 index 0000000..a2dd2ab Binary files /dev/null and b/panorama/styles/custom_game/custom_loading_screen.vcss_c differ diff --git a/panorama/styles/custom_game/cutscenes/cutscene_main.vcss_c b/panorama/styles/custom_game/cutscenes/cutscene_main.vcss_c new file mode 100644 index 0000000..f3556cb Binary files /dev/null and b/panorama/styles/custom_game/cutscenes/cutscene_main.vcss_c differ diff --git a/panorama/styles/custom_game/death_sentence_contracts.vcss_c b/panorama/styles/custom_game/death_sentence_contracts.vcss_c new file mode 100644 index 0000000..89c5d12 Binary files /dev/null and b/panorama/styles/custom_game/death_sentence_contracts.vcss_c differ diff --git a/panorama/styles/custom_game/deck_builder.vcss_c b/panorama/styles/custom_game/deck_builder.vcss_c new file mode 100644 index 0000000..c653863 Binary files /dev/null and b/panorama/styles/custom_game/deck_builder.vcss_c differ diff --git a/panorama/styles/custom_game/difficulty_select.vcss_c b/panorama/styles/custom_game/difficulty_select.vcss_c new file mode 100644 index 0000000..dbcea3a Binary files /dev/null and b/panorama/styles/custom_game/difficulty_select.vcss_c differ diff --git a/panorama/styles/custom_game/dps_display.vcss_c b/panorama/styles/custom_game/dps_display.vcss_c new file mode 100644 index 0000000..6bb826d Binary files /dev/null and b/panorama/styles/custom_game/dps_display.vcss_c differ diff --git a/panorama/styles/custom_game/end_game_summary.vcss_c b/panorama/styles/custom_game/end_game_summary.vcss_c new file mode 100644 index 0000000..9200402 Binary files /dev/null and b/panorama/styles/custom_game/end_game_summary.vcss_c differ diff --git a/panorama/styles/custom_game/equipment.vcss_c b/panorama/styles/custom_game/equipment.vcss_c new file mode 100644 index 0000000..e3ee4cc Binary files /dev/null and b/panorama/styles/custom_game/equipment.vcss_c differ diff --git a/panorama/styles/custom_game/event_notification.vcss_c b/panorama/styles/custom_game/event_notification.vcss_c new file mode 100644 index 0000000..9396026 Binary files /dev/null and b/panorama/styles/custom_game/event_notification.vcss_c differ diff --git a/panorama/styles/custom_game/hero_icon_replacer.vcss_c b/panorama/styles/custom_game/hero_icon_replacer.vcss_c new file mode 100644 index 0000000..0d9c0e1 Binary files /dev/null and b/panorama/styles/custom_game/hero_icon_replacer.vcss_c differ diff --git a/panorama/styles/custom_game/hero_inspect.vcss_c b/panorama/styles/custom_game/hero_inspect.vcss_c new file mode 100644 index 0000000..59d1350 Binary files /dev/null and b/panorama/styles/custom_game/hero_inspect.vcss_c differ diff --git a/panorama/styles/custom_game/hero_rank_tooltip.vcss_c b/panorama/styles/custom_game/hero_rank_tooltip.vcss_c new file mode 100644 index 0000000..47e3d7e Binary files /dev/null and b/panorama/styles/custom_game/hero_rank_tooltip.vcss_c differ diff --git a/panorama/styles/custom_game/hero_selection_timer.vcss_c b/panorama/styles/custom_game/hero_selection_timer.vcss_c new file mode 100644 index 0000000..621feb2 Binary files /dev/null and b/panorama/styles/custom_game/hero_selection_timer.vcss_c differ diff --git a/panorama/styles/custom_game/hero_selection_topbar.vcss_c b/panorama/styles/custom_game/hero_selection_topbar.vcss_c new file mode 100644 index 0000000..af0c4e8 Binary files /dev/null and b/panorama/styles/custom_game/hero_selection_topbar.vcss_c differ diff --git a/panorama/styles/custom_game/leaderboard.vcss_c b/panorama/styles/custom_game/leaderboard.vcss_c new file mode 100644 index 0000000..8e89d68 Binary files /dev/null and b/panorama/styles/custom_game/leaderboard.vcss_c differ diff --git a/panorama/styles/custom_game/match_details.vcss_c b/panorama/styles/custom_game/match_details.vcss_c new file mode 100644 index 0000000..5cf8f64 Binary files /dev/null and b/panorama/styles/custom_game/match_details.vcss_c differ diff --git a/panorama/styles/custom_game/match_end_screen.vcss_c b/panorama/styles/custom_game/match_end_screen.vcss_c new file mode 100644 index 0000000..c075ada Binary files /dev/null and b/panorama/styles/custom_game/match_end_screen.vcss_c differ diff --git a/panorama/styles/custom_game/mini_profile.vcss_c b/panorama/styles/custom_game/mini_profile.vcss_c new file mode 100644 index 0000000..d4059aa Binary files /dev/null and b/panorama/styles/custom_game/mini_profile.vcss_c differ diff --git a/panorama/styles/custom_game/nearby_item_notification.vcss_c b/panorama/styles/custom_game/nearby_item_notification.vcss_c new file mode 100644 index 0000000..df4538d Binary files /dev/null and b/panorama/styles/custom_game/nearby_item_notification.vcss_c differ diff --git a/panorama/styles/custom_game/news_feed.vcss_c b/panorama/styles/custom_game/news_feed.vcss_c new file mode 100644 index 0000000..80f13b5 Binary files /dev/null and b/panorama/styles/custom_game/news_feed.vcss_c differ diff --git a/panorama/styles/custom_game/notification.vcss_c b/panorama/styles/custom_game/notification.vcss_c new file mode 100644 index 0000000..54a7b18 Binary files /dev/null and b/panorama/styles/custom_game/notification.vcss_c differ diff --git a/panorama/styles/custom_game/profile.vcss_c b/panorama/styles/custom_game/profile.vcss_c new file mode 100644 index 0000000..1e3c777 Binary files /dev/null and b/panorama/styles/custom_game/profile.vcss_c differ diff --git a/panorama/styles/custom_game/progress_bar/rage_progress_bar.vcss_c b/panorama/styles/custom_game/progress_bar/rage_progress_bar.vcss_c new file mode 100644 index 0000000..d4b79c7 Binary files /dev/null and b/panorama/styles/custom_game/progress_bar/rage_progress_bar.vcss_c differ diff --git a/panorama/styles/custom_game/quests.vcss_c b/panorama/styles/custom_game/quests.vcss_c new file mode 100644 index 0000000..a776d3e Binary files /dev/null and b/panorama/styles/custom_game/quests.vcss_c differ diff --git a/panorama/styles/custom_game/rage_hud.vcss_c b/panorama/styles/custom_game/rage_hud.vcss_c new file mode 100644 index 0000000..6c89b15 Binary files /dev/null and b/panorama/styles/custom_game/rage_hud.vcss_c differ diff --git a/panorama/styles/custom_game/raid_boss_rewards.vcss_c b/panorama/styles/custom_game/raid_boss_rewards.vcss_c new file mode 100644 index 0000000..91fe8bd Binary files /dev/null and b/panorama/styles/custom_game/raid_boss_rewards.vcss_c differ diff --git a/panorama/styles/custom_game/setup_status.vcss_c b/panorama/styles/custom_game/setup_status.vcss_c new file mode 100644 index 0000000..50a1427 Binary files /dev/null and b/panorama/styles/custom_game/setup_status.vcss_c differ diff --git a/panorama/styles/custom_game/skip_night_vote.vcss_c b/panorama/styles/custom_game/skip_night_vote.vcss_c new file mode 100644 index 0000000..6a7c583 Binary files /dev/null and b/panorama/styles/custom_game/skip_night_vote.vcss_c differ diff --git a/panorama/styles/custom_game/social_links.vcss_c b/panorama/styles/custom_game/social_links.vcss_c new file mode 100644 index 0000000..de63707 Binary files /dev/null and b/panorama/styles/custom_game/social_links.vcss_c differ diff --git a/panorama/styles/custom_game/store.vcss_c b/panorama/styles/custom_game/store.vcss_c new file mode 100644 index 0000000..a755808 Binary files /dev/null and b/panorama/styles/custom_game/store.vcss_c differ diff --git a/panorama_debugger.cfg b/panorama_debugger.cfg new file mode 100644 index 0000000..fabaf64 Binary files /dev/null and b/panorama_debugger.cfg differ diff --git a/particles/battlepass_garden_effect.vpcf_c b/particles/battlepass_garden_effect.vpcf_c new file mode 100644 index 0000000..f0490cf Binary files /dev/null and b/particles/battlepass_garden_effect.vpcf_c differ diff --git a/particles/battlepass_golden_effect.vpcf_c b/particles/battlepass_golden_effect.vpcf_c new file mode 100644 index 0000000..8ccfc3b Binary files /dev/null and b/particles/battlepass_golden_effect.vpcf_c differ diff --git a/particles/blackshop_teleport.vpcf_c b/particles/blackshop_teleport.vpcf_c new file mode 100644 index 0000000..a29b772 Binary files /dev/null and b/particles/blackshop_teleport.vpcf_c differ diff --git a/particles/blood_stone_effect_magical.vpcf_c b/particles/blood_stone_effect_magical.vpcf_c new file mode 100644 index 0000000..bdbfc4e Binary files /dev/null and b/particles/blood_stone_effect_magical.vpcf_c differ diff --git a/particles/bloodbath_circle.vpcf_c b/particles/bloodbath_circle.vpcf_c new file mode 100644 index 0000000..2e49a20 Binary files /dev/null and b/particles/bloodbath_circle.vpcf_c differ diff --git a/particles/bloodstone_full_screen_effect.vpcf_c b/particles/bloodstone_full_screen_effect.vpcf_c new file mode 100644 index 0000000..270c2d7 Binary files /dev/null and b/particles/bloodstone_full_screen_effect.vpcf_c differ diff --git a/particles/blue_gems_effect.vpcf_c b/particles/blue_gems_effect.vpcf_c new file mode 100644 index 0000000..b43df33 Binary files /dev/null and b/particles/blue_gems_effect.vpcf_c differ diff --git a/particles/boss_tinker_laser_preview.vpcf_c b/particles/boss_tinker_laser_preview.vpcf_c new file mode 100644 index 0000000..f697d7a Binary files /dev/null and b/particles/boss_tinker_laser_preview.vpcf_c differ diff --git a/particles/boss_tinker_laser_preview_vector.vpcf_c b/particles/boss_tinker_laser_preview_vector.vpcf_c new file mode 100644 index 0000000..80b677c Binary files /dev/null and b/particles/boss_tinker_laser_preview_vector.vpcf_c differ diff --git a/particles/cion_sky.vpcf_c b/particles/cion_sky.vpcf_c new file mode 100644 index 0000000..9b91868 Binary files /dev/null and b/particles/cion_sky.vpcf_c differ diff --git a/particles/crystal_maiden_aspect_3.vpcf_c b/particles/crystal_maiden_aspect_3.vpcf_c new file mode 100644 index 0000000..1d39a5a Binary files /dev/null and b/particles/crystal_maiden_aspect_3.vpcf_c differ diff --git a/particles/crystal_scepter_shield.vpcf_c b/particles/crystal_scepter_shield.vpcf_c new file mode 100644 index 0000000..dfe9e90 Binary files /dev/null and b/particles/crystal_scepter_shield.vpcf_c differ diff --git a/particles/crystal_scepter_shield_ring.vpcf_c b/particles/crystal_scepter_shield_ring.vpcf_c new file mode 100644 index 0000000..0e3c711 Binary files /dev/null and b/particles/crystal_scepter_shield_ring.vpcf_c differ diff --git a/particles/custom_effect_gems.vpcf_c b/particles/custom_effect_gems.vpcf_c new file mode 100644 index 0000000..703909b Binary files /dev/null and b/particles/custom_effect_gems.vpcf_c differ diff --git a/particles/darkmoon_creep_warning.vpcf_c b/particles/darkmoon_creep_warning.vpcf_c new file mode 100644 index 0000000..f921269 Binary files /dev/null and b/particles/darkmoon_creep_warning.vpcf_c differ diff --git a/particles/darkmoon_creep_warning_pulse.vpcf_c b/particles/darkmoon_creep_warning_pulse.vpcf_c new file mode 100644 index 0000000..e16af22 Binary files /dev/null and b/particles/darkmoon_creep_warning_pulse.vpcf_c differ diff --git a/particles/darkmoon_creep_warning_ring.vpcf_c b/particles/darkmoon_creep_warning_ring.vpcf_c new file mode 100644 index 0000000..0a8fd08 Binary files /dev/null and b/particles/darkmoon_creep_warning_ring.vpcf_c differ diff --git a/particles/darkmoon_creep_warning_rope.vpcf_c b/particles/darkmoon_creep_warning_rope.vpcf_c new file mode 100644 index 0000000..a40f34e Binary files /dev/null and b/particles/darkmoon_creep_warning_rope.vpcf_c differ diff --git a/particles/darkmoon_creep_warning_streak.vpcf_c b/particles/darkmoon_creep_warning_streak.vpcf_c new file mode 100644 index 0000000..dbf9700 Binary files /dev/null and b/particles/darkmoon_creep_warning_streak.vpcf_c differ diff --git a/particles/demon_slayer_black.vpcf_c b/particles/demon_slayer_black.vpcf_c new file mode 100644 index 0000000..2201e0b Binary files /dev/null and b/particles/demon_slayer_black.vpcf_c differ diff --git a/particles/demon_slayer_glow.vpcf_c b/particles/demon_slayer_glow.vpcf_c new file mode 100644 index 0000000..6e68d2f Binary files /dev/null and b/particles/demon_slayer_glow.vpcf_c differ diff --git a/particles/demon_slayer_slash.vpcf_c b/particles/demon_slayer_slash.vpcf_c new file mode 100644 index 0000000..8a0829e Binary files /dev/null and b/particles/demon_slayer_slash.vpcf_c differ diff --git a/particles/demon_slayer_slash_rope.vpcf_c b/particles/demon_slayer_slash_rope.vpcf_c new file mode 100644 index 0000000..28b3006 Binary files /dev/null and b/particles/demon_slayer_slash_rope.vpcf_c differ diff --git a/particles/effect_battlepass_red.vpcf_c b/particles/effect_battlepass_red.vpcf_c new file mode 100644 index 0000000..8a6d641 Binary files /dev/null and b/particles/effect_battlepass_red.vpcf_c differ diff --git a/particles/effect_gem_glow.vpcf_c b/particles/effect_gem_glow.vpcf_c new file mode 100644 index 0000000..c4b2d2e Binary files /dev/null and b/particles/effect_gem_glow.vpcf_c differ diff --git a/particles/enchantress_impetus_custom_gay.vpcf_c b/particles/enchantress_impetus_custom_gay.vpcf_c new file mode 100644 index 0000000..8f7a242 Binary files /dev/null and b/particles/enchantress_impetus_custom_gay.vpcf_c differ diff --git a/particles/fish_screen_effect.vpcf_c b/particles/fish_screen_effect.vpcf_c new file mode 100644 index 0000000..0f0960f Binary files /dev/null and b/particles/fish_screen_effect.vpcf_c differ diff --git a/particles/glave_glow.vpcf_c b/particles/glave_glow.vpcf_c new file mode 100644 index 0000000..faaf930 Binary files /dev/null and b/particles/glave_glow.vpcf_c differ diff --git a/particles/great_cleave_glow.vpcf_c b/particles/great_cleave_glow.vpcf_c new file mode 100644 index 0000000..a8c8f92 Binary files /dev/null and b/particles/great_cleave_glow.vpcf_c differ diff --git a/particles/great_cleave_shockwave.vpcf_c b/particles/great_cleave_shockwave.vpcf_c new file mode 100644 index 0000000..3d81022 Binary files /dev/null and b/particles/great_cleave_shockwave.vpcf_c differ diff --git a/particles/great_cleave_sparks.vpcf_c b/particles/great_cleave_sparks.vpcf_c new file mode 100644 index 0000000..e77d9f8 Binary files /dev/null and b/particles/great_cleave_sparks.vpcf_c differ diff --git a/particles/haste_zone.vpcf_c b/particles/haste_zone.vpcf_c new file mode 100644 index 0000000..eb34cee Binary files /dev/null and b/particles/haste_zone.vpcf_c differ diff --git a/particles/haste_zonea.vpcf_c b/particles/haste_zonea.vpcf_c new file mode 100644 index 0000000..e275b83 Binary files /dev/null and b/particles/haste_zonea.vpcf_c differ diff --git a/particles/haste_zoneb.vpcf_c b/particles/haste_zoneb.vpcf_c new file mode 100644 index 0000000..37aa3d7 Binary files /dev/null and b/particles/haste_zoneb.vpcf_c differ diff --git a/particles/haste_zoneb0.vpcf_c b/particles/haste_zoneb0.vpcf_c new file mode 100644 index 0000000..6d4457d Binary files /dev/null and b/particles/haste_zoneb0.vpcf_c differ diff --git a/particles/haste_zoneb1.vpcf_c b/particles/haste_zoneb1.vpcf_c new file mode 100644 index 0000000..288c5c5 Binary files /dev/null and b/particles/haste_zoneb1.vpcf_c differ diff --git a/particles/heavenly_item_effect.vpcf_c b/particles/heavenly_item_effect.vpcf_c new file mode 100644 index 0000000..df046f5 Binary files /dev/null and b/particles/heavenly_item_effect.vpcf_c differ diff --git a/particles/heavenly_item_effect_dark.vpcf_c b/particles/heavenly_item_effect_dark.vpcf_c new file mode 100644 index 0000000..71f239d Binary files /dev/null and b/particles/heavenly_item_effect_dark.vpcf_c differ diff --git a/particles/heavenly_item_effect_dark_light.vpcf_c b/particles/heavenly_item_effect_dark_light.vpcf_c new file mode 100644 index 0000000..854f8f0 Binary files /dev/null and b/particles/heavenly_item_effect_dark_light.vpcf_c differ diff --git a/particles/heavenly_item_effect_ground_energy.vpcf_c b/particles/heavenly_item_effect_ground_energy.vpcf_c new file mode 100644 index 0000000..e27c713 Binary files /dev/null and b/particles/heavenly_item_effect_ground_energy.vpcf_c differ diff --git a/particles/heavenly_item_effect_ground_energy_embers.vpcf_c b/particles/heavenly_item_effect_ground_energy_embers.vpcf_c new file mode 100644 index 0000000..3c274f3 Binary files /dev/null and b/particles/heavenly_item_effect_ground_energy_embers.vpcf_c differ diff --git a/particles/heavenly_item_effect_head.vpcf_c b/particles/heavenly_item_effect_head.vpcf_c new file mode 100644 index 0000000..0399f3e Binary files /dev/null and b/particles/heavenly_item_effect_head.vpcf_c differ diff --git a/particles/heavenly_item_effect_ring.vpcf_c b/particles/heavenly_item_effect_ring.vpcf_c new file mode 100644 index 0000000..2a90d1d Binary files /dev/null and b/particles/heavenly_item_effect_ring.vpcf_c differ diff --git a/particles/heavenly_item_effect_ring_twinkie.vpcf_c b/particles/heavenly_item_effect_ring_twinkie.vpcf_c new file mode 100644 index 0000000..b4114e7 Binary files /dev/null and b/particles/heavenly_item_effect_ring_twinkie.vpcf_c differ diff --git a/particles/heroes/vengeful/vengefulspirit_revenge_buff.vpcf_c b/particles/heroes/vengeful/vengefulspirit_revenge_buff.vpcf_c new file mode 100644 index 0000000..6a1d145 Binary files /dev/null and b/particles/heroes/vengeful/vengefulspirit_revenge_buff.vpcf_c differ diff --git a/particles/impetus.vpcf_c b/particles/impetus.vpcf_c new file mode 100644 index 0000000..fbdd71d Binary files /dev/null and b/particles/impetus.vpcf_c differ diff --git a/particles/impossible.vpcf_c b/particles/impossible.vpcf_c new file mode 100644 index 0000000..499d6bc Binary files /dev/null and b/particles/impossible.vpcf_c differ diff --git a/particles/jugg_ground_black.vpcf_c b/particles/jugg_ground_black.vpcf_c new file mode 100644 index 0000000..fe192c0 Binary files /dev/null and b/particles/jugg_ground_black.vpcf_c differ diff --git a/particles/jugg_step_beam.vpcf_c b/particles/jugg_step_beam.vpcf_c new file mode 100644 index 0000000..117bece Binary files /dev/null and b/particles/jugg_step_beam.vpcf_c differ diff --git a/particles/jugg_step_ember.vpcf_c b/particles/jugg_step_ember.vpcf_c new file mode 100644 index 0000000..89dc525 Binary files /dev/null and b/particles/jugg_step_ember.vpcf_c differ diff --git a/particles/jugg_step_glow.vpcf_c b/particles/jugg_step_glow.vpcf_c new file mode 100644 index 0000000..74c0244 Binary files /dev/null and b/particles/jugg_step_glow.vpcf_c differ diff --git a/particles/jugg_step_heat.vpcf_c b/particles/jugg_step_heat.vpcf_c new file mode 100644 index 0000000..43dcc98 Binary files /dev/null and b/particles/jugg_step_heat.vpcf_c differ diff --git a/particles/jugg_step_smoke.vpcf_c b/particles/jugg_step_smoke.vpcf_c new file mode 100644 index 0000000..3f3abde Binary files /dev/null and b/particles/jugg_step_smoke.vpcf_c differ diff --git a/particles/jugg_step_spark.vpcf_c b/particles/jugg_step_spark.vpcf_c new file mode 100644 index 0000000..5c776dc Binary files /dev/null and b/particles/jugg_step_spark.vpcf_c differ diff --git a/particles/jugg_step_steam.vpcf_c b/particles/jugg_step_steam.vpcf_c new file mode 100644 index 0000000..61f56b4 Binary files /dev/null and b/particles/jugg_step_steam.vpcf_c differ diff --git a/particles/jugg_step_trail.vpcf_c b/particles/jugg_step_trail.vpcf_c new file mode 100644 index 0000000..bec76c9 Binary files /dev/null and b/particles/jugg_step_trail.vpcf_c differ diff --git a/particles/juggernaut_step.vpcf_c b/particles/juggernaut_step.vpcf_c new file mode 100644 index 0000000..0a67ff0 Binary files /dev/null and b/particles/juggernaut_step.vpcf_c differ diff --git a/particles/lotus_effect.vpcf_c b/particles/lotus_effect.vpcf_c new file mode 100644 index 0000000..36d8849 Binary files /dev/null and b/particles/lotus_effect.vpcf_c differ diff --git a/particles/nagash_1.vpcf_c b/particles/nagash_1.vpcf_c new file mode 100644 index 0000000..581539f Binary files /dev/null and b/particles/nagash_1.vpcf_c differ diff --git a/particles/nagash_2.vpcf_c b/particles/nagash_2.vpcf_c new file mode 100644 index 0000000..f5b8c7d Binary files /dev/null and b/particles/nagash_2.vpcf_c differ diff --git a/particles/nagash_3.vpcf_c b/particles/nagash_3.vpcf_c new file mode 100644 index 0000000..58bcee3 Binary files /dev/null and b/particles/nagash_3.vpcf_c differ diff --git a/particles/nagash_4.vpcf_c b/particles/nagash_4.vpcf_c new file mode 100644 index 0000000..99fb937 Binary files /dev/null and b/particles/nagash_4.vpcf_c differ diff --git a/particles/nagash_world_full.vpcf_c b/particles/nagash_world_full.vpcf_c new file mode 100644 index 0000000..37953ab Binary files /dev/null and b/particles/nagash_world_full.vpcf_c differ diff --git a/particles/ping_player_clown.vpcf_c b/particles/ping_player_clown.vpcf_c new file mode 100644 index 0000000..60df04e Binary files /dev/null and b/particles/ping_player_clown.vpcf_c differ diff --git a/particles/pudge_meathook_hook_custom.vpcf_c b/particles/pudge_meathook_hook_custom.vpcf_c new file mode 100644 index 0000000..a67dbb9 Binary files /dev/null and b/particles/pudge_meathook_hook_custom.vpcf_c differ diff --git a/particles/red_diff.vpcf_c b/particles/red_diff.vpcf_c new file mode 100644 index 0000000..d476868 Binary files /dev/null and b/particles/red_diff.vpcf_c differ diff --git a/particles/shrine/capture_point_ring.vpcf_c b/particles/shrine/capture_point_ring.vpcf_c new file mode 100644 index 0000000..b6a9411 Binary files /dev/null and b/particles/shrine/capture_point_ring.vpcf_c differ diff --git a/particles/shrine/capture_point_ring_b.vpcf_c b/particles/shrine/capture_point_ring_b.vpcf_c new file mode 100644 index 0000000..a232393 Binary files /dev/null and b/particles/shrine/capture_point_ring_b.vpcf_c differ diff --git a/particles/shrine/capture_point_ring_c.vpcf_c b/particles/shrine/capture_point_ring_c.vpcf_c new file mode 100644 index 0000000..6e1bfcb Binary files /dev/null and b/particles/shrine/capture_point_ring_c.vpcf_c differ diff --git a/particles/shrine/capture_point_ring_capturing.vpcf_c b/particles/shrine/capture_point_ring_capturing.vpcf_c new file mode 100644 index 0000000..c76f155 Binary files /dev/null and b/particles/shrine/capture_point_ring_capturing.vpcf_c differ diff --git a/particles/shrine/capture_point_ring_clock.vpcf_c b/particles/shrine/capture_point_ring_clock.vpcf_c new file mode 100644 index 0000000..1b0e03b Binary files /dev/null and b/particles/shrine/capture_point_ring_clock.vpcf_c differ diff --git a/particles/shrine/capture_point_ring_clock_overthrow.vpcf_c b/particles/shrine/capture_point_ring_clock_overthrow.vpcf_c new file mode 100644 index 0000000..45cbb04 Binary files /dev/null and b/particles/shrine/capture_point_ring_clock_overthrow.vpcf_c differ diff --git a/particles/shrine/capture_point_ring_dial_glow.vpcf_c b/particles/shrine/capture_point_ring_dial_glow.vpcf_c new file mode 100644 index 0000000..168cbd8 Binary files /dev/null and b/particles/shrine/capture_point_ring_dial_glow.vpcf_c differ diff --git a/particles/shrine/capture_point_ring_e.vpcf_c b/particles/shrine/capture_point_ring_e.vpcf_c new file mode 100644 index 0000000..f1036b6 Binary files /dev/null and b/particles/shrine/capture_point_ring_e.vpcf_c differ diff --git a/particles/shrine/capture_point_ring_endcap.vpcf_c b/particles/shrine/capture_point_ring_endcap.vpcf_c new file mode 100644 index 0000000..13c8bd2 Binary files /dev/null and b/particles/shrine/capture_point_ring_endcap.vpcf_c differ diff --git a/particles/shrine/capture_point_ring_endcap_burst.vpcf_c b/particles/shrine/capture_point_ring_endcap_burst.vpcf_c new file mode 100644 index 0000000..440c3a2 Binary files /dev/null and b/particles/shrine/capture_point_ring_endcap_burst.vpcf_c differ diff --git a/particles/shrine/capture_point_ring_endcap_flare.vpcf_c b/particles/shrine/capture_point_ring_endcap_flare.vpcf_c new file mode 100644 index 0000000..0946333 Binary files /dev/null and b/particles/shrine/capture_point_ring_endcap_flare.vpcf_c differ diff --git a/particles/shrine/capture_point_ring_hand.vpcf_c b/particles/shrine/capture_point_ring_hand.vpcf_c new file mode 100644 index 0000000..4f46e69 Binary files /dev/null and b/particles/shrine/capture_point_ring_hand.vpcf_c differ diff --git a/particles/shrine/capture_point_ring_overthrow.vpcf_c b/particles/shrine/capture_point_ring_overthrow.vpcf_c new file mode 100644 index 0000000..6c760fd Binary files /dev/null and b/particles/shrine/capture_point_ring_overthrow.vpcf_c differ diff --git a/particles/shrine/capture_point_ring_overthrow_e.vpcf_c b/particles/shrine/capture_point_ring_overthrow_e.vpcf_c new file mode 100644 index 0000000..6f0457b Binary files /dev/null and b/particles/shrine/capture_point_ring_overthrow_e.vpcf_c differ diff --git a/particles/shrine/capture_point_ring_overthrow_endcap.vpcf_c b/particles/shrine/capture_point_ring_overthrow_endcap.vpcf_c new file mode 100644 index 0000000..9f60554 Binary files /dev/null and b/particles/shrine/capture_point_ring_overthrow_endcap.vpcf_c differ diff --git a/particles/shrine/capture_point_ring_overthrow_endcap_burst.vpcf_c b/particles/shrine/capture_point_ring_overthrow_endcap_burst.vpcf_c new file mode 100644 index 0000000..dc96138 Binary files /dev/null and b/particles/shrine/capture_point_ring_overthrow_endcap_burst.vpcf_c differ diff --git a/particles/shrine/capture_point_ring_overthrow_endcap_flare.vpcf_c b/particles/shrine/capture_point_ring_overthrow_endcap_flare.vpcf_c new file mode 100644 index 0000000..5d6ea2b Binary files /dev/null and b/particles/shrine/capture_point_ring_overthrow_endcap_flare.vpcf_c differ diff --git a/particles/shrine/haste_zone.vpcf_c b/particles/shrine/haste_zone.vpcf_c new file mode 100644 index 0000000..eb34cee Binary files /dev/null and b/particles/shrine/haste_zone.vpcf_c differ diff --git a/particles/shrine/haste_zonea.vpcf_c b/particles/shrine/haste_zonea.vpcf_c new file mode 100644 index 0000000..e275b83 Binary files /dev/null and b/particles/shrine/haste_zonea.vpcf_c differ diff --git a/particles/shrine/haste_zoneb.vpcf_c b/particles/shrine/haste_zoneb.vpcf_c new file mode 100644 index 0000000..37aa3d7 Binary files /dev/null and b/particles/shrine/haste_zoneb.vpcf_c differ diff --git a/particles/shrine/haste_zoneb0.vpcf_c b/particles/shrine/haste_zoneb0.vpcf_c new file mode 100644 index 0000000..6d4457d Binary files /dev/null and b/particles/shrine/haste_zoneb0.vpcf_c differ diff --git a/particles/shrine/haste_zoneb1.vpcf_c b/particles/shrine/haste_zoneb1.vpcf_c new file mode 100644 index 0000000..288c5c5 Binary files /dev/null and b/particles/shrine/haste_zoneb1.vpcf_c differ diff --git a/particles/silencer_count.vpcf_c b/particles/silencer_count.vpcf_c new file mode 100644 index 0000000..fb236b4 Binary files /dev/null and b/particles/silencer_count.vpcf_c differ diff --git a/particles/silencer_plus.vpcf_c b/particles/silencer_plus.vpcf_c new file mode 100644 index 0000000..15ae204 Binary files /dev/null and b/particles/silencer_plus.vpcf_c differ diff --git a/particles/sky_cion_full.vpcf_c b/particles/sky_cion_full.vpcf_c new file mode 100644 index 0000000..76fb712 Binary files /dev/null and b/particles/sky_cion_full.vpcf_c differ diff --git a/particles/sponsor_effect.vpcf_c b/particles/sponsor_effect.vpcf_c new file mode 100644 index 0000000..ba3d4f7 Binary files /dev/null and b/particles/sponsor_effect.vpcf_c differ diff --git a/particles/sponsor_effectt.vpcf_c b/particles/sponsor_effectt.vpcf_c new file mode 100644 index 0000000..ca522d2 Binary files /dev/null and b/particles/sponsor_effectt.vpcf_c differ diff --git a/particles/status_effect_red.vpcf_c b/particles/status_effect_red.vpcf_c new file mode 100644 index 0000000..338544c Binary files /dev/null and b/particles/status_effect_red.vpcf_c differ diff --git a/particles/store.vpcf_c b/particles/store.vpcf_c new file mode 100644 index 0000000..28d5b65 Binary files /dev/null and b/particles/store.vpcf_c differ diff --git a/particles/twin_glaves.vpcf_c b/particles/twin_glaves.vpcf_c new file mode 100644 index 0000000..0dafae2 Binary files /dev/null and b/particles/twin_glaves.vpcf_c differ diff --git a/particles/vector_target_range_finder_line.vpcf_c b/particles/vector_target_range_finder_line.vpcf_c new file mode 100644 index 0000000..d96b7a7 Binary files /dev/null and b/particles/vector_target_range_finder_line.vpcf_c differ diff --git a/postman_collection.json b/postman_collection.json new file mode 100644 index 0000000..7a2571f --- /dev/null +++ b/postman_collection.json @@ -0,0 +1,925 @@ +{ + "info": { + "name": "Zombie Invasion API", + "description": "API endpoints for the Zombie Invasion Dota 2 custom game. Base URL: http://82.146.52.69:3000/api\n\nAuth: x-custom-key header from GetDedicatedServerKeyV3(\"zombie_invasion\")\nCheat mode fallback key: menya_ebut_negry_tolpoy", + "schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json", + "_exporter_id": "manual" + }, + "auth": { + "type": "apikey", + "apikey": [ + { + "key": "key", + "value": "menya_ebut_negry_tolpoy", + "type": "string" + }, + { + "key": "value", + "value": "{{x-custom-key}}", + "type": "string" + }, + { + "key": "in", + "value": "header", + "type": "string" + } + ] + }, + "item": [ + { + "name": "Player", + "item": [ + { + "name": "Create Player Profile", + "request": { + "method": "POST", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "x-custom-key", + "value": "{{x-custom-key}}", + "type": "text" + } + ], + "body": { + "mode": "raw", + "raw": "{\n \"steam_id\": \"76561198000000001\",\n \"player_name\": \"TestPlayer\"\n}" + }, + "url": { + "raw": "{{base_url}}/player", + "host": ["{{base_url}}"], + "path": ["player"] + } + }, + "response": [] + }, + { + "name": "Get Player Profile", + "request": { + "method": "GET", + "header": [ + { + "key": "x-custom-key", + "value": "{{x-custom-key}}", + "type": "text" + } + ], + "url": { + "raw": "{{base_url}}/player/{{steam_id}}", + "host": ["{{base_url}}"], + "path": ["player", "{{steam_id}}"] + } + }, + "response": [] + }, + { + "name": "Get Player Match History", + "request": { + "method": "GET", + "header": [ + { + "key": "x-custom-key", + "value": "{{x-custom-key}}", + "type": "text" + } + ], + "url": { + "raw": "{{base_url}}/player/{{steam_id}}/history?limit=10&offset=0", + "host": ["{{base_url}}"], + "path": ["player", "{{steam_id}}", "history"], + "query": [ + {"key": "limit", "value": "10"}, + {"key": "offset", "value": "0"} + ] + } + }, + "response": [] + }, + { + "name": "Get Game Players", + "request": { + "method": "GET", + "header": [ + { + "key": "x-custom-key", + "value": "{{x-custom-key}}", + "type": "text" + } + ], + "url": { + "raw": "{{base_url}}/game/{{game_id}}/players", + "host": ["{{base_url}}"], + "path": ["game", "{{game_id}}", "players"] + } + }, + "response": [] + }, + { + "name": "Redeem Promo Code", + "request": { + "method": "POST", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "x-custom-key", + "value": "{{x-custom-key}}", + "type": "text" + } + ], + "body": { + "mode": "raw", + "raw": "{\n \"code\": \"PROMO2024\"\n}" + }, + "url": { + "raw": "{{base_url}}/player/{{steam_id}}/promo/redeem", + "host": ["{{base_url}}"], + "path": ["player", "{{steam_id}}", "promo", "redeem"] + } + }, + "response": [] + }, + { + "name": "Get Sounds Wheel", + "request": { + "method": "GET", + "header": [ + { + "key": "x-custom-key", + "value": "{{x-custom-key}}", + "type": "text" + } + ], + "url": { + "raw": "{{base_url}}/player/{{steam_id}}/sounds_wheel", + "host": ["{{base_url}}"], + "path": ["player", "{{steam_id}}", "sounds_wheel"] + } + }, + "response": [] + }, + { + "name": "Purchase Deal", + "request": { + "method": "POST", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "x-custom-key", + "value": "{{x-custom-key}}", + "type": "text" + } + ], + "body": { + "mode": "raw", + "raw": "{\n \"deal_key\": \"starter_pack\"\n}" + }, + "url": { + "raw": "{{base_url}}/player/{{steam_id}}/deal-purchase", + "host": ["{{base_url}}"], + "path": ["player", "{{steam_id}}", "deal-purchase"] + } + }, + "response": [] + } + ] + }, + { + "name": "Battle Pass", + "item": [ + { + "name": "Get Battle Pass Data", + "request": { + "method": "GET", + "header": [ + { + "key": "x-custom-key", + "value": "{{x-custom-key}}", + "type": "text" + } + ], + "url": { + "raw": "{{base_url}}/battlepass/{{steam_id}}", + "host": ["{{base_url}}"], + "path": ["battlepass", "{{steam_id}}"] + } + }, + "response": [] + }, + { + "name": "Get Battle Pass Quests", + "request": { + "method": "GET", + "header": [ + { + "key": "x-custom-key", + "value": "{{x-custom-key}}", + "type": "text" + } + ], + "url": { + "raw": "{{base_url}}/battlepass/{{steam_id}}/quests", + "host": ["{{base_url}}"], + "path": ["battlepass", "{{steam_id}}", "quests"] + } + }, + "response": [] + }, + { + "name": "Sync Quest Progress", + "request": { + "method": "POST", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "x-custom-key", + "value": "{{x-custom-key}}", + "type": "text" + } + ], + "body": { + "mode": "raw", + "raw": "{\n \"quest_id\": \"kill_zombies_1\",\n \"progress\": 42\n}" + }, + "url": { + "raw": "{{base_url}}/battlepass/{{steam_id}}/quests/progress", + "host": ["{{base_url}}"], + "path": ["battlepass", "{{steam_id}}", "quests", "progress"] + } + }, + "response": [] + }, + { + "name": "Claim Quest Reward", + "request": { + "method": "POST", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "x-custom-key", + "value": "{{x-custom-key}}", + "type": "text" + } + ], + "body": { + "mode": "raw", + "raw": "{\n \"quest_id\": \"kill_zombies_1\"\n}" + }, + "url": { + "raw": "{{base_url}}/battlepass/{{steam_id}}/quests/claim", + "host": ["{{base_url}}"], + "path": ["battlepass", "{{steam_id}}", "quests", "claim"] + } + }, + "response": [] + }, + { + "name": "Record Hero Played", + "request": { + "method": "POST", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "x-custom-key", + "value": "{{x-custom-key}}", + "type": "text" + } + ], + "body": { + "mode": "raw", + "raw": "{\n \"hero_name\": \"npc_dota_hero_axe\"\n}" + }, + "url": { + "raw": "{{base_url}}/battlepass/{{steam_id}}/hero-played", + "host": ["{{base_url}}"], + "path": ["battlepass", "{{steam_id}}", "hero-played"] + } + }, + "response": [] + } + ] + }, + { + "name": "Payments", + "item": [ + { + "name": "Create Robokassa Payment Link", + "request": { + "method": "POST", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + } + ], + "body": { + "mode": "raw", + "raw": "{\n \"steam_id\": \"{{steam_id}}\",\n \"amount_rub\": 100\n}" + }, + "url": { + "raw": "{{base_url}}/payments/robokassa/link", + "host": ["{{base_url}}"], + "path": ["payments", "robokassa", "link"] + } + }, + "response": [] + }, + { + "name": "Create Bundle Payment Link", + "request": { + "method": "POST", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "x-custom-key", + "value": "{{x-custom-key}}", + "type": "text" + } + ], + "body": { + "mode": "raw", + "raw": "{\n \"steam_id\": \"{{steam_id}}\",\n \"bundle_id\": \"starter_bundle\"\n}" + }, + "url": { + "raw": "{{base_url}}/payments/bundles/link", + "host": ["{{base_url}}"], + "path": ["payments", "bundles", "link"] + } + }, + "response": [] + }, + { + "name": "Get Deals Catalog", + "request": { + "method": "GET", + "header": [ + { + "key": "x-custom-key", + "value": "{{x-custom-key}}", + "type": "text" + } + ], + "url": { + "raw": "{{base_url}}/payments/deals?steam_id={{steam_id}}", + "host": ["{{base_url}}"], + "path": ["payments", "deals"], + "query": [ + {"key": "steam_id", "value": "{{steam_id}}"} + ] + } + }, + "response": [] + } + ] + }, + { + "name": "Cards", + "item": [ + { + "name": "Get Card Levels", + "request": { + "method": "GET", + "header": [ + { + "key": "x-custom-key", + "value": "{{x-custom-key}}", + "type": "text" + } + ], + "url": { + "raw": "{{base_url}}/player/{{steam_id}}/card-levels", + "host": ["{{base_url}}"], + "path": ["player", "{{steam_id}}", "card-levels"] + } + }, + "response": [] + }, + { + "name": "Update Card Levels", + "request": { + "method": "PUT", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "x-custom-key", + "value": "{{x-custom-key}}", + "type": "text" + } + ], + "body": { + "mode": "raw", + "raw": "{\n \"card_levels\": {\n \"1\": 3,\n \"2\": 1\n }\n}" + }, + "url": { + "raw": "{{base_url}}/player/{{steam_id}}/card-levels", + "host": ["{{base_url}}"], + "path": ["player", "{{steam_id}}", "card-levels"] + } + }, + "response": [] + }, + { + "name": "Get All Decks", + "request": { + "method": "GET", + "header": [ + { + "key": "x-custom-key", + "value": "{{x-custom-key}}", + "type": "text" + } + ], + "url": { + "raw": "{{base_url}}/player/{{steam_id}}/decks", + "host": ["{{base_url}}"], + "path": ["player", "{{steam_id}}", "decks"] + } + }, + "response": [] + }, + { + "name": "Get Deck by Index", + "request": { + "method": "GET", + "header": [ + { + "key": "x-custom-key", + "value": "{{x-custom-key}}", + "type": "text" + } + ], + "url": { + "raw": "{{base_url}}/player/{{steam_id}}/decks/0", + "host": ["{{base_url}}"], + "path": ["player", "{{steam_id}}", "decks", "0"] + } + }, + "response": [] + }, + { + "name": "Save Deck", + "request": { + "method": "PUT", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "x-custom-key", + "value": "{{x-custom-key}}", + "type": "text" + } + ], + "body": { + "mode": "raw", + "raw": "{\n \"name\": \"My Deck\",\n \"cards\": [1, 2, 3, 4, 5]\n}" + }, + "url": { + "raw": "{{base_url}}/player/{{steam_id}}/decks/0", + "host": ["{{base_url}}"], + "path": ["player", "{{steam_id}}", "decks", "0"] + } + }, + "response": [] + } + ] + }, + { + "name": "Equipment", + "item": [ + { + "name": "Get Equipment State", + "request": { + "method": "GET", + "header": [ + { + "key": "x-custom-key", + "value": "{{x-custom-key}}", + "type": "text" + } + ], + "url": { + "raw": "{{base_url}}/player/{{steam_id}}/equipment", + "host": ["{{base_url}}"], + "path": ["player", "{{steam_id}}", "equipment"] + } + }, + "response": [] + }, + { + "name": "Save Equipment State", + "request": { + "method": "PUT", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "x-custom-key", + "value": "{{x-custom-key}}", + "type": "text" + } + ], + "body": { + "mode": "raw", + "raw": "{\n \"equipment\": {\n \"weapon\": \"sword_t1\",\n \"armor\": \"plate_t2\"\n }\n}" + }, + "url": { + "raw": "{{base_url}}/player/{{steam_id}}/equipment", + "host": ["{{base_url}}"], + "path": ["player", "{{steam_id}}", "equipment"] + } + }, + "response": [] + }, + { + "name": "Post Equipment Drop", + "request": { + "method": "POST", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "x-custom-key", + "value": "{{x-custom-key}}", + "type": "text" + } + ], + "body": { + "mode": "raw", + "raw": "{\n \"item\": {\n \"name\": \"helmet_t3\",\n \"rarity\": \"epic\"\n }\n}" + }, + "url": { + "raw": "{{base_url}}/player/{{steam_id}}/equipment/drop", + "host": ["{{base_url}}"], + "path": ["player", "{{steam_id}}", "equipment", "drop"] + } + }, + "response": [] + } + ] + }, + { + "name": "Arsenal", + "item": [ + { + "name": "Get Arsenal Loadouts", + "request": { + "method": "GET", + "header": [ + { + "key": "x-custom-key", + "value": "{{x-custom-key}}", + "type": "text" + } + ], + "url": { + "raw": "{{base_url}}/player/{{steam_id}}/arsenal_loadouts", + "host": ["{{base_url}}"], + "path": ["player", "{{steam_id}}", "arsenal_loadouts"] + } + }, + "response": [] + }, + { + "name": "Save Arsenal Loadouts", + "request": { + "method": "PUT", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "x-custom-key", + "value": "{{x-custom-key}}", + "type": "text" + } + ], + "body": { + "mode": "raw", + "raw": "{\n \"arsenal_loadouts\": {\n \"npc_dota_hero_axe\": {\n \"weapon\": \"ars_abc123\",\n \"armor\": \"ars_def456\"\n }\n }\n}" + }, + "url": { + "raw": "{{base_url}}/player/{{steam_id}}/arsenal_loadouts", + "host": ["{{base_url}}"], + "path": ["player", "{{steam_id}}", "arsenal_loadouts"] + } + }, + "response": [] + }, + { + "name": "Get Arsenal Inventory", + "request": { + "method": "GET", + "header": [ + { + "key": "x-custom-key", + "value": "{{x-custom-key}}", + "type": "text" + } + ], + "url": { + "raw": "{{base_url}}/player/{{steam_id}}/arsenal_inventory", + "host": ["{{base_url}}"], + "path": ["player", "{{steam_id}}", "arsenal_inventory"] + } + }, + "response": [] + }, + { + "name": "Save Arsenal Inventory", + "request": { + "method": "PUT", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "x-custom-key", + "value": "{{x-custom-key}}", + "type": "text" + } + ], + "body": { + "mode": "raw", + "raw": "{\n \"arsenal_inventory\": {\n \"instances\": {\n \"ars_abc123\": {\n \"instanceId\": \"ars_abc123\",\n \"itemName\": \"sword_of_doom\",\n \"quality\": \"legendary\",\n \"upgradeLevel\": 3,\n \"serial\": 42,\n \"globalSerial\": 999,\n \"ownerName\": \"TestPlayer\",\n \"pinned\": true,\n \"favorite\": false,\n \"stats\": []\n }\n }\n }\n}" + }, + "url": { + "raw": "{{base_url}}/player/{{steam_id}}/arsenal_inventory", + "host": ["{{base_url}}"], + "path": ["player", "{{steam_id}}", "arsenal_inventory"] + } + }, + "response": [] + } + ] + }, + { + "name": "Arsenal Marketplace", + "item": [ + { + "name": "Get Public Listings", + "request": { + "method": "GET", + "header": [ + { + "key": "x-custom-key", + "value": "{{x-custom-key}}", + "type": "text" + } + ], + "url": { + "raw": "{{base_url}}/arsenal_market/listings?stats=bonus_damage,attack_speed", + "host": ["{{base_url}}"], + "path": ["arsenal_market", "listings"], + "query": [ + {"key": "stats", "value": "bonus_damage,attack_speed", "disabled": true} + ] + } + }, + "response": [] + }, + { + "name": "Get My Listings", + "request": { + "method": "GET", + "header": [ + { + "key": "x-custom-key", + "value": "{{x-custom-key}}", + "type": "text" + } + ], + "url": { + "raw": "{{base_url}}/player/{{steam_id}}/arsenal_market/my_listings", + "host": ["{{base_url}}"], + "path": ["player", "{{steam_id}}", "arsenal_market", "my_listings"] + } + }, + "response": [] + }, + { + "name": "Get Market Slots", + "request": { + "method": "GET", + "header": [ + { + "key": "x-custom-key", + "value": "{{x-custom-key}}", + "type": "text" + } + ], + "url": { + "raw": "{{base_url}}/player/{{steam_id}}/arsenal_market/slots", + "host": ["{{base_url}}"], + "path": ["player", "{{steam_id}}", "arsenal_market", "slots"] + } + }, + "response": [] + }, + { + "name": "Get Sales History", + "request": { + "method": "GET", + "header": [ + { + "key": "x-custom-key", + "value": "{{x-custom-key}}", + "type": "text" + } + ], + "url": { + "raw": "{{base_url}}/player/{{steam_id}}/arsenal_market/sales", + "host": ["{{base_url}}"], + "path": ["player", "{{steam_id}}", "arsenal_market", "sales"] + } + }, + "response": [] + }, + { + "name": "Create Listing", + "request": { + "method": "POST", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "x-custom-key", + "value": "{{x-custom-key}}", + "type": "text" + } + ], + "body": { + "mode": "raw", + "raw": "{\n \"instance_id\": \"ars_abc123\",\n \"instanceId\": \"ars_abc123\",\n \"item_instance_id\": \"ars_abc123\",\n \"itemInstanceId\": \"ars_abc123\",\n \"serial\": 42,\n \"global_serial\": 999,\n \"globalSerial\": 999,\n \"item_name\": \"sword_of_doom\",\n \"itemName\": \"sword_of_doom\",\n \"quality\": \"legendary\",\n \"upgrade_level\": 3,\n \"upgradeLevel\": 3,\n \"price_free\": 5000,\n \"priceFree\": 5000,\n \"request_id\": \"market_create_001\",\n \"requestId\": \"market_create_alt_001\"\n}" + }, + "url": { + "raw": "{{base_url}}/player/{{steam_id}}/arsenal_market/create", + "host": ["{{base_url}}"], + "path": ["player", "{{steam_id}}", "arsenal_market", "create"] + } + }, + "response": [] + }, + { + "name": "Buy Listing", + "request": { + "method": "POST", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "x-custom-key", + "value": "{{x-custom-key}}", + "type": "text" + } + ], + "body": { + "mode": "raw", + "raw": "{\n \"listing_id\": \"list_xyz789\",\n \"request_id\": \"market_buy_001\"\n}" + }, + "url": { + "raw": "{{base_url}}/player/{{steam_id}}/arsenal_market/buy", + "host": ["{{base_url}}"], + "path": ["player", "{{steam_id}}", "arsenal_market", "buy"] + } + }, + "response": [] + }, + { + "name": "Cancel Listing", + "request": { + "method": "POST", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "x-custom-key", + "value": "{{x-custom-key}}", + "type": "text" + } + ], + "body": { + "mode": "raw", + "raw": "{\n \"listing_id\": \"list_xyz789\",\n \"request_id\": \"market_cancel_001\"\n}" + }, + "url": { + "raw": "{{base_url}}/player/{{steam_id}}/arsenal_market/cancel", + "host": ["{{base_url}}"], + "path": ["player", "{{steam_id}}", "arsenal_market", "cancel"] + } + }, + "response": [] + } + ] + }, + { + "name": "Death Sentence Contracts", + "item": [ + { + "name": "Get Contracts", + "request": { + "method": "GET", + "header": [ + { + "key": "x-custom-key", + "value": "{{x-custom-key}}", + "type": "text" + } + ], + "url": { + "raw": "{{base_url}}/player/{{steam_id}}/death_sentence_contracts", + "host": ["{{base_url}}"], + "path": ["player", "{{steam_id}}", "death_sentence_contracts"] + } + }, + "response": [] + }, + { + "name": "Save Contracts", + "request": { + "method": "PUT", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "x-custom-key", + "value": "{{x-custom-key}}", + "type": "text" + } + ], + "body": { + "mode": "raw", + "raw": "{\n \"death_sentence_contracts\": {\n \"roster\": [\n {\n \"instanceId\": \"dsc_abc123\",\n \"serial\": 1,\n \"titleIndex\": 0,\n \"rarity\": \"epic\",\n \"rewardMultiplier\": 2.5,\n \"traitId\": \"none\",\n \"complicationIds\": [\"comp_fire\", \"comp_poison\"],\n \"durability\": 3,\n \"durabilityMax\": 3,\n \"pinned\": false,\n \"favorite\": true\n }\n ]\n }\n}" + }, + "url": { + "raw": "{{base_url}}/player/{{steam_id}}/death_sentence_contracts", + "host": ["{{base_url}}"], + "path": ["player", "{{steam_id}}", "death_sentence_contracts"] + } + }, + "response": [] + } + ] + } + ], + "variable": [ + { + "key": "base_url", + "value": "http://82.146.52.69:3000/api", + "type": "string" + }, + { + "key": "x-custom-key", + "value": "menya_ebut_negry_tolpoy", + "type": "string" + }, + { + "key": "steam_id", + "value": "76561198000000001", + "type": "string" + }, + { + "key": "game_id", + "value": "12345", + "type": "string" + } + ] +} diff --git a/resource/addon_english.txt b/resource/addon_english.txt new file mode 100644 index 0000000..60b4ce9 --- /dev/null +++ b/resource/addon_english.txt @@ -0,0 +1,5063 @@ +"lang" +{ +"Language" "Russian" +"Tokens" +{ + + //FOR ME + // To create a new key ( like $health ) just add the entry here + "dota_ability_variable_health" "Health" // $health + "dota_ability_variable_mana" "Mana" // $mana + "dota_ability_variable_armor" "Armor" // $armor + "dota_ability_variable_damage" "Damage" // $damage + "dota_ability_variable_str" "Strength" // $str + "dota_ability_variable_int" "Intelligence" // $int + "dota_ability_variable_agi" "Agility" // $agi + "dota_ability_variable_all" "All Attributes" // $all + "dota_ability_variable_primary_attribute" "Primary Attribute" // $primary_attribute + "dota_ability_variable_attack" "Attack Speed" // $attack + "dota_ability_variable_attack_pct" "Base Attack Speed Percentage" // $attack_pct + "dota_ability_variable_hp_regen" "Health Regeneration" // $hp_regen + "dota_ability_variable_lifesteal" "Lifesteal" // $lifesteal + "dota_ability_variable_mana_regen" "Mana Regeneration" // $mana_regen + "dota_ability_variable_mana_regen_aura" "Mana Regeneration Aura" // $mana_regen_aura + "dota_ability_variable_spell_amp" "Spell Damage" // $spell_amp + "dota_ability_variable_debuff_amp" "Debuff Duration" // $debuff_amp + "dota_ability_variable_move_speed" "Movement Speed" // $move_speed + "dota_ability_variable_evasion" "Evasion" // $evasion + "dota_ability_variable_spell_resist" "Magic Resistance" // $spell_resist + "dota_ability_variable_spell_lifesteal" "Spell Lifesteal" // $spell_lifesteal + "dota_ability_variable_spell_lifesteal_creep" "Spell Lifesteal (Creeps)" // $spell_lifesteal_creep + "dota_ability_variable_spell_lifesteal_hero" "Spell Lifesteal (Heroes)" // $spell_lifesteal_hero + "dota_ability_variable_selected_attrib" "Selected Attribute" // $selected_attribute + "dota_ability_variable_attack_range" "Attack Range (Ranged Only)" // $attack_range + "dota_ability_variable_attack_range_melee" "Attack Range (Melee Only)" // $attack_range_melee + "dota_ability_variable_cast_range" "Cast Range" // $cast_range + "dota_ability_variable_status_resist" "Status Resistance" // $status_resist + "dota_ability_variable_projectile_speed" "Projectile Speed" // $projectile_speed + "dota_ability_variable_manacost_reduction" "Manacost Reduction" // $manacost_reduction + "dota_ability_variable_cooldown_reduction" "Cooldown Reduction" // $cooldown_reduction + "dota_ability_variable_max_mana_percentage" "Max Mana Bonus" // $max_mana_percentage + "dota_ability_variable_slow_resistance" "Slow Resistance" // $slow_resistance + "dota_ability_variable_aoe_bonus" "AoE Bonus" // $aoe_bonus + "text_cheat_detected" "EgardAntiCheat.exe" + "dota_ability_variable_damage_int_hero" "Damage (Intelligence Heroes)" + "dota_ability_variable_damage_agi_hero" "Damage (Agility Heroes)" + "dota_ability_variable_damage_str_hero" "Damage (Strength Heroes)" + "dota_ability_variable_damage_universal_hero" "Damage (Universal Heroes)" + "dota_ability_variable_crit_multiplier" "critical strike multiplier" //$crit_multiplier + "dota_ability_variable_crit_chance" "critical strike chance" //$crit_chance + "dota_ability_variable_crit_mult" "critical strike damage" //$crit_mult + "dota_ability_variable_spell_crit_mult" "critical magical strike chance" + "dota_ability_variable_spell_crit_chance" "critical magical strike damage" + + +"addon_game_name" "ZOMBIE INVASION 2" +"cooldown_message" "Recharge:" + +"zinv_item_build_night_1" "Night 1" +"zinv_item_build_night_2" "Night 2" +"zinv_item_build_night_3" "Night 3" +"zinv_item_build_night_4" "Night 4" + +"ZOMIEINVASION_NEWS_TITLE" "ZOMBIE INVASION 2 NEWS FEED" + +"ZINV_NEWS_DEMO_TITLE" "Demo: All the Features of News" +"ZINV_NEWS_DEMO_BODY" "Example card: text, pictures, rows and icons of objects." +"ZINV_NEWS_DEMO_DETAILS" "Welcome to the demo post! Below are all layout options.\n\n A regular block of text with line wraps. You can write lists:\n - Point 1\n- Point 2\n\nInsert a large picture by location:\n [img=file://{images}/custom_game/loading_screen/loadscreen.png]\n\n Row: picture on the left, text on the right:\n[imgrow=file://{images}/custom_game/loading_screen/loadscreen.png]\n This is an example of a long description to the right of the image. Here you can talk about features, give numbers, give examples of use\n and any additional information.\n[/imgrow]\n\nIcons of items with dimensions:\n[item=blink] — updated cd.\n [item=ultimate_scepter] — bonus redesigned.\n[item=aghanims_shard] — new upgrades.\n\n If there is no [img=...] in the text, fallback from the image field will work. Thanks for your attention!" + +"ZINV_NEWS_START_TITLE" "Welcome to the news feed!" +"ZINV_NEWS_START_BODY" "We launched a news feed. Follow the updates here." +"ZINV_NEWS_START_DETAILS" "Welcome to the news feed!\n\n Now all important changes, fixes and plans will appear here.\n\n Future plans:\n - UI improvement\n - Balance Edits\n - New game pieces\n - Creating new characters\n\n\n Thanks for playing!" + +//additional +"additional" "ADDITIONAL" +"additional_luck" "Luck:" +"additional_vampirism" "Phys. Vampirism: " +"additional_magic_vampirism" "Magician. Vampirism: " +"additional_crit_mult" "Crirical multiplier:" + + + "difficulty_select_title" "Select difficulty:" + "difficulty_selected" "Complexity selected" + "#easy" "Easy" + "#normal" "Regular" + "#hard" "Complex" + "#impossible" "Impossible" + "easy" "Easy" + "normal" "Regular" + "hard" "Complex" + "impossible" "Impossible" + "death_sentence" "Death Sentence" + "contract_vote_slots_title" "Sentences in vote" + "contract_menu_title" "My sentences" + "contract_menu_title_mine" "Your sentences:" + "contract_menu_empty" "No sentences yet" + "contract_stats_multiplier" "Stats multiplier" + "contract_reward_bonus" "Reward bonus" + + "death_sentence_contracts_title" "Death Sentence Sentences" + "death_sentence_contracts_hint" "" + "death_sentence_contracts_inventory_section" "Contract Inventory" + + "death_sentence_contracts_tooltip_kind" "Sentence" + + "death_sentence_contracts_title" "Death Sentence Sentences" + "death_sentence_contracts_hint" "" + "death_sentence_contracts_inventory_section" "Contract Inventory" + "death_sentence_contracts_tooltip_kind" "Sentence" + "death_sentence_contracts_tooltip_reward" "Reward multiplier:" + "death_sentence_contracts_tooltip_multiplier" "Multiplier:" + "death_sentence_contracts_tooltip_durability" "Durability:" + "death_sentence_complication_explode" "On spawn, with a 25% chance the enemy will gain: On death, explodes dealing physical damage in a radius." + "death_sentence_complication_creep_stats" "Max health, attack damage, and health regen of creeps ×1.25." + "death_sentence_complication_brutal_strikes" "Enemy base attack damage is twice as high." + "death_sentence_complication_zombie_virus" "On spawn, with a 25% chance the enemy will gain: Attacks have a chance to inflict poison and slow on hit." + "death_sentence_complication_ghost_evasive" "On spawn, with a 25% chance the enemy will gain: All enemies have free movement and +33% evasion." + "death_sentence_complication_phasing_march" "On spawn, with a 25% chance the enemy will gain: Pass through other units and +100 movement speed." + "death_sentence_complication_zombie_armor_decress" "On spawn, with a 25% chance the enemy will gain: Attacks apply a brief armor reduction on the target." + "death_sentence_complication_toxin" "On spawn, with a 25% chance the enemy will gain: On death, leaves a neutral neurotoxin in a radius." + "death_sentence_complication_full_brutality" "On spawn, with a 25% chance the enemy will gain: Creeps suddenly become twice as strong." + "death_sentence_complication_desperate_vampirism" "On spawn, with a 25% chance the enemy will gain: Below half health, the enemy heals on attack." + "death_sentence_complication_head_leap" "On spawn, with a 25% chance the enemy will gain: Enemies periodically leap in an arc toward where a hero stood, dealing damage in the landing zone." + "death_sentence_complication_bone_armor" "On spawn, with a 25% chance the enemy will gain: Bonus armor. Against ranged heroes: if no hero is within 450, the attacker takes 5× the damage dealt; otherwise a chance to reflect part of the damage." + "death_sentence_complication_short_day" "The daytime phase is one minute shorter." + "death_sentence_complication_scarce_loot" "Chance for items from killed creatures on the global drop table is twice as low." + + + + + "death_sentence_dismantle_ok" "Sentence dismantled: +{d} dust." + "death_sentence_dismantle_fail" "Could not dismantle the sentence." + "death_sentence_dismantle_batch_ok" "Dismantled {n} sentences: +{d} dust." + "death_sentence_dismantle_batch_skipped_pinned" "({n} skipped — pinned.)" + "death_sentence_repair_ok" "Sentence durability restored to full." + "death_sentence_repair_fail" "Could not restore sentence durability." + "death_sentence_repair_fail_funds" "Not enough donate shards." + "death_sentence_repair_fail_full" "Durability is already full." + "death_sentence_repair_fail_state" "Only available in the lobby before the match starts." + "death_sentence_repair_fail_not_found" "Sentence not found." + "death_sentence_repair_fail_save" "Could not save changes. Donate shards were refunded." + "death_sentence_repair_confirm_body" "Spend {d} donate shards to fully restore this sentence's durability?" + "death_sentence_repair_confirm_yes" "Repair" + "death_sentence_repair_confirm_cancel" "Cancel" + "death_sentence_contracts_context_dismantle" "" + "death_sentence_detail_vote" "Vote for this sentence" + "death_sentence_detail_unvote" "Clear vote" + "death_sentence_detail_lobby_showcase_set" "Show in lobby row" + "death_sentence_detail_lobby_showcase_clear" "Remove from lobby row" + "death_sentence_detail_dismantle" "Dismantle" + "death_sentence_detail_repair" "Repair" + "death_sentence_detail_actions_hint" "" + "death_sentence_detail_debuff_count" "Wave complication slots: %d1" + "death_sentence_contract_tile_hover_hint" "" + "death_sentence_contracts_grid_lore_hint" "" + "death_sentence_rarity_common" "Common" + "death_sentence_rarity_rare" "Rare" + "death_sentence_rarity_epic" "Epic" + "death_sentence_rarity_legendary" "Legendary" + "death_sentence_rarity_mythic" "Mythic" + "ds_sentence_title_001" "Sentence of total annihilation" + "ds_sentence_title_002" "Decree of total annihilation" + "ds_sentence_title_003" "Verdict of total annihilation" + "ds_sentence_title_004" "Writ of total annihilation" + "ds_sentence_title_005" "Mandate of total annihilation" + "ds_sentence_title_006" "Edict of total annihilation" + "ds_sentence_title_007" "Judgment of total annihilation" + "ds_sentence_title_008" "Order of total annihilation" + "ds_sentence_title_009" "Warrant of total annihilation" + "ds_sentence_title_010" "Ruling of total annihilation" + "ds_sentence_title_011" "Sentence of endless siege" + "ds_sentence_title_012" "Decree of endless siege" + "ds_sentence_title_013" "Verdict of endless siege" + "ds_sentence_title_014" "Writ of endless siege" + "ds_sentence_title_015" "Mandate of endless siege" + "ds_sentence_title_016" "Edict of endless siege" + "ds_sentence_title_017" "Judgment of endless siege" + "ds_sentence_title_018" "Order of endless siege" + "ds_sentence_title_019" "Warrant of endless siege" + "ds_sentence_title_020" "Ruling of endless siege" + "ds_sentence_title_021" "Sentence of the red zone" + "ds_sentence_title_022" "Decree of the red zone" + "ds_sentence_title_023" "Verdict of the red zone" + "ds_sentence_title_024" "Writ of the red zone" + "ds_sentence_title_025" "Mandate of the red zone" + "ds_sentence_title_026" "Edict of the red zone" + "ds_sentence_title_027" "Judgment of the red zone" + "ds_sentence_title_028" "Order of the red zone" + "ds_sentence_title_029" "Warrant of the red zone" + "ds_sentence_title_030" "Ruling of the red zone" + "ds_sentence_title_031" "Sentence of the black blockade" + "ds_sentence_title_032" "Decree of the black blockade" + "ds_sentence_title_033" "Verdict of the black blockade" + "ds_sentence_title_034" "Writ of the black blockade" + "ds_sentence_title_035" "Mandate of the black blockade" + "ds_sentence_title_036" "Edict of the black blockade" + "ds_sentence_title_037" "Judgment of the black blockade" + "ds_sentence_title_038" "Order of the black blockade" + "ds_sentence_title_039" "Warrant of the black blockade" + "ds_sentence_title_040" "Ruling of the black blockade" + "ds_sentence_title_041" "Sentence of total quarantine" + "ds_sentence_title_042" "Decree of total quarantine" + "ds_sentence_title_043" "Verdict of total quarantine" + "ds_sentence_title_044" "Writ of total quarantine" + "ds_sentence_title_045" "Mandate of total quarantine" + "ds_sentence_title_046" "Edict of total quarantine" + "ds_sentence_title_047" "Judgment of total quarantine" + "ds_sentence_title_048" "Order of total quarantine" + "ds_sentence_title_049" "Warrant of total quarantine" + "ds_sentence_title_050" "Ruling of total quarantine" + "ds_sentence_title_051" "Sentence of absolute isolation" + "ds_sentence_title_052" "Decree of absolute isolation" + "ds_sentence_title_053" "Verdict of absolute isolation" + "ds_sentence_title_054" "Writ of absolute isolation" + "ds_sentence_title_055" "Mandate of absolute isolation" + "ds_sentence_title_056" "Edict of absolute isolation" + "ds_sentence_title_057" "Judgment of absolute isolation" + "ds_sentence_title_058" "Order of absolute isolation" + "ds_sentence_title_059" "Warrant of absolute isolation" + "ds_sentence_title_060" "Ruling of absolute isolation" + "ds_sentence_title_061" "Sentence of merciless purge" + "ds_sentence_title_062" "Decree of merciless purge" + "ds_sentence_title_063" "Verdict of merciless purge" + "ds_sentence_title_064" "Writ of merciless purge" + "ds_sentence_title_065" "Mandate of merciless purge" + "ds_sentence_title_066" "Edict of merciless purge" + "ds_sentence_title_067" "Judgment of merciless purge" + "ds_sentence_title_068" "Order of merciless purge" + "ds_sentence_title_069" "Warrant of merciless purge" + "ds_sentence_title_070" "Ruling of merciless purge" + "ds_sentence_title_071" "Sentence of frontline liquidation" + "ds_sentence_title_072" "Decree of frontline liquidation" + "ds_sentence_title_073" "Verdict of frontline liquidation" + "ds_sentence_title_074" "Writ of frontline liquidation" + "ds_sentence_title_075" "Mandate of frontline liquidation" + "ds_sentence_title_076" "Edict of frontline liquidation" + "ds_sentence_title_077" "Judgment of frontline liquidation" + "ds_sentence_title_078" "Order of frontline liquidation" + "ds_sentence_title_079" "Warrant of frontline liquidation" + "ds_sentence_title_080" "Ruling of frontline liquidation" + "ds_sentence_title_081" "Sentence of no quarter" + "ds_sentence_title_082" "Decree of no quarter" + "ds_sentence_title_083" "Verdict of no quarter" + "ds_sentence_title_084" "Writ of no quarter" + "ds_sentence_title_085" "Mandate of no quarter" + "ds_sentence_title_086" "Edict of no quarter" + "ds_sentence_title_087" "Judgment of no quarter" + "ds_sentence_title_088" "Order of no quarter" + "ds_sentence_title_089" "Warrant of no quarter" + "ds_sentence_title_090" "Ruling of no quarter" + "ds_sentence_title_091" "Sentence of burning bridges" + "ds_sentence_title_092" "Decree of burning bridges" + "ds_sentence_title_093" "Verdict of burning bridges" + "ds_sentence_title_094" "Writ of burning bridges" + "ds_sentence_title_095" "Mandate of burning bridges" + "ds_sentence_title_096" "Edict of burning bridges" + "ds_sentence_title_097" "Judgment of burning bridges" + "ds_sentence_title_098" "Order of burning bridges" + "ds_sentence_title_099" "Warrant of burning bridges" + "ds_sentence_title_100" "Ruling of burning bridges" + + "death_sentence_contracts_compact_hint" "" + "death_sentence_contracts_ds_row_tooltip" "" + "death_sentence_contracts_preview_empty_tooltip" "" + "death_sentence_contracts_preview_lobby_slot_empty" "" + "death_sentence_contracts_click_to_change" "" + "death_sentence_contracts_open_btn" "§" + "death_sentence_contracts_open_tooltip" "" + "death_sentence_contracts_propose_btn" "Propose sentence" + "death_sentence_contracts_valid_short" "Counted:" + "death_sentence_contracts_total_short" "Total:" + "death_sentence_contracts_tooltip_votes_inline" "Votes" + "death_sentence_contracts_votes_legend" "counted · total" + "death_sentence_contracts_slot_not_owned_tooltip" "" + "ds_contract_iron" "Iron Covenant" + "ds_contract_iron_desc" "Baseline contract: no extra creep modifiers beyond Death Sentence. Final match reward multiplier: ×3.5 (common band ~3–4)." + "ds_contract_blood" "Blood Pact" + "ds_contract_blood_desc" "Enemies gain +1 base armor on spawn. Final match reward multiplier: ×4.5 (rare band ~4–5)." + "death_sentence_contracts_effect_iron_1" "No extra creep modifiers beyond Death Sentence." + + "death_sentence_contracts_effect_iron_2" "Pure Death Sentence: enemy wave multiplier ×6." + + "death_sentence_contracts_effect_blood_1" "Enemy units spawn with +1 base armor." + + "death_sentence_contracts_effect_blood_2" "Harder early wave clearing." + + "ds_contract_shadow" "Shadow Shroud" + "ds_contract_shadow_desc" "Enemies spawn with +300 bonus base movement speed. Final match reward multiplier: ×5.5 (epic band ~5–6)." + + "ds_contract_fury" "Pack Fury" + "ds_contract_fury_desc" "Legendary sentence: final match reward multiplier ×6.5 (band ~6–7). Random wave complication count scales with rarity: common 0, rare 1, epic 2, legendary 3, mythic 4." + + "death_sentence_contracts_effect_shadow_1" "Enemy units spawn with +300 base movement speed." + + "death_sentence_contracts_effect_shadow_2" "Harder to kite down large packs without control." + + "death_sentence_contracts_effect_fury_1" "Enemy min/max base attack damage multiplied by about ×1.08." + + "death_sentence_contracts_effect_fury_2" "More melee pressure if you let waves stack." + + +"difficulty_description_easy" "♡Easy difficulty: ──────────────────────────────────────────

Great for Familiarization.

─────────────────────────────────────────

Features:

-50% Health, damage, restore health to all enemies.

+60/-60 seconds day phase and night phase

─────────────────────────────────────────

For a greater immersion, it is recommended to choose a higher difficulty.

─────────────────────────────────────────

" +"difficulty_description_normal" "Normal difficulty: ──────────────────────────────────────────

Great for beginners.

─────────────────────────────────────────

Features:

Enemy statuses have not been changed.

─────────────────────────────────────────

It is recommended to play on this difficulty.
─────────────────────────────────────────" +"difficulty_description_hard" "Heavy complexity: ──────────────────────────────────────────

Great for players who are already trained.

─────────────────────────────────────────

Features:

+200% Health, damage, restore health to all enemies.

-60/+60 seconds day phase and night phase

─────────────────────────────────────────

Run this difficulty only if you know what do.

─────────────────────────────────────────

" +"difficulty_description_impossible" "AHAHAHAHAHAHAHAHAHAAHAHAHA
AHAHAHAHAHAHAHAHAHAAHAHAHA
AHAHAHAHAHAHAHAHAHAAHAHAHA
AHAHAHAHAHAHAHAHAHAAHAHAHA
AHAHAHAHAHAHAHAHAHAAHAHAHA
AHAHAHAHAHAHAHAHAHAAHAHAHA
AHAHAHAHAHAHAHAHAHAAHAHAHA
AHAHAHAHAHAHAHAHAHAAHAHAHA
AHAHAHAHAHAHAHAHAHAAHAHAHA AHAHAHAHAHAHAHAHAHAAHAHAHA AHAHAHAHAHAHAHAHAHAAHAHAHA AHAHAHAHAHAHAHAHAHAAHAHAHA AHAHAHAHAHAHAHAHAHAAHAHAHA AHAHAHAHAHAHAHAHAHAAHAHAHA AHAHAHAHAHAHAHAHAHAAHAHAHA AHAHAHAHAHAHAHAHAHAAHAHAHA AHAHAHAHAHAHAHAHAHAAHAHAHA AHAHAHAHAHAHAHAHAHAAHAHAHA AHAHAHAHAHAHAHAHAHAAHAHAHA AHAHAHAHAHAHAHAHAHAAHAHAHA AHAHAHAHAHAHAHAHAHAAHAHAHA AHAHAHAHAHAHAHAHAHAAHAHAHA AHAHAHAHAHAHAHAHAHAAHAHAHA AHAHAHAHAHAHAHAHAHAAHAHAHA AHAHAHAHAHAHAHAHAHAAHAHAHA AHAHAHAHAHAHAHAHAHAAHAHAHA AHAHAHAHAHAHAHAHAHAAHAHAHA AHAHAHAHAHAHAHAHAHAAHAHAHA " +"difficulty_description_death_sentence" "Death Sentence:

Contract mode. Choose your contract below and put it into global voting. Contracts increase enemy stat multiplier, add team debuffs and grant extra reward bonuses." + +//Social links +"social_discord" "Discord" +"social_boosty" "Boosty" + +"start_game" "Start game" +"cancel_game" "Cancel" +"ready_status_ready" "Ready" +"ready_status_not_ready" "Not ready" +"ready_button_ready" "Not ready" +"ready_button_not_ready" "Ready" +"setup_hints_title" "Tips" +"setup_hint_fallback_empty" "This tip is not filled yet." +"setup_hint_1" "Every second of daytime matters. Try to do as much as possible before night starts." +"setup_hint_2" "Enemies become 2% stronger every minute, so do not fall behind in power." +"setup_hint_3" "Try to build card combinations with good synergy, but remember that all resources are limited." +"setup_hint_4" "Hold Ctrl to make your hero automatically pick up items from the ground." +"setup_hint_5" "Zombie Invasion 3 is a team game, so playing with friends and good teamwork gives the best results." +"setup_hint_6" "Click the top timer to start a vote for early night (this prevents enemies from getting extra scaling over time)." +"setup_hint_7" "Every hero is strong in their own way, both solo and in team play. Support your tanks and damage dealers when you play support." +"setup_hint_8" "Fire and frost stacks cancel each other: when a target already has many fire stacks, new frost first removes part of them." +"setup_hint_9" "Frost: each stack reduces move and attack speed by 1%; at 30+ stacks the target takes extra incoming damage, and at 100 stacks it becomes frozen." +"setup_hint_10" "Hunger is a food buff: stacks (up to 100) grant bonus base attributes and decay over time." +"setup_hint_11" "Do not forget to spend crystals in the Black Market: you can get cards, useful buffs, and other valuable offers there." +"setup_hint_12" "Helping NPCs is very useful: it is an easy source of income and a way to get special rewards." +"setup_hint_13" "This custom game has unique stats, such as Luck, which increases the chance of other percentage-based effects." +"setup_hint_14" "If winning feels hard, start by learning the map in a practice game with cheats." +"setup_hint_15" "New promo codes are often posted in Discord and Telegram, so keep an eye on them for free currency." +"setup_hint_16" "If you do not have many cards at the start, focus on other gameplay aspects that still let you play effectively." +"setup_hint_17" "This mode uses custom lifesteal, so it works a bit differently than in regular Dota." +"setup_hint_18" "If you need advice from experienced players, visit the Discord channel (you can find it on the custom game's main page)." +"setup_hint_19" "Found a bug? Report it in the bug-reports channel on Discord." +"setup_hint_20" "Rage is used by some heroes: it gives +0.25% outgoing damage per rage point, and rage heroes can use items without spending mana." +"setup_hint_21" "The simplest way to win is to keep trying and play to the end. With effort, you will get there." +"setup_hint_22" "Localization issues can happen. If you find one, report it in Discord and it will be fixed sooner or later." +"setup_hint_23" "On Impossible difficulty, wave enemies have a 15% chance to gain an extra ability." +"setup_hint_24" "Be patient with new players, and try not to leave matches early." +"setup_hint_25" "You can earn extra rewards by reaching top ranks. Check the in-game leaderboard for details." +"ending_cutscene_ready_title" "Ready for final cutscene" +"ending_cutscene_ready_names_prefix" "Ready:" +"ending_cutscene_ready_names_default" "Ready: -" +"ending_cutscene_ready_timer_prefix" "Starting in:" +"ending_cutscene_ready_timer_seconds" "sec" +"ending_cutscene_ready_waiting_first" "Waiting for first ready player..." +"ending_cutscene_ready_toggle_collapse" "Hide panel" +"ending_cutscene_ready_toggle_expand" "Show panel" +"skip_night_vote_title" "Skip day and start night?" +"skip_night_vote_yes" "Agree" +"skip_night_vote_no" "Decline" + + +//Mini profile +"profile_title" "Player profile" +"profile_loading" "Uploading a profile..." +"profile_error" "Boot Error" +"profile_steam_id" "Steam ID:" +"profile_level" "Level" +"profile_stats_title" "Statistics" +"profile_games_played" "Games played:" +"profile_winrate" "Winning percentage:" +"profile_wins" "Win:" +"profile_losses" "Defeats:" +"profile_playtime" "Game time:" +"profile_first_game" "First game:" +"profile_first_game_unknown" "Unknown" +"profile_recent_games" "Recent Games" +"profile_no_games" "There haven’t been any games yet." +"profile_game_win" "Victory" +"profile_game_loss" "Defeat" +"profile_game_result" "Result" +"profile_game_date" "Date" +"profile_game_duration" "Time" +"profile_game_kd" "K/D" +"profile_game_hero" "Hero" +"profile_description" "Player profile" +"mini_profile_tab_stats" "Stats" +"mini_profile_tab_history" "History" +"mini_profile_tab_rewards" "Rewards" +"mini_profile_tab_achievements" "Achievements" +"mini_profile_rewards_title" "Profile Level Rewards" +"mini_profile_achievements_title" "Hero Achievements" +"mini_profile_achievements_search_placeholder" "Search hero..." +"mini_profile_filter_all" "All" +"mini_profile_filter_completed" "Completed" +"mini_profile_filter_impossible" "Impossible" +"mini_profile_filter_unplayed" "Unplayed" +"mini_profile_stats_line" "Games: {games} Wins: {wins} WR: {wr}%" +"mini_profile_rewards_granted_now" "Granted now: +{value} shards" +"mini_profile_rewards_auto_hint" "Rewards are granted automatically when you reach a level" +"mini_profile_reward_level" "Level {level}" +"mini_profile_reward_value" "+{value} shards" +"mini_profile_reward_status_granted_now" "Granted now" +"mini_profile_reward_status_claimed" "Claimed" +"mini_profile_reward_status_locked" "Locked" +"hero_rank_tooltip_title" "Hero Ranks" +"hero_rank_tooltip_subtitle" "Win on higher difficulties and earn shards" +"hero_rank_tooltip_header_rank" "Rank" +"hero_rank_tooltip_header_condition" "Condition" +"hero_rank_tooltip_header_reward" "Reward" +"hero_rank_name_rank0" "Nobody" +"hero_rank_name_rank1" "Novice" +"hero_rank_name_rank3" "Amateur" +"hero_rank_name_rank6" "Veteran" +"hero_rank_name_rank8a" "Pro" +"hero_rank_name_rank8b" "Expert" +"hero_rank_name_rank8c" "Immortal" +"hero_rank_name_master" "Master" +"hero_rank_tooltip_condition_rank0" "No matches on this hero" +"hero_rank_tooltip_condition_rank1" "Win on Easy" +"hero_rank_tooltip_condition_rank3" "Win on Normal" +"hero_rank_tooltip_condition_rank6" "Win on Hard" +"hero_rank_tooltip_condition_rank8a" "Win on Impossible" +"hero_rank_tooltip_condition_rank8b" "Impossible: 10+ wins" +"hero_rank_tooltip_condition_rank8c" "Impossible: 100+ wins" + +//Battle Pass +"battle_pass_title" "BATTLE PASS" +"battle_pass_level" "Level:" +"battle_pass_xp" "XP" +"battle_pass_claim_all" "Take everything" +"battle_pass_loading" "Battle Pass download..." +"battle_pass_tab_rewards" "Awards" +"battle_pass_tab_quests" "Tasks" +"battle_pass_track_free" "FREE" +"battle_pass_track_premium" "PREMIUM" +"battle_pass_premium_match_reward_bonus" "PREMIUM: +50% match end reward bonus active" +"battle_pass_buy_button" "BUY BATTLE PASS" +"battle_pass_claim" "Take away" +"battle_pass_buy_dialog_title" "BATTLE PASS PREMIUM" +"battle_pass_buy_dialog_desc" "Get access to exclusive premium track rewards!" +"battle_pass_buy_dialog_feature_1" "✦ Unique Cosmetic Items" +"battle_pass_buy_dialog_feature_2" "✦ Doubled rewards in each level" +"battle_pass_buy_dialog_feature_3" "✦ Exclusive effects and cards" +"battle_pass_buy_dialog_feature_4" "✦ +50% Battle Pass XP gained" +"battle_pass_buy_dialog_feature_5" "✦ +6 additional daily quests" +"battle_pass_buy_dialog_price" "Cost:" +"battle_pass_buy_dialog_confirm" "Purchase" +"battle_pass_buy_dialog_cancel" "Cancel" +"battle_pass_quests_empty" "Tasks coming soon..." +"battle_pass_quests_loading" "Jobs are loading..." +"battle_pass_quests_daily" "DAILY TASKS" +"battle_pass_quests_completed" "from" +"battle_pass_quests_completed_of" "executed" +"battle_pass_quests_refresh" "Update via:" +"battle_pass_quest_reward_xp" "BP XP" +"battle_pass_quest_completed" "Done" +"battle_pass_error_purchase" "Buy Error" +"battle_pass_description" "Battle pass" +"battle_pass_no_reward" "No rewards" + +//Battle Pass Quests ({target} is substituted from quest.target automatically) + +"battle_pass_quest_kill_zombies_100_name" "Hunter" +"battle_pass_quest_kill_zombies_100_description" "Kill {target} creatures." +"battle_pass_quest_kill_zombies_300_name" "Fighter" +"battle_pass_quest_kill_zombies_300_description" "Kill {target} creatures." +"battle_pass_quest_kill_zombies_1000_name" "Genocide" +"battle_pass_quest_kill_zombies_1000_description" "Kill {target} creatures." +"battle_pass_quest_survive_10min_name" "Survivor" +"battle_pass_quest_survive_10min_description" "Spend {target} minutes in the game." +"battle_pass_quest_survive_20min_name" "Veteran" +"battle_pass_quest_survive_20min_description" "Spend {target} minutes in the game." +"battle_pass_quest_buy_black_shop_3_name" "Buyer" +"battle_pass_quest_buy_black_shop_3_description" "Buy {target} an item on the Black Market." +"battle_pass_quest_buy_black_shop_5_name" "Fixed client" +"battle_pass_quest_buy_black_shop_5_description" "Buy {target} items on the Black Market." +"battle_pass_quest_complete_quest_1_name" "Assistant" +"battle_pass_quest_complete_quest_1_description" "Execute {target} task at NPC" +"battle_pass_quest_complete_quest_3_name" "Hero of the Village" +"battle_pass_quest_complete_quest_3_description" "Execute {target} jobs with NPC" +"battle_pass_quest_earn_gold_5000_name" "Gold Miner" +"battle_pass_quest_earn_gold_5000_description" "Earn {target} Gold" +"battle_pass_quest_earn_gold_15000_name" "Banker" +"battle_pass_quest_earn_gold_15000_description" "Earn {target} Gold" +"battle_pass_quest_hero_level_10_name" "Hero's height" +"battle_pass_quest_hero_level_10_description" "Reach {target} the hero level" +"battle_pass_quest_hero_level_25_name" "Master" +"battle_pass_quest_hero_level_25_description" "Reach {target} the hero level" +"battle_pass_quest_survive_waves_5_name" "Steadfast" +"battle_pass_quest_survive_waves_5_description" "Survive {target} waves" +"battle_pass_quest_survive_waves_10_name" "Indestructible" +"battle_pass_quest_survive_waves_10_description" "Survive {target} waves" +"battle_pass_quest_buy_black_shop_10_name" "True Client" +"battle_pass_quest_buy_black_shop_10_description" "Buy {target} items on the Black Market." +"battle_pass_quest_buy_black_shop_common_name" "Beginner Collector" +"battle_pass_quest_buy_black_shop_common_description" "Buy {target} Common quality items" +"battle_pass_quest_buy_black_shop_rare_name" "Rare Connoisseur" +"battle_pass_quest_buy_black_shop_rare_description" "Buy {target} a Rare quality item" +"battle_pass_quest_buy_black_shop_epic_name" "Epic Hunter" +"battle_pass_quest_buy_black_shop_epic_description" "Buy {target} an Epic quality item" +"battle_pass_quest_buy_black_shop_legendary_name" "Market Legend" +"battle_pass_quest_buy_black_shop_legendary_description" "Buy {target} a Legendary quality item" +"battle_pass_quest_complete_quest_5_name" "Great Helper" +"battle_pass_quest_complete_quest_5_description" "Execute {target} jobs with NPC" +"battle_pass_quest_complete_cooking_3_name" "Cook" +"battle_pass_quest_complete_cooking_3_description" "Cook {target} dishes over a fire." +"battle_pass_quest_cook_grilled_meat_name" "Chef" +"battle_pass_quest_cook_grilled_meat_description" "Make fried meat" +"battle_pass_quest_tip_teammate_3_name" "Generous" +"battle_pass_quest_tip_teammate_3_description" "Send {target} type to allies" +"battle_pass_quest_tip_teammate_5_name" "Magnanimous" +"battle_pass_quest_tip_teammate_5_description" "Send {target} types to allies" +"battle_pass_quest_no_death_15min_name" "Unbreakable" +"battle_pass_quest_no_death_15min_description" "Survive {target} minutes without death." +"battle_pass_quest_heal_ally_2000_name" "Healer" +"battle_pass_quest_heal_ally_2000_description" "Heal allies on {target} HP" +"battle_pass_quest_deal_damage_50000_name" "Destroyer" +"battle_pass_quest_deal_damage_50000_description" "Deal {target} damage" +"battle_pass_quest_collect_item_meat_20_name" "Butcher" +"battle_pass_quest_collect_item_meat_20_description" "Gather {target} meat." +"battle_pass_quest_collect_item_milk_20_name" "Milkmaid" +"battle_pass_quest_collect_item_milk_20_description" "Collect {target} milk" +"battle_pass_quest_collect_item_wolf_claw_15_name" "Wolfhunter" +"battle_pass_quest_collect_item_wolf_claw_15_description" "Collect {target} wolf claws" +"battle_pass_quest_collect_item_ent_heart_10_name" "Ent Hunter" +"battle_pass_quest_collect_item_ent_heart_10_description" "Collect {target} ent hearts." +"battle_pass_quest_earn_gold_30000_name" "Mogul" +"battle_pass_quest_earn_gold_30000_description" "Earn {target} Gold" +"battle_pass_quest_survive_30min_name" "Legend of Survival" +"battle_pass_quest_survive_30min_description" "Spend {target} minutes in the game." +"battle_pass_quest_survive_waves_15_name" "Impenetrable" +"battle_pass_quest_survive_waves_15_description" "Survive {target} waves" +"battle_pass_quest_heal_ally_5000_name" "Great Healer" +"battle_pass_quest_heal_ally_5000_description" "Heal allies on {target} HP" +"battle_pass_quest_deal_damage_100000_name" "Worldbuster" +"battle_pass_quest_deal_damage_100000_description" "Deal {target} damage" +"battle_pass_quest_play_different_hero_3_name" "Variety" +"battle_pass_quest_play_different_hero_3_description" "Play {target} with different heroes" +"battle_pass_quest_play_different_hero_5_name" "Master of All" +"battle_pass_quest_play_different_hero_5_description" "Play {target} with different heroes" + +//Admin menu UI +"admin_menu_description" "Admin Menu" +"admin_menu_section_create" "CREATE" +"admin_menu_change_hero" "CHANGE HERO" +"admin_menu_add_ally" "+ALLIED" +"admin_menu_add_enemy" "+ENEMY" +"admin_menu_add_dummy" "+TARGET" + +"admin_menu_section_selected_units" "SELECTED CREATURES" +"admin_menu_level_plus_1" "+1 lvl." +"admin_menu_level_plus_30" "+30 lvl." +"admin_menu_spells" "CLOSED." +"admin_menu_hp" "HEALTH" +"admin_menu_invulnerable" "NON-ULCER." +"admin_menu_add_scepter" "+SCEPTER" +"admin_menu_add_shard" "+SHARD" +"admin_menu_add_card" "+MAP" +"admin_menu_side" "SIDE" +"admin_menu_reset" "RESET" +"admin_menu_delete" "DELETE" + +"admin_menu_section_give" "FUNCTIONS" +"admin_menu_give_items" "ISSUE ITEMS" +"admin_menu_give_stats" "ISSUE STATS" +"admin_menu_spawn_zones" "SPAVNA ZONES" + +"admin_menu_game_controls" "GAME ELEMENTS:" +"admin_menu_pause" "PAUSE" +"admin_menu_apply" "APPLY" +"admin_menu_script_reload" "SCRIPT RELOAD" +"admin_menu_spawn_zones_debug" "+SPAWN ZONES" + +"admin_menu_luck" "LUCK" +"admin_menu_phys_vamp" "PHYS. VAMPIRISM" +"admin_menu_magic_vamp" "MAG. VAMPIRISM" +"admin_menu_gold" "GOLD" +"admin_menu_crystals" "CRYSTALS" +"admin_menu_give" "ISSUE" + +"admin_menu_spawn_shape" "ZONE SHAPE" +"admin_menu_spawn_shape_circle" "CIRCLE" +"admin_menu_spawn_shape_square" "SQUARE" +"admin_menu_spawn_radius" "RADIUS (CIRCLE)" +"admin_menu_spawn_size" "WIDTH/HEIGHT (SQUARE)" +"admin_menu_spawn_units_label" "UNITS (npc_name:count;npc_name2:count2)" +"admin_menu_spawn_create_here" "CREATE A ZONE UNDER THE HERO" +"admin_menu_spawn_delete_here" "REMOVE THE ZONE UNDER THE HERO" +"admin_menu_spawn_behavior" "ZONE BEHAVIOR" +"admin_menu_spawn_behavior_neutral" "NEUTRAL" +"admin_menu_spawn_behavior_friendly" "FRIENDLY" +"admin_menu_spawn_behavior_aggressive" "AGGRESSIVE" + +"admin_menu_items_tab_default" "REGULAR" +"admin_menu_items_tab_blackshop" "BLACKSHOP" +"admin_menu_search_item" "Search for an object..." + +"admin_spawn_units_help_title" "Available units" + +"hero_selection_time_remaining" "Before penalty time" +"hero_selection_gold_lost" "lost gold" + +//Match Details (match details) +"match_details_title" "Match Details" +"match_details_info" "Match info" +"match_details_stats" "Statistics" +"match_details_items" "Items" +"match_details_permanent_effects" "Permanent effects" +"match_details_result_win" "victory" +"match_details_result_loss" "Lose" +"match_details_duration" "Duration" +"match_details_date" "Date" +"match_details_hero" "Hero" +"match_details_level" "Hero level" +"match_details_kills" "Murders" +"match_details_deaths" "Deaths" +"match_details_gold" "Gold earned" +"match_details_no_items" "No items found" +"match_details_no_modifiers" "Permanent effects are missing" +"match_details_upgrades_section" "Aghanim upgrades" +"match_details_aghanim_scepter" "Aghanim's Scepter" +"match_details_aghanim_shard" "Aghanim's Shard" + +"leaderboard_title" "Leaderboard" +"leaderboard_loading" "Loading leaderboard..." +"leaderboard_col_player" "Player" +"leaderboard_col_rank" "Rank" +"leaderboard_col_rating" "Rating" +"leaderboard_col_level" "Level" +"leaderboard_col_games" "Games" +"leaderboard_error_prefix" "Error:" +"leaderboard_request_failed" "Request failed" +"leaderboard_no_data" "Leaderboard data unavailable" +"leaderboard_invalid_format" "Invalid leaderboard data" +"leaderboard_process_error" "Failed to process data" +"leaderboard_your_position" "Your position:" +"leaderboard_rank_novice" "Uncalibrated" +"leaderboard_tooltip" "Open leaderboard" + +"store_donation_title" "Top up donate shards" +"store_donation_desc" "Enter amount in rubles — Robokassa payment opens in your browser (card, SBP)." +"store_donation_rate" "Rate: 1 ₽ = 2 donate shards" +"store_donation_amount_placeholder" "Amount in rubles" +"store_donation_pay" "Pay" +"store_donation_loading" "Creating payment link…" +"store_donation_preview_get" "You will receive" +"store_donation_preview_empty" "Enter amount in rubles" +"store_donation_amount_range" "Amount from 10 to 50000 ₽" + +"admin_menu_search_hero" "Search hero..." +"admin_menu_spawn_without_zone" "Spawn without zone" +"admin_menu_night_wave_title" "Night / wave" +"admin_menu_force_night" "Force night" +"admin_menu_force_morning" "Force morning" +"admin_menu_day_night_timer_title" "Day / night timer" +"admin_menu_set_timer" "Set" +"admin_menu_endless_day" "Endless day" +"admin_menu_items_tab_util" "UTIL ITEMS" +"admin_menu_items_tab_quest" "QUEST ITEMS" +"admin_menu_items_tab_admin" "ADMIN ITEMS" +"admin_menu_time_scale_title" "Time scale" + +"battle_pass_reward_battle_shards" "Battle shards" +"battle_pass_reward_zombie_shards" "Zombie shards" +"battle_pass_reward_chest" "Treasure chest" +"battle_pass_reward_card" "Card" +"battle_pass_reward_lightning_effect" "Lightning effect" +"battle_pass_reward_dust" "dust" +"battle_pass_reward_dust_title" "Dust" +"battle_pass_reward_arcade_pack_standard" "standard pack" +"battle_pass_time_abbr_hours" "h" +"battle_pass_time_abbr_minutes" "m" +"battle_pass_quest_progress_min_suffix" "min" + +"mmr_rank_immortal" "Immortal" +"mmr_rank_divine" "Divine" +"mmr_rank_ancient" "Ancient" +"mmr_rank_legend" "Legend" +"mmr_rank_archon" "Archon" +"mmr_rank_crusader" "Crusader" +"mmr_rank_guardian" "Guardian" +"mmr_rank_herald" "Herald" +"mmr_rank_uncalibrated" "Uncalibrated" + +"profile_exp_abbr" "XP" +"mini_profile_error_processing" "Failed to process profile data" + +"cooking_panel_tab_ingredients" "Ingredients" +"cooking_panel_tab_dishes" "Dishes" + +"deck_builder_no_deck_selected" "No deck" +"deck_builder_slot_empty" "Empty" +"deck_builder_card_in_deck_label" "In deck" +"deck_builder_quality_common" "Common" +"deck_builder_quality_rare" "Rare" +"deck_builder_quality_epic" "Epic" +"deck_builder_quality_legendary" "Legendary" +"deck_builder_quality_mythic" "Mythical" + +// -----------------Arsenal-------------------- +"arsenal_open_button" "Arsenal" +"arsenal_title" "ARSENAL" +"arsenal_heroes_section" "Heroes" +"arsenal_catalog_title" "Available gear" +"arsenal_filter_slot_label" "Slot" +"arsenal_filter_rarity_label" "Rarity" +"arsenal_filter_stat_label" "Stats" +"arsenal_filter_stats_toggle_show" "Filter by stats…" +"arsenal_filter_stats_toggle_hide" "Hide stat filters" +"arsenal_equipped_stats_title" "EQUIPMENT BONUSES" +"arsenal_equipped_stats_empty" "No equipped items" +"arsenal_incoming_reduction_linear_sum" "sum on items" +"arsenal_batch_selected_prefix" "Selected" +"arsenal_batch_selected_empty" "Selected: 0" +"arsenal_batch_alt_hint" "Alt + LMB for multi-select" +"arsenal_batch_select_all" "Select all" +"arsenal_batch_favorite" "Favorite" +"arsenal_catalog_empty" "Items earned from drops." +"arsenal_select_hero_hint" "Select a hero on the left" +"arsenal_no_heroes" "No heroes available" +"arsenal_clear" "Unequip all" +"arsenal_filter_all" "All" +"arsenal_slot_weapon" "Weapon" +"arsenal_slot_armor" "Armor" +"arsenal_slot_helmet" "Helmet" +"arsenal_slot_boots" "Boots" +"arsenal_slot_necklace" "Necklace" +"arsenal_slot_ring" "Ring" +"arsenal_locked_in_game" "Arsenal can only be edited on the custom game setup screen (before the match starts)" +"arsenal_item_not_owned" "You don't own this item" +"arsenal_rarity_common" "Common" +"arsenal_rarity_rare" "Rare" +"arsenal_rarity_epic" "Epic" +"arsenal_rarity_legendary" "Legendary" +"arsenal_rarity_mythic" "Mythical" +"arsenal_tooltip_owned" "Owned: {d:arsenal_owned_qty}" +"arsenal_tooltip_owned_unique" "Unique copy in stash" +"arsenal_tooltip_not_owned" "Not in inventory" +"arsenal_tooltip_serial_prefix" "Serial No." +"arsenal_tooltip_stats_title" "Random stats" +"arsenal_tooltip_stats_unavailable" "Stats unavailable" +"arsenal_tooltip_unknown_fifth" "5th stat: ??? (unlocks at level 5)" +"arsenal_detail_pin" "Pin" +"arsenal_detail_unpin" "Unpin" +"arsenal_detail_disassemble" "Disassemble" +"arsenal_detail_upgrade" "Upgrade" +"arsenal_detail_upgrade_max" "Max level" +"arsenal_detail_pinned_mark" "pinned" +"arsenal_disassemble_pinned" "Cannot disassemble a pinned item" +"arsenal_disassemble_equipped" "Cannot disassemble an equipped item. Unequip it first." +"arsenal_tooltip_equipped_header" "Equipped on heroes:" +"arsenal_upgrade_not_enough_shards" "Not enough zombie shards to upgrade" +"arsenal_stat_bonus_damage" "damage" +"arsenal_stat_base_damage_pct" "base attack damage" +"arsenal_stat_outgoing_damage_pct" "outgoing damage" +"arsenal_stat_attack_speed" "attack speed" +"arsenal_stat_attack_speed_pct" "attack speed" +"arsenal_stat_bonus_armor" "armor" +"arsenal_stat_max_health" "health" +"arsenal_stat_max_mana" "mana" +"arsenal_stat_health_regen" "health regen" +"arsenal_stat_mana_regen" "mana regen" +"arsenal_stat_move_speed" "movement speed" +"arsenal_stat_move_speed_pct" "movement speed" +"arsenal_stat_magic_resist" "magic resistance" +"arsenal_stat_spell_amp" "spell amplification" +"arsenal_stat_all_stats" "all attributes" +"arsenal_stat_all_stats_pct" "all attributes (%)" +"arsenal_stat_bonus_strength" "Strength" +"arsenal_stat_bonus_agility" "Agility" +"arsenal_stat_bonus_intellect" "Intelligence" +"arsenal_stat_strength_pct" "Strength" +"arsenal_stat_agility_pct" "Agility" +"arsenal_stat_intellect_pct" "Intelligence" +"arsenal_stat_luck" "to luck" +"arsenal_stat_incoming_damage_reduction_pct" "to incoming damage reduction" +"arsenal_stat_crit_mult" "critical multiplier" +"arsenal_stat_battle_level" "battle level" +"DOTA_Tooltip_Ability_item_arsenal_weapon_iron_blade" "Iron Dagger" +"DOTA_Tooltip_Ability_item_arsenal_weapon_iron_blade_Description" "A simple blade forged from iron." +"DOTA_Tooltip_Ability_item_arsenal_weapon_storm_edge" "Storm Edge" +"DOTA_Tooltip_Ability_item_arsenal_weapon_storm_edge_Description" "A blade humming with lightning." +"DOTA_Tooltip_Ability_item_arsenal_weapon_sunfang_saber" "Sunfang Saber" +"DOTA_Tooltip_Ability_item_arsenal_weapon_sunfang_saber_Description" "A searing saber that leaves a burning trail." +"DOTA_Tooltip_Ability_item_arsenal_armor_chain_mail" "Chain Mail" +"DOTA_Tooltip_Ability_item_arsenal_armor_chain_mail_Description" "Light protection." +"DOTA_Tooltip_Ability_item_arsenal_armor_dragon_plate" "Dragon Plate" +"DOTA_Tooltip_Ability_item_arsenal_armor_dragon_plate_Description" "Forged from ancient dragon scales." +"DOTA_Tooltip_Ability_item_arsenal_armor_vanguard_shell" "Vanguard Shell" +"DOTA_Tooltip_Ability_item_arsenal_armor_vanguard_shell_Description" "A massive shell forged for frontal assaults." +"DOTA_Tooltip_Ability_item_arsenal_helmet_leather_cap" "Leather Cap" +"DOTA_Tooltip_Ability_item_arsenal_helmet_leather_cap_Description" "Modest head protection." +"DOTA_Tooltip_Ability_item_arsenal_helmet_arcane_circlet" "Arcane Circlet" +"DOTA_Tooltip_Ability_item_arsenal_helmet_arcane_circlet_Description" "Strengthens the bond with mana." +"DOTA_Tooltip_Ability_item_arsenal_helmet_warcrown_mask" "Warcrown Mask" +"DOTA_Tooltip_Ability_item_arsenal_helmet_warcrown_mask_Description" "A commander's mask that inspires fear on the battlefield." +"DOTA_Tooltip_Ability_item_arsenal_boots_swift_boots" "Swift Boots" +"DOTA_Tooltip_Ability_item_arsenal_boots_swift_boots_Description" "Comfortable boots for long marches." +"DOTA_Tooltip_Ability_item_arsenal_boots_phase_treads" "Phase Treads" +"DOTA_Tooltip_Ability_item_arsenal_boots_phase_treads_Description" "Boots that step through space itself." +"DOTA_Tooltip_Ability_item_arsenal_boots_hermes_greaves" "Hermes Greaves" +"DOTA_Tooltip_Ability_item_arsenal_boots_hermes_greaves_Description" "Winged greaves that grant lightning-fast bursts." +"DOTA_Tooltip_Ability_item_arsenal_necklace_amber_pendant" "Amber Pendant" +"DOTA_Tooltip_Ability_item_arsenal_necklace_amber_pendant_Description" "Warm amber absorbs spells." +"DOTA_Tooltip_Ability_item_arsenal_necklace_soul_locket" "Soul Locket" +"DOTA_Tooltip_Ability_item_arsenal_necklace_soul_locket_Description" "The locket holds souls of fallen mages." +"DOTA_Tooltip_Ability_item_arsenal_necklace_prism_choker" "Prism Choker" +"DOTA_Tooltip_Ability_item_arsenal_necklace_prism_choker_Description" "Crystals in the choker refract and amplify magic." +"DOTA_Tooltip_Ability_item_arsenal_ring_copper_band" "Copper Band" +"DOTA_Tooltip_Ability_item_arsenal_ring_copper_band_Description" "A plain copper-coloured band." +"DOTA_Tooltip_Ability_item_arsenal_ring_void_signet" "Void Signet" +"DOTA_Tooltip_Ability_item_arsenal_ring_void_signet_Description" "A ring brimming with the power of the void." +"DOTA_Tooltip_Ability_item_arsenal_ring_titan_loop" "Titan Loop" +"DOTA_Tooltip_Ability_item_arsenal_ring_titan_loop_Description" "An ancient ring stamped with the sigil of titans." + +"card_selection_card_word" "Card" +"card_selection_no_description" "No description available" +"card_selection_error_hero_dead" "Cannot choose a card while your hero is dead" + +"store_item_hero_drow_ranger_name" "Drow Ranger" +"store_item_hero_drow_ranger_description" "Legendary archer with incredible accuracy. Deals colossal physical damage." +"store_item_hero_lina_name" "Lina" +"store_item_hero_lina_description" "Fire mage with devastating spells. Deals huge magic damage." +"store_item_hero_vengefulspirit_name" "Vengeful Spirit" +"store_item_hero_vengefulspirit_description" "Vengeful support: damage and lifesteal aura, armor-shredding Wave of Terror, Nether Swap, and Spirit Feast on marked kills." +"store_item_hero_phantom_assassin_name" "Phantom Assassin" +"store_item_hero_phantom_assassin_description" "Deadly assassin. Deals huge physical damage to single targets." +"store_item_hero_sven_name" "Sven" +"store_item_hero_sven_description" "Mighty warrior. Holds off hordes and deals huge physical damage." +"store_item_skin_effect_fire_name" "Fire-Touched" +"store_item_skin_effect_ice_name" "Icebound" +"store_item_skin_effect_heaven_name" "Divine Effect" +"store_item_skin_effect_fall_2021_emblem_name" "Aghanim's Radiance" +"store_item_skin_effect_blue_gems_name" "Blue Gems" +"store_item_skin_effect_sponsor_name" "Sponsor Effect" +"store_item_skin_effect_lotus_name" "Lotus Effect" +"store_item_skin_effect_bp_red_name" "Battle Pass: Crimson" +"store_item_skin_effect_bp_garden_name" "Battle Pass: Garden" +"store_item_skin_effect_bp_golden_name" "Battle Pass: Golden" +"store_item_skin_effect_bp_diretide_emblem_name" "Battle Pass: Diretide Emblem" +"store_item_skin_effect_bp_diretide_emblem_v1_name" "Battle Pass: Diretide Emblem I" +"store_item_skin_effect_bp_diretide_emblem_v3_name" "Battle Pass: Diretide Emblem III" +"store_item_chat_wheel_sound_jump_name" "Jump, fool" + +"store_item_hero_bloodhunter_name" "Bloodhunter" +"store_item_hero_bloodhunter_description" "Custom bloodseeker-style carry: blood rage, illusions and execution." +"store_item_hero_sand_king_name" "Sand King" +"store_item_hero_sand_king_description" "Desert king: area control, wave damage and wave survival." +"store_item_hero_nagash_name" "Nagash" +"store_item_hero_nagash_description" "Custom strength hero: dark friends, golems and army buffs." +"store_item_hero_yuki_onna_name" "Yuki-onna" +"store_item_hero_yuki_onna_description" "Custom ice spirit: snowstorms, rituals and summons." +"store_item_hero_sargatanas_name" "Sargatanas" +"store_item_hero_sargatanas_description" "Custom demon: hellstep, cleave, summons and Metamorphosis." +"store_item_hero_elder_dragon_smaug_name" "Elder Dragon Smaug" +"store_item_hero_elder_dragon_smaug_description" "Custom all-attributes dragon: fire, fear aura and incandescent fury." +"store_item_hero_mirana_name" "Mirana" +"store_item_hero_mirana_description" "Moon huntress: arrows, Starstorm, and synergy with Luna. Damage scales with mana." +"store_item_hero_spectre_name" "Spectre" +"store_item_hero_spectre_description" "Shadow on the battlefield: Dagger, Dispersion, Echo shadows, and Haunt with Reality." +"store_item_hero_silencer_name" "Silencer" +"store_item_hero_silencer_description" "Intelligence carry-controller: silences, pure-damage glaives and global control." +"store_item_hero_keeper_of_the_light_name" "Keeper of the Light" +"store_item_hero_keeper_of_the_light_description" "Powerful support mage: heals allies, blinds enemies and controls teamfights." + +"hud_button_skip" "Vote for skip" + +//Quests +"quest_kill_pigs_title" "Kill the pigs" +"quest_kill_pigs_description" "Firestars asks you to help him get food for his tribe." + +"quest_firestar_gourmet_title" "Gourmet" +"quest_firestar_gourmet_description" "Prepare 5 servings of fried meat for Firestar to have a real feast." +"quest_firestar_leader_horn_title" "Leader's Horn" +"quest_firestar_leader_horn_description" "Nights asks to bring the horn of the leader of the lycans. Says for a very important ritual." +"quest_firestar_fishing_title" "Fish Debt" +"quest_firestar_fishing_description" "Firestars admitted that he took a fishing rod from Kunka. Catch 10 fish and bring it to him so he doesn't get hit in the mustache." + +"firestar_quest_1_message_1" "Fires: Mrrryau! Meow-meow moore-moore!" +"firestar_quest_1_message_2" "Fires: RRRR! Meow moore-moore 10 meow! Mrrryau meow meow!" +"firestar_quest_1_message_3" "Fires: Moore-moore! RRRR! Myayayayow!" + +"firestar_quest_1_message_complete_1" "Fires: Meow meow-muya meow mua meow!" + +"firestar_quest_gourmet_message_1" "Fires: Mrrr... Smells like fried meat! Will you make a couple more servings just for me?" +"firestar_quest_gourmet_message_complete_1" "Fires: Meow! What a feast! It’s a sin to eat such meat alone — take the reward, cook." +"firestar_quest_leader_horn_message_complete_1" "Fires: I have the leader’s horn! Great. Now there is a more complicated matter..." +"firestar_quest_fishing_message_1" "Fires: Okay, I admit... I took a fishing rod from Kunka. Hold her and get me 10 fish before he notices!" +"firestar_quest_fishing_message_complete_1" "Fires: Meow! There are fish, and Kunka doesn’t know. You bailed me out!" + +"quest_kill_sheep_title" "Sheep killing" +"quest_kill_sheep_description" "Help your grandfather get milk and bring it to him." +"quest_oldmen_cheesemaker_title" "Cheese maker" +"quest_oldmen_cheesemaker_description" "Prepare and bring grandpa a tile of cheese heads so he can make his favorite dumplings." + +"lina_quest_ent_heart_title" "Heart of ent" +"lina_quest_ent_heart_description" "Lina asks to bring her the ent’s heart, in return she will give something." +"lina_quest_ent_heart_message_complete_1" "Lina: Great job! The flame in this heart is still alive. Take this core, I don't need it." + + +"dota_tooltip_ability_item_pollen_keeper" "Pollen Collector" +"dota_tooltip_ability_item_pollen_keeper_Description" "

Passive: Pollen Harvest

Attack Energy clots, with a chance of %chance%%% gets 1 pollen charge (maximum: 20)." + +"lina_quest_bottle_title" "Pollen for Lina" +"lina_quest_bottle_description" "Collect 20 charges of pollen in a bottle and return to Lina." +"lina_quest_bottle_message_1" "Lina: Here's the bottle. Fill it with pollen from the flowers of fire and bring back 20 charges." +"lina_quest_bottle_message_complete_1" "Lina: Great! The pollen has been collected and the flames have become stronger. Thanks for your help." + +"largo_quest_kill_frogs_title" "Frog Hunt" +"largo_quest_kill_frogs_description" "Largo asks to clear the swamp: destroy mini frogs, frogs and master frogs." +"largo_quest_kill_frogs_message_1" "Largo: The swamp is teeming with creatures again. Kill 20 frogs and come back to me." +"largo_quest_kill_frogs_message_complete_1" "Largo: Great job! The swamp became quieter. Hold your reward." + +"largo_quest_spider_legs_title" "Spider Feet" +"largo_quest_spider_legs_description" "Largo asks to bring 20 spider legs for hunting trophies." +"largo_quest_spider_legs_message_1" "Largo: After clearing the swamps, we need trophies from spiders. Bring 20 spider legs." +"largo_quest_spider_legs_message_complete_1" "Largo: Great trophies. These paws will be useful in the craft and for baits." + +"maiden_quest_eggs_title" "Eggs for CMka" +"maiden_quest_eggs_description" "Crystal Maiden asks to bring 20 eggs." +"maiden_quest_eggs_message_1" "Webcup: I need eggs for a cold ritual. Bring 20 grand and I'll thank you generously." +"maiden_quest_eggs_message_complete_1" "CMCA: Great! Just as much as needed. Thank you, keep the reward." + +"doctor_quest_frog_paws_title" "Frog legs" +"doctor_quest_frog_paws_description" "The doctor asks you to bring 20 frog legs for your potions." +"doctor_quest_frog_paws_message_1" "Doctor: I need fresh frog legs. Bring 20 pieces for my potions." +"doctor_quest_frog_paws_message_complete_1" "Doctor: Excellent material. This is enough for a whole batch of potions. Hold the reward." + +"doctor_quest_poison_title" "Toxic Extract" +"doctor_quest_poison_description" "Doctor needs spider venom. Bring 20 servings for him to complete the formula." +"doctor_quest_poison_message_1" "Doctor: Great, now I need pure spider venom. Bring 20 servings." +"doctor_quest_poison_message_complete_1" "Doctor: Perfect. Poison of the right concentration. I'll finish the serum based on it." + +"friend_quest_pizza_prep_title" "Pizza Preparation" +"friend_quest_pizza_prep_description" "Gourde asks for a pizza preparation." +"friend_quest_pizza_prep_message_1" "Gourde: Seeker! Hold the recipe for the pizza preparation and then I'll give you my cool sauce. There will be the most delicious thing in the world! You'll lick your fingers!" + +"friend_quest_pizza_prep_message_complete_1" "Gourde: Use my cool sauce. It's going to be the most delicious thing in the world!" +"friend_quest_pizza_prep_message_complete_2" "Gourde: Thank you for sharing with me, hold my gift! I'm sure he'll help you." + +"oldmen_quest_1_message_1" "Did: Oh, what are you talking about, help the old child, be affectionate!" +"oldmen_quest_1_message_2" "Did: Milk would be me, bo without yak without lard - niyak! Get some trochas, huh?" +"oldmen_quest_1_message_3" "Did: I’ll give you such a gift for this - mom, don’t worry!" + +"oldmen_quest_1_message_complete_1" "Did: Oh, damn you, sink! Oh milk - those who need it!" +"oldmen_quest_1_message_complete_2" "Did: Now I can grab dumplings with sire, like my grandmother, you were timid!" +"oldmen_quest_1_message_complete_3" "Did: To you, goat, for your kindness! What a charming dough, you will be fit!" + +"oldmen_quest_cheesemaker_message_1" "Did: Oh, sinku, milk — then good, or without the help of a syru dumplings!" +"oldmen_quest_cheesemaker_message_2" "Did: Zrobi meni trohi siru, ha? If you want to bring a bottle of heads, I’ll make such a fuss for you!" +"oldmen_quest_cheesemaker_message_complete_1" "Did: Otse so sir! From now on there will be dumplings — licking your fingers! I tell you, cotton." + +"quest_give_claw_title" "Does a Pirate Need Claws?" +"quest_give_claw_description" "A pirate asks you to get wolf claws for him. He doesn’t explain why, but promises to pay generously." + +"kunkka_quest_give_claw_message_1" "Kunkevich: Hey sailor! I need the claws of wolves, and quickly!" +"kunkka_quest_give_claw_message_2" "Kunkevich: Don’t ask why — just bring it. Consider it part of a pirate rite!" +"kunkka_quest_give_claw_message_3" "Kunkevich: For each claw you will get more gold than for a barrel of rum!" + +"kunkka_quest_give_claw_message_complete_1" "Kunkevich: Wow! You really got them!" +"kunkka_quest_give_claw_message_complete_2" "Kunkevich: I'll stitch you a wolf hat from these claws — wear it like a true pack leader!" +"kunkka_quest_give_claw_message_complete_3" "Kunkevich: Hold the reward, you deserve it!" +"kunkka_quest_give_claw_message_complete_4" "Kunkevich: If you get into trouble — call me, I’m happy now!" + +"quest_found_rom_title" "In Search of Rum" +"quest_found_rom_description" "Help the pirate find his treasure in a bottle." + +"kunkka_quest_find_rom_message_1" "Kunkevich: Damn it, where's my rum?! Did any of you rats steal it from the hold?" +"kunkka_quest_find_rom_message_2" "Kunkevich: If in five minutes I don’t have the bottle in my hands, I’ll start hanging it after one! " +"kunkka_quest_find_rom_message_3" "Kunkevich: Look, bastards, this rum is more valuable than your miserable lives!" + +"kunkka_quest_find_rom_message_complete_1" "Kunkiewicz: HA-HA-HA! LOOK AT THIS GENIUS!" +"kunkka_quest_find_rom_message_complete_2" "Kunkevich: You, my friend, just bought yourself a place in heaven." +"kunkka_quest_find_rom_message_complete_3" "Kunkevich: Well, or at least a reprieve from hell on this fucking ship!" +"kunkka_quest_find_rom_message_complete_4" "Kunkevich: Take my spare blade, dog! Let him now serve him who is not afraid of his heaviness!" + +"quest_kunkka_skeletons_title" "Cemetery Cleaner Part 1" +"quest_kunkka_skeletons_description" "The pirate has already been lured by skeletons to the cemetery and he asked you to get rid of a couple of their species." + +"zone_skeletons_clean" "Cemetery Cleanup" + +"quest_kunkka_clean_harbor_title" "Clean Harbor" +"quest_kunkka_clean_harbor_description" "Place a flag in the dead water and clear the harbor of bone scum." + +"quest_kunkka_kill_lycan_title" "Black Fang Hunt" +"quest_kunkka_kill_lycan_description" "Hunt down and kill the boss. His howling has been troubling these lands for too long." +"quest_kunkka_kill_satyr_demon_title" "Satyr Demon Hunt" +"quest_kunkka_kill_satyr_demon_description" "Find and kill the satyr demon. Return to Kunkevich for your reward." + +"kunkka_quest_2_message_1" "Kunkevich: SO THAT KRAKEN EATS THEM ALL! Damn skeletons! They were bred here like rats in the hold, so that they would be empty!" +"kunkka_quest_2_message_2" "Kunkiewicz: HEY, BRAVE SEA WOLF! Don't you want to get some gold?! Save the old pirate from this bony scourge!" +"kunkka_quest_2_message_3" "Kunkevich: SMASH AT LEAST A DOZEN OF THESE WALKING DEAD INTO PIECES, AND I SWEAR BY THE SEA DEVIL, I will make you rich up to your ears!" + +"kunkka_quest_kill_lycan_message_1" "Kunkevich: Did you hear Lycan howl? This beast spoils my entire view of the sea. You will lay him down — you will receive a reward like a true hunter." +"kunkka_quest_kill_lycan_message_complete_1" "Kunkevich: What a loot! Such an animal does not fall from a weak hand. Keep the fee, you honestly deserve it." +"kunkka_quest_kill_satyr_demon_message_1" "Kunkevich: A satyr demon is roaming nearby and terrorizing the shore. Find it and send it to the depths." +"kunkka_quest_kill_satyr_demon_message_complete_1" "Kunkevich: Fine work! The satyr demon is down, and the sea can breathe in peace again. Take your reward." + +"kunkka_quest_2_message_complete_1" "Kunkevich: HA-HA-HA! GREAT, sailor! Yes, you are a real undead slayer!" +"kunkka_quest_2_message_complete_2" "Kunkevich: GLORY TO THE SEA GODS! You can finally breathe deeply without this bone scum!" + +"kunkka_quest_clean_harbor_message_1" "Kunkevich: Stinks of dead meat in my harbor. Here's a flag for you — stick between their lairs and keep the point." +"kunkka_quest_clean_harbor_message_complete_1" "Kunkevich: This is work, sailor! The water is clean again, you can wash it down with rum. Catch your jackpot." +"kunkka_quest_clean_harbor_message_fail_1" "Kunkevich: The flag was demolished, the harbor is again teeming with bones. Let’s take a different course and try again." +"quest_kunkka_find_anchor_title" "Treasure Under the Cross" +"quest_kunkka_find_anchor_description" "Kunkevich says he buried treasure near that damn witch. Find the hoard — and you can keep the reward for yourself." +"kunkka_quest_find_anchor_message_1" "Kunkevich: Take the shovel! Find crosses on the map — stand on one and dig. Gold will surface if you are not lazy." +"kunkka_quest_find_anchor_message_complete_1" "Kunkevich: Ha! I hear coins — you dug well. Take your reward, sea wolf!" +"kunkka_quest_find_anchor_message_fail_1" "Kunkevich: Shovel in the sand, no hoard… We will try again later." +"DOTA_Tooltip_ability_item_shovel" "Trusty Shovel" +"DOTA_Tooltip_ability_item_shovel_Description" "

Active: Dig

Dig under your feet while standing on a cross. Yields %gold_min% to %gold_max% gold.

Passive: Sturdy Handle

Grants bonus health." +"DOTA_Tooltip_ability_item_shovel_Lore" "A magic shovel Kunkevich found on magnetic treasure hunts." +"DOTA_Tooltip_ability_item_shovel_bonus_health" "+$health" +"DOTA_Tooltip_ability_item_shameful_pipe" "Shameful Pipe" +"DOTA_Tooltip_ability_item_shameful_pipe_Description" "Grants %bonus_attack_range% attack range to ranged heroes. The bearer is immune to creep bone armor damage and reflection." +"DOTA_Tooltip_ability_item_shameful_pipe_Lore" "A pipe from a pirate hoard—long as a captain's pride, useless in melee, but arrows fly farther and creep bone armor cannot touch its owner." +"DOTA_Tooltip_ability_item_shameful_pipe_bonus_attack_range" "+$attack_range" +"DOTA_Tooltip_modifier_item_shameful_pipe" "Shameful Pipe" +"DOTA_Tooltip_modifier_item_shameful_pipe_Description" "Bonus attack range. Immune to creep bone armor." +"npc_kunkka_quest_anchor_marker" "Anchor Mark" + +"clean_harbor_no_hero_near_flag" "The flag has no one — Hurry up and return to the circle." + +"DOTA_Tooltip_ability_item_clean_harbor" "Harbor Clearing Flag" +"DOTA_Tooltip_ability_item_clean_harbor_Description" "A one-time pirate flag that is placed between two skeleton lairs. If the flag stands for 60 seconds under the hero's protection, the spawn of the skeletons in the cemetery stops, and all the dead that have already emerged are scattered to the wind." + +"DOTA_Tooltip_ability_item_fishing_rod" "Fishing Rod" +"DOTA_Tooltip_ability_item_fishing_rod_Description" "

Active: Hook Throw

Throws the hook in the specified direction up to %hook_distance%. When hit by an enemy, deals %Damage% physical damage plus a share of your attack, gives an overview with a radius of %vision_radius% on %vision_duration% sec., imposes a slowdown of %slow_movespeed%%% and periodic damage to %damage_per_tick% every %tick% sec. While the hook flies and returns, the hero stands still. The target is attracted to you when the hook is rolled back.

If a fish is caught (npc_fish), after the hook is returned it disappears and the reward flies to the hero." +"DOTA_Tooltip_ability_item_fishing_rod_Lore" "Tackle with which and troll on shore — as long as it pecks." +"DOTA_Tooltip_ability_item_fish" "Fish" +"DOTA_Tooltip_ability_item_fish_Description" "

Active: Eat Fish

Treats the target at %heal% health and gives %hunger_bonus% hunger stakes." +"DOTA_Tooltip_ability_item_fish_Lore" "Fresh catch. Especially useful after a heavy wave." +"dota_tooltip_ability_item_fish_heal" "+$health" +"dota_tooltip_ability_item_fish_hunger_bonus" "+$all" +"DOTA_Tooltip_modifier_bad_cast_debuff" "Bad cast" +"DOTA_Tooltip_modifier_bad_cast_debuff_Description" "Your directional abilities fly in the opposite direction." +"DOTA_Tooltip_modifier_blind_debuff" "Dazzle" +"DOTA_Tooltip_modifier_blind_debuff_Description" "The screen is covered with interference, preventing navigation." +"DOTA_Tooltip_modifier_stats_multiplier" "Stats Multiplier" +"DOTA_Tooltip_modifier_stats_multiplier_Description" "Combined card bonus to primary attributes. Current multiplier: %dMODIFIER_PROPERTY_TOOLTIP%%%" + +"quest_denny_quest_kill_sheeps_title" "Denny and his teachings" + "quest_denny_quest_kill_sheeps_description" "Denny teaches you and even pays to train you by asking you to kill sheep to further develop your skills." + +"denny_quest_kill_sheeps_message_1" "Denny: Hey hero! I've become much stronger after these training sessions!" +"denny_quest_kill_sheeps_message_2" "Denny: You know you need more practice! Can you kill 20 sheep?" +"denny_quest_kill_sheeps_message_3" "Denny: I want to show everyone that you are not a weakling! Together we can handle it!" + +"denny_quest_kill_sheeps_message_complete_1" "Denny: Wow! We did it! I feel like we're becoming real warriors!" +"denny_quest_kill_sheeps_message_complete_2" "Denny: Now you're ready for more serious challenges!" + +"quest_denny_quest_kill_thieves_title" "Rogue On The Road" +"quest_denny_quest_kill_thieves_description" "Denny is confident that the road will only be safe after you destroy all the robbers." + +"denny_quest_kill_thieves_message_1" "Denny: There are robbers operating on the road! Kill the leader and all his henchmen!" +"denny_quest_kill_thieves_message_complete_1" "Denny: Great! The robbers are defeated — now you can walk along the road calmly!" +"denny_quest_kill_thieves_message_complete_2" "Denny: I appreciate your courage. You take the next reward!" + + +"quest_denny_need_a_pet_title" "Denny and his LITTLE friend" +"quest_denny_need_a_pet_description" "Denny asked you to find a special friend for him. He says he wants someone fast and agile, or maybe strong and brave... It's strange that he's so persistent about this during the zombie apocalypse." +"denny_pet" "Pet for Denny." +"npc_pig_event" "Turbo Pig" +"npc_wolf_event" "Wolfhound" +"npc_squirrel_event" "Squirrel" + + +"dota_tooltip_modifier_pet_buff_npc_pig_event" "Pig Madness" +"dota_tooltip_modifier_pet_buff_npc_pig_event_Description" "All incoming damage reduced by 10%%" + +"dota_tooltip_modifier_pet_buff_npc_wolf_event" "Wolf power" +"dota_tooltip_modifier_pet_buff_npc_wolf_event_Description" "All outgoing damage is increased by 15%%, and attacks also have a 160% chance of dealing 15% damage." + +"dota_tooltip_modifier_pet_buff_npc_squirrel_event" "Squirrel Wisdom" +"dota_tooltip_modifier_pet_buff_npc_squirrel_event_Description" "Experience gained increased by 35%%" + +"dota_tooltip_ability_item_pet" "How to tame anyone?" +"dota_tooltip_ability_item_pet_Description" "

Active: Use

If used correctly, you can achieve some result, you don’t know it yet, but the book may break." +"dota_tooltip_ability_item_pet_Lore" "An ancient tome written by legendary animal trainer Eldrik Zveregov. They say that he could tame any creature - from a tiny squirrel to a ferocious wolf. His secrets were written down in this book, but over the years the pages became fragile and some recipes for domestication were lost. Now a book can help you find a true friend, but only for someone who can use it correctly." + +"denny_quest_find_pet_message_1" "Denny: Hey hero! I have one request... Can you help me find a special friend?" +"denny_quest_find_pet_message_2" "Denny: I dream of someone who will always be with me. Maybe fast and dexterous, like those squirrels that jump on trees... or strong and brave, like those wolves that howl at the moon..." +"denny_quest_find_pet_message_3" "Denny: Or maybe even someone special who knows how to dig the ground and find treasures! You know, I've heard that such creatures are very intelligent and loyal!" + +"denny_quest_find_pet_message_complete_1" "Denny: Whoa! You found... this is... This is a real pet! I've been dreaming about this for so long!" +"denny_quest_find_pet_message_complete_2" "Denny: You know, I always wanted a friend... one who would be with me in any weather. Maybe fast and agile, like the wind, or strong and brave, like a real warrior..." +"denny_quest_find_pet_message_complete_3" "Denny: Thank you so much! Now I have a real companion! I'll train with him every day and be the strongest guard!" + + +"quest_denny_training_title" "Guardian path part 1" +"quest_denny_training_description" "Denny really wants to become a guard and asked you to help him with this, for this he will go fight the sheep and asked you to help him with this." + +"Denny_quest_1_message_1" "Denny: Um... I'm... I want to be like real guards! But everyone at school offends me because I'm weak..." +"Denny_quest_1_message_2" "Denny: Please teach me how to fight! I can't even handle sheep... I... I'll pay what I can!" +"Denny_quest_1_message_3" "Denny: Can you really help? Hurray! Just... don't hit hard, okay?" + +"Denny_quest_1_rofl_message_1" "Denny: Hiya! Here you go!" +"Denny_quest_1_rofl_message_2" "Denny: Get it! I am strong!" +"Denny_quest_1_rofl_message_3" "Denny: Ha! I'm like a real warrior!" +"Denny_quest_1_rofl_message_4" "Denny: Bam! Bam! Bam!" +"Denny_quest_1_rofl_message_5" "Denny: I am invincible! Hurrah!" + + +"Denny_quest_1_message_complete_1" "Denny: Thank you, hero! I've become much stronger thanks to you. Here, take your sword back - it helped me a lot in my training." +"Denny_quest_1_message_complete_2" "Denny: But you know... I have one more request. Come when you can help me become even stronger! Good luck to you, hero!" +"Denny_quest_1_message_complete_3" "Denny: I will definitely become a real guard, just as I dreamed! And it's all thanks to you!" + +"Denny_quest_1_message_fail_1" "Denny: *sniffles* I... I failed... I'm so weak... if I had a sword like a kunki." +"Denny_quest_1_message_fail_2" "Denny: I guess I really shouldn’t dream of becoming a guard..." +"Denny_quest_1_message_fail_3" "Denny: Thank you for trying to help me... But apparently it's not mine..." + + +"quest_collect_items_title" "Collect Items" + "quest_collect_items_description" "Gather three iron branches for amplification" + +//Default functionality + +"text_no_alphatest_detected" "Fag detected not with alpha test, then play go lessons do schmuck." + +"music_on" "Turn on the music." +"music_off" "Turn off the music." +"dota_game_end_victory_title_radiant" "Village saved!" + "dota_game_end_victory_title_dire" "Oh fuck, I'm in the tilt...(((" + +"black_shop_refreshed" "The store has been updated!" +"black_shop_title" "Black market" +"FREE" "free" +"rarity_common" "Regular" +"rarity_rare" "Rare" +"rarity_epic" "Epic" +"rarity_legendary" "Legendary" +"rarity_heavenly" "Divine" +"rarity_cursed" "Damned" +"time_now" "The time of the current game is:" +"seconds" "seconds" +"duration" "Duration" +"day_time" "Until night" +"night_time" "Until the day" +"night_begin" "Night has come." +"be_ready" "Get ready for defense!" +"night_begin_1" "Team, hurry up! Night is very close!" +"NIGHT_BEGIN_2" "HELLO, WHERE ARE YOU FUCKED!???!?!?!? NIGHT HAS ALREADY COME!" + +"boss_spawned" "YOU'RE GETTING CLOSE BOSS HIMSELF!" +"dota_tooltip_ability_ghost_evasive" "Phantom Evasiveness" +"dota_tooltip_ability_ghost_evasive_Description" "Passively allows you to fly and pass through units." +"dota_tooltip_ability_ghost_evasive_evasive" "$evasion" +"dota_tooltip_ability_wave_phasing_march" "Phasing March" +"dota_tooltip_ability_wave_phasing_march_Description" "Passive: pass through other units and gain %bonus_movement_speed% bonus movement speed." +"dota_tooltip_ability_wave_phasing_march_bonus_movement_speed" "+$move_speed" + +"1minfornight" "There is 1 minute left until nightfall!" +"30secfornight" "30 seconds left until nightfall!" +"dota_tooltip_ability_item_test" "Test item" +"nearby_item_distance" "of units" + +"no_quests_available" "There are no currently available kvets. Check later." +"quests" "Job Board" +"quest_accept_button" "Accept" +"quest_rewards_title" "Rewards:" +"quest_reward_gold" "gold" +"quest_reward_experience" "experience" +"quest_reward_crystals" "Crystals" +"quest_reward_team_split" "split among team" +"quest_state_available" "Available" +"quest_state_in_progress" "In progress" +"quest_state_completed" "Completed" +"quest_state_locked" "Locked" +"quest_state_failed" "Failed" + + + + + +//Black market +"black_shop_refresh_description" "Want to refresh the shop contents?" +"black_shop_refresh_button" "UPDATE" +"black_shop_buy_card_description" "I also sell cards, but their price increases after each purchase." +"black_shop_buy_card_button" "BUY CARD" +"black_shop_not_enough_crystals" "Not enough Crystals" +"black_shop_not_enough_gold" "Not enough Denyag" +"black_shop_teleport_section_description" "Teleportation services: select your destination." +"black_shop_teleport_menu_open" "Teleport me to..." +"black_shop_teleport_title" "Where do you need to go?" +"black_shop_teleport_cost_label" "Teleport cost:" +"black_shop_teleport_to_grove" "Teleport me to the grove" +"black_shop_teleport_to_two_sisters" "Teleport me to Two Sisters" +"black_shop_teleport_to_village_waterfall" "Teleport me to the village beyond the waterfall" +"black_shop_teleport_to_ancient_ridge" "Teleport me to the ancient ridge" +"black_shop_back_button" "Back" +"black_shop_teleport_dead" "Teleport is unavailable while dead." +"black_shop_card_purchased" "Card purchased" +"black_shop_exchange_section_description" "Currency exchange: rate is 100 gold = 1 crystal." +"black_shop_exchange_open_button" "Open currency exchange" +"black_shop_exchange_title" "Currency Exchange" +"black_shop_exchange_hint" "Choose direction, set amount, then confirm." +"black_shop_exchange_summary_default" "Exchange 1 crystal for 100 gold" +"black_shop_exchange_max_default" "Max: 1" +"black_shop_exchange_apply_button" "Exchange" +"black_shop_exchange_not_enough" "Not enough resources" +"black_shop_exchange_max_prefix" "Max:" +"black_shop_exchange_summary_spend" "Spend" +"black_shop_exchange_summary_get" "receive" +"black_shop_exchange_result_spent" "Exchange: spent" +"black_shop_exchange_result_received" "received" +"black_shop_gold_unit" "gold" +"black_shop_crystal_unit_short" "crystals" + +//default_gameplay_modifiers + +"dota_tooltip_modifier_glyph_custom_passive" "Blessing ready" +"dota_tooltip_modifier_glyph_custom_passive_Description" "Protects against death at any second." +"dota_tooltip_modifier_glyph_custom" "Blessing" +"dota_tooltip_modifier_glyph_custom_Description" "The hero is gifted with protection from heaven." +"dota_tooltip_modifier_glyph_custom_passive_cd" "Blessing recharging" +"dota_tooltip_modifier_glyph_custom_passive_cd_Description" "Blessing went to recharge." + +"dota_tooltip_modifier_general_fired" "BURN" +"dota_tooltip_modifier_general_fired_Description" "The creature is on fire." + +"dota_tooltip_modifier_general_froze" "COOLING" +"dota_tooltip_modifier_general_froze_Description" "The creature is cold." + +"dota_tooltip_modifier_general_hunger" "Saturation" +"dota_tooltip_modifier_general_hunger_Description" "The hero received a bonus to all basic attributes." + +"dota_tooltip_modifier_item_pizza" "Pizza" +"dota_tooltip_modifier_item_pizza_Description" "Increases Strength, agility and Intelligence by the number of stacks." + + +"dota_tooltip_modifier_swamp_slow" "swamp fate" +"dota_tooltip_modifier_swamp_slow_Description" "The hero is mired in mud due to which he is slowed down by %MODIFIER_PROPERTY_MOVESPEED_BONUS_CONSTANT% of units and takes damage." + +//zapret + +"dota_hud_error_cheese_bad_target" "Not the right target." +"wait_some_time_before_tree_growth" "I think I should come back here a little later." +"dota_hud_error_full_inventory" "The target has a complete inventory." +"dota_hud_error_ability_not_ready" "The hero is already full, wait until he gets hungry." +"dota_hud_error_havent_charges" "The item has no charges." +"dota_hud_error_rubick_spellsteal_self" "You can’t steal an ability from yourself." +"dota_hud_error_rubick_spellsteal_no_ability" "The target does not have the right ability to steal." +"dota_hud_error_rubick_spellsteal_unstealable" "This ability cannot be stolen." +"dota_hud_error_not_in_zone" "The hero is not in the center of the cemetery." +"dota_hud_error_shovel_not_on_cross" "Move closer to the marked treasure spot." +"dota_hud_error_shovel_broken" "The shovel is broken — all treasure spots have been dug." +"dota_hud_error_font_of_mercy_cd" "The Font of Mercy is still recharging." +"dota_hud_error_font_of_mercy_no_crisis" "No allied hero in range is below the health threshold." + +//Events +"event_gold_rush" "Gold Rush" +"event_midas_hands" "Midas Madness" +"event_gold_rush_description" "Game fauna:

• Next to the hero, bags of gold fall into the limit of 800 units

• By picking them up you will get from 100 to 1000 gold
" +"event_midas_hands_description" "Midas Blessing:

• With a 15% chance of killing an enemy, a bag of gold falls out" + +//npc_dota_creature +"npc_pig" "Pig" +"npc_sheep" "Sheep" +"npc_wolf" "Wolf" +"npc_boss_lycan" "Black Fang" +"npc_ent" "Small Ancient Ent" +"npc_skeleton_zombie_undead" "Skeleton" +"npc_dead_skeleton_archer_undead" "Skeleton Archer" +"npc_skeleton_zombie_half_undead" "Skeleton scavenger" +"npc_dead_skeleton_undead" "Skeleton Swordsman" +"npc_witch" "The Witch" +"npc_chicken" "Chicken" +"npc_thief_leader" "Head of Thieves" +"npc_thief_archer" "Archer thief" +"npc_thief_backer" "Thief" +"npc_black_dragon" "Black Dragon" +"npc_red_dragon" "Red Dragon" +"npc_blue_dragon" "Blue Dragon" +"npc_blue_dragon_small" "Small blue dragon" +"npc_red_dragon_small" "Small Red Dragon" +"npc_lycosidae_stalker" "Little Little She-Eater" +"npc_venomancer_brute" "Big Snake" +"npc_ravenous_woodfang" "Damn bites" + +"npc_campfire" "Bonfire" +"npc_wisps" "Energy Clot" + +"npc_frop_tadpole" "Mini frog" +"npc_small_frog_froglet" "Frog" +"npc_mini_frog" "Tadpole" +"npc_frogman_magi" "Enchanted Frog" + +"npc_sakura_tree" "Sakura Tree" +"npc_mound" "Hoop" + + +"npc_dummy_test" "sosal for what?" + +"npc_attack_box" "box" + +//rename heroes +"npc_dota_hero_sargatanas" "Sargatanas" +"npc_dota_hero_elder_dragon_smaug" "Elder Dragon Smaug" +"npc_dota_hero_yuki_onna" "Yuki-onna" +"npc_dota_hero_bloodhunter" "Bloodhunter" +"npc_dota_hero_sand_king" "Sand King" +"npc_dota_hero_nagash" "Nagash" + + +"npc_dota_melee_nagash_summon" "Marduk" + "npc_dota_ranged_nagash_summon" "Appolon" + "npc_dota_mage_nagash_summon" "Odin" + "npc_dota_shield_nagash_summon" "Anubis" +"npc_dota_nagash_soul_eater" "Souleater" + +//npc +"npc_market_blackshop" "Illegal shopkeeper" +"npc_homer" "Head of the Village" +"homer_defend_toast_kicker" "Base defense" +"homer_defend_toast_title" "{hom_name} is under attack" +"homer_defend_toast_hint" "Return and drive enemies away from the spawn point." +"homer_defend_toast_fallback" "{hom_name} is under attack! Defend the base." +"homer_defend_toast_name_fallback" "Head of the Village" +"npc_quest_giver_kunkka" "Kunkka" +"npc_quest_giver_denny" "Denny" +"npc_quest_giver_oldmen" "Old Man" +"npc_quest_giver_firestar" "Firestar" +"npc_quest_giver_doctor" "Azaz the Herbalist" +"npc_quest_giver_friend" "Gourde" +"npc_quest_giver_largo" "Largo Writer" +"npc_quest_giver_maiden" "Friendly Smack" +"npc_quest_giver_lina" "Toxic Lina" + + + + +"npc_kot_roflik_0" "MerseyK" +"npc_kot_roflik_1" "beaver" +"npc_kot_roflik_2" "KulStorybobik" +"npc_dota_rofl_kaban_pumba" "rumba" +"npc_dota_golden_fish" "golden skirt" + + +//summon + +"npc_dota_fire_summon" "Fire Elemental" + + + +//npc_wave_creeps +"npc_wave_zombie" "Zonbu-Hodong" +"npc_wave_half_zombie" "Zonbu the carrion" +"npc_wave_toxin_zombie" "Zonbu-toxic" +"npc_wave_bearst_zombie" "Zonbu-Beast" +"npc_wave_ghost_ranged" "Ghost shooter" +"npc_wave_ghoul" "Ghoul" +"npc_wave_skeleton_warrior" "Warrior Skeleton" +"npc_wave_skeleton_assassin" "Assassin Skeleton" +"npc_wave_boss_death_prophet" "Boss prophet of death" +"npc_wave_boss_lifestealer" "Boss Life Eater" +"npc_wave_boss_skeleton" "Boss Skeleton" +"npc_wave_boss_zombie" "Boss zombie" + +"npc_wave_dead_enchantress" "Atrophy" + + +//creep_abilities +"dota_tooltip_ability_sheep_coil" "Magic Horns" +"dota_tooltip_ability_sheep_coil_description" "Eleases a lightning bolt that deals damage and slows enemies within a %radius% unit radius of its ill-wisher." +"dota_tooltip_ability_sheep_coil_damage" "DAMAGE:" +"dota_tooltip_ability_sheep_coil_slow_duration" "DURATION:" +"dota_tooltip_ability_sheep_coil_movement_slow" "%SLOW:" +"dota_tooltip_ability_sheep_coil_lore" "Horned motherfucker, with his magical horns gives pussy to his ill-wisher." +"dota_tooltip_modifier_sheep_coil_slow" "Sluggish magic" +"dota_tooltip_modifier_sheep_coil_slow_Description" "You are covered in magic liquid, which is why you are slowed down by %dMODIFIER_PROPERTY_MOVESPEED_BONUS_PERCENTAGE%%%" + +"dota_tooltip_ability_frogmen_acid_jump" "Acid Jump" +"dota_tooltip_ability_frogmen_acid_jump_description" "The frog jumps to a selected point and crashes to the ground, physically damaging enemies within a radius and stunning them." +"dota_tooltip_ability_frogmen_acid_jump_radius" "RADIUS:" +"dota_tooltip_ability_frogmen_acid_jump_stun_duration" "STUN DURATION:" +"dota_tooltip_ability_frogmen_acid_jump_land_damage" "LANDING DAMAGE:" +"dota_tooltip_ability_frogmen_acid_jump_lore" "Toads raised in mutated swamps have learned to unleash waves of acrid mucus on their enemies with a deafening blow." + +"dota_tooltip_ability_pig_charge" "Pig-in" +"dota_tooltip_ability_pig_charge_description" "The pig makes a dash towards its target, causing damage if it knocks and pushes away the deafening for a short period of time." +"dota_tooltip_ability_pig_charge_knockback_damage" "DAMAGE:" +"dota_tooltip_ability_pig_charge_stun_duration" "STUN:" +"dota_tooltip_ability_pig_charge_lore" "A pig that can't figure out what to do decided to try everything it can." + +"dota_tooltip_ability_thief_arrow" "Arrow" +"dota_tooltip_ability_thief_arrow_description" "The thief fires an arrow that flies in a straight line and physically damages the first enemy in its path." +"dota_tooltip_ability_thief_arrow_arrow_speed" "ARROW SPEED:" +"dota_tooltip_ability_thief_arrow_arrow_width" "ARROW WIDTH:" +"dota_tooltip_ability_thief_arrow_arrow_range" "RANGE:" +"dota_tooltip_ability_thief_arrow_lore" "Those who are too lazy in the shadows rarely have time to notice where the blow came from." + +"dota_tooltip_ability_thief_charge" "Shadow Jerk" +"dota_tooltip_ability_thief_charge_description" "The thief selects a target and rushes towards it at great speed, stunning and throwing enemies into a small area upon arrival." +"dota_tooltip_ability_thief_charge_movement_speed" "MOVEMENT SPEED:" +"dota_tooltip_ability_thief_charge_stun_duration" "STUN DURATION:" +"dota_tooltip_ability_thief_charge_bash_radius" "IMPACT RADIUS:" +"dota_tooltip_ability_thief_charge_lore" "For a real thief, the distance between the prey and the blade is only a matter of determination." + +"dota_tooltip_ability_agro_leader" "Scream" +"dota_tooltip_ability_agro_leader_description" "Makes a furious call, causing enemies within a %radius% radius to attack it within %duration% seconds." +"dota_tooltip_ability_agro_leader_radius" "RADIUS:" +"dota_tooltip_ability_agro_leader_duration" "DURATION:" +"dota_tooltip_ability_agro_leader_lore" "Whoever dares to challenge the Thieves Leader will inevitably be the center of attention." + +//wave_creep_abilities +"dota_tooltip_ability_zombie_virus" "Zombie virus" +"dota_tooltip_ability_zombie_virus_description" "%chance%%% chance on attack to apply a separate poison layer on the enemy (up to 9 layers on a target). Each layer deals %damage% damage per second, reduces movement speed by %slow_movespeed%%% and armor by %armor%. Layer duration is %duration% sec. A layer is removed if the zombie that applied it dies." +"dota_tooltip_ability_zombie_virus_damage" "DAMAGE PER SECOND:" +"dota_tooltip_ability_zombie_virus_slow_movespeed" "%SLOW:" +"dota_tooltip_ability_zombie_virus_armor" "ARMOR REDUCTION:" +"dota_tooltip_ability_zombie_virus_duration" "LAYER DURATION:" +"dota_tooltip_ability_zombie_virus_chance" "% CHANCE:" +"dota_tooltip_ability_zombie_virus_lore" "An ancient virus mutated in the bodies of the dead turns them into walking carriers of infection." + +"dota_tooltip_modifier_zombie_virus_debuff" "Zombie virus" +"dota_tooltip_modifier_zombie_virus_debuff_description" "Poison layer: damage over time, slow, and armor reduction. Multiple layers stack. %dMODIFIER_PROPERTY_MOVESPEED_BONUS_PERCENTAGE%%% movement speed." + +"dota_tooltip_ability_bone_armor" "Bone Armor" +"dota_tooltip_ability_bone_armor_description" "Grants bonus armor. Against ranged heroes: if no hero is within %hero_proximity_radius%, the attacker takes %isolated_damage_multiplier% times the damage dealt. Otherwise, has a %damage_reflect_chance%%% chance to reflect %damage_reflect_pct%%% of the damage back." +"dota_tooltip_ability_bone_armor_armor_bonus" "ARMOR:" +"dota_tooltip_ability_bone_armor_hero_proximity_radius" "HERO CHECK RADIUS:" +"dota_tooltip_ability_bone_armor_isolated_damage_multiplier" "ISOLATED DAMAGE MULTIPLIER:" +"dota_tooltip_ability_bone_armor_lore" "Bones tempered in the fire of the underworld become stronger than steel and are able to deflect blows back at the attacker." + +"dota_tooltip_ability_toxin" "Toxin" +"dota_tooltip_ability_toxin_description" "After death, it leaves a toxin pool that disables enemies' passive abilities within %radius% range. Overlapping pools merge into one: their damage stacks, radius grows by %merge_radius_bonus% per merge, and duration is extended by %merge_duration_bonus% s per merge." +"dota_tooltip_ability_toxin_damage" "DAMAGE:" +"dota_tooltip_ability_toxin_radius" "RADIUS:" +"dota_tooltip_ability_toxin_duration" "DURATION:" +"dota_tooltip_ability_toxin_merge_radius_bonus" "RADIUS PER MERGE:" +"dota_tooltip_ability_toxin_merge_duration_bonus" "DURATION PER MERGE (SEC):" +"dota_tooltip_ability_toxin_lore" "The deadly toxin released by the decomposing bodies of zombies poisons all life in the area and disrupts the functioning of magical abilities." + +"dota_tooltip_ability_skeleton_archer_fire_arrow" "Fire Arrows" +"dota_tooltip_ability_skeleton_archer_fire_arrow_description" "The archer skeleton fires fire arrows that damage and stun enemies in its path." +"dota_tooltip_ability_skeleton_archer_fire_arrow_bonus_damage" "DAMAGE:" +"dota_tooltip_ability_skeleton_archer_fire_arrow_lore" "Arrows tempered in the fire of the underworld become stronger than steel and are able to deflect blows back at the attacker." + +"dota_tooltip_ability_weaking_impetus" "Weakening resonance" +"dota_tooltip_ability_weaking_impetus_description" "In an attack, it imposes an effect on the target that reduces the outgoing damage by %damage_reduction%%% per stack. The effect adds up to %max_stacks% times." +"dota_tooltip_ability_weaking_impetus_damage_reduction" "%DESTRUCTION DAMAGE PER STACK:" +"dota_tooltip_ability_weaking_impetus_mana_hit" "MANA ON HIT:" +"dota_tooltip_ability_weaking_impetus_debuff_duration" "DURATION:" +"dota_tooltip_ability_weaking_impetus_lore" "The enchanted arrows of a dead forest nymph suck the power out of their victims, making their attacks weaker with every hit." + +"dota_tooltip_ability_zombie_armor_decress" "Armor-corrosive attack" +"dota_tooltip_ability_zombie_armor_decress_description" "Each attack reduces the target’s armor by %armor_debuff% to %corruption_duration% sec." +"dota_tooltip_ability_zombie_armor_decress_armor_debuff" "RESET ARMOR:" +"dota_tooltip_ability_zombie_armor_decress_corruption_duration" "DURATION:" + +"dota_tooltip_ability_zombie_rage" "Zombie Rage" +"dota_tooltip_ability_zombie_rage_description" "Enters into a rage at %duration% sec., receiving magic resistance and control effects." + +"dota_tooltip_ability_zombie_feast" "Feast" +"dota_tooltip_ability_zombie_feast_description" "Passively steals the target’s health when attacked. Against creeps, effectiveness is reduced." + +"dota_tooltip_ability_zombie_open_wounds" "Blood wound" +"dota_tooltip_ability_zombie_open_wounds_description" "Slow down target by %duration% sec. and it allows you to heal from the damage caused." +"dota_tooltip_ability_wave_desperate_vampirism" "Desperate Thirst" +"dota_tooltip_ability_wave_desperate_vampirism_description" "While below %hp_threshold_pct%%% max health, each landed attack heals for %vamp_pct%%% of the damage it dealt." +"dota_tooltip_ability_wave_desperate_vampirism_hp_threshold_pct" "% HP THRESHOLD (MAX):" +"dota_tooltip_ability_wave_desperate_vampirism_vamp_pct" "% OF DAMAGE HEALED:" + +"dota_tooltip_ability_skeleton_blast" "Hellburst" +"dota_tooltip_ability_skeleton_blast_description" "Stuns the target and deals damage: %damage% immediately and %blast_dot_damage% periodically." + +"dota_tooltip_ability_skeleton_king_mortal_strike" "Deathblow" +"dota_tooltip_ability_skeleton_king_mortal_strike_description" "Periodically strikes critically with a multiplier of %crit_mult%%." + +"dota_tooltip_modifier_weaking_impetus_debuff" "Weakening impetus" +"dota_tooltip_modifier_weaking_impetus_debuff_description" "Your outbound damage is reduced by %dMODIFIER_PROPERTY_DAMAGEOUTGOING_PERCENTAGE%%% and your magic gain is reduced by %dMODIFIER_PROPERTY_SPELL_AMPLIFY_PERCENTAGE%%%." + +//sven +"dota_tooltip_ability_ability_sven_storm_hammer_custom" "Storm Hammer" +"dota_tooltip_ability_ability_sven_storm_hammer_custom_Lore" "The iron mitten of a rebel knight, borrowed from his father’s school, knocks the spirit out of any enemy." +"dota_tooltip_ability_ability_sven_storm_hammer_custom_radius" "RADIUS:" +"dota_tooltip_ability_ability_sven_storm_hammer_custom_stun_duration" "STUN DURATION:" +"dota_tooltip_ability_ability_sven_storm_hammer_custom_stun_duration_for_boss_mult" "BOSS STUN DURATION MULTIPLIER:" +"dota_tooltip_ability_ability_sven_storm_hammer_custom_damage" "DAMAGE:" +"dota_tooltip_ability_ability_sven_storm_hammer_custom_Description" "Throws the hammer at an enemy unit or point: on impact, deals damage and stuns enemies in %radius%. With autocast on a tracking shot, purges the main target, performs an extra attack, and Sven follows the projectile." + +"dota_tooltip_ability_ability_sven_great_cleave_custom" "The Great Schism" +"dota_tooltip_ability_ability_sven_great_cleave_custom_Description" "Passive: attacks cleave in a cone (%cleave_damage_pct%%% of attack damage; width %cleave_starting_width%–%cleave_ending_width%, range %cleave_radius%). Active: costs %health_cost_pct%%% current health and empowers the next attack—it crits and cleaves for %cleave_damage_multiple_facet%%% of normal." +"dota_tooltip_ability_ability_sven_great_cleave_custom_Lore" "Sven’s powerful strikes can hit several opponents at once." +"dota_tooltip_ability_ability_sven_great_cleave_custom_cleave_damage_pct" "% CLEAVE DAMAGE:" +"dota_tooltip_ability_ability_sven_great_cleave_custom_cleave_radius" "DISTANCE:" +"dota_tooltip_ability_ability_sven_great_cleave_custom_health_cost_pct" "% CURRENT HEALTH (ACTIVE):" +"dota_tooltip_ability_ability_sven_great_cleave_custom_cleave_damage_multiple_facet" "% EMPOWERED CLEAVE POWER:" + +"dota_tooltip_ability_ability_sven_gods_strength_custom" "God's power" +"dota_tooltip_ability_ability_sven_gods_strength_custom_Description" "Sven calls on the power of the ancient gods, greatly increasing his damage. With the talent, also grants %gods_strength_damage_bonus_per_health%%% damage per point of missing health." +"dota_tooltip_ability_ability_sven_gods_strength_custom_Lore" "The ancient gods grant Sven incredible power in battle." +"dota_tooltip_ability_ability_sven_gods_strength_custom_gods_strength_damage_bonus" "% DAMAGE BONUS:" +"dota_tooltip_ability_ability_sven_gods_strength_custom_duration" "DURATION:" + +"dota_tooltip_ability_ability_sven_gods_strength_custom_Scepter_Description" "Sven receives an additional %gods_strength_bonus_strength_pct% force units for each armor unit for the duration of the ability." +"dota_tooltip_ability_ability_sven_gods_strength_custom_Shard_Description" "While God's Strength is active, Sven gains %gods_strength_shard_magic_resist%%% magic resistance." +"dota_tooltip_ability_ability_sven_gods_strength_custom_gods_strength_shard_magic_resist" "SHARD MAGIC RESIST:" + +"dota_tooltip_modifier_sven_gods_strength_custom" "God's Strength" +"dota_tooltip_modifier_sven_gods_strength_custom_Description" "Damage increased by %dMODIFIER_PROPERTY_DAMAGEOUTGOING_PERCENTAGE%%%" + +"dota_tooltip_ability_ability_sven_warcry_custom" "Battle Cry" +"dota_tooltip_ability_ability_sven_warcry_custom_Description" "Sven makes a battle cry that gives him and all allied heroes nearby a bonus to armor, movement speed and attack. For each piece of armor, the mighty knight receives additional damage in the amount of %damage_bonus_per_armor%." +"dota_tooltip_ability_ability_sven_warcry_custom_Lore" "Sven's battle cry instills courage in the hearts of allies and fear in enemies." +"dota_tooltip_ability_ability_sven_warcry_custom_armor_bonus" "%BONUS ARMOR:" +"dota_tooltip_ability_ability_sven_warcry_custom_movespeed_bonus" "%BONUS MOVEMENT SPEED:" +"dota_tooltip_ability_ability_sven_warcry_custom_attackspeed_bonus" "%BONUS ATTACK SPEED:" +"dota_tooltip_ability_ability_sven_warcry_custom_duration" "DURATION:" +"dota_tooltip_ability_ability_sven_warcry_custom_radius" "RADIUS:" + +"dota_tooltip_modifier_sven_warcry_custom_active" "Warcry" +"dota_tooltip_modifier_sven_warcry_custom_active_Description" "Warcry increases your armor, movement speed and attack speed by %dMODIFIER_PROPERTY_PHYSICAL_ARMOR_BONUS%%%, %dMODIFIER_PROPERTY_MOVESPEED_BONUS_PERCENTAGE%%% and %dMODIFIER_PROPERTY_ATTACKSPEED_PERCENTAGE%%% respectively." + +"dota_tooltip_modifier_sven_warcry_custom" "Warcry" +"dota_tooltip_modifier_sven_warcry_custom_Description" "Armor increased by %dMODIFIER_PROPERTY_PHYSICAL_ARMOR_BONUS%, movement speed increased by %dMODIFIER_PROPERTY_MOVESPEED_BONUS_PERCENTAGE%%%" + + +// -----------------Vengeful Spirit-------------------- +"dota_tooltip_ability_vengefulspirit_magic_missile_custom" "Magic Missile" +"dota_tooltip_ability_vengefulspirit_magic_missile_custom_Description" "Launches a magic missile at an enemy: stuns, deals damage, and burns mana. After the first hit, the missile bounces to the nearest enemy within cast range." +"dota_tooltip_ability_vengefulspirit_magic_missile_custom_Lore" "The simplest Skywrath technique—a magic missile—is Shendelzare's primary tool of vengeance." +"dota_tooltip_ability_vengefulspirit_magic_missile_custom_stun_duration" "STUN DURATION:" +"dota_tooltip_ability_vengefulspirit_magic_missile_custom_damage" "DAMAGE:" +"dota_tooltip_ability_vengefulspirit_magic_missile_custom_mana_burn" "MANA BURN:" + +"dota_tooltip_ability_vengefulspirit_wave_of_terror_custom" "Wave of Terror" +"dota_tooltip_ability_vengefulspirit_wave_of_terror_custom_Description" "Unleashes a terrifying scream: the wave grants vision, deals damage, and reduces enemy armor (flat and by %armor_reduction_pct%%% of base armor) and attack damage along its path." +"dota_tooltip_ability_vengefulspirit_wave_of_terror_custom_Lore" "Shendelzare's soul-shredding voice warns of her approach." +"dota_tooltip_ability_vengefulspirit_wave_of_terror_custom_armor_reduction" "ARMOR REDUCTION:" +"dota_tooltip_ability_vengefulspirit_wave_of_terror_custom_armor_reduction_pct" "%BASE ARMOR REDUCTION:" +"dota_tooltip_ability_vengefulspirit_wave_of_terror_custom_attack_reduction" "%ATTACK DAMAGE REDUCTION:" +"dota_tooltip_ability_vengefulspirit_wave_of_terror_custom_damage" "DAMAGE:" +"dota_tooltip_ability_vengefulspirit_wave_of_terror_custom_wave_width" "WAVE WIDTH:" +"dota_tooltip_ability_vengefulspirit_wave_of_terror_custom_vision_duration" "VISION DURATION:" + +"dota_tooltip_modifier_vengefulspirit_wave_of_terror_custom" "Wave of Terror" +"dota_tooltip_modifier_vengefulspirit_wave_of_terror_custom_Description" "Armor reduced. Attack damage reduced." + +"dota_tooltip_modifier_vengefulspirit_wave_of_terror_custom_buff" "Wave of Terror" +"dota_tooltip_modifier_vengefulspirit_wave_of_terror_custom_buff_Description" "Stolen armor and attack damage from enemies." + +"dota_tooltip_ability_ability_vengefulspirit_command_aura_custom" "Command Aura" +"dota_tooltip_ability_ability_vengefulspirit_command_aura_custom_Description" "Passive aura: nearby allies gain bonus base damage, physical lifesteal, and spell lifesteal." +"dota_tooltip_ability_ability_vengefulspirit_command_aura_custom_Lore" "Shendelzare shares her fury with those who fight beside her." +"dota_tooltip_ability_ability_vengefulspirit_command_aura_custom_bonus_base_damage" "BONUS DAMAGE:" +"dota_tooltip_ability_ability_vengefulspirit_command_aura_custom_physical_vampirism" "%PHYSICAL LIFESTEAL:" +"dota_tooltip_ability_ability_vengefulspirit_command_aura_custom_magical_vampirism" "%SPELL LIFESTEAL:" +"dota_tooltip_ability_ability_vengefulspirit_command_aura_custom_aura_radius" "RADIUS:" + +"dota_tooltip_ability_ability_vengefulspirit_spirit_debt_innate" "Spirit Feast" +"dota_tooltip_ability_ability_vengefulspirit_spirit_debt_innate_Description" "Your abilities mark enemies with Spirit Debt. When a marked enemy dies, you restore health and mana; allies in Command Aura radius receive a share of the heal." +"dota_tooltip_ability_ability_vengefulspirit_spirit_debt_innate_Lore" "Fallen spirits feed the living's vengeance." +"dota_tooltip_ability_ability_vengefulspirit_spirit_debt_innate_mark_duration" "MARK DURATION:" +"dota_tooltip_ability_ability_vengefulspirit_spirit_debt_innate_heal_on_kill_pct" "%HEAL FROM TARGET MAX HEALTH:" +"dota_tooltip_ability_ability_vengefulspirit_spirit_debt_innate_mana_restore" "MANA RESTORED:" +"dota_tooltip_ability_ability_vengefulspirit_spirit_debt_innate_ally_heal_share_pct" "%HEAL SHARED TO ALLIES:" + +"dota_tooltip_modifier_vengefulspirit_spirit_debt_mark" "Spirit Debt" +"dota_tooltip_modifier_vengefulspirit_spirit_debt_mark_Description" "Death will feed Vengeful Spirit and nearby allies." + +"dota_tooltip_ability_vengefulspirit_nether_swap_custom" "Nether Swap" +"dota_tooltip_ability_vengefulspirit_nether_swap_custom_Description" "Swaps health percentages with the target; a minimum portion of health remains. Transfers all timed buffs from Vengeful Spirit to an allied target." +"dota_tooltip_ability_vengefulspirit_nether_swap_custom_Lore" "Martyrdom is a small price to pay for vengeance." +"dota_tooltip_ability_vengefulspirit_nether_swap_custom_hit_point_minimum_pct" "%MIN HEALTH AFTER SWAP:" + +"dota_tooltip_ability_vengefulspirit_revenge" "Revenge" +"dota_tooltip_ability_vengefulspirit_revenge_Description" "Enters the shadow realm: cannot be targeted and every attack deals critical damage." +"dota_tooltip_ability_vengefulspirit_revenge_Lore" "Vengeance knows no mercy." +"dota_tooltip_ability_vengefulspirit_revenge_duration" "DURATION:" +"dota_tooltip_ability_vengefulspirit_revenge_crit_bonus" "%CRITICAL DAMAGE:" + +"DOTA_Tooltip_ability_special_bonus_unique_vengefulspirit_4_1" "+{s:bonus_heal_or_damage}% max health damage or heal on Nether Swap target" +"DOTA_Tooltip_ability_special_bonus_unique_vengefulspirit_4_2" "Nether Swap applies basic dispel" + +//talents sven +"DOTA_Tooltip_ability_special_bonus_unique_sven_warcry_duration_base" "+{s:bonus_duration} sec to the duration of the Battle Cry." +"DOTA_Tooltip_ability_special_bonus_unique_sven_storm_hammer_stun_duration" "+{s:bonus_stun_duration} sec to the Storm Hammer Stun" +"DOTA_Tooltip_ability_special_bonus_unique_sven_gods_strength_duration_base" "+{s:bonus_duration} sec to the duration of God's power." +"DOTA_Tooltip_ability_special_bonus_unique_sven_warcry_damage_bonus_per_armor" "+{s:bonus_damage_bonus_per_armor} to damage for each unit of armor from the Battle Cry." +"DOTA_Tooltip_ability_special_bonus_unique_sven_gods_strength_damage_bonus_base" "+{s:bonus_gods_strength_damage_bonus}% to damage from God's power." +"DOTA_Tooltip_ability_special_bonus_unique_sven_gods_strength_damage_per_health" "+{s:bonus_gods_strength_damage_bonus_per_health} bonus damage per point of missing health during God's Strength" +"DOTA_Tooltip_ability_special_bonus_unique_sven_great_cleave_damage_pct" "+{s:bonus_cleave_damage_pct}% to Great Schism damage" + +// -----------------Sand King-------------------- +"dota_tooltip_ability_sandking_burrowstrike_custom" "Burrowstrike" +"dota_tooltip_ability_sandking_burrowstrike_custom_Description" "Burrows underground and surges to the target point. Enemies in the way take magical damage and are stunned for %stun_duration% sec. Part of the damage scales with your max health." +"dota_tooltip_ability_sandking_burrowstrike_custom_Lore" "From the Scintillating Waste come legends written in sand." +"dota_tooltip_ability_sandking_burrowstrike_custom_burrow_anim_time" "BURROW WINDUP:" +"dota_tooltip_ability_sandking_burrowstrike_custom_burrow_width" "TUNNEL WIDTH:" +"dota_tooltip_ability_sandking_burrowstrike_custom_burrow_speed" "BURROW SPEED:" +"dota_tooltip_ability_sandking_burrowstrike_custom_AbilityCastRange" "CAST RANGE:" +"dota_tooltip_ability_sandking_burrowstrike_custom_stun_duration" "STUN DURATION:" +"dota_tooltip_ability_sandking_burrowstrike_custom_burrow_bonus_damage_max_hp_pct" "%MAX HEALTH DAMAGE:" + +"dota_tooltip_ability_sandking_sand_storm_custom" "Sand Storm" +"dota_tooltip_ability_sandking_sand_storm_custom_Description" "Raises a sand storm around you for %duration% sec. It follows you and every %damage_tick_rate% sec. strikes enemies within %sand_storm_radius% for magical damage, slowing them by %sand_storm_move_speed%%%. Base damage is %sand_storm_damage% per second; extra damage grows with your max health and health regen (see value below)." +"dota_tooltip_ability_sandking_sand_storm_custom_Lore" "In the Scintillating Waste, storms do not ask names—only who still stands in the wind." +"dota_tooltip_ability_sandking_sand_storm_custom_duration" "DURATION:" +"dota_tooltip_ability_sandking_sand_storm_custom_damage_tick_rate" "DAMAGE INTERVAL:" +"dota_tooltip_ability_sandking_sand_storm_custom_sand_storm_radius" "RADIUS:" +"dota_tooltip_ability_sandking_sand_storm_custom_sand_storm_damage" "BASE DAMAGE PER SECOND:" +"dota_tooltip_ability_sandking_sand_storm_custom_sand_storm_regen_damage_pct" "REGEN AND MAX HP SCALING:" +"dota_tooltip_ability_sandking_sand_storm_custom_sand_storm_move_speed" "%MOVE SLOW:" + +"dota_tooltip_ability_sandking_scorpion_strike_custom" "Scorpion Strike" +"dota_tooltip_ability_sandking_scorpion_strike_custom_Description" "Slams the tail into the ground, dealing physical damage to enemies in %radius% radius. Enemies inside the inner %inner_radius% radius take %inner_radius_bonus_damage_pct%%% more damage. Applies %strike_slow%%% slow for %debuff_duration% sec. Damage includes a flat bonus, a portion of your attack, and a share of your max health." +"dota_tooltip_ability_sandking_scorpion_strike_custom_Lore" "One sting into the sand—and the desert remembers who rules it." +"dota_tooltip_ability_sandking_scorpion_strike_custom_Scepter_Description" "Greatly reduces cooldown. Each enemy hit also gains Caustic Finale poison stacks %scepter_stack% times." +"dota_tooltip_ability_sandking_scorpion_strike_custom_radius" "RADIUS:" +"dota_tooltip_ability_sandking_scorpion_strike_custom_inner_radius" "INNER RADIUS:" +"dota_tooltip_ability_sandking_scorpion_strike_custom_inner_radius_bonus_damage_pct" "%BONUS DAMAGE INNER ZONE:" +"dota_tooltip_ability_sandking_scorpion_strike_custom_attack_damage" "BONUS DAMAGE:" +"dota_tooltip_ability_sandking_scorpion_strike_custom_attack_damage_pct_from_attack" "%ATTACK DAMAGE CONTRIBUTION:" +"dota_tooltip_ability_sandking_scorpion_strike_custom_scorpion_bonus_damage_max_hp_pct" "%MAX HEALTH DAMAGE:" +"dota_tooltip_ability_sandking_scorpion_strike_custom_debuff_duration" "SLOW DURATION:" +"dota_tooltip_ability_sandking_scorpion_strike_custom_strike_slow" "%MOVE SLOW:" +"dota_tooltip_ability_sandking_scorpion_strike_custom_scepter_stack" "POISON STACKS PER TARGET:" +"dota_tooltip_ability_sandking_scorpion_strike_custom_scepter_cd_pct" "%COOLDOWN REDUCTION:" +"dota_tooltip_ability_sandking_scorpion_strike_custom_AbilityCooldown" "COOLDOWN:" + +"dota_tooltip_ability_sandking_caustic_finale_custom" "Caustic Finale" +"dota_tooltip_ability_sandking_caustic_finale_custom_Description" "Attacks apply venom. After %max_attacks% hits the target detonates in %caustic_finale_radius% radius for magical damage and %explosion_slow_pct%%% slow. Explosion damage grows with your level. If a poisoned enemy dies early, the blast hits harder—part of the damage scales with the victim's max health and your level (see values below)." +"dota_tooltip_ability_sandking_caustic_finale_custom_Lore" "Caravans have drowned in worse poisons—and every step on hot sand can be the last." +"dota_tooltip_ability_sandking_caustic_finale_custom_caustic_finale_radius" "EXPLOSION RADIUS:" +"dota_tooltip_ability_sandking_caustic_finale_custom_explosion_damage_per_hero_level" "EXPLOSION DAMAGE PER LEVEL:" +"dota_tooltip_ability_sandking_caustic_finale_custom_kill_explosion_max_hp_pct_base" "ON DEATH — BASE BONUS (% OF VICTIM MAX HEALTH):" +"dota_tooltip_ability_sandking_caustic_finale_custom_kill_explosion_max_hp_pct_per_level" "ON DEATH — EXTRA PER LEVEL (% OF VICTIM MAX HEALTH):" +"dota_tooltip_ability_sandking_caustic_finale_custom_caustic_finale_duration" "POISON DURATION:" +"dota_tooltip_ability_sandking_caustic_finale_custom_max_attacks" "HITS TO DETONATE:" +"dota_tooltip_ability_sandking_caustic_finale_custom_explosion_slow_pct" "%EXPLOSION SLOW:" + +"dota_tooltip_ability_sandking_epicenter_custom" "Epicenter" +"dota_tooltip_ability_sandking_epicenter_custom_Description" "After a short cast, for %pulse_phase_duration% sec. sends out %epicenter_pulses% expanding shockwaves from your feet. You can move freely. Each wave deals %epicenter_damage% magical damage, slows movement by %epicenter_slow%%% and attack speed by %epicenter_slow_as% for %slow_duration% sec. Radius grows from %epicenter_radius_base% by %epicenter_radius_increment% per wave." +"dota_tooltip_ability_sandking_epicenter_custom_Lore" "The ground shudders, sand rises like a wall—and the desert answers its king." +"dota_tooltip_ability_sandking_epicenter_custom_Scepter_Description" "While the waves pulse, time advances %scepter_rolls_per_second% times per second: every %scepter_proc_every_n_time_checks% ticks, if enemies stand in the current ring, a full Scorpion Strike hits under them. Starts at one strike; every %scepter_luck_per_extra_tail% Luck adds another strike (cycles through different enemies when possible). Requires a learned Scorpion Strike." +"dota_tooltip_ability_sandking_epicenter_custom_Shard_Description" "Reduces cast time by %shard_cast_reduction% sec. Every %shard_break_count% pulses stuns enemies within %shard_break_radius% radius for %shard_break_duration% sec." +"dota_tooltip_ability_sandking_epicenter_custom_epicenter_pulses" "PULSE COUNT:" +"dota_tooltip_ability_sandking_epicenter_custom_epicenter_damage" "DAMAGE PER PULSE:" +"dota_tooltip_ability_sandking_epicenter_custom_epicenter_radius_base" "STARTING RADIUS:" +"dota_tooltip_ability_sandking_epicenter_custom_epicenter_radius_increment" "RADIUS GROWTH:" +"dota_tooltip_ability_sandking_epicenter_custom_epicenter_slow" "%MOVE SLOW:" +"dota_tooltip_ability_sandking_epicenter_custom_epicenter_slow_as" "ATTACK SLOW:" +"dota_tooltip_ability_sandking_epicenter_custom_slow_duration" "SLOW DURATION:" +"dota_tooltip_ability_sandking_epicenter_custom_pulse_phase_duration" "PULSE PHASE DURATION:" +"dota_tooltip_ability_sandking_epicenter_custom_scepter_rolls_per_second" "TIME TICKS PER SECOND:" +"dota_tooltip_ability_sandking_epicenter_custom_scepter_proc_every_n_time_checks" "TAIL PROC EVERY N TICKS:" +"dota_tooltip_ability_sandking_epicenter_custom_scepter_luck_per_extra_tail" "LUCK PER +1 EXTRA STRIKE:" +"dota_tooltip_ability_sandking_epicenter_custom_shard_cast_reduction" "CAST TIME REDUCTION:" +"dota_tooltip_ability_sandking_epicenter_custom_shard_break_duration" "STUN DURATION:" +"dota_tooltip_ability_sandking_epicenter_custom_shard_break_count" "PULSES BETWEEN STUNS:" +"dota_tooltip_ability_sandking_epicenter_custom_shard_break_radius" "STUN RADIUS:" + +"DOTA_Tooltip_ability_special_bonus_unique_sandking_burrow_range" "+{s:bonus_AbilityCastRange} Burrowstrike cast range" +"DOTA_Tooltip_ability_special_bonus_unique_sandking_burrow_stun" "+{s:bonus_stun_duration}s Burrowstrike stun" +"DOTA_Tooltip_ability_special_bonus_unique_sandking_storm_damage" "+{s:bonus_sand_storm_damage} Sand Storm magical damage per second" +"DOTA_Tooltip_ability_special_bonus_unique_sandking_scorpion_cd" "-{s:bonus_AbilityCooldown}s Scorpion Strike cooldown" +"DOTA_Tooltip_ability_special_bonus_unique_sandking_epicenter_pulses" "+{s:bonus_epicenter_pulses} Epicenter pulses" +"DOTA_Tooltip_ability_special_bonus_unique_sandking_epicenter_damage" "+{s:bonus_epicenter_damage} Epicenter damage per pulse" +"DOTA_Tooltip_ability_special_bonus_unique_sandking_finale_radius" "+{s:bonus_caustic_finale_radius} Caustic Finale explosion radius" + + +// -------------------------Juggernaut-------------------- +"dota_tooltip_ability_ability_juggernaut_blade_fury_custom" "Blade Fury" +"dota_tooltip_ability_ability_juggernaut_blade_fury_custom_Description" "Juggernaut spins, dealing his damage + magic in %radius% radius and gaining spell immunity.

Alt cast sends the storm to a point." +"dota_tooltip_ability_ability_juggernaut_blade_fury_custom_Lore" "Juggernaut's skill with a blade allows him to create a deadly vortex that sweeps away all enemies in his path." +"dota_tooltip_ability_ability_juggernaut_blade_fury_custom_duration" "DURATION:" +"dota_tooltip_ability_ability_juggernaut_blade_fury_custom_radius" "RADIUS:" +"dota_tooltip_ability_ability_juggernaut_blade_fury_custom_abilitycastrange" "VORTEX APPLICATION RANGE:" +"dota_tooltip_ability_ability_juggernaut_blade_fury_custom_damage_per_tick" "DAMAGE PER TICK:" +"dota_tooltip_ability_ability_juggernaut_blade_fury_custom_Shard_Description" "Increases damage and ability duration. Adds a passive effect: attacks have a %proc_chance%%% chance (Luck) to grant one Blade Fury tick or extend an active storm." + + + +"dota_tooltip_modifier_juggernaut_blade_fury_custom" "Blade Fury" +"dota_tooltip_modifier_juggernaut_blade_fury_custom_Description" "The hero revolves around himself, inflicting magical damage to enemies within a radius. The magic immunity is active, but the damage from attacks is reduced." + +"dota_tooltip_ability_ability_juggernaut_healing_ward_custom" "Healing Ward" +"dota_tooltip_ability_ability_juggernaut_healing_ward_custom_Description" "Jagernaut calls for a healing ward that follows the hero and heals all allies within a radius of %heal_radius% units at %heal_per_second%% of their maximum health per second. Ward exists %ward_duration% seconds." +"dota_tooltip_ability_ability_juggernaut_healing_ward_custom_Lore" "An ancient ward created by master healers carries the power of life to heal even the most severe wounds." +"dota_tooltip_ability_ability_juggernaut_healing_ward_custom_ward_duration" "DURATION:" +"dota_tooltip_ability_ability_juggernaut_healing_ward_custom_heal_radius" "TREATMENT RADIUS:" +"dota_tooltip_ability_ability_juggernaut_healing_ward_custom_heal_per_second" "% TREATMENT PER SECOND:" + +"dota_tooltip_modifier_juggernaut_healing_ward_custom" "Healing Ward" +"dota_tooltip_modifier_juggernaut_healing_ward_custom_Description" "Vard heals all allies within a radius." + +"dota_tooltip_ability_ability_juggernaut_blade_dance_custom" "Blade Dance" +"dota_tooltip_ability_ability_juggernaut_blade_dance_custom_Description" "Grants critical strike chance.

Active — Astral Slash:

Dash to a point (%min_travel_distance%–%max_travel_distance%), line width %astral_slash_radius%, %astral_slash_attack_count% slashes (uses your crits). Each slash applies a %astral_slash_debuff_duration% sec debuff that reduces armor by %juggernaut_blade_dance_jugg_step_armor_reduce%%%.

Alt: after 0.25s dash back to start, damaging the line again." +"dota_tooltip_ability_ability_juggernaut_blade_dance_custom_Lore" "The art of wielding Jagernaut's blade allows him to find enemies' vulnerabilities and cut everything in his path." +"dota_tooltip_ability_ability_juggernaut_blade_dance_custom_crit_chance" "%CRITICAL IMPACT CHANCE:" +"dota_tooltip_ability_ability_juggernaut_blade_dance_custom_crit_mult" "%CRITICAL DAMAGE:" +"dota_tooltip_ability_ability_juggernaut_blade_dance_custom_lifesteal_percent" "% VAMPIRISM:" +"dota_tooltip_ability_ability_juggernaut_blade_dance_custom_AbilityCastRange" "BREAK RANGE:" +"dota_tooltip_ability_ability_juggernaut_blade_dance_custom_AbilityCooldown" "RECHARGE:" +"dota_tooltip_ability_ability_juggernaut_blade_dance_custom_AbilityManaCost" "MANA CONSUMPTION:" +"dota_tooltip_ability_ability_juggernaut_blade_dance_custom_min_travel_distance" "MIN. SNATCH DISTANCE:" +"dota_tooltip_ability_ability_juggernaut_blade_dance_custom_max_travel_distance" "MAX. SNATCH DISTANCE:" +"dota_tooltip_ability_ability_juggernaut_blade_dance_custom_astral_slash_radius" "LINE WIDTH:" + +"dota_tooltip_modifier_juggernaut_blade_dance_astral_slash_debuff" "Astral Slash" +"dota_tooltip_modifier_juggernaut_blade_dance_astral_slash_debuff_Description" "Struck by a blow from Astral Slash." + +"dota_tooltip_ability_ability_juggernaut_omnislash_custom" "Omnislash" +"dota_tooltip_ability_ability_juggernaut_omnislash_custom_Description" "Jagernaut jumps to a selected target and launches a series of attacks, dealing %damage%% attack damage. The ability continues %duration% seconds by jumping between enemies within a radius of %bounce_radius% units. During the action, the hero is invulnerable and cannot be stopped." +"dota_tooltip_ability_ability_juggernaut_omnislash_custom_Lore" "Jagernaut's greatest technique, allowing him to land many punches in an instant, turning enemies to dust." +"dota_tooltip_ability_ability_juggernaut_omnislash_custom_duration" "DURATION:" +"dota_tooltip_ability_ability_juggernaut_omnislash_custom_damage" "%ATTACK DAMAGE:" +"dota_tooltip_ability_ability_juggernaut_omnislash_custom_bounce_radius" "JUMP RADIUS:" +"dota_tooltip_ability_ability_juggernaut_omnislash_custom_slash_interval_mult" "ATTACK RATE MULTIPLIER:" +"dota_tooltip_ability_ability_juggernaut_omnislash_custom_Scepter_Description" "Additionally gives the Swift Slash ability, which delivers a quick strike on the target." + +"dota_tooltip_ability_ability_juggernaut_miniomnislash_custom" "Swift Slash" +"dota_tooltip_ability_ability_juggernaut_miniomnislash_custom_duration" "DURATION:" +"dota_tooltip_ability_ability_juggernaut_miniomnislash_custom_damage" "%ATTACK DAMAGE:" +"dota_tooltip_ability_ability_juggernaut_miniomnislash_custom_bounce_radius" "JUMP RADIUS:" +"dota_tooltip_ability_ability_juggernaut_miniomnislash_custom_slash_interval_mult" "ATTACK RATE MULTIPLIER:" +"dota_tooltip_ability_ability_juggernaut_miniomnislash_custom_Description" "Jagernaut makes a quick jump to the target, dealing %damage%% attack damage. Duration: %duration% seconds." +"dota_tooltip_ability_ability_juggernaut_miniomnislash_custom_Lore" "A simplified version of Omnislash that allows you to quickly strike a target." + +"dota_tooltip_modifier_juggernaut_omnislash_custom" "Omnislash" +"dota_tooltip_modifier_juggernaut_omnislash_custom_Description" "The hero is invulnerable and jumps between enemies, launching a series of attacks." + +"dota_tooltip_ability_ability_juggernaut_samurai_soul" "Samurai Soul" +"dota_tooltip_ability_ability_juggernaut_samurai_soul_Description" "When a hero is about to die, he instantly restores his health to the maximum and gains a stack of weakness. For each stack, the hero loses %debuff_pct%%% of basic characteristics (strength, agility, intelligence). Maximum 4 glasses, after receiving 4 glasses the hero becomes mortal. If the hero is not attacked by %cooldown% seconds, all stakes are reset." +"dota_tooltip_ability_ability_juggernaut_samurai_soul_Lore" "The soul of the samurai Jagernaut allows him to survive in the most desperate situations, but the price for this is gradual weakening." +"dota_tooltip_ability_ability_juggernaut_samurai_soul_cooldown" "TIME TO STACK RESET:" +"dota_tooltip_ability_ability_juggernaut_samurai_soul_debuff_pct" "%ATTENUATION OF CHARACTERISTICS:" + +"dota_tooltip_modifier_juggernaut_samurai_soul" "Samurai Soul" +"dota_tooltip_modifier_juggernaut_samurai_soul_Description" "For each stack, 25%% of the base stats are lost. If the hero has not been attacked for some time, the stacks are reset." + + + + + + + +//Talents Juggernaut +"dota_tooltip_ability_special_bonus_unique_juggernaut_healing_ward_duration_custom" "+{s:bonus_ward_duration} seconds to Healing Ward duration." +"dota_tooltip_ability_special_bonus_unique_juggernaut_blade_dance_lifesteal_custom" "+{s:bonus_lifesteal_percent}% physical vampirism in Blade Dance." +"dota_tooltip_ability_special_bonus_unique_juggernaut_blade_fury_radius_custom" "+{s:bonus_radius} units to the Blade Fury radius." + + + +//Phantom Assassin +"dota_tooltip_ability_ability_phantom_assassin_stifling_dagger_custom" "Stifling Dagger" +"dota_tooltip_ability_ability_phantom_assassin_stifling_dagger_custom_Description" "Throws a dagger that slows movement, deals %damage% + %attack_factor%%% of attack, and applies on-hit effects. With the talent, also hits up to 2 extra enemies near the main target." +"dota_tooltip_ability_ability_phantom_assassin_stifling_dagger_custom_Note0" "The attack effects work with their usual probability." +"dota_tooltip_ability_ability_phantom_assassin_stifling_dagger_custom_Note1" "The ability gives an overview around the dagger during flight and after hitting the target." +"dota_tooltip_ability_ability_phantom_assassin_stifling_dagger_custom_Lore" "The first skill learned by the Veil Sisters often foreshadows a quick strike." +"dota_tooltip_ability_ability_phantom_assassin_stifling_dagger_custom_slow_movespeed" "%SLOW MOVEMENT:" +"dota_tooltip_ability_ability_phantom_assassin_stifling_dagger_custom_duration" "DURATION:" +"dota_tooltip_ability_ability_phantom_assassin_stifling_dagger_custom_damage" "BASIC DAMAGE:" +"dota_tooltip_ability_ability_phantom_assassin_stifling_dagger_custom_attack_factor" "%ATTACK DAMAGE:" +"dota_tooltip_ability_ability_phantom_assassin_stifling_dagger_custom_abilitycastrange" "RANGE OF APPLICATION:" + + + +"dota_tooltip_modifier_ability_phantom_assassin_stifling_dagger_slow" "Stifling Dagger" +"dota_tooltip_modifier_ability_phantom_assassin_stifling_dagger_slow_Description" "Movement speed slowdown by %dMODIFIER_PROPERTY_MOVESPEED_BONUS_PERCENTAGE%%%" + +//Phantom Strike +"dota_tooltip_ability_ability_phantom_assassin_phantom_strike_custom" "Phantom strike" +"dota_tooltip_ability_ability_phantom_assassin_phantom_strike_custom_Description" "Teleports to the target and gains attack speed for %bonus_duration% sec. With the crit talent: no cooldown, can target unit or ground, buff grants a critical strike for %crit_mult%%% damage." +"dota_tooltip_ability_ability_phantom_assassin_phantom_strike_custom_Note0" "Can be used on both enemies and allies." +"dota_tooltip_ability_ability_phantom_assassin_phantom_strike_custom_Note1" "The attack speed bonus only works when attacking enemy creatures." +"dota_tooltip_ability_ability_phantom_assassin_phantom_strike_custom_attack_speed_bonus" "ATTACK SPEED BONUS:" +"dota_tooltip_ability_ability_phantom_assassin_phantom_strike_custom_bonus_duration" "DURATION:" +"dota_tooltip_ability_ability_phantom_assassin_phantom_strike_custom_Lore" "Technique honed by years of training allows Mortred to disappear and appear next to the target in the blink of an eye." + +"dota_tooltip_modifier_phantom_assassin_phantom_strike_custom" "Phantom Strike" +"dota_tooltip_modifier_phantom_assassin_phantom_strike_custom_Description" "Your attack speed is increased by %dMODIFIER_PROPERTY_ATTACKSPEED_BONUS_CONSTANT%" + +//Blur +"dota_tooltip_ability_ability_phantom_assassin_blur_custom" "Blur" +"dota_tooltip_ability_ability_phantom_assassin_blur_custom_Description" "The hero becomes invisible, gaining bonus agility and movement speed for %duration% sec." +"dota_tooltip_ability_ability_phantom_assassin_blur_custom_Lore" "Mortred is so adept at hiding her presence that she can become invisible to even the most keen-eyed." +"dota_tooltip_ability_ability_phantom_assassin_blur_custom_bonus_agility_pct" "% agility BONUS:" +"dota_tooltip_ability_ability_phantom_assassin_blur_custom_move_speed_pct" "% MOVEMENT SPEED BONUS:" +"dota_tooltip_ability_ability_phantom_assassin_blur_custom_bonus_agility" "agility BONUS:" +"dota_tooltip_ability_ability_phantom_assassin_blur_custom_duration" "DURATION:" + +"dota_tooltip_modifier_phantom_assassin_blur_active_custom" "Blink" +"dota_tooltip_modifier_phantom_assassin_blur_active_custom_Description" "You have become invisible and accelerated by %dMODIFIER_PROPERTY_MOVESPEED_BONUS_PERCENTAGE%%%" +"dota_tooltip_ability_ability_phantom_assassin_blur_custom_Shard_Description" "Creates an illusion at the cast point. The illusion lasts for the same duration as Blur, deals %shard_illusion_damage_outgoing%%% damage, and takes %shard_illusion_damage_incoming%%% incoming damage." + +//Coup de Grace +"dota_tooltip_ability_ability_phantom_assassin_coup_de_grace_custom" "Coup de grace" +"dota_tooltip_ability_ability_phantom_assassin_coup_de_grace_custom_Description" "Each attack has a chance to critically strike." +"dota_tooltip_ability_ability_phantom_assassin_coup_de_grace_custom_Lore" "Mortred often strikes a blow of death when the enemy is no longer able to withstand his attack." +"dota_tooltip_ability_ability_phantom_assassin_coup_de_grace_custom_crit_chance" "%CRITICAL ATTACK CHANCE:" +"dota_tooltip_ability_ability_phantom_assassin_coup_de_grace_custom_crit_mult" "%CRITICAL DAMAGE:" +"dota_tooltip_ability_ability_phantom_assassin_coup_de_grace_custom_damage_mult" "% BONUS DAMAGE:" +"dota_tooltip_ability_ability_phantom_assassin_coup_de_grace_custom_health_mult_decrease" "% HEALTH LOSS:" +"dota_tooltip_ability_ability_phantom_assassin_coup_de_grace_custom_Scepter_Description" "Becomes an active ability. For %duration% sec, every attack is a critical strike and reduces target armor by %armor_reduction% + %armor_reduction_agility_pct%%% of agility. Cooldown: %ability_cooldown% sec." +"dota_tooltip_ability_ability_phantom_assassin_coup_de_grace_custom_Note0" "The first attack on a target during the ability reduces its armor." + +"dota_tooltip_modifier_phantom_assassin_coup_de_grace_custom_armor_reduction" "Weakness" +"dota_tooltip_modifier_phantom_assassin_coup_de_grace_custom_armor_reduction_Description" "Target lost %dMODIFIER_PROPERTY_PHYSICAL_ARMOR_BONUS% of armor." + +"dota_tooltip_modifier_phantom_assassin_coup_de_grace_custom_active" "Coup de Grace" +"dota_tooltip_modifier_phantom_assassin_coup_de_grace_custom_active_Description" "Each attack is critical and reduces the target's armor." + +//phantom bash +"dota_tooltip_ability_ability_phantom_assassin_phantom_bash_custom" "Phantom Daze" +"dota_tooltip_ability_ability_phantom_assassin_phantom_bash_custom_Description" "Phantom Assassin strikes, stunning the target for %bash_duration% sec. Cooldown: %cooldown% sec." +"dota_tooltip_ability_ability_phantom_assassin_phantom_bash_custom_Note0" "Recharging is reduced by %bash_cd_level% seconds per hero level." + +"dota_tooltip_modifier_cooldown" "Phantom Stun" +"dota_tooltip_modifier_cooldown_Description" "Phantom Stun is recharging." + +//talents phantom assassin +"dota_tooltip_ability_special_bonus_unique_assassin_blur_duration_base" "+{s:bonus_duration} seconds blur." +"dota_tooltip_ability_special_bonus_unique_assassin_blur_agility_base" "+{s:bonus_bonus_agility} agility in the blur effect." +"dota_tooltip_ability_special_bonus_unique_assassin_stifling_dagger_damage_base" "+{s:bonus_attack_factor}% of the damage from a Stifling dagger attack." +"dota_tooltip_ability_special_bonus_unique_assassin_stifling_dagger_chance_again" "An attack with a {s:bonus_chance}% chance will release a Stifling dagger." +"dota_tooltip_ability_special_bonus_unique_assassin_stifling_dagger_chance_again_Description" "★The dagger uses a pseudo-random to determine the critical strike. chance + 2% with each blow until it works." +"dota_tooltip_ability_special_bonus_unique_assassin_phantom_strike_lifesteal" "+{s:bonus_lifesteal_percent}% Vampirism under the influence of Phantom strike." +"dota_tooltip_ability_special_bonus_unique_assassin_stifling_dagger_max_targets" "+{s:bonus_max_targets} additional targets for Stifling Dagger" +"dota_tooltip_ability_special_bonus_unique_assassin_phantom_strike_crit" "Phantom Strike: {s:bonus_crit_chance}% crit chance, {s:bonus_crit_mult}% crit multiplier" + + + +// -----------------Templar Assassin-------------------- +"dota_tooltip_ability_ability_templar_assassin_refraction_custom" "Refraction" +"dota_tooltip_ability_ability_templar_assassin_refraction_custom_Description" "Grants a shield that blocks %instances% incoming attacks and empowers the next attacks with +%bonus_damage% bonus damage for %duration% sec." +"dota_tooltip_ability_ability_templar_assassin_refraction_custom_instances" "CHARGES:" +"dota_tooltip_ability_ability_templar_assassin_refraction_custom_bonus_damage" "BONUS DAMAGE:" +"dota_tooltip_ability_ability_templar_assassin_refraction_custom_duration" "DURATION:" +"dota_tooltip_ability_ability_templar_assassin_refraction_custom_Shard_Description" "Increases Refraction charges by %shard_instances_bonus%." +"dota_tooltip_ability_ability_templar_assassin_refraction_custom_Lore" "A secret technique that allows Lanaya to foresee the moment of impact and answer in an instant." + +"dota_tooltip_ability_ability_templar_assassin_psi_blades_custom" "Psi Blades" +"dota_tooltip_ability_ability_templar_assassin_psi_blades_custom_Description" "Innate. Attacks pierce through the target and deal %attack_spill_pct%%% damage to enemies behind. Bonus attack range increases with hero level." +"dota_tooltip_ability_ability_templar_assassin_psi_blades_custom_bonus_attack_range" "BASE BONUS RANGE:" +"dota_tooltip_ability_ability_templar_assassin_psi_blades_custom_bonus_attack_range_per_hero_level" "RANGE PER HERO LEVEL:" +"dota_tooltip_ability_ability_templar_assassin_psi_blades_custom_attack_spill_range" "SPILL RANGE:" +"dota_tooltip_ability_ability_templar_assassin_psi_blades_custom_attack_spill_pct" "%SPILL DAMAGE:" +"dota_tooltip_ability_ability_templar_assassin_psi_blades_custom_Lore" "Psi energy passes through the target like a blade through shadow, striking those who hide behind their ally." + +"dota_tooltip_ability_ability_templar_assassin_meld_custom" "Meld" +"dota_tooltip_ability_ability_templar_assassin_meld_custom_Description" "Two modes. Default: become invisible for %duration% sec. Alt cast: blink and fire attack projectiles at up to %blink_attack_targets% enemies in radius (armor reduced by %armor_reduction%)." +"dota_tooltip_ability_ability_templar_assassin_meld_custom_blink_attack_targets" "TARGETS:" +"dota_tooltip_ability_ability_templar_assassin_meld_custom_meld_damage_deal" "FIRST-STRIKE BONUS DAMAGE:" +"dota_tooltip_ability_ability_templar_assassin_meld_custom_armor_reduction" "ARMOR REDUCTION:" +"dota_tooltip_ability_ability_templar_assassin_meld_custom_radius" "SEARCH RADIUS:" +"dota_tooltip_ability_ability_templar_assassin_meld_custom_duration" "INVIS DURATION:" +"dota_tooltip_ability_ability_templar_assassin_meld_custom_Shard_Description" "Alt cast hits %shard_bonus_targets% additional target." +"dota_tooltip_ability_ability_templar_assassin_meld_custom_Lore" "One breath, one step into the void, and the enemy no longer understands where the killing blow came from." + +"dota_tooltip_ability_ability_templar_assassin_templar_secret_custom" "Templar Secret" +"dota_tooltip_ability_ability_templar_assassin_templar_secret_custom_Description" "Passively grants a custom critical strike with %temp_crit_chance%%% chance and %temp_crit_mult%%% damage." +"dota_tooltip_ability_ability_templar_assassin_templar_secret_custom_temp_crit_chance" "%CRIT CHANCE:" +"dota_tooltip_ability_ability_templar_assassin_templar_secret_custom_temp_crit_mult" "%CRIT DAMAGE:" +"dota_tooltip_ability_ability_templar_assassin_templar_secret_custom_Lore" "The secrets of the Veiled Ones open only to the chosen: every precise strike can become a sentence." + +"dota_tooltip_ability_ability_templar_assassin_bedlam_custom" "Bedlam" +"dota_tooltip_ability_ability_templar_assassin_bedlam_custom_Description" "A companion orbits the hero and periodically fires projectiles. On a hit, Templar Assassin performs a normal attack on the target." +"dota_tooltip_ability_ability_templar_assassin_bedlam_custom_attack_interval" "ATTACK INTERVAL:" +"dota_tooltip_ability_ability_templar_assassin_bedlam_custom_attack_radius" "ATTACK RADIUS:" +"dota_tooltip_ability_ability_templar_assassin_bedlam_custom_attack_targets" "TARGETS:" +"dota_tooltip_ability_ability_templar_assassin_bedlam_custom_Scepter_Description" "Increases targets by %scepter_bonus_targets% and speeds up companion attacks to %scepter_interval_mult_pct%%% of the base interval." +"dota_tooltip_ability_ability_templar_assassin_bedlam_custom_Lore" "A companion born from ancient mysteries circles nearby, tearing enemy lines apart with a storm of psi bolts." + +"dota_tooltip_ability_ability_templar_assassin_refusion_trap_custom" "Refusion Trap" +"dota_tooltip_ability_ability_templar_assassin_refusion_trap_custom_Description" "Places a trap. When Templar Assassin attacks the trap, it fires projectiles at nearby enemies." +"dota_tooltip_ability_ability_templar_assassin_refusion_trap_custom_radius" "TRAP RADIUS:" +"dota_tooltip_ability_ability_templar_assassin_refusion_trap_custom_count" "PROJECTILES:" +"dota_tooltip_ability_ability_templar_assassin_refusion_trap_custom_duration" "TRAP DURATION:" +"dota_tooltip_ability_ability_templar_assassin_refusion_trap_custom_Lore" "A trap prepared in advance patiently waits for its mistress's touch to unleash psi wrath upon nearby foes." + +"dota_tooltip_ability_special_bonus_unique_templar_assassin_4" "+{s:bonus_bonus_damage} Refraction attack damage" +"dota_tooltip_ability_special_bonus_unique_templar_assassin_8" "+{s:bonus_instances} Refraction charges" +"dota_tooltip_ability_special_bonus_unique_templar_assassin_3" "+{s:bonus_radius} Meld search radius" +"dota_tooltip_ability_special_bonus_unique_templar_assassin_2" "Bedlam: +{s:bonus_attack_targets} target" +"dota_tooltip_ability_special_bonus_unique_templar_assassin_7" "+{s:bonus_attack_spill_pct}% Psi Blades spill damage" + +//Crystal Maiden +"dota_tooltip_ability_ability_crystal_maiden_crystal_nova_custom" "Crystal nova" +"dota_tooltip_ability_ability_crystal_maiden_crystal_nova_custom_Description" "Creates an explosion of crystal energy at a point, dealing magic damage to enemies and healing allies in %radius% radius. Damage and heal scale with Intelligence. Enemies gain frost stacks. Overheal on allies becomes a shield (up to %max_shield_pct%%% of their max health, %shield_duration% sec)." +"dota_tooltip_ability_ability_crystal_maiden_crystal_nova_custom_Lore" "The ancient magic of ice crystals, transmitted to Riley from her sister, can both freeze enemies and heal allies." +"dota_tooltip_ability_ability_crystal_maiden_crystal_nova_custom_damage" "DAMAGE:" +"dota_tooltip_ability_ability_crystal_maiden_crystal_nova_custom_heal" "HEALING:" +"dota_tooltip_ability_ability_crystal_maiden_crystal_nova_custom_radius" "RADIUS:" +"dota_tooltip_ability_ability_crystal_maiden_crystal_nova_custom_intellect_per_damage" "HEAL/DAMAGE FROM INT:" +"dota_tooltip_ability_ability_crystal_maiden_crystal_nova_custom_frost_stacks_per_level" "FROST STACKS:" +"dota_tooltip_ability_ability_crystal_maiden_crystal_nova_custom_slow_duration" "SLOW DURATION:" +"dota_tooltip_ability_ability_crystal_maiden_crystal_nova_custom_max_shield_pct" "MAX SHIELD (% OF MAX HEALTH):" +"dota_tooltip_ability_ability_crystal_maiden_crystal_nova_custom_shield_duration" "SHIELD DURATION:" + +"dota_tooltip_ability_ability_crystal_maiden_frostbite_custom" "Frostbite" +"dota_tooltip_ability_ability_crystal_maiden_frostbite_custom_Description" "Roots a target in %radius% radius for %duration% sec. Enemies take periodic magic damage; allies are healed and take %incoming_damage_pct%%% less incoming damage. Alt cast doubles damage vs enemies only. Enemies below %execute_threshold_pct%%% health are executed: leaves an ice copy that can be hit—it flies away and explodes in %explosion_radius% for damage (%damage_per_mana% per point of caster mana)." +"dota_tooltip_ability_ability_crystal_maiden_frostbite_custom_Lore" "Riley's ice shackles bind enemies, preventing them from moving until the cold penetrates their bones." +"dota_tooltip_ability_ability_crystal_maiden_frostbite_custom_damage" "DAMAGE PER SECOND:" +"dota_tooltip_ability_ability_crystal_maiden_frostbite_custom_heal_per_second" "HEAL PER SECOND:" +"dota_tooltip_ability_ability_crystal_maiden_frostbite_custom_incoming_damage_pct" "%ALLY INCOMING DAMAGE REDUCTION:" +"dota_tooltip_ability_ability_crystal_maiden_frostbite_custom_duration" "DURATION:" +"dota_tooltip_ability_ability_crystal_maiden_frostbite_custom_shard_Description" "Enhances the damage reduction effect." + +"dota_tooltip_ability_ability_crystal_maiden_frostbite_custom_radius" "RADIUS:" +"dota_tooltip_ability_ability_crystal_maiden_frostbite_custom_frost_stacks_per_level" "FROST STACKS / SEC:" +"dota_tooltip_ability_ability_crystal_maiden_frostbite_custom_execute_threshold_pct" "EXECUTE THRESHOLD (%):" +"dota_tooltip_ability_ability_crystal_maiden_frostbite_custom_explosion_radius" "COPY EXPLOSION RADIUS:" +"dota_tooltip_ability_ability_crystal_maiden_frostbite_custom_damage_per_mana" "EXPLOSION DAMAGE PER MANA:" +"dota_tooltip_ability_ability_crystal_maiden_frostbite_custom_Note0" "Alt cast doubles damage and only applies to enemies." + +"dota_tooltip_ability_ability_crystal_maiden_brilliance_aura_custom" "Brilliance aura" +"dota_tooltip_ability_ability_crystal_maiden_brilliance_aura_custom_Description" "Passive aura: allies in %aura_radius% gain %mana_regen_pct%%% mana regen. Active: magic damage and frost in %active_radius%; alt cast teleports %teleport_range% units. While Brilliance is active, each ability cast spawns a snowflake (max %max_crystals%): %crystal_damage% damage + %pct_25_mana_damage%%% of enemies' missing mana, %crystal_frost_stacks% frost per hit, lasts %crystal_duration% sec." +"dota_tooltip_ability_ability_crystal_maiden_brilliance_aura_custom_Lore" "Riley's magical aura fills allies with energy, allowing them to use their abilities more often." +"dota_tooltip_ability_ability_crystal_maiden_brilliance_aura_custom_mana_regen_pct" "%MANA REGEN:" +"dota_tooltip_ability_ability_crystal_maiden_brilliance_aura_custom_aura_radius" "AURA RADIUS:" +"dota_tooltip_ability_ability_crystal_maiden_brilliance_aura_custom_active_radius" "ACTIVE RADIUS:" +"dota_tooltip_ability_ability_crystal_maiden_brilliance_aura_custom_damage" "DAMAGE:" +"dota_tooltip_ability_ability_crystal_maiden_brilliance_aura_custom_duration" "EFFECT DURATION:" +"dota_tooltip_ability_ability_crystal_maiden_brilliance_aura_custom_frost_stacks_per_level" "FROST STACKS:" +"dota_tooltip_ability_ability_crystal_maiden_brilliance_aura_custom_intellect_per_damage" "INT DAMAGE:" +"dota_tooltip_ability_ability_crystal_maiden_brilliance_aura_custom_teleport_range" "TELEPORT RANGE:" +"dota_tooltip_ability_ability_crystal_maiden_brilliance_aura_custom_mana_cost_pct" "%MANA COST:" +"dota_tooltip_ability_ability_crystal_maiden_brilliance_aura_custom_crystal_damage" "SNOWFLAKE DAMAGE:" +"dota_tooltip_ability_ability_crystal_maiden_brilliance_aura_custom_pct_25_mana_damage" "% DAMAGE FROM MISSING MANA:" +"dota_tooltip_ability_ability_crystal_maiden_brilliance_aura_custom_crystal_frost_stacks" "FROST PER HIT:" +"dota_tooltip_ability_ability_crystal_maiden_brilliance_aura_custom_max_crystals" "MAX SNOWFLAKES:" +"dota_tooltip_ability_ability_crystal_maiden_brilliance_aura_custom_crystal_duration" "SNOWFLAKE LIFETIME:" +"dota_tooltip_ability_ability_crystal_maiden_brilliance_aura_custom_Note0" "Damage scales with the hero's missing mana." +"dota_tooltip_ability_ability_crystal_maiden_brilliance_aura_custom_Note1" "Alt cast teleports to the target point." + +"dota_tooltip_ability_ability_crystal_maiden_freezing_field_custom" "Freezing Field" +"dota_tooltip_ability_ability_crystal_maiden_freezing_field_custom_Description" "Channeled. Icy explosions around the hero in %radius% radius, magic damage and frost; scales with Intelligence. With Aghanim, periodically casts a mini Crystal Nova." +"dota_tooltip_ability_ability_crystal_maiden_freezing_field_custom_Lore" "The greatest Riley spell that can freeze an entire battlefield, turning enemies into ice statues." +"dota_tooltip_ability_ability_crystal_maiden_freezing_field_custom_radius" "RADIUS:" +"dota_tooltip_ability_ability_crystal_maiden_freezing_field_custom_explosion_radius" "EXPLOSION RADIUS:" +"dota_tooltip_ability_ability_crystal_maiden_freezing_field_custom_explosion_damage" "EXPLOSION DAMAGE:" +"dota_tooltip_ability_ability_crystal_maiden_freezing_field_custom_explosion_interval" "EXPLOSION INTERVAL:" +"dota_tooltip_ability_ability_crystal_maiden_freezing_field_custom_frost_stacks_per_explosion" "FROST STACKS PER EXPLOSION:" +"dota_tooltip_ability_ability_crystal_maiden_freezing_field_custom_intellect_per_damage" "HEAL/INT DAMAGE:" +"dota_tooltip_ability_ability_crystal_maiden_freezing_field_custom_slow_duration" "SLOW DURATION:" +"dota_tooltip_ability_ability_crystal_maiden_freezing_field_custom_Scepter_Description" "Periodically casts mini Crystal Nova; on hit applies Frostbite to enemies or allies (heal allies, damage enemies). Can be used while moving." + + +//Crystal Maiden Modifiers +"dota_tooltip_modifier_crystal_maiden_frostbite_ally" "Frostbite (ally)" +"dota_tooltip_modifier_crystal_maiden_frostbite_ally_Description" "Immobilized, receiving healing, and taking reduced incoming damage." + +"dota_tooltip_modifier_crystal_maiden_frostbite_enemy" "Frostbite (enemy)" +"dota_tooltip_modifier_crystal_maiden_frostbite_enemy_Description" "Immobilized and receives periodic magical damage. Also gets freeze glasses." + +"dota_tooltip_modifier_crystal_maiden_brilliance_aura_buff" "Passive Brilliance aura" +"dota_tooltip_modifier_crystal_maiden_brilliance_aura_buff_Description" "Mana regeneration increased by %dMODIFIER_PROPERTY_MANA_REGEN_TOTAL_PERCENTAGE%%%." + +"dota_tooltip_modifier_crystal_maiden_brilliance_crystals" "Active Brilliance aura" +"dota_tooltip_modifier_crystal_maiden_brilliance_crystals_Description" "Snowflakes swirl and attack all approaching enemies" + +"dota_tooltip_modifier_crystal_maiden_freezing_field_custom" "Freezing Field" +"dota_tooltip_modifier_crystal_maiden_freezing_field_custom_Description" "Creates explosions of icy energy around the hero, damaging enemies and applying freezing." + +//Drow ranger +"dota_tooltip_ability_ability_drow_ranger_frost_arrows_custom" "Frost arrows" +"dota_tooltip_ability_ability_drow_ranger_frost_arrows_custom_Description" "Traxex enchants arrows with frost: bonus damage (%damage_pct%%% of attack) and chill stacks per shot." +"dota_tooltip_ability_ability_drow_ranger_frost_arrows_custom_Scepter_Description" "%ricochet_chance%%% chance on hit to ricochet to another enemy within 500 and apply chill." +"dota_tooltip_ability_ability_drow_ranger_frost_arrows_custom_damage_pct" "%ADDITIONAL DAMAGE:" +"dota_tooltip_ability_ability_drow_ranger_frost_arrows_custom_frost_stacks_per_level" "CHILL STACKS:" +"dota_tooltip_ability_ability_drow_ranger_frost_arrows_custom_ricochet_chance" "% RICOCHET CHANCE (AGHANIM):" +"dota_tooltip_ability_ability_drow_ranger_frost_arrows_custom_Lore" "Ice Arrows Traxex, created from the purest ice of the Ice Reach, can freeze even the warmest heart." + +"dota_tooltip_ability_ability_drow_ranger_gust_custom" "Gust" +"dota_tooltip_ability_ability_drow_ranger_gust_custom_Description" "A gust of wind that knocks enemies back, deals damage, and applies chill stacks. Damaged enemies are also frozen for %frozen_son_duration% sec." +"dota_tooltip_ability_ability_drow_ranger_gust_custom_gust_speed" "GUST SPEED:" +"dota_tooltip_ability_ability_drow_ranger_gust_custom_gust_width" "GUST WIDTH:" +"dota_tooltip_ability_ability_drow_ranger_gust_custom_gust_distance" "RANGE OF IMPULSE:" +"dota_tooltip_ability_ability_drow_ranger_gust_custom_knockback_distance" "REPULSIVE RANGE:" +"dota_tooltip_ability_ability_drow_ranger_gust_custom_knockback_duration" "REPULSION DURATION:" +"dota_tooltip_ability_ability_drow_ranger_gust_custom_damage" "DAMAGE:" +"dota_tooltip_ability_ability_drow_ranger_gust_custom_frost_stacks_per_level" "CHILL STACKS:" +"dota_tooltip_ability_ability_drow_ranger_gust_custom_frozen_son_duration" "POST-DAMAGE FREEZE DURATION:" +"dota_tooltip_ability_ability_drow_ranger_gust_custom_Lore" "The cold winds of the Ice Reach taught Trucksex to control the elements, turning them into deadly weapons against enemies." + +"dota_tooltip_ability_ability_drow_ranger_multishot_custom" "Multishot" +"dota_tooltip_ability_ability_drow_ranger_multishot_custom_Description" "Channeled (1.75 sec): volleys of arrows that deal a portion of attack damage and apply chill. Arrows pierce through enemies." +"dota_tooltip_ability_ability_drow_ranger_multishot_custom_arrow_count" "NUMBER OF ARROWS:" +"dota_tooltip_ability_ability_drow_ranger_multishot_custom_arrow_damage_pct" "% SHARE OF BASE DAMAGE:" +"dota_tooltip_ability_ability_drow_ranger_multishot_custom_arrow_width" "ARROW WIDTH:" +"dota_tooltip_ability_ability_drow_ranger_multishot_custom_frost_stacks_per_level" "CHILL STACKS:" +"dota_tooltip_ability_ability_drow_ranger_multishot_custom_Lore" "Years of training in the harsh conditions of the Ice Limit allowed Trucksex to fire many arrows with incredible speed and accuracy." + +"dota_tooltip_ability_ability_drow_ranger_marksmanship_custom" "Marksmanship" +"dota_tooltip_ability_ability_drow_ranger_marksmanship_custom_Description" "While no enemy hero is within %disable_range% of Traxex, she gains %bonus_agility_pct%%% agility. Every %hits% successful attacks adds %bonus_damage% bonus physical damage; the proc hit briefly makes the target ignore base armor against that shot." +"dota_tooltip_ability_ability_drow_ranger_marksmanship_custom_bonus_damage" "BONUS PROC DAMAGE:" +"dota_tooltip_ability_ability_drow_ranger_marksmanship_custom_bonus_agility_pct" "% AGILITY BONUS:" + +"dota_tooltip_ability_ability_drow_ranger_marksmanship_custom_disable_range" "DISABLE RADIUS:" +"dota_tooltip_ability_ability_drow_ranger_marksmanship_custom_hits" "ATTACKS BETWEEN PROCS:" +"dota_tooltip_ability_ability_drow_ranger_marksmanship_custom_Lore" "Traxex's unrivaled skill in archery makes her one of the most dangerous shooters in the world." + + +//drow ranger talents +"dota_tooltip_ability_special_bonus_unique_drow_ranger_frost_arrow_damage_pct" "+{s:bonus_damage_pct}% Frost Arrow Damage" +"dota_tooltip_ability_special_bonus_unique_drow_ranger_arrow_damage_pct" "+{s:bonus_arrow_damage_pct}% Frost Squall damage" +"dota_tooltip_ability_special_bonus_unique_drow_ranger_AbilityCooldown" "-{s:bonus_AbilityCooldown} sec recharging ability Frosty squall." +"dota_tooltip_ability_special_bonus_unique_drow_ranger_arrow_count_per_wave" "+{s:bonus_arrow_count_per_wave} arrows in the Frosty Squall." +"dota_tooltip_ability_special_bonus_unique_drow_ranger_gust_frozen_stack" "+{s:bonus_frost_stacks_per_level} glasses cooling from a gust of wind." + + +//lina + +"dota_tooltip_ability_ability_lina_dragon_slave_custom" "Firelash" +"dota_tooltip_ability_ability_lina_dragon_slave_custom_Note0" "

Pasivnoe: Burn

With 0+ stacks, the owner begins to take damage per second from the number of stacks * 3.

With 70+ stacks, the effect owner loses 25% of the number of stacks of his armor and magician. resistance.

The effect does not get frostBITE, which is why the BURN absorbs the frostBITE effect." +"dota_tooltip_ability_ability_lina_dragon_slave_custom_Description" "Lina releases a wave of fire that damages enemies in her path, plus bonus damage equal to %mana_damage_from_current_pct%%% of her current mana. Attacks have a %proc_chance%%% chance to automatically cast Dragon Slave on the attack target." +"dota_tooltip_ability_ability_lina_dragon_slave_custom_Scepter_Description" "Damage increases by %damage_mult%% of Lina's intelligence." +"dota_tooltip_ability_ability_lina_dragon_slave_custom_proc_chance" "% PROC CHANCE ON ATTACK:" +"dota_tooltip_ability_ability_lina_dragon_slave_custom_dragon_slave_damage" "DAMAGE:" +"dota_tooltip_ability_ability_lina_dragon_slave_custom_mana_damage_from_current_pct" "% OF CURRENT MANA AS BONUS DAMAGE:" +"dota_tooltip_ability_ability_lina_dragon_slave_custom_fire_stacks_per_level" "BURNS:" +"dota_tooltip_ability_ability_lina_dragon_slave_custom_dragon_slave_distance" "RANGE:" +"dota_tooltip_ability_ability_lina_dragon_slave_custom_dragon_slave_width_initial" "INITIAL WIDTH:" +"dota_tooltip_ability_ability_lina_dragon_slave_custom_dragon_slave_width_end" "FINAL WIDTH:" +"dota_tooltip_ability_ability_lina_dragon_slave_custom_Lore" "The first spell in Lina's arsenal, Fire Squall, burns everything in its path." + +"dota_tooltip_ability_ability_lina_light_strike_array_custom" "Flow of explosions" +"dota_tooltip_ability_ability_lina_light_strike_array_custom_Note0" "

Pasivnoe: Burn

With 0+ stacks, the owner begins to suffer damage per second from the number of stacks * 3.

With 70+ stacks, the effect owner loses 25% of the number of stacks of his armor and magician. resistance.

The effect does not get frostBITE, which is why the BURN absorbs the frostBITE effect." + +"dota_tooltip_ability_ability_lina_light_strike_array_custom_Description" "Calls a flame column that stuns and damages enemies in the area, plus bonus damage equal to %mana_damage_from_current_pct%%% of current mana. After the first explosion, for each ability level beyond the first a separate roll can add another identical hit in the same spot (same delay as the main cast)." +"dota_tooltip_ability_ability_lina_light_strike_array_custom_shard_Description" "The damage increases by the amount of Lina's intelligence." +"dota_tooltip_ability_ability_lina_light_strike_array_custom_light_strike_array_aoe" "RADIUS:" +"dota_tooltip_ability_ability_lina_light_strike_array_custom_fire_stacks_per_level" "BURNS:" +"dota_tooltip_ability_ability_lina_light_strike_array_custom_light_strike_array_damage" "DAMAGE:" +"dota_tooltip_ability_ability_lina_light_strike_array_custom_mana_damage_from_current_pct" "% OF CURRENT MANA AS BONUS DAMAGE:" +"dota_tooltip_ability_ability_lina_light_strike_array_custom_light_strike_array_stun_duration" "DURATION OF STUNNING:" +"dota_tooltip_ability_ability_lina_light_strike_array_custom_Lore" "Although Lina prefers to burn her enemies, she doesn't mind stunning them." + +"dota_tooltip_ability_ability_lina_flame_cloak_custom_Note0" "

Pasivnoe: Burn

With 0+ stacks, the owner begins to take damage per second from the number of stacks * 3.

With 70+ stacks, the effect owner loses 25% of the number of stacks of his armor and magician. resistance.

The effect does not get frostBITE, which is why the BURN absorbs the frostBITE effect." +"dota_tooltip_ability_ability_lina_flame_cloak_custom" "Firecloak" +"dota_tooltip_ability_ability_lina_flame_cloak_custom_Description" "Lina surrounds herself in flame: damages enemies in radius (each tick plus %mana_damage_from_current_pct%%% of current mana) and applies burn. Passive stacks grant move and attack speed; each stack also grants %spell_amplify%%% spell amp and %magical_resistance%%% magic resist." +"dota_tooltip_ability_ability_lina_flame_cloak_custom_damage_per_second" "DAMAGE PER SECOND:" +"dota_tooltip_ability_ability_lina_flame_cloak_custom_mana_damage_from_current_pct" "% OF CURRENT MANA AS BONUS DAMAGE PER TICK:" +"dota_tooltip_ability_ability_lina_flame_cloak_custom_radius" "RADIUS:" +"dota_tooltip_ability_ability_lina_flame_cloak_custom_fire_stacks_per_level" "BURNS:" +"dota_tooltip_ability_ability_lina_flame_cloak_custom_duration" "DURATION:" +"dota_tooltip_ability_ability_lina_flame_cloak_custom_attackspeed_bonus" "%ATTACK RATE PER STACK:" +"dota_tooltip_ability_ability_lina_flame_cloak_custom_movespeed_bonus" "%MOVEMENT SPEED PER STACK:" +"dota_tooltip_ability_ability_lina_flame_cloak_custom_max_stacks" "MAXIMUM STACKS:" +"dota_tooltip_ability_ability_lina_flame_cloak_custom_Lore" "Lina surrounds herself with a cloak of fire that burns anyone who dares to get too close." + +"dota_tooltip_ability_ability_lina_laguna_blade_custom" "Blade of the Lagoon" +"dota_tooltip_ability_ability_lina_laguna_blade_custom_Description" "Lina releases a powerful lightning strike that deals massive damage to the target, plus bonus damage equal to %mana_damage_from_current_pct%%% of her current mana." +"dota_tooltip_ability_ability_lina_laguna_blade_custom_damage" "DAMAGE:" +"dota_tooltip_ability_ability_lina_laguna_blade_custom_mana_damage_from_current_pct" "% OF CURRENT MANA AS BONUS DAMAGE:" +"dota_tooltip_ability_ability_lina_laguna_blade_custom_Lore" "The most powerful spell in Lina's arsenal, the Lagoon Blade, can incinerate almost any enemy." +"dota_tooltip_ability_ability_lina_laguna_blade_custom_Scepter_Description" "Damage becomes pure and scales with Intelligence. Mana cost is recalculated using the values below; after the cast, for %duration% sec missing mana becomes spell amplification (1 spell amp per missing mana)." + +"dota_tooltip_modifier_lina_flame_cloak_custom_passive_buff" "Flame Cloak" +"dota_tooltip_modifier_lina_flame_cloak_custom_passive_buff_Description" "The hero receives a bonus to the movement speed of %dMODIFIER_PROPERTY_MOVESPEED_BONUS_PERCENTAGE%%% and %dMODIFIER_PROPERTY_ATTACKSPEED_PERCENTAGE%%% attack speed from Flame Cloak." + +"dota_tooltip_modifier_lina_laguna_blade_custom_buff" "Laguna Blade" +"dota_tooltip_modifier_lina_laguna_blade_custom_buff_Description" "Increases spell gain by %dMODIFIER_PROPERTY_SPELL_AMPLIFY_PERCENTAGE%%%." + +//lina talents +"dota_tooltip_ability_special_bonus_unique_lina_light_strike_array_five" "+{s:bonus_array_five} Hot blows." +"dota_tooltip_ability_special_bonus_unique_lina_laguna_blade_custom_cd" "-{s:bonus_AbilityCooldown} Lagoon Blade Recharge Sec" +"dota_tooltip_ability_special_bonus_unique_lina_flame_cloak_custom_damage_bonus" "+{s:bonus_damage_per_second} damage per second from Firecloak." +"dota_tooltip_ability_special_bonus_unique_lina_flame_cloak_custom_bonus_movespeed_attack_speed_pct" "+{s:bonus_movespeed_bonus}% attack speed and movement per stack from Firecloak." + +"dota_tooltip_ability_ability_lina_flame_cloak_custom_spell_amplify" "% SPELL AMP PER STACK:" +"dota_tooltip_ability_ability_lina_flame_cloak_custom_magical_resistance" "% MAGIC RESIST PER STACK:" + +"dota_tooltip_ability_ability_lina_scorch_affinity_innate" "Scorch Affinity" +"dota_tooltip_ability_ability_lina_scorch_affinity_innate_Description" "For each unit within %radius% that has burn, Lina gains +%bonus_pct_base%%% outgoing damage and the same amount of incoming damage reduction, plus an additional %bonus_pct_per_hero_level%%% per hero level for each such unit." +"dota_tooltip_ability_ability_lina_scorch_affinity_innate_Lore" "The more the world burns around her, the more Lina draws on the heat — and the less harm fire can do to her." +"dota_tooltip_ability_ability_lina_scorch_affinity_innate_radius" "RADIUS:" +"dota_tooltip_ability_ability_lina_scorch_affinity_innate_bonus_pct_base" "BONUS PER BURNING UNIT:" +"dota_tooltip_ability_ability_lina_scorch_affinity_innate_bonus_pct_per_hero_level" "EXTRA BONUS PER HERO LEVEL:" + +// keeper of the light +"DOTA_Tooltip_ability_keeper_of_the_light_illuminate_custom" "Illuminate" +"DOTA_Tooltip_ability_keeper_of_the_light_illuminate_custom_Description" "CHANNELED — channels and releases a wave of light. Enemies in the line take damage plus bonus damage equal to %mana_damage_from_current_pct%%% of Keeper of the Light's current mana when released, while allies are healed based on dealt damage." +"DOTA_Tooltip_ability_keeper_of_the_light_illuminate_custom_Lore" "Ezalor's light sweeps away darkness and restores hope to those beside him." +"DOTA_Tooltip_ability_keeper_of_the_light_illuminate_custom_heal_percent" "% HEAL FROM DEALT DAMAGE:" +"DOTA_Tooltip_ability_keeper_of_the_light_illuminate_custom_max_damage" "MAX DAMAGE:" +"DOTA_Tooltip_ability_keeper_of_the_light_illuminate_custom_min_damage" "MIN DAMAGE:" +"DOTA_Tooltip_ability_keeper_of_the_light_illuminate_custom_radius" "WIDTH:" +"DOTA_Tooltip_ability_keeper_of_the_light_illuminate_custom_mana_damage_from_current_pct" "% CURRENT MANA AS BONUS DAMAGE:" + +"DOTA_Tooltip_ability_keeper_of_the_light_blinding_light_custom" "Blinding Light" +"DOTA_Tooltip_ability_keeper_of_the_light_blinding_light_custom_Description" "Creates a flash of light at the target point, knocking enemies back and blinding them. Blinded attacks always miss." +"DOTA_Tooltip_ability_keeper_of_the_light_blinding_light_custom_Lore" "A blazing flare burns resolve from foes before their weapons can find a mark." +"DOTA_Tooltip_ability_keeper_of_the_light_blinding_light_custom_miss_stack" "MISSES:" +"DOTA_Tooltip_ability_keeper_of_the_light_blinding_light_custom_miss_stack_boss" "BOSS MISSES:" +"DOTA_Tooltip_ability_keeper_of_the_light_blinding_light_custom_duration_stack" "DURATION:" +"DOTA_Tooltip_ability_keeper_of_the_light_blinding_light_custom_radius" "RADIUS:" +"DOTA_Tooltip_ability_keeper_of_the_light_blinding_light_custom_damage" "DAMAGE:" + +"DOTA_Tooltip_ability_keeper_of_the_light_chakra_magic_custom" "Chakra Magic" +"DOTA_Tooltip_ability_keeper_of_the_light_chakra_magic_custom_Description" "Restores mana to an ally, reduces the remaining cooldown of their abilities, and briefly lowers incoming physical and magical damage." +"DOTA_Tooltip_ability_keeper_of_the_light_chakra_magic_custom_Lore" "Ezalor shares a spark of his inner radiance to quicken an ally's mind and spellwork." +"DOTA_Tooltip_ability_keeper_of_the_light_chakra_magic_custom_mana_restore" "MANA RESTORE:" +"DOTA_Tooltip_ability_keeper_of_the_light_chakra_magic_custom_cooldown_reduction" "COOLDOWN REDUCTION:" +"DOTA_Tooltip_ability_keeper_of_the_light_chakra_magic_custom_incoming_damage_reduction_pct" "INCOMING DAMAGE REDUCTION:" +"DOTA_Tooltip_ability_keeper_of_the_light_chakra_magic_custom_damage_reduction_duration" "PROTECTION DURATION:" + +"DOTA_Tooltip_ability_keeper_of_the_light_recall_custom" "Recall" +"DOTA_Tooltip_ability_keeper_of_the_light_recall_custom_Description" "After a delay, teleports the target to the caster. After teleport, both units gain bonus movement speed." +"DOTA_Tooltip_ability_keeper_of_the_light_recall_custom_Lore" "Those who hear his call are carried by light to where they are needed most." +"DOTA_Tooltip_ability_keeper_of_the_light_recall_custom_teleport_delay" "TELEPORT DELAY:" +"DOTA_Tooltip_ability_keeper_of_the_light_recall_custom_ally_movespeed_pct" "% BONUS MOVE SPEED:" + +"DOTA_Tooltip_ability_keeper_of_the_light_will_o_wisp_custom" "Will-O-Wisp" +"DOTA_Tooltip_ability_keeper_of_the_light_will_o_wisp_custom_Description" "Summons a wisp at target location. During each activation it damages enemies in the area with bonus damage equal to 25% of Keeper of the Light's current mana and heavily slows them." +"DOTA_Tooltip_ability_keeper_of_the_light_will_o_wisp_custom_Lore" "An ancient wandering flame dances across battle, luring foes into ruin." +"DOTA_Tooltip_ability_keeper_of_the_light_will_o_wisp_custom_radius" "RADIUS:" +"DOTA_Tooltip_ability_keeper_of_the_light_will_o_wisp_custom_slow_movespeed" "% MOVE SLOW:" +"DOTA_Tooltip_ability_keeper_of_the_light_will_o_wisp_custom_hit_count" "ACTIVATIONS:" +"DOTA_Tooltip_ability_keeper_of_the_light_will_o_wisp_custom_delay" "INTERVAL:" +"DOTA_Tooltip_ability_keeper_of_the_light_will_o_wisp_custom_damage" "DAMAGE:" + +"DOTA_Tooltip_ability_keeper_of_the_light_radiant_bind_custom" "Solar Bind" +"DOTA_Tooltip_ability_keeper_of_the_light_radiant_bind_custom_Description" "Reduces magic resistance and applies stronger slow the farther the target moves. On allies, the effect is reversed and grants speed plus magic resistance." +"DOTA_Tooltip_ability_keeper_of_the_light_radiant_bind_custom_Lore" "These bonds of sunlight are merciless to enemies and merciful to allies." +"DOTA_Tooltip_ability_keeper_of_the_light_radiant_bind_custom_duration" "DURATION:" +"DOTA_Tooltip_ability_keeper_of_the_light_radiant_bind_custom_slow_pct_per_distance" "% SLOW PER 100 DISTANCE:" +"DOTA_Tooltip_ability_keeper_of_the_light_radiant_bind_custom_magres_pct" "% MAGIC RESIST CHANGE:" + +"DOTA_Tooltip_Ability_special_bonus_unique_keeper_of_the_light_4_1" "+{s:bonus_radius} Will-O-Wisp radius" +"DOTA_Tooltip_Ability_special_bonus_unique_keeper_of_the_light_1_1" "+{s:bonus_heal_percent}% Illuminate healing" +"DOTA_Tooltip_Ability_special_bonus_unique_keeper_of_the_light_3_1" "Chakra Magic restores 1 ability charge" +"DOTA_Tooltip_Ability_special_bonus_unique_keeper_of_the_light_4_2" "+{s:bonus_hit_count} Will-O-Wisp activations" + +// silencer +"dota_tooltip_ability_silencer_curse_of_the_silent" "Arcane Curse" +"dota_tooltip_ability_silencer_curse_of_the_silent_Description" "Curses enemies in an area, dealing periodic damage and slowing them. The damage is additionally amplified by Silencer's Intellect. Each spell cast by the cursed target extends the debuff." +"dota_tooltip_ability_silencer_curse_of_the_silent_Lore" "Silencer's curse drains both speech and resolve." +"dota_tooltip_ability_silencer_curse_of_the_silent_damage" "DAMAGE PER SECOND:" +"dota_tooltip_ability_silencer_curse_of_the_silent_radius" "RADIUS:" +"dota_tooltip_ability_silencer_curse_of_the_silent_duration" "DURATION:" +"dota_tooltip_ability_silencer_curse_of_the_silent_penalty_duration" "EXTENSION PER CAST:" +"dota_tooltip_ability_silencer_curse_of_the_silent_movespeed" "% MOVE SLOW:" + +"dota_tooltip_ability_glaives_of_wisdom" "Glaives of Wisdom" +"dota_tooltip_ability_glaives_of_wisdom_Description" "Orb attack that deals bonus pure damage based on the caster's intellect." +"dota_tooltip_ability_glaives_of_wisdom_Lore" "Each glaive carries a verdict forged by intellect." +"dota_tooltip_ability_glaives_of_wisdom_intellect_damage_pct" "% INT TO DAMAGE:" + +"dota_tooltip_ability_razor_eye_of_the_storm_lua" "Mind Storm" +"dota_tooltip_ability_razor_eye_of_the_storm_lua_Description" "Creates a storm around the caster that periodically strikes nearby enemies, dealing physical damage and reducing armor. Storm damage is additionally amplified by Silencer's Intellect." +"dota_tooltip_ability_razor_eye_of_the_storm_lua_Scepter_Description" "Aghanim's Scepter makes the storm strike %scepter_bonus_targets% additional targets each interval." +"dota_tooltip_ability_razor_eye_of_the_storm_lua_Lore" "A silent tempest strips armor and courage alike." +"dota_tooltip_ability_razor_eye_of_the_storm_lua_radius" "RADIUS:" +"dota_tooltip_ability_razor_eye_of_the_storm_lua_duration" "DURATION:" +"dota_tooltip_ability_razor_eye_of_the_storm_lua_strike_interval" "STRIKE INTERVAL:" +"dota_tooltip_ability_razor_eye_of_the_storm_lua_armor_reduction" "ARMOR REDUCTION PER HIT:" +"dota_tooltip_ability_razor_eye_of_the_storm_lua_damage" "DAMAGE:" + +"dota_tooltip_ability_int" "Int Collection" +"dota_tooltip_ability_int_Description" "Passively gains intellect stacks over time." +"dota_tooltip_ability_int_grow_int" "INT PER STACK:" +"dota_tooltip_ability_int_stack_interval" "STACK INTERVAL:" + +"dota_tooltip_ability_ability_last_word" "Last Word" +"dota_tooltip_ability_ability_last_word_Description" "Applies a delayed effect to the target that silences and deals magical damage when triggered." +"dota_tooltip_ability_ability_last_word_Shard_Description" "Aghanim's Shard increases Last Word intellect scaling by %shard_bonus_int%." +"dota_tooltip_ability_ability_last_word_Lore" "The final word belongs to the one who can force silence." +"dota_tooltip_ability_ability_last_word_damage" "BASE DAMAGE:" +"dota_tooltip_ability_ability_last_word_debuff_duration" "TRIGGER DELAY:" +"dota_tooltip_ability_ability_last_word_duration" "SILENCE DURATION:" +"dota_tooltip_ability_ability_last_word_int" "% INT TO DAMAGE:" + +"dota_tooltip_ability_ability_global_silence" "Global Silence" +"dota_tooltip_ability_ability_global_silence_Description" "Silences all enemy units on the map and increases incoming damage they take." +"dota_tooltip_ability_ability_global_silence_Lore" "When the world falls silent, only Silencer's judgment remains." +"dota_tooltip_ability_ability_global_silence_tooltip_duration" "DURATION:" +"dota_tooltip_ability_ability_global_silence_icnoming_enemy" "% INCOMING DAMAGE:" + +"dota_tooltip_ability_special_bonus_unique_silencer_storm_interval" "-{s:bonus_strike_interval} Mind Storm strike interval" +"dota_tooltip_ability_special_bonus_unique_silencer_grow_int" "+{s:bonus_grow_int} Int Collection intellect per stack" +"dota_tooltip_ability_special_bonus_unique_silencer_storm_duration" "+{s:bonus_duration} sec Mind Storm duration" +"dota_tooltip_ability_special_bonus_unique_silencer_intellect_damage_pct" "+{s:bonus_intellect_damage_pct}% Glaives of Wisdom intellect damage" +"dota_tooltip_ability_special_bonus_unique_silencer_glaives_bounces" "+{s:bonus_bounce_count} Glaives of Wisdom bounces" +"dota_tooltip_ability_special_bonus_unique_silencer" "+{s:bonus_damage} Arcane Curse damage" +"dota_tooltip_ability_special_bonus_unique_silencer_stack_interval" "-{s:bonus_stack_interval} sec Int Collection stack interval" + +// nevermore +"dota_tooltip_ability_nevermore_shadowraze1_custom" "Shadowraze" +"dota_tooltip_ability_nevermore_shadowraze1_custom_Description" "Nevermore devastates the ground in front of him, dealing damage (owner attack + current mana value) to all enemies in the area. Each hit applies a stacking effect that empowers subsequent razes and additionally slows the target." +"dota_tooltip_ability_nevermore_shadowraze1_custom_shadowraze_range" "RANGE:" +"dota_tooltip_ability_nevermore_shadowraze1_custom_stack_bonus_damage" "BONUS DAMAGE PER STACK:" +"dota_tooltip_ability_nevermore_shadowraze1_custom_duration" "DEBUFF DURATION:" +"dota_tooltip_ability_nevermore_shadowraze1_custom_movement_speed_debuff" "% MOVE SLOW PER STACK:" +"dota_tooltip_ability_nevermore_shadowraze1_custom_Lore" "A single scar of shadow is never enough for Nevermore." + +"dota_tooltip_ability_nevermore_shadowraze2_custom" "Shadowraze" +"dota_tooltip_ability_nevermore_shadowraze2_custom_Description" "Nevermore devastates the ground in front of him, dealing damage (owner attack + current mana value) to all enemies in the area. Each hit applies a stacking effect that empowers subsequent razes and additionally slows the target." +"dota_tooltip_ability_nevermore_shadowraze2_custom_shadowraze_range" "RANGE:" +"dota_tooltip_ability_nevermore_shadowraze2_custom_stack_bonus_damage" "BONUS DAMAGE PER STACK:" +"dota_tooltip_ability_nevermore_shadowraze2_custom_duration" "DEBUFF DURATION:" +"dota_tooltip_ability_nevermore_shadowraze2_custom_movement_speed_debuff" "% MOVE SLOW PER STACK:" +"dota_tooltip_ability_nevermore_shadowraze2_custom_Lore" "The second mark of ruin leaves no room for hope." + +"dota_tooltip_ability_nevermore_shadowraze3_custom" "Shadowraze" +"dota_tooltip_ability_nevermore_shadowraze3_custom_Description" "Nevermore devastates the ground in front of him, dealing damage (owner attack + current mana value) to all enemies in the area. Each hit applies a stacking effect that empowers subsequent razes and additionally slows the target." +"dota_tooltip_ability_nevermore_shadowraze3_custom_shadowraze_range" "RANGE:" +"dota_tooltip_ability_nevermore_shadowraze3_custom_stack_bonus_damage" "BONUS DAMAGE PER STACK:" +"dota_tooltip_ability_nevermore_shadowraze3_custom_duration" "DEBUFF DURATION:" +"dota_tooltip_ability_nevermore_shadowraze3_custom_movement_speed_debuff" "% MOVE SLOW PER STACK:" +"dota_tooltip_ability_nevermore_shadowraze3_custom_Lore" "No distance can hide a soul from his final reach." + +"dota_tooltip_ability_nevermore_necromastery_custom" "Necromastery" +"dota_tooltip_ability_nevermore_necromastery_custom_Description" "Passive: Nevermore gains bonus attack damage for each soul he holds (%necromastery_damage_per_soul% per soul, up to %necromastery_max_souls% souls before cap increase). Each enemy kill grants %souls_per_kill% soul(s), up to the current cap. The damage bonus does not apply while passives are broken.\n\nOn death, Nevermore loses %necromastery_soul_pct_release%%% of his current souls (the rest remain). If Requiem of Souls is learned and not disabled, it triggers automatically.\n\nActive — no mana cost, cooldown %AbilityCooldown%: while at the base soul cap, activate to raise the max souls by %active_bonus_soul_cap% for %active_duration% seconds." +"dota_tooltip_ability_nevermore_necromastery_custom_necromastery_max_souls" "MAX SOULS:" +"dota_tooltip_ability_nevermore_necromastery_custom_necromastery_damage_per_soul" "DAMAGE PER SOUL:" +"dota_tooltip_ability_nevermore_necromastery_custom_necromastery_soul_pct_release" "% SOULS LOST ON DEATH:" +"dota_tooltip_ability_nevermore_necromastery_custom_souls_per_kill" "SOULS PER KILL:" +"dota_tooltip_ability_nevermore_necromastery_custom_active_duration" "OVERFLOW DURATION:" +"dota_tooltip_ability_nevermore_necromastery_custom_active_bonus_soul_cap" "BONUS SOUL CAP:" + +"dota_tooltip_ability_nevermore_necromastery_custom_Lore" "Each stolen soul is chained forever to his hunger." + +"dota_tooltip_ability_nevermore_dark_lord_custom" "Presence of the Dark Lord" +"dota_tooltip_ability_nevermore_dark_lord_custom_Description" "Nevermore emits an aura that reduces armor of nearby enemies based on souls. With Aghanim's Shard, allied heroes in aura (except Nevermore) gain bonus armor." +"dota_tooltip_ability_nevermore_dark_lord_custom_Shard_Description" "Also grants bonus armor to allied heroes in the aura, but not to Shadow Fiend himself." +"dota_tooltip_ability_nevermore_dark_lord_custom_armor_reduction_per_soul" "ARMOR REDUCTION PER SOUL:" +"dota_tooltip_ability_nevermore_dark_lord_custom_presence_radius" "RADIUS:" +"dota_tooltip_ability_nevermore_dark_lord_custom_shard_ally_armor_bonus" "SHARD ALLY ARMOR BONUS:" +"dota_tooltip_ability_nevermore_dark_lord_custom_Lore" "His presence alone bends armor and courage alike." + +"dota_tooltip_ability_nevermore_deadly_strike_custom" "Deadly Strike" +"dota_tooltip_ability_nevermore_deadly_strike_custom_Description" "After each landed attack, Nevermore has a chance to empower his next attack with a deadly critical strike. Critical damage scales with current souls." +"dota_tooltip_ability_nevermore_deadly_strike_custom_deadly_strike_chance" "PROC CHANCE:" +"dota_tooltip_ability_nevermore_deadly_strike_custom_deadly_strike_crit_per_soul" "CRIT DAMAGE PER SOUL:" +"dota_tooltip_ability_nevermore_deadly_strike_custom_Lore" "One prepared strike is enough to silence the living." + +"dota_tooltip_ability_nevermore_requiem_custom" "Requiem of Souls" +"dota_tooltip_ability_nevermore_requiem_custom_Description" "Nevermore releases %requiem_soul_pct_release%%% of captured souls in waves of energy. Each wave deals damage and applies fear and weakening effects to enemies. The spell also triggers automatically when Nevermore dies." +"dota_tooltip_ability_nevermore_requiem_custom_Scepter_Description" "Each released wave that hits an enemy returns back to Nevermore, dealing %requiem_damage_pct_scepter%%% of the wave damage again." +"dota_tooltip_ability_nevermore_requiem_custom_damage" "DAMAGE:" +"dota_tooltip_ability_nevermore_requiem_custom_requiem_radius" "RADIUS:" +"dota_tooltip_ability_nevermore_requiem_custom_requiem_reduction_ms" "% MOVE SLOW:" +"dota_tooltip_ability_nevermore_requiem_custom_requiem_reduction_mres" "% MAGIC RESIST REDUCTION:" +"dota_tooltip_ability_nevermore_requiem_custom_requiem_slow_duration" "DURATION PER WAVE:" +"dota_tooltip_ability_nevermore_requiem_custom_requiem_slow_duration_max" "MAX DURATION:" +"dota_tooltip_ability_nevermore_requiem_custom_Lore" "When he sings, the dead answer in agony." + +"dota_tooltip_ability_special_bonus_unique_nevermore_custom_1" "+$attack_pct Deadly Strike chance" +"dota_tooltip_ability_special_bonus_unique_nevermore_custom_2" "+$attack_pct Deadly Strike crit per soul" +"dota_tooltip_ability_special_bonus_unique_nevermore_custom_3" "+$all Necromastery max souls" +"dota_tooltip_ability_special_bonus_unique_nevermore_custom_4" "+$damage Necromastery damage per soul" +"dota_tooltip_ability_special_bonus_unique_nevermore_custom_5" "+$armor Dark Lord armor reduction per soul" +"dota_tooltip_ability_special_bonus_unique_nevermore_custom_6" "+$aoe_bonus Presence radius" +"dota_tooltip_ability_special_bonus_unique_nevermore_custom_7" "+$damage Requiem damage" +"dota_tooltip_ability_special_bonus_unique_nevermore_custom_8" "+$damage Shadowraze stack bonus damage" + +// -----------------Troll Warlord-------------------- +"dota_tooltip_ability_troll_warlord_switch_stance_custom" "Berserker's Rage" +"dota_tooltip_ability_troll_warlord_switch_stance_custom_Description" "Toggle melee stance: changes attack range, base attack time, armor, and move speed. While ranged, attacks have a %chance_ensnare%%% chance to launch an ensnaring net (internal cooldown %cooldown_ensnare% sec.)." +"dota_tooltip_ability_troll_warlord_switch_stance_custom_Lore" "Rage is a discipline—just louder." +"dota_tooltip_ability_troll_warlord_switch_stance_custom_bonus_armor" "BONUS ARMOR:" +"dota_tooltip_ability_troll_warlord_switch_stance_custom_bonus_move_speed" "BONUS MOVE SPEED:" +"dota_tooltip_ability_troll_warlord_switch_stance_custom_base_attack_time" "BASE ATTACK TIME:" +"dota_tooltip_ability_troll_warlord_switch_stance_custom_chance_ensnare" "% ENSNARE CHANCE (RANGED):" +"dota_tooltip_ability_troll_warlord_switch_stance_custom_cooldown_ensnare" "ENSNARE INTERNAL COOLDOWN:" +"dota_tooltip_ability_troll_warlord_switch_stance_custom_duration_ensnare" "ROOT DURATION:" +"dota_tooltip_ability_troll_warlord_switch_stance_custom_ensnare_speed" "NET SPEED:" +"dota_tooltip_ability_troll_warlord_switch_stance_custom_split_radius" "SPLIT SEARCH RADIUS:" +"dota_tooltip_ability_troll_warlord_switch_stance_custom_max_split_attack" "MAX SPLIT ATTACKS:" +"dota_tooltip_ability_troll_warlord_switch_stance_custom_Shard_Description" "Ranged attacks can hit multiple enemies near the primary target." + +"dota_tooltip_ability_troll_warlord_active_axes_ranged" "Whirling Axes (Ranged)" +"dota_tooltip_ability_troll_warlord_active_axes_ranged_Description" "Launches axes in a cone, dealing magical damage plus extra damage based on the owner's attack (%attack_damage_pct%%%), and slows enemies for %axe_slow_duration% sec." +"dota_tooltip_ability_troll_warlord_active_axes_ranged_Lore" "Axes fly where the arm points." +"dota_tooltip_ability_troll_warlord_active_axes_ranged_axe_damage" "DAMAGE:" +"dota_tooltip_ability_troll_warlord_active_axes_ranged_attack_damage_pct" "% OWNER ATTACK AS BONUS DAMAGE:" +"dota_tooltip_ability_troll_warlord_active_axes_ranged_axe_slow_duration" "SLOW DURATION:" +"dota_tooltip_ability_troll_warlord_active_axes_ranged_movement_speed" "% MOVE SLOW:" +"dota_tooltip_ability_troll_warlord_active_axes_ranged_axe_width" "WIDTH:" +"dota_tooltip_ability_troll_warlord_active_axes_ranged_axe_range" "TRAVEL RANGE:" + +"dota_tooltip_ability_troll_warlord_active_axes_melee" "Whirling Axes (Melee)" +"dota_tooltip_ability_troll_warlord_active_axes_melee_Description" "Axes orbit the caster, dealing magical damage plus extra damage based on the owner's attack (%attack_damage_pct%%%), and disarming enemies." +"dota_tooltip_ability_troll_warlord_active_axes_melee_Lore" "Close range is someone else's danger zone." +"dota_tooltip_ability_troll_warlord_active_axes_melee_damage" "DAMAGE:" +"dota_tooltip_ability_troll_warlord_active_axes_melee_attack_damage_pct" "% OWNER ATTACK AS BONUS DAMAGE:" +"dota_tooltip_ability_troll_warlord_active_axes_melee_debuff_duration" "DISARM DURATION:" +"dota_tooltip_ability_troll_warlord_active_axes_melee_hit_radius" "HIT RADIUS:" +"dota_tooltip_ability_troll_warlord_active_axes_melee_max_range" "MAX ORBIT RADIUS:" + +"dota_tooltip_ability_troll_warlord_fervor_custom" "Fervor" +"dota_tooltip_ability_troll_warlord_fervor_custom_Description" "Each landed attack grants a stack of attack speed (up to %max_stacks%), any target counts. Stacks stay while you keep landing hits; if no hits for %stack_linger_duration% sec., stacks reset." +"dota_tooltip_ability_troll_warlord_fervor_custom_Lore" "One foe, one rhythm." +"dota_tooltip_ability_troll_warlord_fervor_custom_attack_speed" "ATTACK SPEED PER STACK:" +"dota_tooltip_ability_troll_warlord_fervor_custom_max_stacks" "MAX STACKS:" +"dota_tooltip_ability_troll_warlord_fervor_custom_stack_linger_duration" "NO-HIT TIME TO RESET STACKS:" +"dota_tooltip_ability_troll_warlord_fervor_custom_locked_attack_speed" "ATTACK SPEED THRESHOLD:" +"dota_tooltip_ability_troll_warlord_fervor_custom_pct_damage_per_attack_speed" "% DAMAGE PER ATTACK SPEED ABOVE THRESHOLD:" +"dota_tooltip_ability_troll_warlord_fervor_custom_Scepter_Description" "Attack speed beyond the threshold increases attack damage." + +"dota_tooltip_ability_troll_warlord_battle_trance_custom" "Battle Trance" +"dota_tooltip_ability_troll_warlord_battle_trance_custom_Description" "Puts Troll Warlord into a battle trance for %trance_duration% sec., granting bonus attack speed, bonus move speed, and %lifesteal%%% physical lifesteal from attacks." +"dota_tooltip_ability_troll_warlord_battle_trance_custom_Lore" "Let the enemy's blood pay for the next step." +"dota_tooltip_ability_troll_warlord_battle_trance_custom_trance_duration" "DURATION:" +"dota_tooltip_ability_troll_warlord_battle_trance_custom_lifesteal" "% LIFESTEAL:" +"dota_tooltip_ability_troll_warlord_battle_trance_custom_attack_speed" "ATTACK SPEED:" +"dota_tooltip_ability_troll_warlord_battle_trance_custom_movement_speed" "BONUS MOVE SPEED:" + +"dota_tooltip_ability_special_bonus_unique_troll_warlord_1_1" "+{s:bonus_bonus_move_speed} move speed from Berserker's Rage" +"dota_tooltip_ability_special_bonus_unique_troll_warlord_1_2" "+{s:bonus_base_attack_time} sec. to Berserker's Rage base attack time modifier" +"dota_tooltip_ability_special_bonus_unique_troll_warlord_2_1" "+{s:bonus_axe_slow_duration}% Whirling Axes slow/disarm duration" +"dota_tooltip_ability_special_bonus_unique_troll_warlord_4_1" "-{s:bonus_AbilityCooldown}s Battle Trance cooldown" +"dota_tooltip_ability_special_bonus_unique_troll_warlord_1_3" "+{s:bonus_bonus_armor} armor from Berserker's Rage" +"dota_tooltip_ability_special_bonus_unique_troll_warlord_3" "Increases Whirling Axes magical damage (ranged and melee)." +"dota_tooltip_ability_special_bonus_unique_troll_warlord_5" "+{s:bonus_attack_speed} attack speed per Fervor stack" +"dota_tooltip_ability_special_bonus_unique_troll_warlord_1_4" "+{s:bonus_chance_ensnare}% ranged ensnare chance" +"dota_tooltip_ability_special_bonus_unique_troll_warlord_battle_trance_movespeed" "+{s:bonus_movement_speed} Battle Trance move speed bonus" + +// -----------------Ogre Magi-------------------- +"dota_tooltip_ability_ability_ogre_magi_fireblast_custom" "Fireblast" +"dota_tooltip_ability_ability_ogre_magi_fireblast_custom_Description" "Blasts the target area: enemies within %blast_radius% take %fireblast_damage% magical damage plus %max_health_damage_pct%%% of Ogre Magi's max health and are stunned for %stun_duration% s. Can Multicast." +"dota_tooltip_ability_ability_ogre_magi_fireblast_custom_fireblast_damage" "DAMAGE:" +"dota_tooltip_ability_ability_ogre_magi_fireblast_custom_max_health_damage_pct" "OGRE MAX HEALTH %:" +"dota_tooltip_ability_ability_ogre_magi_fireblast_custom_stun_duration" "STUN:" +"dota_tooltip_ability_ability_ogre_magi_fireblast_custom_blast_radius" "BLAST RADIUS:" +"dota_tooltip_ability_ability_ogre_magi_fireblast_custom_Lore" "Two fires make one blast. The third fire is Multicast." + +"dota_tooltip_ability_ability_ogre_magi_ignite_custom" "Ignite" +"dota_tooltip_ability_ability_ogre_magi_ignite_custom_Description" "Hurls flaming goo: ignites the target and enemies within %ignite_radius% for %duration% s, dealing %burn_damage% magical damage per second plus %max_health_damage_pct%%% of Ogre Magi's max health per second and slowing by %slow_movement_speed_pct%%%. Can Multicast." +"dota_tooltip_ability_ability_ogre_magi_ignite_custom_duration" "DURATION:" +"dota_tooltip_ability_ability_ogre_magi_ignite_custom_burn_damage" "DAMAGE PER SECOND:" +"dota_tooltip_ability_ability_ogre_magi_ignite_custom_max_health_damage_pct" "OGRE MAX HEALTH % PER SEC:" +"dota_tooltip_ability_ability_ogre_magi_ignite_custom_ignite_radius" "SPLASH RADIUS:" +"dota_tooltip_ability_ability_ogre_magi_ignite_custom_Lore" "Snot burns longer than pride." + +"dota_tooltip_ability_ability_ogre_magi_bloodlust_custom" "Bloodlust" +"dota_tooltip_ability_ability_ogre_magi_bloodlust_custom_Description" "For %duration% s, empowers an ally with +%bonus_attack_speed% attack speed and +%bonus_movement_speed%%% movement speed. On self, grants +%self_bonus% attack speed. Autocast applies to the nearest ally missing the buff. Can Multicast." +"dota_tooltip_ability_ability_ogre_magi_bloodlust_custom_bonus_attack_speed" "ALLY ATTACK SPEED:" +"dota_tooltip_ability_ability_ogre_magi_bloodlust_custom_self_bonus" "SELF ATTACK SPEED:" +"dota_tooltip_ability_ability_ogre_magi_bloodlust_custom_bonus_movement_speed" "MOVEMENT SPEED:" +"dota_tooltip_ability_ability_ogre_magi_bloodlust_custom_Lore" "When the Ogre cheers, everyone swings faster." + +"dota_tooltip_ability_ability_ogre_magi_multicast_custom" "Multicast" +"dota_tooltip_ability_ability_ogre_magi_multicast_custom_Description" "Passive: Ogre's abilities may repeat — %chance_2x%%% chance to double-cast, at level 2 also %chance_3x%%% to triple, at level 3 %chance_4x%%% to quadruple. Luck affects rolls. Disabled by Break." +"dota_tooltip_ability_ability_ogre_magi_multicast_custom_chance_2x" "×2 CHANCE:" +"dota_tooltip_ability_ability_ogre_magi_multicast_custom_chance_3x" "×3 CHANCE:" +"dota_tooltip_ability_ability_ogre_magi_multicast_custom_chance_4x" "×4 CHANCE:" +"dota_tooltip_ability_ability_ogre_magi_multicast_custom_Lore" "Sometimes the spell fires so many times even the Ogre is surprised." +"dota_tooltip_ability_ability_ogre_magi_multicast_custom_Shard_Description" "Active: grants allies within %shard_radius% Fire Shield stacks so their abilities can also Multicast. Lasts %shard_duration% s." + +"dota_tooltip_ability_ability_ogre_magi_unrefined_fireblast_custom" "Unrefined Fireblast" +"dota_tooltip_ability_ability_ogre_magi_unrefined_fireblast_custom_Description" "A Fireblast-like blast at the point: within %blast_radius%, %blast_damage% magical damage plus %max_health_damage_pct%%% of Ogre Magi's max health and a %stun_duration% s stun. Costs %mana_cost_pct%%% of current mana." +"dota_tooltip_ability_ability_ogre_magi_unrefined_fireblast_custom_blast_radius" "BLAST RADIUS:" +"dota_tooltip_ability_ability_ogre_magi_unrefined_fireblast_custom_max_health_damage_pct" "OGRE MAX HEALTH %:" +"dota_tooltip_ability_ability_ogre_magi_unrefined_fireblast_custom_blast_damage" "DAMAGE:" +"dota_tooltip_ability_ability_ogre_magi_unrefined_fireblast_custom_mana_cost_pct" "MANA COST:" +"dota_tooltip_ability_ability_ogre_magi_unrefined_fireblast_custom_Lore" "When you're full of mana, it hits extra hard." + +"dota_tooltip_ability_ability_ogre_magi_innate_custom" "Dumb Luck" +"dota_tooltip_ability_ability_ogre_magi_innate_custom_Description" "Innate: +%luck_per_level% Luck per hero level. Disabled by Break." +"dota_tooltip_ability_ability_ogre_magi_innate_custom_luck_per_level" "LUCK PER LEVEL:" +"dota_tooltip_ability_ability_ogre_magi_innate_custom_Lore" "Stupidity and luck are close cousins." + +"dota_tooltip_modifier_modifier_ogre_magi_ignite_debuff" "Ignite" +"dota_tooltip_modifier_modifier_ogre_magi_ignite_debuff_Description" "Damage over time and slow. Stacks increase damage." + +"dota_tooltip_modifier_modifier_ogre_magi_bloodlust_buff" "Bloodlust" +"dota_tooltip_modifier_modifier_ogre_magi_bloodlust_buff_Description" "Increased attack and movement speed." + +"dota_tooltip_modifier_modifier_ogre_magi_multicast_stack" "Fire Shield" +"dota_tooltip_modifier_modifier_ogre_magi_multicast_stack_Description" "The next ability casts may Multicast." + +"dota_tooltip_ability_special_bonus_unique_ogre_magi_fireblast_damage" "+{s:bonus_fireblast_damage} Fireblast damage" +"dota_tooltip_ability_special_bonus_unique_ogre_magi_fireblast_attack_proc" "{s:bonus_attack_proc_chance}% chance (luck): on attack, cast Fireblast in front of you" +"dota_tooltip_ability_special_bonus_unique_ogre_magi_ignite_damage" "+{s:bonus_burn_damage} Ignite damage per second" +"dota_tooltip_ability_special_bonus_unique_ogre_magi_bloodlust_as" "+{s:bonus_bonus_attack_speed} Bloodlust attack speed and +{s:bonus_physical_vampirism} vampirism" +"dota_tooltip_ability_special_bonus_unique_ogre_magi_ignite_stacks" "Ignite stacks increase burn damage" +"dota_tooltip_ability_special_bonus_unique_ogre_magi_ignite_on_cast" "20% chance to launch Ignite at the nearest enemy on any cast" +"dota_tooltip_ability_special_bonus_unique_ogre_magi_ignite_proc_damage" "+30% physical attack damage vs targets under Ignite" +"dota_tooltip_ability_special_bonus_unique_ogre_magi_luck" "+{s:bonus_luck_per_level} Luck per level (innate)" + +// -----------------Spectre-------------------- +"dota_tooltip_ability_ability_spectre_spectral_dagger_custom" "Spectral Dagger" +"dota_tooltip_ability_ability_spectre_spectral_dagger_custom_Description" "Throws a dagger at a point or enemy: %damage% magical damage plus %health_cost_pct%%% of Spectre's max health, %slow_pct%%% slow, and a trail for %buff_persistence% sec. Spectre gains +%bonus_movespeed%%% move speed for the duration." +"dota_tooltip_ability_ability_spectre_spectral_dagger_custom_damage" "DAMAGE:" +"dota_tooltip_ability_ability_spectre_spectral_dagger_custom_health_cost_pct" "MAX HEALTH %:" +"dota_tooltip_ability_ability_spectre_spectral_dagger_custom_slow_pct" "SLOW:" +"dota_tooltip_ability_ability_spectre_spectral_dagger_custom_bonus_movespeed" "MOVE SPEED (SELF):" +"dota_tooltip_ability_ability_spectre_spectral_dagger_custom_buff_persistence" "TRAIL DURATION:" +"dota_tooltip_ability_ability_spectre_spectral_dagger_custom_Lore" "The shadow cuts flesh—and the way back." + +"dota_tooltip_ability_ability_spectre_spectral_echo_custom" "Spectral Echo" +"dota_tooltip_ability_ability_spectre_spectral_echo_custom_Description" "Passive: %proc_chance%%% chance (luck applies) on attack to spawn %shadows_per_proc% invulnerable black shadow(s) near the target—each vanishes after %shadow_attacks% strike(s). With no enemies nearby, the shadow follows Spectre until it finds a target within %search_radius%. Up to %shadows_per_proc% at once. Damage: %illusion_outgoing_damage%%%." +"dota_tooltip_ability_ability_spectre_spectral_echo_custom_shadow_attacks" "STRIKES BEFORE VANISH:" +"dota_tooltip_ability_ability_spectre_spectral_echo_custom_proc_chance" "CHANCE:" +"dota_tooltip_ability_ability_spectre_spectral_echo_custom_shadows_per_proc" "SHADOWS PER PROC:" +"dota_tooltip_ability_ability_spectre_spectral_echo_custom_spawn_radius_near_target" "SPAWN RADIUS AT TARGET:" +"dota_tooltip_ability_special_bonus_unique_spectre_dagger_cooldown" "-{s:bonus_AbilityCooldown}s Spectral Dagger cooldown" +"dota_tooltip_ability_special_bonus_unique_spectre_echo_proc_chance" "+{s:bonus_proc_chance}% Spectral Echo chance" +"dota_tooltip_ability_special_bonus_unique_spectre_spectral_echo_twin" "+{s:bonus_shadows_per_proc} Spectral Echo shadow per proc (2 total)" +"dota_tooltip_ability_special_bonus_unique_spectre_echo_double_strike" "+{s:bonus_shadow_attacks} Spectral Echo shadow attack before vanishing (2 total)" +"dota_tooltip_ability_special_bonus_unique_spectre_dispersion_pct" "+{s:bonus_damage_reflection_pct}% Dispersion" +"dota_tooltip_ability_special_bonus_unique_spectre_desolate_hp_pct" "+{s:bonus_health_damage_pct}% Desolate max HP damage" +"dota_tooltip_ability_special_bonus_unique_spectre_echo_shadow_damage" "+{s:bonus_illusion_outgoing_damage}% Spectral Echo shadow damage" +"dota_tooltip_ability_special_bonus_unique_spectre_reality_all_haunts" "Reality: Spectral Dagger from each Haunt illusion, then swap with the nearest" +"dota_tooltip_ability_ability_spectre_spectral_echo_custom_search_radius" "TARGET SEARCH RADIUS:" +"dota_tooltip_ability_ability_spectre_spectral_echo_custom_illusion_outgoing_damage" "ILLUSION DAMAGE:" +"dota_tooltip_ability_ability_spectre_spectral_echo_custom_illusion_attack_speed" "ILLUSION ATTACK SPEED:" +"dota_tooltip_ability_ability_spectre_spectral_echo_custom_Lore" "Every strike echoes in shadow—and the echo cuts too." + +"dota_tooltip_ability_ability_spectre_desolate_custom" "Desolate" +"dota_tooltip_ability_ability_spectre_desolate_custom_Description" "Innate: each attack deals bonus pure damage equal to %health_damage_pct%%% of Spectre's max health (1%% at level 1, +0.1%% per innate level)." +"dota_tooltip_ability_ability_spectre_desolate_custom_health_damage_pct" "MAX HEALTH AS DAMAGE:" +"dota_tooltip_ability_ability_spectre_desolate_custom_Lore" "The shadow strikes with the strength its bearer holds within." + +"dota_tooltip_ability_ability_spectre_dispersion_custom" "Dispersion" +"dota_tooltip_ability_ability_spectre_dispersion_custom_Description" "Passive: reduces incoming damage by %damage_reflection_pct%%% and reflects part of damage taken to enemies within %max_radius% (stronger up close, full at %min_radius%). Reflection is stronger on dagger trail targets." +"dota_tooltip_ability_ability_spectre_dispersion_custom_damage_reflection_pct" "INCOMING REDUCTION:" +"dota_tooltip_ability_ability_spectre_dispersion_custom_min_radius" "MAX REFLECT RANGE:" +"dota_tooltip_ability_ability_spectre_dispersion_custom_max_radius" "REFLECT RANGE:" +"dota_tooltip_ability_ability_spectre_dispersion_custom_Lore" "Pain spreads to all who dare come close." +"dota_tooltip_ability_ability_spectre_dispersion_custom_Shard_Description" "Active: for %activation_duration% sec., boosts Dispersion by +%activation_bonus_pct%%% reflection. Cooldown %activation_cooldown% sec., mana cost %activation_manacost%." + +"dota_tooltip_ability_ability_spectre_haunt_custom" "Haunt" +"dota_tooltip_ability_ability_spectre_haunt_custom_Description" "Creates %illusion_count% Spectre illusions for %duration% sec. (%illusion_damage_outgoing%%% outgoing damage). Their attacks trigger Spectral Echo. Reality swaps with the closest illusion and auto-casts Spectral Dagger on the nearest enemy." +"dota_tooltip_ability_ability_spectre_haunt_custom_illusion_count" "ILLUSION COUNT:" +"dota_tooltip_ability_ability_spectre_haunt_custom_duration" "DURATION:" +"dota_tooltip_ability_ability_spectre_haunt_custom_Lore" "A shadow steps out—and waits to strike." + +"dota_tooltip_ability_ability_spectre_reality_custom" "Reality" +"dota_tooltip_ability_ability_spectre_reality_custom_Description" "Swaps places with the Haunt illusion and casts Spectral Dagger at the nearest enemy within dagger cast range." + +"dota_tooltip_ability_ability_spectre_shadow_step_custom" "Shadow Step" +"dota_tooltip_ability_ability_spectre_shadow_step_custom_Description" "With Aghanim's Scepter: each attack for %duration_steal% sec. steals %health_steal% max health from the target and grants Spectre %health_bonus_self% per stack (illusions get %illusion_decrease%%% reduced effect)." +"dota_tooltip_ability_ability_spectre_shadow_step_custom_health_steal" "HEALTH STOLEN PER STACK:" +"dota_tooltip_ability_ability_spectre_shadow_step_custom_health_bonus_self" "HEALTH GAINED PER STACK:" +"dota_tooltip_ability_ability_spectre_shadow_step_custom_duration_steal" "STACK DURATION:" +"dota_tooltip_ability_ability_spectre_shadow_step_custom_Lore" "Every hit—a piece of another life in your hands." + +"dota_tooltip_modifier_modifier_spectre_dagger_debuff_custom" "Spectral Dagger" +"dota_tooltip_modifier_modifier_spectre_dagger_debuff_custom_Description" "Slowed; trail empowers Desolate and Dispersion." +"dota_tooltip_modifier_modifier_spectre_dagger_buff_custom" "Spectral Dagger" +"dota_tooltip_modifier_modifier_spectre_dagger_buff_custom_Description" "Increased move speed." +"dota_tooltip_modifier_modifier_spectre_dispersion_custom_boosted" "Dispersion" +"dota_tooltip_modifier_modifier_spectre_dispersion_custom_boosted_Description" "Boosted damage reflection." +"dota_tooltip_modifier_modifier_spectre_stack_buff" "Shadow Step" +"dota_tooltip_modifier_modifier_spectre_stack_buff_Description" "Bonus max health per stack." +"dota_tooltip_modifier_modifier_spectre_stack_debuff" "Shadow Step" +"dota_tooltip_modifier_modifier_spectre_stack_debuff_Description" "Reduced max health per stack." + +// -----------------Bristleback (custom kit)-------------------- +"dota_tooltip_ability_bristleback_viscous_nasal_goo_custom" "Viscous Nasal Goo" +"dota_tooltip_ability_bristleback_viscous_nasal_goo_custom_Description" "Targets an enemy and splashes in %radius% radius: reduces base armor by %base_armor_pct%%% + %armor_per_stack_pct%%% per stack and move speed for %goo_duration% sec. Stacks refresh duration." +"dota_tooltip_ability_bristleback_viscous_nasal_goo_custom_Lore" "A cold caught in the snow becomes Rigwarl's edge." +"dota_tooltip_ability_bristleback_viscous_nasal_goo_custom_radius" "RADIUS:" +"dota_tooltip_ability_bristleback_viscous_nasal_goo_custom_base_armor_pct" "%BASE ARMOR REDUCTION:" +"dota_tooltip_ability_bristleback_viscous_nasal_goo_custom_armor_per_stack_pct" "%ARMOR REDUCTION PER STACK:" +"dota_tooltip_ability_bristleback_viscous_nasal_goo_custom_base_move_slow" "%BASE MOVE SLOW:" +"dota_tooltip_ability_bristleback_viscous_nasal_goo_custom_move_slow_per_stack" "%MOVE SLOW PER STACK:" + +"dota_tooltip_ability_bristleback_quill_spray_custom" "Quill Spray" +"dota_tooltip_ability_bristleback_quill_spray_custom_Description" "Releases quills in all directions, dealing physical damage plus %attack_damage_bonus_pct%%% of the owner's attack damage against each target. Enemies take bonus damage for each recent quill hit within %quill_stack_duration% sec." +"dota_tooltip_ability_bristleback_quill_spray_custom_Lore" "A bouncer's honor is a sharp thing. So are his quills." +"dota_tooltip_ability_bristleback_quill_spray_custom_Note0" "Damage is not reduced by damage block." +"dota_tooltip_ability_bristleback_quill_spray_custom_radius" "RADIUS:" +"dota_tooltip_ability_bristleback_quill_spray_custom_attack_damage_bonus_pct" "%ATTACK DAMAGE ADDED:" +"dota_tooltip_ability_bristleback_quill_spray_custom_quill_base_damage" "BASE DAMAGE:" +"dota_tooltip_ability_bristleback_quill_spray_custom_quill_stack_damage" "BONUS DAMAGE PER STACK:" +"dota_tooltip_ability_bristleback_quill_spray_custom_quill_stack_duration" "STACK WINDOW:" +"dota_tooltip_ability_bristleback_quill_spray_custom_max_damage" "MAX DAMAGE PER HIT:" +"dota_tooltip_ability_bristleback_quill_spray_custom_scepter_description" "Increases damage and stack duration." + +"dota_tooltip_ability_bristleback_bristleback_custom" "Bristleback" +"dota_tooltip_ability_bristleback_bristleback_custom_Description" "Takes reduced damage from the sides and back. After taking %quill_release_threshold% damage from behind, automatically casts Quill Spray at its current level." +"dota_tooltip_ability_bristleback_bristleback_custom_Lore" "Sometimes it pays to turn your back." +"dota_tooltip_ability_bristleback_bristleback_custom_Note0" "Back is within 70 degrees of the middle of his back." +"dota_tooltip_ability_bristleback_bristleback_custom_Note1" "Sides are within 110 degrees of the middle of his back." +"dota_tooltip_ability_bristleback_bristleback_custom_side_damage_reduction" "%SIDE DAMAGE REDUCTION:" +"dota_tooltip_ability_bristleback_bristleback_custom_back_damage_reduction" "%BACK DAMAGE REDUCTION:" +"dota_tooltip_ability_bristleback_bristleback_custom_quill_release_threshold" "BACK DAMAGE THRESHOLD:" +"dota_tooltip_ability_bristleback_bristleback_custom_goo_radius" "SNOT SPLASH RADIUS:" +"dota_tooltip_ability_bristleback_bristleback_custom_scepter_description" "When damage from behind crosses higher thresholds, fires multiple Quill Sprays." + +"dota_tooltip_ability_bristleback_hairball_custom" "Hairball" +"dota_tooltip_ability_bristleback_hairball_custom_Description" "Hawks a ball of quills to a point. On landing it pops, applying several Viscous Nasal Goo stacks and multiple Quill Spray waves to nearby enemies." +"dota_tooltip_ability_bristleback_hairball_custom_Lore" "Rigwarl weaponizes every part of himself—even parts he swallowed by accident." +"dota_tooltip_ability_bristleback_hairball_custom_radius" "RADIUS:" +"dota_tooltip_ability_bristleback_hairball_custom_quill_stacks" "QUILL SPRAY PROC COUNT:" +"dota_tooltip_ability_bristleback_hairball_custom_goo_stacks" "GOO APPLICATIONS:" + +"dota_tooltip_ability_bristleback_warpath" "Warpath" +"dota_tooltip_ability_bristleback_warpath_Description" "Innate. Each time Bristleback takes damage, he gains a stack (max stacks and duration scale with hero level). Each stack grants %damage_per_stack_base% + %damage_per_stack_per_hero_level% damage per hero level and %move_speed_per_stack_base% + %move_speed_per_stack_per_hero_level% move speed per hero level." +"dota_tooltip_ability_bristleback_warpath_damage_per_stack_base" "BASE DAMAGE PER STACK:" +"dota_tooltip_ability_bristleback_warpath_damage_per_stack_per_hero_level" "DAMAGE PER STACK / HERO LEVEL:" +"dota_tooltip_ability_bristleback_warpath_move_speed_per_stack_base" "BASE MOVE SPEED PER STACK:" +"dota_tooltip_ability_bristleback_warpath_move_speed_per_stack_per_hero_level" "MOVE SPEED PER STACK / HERO LEVEL:" +"dota_tooltip_ability_bristleback_warpath_max_stacks_base" "BASE MAX STACKS:" +"dota_tooltip_ability_bristleback_warpath_max_stacks_per_hero_level" "MAX STACKS / HERO LEVEL:" +"dota_tooltip_ability_bristleback_warpath_stack_duration_base" "BASE STACK DURATION:" +"dota_tooltip_ability_bristleback_warpath_stack_duration_per_hero_level" "STACK DURATION / HERO LEVEL:" + +"dota_tooltip_ability_bristleback_rage_fortitude_custom" "Battle Fortitude" +"dota_tooltip_ability_bristleback_rage_fortitude_custom_Description" "While rage is above %rage_threshold%, grants debuff immunity, bonus armor, and bonus magic resistance." +"dota_tooltip_ability_bristleback_rage_fortitude_custom_Lore" "When Rigwarl's blood boils, hide and quills turn nearly impenetrable." +"dota_tooltip_ability_bristleback_rage_fortitude_custom_rage_threshold" "RAGE THRESHOLD:" +"dota_tooltip_ability_bristleback_rage_fortitude_custom_bonus_armor" "+$armor" +"dota_tooltip_ability_bristleback_rage_fortitude_custom_bonus_magic_resist" "%+$spell_resist" +"dota_tooltip_modifier_bristleback_rage_fortitude_buff" "Battle Fortitude" +"dota_tooltip_modifier_bristleback_rage_fortitude_buff_Description" "Debuff immune with bonus armor and magic resistance." + +"dota_tooltip_ability_special_bonus_unique_bristleback_custom_3" "+{s:bonus_quill_base_damage} / +{s:bonus_max_damage} Quill Spray base / max damage" +"dota_tooltip_ability_special_bonus_unique_bristleback_custom_5" "+{s:bonus_quill_stack_damage} Quill Spray stack damage" +"dota_tooltip_ability_special_bonus_unique_bristleback_custom_6" "+{s:bonus_radius} Viscous Nasal Goo AoE radius" +"dota_tooltip_ability_special_bonus_unique_bristleback_3" "+{s:bonus_damage_per_stack} Warpath damage per stack" + +// -----------------Legion Commander-------------------- +"dota_tooltip_ability_ability_legion_commander_overwhelming_odds_custom" "Overwhelming Odds" +"dota_tooltip_ability_ability_legion_commander_overwhelming_odds_custom_Description" "Legion Commander slams the ground at a point, dealing magic damage in the area—each enemy hero and creep hit adds more damage. Hit enemies are slowed; Legion gains armor for each enemy struck, and for a short time gains bonus attack speed and movement speed." +"dota_tooltip_ability_ability_legion_commander_overwhelming_odds_custom_Lore" "The tighter the enemy line, the clearer the path to victory." +"dota_tooltip_ability_ability_legion_commander_overwhelming_odds_custom_radius" "RADIUS:" +"dota_tooltip_ability_ability_legion_commander_overwhelming_odds_custom_damage_base" "BASE DAMAGE:" +"dota_tooltip_ability_ability_legion_commander_overwhelming_odds_custom_damage_per_enemy" "BONUS DAMAGE PER UNIT:" +"dota_tooltip_ability_ability_legion_commander_overwhelming_odds_custom_armor_buff_duration" "ARMOR DURATION:" +"dota_tooltip_ability_ability_legion_commander_overwhelming_odds_custom_armor_per_enemy" "ARMOR PER ENEMY HIT:" +"dota_tooltip_ability_ability_legion_commander_overwhelming_odds_custom_debuff_duration" "SLOW DURATION:" +"dota_tooltip_ability_ability_legion_commander_overwhelming_odds_custom_enemy_movespeed_slow" "%MOVE SLOW:" +"dota_tooltip_ability_ability_legion_commander_overwhelming_odds_custom_self_buff_duration" "BUFF DURATION:" +"dota_tooltip_ability_ability_legion_commander_overwhelming_odds_custom_self_bonus_attack_speed" "BONUS ATTACK SPEED (SELF):" +"dota_tooltip_ability_ability_legion_commander_overwhelming_odds_custom_self_bonus_movespeed" "BONUS MOVE SPEED (SELF):" + +"dota_tooltip_ability_ability_legion_commander_press_the_attack_custom" "Press the Attack" +"dota_tooltip_ability_ability_legion_commander_press_the_attack_custom_Description" "Legion Commander targets an allied hero or herself with a strong dispel, then for %duration% sec. raises the target's health regen, attack speed, and movement speed." +"dota_tooltip_ability_ability_legion_commander_press_the_attack_custom_Lore" "One clap on the shoulder—and an ally snaps back into the fight." +"dota_tooltip_ability_ability_legion_commander_press_the_attack_custom_duration" "DURATION:" +"dota_tooltip_ability_ability_legion_commander_press_the_attack_custom_attack_speed_bonus" "BONUS ATTACK SPEED:" +"dota_tooltip_ability_ability_legion_commander_press_the_attack_custom_hp_regen" "HEALTH REGEN:" +"dota_tooltip_ability_ability_legion_commander_press_the_attack_custom_movespeed_bonus" "BONUS MOVE SPEED:" + +"dota_tooltip_ability_ability_legion_commander_moment_of_courage_custom" "Moment of Courage" +"dota_tooltip_ability_ability_legion_commander_moment_of_courage_custom_Description" "Passive: when an enemy lands a basic attack on Legion Commander, %trigger_chance_pct%%% chance to instantly counter-attack, heal for a portion of that counter damage, and briefly gain movement speed. Proc checks cannot occur more than once every %proc_cooldown% sec." +"dota_tooltip_ability_ability_legion_commander_moment_of_courage_custom_Lore" "Panic flees faster than steel—until Tresdin answers." +"dota_tooltip_ability_ability_legion_commander_moment_of_courage_custom_trigger_chance_pct" "%PROC CHANCE:" +"dota_tooltip_ability_ability_legion_commander_moment_of_courage_custom_proc_cooldown" "PROC CHECK INTERVAL:" +"dota_tooltip_ability_ability_legion_commander_moment_of_courage_custom_buff_duration" "HASTE DURATION:" +"dota_tooltip_ability_ability_legion_commander_moment_of_courage_custom_buff_bonus_movespeed" "BONUS MOVE SPEED AFTER PROC:" +"dota_tooltip_ability_ability_legion_commander_moment_of_courage_custom_counter_lifesteal_pct" "%HEAL FROM COUNTER DAMAGE:" +"dota_tooltip_ability_ability_legion_commander_moment_of_courage_custom_attack_count" "INSTANT ATTACK SWINGS:" + +"dota_tooltip_ability_ability_legion_commander_duel_custom" "Duel" +"dota_tooltip_ability_ability_legion_commander_duel_custom_Description" "Legion Commander forces one enemy into a duel for %duration% sec. Both duelists cannot use abilities or active items but gain bonus attack speed; on enemy heroes these effects last shorter because of status resistance. Winning permanently adds attack damage (more vs heroes, less vs creeps; total bonus capped at %max_stack_damage%) and %reward_random_stat% to a random attribute. If the duelists move more than %victory_range% apart, the duel ends with no reward." +"dota_tooltip_ability_ability_legion_commander_duel_custom_Lore" "Glory settles every argument." +"dota_tooltip_ability_ability_legion_commander_duel_custom_duration" "DURATION:" +"dota_tooltip_ability_ability_legion_commander_duel_custom_victory_range" "BREAK DISTANCE:" +"dota_tooltip_ability_ability_legion_commander_duel_custom_reward_damage" "BONUS DAMAGE ON HERO WIN:" +"dota_tooltip_ability_ability_legion_commander_duel_custom_reward_damage_creep" "BONUS DAMAGE ON CREEP WIN:" +"dota_tooltip_ability_ability_legion_commander_duel_custom_reward_random_stat" "RANDOM ATTRIBUTE PER WIN:" +"dota_tooltip_ability_ability_legion_commander_duel_custom_max_stack_damage" "MAX TOTAL BONUS DAMAGE:" +"dota_tooltip_ability_ability_legion_commander_duel_custom_bonus_attack_speed" "BONUS ATTACK SPEED:" +"dota_tooltip_ability_ability_legion_commander_duel_custom_scepter_cooldown_reduction" "COOLDOWN REDUCTION:" +"dota_tooltip_ability_ability_legion_commander_duel_custom_scepter_magic_resist" "MAGIC RESISTANCE:" +"dota_tooltip_ability_ability_legion_commander_duel_custom_Scepter_Description" "Increases Duel's max charges, reduces its cooldown, and grants Legion Commander %scepter_magic_resist%%% magic resistance while Duel is active." +"dota_tooltip_ability_ability_legion_commander_duel_custom_Shard_Description" "While Duel is active on Legion Commander, every %shard_overwhelming_interval% sec. Overwhelming Odds automatically triggers at her feet at its current level. These triggers cost no mana and do not put Overwhelming Odds on cooldown (requires at least one point in Overwhelming Odds)." +"dota_tooltip_ability_ability_legion_commander_duel_custom_shard_overwhelming_interval" "BURST INTERVAL:" +"dota_tooltip_ability_ability_legion_commander_duel_custom_Note0" "Attack damage won from Duel persists for the whole match." + +"dota_tooltip_ability_special_bonus_unique_legion_commander_odds_radius" "+70 Overwhelming Odds radius" +"dota_tooltip_ability_special_bonus_unique_legion_commander_odds_damage" "+60 Overwhelming Odds base damage" +"dota_tooltip_ability_special_bonus_unique_legion_commander_pta_duration" "+2s Press the Attack duration" +"dota_tooltip_ability_special_bonus_unique_legion_commander_pta_regen" "+12 Press the Attack health regen" +"dota_tooltip_ability_special_bonus_unique_legion_commander_moc_chance" "+10% Moment of Courage proc chance" +"dota_tooltip_ability_special_bonus_unique_legion_commander_moc_lifesteal" "+25% Moment of Courage heal from counter" +"dota_tooltip_ability_special_bonus_unique_legion_commander_duel_duration" "+2s Duel duration" +"dota_tooltip_ability_special_bonus_unique_legion_commander_duel_reward" "+10 Duel reward bonus damage on hero wins" + +// --------------------Queen of Pain-------------------- + +"DOTA_Tooltip_ability_queenofpain_shadow_strike_custom" "Shadow Strike" +"DOTA_Tooltip_ability_queenofpain_shadow_strike_custom_Description" "Hurls a dagger that hits enemies in a %radius% radius around the target, dealing magic damage and poisoning for %duration% sec. The victim is slowed, and every %damage_interval% sec. takes additional poison damage. The first hit and each poison tick deal extra damage equal to %mana_damage_from_current_pct%%% of Akasha's current mana." +"DOTA_Tooltip_ability_queenofpain_shadow_strike_custom_Scepter_Description" "When this debuff on an enemy ends or is refreshed, it releases one Scream of Pain wave from that unit." +"DOTA_Tooltip_ability_queenofpain_shadow_strike_custom_Lore" "Akasha savors a victim's suffering, pinning them with a venomous blade." +"DOTA_Tooltip_ability_queenofpain_shadow_strike_custom_movement_slow" "%MOVE SLOW:" +"DOTA_Tooltip_ability_queenofpain_shadow_strike_custom_strike_damage" "DAMAGE ON HIT:" +"DOTA_Tooltip_ability_queenofpain_shadow_strike_custom_duration_damage" "PERIODIC POISON DAMAGE:" +"DOTA_Tooltip_ability_queenofpain_shadow_strike_custom_duration" "DURATION:" +"DOTA_Tooltip_ability_queenofpain_shadow_strike_custom_damage_interval" "INTERVAL:" +"DOTA_Tooltip_ability_queenofpain_shadow_strike_custom_radius" "RADIUS:" +"DOTA_Tooltip_ability_queenofpain_shadow_strike_custom_mana_damage_from_current_pct" "% OF CURRENT MANA ADDED AS DAMAGE:" + +"DOTA_Tooltip_ability_queenofpain_blink_custom" "Blink" +"DOTA_Tooltip_ability_queenofpain_blink_custom_Description" "A short-range blink: a sonic impact at the start and end point deals magic damage in %radius% and silences. Damage in each area is also increased by %mana_damage_from_current_pct%%% of your current mana." +"DOTA_Tooltip_ability_queenofpain_blink_custom_Lore" "The unspoken Queen earned her name by giving pain to every who crossed her path." +"DOTA_Tooltip_ability_queenofpain_blink_custom_Note0" "Causes many projectiles that track the hero to be disjointed." +"DOTA_Tooltip_ability_queenofpain_blink_custom_damage" "DAMAGE:" +"DOTA_Tooltip_ability_queenofpain_blink_custom_duration" "SILENCE DURATION:" +"DOTA_Tooltip_ability_queenofpain_blink_custom_radius" "RADIUS:" +"DOTA_Tooltip_ability_queenofpain_blink_custom_cast_range" "RANGE:" +"DOTA_Tooltip_ability_queenofpain_blink_custom_mana_damage_from_current_pct" "% OF CURRENT MANA ADDED AS DAMAGE:" + +"DOTA_Tooltip_ability_queenofpain_scream_of_pain_custom" "Scream of Pain" +"DOTA_Tooltip_ability_queenofpain_scream_of_pain_custom_Description" "A piercing wail that deals magic damage to all nearby enemies in %radius%. The damage is also increased by %mana_damage_from_current_pct%%% of your current mana." +"DOTA_Tooltip_ability_queenofpain_scream_of_pain_custom_Shard_Description" "Enemies hit (except buildings, couriers, and Roshan) are vulnerable for %shard_mark_duration% sec.: they take %shard_incoming_damage_pct%%% more incoming damage. Dispellable." +"DOTA_Tooltip_ability_queenofpain_scream_of_pain_custom_Lore" "Akasha maddens foes with her lascivious voice as she feasts on their souls." +"DOTA_Tooltip_ability_queenofpain_scream_of_pain_custom_Note0" "Damages invisible enemies." +"DOTA_Tooltip_ability_queenofpain_scream_of_pain_custom_Note1" "This ability's projectiles cannot be disjointed from the target." +"DOTA_Tooltip_ability_queenofpain_scream_of_pain_custom_radius" "RADIUS:" +"DOTA_Tooltip_ability_queenofpain_scream_of_pain_custom_damage" "DAMAGE:" +"DOTA_Tooltip_ability_queenofpain_scream_of_pain_custom_shard_mark_duration" "VULNERABILITY DURATION:" +"DOTA_Tooltip_ability_queenofpain_scream_of_pain_custom_shard_incoming_damage_pct" "BONUS INCOMING DAMAGE (%):" +"DOTA_Tooltip_ability_queenofpain_scream_of_pain_custom_mana_damage_from_current_pct" "% OF CURRENT MANA ADDED AS DAMAGE:" + +"DOTA_Tooltip_Ability_special_bonus_unique_queen_of_pain_2_1" "+{s:bonus_damage} Blink damage" +"DOTA_Tooltip_Ability_special_bonus_unique_queen_of_pain_8" "+{s:bonus_mitigation_pct_per_level}% less self-damage from the innate per your level" + +"DOTA_Tooltip_ability_queenofpain_agony_innate" "Masochistic Agony" +"DOTA_Tooltip_ability_queenofpain_agony_innate_Description" "Damage to enemies with your abilities also hurts you: you take Pure damage equal to %self_damage_pct_per_level%%% of the damage dealt per your level. Grants +%spell_amp_bonus%%% spell amplification. Also gains +%spell_amp_per_missing_hp_pct%%% spell amplification for each 1% of missing health. After killing any unit, restores 5% of your max health. With the talent, that self-damage is further reduced by %mitigation_pct_per_level%%% per your level." +"DOTA_Tooltip_ability_queenofpain_agony_innate_Lore" "Akasha savors her foe's pain—but ecstasy has a price she is willing to pay." +"DOTA_Tooltip_ability_queenofpain_agony_innate_self_damage_pct_per_level" "SELF-DAMAGE SHARE PER LEVEL (%):" +"DOTA_Tooltip_ability_queenofpain_agony_innate_spell_amp_bonus" "SPELL AMP (%):" +"DOTA_Tooltip_ability_queenofpain_agony_innate_spell_amp_per_missing_hp_pct" "SPELL AMP PER 1% MISSING HP (%):" +"DOTA_Tooltip_ability_queenofpain_agony_innate_mitigation_pct_per_level" "SELF-DAMAGE REDUCTION PER LEVEL WITH TALENT (%):" + +"dota_tooltip_ability_ability_lina_laguna_blade_custom_mana_cost_facet" "% MANA IN AGHANIM FORMULA:" +"dota_tooltip_ability_ability_lina_laguna_blade_custom_mana_cost_facet_tooltip" "% MANA SHARE IN FORMULA:" + + +// --------------------Luna-------------------- + +"dota_tooltip_ability_ability_luna_lucent_beam_custom" "Lucent Beam" +"dota_tooltip_ability_ability_luna_lucent_beam_custom_Description" "Fires a moon beam at an enemy for %lucent_beam_damage% magic damage and a %stun_duration% sec stun. Also hits enemies within %lucent_beam_aoe_radius% of the main target for %lucent_beam_aoe_damage_pct%%% of the main hit's damage and stun (Eclipse beams use the same splash)." +"dota_tooltip_ability_ability_luna_lucent_beam_custom_lucent_beam_damage" "DAMAGE:" +"dota_tooltip_ability_ability_luna_lucent_beam_custom_stun_duration" "STUN:" +"dota_tooltip_ability_ability_luna_lucent_beam_custom_lucent_beam_aoe_radius" "SPLASH RADIUS AROUND TARGET:" +"dota_tooltip_ability_ability_luna_lucent_beam_custom_lucent_beam_aoe_damage_pct" "SPLASH DAMAGE & STUN SHARE (%):" +"dota_tooltip_ability_ability_luna_lucent_beam_custom_AbilityCastRange" "RANGE OF APPLICATION:" +"dota_tooltip_ability_ability_luna_lucent_beam_custom_Lore" "The Selemena does not forgive those who stand in the way of her warrior: one ray — and darkness has closed over the enemy." + +"dota_tooltip_ability_ability_luna_twin_glaives_custom" "Twin Glaives" +"dota_tooltip_ability_ability_luna_twin_glaives_custom_Description" "Luna throws two glaives in an arc; they converge on the target then return, crossing paths. Wide damage along the flight path; the target can be hit twice. Each hit deals %glaive_damage% plus a portion of Luna's attack. Hit enemies take increased incoming physical and magic damage for %twin_facet_vuln_duration% sec: %twin_facet_pct%%% per debuff stack." +"dota_tooltip_ability_ability_luna_twin_glaives_custom_glaive_damage" "BASIC GLEFA DAMAGE:" +"dota_tooltip_ability_ability_luna_twin_glaives_custom_projectile_speed" "PROJECTILE SPEED:" +"dota_tooltip_ability_ability_luna_twin_glaives_custom_projectile_range" "MAX. RANGE:" +"dota_tooltip_ability_ability_luna_twin_glaives_custom_projectile_width" "DAMAGE RADIUS:" +"dota_tooltip_ability_ability_luna_twin_glaives_custom_axe_spread" "THROW SPACER:" +"dota_tooltip_ability_ability_luna_twin_glaives_custom_twin_facet_vuln_duration" "VULNERABILITY DURATION:" +"dota_tooltip_ability_ability_luna_twin_glaives_custom_twin_facet_pct" "% INCOMING DAMAGE PER STACK:" +"dota_tooltip_ability_ability_luna_twin_glaives_custom_AbilityCastRange" "RANGE OF APPLICATION:" +"dota_tooltip_ability_ability_luna_twin_glaives_custom_Lore" "The blade of the lunar disk knows no mercy: where the moon shines, steel falls." + +"dota_tooltip_ability_ability_luna_moon_glaive_custom" "Moon Glaives" +"dota_tooltip_ability_ability_luna_moon_glaive_custom_Description" "Attacks launch glaives that bounce to enemies within %bounce_range%. First bounce deals %bounce_damage_pct%%% attack damage; each next deals %bounce_falloff_pct%%% of the previous (weaker at low levels). A talent can remove falloff. With the beam-attack talent, after Lucent Beam or an Eclipse beam hits an enemy, Luna instantly attacks that target twice." +"dota_tooltip_ability_ability_luna_moon_glaive_custom_bounce_range" "BOUNCE RADIUS:" +"dota_tooltip_ability_ability_luna_moon_glaive_custom_moon_glaive_bounce_count" "BOUNCES:" +"dota_tooltip_ability_ability_luna_moon_glaive_custom_bounce_falloff_pct" "% SHARE OF REBOUND DAMAGE:" +"dota_tooltip_ability_ability_luna_moon_glaive_custom_Lore" "The hunt doesn’t end with the first strike: as long as the enemies stand nearby, lunar steel will find everyone." + +"dota_tooltip_ability_ability_luna_lunar_blessing_custom" "Lunar Blessing" +"dota_tooltip_ability_ability_luna_lunar_blessing_custom_Description" "Inherent force associated with Eclipse. Allies within %blessing_radius% gain bonus attack damage: %blessing_bonus_damage% per hero level under the buff. While allied Mirana is within %moon_sisters_radius%, Luna and Mirana take %moon_sisters_damage_reduction_pct%%% less incoming damage." +"dota_tooltip_ability_ability_luna_lunar_blessing_custom_blessing_radius" "RADIUS:" +"dota_tooltip_ability_ability_luna_lunar_blessing_custom_blessing_bonus_damage" "LEVEL DAMAGE BONUS:" +"dota_tooltip_ability_ability_luna_lunar_blessing_custom_moon_sisters_radius" "MIRANA LINK RADIUS:" +"dota_tooltip_ability_ability_luna_lunar_blessing_custom_moon_sisters_damage_reduction_pct" "INCOMING DAMAGE REDUCTION (SISTERS):" +"dota_tooltip_ability_ability_luna_lunar_blessing_custom_Shard_Description" "Allies under the Lunar Blessing effect have an additional increase in spell gain: %blessing_shard_spell_amp_per_level%%% for each hero level." +"dota_tooltip_ability_ability_luna_lunar_blessing_custom_Lore" "The moon carries the will of the Selemena: the moonlight also touches the blades of the allies, not just her own steel." + +"dota_tooltip_ability_ability_luna_eclipse_custom" "Eclipse" +"dota_tooltip_ability_ability_luna_eclipse_custom_Description" "Eclipse lasts %eclipse_duration% sec. in %eclipse_radius% radius; beams fall every %beam_interval% sec. Without Aghanim, each interval picks one random enemy in the zone; there are %eclipse_beam_count% such strikes total. Each beam uses Lucent Beam's current damage and stun; if Lucent Beam is not learned, beams deal no damage." +"dota_tooltip_ability_ability_luna_eclipse_custom_eclipse_duration" "DURATION:" +"dota_tooltip_ability_ability_luna_eclipse_custom_beam_interval" "BEAM INTERVAL:" +"dota_tooltip_ability_ability_luna_eclipse_custom_eclipse_beam_count" "NUMBER OF RAYS:" +"dota_tooltip_ability_ability_luna_eclipse_custom_eclipse_radius" "RADIUS:" +"dota_tooltip_ability_ability_luna_eclipse_custom_eclipse_facet_wave_count" "WAVES (AGHANIM):" +"dota_tooltip_ability_ability_luna_eclipse_custom_Scepter_Description" "Instead of random targets, Eclipse fires %eclipse_facet_wave_count% waves; each wave hits every enemy in the area with a full Lucent Beam strike." +"dota_tooltip_ability_ability_luna_eclipse_custom_Lore" "When the moon covers the sun, the sky itself becomes Selemena’s weapon — and no one will escape from her judgment." + +"dota_tooltip_modifier_modifier_luna_lunar_blessing_buff" "Lunar Blessing" +"dota_tooltip_modifier_modifier_luna_lunar_blessing_buff_Description" "Moonlight amplifies your attack damage: the bonus scales with your level under the buff and with Luna's level. With Aghanim's Shard on Luna — additional spell amplification from your level." +"dota_tooltip_modifier_modifier_luna_lunar_blessing_aura" "Lunar Blessing" +"dota_tooltip_modifier_modifier_luna_lunar_blessing_aura_Description" "Hurray: allies nearby get the Lunar Blessing effect." + +"dota_tooltip_modifier_modifier_moon_sisters_innate" "Moon Sisters" +"dota_tooltip_modifier_modifier_moon_sisters_innate_Description" "Your moon sister is nearby: incoming damage is reduced." + +"dota_tooltip_modifier_modifier_luna_eclipse_active" "Eclipse" +"dota_tooltip_modifier_modifier_luna_eclipse_active_Description" "Moonbeams hit enemies in the area: magic damage and short stun (random one per tick, or all enemies per wave with Aghanim)." + +// --------------------Mirana-------------------- + +"dota_tooltip_ability_ability_mirana_starstorm_custom" "Starstorm" +"dota_tooltip_ability_ability_mirana_starstorm_custom_Description" "Calls a starstorm around Mirana: meteors fall on enemies within %radius% at the cast location; %meteor_impact_delay% sec after they appear they deal %damage% magic damage plus %mana_damage_pct%%% of max mana. After %secondary_delay% sec the closest enemy in that area takes a second meteor for %secondary_damage_pct%%% of the main hit (%meteor_impact_delay% sec fall time)." +"dota_tooltip_ability_ability_mirana_starstorm_custom_damage" "DAMAGE:" +"dota_tooltip_ability_ability_mirana_starstorm_custom_mana_damage_pct" "MANA DAMAGE (%):" +"dota_tooltip_ability_ability_mirana_starstorm_custom_radius" "RADIUS:" +"dota_tooltip_ability_ability_mirana_starstorm_custom_meteor_impact_delay" "METEOR FALL TIME:" +"dota_tooltip_ability_ability_mirana_starstorm_custom_secondary_delay" "SECOND METEOR DELAY:" +"dota_tooltip_ability_ability_mirana_starstorm_custom_secondary_damage_pct" "SECONDARY HIT DAMAGE (%):" +"dota_tooltip_ability_ability_mirana_starstorm_custom_Lore" "The sky answers the moon huntress — foes cannot hide in shadow." + +"dota_tooltip_ability_ability_mirana_sacred_arrow_custom" "Sacred Arrow" +"dota_tooltip_ability_ability_mirana_sacred_arrow_custom_Description" "Fires a sacred arrow toward the cursor. Deals %arrow_damage% magic damage plus %mana_damage_pct%%% of max mana to each enemy hit and stops only on bosses. Boss stun scales with travel distance: %stun_per_100_distance% sec per 100 units, up to %max_stun_duration% sec." +"dota_tooltip_ability_ability_mirana_sacred_arrow_custom_arrow_damage" "DAMAGE:" +"dota_tooltip_ability_ability_mirana_sacred_arrow_custom_mana_damage_pct" "MANA DAMAGE (%):" +"dota_tooltip_ability_ability_mirana_sacred_arrow_custom_arrow_range" "RANGE:" +"dota_tooltip_ability_ability_mirana_sacred_arrow_custom_arrow_speed" "SPEED:" +"dota_tooltip_ability_ability_mirana_sacred_arrow_custom_stun_per_100_distance" "STUN PER 100 DISTANCE:" +"dota_tooltip_ability_ability_mirana_sacred_arrow_custom_max_stun_duration" "MAX STUN:" +"dota_tooltip_ability_ability_mirana_sacred_arrow_custom_Shard_Description" "While the arrow travels, each enemy within %shard_starfall_radius% of its path is struck once by Starstorm: %shard_starfall_damage_pct_first%%% then %shard_starfall_damage_pct_second%%% of Sacred Arrow damage (base + mana)." +"dota_tooltip_ability_ability_mirana_sacred_arrow_custom_shard_starfall_radius" "STARSTORM RADIUS:" +"dota_tooltip_ability_ability_mirana_sacred_arrow_custom_shard_starfall_damage_pct_first" "FIRST METEOR DAMAGE (%):" +"dota_tooltip_ability_ability_mirana_sacred_arrow_custom_shard_starfall_damage_pct_second" "SECOND METEOR DAMAGE (%):" +"dota_tooltip_ability_ability_mirana_sacred_arrow_custom_Lore" "The farther the arrow flies, the harder Selemene's judgment lands." + +"dota_tooltip_ability_ability_mirana_leap_custom" "Leap" +"dota_tooltip_ability_ability_mirana_leap_custom_Description" "Mirana leaps forward in the direction she faces up to %leap_distance%. Each leap costs %mana_cost_pct%%% of max mana. While airborne, she takes %leap_damage_reduction_pct%%% less damage and cannot leap again until she lands. On landing, enemies within %leap_landing_radius% take %leap_landing_damage_pct%%% of Mirana's attack damage plus bonus magical damage equal to %mana_damage_pct%%% of her max mana. Has %max_charges% charges (4 + 1 per hero level). One charge restores in %AbilityChargeRestoreTime% sec. Instantly gains +%leap_speedbonus%%% movement speed and +%leap_speedbonus_as% attack speed for %leap_bonus_duration% sec. Innate damage is ×%innate_damage_multiplier% for the buff duration." +"dota_tooltip_ability_ability_mirana_leap_custom_mana_cost_pct" "MAX MANA COST (%):" +"dota_tooltip_ability_ability_mirana_leap_custom_leap_landing_radius" "LANDING DAMAGE RADIUS:" +"dota_tooltip_ability_ability_mirana_leap_custom_leap_landing_damage_pct" "LANDING DAMAGE (% OF ATTACK):" +"dota_tooltip_ability_ability_mirana_leap_custom_mana_damage_pct" "LANDING MANA DAMAGE (%):" +"dota_tooltip_ability_ability_mirana_leap_custom_leap_damage_reduction_pct" "IN-AIR DAMAGE REDUCTION (%):" +"dota_hud_error_mirana_leap_in_air" "Cannot leap again until you land." +"dota_tooltip_ability_ability_mirana_leap_custom_innate_damage_multiplier" "INNATE DAMAGE MULTIPLIER:" +"dota_tooltip_ability_ability_mirana_leap_custom_max_charges" "CHARGES:" +"dota_tooltip_ability_ability_mirana_leap_custom_AbilityChargeRestoreTime" "CHARGE RESTORE:" +"dota_tooltip_ability_ability_mirana_leap_custom_leap_distance" "LEAP DISTANCE:" +"dota_tooltip_ability_ability_mirana_leap_custom_leap_speed" "LEAP SPEED:" +"dota_tooltip_ability_ability_mirana_leap_custom_leap_height" "ARC HEIGHT:" +"dota_tooltip_ability_ability_mirana_leap_custom_leap_speedbonus" "MOVEMENT SPEED:" +"dota_tooltip_ability_ability_mirana_leap_custom_leap_speedbonus_as" "ATTACK SPEED:" +"dota_tooltip_ability_ability_mirana_leap_custom_leap_bonus_duration" "BUFF DURATION:" +"dota_tooltip_ability_ability_mirana_leap_custom_Lore" "She does not flee — she chooses the angle of her next shot." + +"dota_tooltip_ability_ability_mirana_innate_custom" "Celestial Quiver" +"dota_tooltip_ability_ability_mirana_innate_custom_Description" "Innate: ranged attacks deal bonus magic damage equal to %mana_damage_pct%%% of max mana. While Leap buff is active, innate damage is increased 4×. While allied Luna is within %moon_sisters_radius%, Mirana and Luna take %moon_sisters_damage_reduction_pct%%% less incoming damage. Disabled by Break." +"dota_tooltip_ability_ability_mirana_innate_custom_mana_damage_pct" "MANA DAMAGE (%):" +"dota_tooltip_ability_ability_mirana_innate_custom_moon_sisters_radius" "LUNA LINK RADIUS:" +"dota_tooltip_ability_ability_mirana_innate_custom_moon_sisters_damage_reduction_pct" "INCOMING DAMAGE REDUCTION (SISTERS):" +"dota_tooltip_ability_ability_mirana_innate_custom_Lore" "Selene's quiver: each arrow holds a spark of sky-fire." + +"dota_tooltip_ability_ability_mirana_moonlight_shadow_custom" "Moonlight Shadow" +"dota_tooltip_ability_ability_mirana_moonlight_shadow_custom_Description" "For %duration% sec all allied heroes cannot die (health cannot drop below 1) and gain +%bonus_movement_speed%%% movement speed. Mirana also gains +%damage_bonus_base%%% outgoing damage plus +%damage_bonus_per_second%%% more for each second the effect lasts." +"dota_tooltip_ability_ability_mirana_moonlight_shadow_custom_duration" "DURATION:" +"dota_tooltip_ability_ability_mirana_moonlight_shadow_custom_bonus_movement_speed" "MOVEMENT SPEED:" +"dota_tooltip_ability_ability_mirana_moonlight_shadow_custom_damage_bonus_base" "BASE DAMAGE BONUS (MIRANA):" +"dota_tooltip_ability_ability_mirana_moonlight_shadow_custom_damage_bonus_per_second" "DAMAGE BONUS PER SECOND (MIRANA):" +"dota_tooltip_ability_ability_mirana_moonlight_shadow_custom_scepter_ally_damage_share_pct" "ALLY DAMAGE SHARE (%):" +"dota_tooltip_ability_ability_mirana_moonlight_shadow_custom_Scepter_Description" "Allies under Moonlight Shadow gain %scepter_ally_damage_share_pct%%% of Mirana's growing damage bonus." +"dota_tooltip_ability_ability_mirana_moonlight_shadow_custom_Lore" "Moonlight shields her own until the hunt is done." + +"dota_tooltip_ability_special_bonus_unique_mirana_starstorm_damage" "+{s:bonus_damage} Starstorm damage" +"dota_tooltip_ability_special_bonus_unique_mirana_starstorm_cooldown" "-{s:bonus_AbilityCooldown}s Starstorm cooldown" +"dota_tooltip_ability_special_bonus_unique_mirana_starstorm_attack_proc" "{s:bonus_attack_proc_chance}% chance (luck): attacks trigger Starstorm on the attacked enemy" +"dota_tooltip_ability_special_bonus_unique_mirana_sacred_arrow_side_arrows" "Sacred Arrow fires 2 additional arrows at 25° to the left and right of the main shot." +"dota_tooltip_ability_special_bonus_unique_mirana_sacred_arrow_damage" "+{s:bonus_arrow_damage} Sacred Arrow damage" +"dota_tooltip_ability_special_bonus_unique_mirana_leap_charge_time" "-{s:bonus_AbilityChargeRestoreTime}s Leap charge restore" +"dota_tooltip_ability_special_bonus_unique_mirana_leap_air_strike" "While leaping: fires a projectile at each enemy within her attack range around her (full attack on hit, once per leap per enemy)" +"dota_tooltip_ability_special_bonus_unique_mirana_leap_landing_radius" "+{s:bonus_leap_landing_radius} Leap landing damage radius" +"dota_tooltip_ability_special_bonus_unique_mirana_moonlight_duration" "+{s:bonus_duration}s Moonlight Shadow duration" +"dota_tooltip_ability_special_bonus_unique_mirana_innate_mana_damage" "+{s:bonus_mana_damage_pct}% mana-based damage (innate, Starstorm, Sacred Arrow)" + +"dota_tooltip_modifier_modifier_mirana_leap_custom_buff" "Leap" +"dota_tooltip_modifier_modifier_mirana_leap_custom_buff_Description" "Increased movement and attack speed. Celestial Quiver innate damage is 4× higher." + +"dota_tooltip_modifier_modifier_mirana_moonlight_shadow_custom" "Moonlight Shadow" +"dota_tooltip_modifier_modifier_mirana_moonlight_shadow_custom_Description" "Cannot die. Bonus movement speed under moonlight. Outgoing damage ramps over time (allies share part of Mirana's bonus with Aghanim's)." + +"dota_tooltip_modifier_modifier_luna_twin_glaives_flight" "Twin Glaives" +"dota_tooltip_modifier_modifier_luna_twin_glaives_flight_Description" "Glephs in flight to the target or back to the hero." + +"dota_tooltip_modifier_modifier_luna_moon_glaive_passive" "Moon Glaives" +"dota_tooltip_modifier_modifier_luna_moon_glaive_passive_Description" "Attacks can launch a chain of bounces on enemies near the target." + +"dota_tooltip_modifier_modifier_luna_twin_glaives_facet_vuln" "Glef wound" +"dota_tooltip_modifier_modifier_luna_twin_glaives_facet_vuln_Description" "Get increased physical and magical damage." + +"dota_tooltip_ability_special_bonus_unique_luna_lucent_beam_damage" "+{s:bonus_lucent_beam_damage} to Lucent Beam damage" +"dota_tooltip_ability_special_bonus_unique_luna_orbit_radius" "+{s:bonus_cast_range} to the range and flight of Twin Glaives." +"dota_tooltip_ability_special_bonus_unique_luna_glaive_extra_bounce" "+{s:bonus_moon_glaive_bounce_count} bounce Moon Glaives" +"dota_tooltip_ability_special_bonus_unique_luna_blessing_aura" "+{s:bonus_blessing_radius} to the radius of Lunar Blessing" +"dota_tooltip_ability_special_bonus_unique_luna_lucent_beam_cooldown" "-{s:bonus_AbilityCooldown} sec. recharges Lucent Beam" +"dota_tooltip_ability_special_bonus_unique_luna_orbit_damage" "+{s:bonus_glaive_damage} to Twin Glaives damage" +"dota_tooltip_ability_special_bonus_unique_luna_eclipse_power" "+{s:bonus_eclipse_beam_count} Eclipse rays" +"dota_tooltip_ability_special_bonus_unique_luna_glaives_no_falloff" "Moon Glaives: rebound damage not weakened ({s:bonus_bounce_falloff_pct}% of previous)" +"dota_tooltip_ability_special_bonus_unique_luna_moon_glaive_beam_attack" "After Lucent Beam or an Eclipse beam hits an enemy, Luna instantly attacks that target twice" + +// ----------------Pudge-------------------- +"dota_tooltip_ability_ability_pudge_meat_hook_custom" "Meat Hook" +"dota_tooltip_ability_ability_pudge_meat_hook_custom_Description" "Pudge throws a hook: %hook_damage% pure damage plus %max_health_damage_pct%%% of Pudge's max health on hit, then pulls enemies." +"dota_tooltip_ability_ability_pudge_meat_hook_custom_hook_damage" "DAMAGE:" +"dota_tooltip_ability_ability_pudge_meat_hook_custom_hook_distance" "HOOK RANGE:" +"dota_tooltip_ability_ability_pudge_meat_hook_custom_hook_speed" "HOOK SPEED:" +"dota_tooltip_ability_ability_pudge_meat_hook_custom_hook_width" "HOOK WIDTH:" +"dota_tooltip_ability_ability_pudge_meat_hook_custom_max_health_damage_pct" "% MAX HP ON HIT:" + +"dota_tooltip_ability_ability_pudge_rot_custom" "Rot" +"dota_tooltip_ability_ability_pudge_rot_custom_Description" "Pudge poisons the air: every %tick_interval% sec. enemies in %rot_radius% take magical damage (%rot_damage_per_sec% per sec. + %rot_damage_increase_per_sec% ramp, +%max_health_damage_pct%%% of Pudge max HP per sec.), are slowed, and Pudge takes self-damage." +"dota_tooltip_ability_ability_pudge_rot_custom_rot_radius" "RADIUS:" +"dota_tooltip_ability_ability_pudge_rot_custom_rot_damage_per_sec" "DAMAGE PER SECOND:" +"dota_tooltip_ability_ability_pudge_rot_custom_Scepter_Description" "Increases Rot radius by %scepter_bonus_radius% and reduces enemy health recovery within radius by %scepter_enemy_hp_regen_reduction_pct%%%." +"dota_tooltip_ability_ability_pudge_rot_custom_self_damage_pct_per_sec" "%SELF DAMAGE PER SECOND FROM MAX. HEALTH:" +"dota_tooltip_ability_ability_pudge_rot_custom_rot_slow_pct" "%SLOW:" +"dota_tooltip_ability_ability_pudge_rot_custom_scepter_enemy_hp_regen_reduction_pct" "%DECREASE IN ENEMY HEALTH RECOVERY:" +"dota_tooltip_ability_ability_pudge_rot_custom_rot_damage_increase_per_sec" "DAMAGE GROWTH PER SECOND:" +"dota_tooltip_ability_ability_pudge_rot_custom_max_health_damage_pct" "% MAX HP PER SEC:" + +"dota_tooltip_ability_ability_pudge_flesh_heap_custom" "Flesh Heap" +"dota_tooltip_ability_ability_pudge_flesh_heap_custom_Description" "Inherent passive ability. When the enemy dies under the influence of Dismember, Pooj receives a Flesh Heap stack. Each stack gives strength and resistance to magic." +"dota_tooltip_ability_ability_pudge_flesh_heap_custom_stack_range" "STACK SET RADIUS:" +"dota_tooltip_ability_ability_pudge_flesh_heap_custom_strength_per_stack" "STRENGTH FOR A STAKE:" +"dota_tooltip_ability_ability_pudge_flesh_heap_custom_magic_resist_per_stack" "%ADD. MAGICIAN. RESISTANCE FOR STACK:" + +"dota_tooltip_ability_ability_pudge_meat_shield_custom" "Meat Shield" +"dota_tooltip_ability_ability_pudge_meat_shield_custom_Description" "Pooja flesh constantly blocks incoming damage." +"dota_tooltip_ability_ability_pudge_meat_shield_custom_block_damage" "DAMAGE BLOCK:" +"dota_tooltip_ability_ability_pudge_meat_shield_custom_shard_block_per_strength" "BLOCK PER UNIT OF FORCE:" +"dota_tooltip_ability_ability_pudge_meat_shield_custom_Shard_Description" "Meat Shield additionally blocks %shard_block_per_strength% damage for each Pooja force unit." + +"dota_tooltip_ability_ability_pudge_dismember_custom" "Dismember" +"dota_tooltip_ability_ability_pudge_dismember_custom_Description" "Enemies within %width% radius are pulled to the center and take periodic magical damage. Damage per second: %damage_per_second% base, +%strength_damage_pct%%% of strength, +%max_health_damage_pct%%% of Pudge's max health. Heals for %heal_from_damage_pct%%% of damage dealt; +%hunger_bonus% hunger per tick." +"dota_tooltip_ability_ability_pudge_dismember_custom_damage_per_second" "BASE DAMAGE PER SECOND:" +"dota_tooltip_ability_ability_pudge_dismember_custom_strength_damage_pct" "% STR DAMAGE PER SEC:" +"dota_tooltip_ability_ability_pudge_dismember_custom_max_health_damage_pct" "% MAX HP DAMAGE PER SEC:" +"dota_tooltip_ability_ability_pudge_dismember_custom_heal_from_damage_pct" "% DAMAGE CURE:" +"dota_tooltip_ability_ability_pudge_dismember_custom_pull_speed" "CONTRACTION SPEED:" +"dota_tooltip_ability_ability_pudge_dismember_custom_pulse_interval" "TICK INTERVAL:" + +//pudge talents +"dota_tooltip_ability_special_bonus_unique_pudge_hook_range" "+{s:bonus_hook_distance} to Meat Hook range" +"dota_tooltip_ability_special_bonus_unique_pudge_hook_damage" "+{s:bonus_hook_damage} Meat Hook damage" +"dota_tooltip_ability_special_bonus_unique_pudge_rot_radius" "+{s:bonus_rot_radius} to the Rot radius" +"dota_tooltip_ability_special_bonus_unique_pudge_rot_damage" "+{s:bonus_rot_damage_per_sec} damage per second Rot" +"dota_tooltip_ability_special_bonus_unique_pudge_heap_range" "+{s:bonus_stack_range} to the Flesh Heap radius" +"dota_tooltip_ability_special_bonus_unique_pudge_heap_strength" "+{s:bonus_block_damage} to Meat Shield Damage Block" +"dota_tooltip_ability_special_bonus_unique_pudge_dismember_range" "+{s:bonus_cast_range} to the range of Dismember" +"dota_tooltip_ability_special_bonus_unique_pudge_dismember_damage" "+{s:bonus_damage_per_second} damage per second Dismember" + +//pudge modifiers +"dota_tooltip_modifier_pudge_meat_hook_bleed_custom" "Hook Bleeding" +"dota_tooltip_modifier_pudge_meat_hook_bleed_custom_Description" "The target bleeds and suffers periodic physical damage from the power of the Pooja." +"dota_tooltip_modifier_pudge_dismember_growth_custom" "Bloated butcher" +"dota_tooltip_modifier_pudge_dismember_growth_custom_Description" "During Dismember, Pudge's size is increased depending on his maximum health." +"dota_tooltip_modifier_pudge_rot_slow_custom" "Rot" +"dota_tooltip_modifier_pudge_rot_slow_custom_Description" "Movement speed is reduced, and with Aganim, Pudge's health recovery is reduced." +"dota_tooltip_modifier_pudge_dismember_custom" "Dismember" +"dota_tooltip_modifier_pudge_dismember_custom_Description" "The target is immobilized and suffers periodic damage." +"dota_tooltip_modifier_pudge_rot_custom" "Rot (aura)" +"dota_tooltip_modifier_pudge_rot_custom_Description" "Pudge spreads a poisonous aura around himself, inflicting periodic damage on enemies but losing health." +"dota_tooltip_modifier_pudge_meat_hook_pull" "Meat Hook" +"dota_tooltip_modifier_pudge_meat_hook_pull_Description" "The target is hooked and forcibly moved towards Pudge." +"dota_tooltip_modifier_pudge_flesh_heap_custom" "Flesh Heap" +"dota_tooltip_modifier_pudge_flesh_heap_custom_Description" "Continuous effect of bets: increases the power and resistance of Pooja magic." +"dota_tooltip_modifier_pudge_meat_shield_custom" "Meat Shield" +"dota_tooltip_modifier_pudge_meat_shield_custom_Description" "The pudge blocks some of the incoming damage and reflects the attacker’s damage from its own power." + +// -------------------- + +"DOTA_Tooltip_ability_ability_yuki_pohela" "Pohela" +"DOTA_Tooltip_ability_ability_yuki_pohela_Description" "Pohela concentrates its ice power by covering an ally with an ice crust, increasing its resistance to incoming damage, and slowing down the attack speed of enemies that attack it." +"DOTA_Tooltip_ability_ability_yuki_pohela_AbilityCastRange" "RADIUS:" +"DOTA_Tooltip_ability_ability_yuki_pohela_AbilityChannelTime" "DURATION OF APPLICATION:" +"DOTA_Tooltip_ability_ability_yuki_pohela_debuff_duration" "DEBUFF DURATION:" +"DOTA_Tooltip_ability_ability_yuki_pohela_bonus_resistance" "% DAMAGE RESIST:" +"DOTA_Tooltip_ability_ability_yuki_pohela_reduce_atack_speed" "REDUCE.ATTACK SPEED:" + +"DOTA_Tooltip_modifier_ability_pohela_buff" "Pohela buff" +"DOTA_Tooltip_modifier_ability_pohela_buff_Description" "Hero under reliable protection^^" + + +"DOTA_Tooltip_ability_ability_yuki_frostshtorm" "Frostshtorm" +"DOTA_Tooltip_ability_ability_yuki_frostshtorm_Description" "The hero desecrates an area with his ice, those entering it deal less damage, and allies receive an increase in damage of half from the decrease." +"DOTA_Tooltip_ability_ability_yuki_frostshtorm_AbilityCastRange" "APPLICATION RADIUS:" +"DOTA_Tooltip_ability_ability_yuki_frostshtorm_radius" "RADIUS" +"DOTA_Tooltip_ability_ability_yuki_frostshtorm_duration" "DURATION:" +"DOTA_Tooltip_ability_ability_yuki_frostshtorm_dmg_reduced" "REDUCE DAMAGE TO ENEMIES:" +"DOTA_Tooltip_ability_ability_yuki_frostshtorm_dmg_increese" "INCREASE DAMAGE TO ALLIES:" + +"DOTA_Tooltip_ability_ability_yuki_revenge" "Revenge" +"DOTA_Tooltip_ability_ability_yuki_revenge_Description" "Yuki-onna's aura chills enemies: they take magical damage each second equal to %mana_damage_pct%%% of her max mana plus %base_damage%; allies are healed for %heal% per second." +"DOTA_Tooltip_ability_ability_yuki_revenge_mana_damage_pct" "% OF MAX MANA TO DAMAGE PER SECOND:" +"DOTA_Tooltip_ability_ability_yuki_revenge_radius" "RADIUS" +"DOTA_Tooltip_ability_ability_yuki_revenge_heal" "TREATMENTS PER SECOND:" +"DOTA_Tooltip_ability_ability_yuki_revenge_base_damage" "BASE DAMAGE PER SECOND:" + +"DOTA_Tooltip_modifier_ability_revenge_buff" "Revenge Aura" +"DOTA_Tooltip_modifier_ability_revenge_buff_Description" "The cold warms you up." + + + + + +"DOTA_Tooltip_ability_ability_yuki_ritual" "Ritual" +"DOTA_Tooltip_ability_ability_yuki_ritual_Description" "The hero performs the Cold ritual, enlarging all armor and regenerating allied heroes in the area. Slows down enemy movement and attack speed." +"DOTA_Tooltip_ability_ability_yuki_ritual_AbilityCastRange" "RANGE OF APPLICATION:" +"DOTA_Tooltip_ability_ability_yuki_ritual_duration" "DURATION:" +"DOTA_Tooltip_ability_ability_yuki_ritual_bonus_armor" "%ADD.ARMOR:" +"DOTA_Tooltip_ability_ability_yuki_ritual_bonus_hp_regen" "%ADD.HEALTH RECOVERY:" +"DOTA_Tooltip_ability_ability_yuki_ritual_bonus_hp_regen_pct" "%HEALTH RECOVERY FROM MAX. HEALTH:" + + "DOTA_Tooltip_modifier_ability_ritual_buff" "Ritual buff" +"DOTA_Tooltip_modifier_ability_ritual_buff_Description" "Blessed by the cold." + + +"DOTA_Tooltip_ability_ability_yuki_snowman" "Snowman" +"DOTA_Tooltip_ability_ability_yuki_snowman_Description" "Yuki-onna puts a cute Snowman who throws snowballs at allies. Snowballs wither within the radius of all allied heroes and slow down the attack speed of enemy creeps." +"DOTA_Tooltip_ability_ability_yuki_snowman_bonus_health" "HEALTH:" +"DOTA_Tooltip_ability_ability_yuki_snowman_max_snowman" "MAX.SNOWMEN:" +"DOTA_Tooltip_ability_ability_yuki_snowmanl_slow_as" "ATTACK SPEED SLOWDOWN:" +"DOTA_Tooltip_ability_ability_yuki_snowball" "Snowball" +"DOTA_Tooltip_ability_ability_yuki_snowball_Description" "Snowball><" +"DOTA_Tooltip_ability_ability_yuki_snowball_radius" "RADIUS:" +"DOTA_Tooltip_ability_ability_yuki_snowball_heal" "TREATMENT:" +"DOTA_Tooltip_ability_ability_yuki_snowball_slow_as" "ATTACK SPEED SLOWDOWN:" + +"DOTA_Tooltip_ability_ability_yuki_challenge" "Challenge from yuki" +"DOTA_Tooltip_ability_ability_yuki_challenge_Description" "Yuki-onna tests an allied hero: for %duration% sec. applies mute, stun, disarm, and periodic pure damage based on primary attribute. If the hero survives, they permanently gain %bonus_main%%% primary attribute per completed trial. Each death during a trial reduces damage from future challenges by %dmg_reduce%%%; each success increases it by %dmg_incr%%%." +"DOTA_Tooltip_ability_ability_yuki_challenge_Scepter_Description" "Increases the main attribute by another %main_pct%" +"DOTA_Tooltip_ability_ability_yuki_challenge_Shard_Description" "Reduces damage per attribute" +"DOTA_Tooltip_ability_ability_yuki_challenge_duration" "DURATION:" +"DOTA_Tooltip_ability_ability_yuki_challenge_dmg_per_atr" "DAMAGE PER ATTRIBUTE:" +"DOTA_Tooltip_ability_ability_yuki_challenge_interval" "DAMAGE INTERVAL:" +"DOTA_Tooltip_ability_ability_yuki_challenge_bonus_main" "%bonus_main%%% TO PRIMARY ATTRIBUTE PER TRIAL:" +"DOTA_Tooltip_ability_ability_yuki_challenge_dmg_incr" "DAMAGE ENHANCEMENT:" +"DOTA_Tooltip_ability_ability_yuki_challenge_dmg_reduce" "REDUCE RECEIVED:" +"DOTA_Tooltip_ability_ability_yuki_challenge_Note0" "Total primary attribute granted is shown on the target's trial buff tooltip." + + +"DOTA_Tooltip_modifier_ability_challenge_buff" "Challenge from yuki" +"DOTA_Tooltip_modifier_ability_challenge_buff_Description" "You are a guilan." +"dota_tooltip_modifier_challenge_buff" "Yuki's Trial" +"dota_tooltip_modifier_challenge_buff_Description" "Total bonus to primary attribute: +%dMODIFIER_PROPERTY_TOOLTIP%." +"dota_tooltip_modifier_challenge_debuff" "Yuki's Trial" +"dota_tooltip_modifier_challenge_debuff_Description" "Under trial: disabled, no healing, and periodic pure damage." + + +"DOTA_Tooltip_ability_special_bonus_unique_yuki_pohela_resistance" "+{s:bonus_bonus_resistance}% to resistance from Pohela ability." +"DOTA_Tooltip_ability_special_bonus_unique_yuki_frostshtorm_duration" "+{s:bonus_duration} seconds to FrostShtorm duration." +"DOTA_Tooltip_ability_special_bonus_unique_yuki_revenge_damage" "+{s:bonus_base_damage} to BASE DAMAGE from Revenge ability" +"DOTA_Tooltip_ability_special_bonus_unique_yuki_revenge_heal" "+{s:bonus_heal} to treatment for the ability of Revenge." +"DOTA_Tooltip_ability_special_bonus_unique_yuki_snowman_max" "+{s:bonus_max_snowman} charge of snowman ability" + + +// ----------------------Sargatanas------------------- + + "DOTA_Tooltip_ability_ability_star_devour" "Hell devour" + "DOTA_Tooltip_ability_ability_star_devour_Description" "The hero can absorb a creep and get 1 stack ability Hell devour" + "DOTA_Tooltip_ability_ability_star_devour_creep_level" "MAXIMUM CREEP LEVEL:" + "DOTA_Tooltip_ability_ability_star_devour_max_stack" "MAXIMUM NUMBER OF STACKS:" + + + "DOTA_Tooltip_ability_ability_hellstep" "Hell step" + "DOTA_Tooltip_ability_ability_hellstep_Description" "The hero spews a stream of fire that burns the body of his opponents harder and harder with every second, and the ability also heal aliies increases the damage and Sargatanas himself within the radius of the ability." + "DOTA_Tooltip_ability_ability_hellstep_min_damage" "MIN.DAMAGE:" + "DOTA_Tooltip_ability_ability_hellstep_max_damage" "MAX. DAMAGE:" + "DOTA_Tooltip_ability_ability_hellstep_max_duration" "MAXIMUM DAMAGE SEC:" + "DOTA_Tooltip_ability_ability_hellstep_radius" "RADIUS:" + "DOTA_Tooltip_ability_ability_hellstep_duration" "DURATION" + "DOTA_Tooltip_ability_ability_hellstep_stack_overhell" "CALL-IN STACKS" + "DOTA_Tooltip_ability_ability_hellstep_bonus_damage" "BONUS DAMAGE:" + "DOTA_Tooltip_ability_ability_hellstep_bonus_attack_speed" "BONUS ATTACK SPEED:" + + "DOTA_Tooltip_ability_ability_firecleave_creep" "Fire cleave" + "DOTA_Tooltip_ability_ability_firecleave_creep_fire_damage" "FIRE DAMAGE:" + + "DOTA_Tooltip_ability_ability_firecleave" "Fire cleave" + "DOTA_Tooltip_ability_ability_firecleave_Description" "The hero, with unbridled power attacking one opponent, attacks the opponents behind and sets fire to the original target of the attack." + "DOTA_Tooltip_ability_ability_firecleave_great_cleave_damage" "%CUTTING DAMAGE:" + "DOTA_Tooltip_ability_ability_firecleave_fire_damage" "FIRE DAMAGE:" + "DOTA_Tooltip_ability_ability_firecleave_duration" "FIRE DURATION:" + + "DOTA_Tooltip_ability_ability_sunflame" "Sun flame" + "DOTA_Tooltip_ability_ability_sunflame_Description" "The hero spews streams of demonic fire that deals damage and beats up a debuff from overheating, increasing the damage from the fire" + "DOTA_Tooltip_ability_ability_sunflame_range" "RADIUS:" + "DOTA_Tooltip_ability_ability_sunflame_stack_overhell" "OVERHEATING STACKS:" + "DOTA_Tooltip_ability_ability_sunflame_damage" "DAMAGE:" + "DOTA_Tooltip_ability_ability_sunflame_unduced" "% DAMAGE REDUCTION:" + "DOTA_Tooltip_ability_ability_sunflame_damage_meta" "DAMAGE UNDER ULTRA:" + "DOTA_Tooltip_ability_ability_sunflame_unduced_meta" "% DAMAGE REDUCTION:" + + "DOTA_Tooltip_ability_ability_hell_summon" "Hell summon" + "DOTA_Tooltip_ability_ability_hell_summon_Description" "Damage depends on character characteristics. Strength – hp and damage. The player can control the summoned creeps." +"DOTA_Tooltip_ability_ability_hell_summon_str_dmg" "DAMAGE FOR 1 FORCE" +"DOTA_Tooltip_ability_ability_hell_summon_str_hp" "HP FOR 1 STRENGTH:" + + +"DOTA_Tooltip_ability_ability_metamorphosis" "Revolve metamorphosis" +"DOTA_Tooltip_ability_ability_metamorphosis_Description" "The demon puts on its true form, strengthening its skils and taking less damage (the percentage of damage block depends on the strength of the daemon, but must be limited to some value)." +"DOTA_Tooltip_ability_ability_metamorphosis_duration" "DURATION:" +"DOTA_Tooltip_ability_ability_metamorphosis_bonus_resistance" "STRENGTH DAMAGE BONUS RESIST:" +"DOTA_Tooltip_ability_ability_metamorphosis_max_resistance" "MAXIMUM DAMAGE RESIST:" +"DOTA_Tooltip_ability_ability_metamorphosis_radius" "RADIUS:" +"DOTA_Tooltip_ability_ability_metamorphosis_armor_ag" "ARMOR FOR AGUILA:" +"DOTA_Tooltip_ability_ability_metamorphosis_attack_speed_ag" "ATTACK SPEED PER AGILITY:" + +"DOTA_Tooltip_Ability_special_bonus_unique_sargatanas_max_stack" "+{s:bonus_max_stack} to the number of stacks of Devour ability." + +"DOTA_Tooltip_Ability_special_bonus_unique_sargatanas_str_dmg" "+{s:bonus_str_dmg} damage called creatures of the hell summon ability." + +"DOTA_Tooltip_Ability_special_bonus_unique_sargatanas_duration_1" "+{s:bonus_duration} hellstep ability duration" + + + +// -----------------Rubick-------------------- + +"DOTA_Tooltip_ability_ability_rubick_telekinesis_custom" "telekinesis" +"DOTA_Tooltip_ability_ability_rubick_telekinesis_custom_Description" "Rubik lifts target into the air. Enemies upon landing receive damage and stunning within a radius. Allies can navigate through the air, receive treatment, a bonus to attack range, attack speed, and magical damage." +"DOTA_Tooltip_ability_ability_rubick_telekinesis_custom_Scepter_Description" "Can be maintained on Rubick and allies indefinitely. The target's airborne position updates every 2 seconds. The effect ends immediately if the target leaves the allowed area." +"DOTA_Tooltip_ability_ability_rubick_telekinesis_custom_AbilityCastRange" "RANGE:" +"DOTA_Tooltip_ability_ability_rubick_telekinesis_custom_land_damage" "LANDING DAMAGE:" +"DOTA_Tooltip_ability_ability_rubick_telekinesis_custom_land_stun_duration" "DURATION OF STUNNING:" +"DOTA_Tooltip_ability_ability_rubick_telekinesis_custom_land_stun_radius" "RADIUS:" +"DOTA_Tooltip_ability_ability_rubick_telekinesis_custom_throw_distance" "THROWING RANGE:" +"DOTA_Tooltip_ability_ability_rubick_telekinesis_custom_ally_heal_per_second" "TREATMENT PER SECOND:" +"DOTA_Tooltip_ability_ability_rubick_telekinesis_custom_ally_attack_range_bonus" "ADD. ATTACK RANGES:" +"DOTA_Tooltip_ability_ability_rubick_telekinesis_custom_ally_attack_speed_bonus" "ext. ATTACK SPEEDS:" +"DOTA_Tooltip_ability_ability_rubick_telekinesis_custom_ally_bonus_magic_damage" "ext. MAGICIAN. DAMAGE:" +"DOTA_Tooltip_ability_ability_rubick_telekinesis_custom_ally_bonus_magic_damage_per_int" "MAGICIAN. DAMAGE TO INTELLIGENCE:" +"DOTA_Tooltip_ability_ability_rubick_telekinesis_custom_Lore" "When the world seems constrained by the laws of gravity, only a true arcana master is able to lift a prisoner upward and control his fate. Rubik's favorite trick — to disrupt the usual flow of magic, turning chaos into the art of telekinetic control." +"dota_hud_error_rubick_telekinesis_boss" "Cannot be cast on bosses." +"dota_hud_error_rubick_telekinesis_homer" "Cannot be cast on the village chief." + +"DOTA_Tooltip_ability_ability_rubick_fade_bolt_custom" "Fade Bolt" +"DOTA_Tooltip_ability_ability_rubick_fade_bolt_custom_Description" "Rubik creates a stream of arcane energy that jumps between enemies, dealing %damage% magic damage and reducing their attack damage by %attack_damage_reduction%%%. Each jump reduces the damage to the bolt by %jump_damage_reduction_pct%%%." +"DOTA_Tooltip_ability_ability_rubick_fade_bolt_custom_Shard_Description" "Allows Fade Bolt to strike a target again while the same chain is active." +"DOTA_Tooltip_ability_ability_rubick_fade_bolt_custom_AbilityCastRange" "RANGE:" +"DOTA_Tooltip_ability_ability_rubick_fade_bolt_custom_damage" "DAMAGE:" +"DOTA_Tooltip_ability_ability_rubick_fade_bolt_custom_radius" "JUMP RADIUS:" +"DOTA_Tooltip_ability_ability_rubick_fade_bolt_custom_attack_damage_reduction" "% ATTACK DAMAGE REDUCTION:" +"DOTA_Tooltip_ability_ability_rubick_fade_bolt_custom_jump_damage_reduction_pct" "% DAMAGE REDUCTION PER JUMP:" +"DOTA_Tooltip_ability_ability_rubick_fade_bolt_custom_duration" "DEBUFF DURATION:" +"DOTA_Tooltip_ability_ability_rubick_fade_bolt_custom_Lore" "Rubik's favorite spell to kill hitmen — simple but effective environment." + +"DOTA_Tooltip_ability_ability_rubick_arcane_supremacy" "Arcane supremacy" +"DOTA_Tooltip_ability_ability_rubick_arcane_supremacy_Description" "Part of the magical damage that Rubik or allies within a radius deal to enemies is distributed to all other enemies within a radius. Damage is divided equally and applied based on the percentage of ability." +"DOTA_Tooltip_ability_ability_rubick_arcane_supremacy_aura_radius" "RADIUS:" +"DOTA_Tooltip_ability_ability_rubick_arcane_supremacy_damage_pct" "% DAMAGE DISTRIBUTED:" +"DOTA_Tooltip_ability_ability_rubick_arcane_supremacy_spell_amp" "%BONUS SPELL DAMAGE:" +"DOTA_Tooltip_ability_ability_rubick_arcane_supremacy_spell_amp_pct_lvl" "%BONUS SPELL DAMAGE PER LEVEL:" +"DOTA_Tooltip_ability_ability_rubick_arcane_supremacy_Lore" "Understanding the most intimate mysteries of magic allowed Rubik not only to strengthen his spells, but also to distort the very essence of the magical damage around him. Enemies are better off staying away —after all, even if they miraculously resist, some of the destructive energy will still find them." + +"DOTA_Tooltip_ability_ability_rubick_spellsteal_custom" "Spell steel" +"DOTA_Tooltip_ability_ability_rubick_spellsteal_custom_Description" "Rubik steals the last used ally ability. The stolen ability replaces the current stolen ability and remains until the next theft." +"DOTA_Tooltip_ability_ability_rubick_spellsteal_custom_AbilityCastRange" "RANGE:" +"DOTA_Tooltip_ability_ability_rubick_spellsteal_custom_Lore" "A true magician does not create spells — he borrows them from those who are not careful enough. Rubik absorbs the enemy's magic, making it his own." + +"DOTA_Tooltip_ability_special_bonus_unique_rubick_telekinesis_stun" "+{s:bonus_land_stun_duration} sec to stunning when Telekinesis lands." +"DOTA_Tooltip_ability_special_bonus_unique_rubick_telekinesis_land_radius" "+{s:bonus_land_stun_radius} to Telekinesis landing radius" +"DOTA_Tooltip_ability_special_bonus_unique_rubick_fade_bolt_damage" "+{s:bonus_damage} damage Fade Bolt" +"DOTA_Tooltip_ability_special_bonus_unique_rubick_spellsteal_cooldown" "-{s:bonus_AbilityCooldown} Spell Steal recharge sec" +"DOTA_Tooltip_ability_special_bonus_unique_rubick_arcane_supremacy_damage_pct" "+{s:bonus_damage_pct}% Arcane Supremacy damage distributed" +"DOTA_Tooltip_ability_special_bonus_unique_rubick_telekinesis_charges" "+{s:bonus_AbilityCharges} Telekinesis charge" +"DOTA_Tooltip_ability_special_bonus_unique_rubick_fade_bolt_convert" "Fade bolt also gives a magical boost that depends on the reduced damage of this ability." + +// -----------------Skywrath Mage-------------------- + +"DOTA_Tooltip_ability_skywrath_mage_arcane_bolt_custom" "Arcane Bolt" +"DOTA_Tooltip_ability_skywrath_mage_arcane_bolt_custom_Description" "Skywrath Mage launches an arcane projectile that deals %bolt_damage% magical damage plus additional damage equal to %int_multiplier% of his Intelligence." +"DOTA_Tooltip_ability_skywrath_mage_arcane_bolt_custom_Shard_Description" "Grants all Skywrath Mage active abilities +1 charge." +"DOTA_Tooltip_ability_skywrath_mage_arcane_bolt_custom_Lore" "The scholars of the Skywrath court claim that perfect formulae strike truer than brute force ever could." +"DOTA_Tooltip_ability_skywrath_mage_arcane_bolt_custom_bolt_damage" "BASE DAMAGE:" +"DOTA_Tooltip_ability_skywrath_mage_arcane_bolt_custom_int_multiplier" "INTELLIGENCE MULTIPLIER:" +"DOTA_Tooltip_ability_skywrath_mage_arcane_bolt_custom_bolt_speed" "PROJECTILE SPEED:" +"DOTA_Tooltip_ability_skywrath_mage_arcane_bolt_custom_bolt_vision" "VISION RADIUS:" + +"DOTA_Tooltip_ability_skywrath_mage_concussive_shot_custom" "Concussive Shot" +"DOTA_Tooltip_ability_skywrath_mage_concussive_shot_custom_Description" "Releases a seeking missile toward the nearest enemy within %launch_radius%. On impact, it explodes in a %slow_radius% radius, dealing %damage% magical damage and slowing enemies for %slow_duration% sec." +"DOTA_Tooltip_ability_skywrath_mage_concussive_shot_custom_Shard_Description" "Grants all Skywrath Mage active abilities +1 charge." +"DOTA_Tooltip_ability_skywrath_mage_concussive_shot_custom_Lore" "When Dragonus commands the high winds of his realm, even the air itself delivers judgment." +"DOTA_Tooltip_ability_skywrath_mage_concussive_shot_custom_launch_radius" "TARGET SEARCH RADIUS:" +"DOTA_Tooltip_ability_skywrath_mage_concussive_shot_custom_slow_radius" "EXPLOSION RADIUS:" +"DOTA_Tooltip_ability_skywrath_mage_concussive_shot_custom_damage" "DAMAGE:" +"DOTA_Tooltip_ability_skywrath_mage_concussive_shot_custom_slow_duration" "SLOW DURATION:" +"DOTA_Tooltip_ability_skywrath_mage_concussive_shot_custom_slow" "%MOVEMENT SLOW:" +"DOTA_Tooltip_ability_skywrath_mage_concussive_shot_custom_int_multiplier" "INTELLIGENCE MULTIPLIER:" + +"DOTA_Tooltip_ability_skywrath_mage_ancient_seal_custom" "Ancient Seal" +"DOTA_Tooltip_ability_skywrath_mage_ancient_seal_custom_Description" "Affects all enemies in %radius% around the target, silencing for %seal_duration% sec. and reducing magic resistance by %resist_debuff%%%." +"DOTA_Tooltip_ability_skywrath_mage_ancient_seal_custom_Shard_Description" "Grants all Skywrath Mage active abilities +1 charge." +"DOTA_Tooltip_ability_skywrath_mage_ancient_seal_custom_Lore" "An edict etched in elder runes can snuff out hostile sorcery before it can take shape." +"DOTA_Tooltip_ability_skywrath_mage_ancient_seal_custom_seal_duration" "DURATION:" +"DOTA_Tooltip_ability_skywrath_mage_ancient_seal_custom_resist_debuff" "%MAGIC RESIST REDUCTION:" +"DOTA_Tooltip_ability_skywrath_mage_ancient_seal_custom_radius" "RADIUS:" +"DOTA_Tooltip_ability_skywrath_mage_ancient_seal_custom_int_multiplier" "INTELLIGENCE MULTIPLIER:" + +"DOTA_Tooltip_ability_skywrath_mage_mystic_flare_custom" "Mystic Flare" +"DOTA_Tooltip_ability_skywrath_mage_mystic_flare_custom_Description" "Conjures a mystical field for %duration% sec. that deals magical damage to all enemies in the area." +"DOTA_Tooltip_ability_skywrath_mage_mystic_flare_custom_Shard_Description" "Grants all Skywrath Mage active abilities +1 charge." +"DOTA_Tooltip_ability_skywrath_mage_mystic_flare_custom_Lore" "Where the heavens split open, arcane light descends as a merciless blade." +"DOTA_Tooltip_ability_skywrath_mage_mystic_flare_custom_radius" "RADIUS:" +"DOTA_Tooltip_ability_skywrath_mage_mystic_flare_custom_duration" "DURATION:" +"DOTA_Tooltip_ability_skywrath_mage_mystic_flare_custom_damage" "TOTAL DAMAGE:" +"DOTA_Tooltip_ability_skywrath_mage_mystic_flare_custom_int_multiplier" "INTELLIGENCE MULTIPLIER:" +"DOTA_Tooltip_ability_skywrath_mage_mystic_flare_custom_damage_interval" "TICK INTERVAL:" + +"DOTA_Tooltip_ability_skywrath_mage_innate_custom" "Ruin And Restoration" +"DOTA_Tooltip_ability_skywrath_mage_innate_custom_Description" "After casting any ability, Skywrath Mage launches up to %max_targets% Arcane Bolts at nearby enemies within %search_radius%. Each bolt deals %damage_pct%%% of Arcane Bolt damage." +"DOTA_Tooltip_ability_skywrath_mage_innate_custom_Lore" "To Dragonus, ruin and restoration are merely two verses of the same celestial law." +"DOTA_Tooltip_ability_skywrath_mage_innate_custom_search_radius" "SEARCH RADIUS:" +"DOTA_Tooltip_ability_skywrath_mage_innate_custom_max_targets" "TARGETS:" +"DOTA_Tooltip_ability_skywrath_mage_innate_custom_damage_pct" "% ARCANE BOLT DAMAGE:" + +"DOTA_Tooltip_ability_skywrath_mage_staff_of_the_scion_custom" "Staff of the Scion" +"DOTA_Tooltip_ability_skywrath_mage_staff_of_the_scion_custom_Description" "While holding Aghanim's Scepter, each ability has its own instant refresh chance chain:

50% -> 25% -> 12.5% -> 6.25%

Each ability tracks its own chain independently. The chain resets after %max_successes% successful procs or after %reset_duration% sec." +"DOTA_Tooltip_ability_skywrath_mage_staff_of_the_scion_custom_Lore" "This heirloom of the high house answers flawless spellwork with another chance to unleash it." +"DOTA_Tooltip_ability_skywrath_mage_staff_of_the_scion_custom_reset_duration" "RESET TIME:" +"DOTA_Tooltip_ability_skywrath_mage_staff_of_the_scion_custom_max_successes" "PROCS BEFORE RESET:" + +"DOTA_Tooltip_Ability_special_bonus_unique_skywrath_6" "+{s:bonus_AbilityCastRange} Arcane Bolt cast range" +"DOTA_Tooltip_Ability_special_bonus_unique_skywrath_concussive_shot_slow" "+{s:bonus_slow}% Concussive Shot slow" +"DOTA_Tooltip_Ability_special_bonus_unique_skywrath" "-{s:bonus_AbilityCooldown}s Ancient Seal cooldown" +"DOTA_Tooltip_Ability_special_bonus_unique_skywrath_2" "+{s:bonus_int_multiplier} Arcane Bolt intelligence multiplier" +"DOTA_Tooltip_Ability_special_bonus_unique_skywrath_4" "+{s:bonus_launch_radius} Concussive Shot search radius" +"DOTA_Tooltip_Ability_special_bonus_unique_skywrath_3" "{s:bonus_resist_debuff} Ancient Seal magic resistance reduction" +"DOTA_Tooltip_Ability_special_bonus_unique_skywrath_5" "+{s:bonus_damage} Mystic Flare damage" +"dota_tooltip_modifier_modifier_skywrath_mage_staff_of_the_scion_arcane_tracker" "Staff of the Scion: Arcane Bolt" +"dota_tooltip_modifier_modifier_skywrath_mage_staff_of_the_scion_arcane_tracker_Description" "Instant refresh chain for Arcane Bolt is active. The stack count shows successful procs before reset." +"dota_tooltip_modifier_modifier_skywrath_mage_staff_of_the_scion_concussive_tracker" "Staff of the Scion: Concussive Shot" +"dota_tooltip_modifier_modifier_skywrath_mage_staff_of_the_scion_concussive_tracker_Description" "Instant refresh chain for Concussive Shot is active. The stack count shows successful procs before reset." +"dota_tooltip_modifier_modifier_skywrath_mage_staff_of_the_scion_seal_tracker" "Staff of the Scion: Ancient Seal" +"dota_tooltip_modifier_modifier_skywrath_mage_staff_of_the_scion_seal_tracker_Description" "Instant refresh chain for Ancient Seal is active. The stack count shows successful procs before reset." +"dota_tooltip_modifier_modifier_skywrath_mage_staff_of_the_scion_flare_tracker" "Staff of the Scion: Mystic Flare" +"dota_tooltip_modifier_modifier_skywrath_mage_staff_of_the_scion_flare_tracker_Description" "Instant refresh chain for Mystic Flare is active. The stack count shows successful procs before reset." + +// ------------------Elder dragon smaug-------------------- + +"DOTA_Tooltip_ability_ability_fire_punishment" "fire punishment" +"DOTA_Tooltip_ability_ability_fire_punishment_Description" "Smaug sets its target on fire at %AbilityDuration% seconds, dealing it periodic damage %damage% + %pct_dmg%%% of its damage, also reducing its magic resistance by %debuff_magic%%%." +"DOTA_Tooltip_ability_ability_fire_punishment_Lore" "Burns yes?" +"DOTA_Tooltip_ability_ability_fire_punishment_radius" "ARSON RADIUS:" + +"DOTA_Tooltip_ability_ability_jakiro_buff_1" "Fly to moon" +"DOTA_Tooltip_ability_ability_jakiro_buff_1_Description" "

Pasivnoe: Dragon fly

Smaug soars up, causing it to lose 25% maneuverability, but this can cause it to pass through the pimples." +"DOTA_Tooltip_ability_ability_jakiro_buff_1_Lore" "Winged" + +"DOTA_Tooltip_ability_ability_incandescent_fury" "incandescent fury" +"DOTA_Tooltip_ability_ability_incandescent_fury_Description" "Smaug spews lava causing damage to %damage% + %pct_dmg%%% of its damage to enemies at intervals of %burn_interval% sec." +"DOTA_Tooltip_ability_ability_incandescent_fury_Lore" "The worst death is to burn alive, but I don't need to worry about it - Smaug." +"DOTA_Tooltip_ability_ability_incandescent_fury_Scepter_Description" "Reduces recharge to %AbilityCooldown% seconds" +"DOTA_Tooltip_ability_ability_incandescent_fury_cast_range" "RADIUS:" +"DOTA_Tooltip_ability_ability_incandescent_fury_duration" "DURATION:" + +"DOTA_Tooltip_ability_ability_dragon_fear_aura" "dragon fear" +"DOTA_Tooltip_ability_ability_dragon_fear_aura_Description" "Enemies attacking Smaug cannot fight their own fear, which causes them to strike weaker, and Smaug himself receives additional magical damage." +"DOTA_Tooltip_ability_ability_dragon_fear_aura_Lore" "Smaug's scary aura doesn't even let them stop shaking." +"DOTA_Tooltip_ability_ability_dragon_fear_aura_outgoing" "%OUTGOING DAMAGE:" +"DOTA_Tooltip_ability_ability_dragon_fear_aura_incoming" "%INBOUND DAMAGE:" +"DOTA_Tooltip_ability_ability_dragon_fear_aura_duration" "DURATION:" + +"DOTA_Tooltip_ability_ability_dragon_gold_deal" "Dragon gold deal" +"DOTA_Tooltip_ability_ability_dragon_gold_deal_Description" "Smaug values its things very much and benefits more from them, receiving additional armor, damage and health." +"DOTA_Tooltip_ability_ability_dragon_gold_deal_Lore" "Smaug - gold is what makes you richer, but for me it is power." +"DOTA_Tooltip_ability_ability_dragon_gold_deal_gold" "GOLD QUANTITY FOR STACK:" +"DOTA_Tooltip_ability_ability_dragon_gold_deal_physical_armor" "%BONUS. ARMOR:" +"DOTA_Tooltip_ability_ability_dragon_gold_deal_spell_amp" "BONUS. MAGICIAN. DAMAGE:" +"DOTA_Tooltip_ability_ability_dragon_gold_deal_health_bonus" "BONUS. HEALTH:" +"DOTA_Tooltip_ability_modifier_dragon_gold_deal_buff" "Golden Madness" +"DOTA_Tooltip_ability_modifier_dragon_gold_deal_buff_Description" "Thanks to his Jewish inclinations, the hero receives a bonus: damage, health and armor." + +"DOTA_Tooltip_ability_ability_dragon_reward" "dragon reward" +"DOTA_Tooltip_ability_ability_dragon_reward_Description" "Smaug uses the souls of the dead for its own purposes." +"DOTA_Tooltip_ability_ability_dragon_reward_Lore" "Soul collection, here I have a fisherman, a hunter, and even a toad soul? Okay. It will do." +"DOTA_Tooltip_ability_ability_dragon_reward_max" "MAXIMUM SHOWER:" +"DOTA_Tooltip_ability_ability_dragon_reward_bonus_attributes" "ATTACHMENTS BONUS PER STACK:" +"DOTA_Tooltip_ability_modifier_dragon_reward" "Accumulated souls" +"DOTA_Tooltip_ability_modifier_dragon_reward_Description" "The hero received bonus attributes." + +"DOTA_Tooltip_ability_ability_dragon_scales" "Dragon scale" +"DOTA_Tooltip_ability_ability_dragon_scales_Description" "Smaug envelops his body in scales that allow him to avoid any damage and deflect it at the enemy." +"DOTA_Tooltip_ability_ability_dragon_scales_Lore" "You can forge impenetrable armor from this scale, but Smaug does not plan to share the scales." +"DOTA_Tooltip_ability_ability_dragon_scales_reflect" "%REFLECTION:" +"DOTA_Tooltip_ability_ability_dragon_scales_duration" "DURATION:" +"DOTA_Tooltip_ability_ability_dragon_scales_max_radius" "RADIUS:" +"DOTA_Tooltip_ability_modifier_dragon_scales" "Dragon Scale" +"DOTA_Tooltip_ability_modifier_dragon_scales_Description" "Your body has become stronger and has begun to reflect damage." + +"DOTA_Tooltip_ability_special_bonus_unique_smaug_dragon_scales_reflect" "+{s:bonus_reflect}% to reflection scale reflect" +"DOTA_Tooltip_ability_special_bonus_unique_smaug_dragon_scales_cd" "-{s:bonus_AbilityCooldown} seconds of recharging scale reflect" +"DOTA_Tooltip_ability_special_bonus_unique_smaug_dragon_gold_deal" "-{s:bonus_gold} gold to get dragon gold deal" +"DOTA_Tooltip_ability_special_bonus_unique_smaug_incandescent_fury" "+{s:bonus_damage} damage to the ability of incandescent fury." +"DOTA_Tooltip_ability_special_bonus_unique_smaug_fire_punishment" "+100% to fire punishment ability" + +// --------------------Nagash-------------------- + +"DOTA_Tooltip_ability_dark_friends" "dark friends" +"DOTA_Tooltip_ability_dark_friends_Description" "Calls on a dark ally for every level of ability. Each summoned unit gives an aura to allies within a radius of %aura_radius%. The effects of the aura are added to each summoned unit." +"DOTA_Tooltip_ability_dark_friends_Lore" "In the shadow of the ancient catacombs, where the souls of fallen warriors rest, Nagash learned to call upon faithful servants from the world of the dead. These dark friends, once great warriors, now serve him in eternal battle, carrying with them the strength and wisdom of past eras." +"DOTA_Tooltip_ability_dark_friends_Note0" "This creature gets its owner's crete modifiers." +"DOTA_Tooltip_ability_dark_friends_aura_bonus_damage" "ADD DAMAGE" +"DOTA_Tooltip_ability_dark_friends_aura_bonus_attack_speed" "ATTACK SPEED ADD" +"DOTA_Tooltip_ability_dark_friends_aura_bonus_movespeed_pct" "%MOVEMENT SPEED" +"DOTA_Tooltip_ability_dark_friends_aura_bonus_armor" "ADD ARMOR" + +"DOTA_Tooltip_modifier_dark_friends_create" "Dark friends beyond" +"DOTA_Tooltip_modifier_dark_friends_create_Description" "the hero receives %dMODIFIER_PROPERTY_PREATTACK_BONUS_DAMAGE% damage, %dMODIFIER_PROPERTY_ATTACKSPEED_BONUS_CONSTANT% attack speed, %dMODIFIER_PROPERTY_MOVESPEED_BONUS_PERCENTAGE%% movement speed and %dMODIFIER_PROPERTY_PHYSICAL_ARMOR_BONUS% armor." + +"DOTA_Tooltip_ability_world_destroyer" "world destroyer" +"DOTA_Tooltip_ability_world_destroyer_Description" "Nagash calls upon a destructive force from the depths of hell, creating an explosion at a specified point. The explosion rips out enemy souls and turns them to his side for %dominate_duration% seconds. Soul Eater also gains an additional 50% of the owner's health." +"DOTA_Tooltip_ability_world_destroyer_Lore" "When the world is bursting at the seams, Nagash knows how to use these cracks to his advantage." +"DOTA_Tooltip_ability_world_destroyer_Note0" "Can only tame creatures below its level." +"DOTA_Tooltip_ability_world_destroyer_radius" "RADIUS:" +"DOTA_Tooltip_ability_world_destroyer_health_soul_eater" "SHOWER SUCKER HEALTH:" +"DOTA_Tooltip_ability_world_destroyer_dominate_duration" "SHOWER DURATION:" +"DOTA_Tooltip_ability_world_destroyer_bonus_damage" "BLAST DAMAGE:" +"DOTA_Tooltip_ability_world_destroyer_bonus_attack_speed" "Add. ATTACK SPEED:" +"DOTA_Tooltip_ability_world_destroyer_bonus_armor" "Add. ARMOR:" +"DOTA_Tooltip_ability_world_destroyer_Shard_Description" "Nagash becomes invulnerable for %shard_invulnerable_duration% sec. when casting World Destroyer or Leader Call." + +"DOTA_Tooltip_ability_leader_call" "leader call" +"DOTA_Tooltip_ability_leader_call_Description" "Nagash donates part of his health to summon explosive power in his subdued souls. The explosion deals %explosion_damage% damage within %explosion_radius% and provides stat bonuses for every soul ever blown up or killed. When the hero dies, he loses %lost_souls_pct%%% of his accumulated souls." +"DOTA_Tooltip_ability_leader_call_Lore" "The leader must be willing to sacrifice everything to win." +"DOTA_Tooltip_ability_leader_call_explosion_radius" "BLAST RADIUS:" +"DOTA_Tooltip_ability_leader_call_explosion_damage" "BLAST DAMAGE:" +"DOTA_Tooltip_ability_leader_call_bonus_stats_per_soul" "SOUL PERFORMANCE BONUS:" + +"DOTA_Tooltip_modifier_leader_token" "Soul tokens" +"DOTA_Tooltip_modifier_leader_token_Description" "Nothing special, just the number of souls that the hero managed to swallow throughout the entire time." + +"DOTA_Tooltip_ability_fighting_up" "fighting up" +"DOTA_Tooltip_ability_fighting_up_Description" "Nagash goes into a fighting rage, increasing damage, movement speed and attack speed for all allies within a %radius% radius by %duration% seconds." +"DOTA_Tooltip_ability_fighting_up_Lore" "There is no room for doubt in battle - only rage and determination." +"DOTA_Tooltip_ability_fighting_up_radius" "RADIUS:" +"DOTA_Tooltip_ability_fighting_up_duration" "DURATION:" +"DOTA_Tooltip_ability_fighting_up_bonus_damage_pct" "%BONUS. DAMAGE:" +"DOTA_Tooltip_ability_fighting_up_bonus_movespeed_pct" "%BONUS. MOVEMENT SPEEDS:" +"DOTA_Tooltip_ability_fighting_up_bonus_attackspeed_pct" "%BONUS. ATTACK SPEEDS:" + +"DOTA_Tooltip_ability_war_for_life" "war for life" +"DOTA_Tooltip_ability_war_for_life_Description" "Nagash declares war on death, resurrecting all fallen allies within %radius% radius with %health_res_pct%% health and granting %inc_damage_res%% damage resistance for %duration% sec." +"DOTA_Tooltip_ability_war_for_life_Lore" "Life is the greatest battle, and Nagash is ready to fight to the end." +"DOTA_Tooltip_ability_war_for_life_radius" "RADIUS:" +"DOTA_Tooltip_ability_war_for_life_health_res_pct" "%HEALTH OF THE RESURRECTED:" +"DOTA_Tooltip_ability_war_for_life_inc_damage_res" "% DAMAGE RESISTANCE:" +"DOTA_Tooltip_ability_war_for_life_duration" "DURATION:" + +"DOTA_Tooltip_ability_dark_golem" "dark golem" +"DOTA_Tooltip_ability_dark_golem_Description" "Nagash becomes a powerful dark golem that attacks with a cut at %cleave_damage%% damage within %cleave_distance%. The golem gains damage bonuses, attack speed, and movement speed for each soul by %duration% of a second." +"DOTA_Tooltip_ability_dark_golem_Lore" "From the depths of hell comes the greatest servant of darkness - a golem forged from the souls of fallen warriors." +"DOTA_Tooltip_ability_dark_golem_cleave_damage" "%DRIFT DAMAGE:" +"DOTA_Tooltip_ability_dark_golem_cleave_distance" "CUTTING DISTANCE:" +"DOTA_Tooltip_ability_dark_golem_cleave_starting_width" "INITIAL CUTTING WIDTH:" +"DOTA_Tooltip_ability_dark_golem_cleave_ending_width" "FINAL CUTTING WIDTH:" +"DOTA_Tooltip_ability_dark_golem_bonus_damage_pct_per_token" "% SOUL DAMAGE:" +"DOTA_Tooltip_ability_dark_golem_bonus_attack_speed_pct_per_token" "% ATTACK SPEED PER SOUL:" +"DOTA_Tooltip_ability_dark_golem_bonus_movespeed_pct_per_token" "% MOVEMENT SPEED PER SHOWER:" +"DOTA_Tooltip_ability_dark_golem_duration" "DURATION:" + +"DOTA_Tooltip_modifier_dark_golem_buff" "Dark golem" +"DOTA_Tooltip_modifier_dark_golem_buff_Description" "You can't be stopped." + +"DOTA_Tooltip_ability_special_bonus_unique_nagash_leader_call_damage" "+{s:bonus_unit_health_for_damage}% damage from the maximum health of the recruited creature leader call." +"DOTA_Tooltip_ability_special_bonus_unique_nagash_dark_friend_damage_pct" "+{s:bonus_outgoing_damage_pct}% damage to dark friends creatures." + + + + + +// --------------------Blood hunter-------------------- + +"DOTA_Tooltip_ability_ability_bloodrage" "Blood rage" +"DOTA_Tooltip_ability_ability_bloodrage_Description" "Blood Hunter tears his victim apart, absorbing his blood and receiving glasses in the tart %stack% on %stack_duration% seconds. For each stake, the hero receives bonus damage and attack speed." +"DOTA_Tooltip_ability_ability_bloodrage_physical_vampirism" "%ADD.VAMPIRISM:" +"DOTA_Tooltip_ability_ability_bloodrage_Lore" "His claws are sharper than any blade." +"DOTA_Tooltip_ability_ability_bloodrage_bonus_damage" "ADD DAMAGE:" +"DOTA_Tooltip_ability_ability_bloodrage_bonus_attack_speed" "ADD.ATTACK SPEED:" +"DOTA_Tooltip_ability_ability_bloodrage_stack_damage" "DAMAGE PER STACK:" +"DOTA_Tooltip_ability_ability_bloodrage_stack_attackspeed" "ATTACK SPEED PER STACK:" +"DOTA_Tooltip_ability_ability_bloodrage_duration" "DURATION:" +"DOTA_Tooltip_ability_ability_bloodrage_scepter_Description" "The hero receives %stack% stacks." + +"DOTA_Tooltip_ability_ability_bloodstained_memory" "bloodstained memory" +"DOTA_Tooltip_ability_ability_bloodstained_memory_Description" "The power is in the mind of your prejudice and Blood Hunter knows how to get this power out." +"DOTA_Tooltip_ability_ability_bloodstained_memory_healthbonus" "Add. HEALTH PER STACK:" +"DOTA_Tooltip_ability_ability_bloodstained_memory_movespeed" "REALITY PERCEPTION SPEED PER STACK:" +"DOTA_Tooltip_ability_ability_bloodstained_memory_Lore" "There is nothing stronger than my mind - Blood Hunter." + + + +"DOTA_Tooltip_ability_ability_illusion_of_blood" "illusion of blood" +"DOTA_Tooltip_ability_ability_illusion_of_blood_Description" "Blood Hunter seeks help from the dead by donating 15 glasses of blood, calling his copies into the world to protect himself." +"DOTA_Tooltip_ability_ability_illusion_of_blood_Lore" "There is nothing better than me - egoist Blood Hunter." +"DOTA_Tooltip_ability_ability_illusion_of_blood_images_count" "NUMBER OF ILLUSIONS:" +"DOTA_Tooltip_ability_ability_illusion_of_blood_blood" "BLOOD COST:" +"DOTA_Tooltip_ability_ability_illusion_of_blood_illusion_duration" "ILLUSION LIFETIME:" +"DOTA_Tooltip_ability_ability_illusion_of_blood_outgoing_damage" "%ILLUSION DAMAGE:" +"DOTA_Tooltip_ability_ability_illusion_of_blood_incoming_damage" "%DAMAGE BY ILLUSION:" +"DOTA_Tooltip_ability_ability_illusion_of_blood_magic_resistance" "%MAG.COMPR.ILLUSIONS:" + +"DOTA_Tooltip_ability_ability_sacrifice_revenge" "sacrifice revenge" +"DOTA_Tooltip_ability_ability_sacrifice_revenge_Description" "Blood Hunter unleashes his true essence and enters a furious state for %duration% sec." +"DOTA_Tooltip_ability_ability_sacrifice_revenge_Lore" "You always have to sacrifice something, Blood Hunter knew it from birth." +"DOTA_Tooltip_ability_ability_sacrifice_revenge_base_attack_time" "ATTACK BASE INTERVAL:" +"DOTA_Tooltip_ability_ability_sacrifice_revenge_bonus_armor" "ARMOR MULTIPLIER:" +"DOTA_Tooltip_ability_ability_sacrifice_revenge_shard_Description" "Aptitude also deals %pure_damage%% net damage" + +"DOTA_Tooltip_ability_ability_ring_of_corrosive" "ring of corrosive" +"DOTA_Tooltip_ability_ability_ring_of_corrosive_Description" "Blood Hunter performs a blood ritual, creating a blood circle for %duration% sec. Enemies inside take pure damage every %tick_rate% sec equal to %damage% + %damage_mult%%% of their damage." +"DOTA_Tooltip_ability_ability_ring_of_corrosive_Lore" "A harmless circle that you’d better not walk in." +"DOTA_Tooltip_ability_ability_ring_of_corrosive_radius" "RADIUS:" + + + +"DOTA_Tooltip_ability_special_bonus_unique_blood_hunter_bloodstained_health" "+{s:bonus_healthbonus} bonus health bloodstained memory" +"DOTA_Tooltip_ability_special_bonus_unique_blood_hunter_duration_blood" "+{s:bonus_duration} seconds duration bloodrage" +"DOTA_Tooltip_ability_special_bonus_unique_blood_hunter_blood_of_illusions_count" "+{s:bonus_images_count} the number of illusions caused." +"DOTA_Tooltip_ability_special_bonus_unique_blood_hunter_blood_of_illusions_incoming_damage" "-{s:bonus_incoming_damage}% incoming illusion damage" +"DOTA_Tooltip_ability_special_bonus_unique_blood_hunter_pure_damage_bonus" "+{s:bonus_pure_damage}% net damage sacrifice revenge" +"DOTA_Tooltip_ability_special_bonus_unique_blood_hunter_sacrifice_revenge_duration" "+{s:bonus_duration} to the duration of sacrifice revenge" + + +"DOTA_Tooltip_modifier_bloodrage_buff" "Bloodrage" +"DOTA_Tooltip_modifier_bloodrage_buff_Description" "You feel like an animal." +"DOTA_Tooltip_modifier_sacrifice_revenge_buff" "Madness" +"DOTA_Tooltip_modifier_sacrifice_revenge_buff_Description" "In its true form" +"DOTA_Tooltip_modifier_bloodstained_memory" "Bloodstained Memory" +"DOTA_Tooltip_modifier_bloodstained_memory_Description" "Your mind is faster than the speed of light." + +// --------------------Sniper-------------------- + +"DOTA_Tooltip_ability_ability_sniper_shrapnel_custom" "Shrapnel" +"DOTA_Tooltip_ability_ability_sniper_shrapnel_custom_Description" "Fire into the ground: after %damage_delay% seconds a shrapnel cloud appears with radius %radius% for %duration% seconds. Enemies inside every %tick_interval% sec. take physical damage equal to %attack_damage_pct%%% of Sniper's attack, are slowed by %slow_movement_speed%%% movement speed, and take %incoming_damage_pct%%% increased damage." +"DOTA_Tooltip_ability_ability_sniper_shrapnel_custom_Lore" "Kardel shares the scrap with his guests." +"DOTA_Tooltip_ability_ability_sniper_shrapnel_custom_radius" "RADIUS:" +"DOTA_Tooltip_ability_ability_sniper_shrapnel_custom_damage_delay" "DELAY BEFORE ZONE:" +"DOTA_Tooltip_ability_ability_sniper_shrapnel_custom_duration" "ZONE DURATION:" +"DOTA_Tooltip_ability_ability_sniper_shrapnel_custom_attack_damage_pct" "% ATTACK DAMAGE PER TICK:" +"DOTA_Tooltip_ability_ability_sniper_shrapnel_custom_tick_interval" "DAMAGE TICK INTERVAL:" +"DOTA_Tooltip_ability_ability_sniper_shrapnel_custom_slow_movement_speed" "%MOVE SLOW:" +"DOTA_Tooltip_ability_ability_sniper_shrapnel_custom_incoming_damage_pct" "%INCREASED INCOMING DAMAGE:" + +"DOTA_Tooltip_ability_ability_sniper_critical_focus_custom" "Critical Focus" +"DOTA_Tooltip_ability_ability_sniper_critical_focus_custom_Description" "For %duration% seconds every attack is a critical strike that combines all crit sources; also adds %crit_chance%%% chance and %crit_mult%%% multiplier to the crit pool." +"DOTA_Tooltip_ability_ability_sniper_critical_focus_custom_Lore" "For a moment, only bullseyes." +"DOTA_Tooltip_ability_ability_sniper_critical_focus_custom_duration" "DURATION:" +"DOTA_Tooltip_ability_ability_sniper_critical_focus_custom_crit_chance" "%BONUS CRIT CHANCE:" +"DOTA_Tooltip_ability_ability_sniper_critical_focus_custom_crit_mult" "%BONUS CRIT MULTIPLIER:" + +"DOTA_Tooltip_ability_ability_sniper_keen_eye_custom" "Keen Eye" +"DOTA_Tooltip_ability_ability_sniper_keen_eye_custom_Description" "Passive: +%bonus_attack_range% attack range. %proc_chance%%% chance (with Luck) on hit to deal bonus physical damage %headshot_bonus_damage%, knock the target back, and for %slow_duration% seconds slow movement by %slow_movement_pct%%% and attack speed by %slow_attack_speed%. Knockback scales with distance; max knockback %knockback_distance%." +"DOTA_Tooltip_ability_ability_sniper_keen_eye_custom_Lore" "Does not blink." +"DOTA_Tooltip_ability_ability_sniper_keen_eye_custom_bonus_attack_range" "BONUS ATTACK RANGE:" +"DOTA_Tooltip_ability_ability_sniper_keen_eye_custom_proc_chance" "%HEADSHOT CHANCE:" +"DOTA_Tooltip_ability_ability_sniper_keen_eye_custom_headshot_bonus_damage" "HEADSHOT BONUS DAMAGE:" +"DOTA_Tooltip_ability_ability_sniper_keen_eye_custom_slow_duration" "SLOW DURATION:" +"DOTA_Tooltip_ability_ability_sniper_keen_eye_custom_knockback_distance" "KNOCKBACK (MAX):" +"DOTA_Tooltip_ability_ability_sniper_keen_eye_custom_slow_movement_pct" "%MOVE SLOW:" +"DOTA_Tooltip_ability_ability_sniper_keen_eye_custom_slow_attack_speed" "ATTACK SLOW:" + +"DOTA_Tooltip_ability_ability_sniper_assassinate_custom" "Assassinate" +"DOTA_Tooltip_ability_ability_sniper_assassinate_custom_Description" "During the aim phase, enemies within %aoe_radius% of the cursor are marked; then a projectile fires at each marked enemy (or at your target if the area was empty). On hit: stun, guaranteed crit, attack, bonus damage %attack_damage_bonus%, and an armor mark for %armor_mark_duration% seconds. Scepter: longer stun and lower cooldown." +"DOTA_Tooltip_ability_ability_sniper_assassinate_custom_scepter_Description" "Lower cooldown and longer stun." +"DOTA_Tooltip_ability_ability_sniper_assassinate_custom_Lore" "One shot, one less problem." +"DOTA_Tooltip_ability_ability_sniper_assassinate_custom_aoe_radius" "MARK RADIUS:" +"DOTA_Tooltip_ability_ability_sniper_assassinate_custom_debuff_duration" "MARK DURATION:" +"DOTA_Tooltip_ability_ability_sniper_assassinate_custom_projectile_speed" "PROJECTILE SPEED:" +"DOTA_Tooltip_ability_ability_sniper_assassinate_custom_ministun_duration" "MINISTUN:" +"DOTA_Tooltip_ability_ability_sniper_assassinate_custom_scepter_stun_duration" "STUN (SCEPTER):" +"DOTA_Tooltip_ability_ability_sniper_assassinate_custom_scepter_cooldown" "COOLDOWN (SCEPTER):" +"DOTA_Tooltip_ability_ability_sniper_assassinate_custom_attack_damage_bonus" "BONUS DAMAGE:" +"DOTA_Tooltip_ability_ability_sniper_assassinate_custom_armor_mark_duration" "ARMOR MARK DURATION:" + +"DOTA_Tooltip_modifier_sniper_critical_focus_active" "Critical Focus" +"DOTA_Tooltip_modifier_sniper_critical_focus_active_Description" "Every attack crits and merges all crit sources." + +"DOTA_Tooltip_modifier_sniper_assassinate_mark" "Assassinate" +"DOTA_Tooltip_modifier_sniper_assassinate_mark_Description" "Armor is reduced by about half for penetration." + +"dota_tooltip_ability_special_bonus_unique_sniper_shrapnel_radius" "+150 Shrapnel radius" +"dota_tooltip_ability_special_bonus_unique_sniper_keen_range" "+75 attack range from Keen Eye" +"dota_tooltip_ability_special_bonus_unique_sniper_shrapnel_cooldown" "-3s Shrapnel cooldown" +"dota_tooltip_ability_special_bonus_unique_sniper_critical_focus_duration" "+3s Critical Focus duration" +"dota_tooltip_ability_special_bonus_unique_sniper_critical_focus_crit_mult" "+40 Critical Focus crit multiplier" +"dota_tooltip_ability_special_bonus_unique_sniper_keen_proc" "+12% Keen Eye headshot chance" +"dota_tooltip_ability_special_bonus_unique_sniper_assassinate_aoe" "+250 Assassinate mark radius" +"dota_tooltip_ability_special_bonus_unique_sniper_assassinate_damage" "+50 Assassinate bonus damage" + +// --------------------Hoodwink-------------------- + +"DOTA_Tooltip_ability_hoodwink_acorn_shot_custom" "Acorn Shot" +"DOTA_Tooltip_ability_hoodwink_acorn_shot_custom_Description" "Hoodwink fires a special acorn attack that bounces to nearby enemies, slows them, and deals base damage plus a percentage of her attack damage.\nCan be cast on the ground to create a tree, then the acorn will bounce to nearby enemies." +"DOTA_Tooltip_ability_hoodwink_acorn_shot_custom_scepter_Description" "On normal attacks, Hoodwink has a chance to fire an acorn with a reduced number of bounces." +"DOTA_Tooltip_ability_hoodwink_acorn_shot_custom_Lore" "Hoodwink is not picky, but acorns from oak and ironwood make the best ammo - and she keeps a lot of them." +"DOTA_Tooltip_ability_hoodwink_acorn_shot_custom_bounce_range" "BOUNCE RADIUS:" +"DOTA_Tooltip_ability_hoodwink_acorn_shot_custom_bounce_count" "BOUNCES:" +"DOTA_Tooltip_ability_hoodwink_acorn_shot_custom_debuff_duration" "SLOW DURATION:" +"DOTA_Tooltip_ability_hoodwink_acorn_shot_custom_slow" "%MOVEMENT SLOW:" +"DOTA_Tooltip_ability_hoodwink_acorn_shot_custom_acorn_shot_damage" "BASE DAMAGE:" +"DOTA_Tooltip_ability_hoodwink_acorn_shot_custom_base_damage_pct" "%ATTACK DAMAGE AS BONUS:" +"DOTA_Tooltip_ability_hoodwink_acorn_shot_custom_bounce_count_scepter" "BOUNCES FROM ATTACK:" +"DOTA_Tooltip_ability_hoodwink_acorn_shot_custom_scepter_chance" "%CHANCE TO FIRE ACORN:" +"DOTA_Tooltip_ability_hoodwink_acorn_shot_custom_Note0" "The acorn can bounce to the same target multiple times." +"DOTA_Tooltip_ability_hoodwink_acorn_shot_custom_Note1" "The acorn cannot bounce to invisible units." +"DOTA_Tooltip_ability_hoodwink_acorn_shot_custom_Note2" "The tree lasts %tree_duration% seconds." + +"DOTA_Tooltip_ability_hoodwink_bushwhack_custom" "Bushwhack" +"DOTA_Tooltip_ability_hoodwink_bushwhack_custom_Description" "Hoodwink throws a trap that stuns enemies in the target area if there are trees nearby. Victims take damage, are pulled to the nearest tree, and lose vision while stunned." +"DOTA_Tooltip_ability_hoodwink_bushwhack_custom_Lore" "Hoodwink prefers setting ambushes in ironwood thickets, but the most cautious guests of the mistwood never let their guard down." +"DOTA_Tooltip_ability_hoodwink_bushwhack_custom_radius" "RADIUS:" +"DOTA_Tooltip_ability_hoodwink_bushwhack_custom_duration" "STUN DURATION:" +"DOTA_Tooltip_ability_hoodwink_bushwhack_custom_total_damage" "TOTAL DAMAGE:" + +"DOTA_Tooltip_ability_hoodwink_scurry_custom" "Scurry" +"DOTA_Tooltip_ability_hoodwink_scurry_custom_Description" "If Hoodwink is near a tree within %radius%, her power rises sharply: bonus damage, attack speed, and %incoming_damage_reduction_pct%%% reduced incoming damage. On activation she gains movement speed and keeps her bonuses even when no trees are nearby." +"DOTA_Tooltip_ability_hoodwink_scurry_custom_Lore" "There is no burrow or branch in Tomo'kan Grove that Hoodwink's paws cannot reach." +"DOTA_Tooltip_ability_hoodwink_scurry_custom_bonus_damage" "BONUS DAMAGE:" +"DOTA_Tooltip_ability_hoodwink_scurry_custom_bonus_attack_speed" "BONUS ATTACK SPEED:" +"DOTA_Tooltip_ability_hoodwink_scurry_custom_movement_speed_pct" "%BONUS MOVE SPEED:" +"DOTA_Tooltip_ability_hoodwink_scurry_custom_duration" "DURATION:" +"DOTA_Tooltip_ability_hoodwink_scurry_custom_luck_evasion_multiplier" "%EVASION PER 1 LUCK:" +"DOTA_Tooltip_ability_hoodwink_scurry_custom_max_luck_evasion" "%MAX EVASION:" +"DOTA_Tooltip_ability_hoodwink_scurry_custom_incoming_damage_reduction_pct" "%INCOMING DAMAGE REDUCTION:" + +"DOTA_Tooltip_ability_hoodwink_lucky_innate_custom" "Lucky Instinct" +"DOTA_Tooltip_ability_hoodwink_lucky_innate_custom_Description" "Innate ability. Hoodwink gains Luck for each hero level." +"DOTA_Tooltip_ability_hoodwink_lucky_innate_custom_luck_per_level" "LUCK PER LEVEL:" + +"DOTA_Tooltip_modifier_modifier_hoodwink_scurry_custom_buff" "Scurry" +"DOTA_Tooltip_modifier_modifier_hoodwink_scurry_custom_buff_Description" "Grants bonus attack speed and bonus damage, and reduces incoming damage. Evasion depends on the hero's current Luck. The current evasion value is shown in the modifier tooltip." + +"DOTA_Tooltip_ability_hoodwink_sharpshooter_custom" "Sharpshooter" +"DOTA_Tooltip_ability_hoodwink_sharpshooter_custom_Description" "Hoodwink prepares a deadly shot with her crossbow. The shot pierces all enemies, slows them, and applies a weakening debuff. Damage and debuff duration reach maximum after %max_charge_time% sec of charging, and the shot is fired automatically after %misfire_time% sec.\nRecoil pushes Hoodwink back by %recoil_distance%." +"DOTA_Tooltip_ability_hoodwink_sharpshooter_custom_Lore" "Hoodwink found her first auto-crossbow by accident, but now she cherishes her own one - and of course, some parts were pinched from someone else." +"DOTA_Tooltip_ability_hoodwink_sharpshooter_custom_max_charge_time" "MAX CHARGE TIME:" +"DOTA_Tooltip_ability_hoodwink_sharpshooter_custom_max_damage" "MAX DAMAGE:" +"DOTA_Tooltip_ability_hoodwink_sharpshooter_custom_max_slow_debuff_duration" "MAX DEBUFF DURATION:" +"DOTA_Tooltip_ability_hoodwink_sharpshooter_custom_slow_move_pct" "%MOVEMENT SLOW:" +"DOTA_Tooltip_ability_hoodwink_sharpshooter_custom_arrow_speed" "PROJECTILE SPEED:" + +"DOTA_Tooltip_ability_hoodwink_sharpshooter_release_custom" "End Sharpshooter" +"DOTA_Tooltip_ability_hoodwink_sharpshooter_release_custom_Description" "Fire the shot and regain the ability to move and attack." +"DOTA_Tooltip_ability_hoodwink_sharpshooter_release_custom_Lore" "Fire!" + +"DOTA_Tooltip_ability_special_bonus_unique_hoodwink_scurry_charges" "+1 Scurry charge" +"DOTA_Tooltip_ability_special_bonus_unique_hoodwink_bushwhack_damage" "+120 Bushwhack damage" +"DOTA_Tooltip_ability_special_bonus_unique_hoodwink_1_1" "Acorn Shot can bounce off trees" +"DOTA_Tooltip_ability_special_bonus_unique_hoodwink_4_1" "Acorn Shot: -1s Sharpshooter cooldown" +"DOTA_Tooltip_ability_special_bonus_unique_hoodwink_acorn_shot_bounces" "+2 Acorn Shot bounces" +"DOTA_Tooltip_ability_special_bonus_unique_hoodwink_3_1" "+50% Scurry bonuses" +"DOTA_Tooltip_ability_special_bonus_unique_hoodwink_sharpshooter_damage" "+500 Sharpshooter damage" +"DOTA_Tooltip_ability_special_bonus_unique_hoodwink_scurry_duration" "+1s Scurry duration" + +//default items +"dota_tooltip_ability_item_mega_treads" "Reinforced leather boot" +"dota_tooltip_ability_item_mega_treads_bonus_stat" "+to main attribute" +"dota_tooltip_ability_item_mega_treads_bonus_other_stat" "+to other attributes" +"dota_tooltip_ability_item_mega_treads_bonus_movement_speed" "+$move_speed" +"dota_tooltip_ability_item_mega_treads_Description" "

Attribute Switching

Gives %bonus_stat% to the selected attribute and %bonus_other_stat% to all others. \n\n

Bonus of selected attribute:

Strength: +%bonus_magical_resistance%%% to magic resistance.

Intelligence: +%spell_amplify%% to spell damage.

Agility: +%bonus_attackspeed% to attack speed." +"dota_tooltip_ability_item_mega_treads_Lore" "These boots were created by a legendary blacksmith who wanted to outperform the regular power boots. He is said to have experimented with ancient magic and rare materials until he created this powerful artifact." +"dota_tooltip_ability_item_mega_treads_Note0" "Can be received multiple times." + +"dota_tooltip_ability_item_bloodstone_magical" "Magical Bloodstone" +"dota_tooltip_ability_item_bloodstone_magical_bonus_health" "+$health" +"dota_tooltip_ability_item_bloodstone_magical_bonus_mana" "+$mana" +"dota_tooltip_ability_item_bloodstone_magical_bonus_health_regen" "+$hp_regen" +"dota_tooltip_ability_item_bloodstone_magical_spell_lifesteal" "%+$spell_lifesteal" +"dota_tooltip_ability_item_bloodstone_magical_Description" "

Magical Stone

When activated, additionally gives %spell_lifesteal_active%% spell vampirism on %duration% seconds, as well as %cooldown_reduction_active%%% recharge reduction and %casttime_reduction%%% reduction in spell application time." +"dota_tooltip_ability_item_bloodstone_magical_Lore" "This stone was found in an ancient temple that was hidden underground. He is said to have magical powers that can heal and enhance." + +"dota_tooltip_modifier_item_bloodstone_magical_vampirism" "Blood weave" +"dota_tooltip_modifier_item_bloodstone_magical_vampirism_Description" "The hero received %dMODIFIER_PROPERTY_CASTTIME_PERCENTAGE%% reduction in spell application time and %dMODIFIER_PROPERTY_COOLDOWN_PERCENTAGE%% reduction in ability reloading." + +"dota_tooltip_ability_item_bearstbow" "Bearstbow" +"dota_tooltip_ability_item_bearstbow_attack_speed" "+$attack" +"dota_tooltip_ability_item_bearstbow_attack_range" "+$attack_range" +"dota_tooltip_ability_item_bearstbow_damage" "+$damage" +"dota_tooltip_ability_item_bearstbow_strength" "+$str" +"dota_tooltip_ability_item_bearstbow_agility" "+$agi" +"dota_tooltip_ability_item_bearstbow_intellect" "+$int" +"dota_tooltip_ability_item_bearstbow_health" "+$health" +"dota_tooltip_ability_item_bearstbow_Description" "

Bearstbow

Attacks additional %attack_count% of the target within its attack radius." +"dota_tooltip_ability_item_bearstbow_Lore" "A bow that was found in an ancient temple that was hidden underground. He is said to have magical powers that can break even a lion." + +"DOTA_Tooltip_ability_item_magical_quiver" "Magical quiver" +"DOTA_Tooltip_ability_item_magical_quiver_Description" "

Pasivnoe: Magic Arrows

ranged attacks deal an additional %proc_damage_magical% magic damage." +"DOTA_Tooltip_ability_item_magical_quiver_Lore" "A quiver filled with arrows soaked in ancient magic. Every shot from it carries a particle of destructive energy." + +"DOTA_Tooltip_ability_item_demonic_bow" "Demonic bow" +"DOTA_Tooltip_ability_item_demonic_bow_attack_speed" "+$attack" +"DOTA_Tooltip_ability_item_demonic_bow_attack_range" "+$attack_range" +"DOTA_Tooltip_ability_item_demonic_bow_damage" "+$damage" +"DOTA_Tooltip_ability_item_demonic_bow_strength" "+$str" +"DOTA_Tooltip_ability_item_demonic_bow_agility" "+$agi" +"DOTA_Tooltip_ability_item_demonic_bow_intellect" "+$int" +"DOTA_Tooltip_ability_item_demonic_bow_health" "+$health" +"DOTA_Tooltip_ability_item_demonic_bow_Description" "

Pasivnoe: Multi-shot

Ranging attacks hit up to %attack_count% of additional targets within attack radius. Each attack deals an additional %proc_damage_magical% magic damage. The hero's attacks cannot miss." +"DOTA_Tooltip_ability_item_demonic_bow_Lore" "Bow forged from the horns of fallen demons. His bowstring is stretched by the veins of an ancient monster, and the arrows bear a curse that burns the souls of enemies." + +"dota_tooltip_ability_item_armlet_of_eternal_hunger" "armlet of eternal hunger" +"dota_tooltip_ability_item_armlet_of_eternal_hunger_Description" "

Active: Eternal hunger

If enabled, hero becomes silent, increases damage by +%bonus_damage_active%, strength by +%bonus_str_active%, attack speed by %bonus_attack_speed_active% and movement speed by %movespeed_active%.
When enabled loses %health_per_sec% health per sec." +"dota_tooltip_ability_item_armlet_of_eternal_hunger_bonus_armor" "-to armor" +"dota_tooltip_ability_item_armlet_of_eternal_hunger_lifesteal" "%+$lifesteal" +"dota_tooltip_ability_item_armlet_of_eternal_hunger_bonus_damage" "+$damage" +"dota_tooltip_ability_item_armlet_of_eternal_hunger_bonus_attack_speed" "+$attack" +"dota_tooltip_ability_item_armlet_of_eternal_hunger_bonus_hp_regen" "+$hp_regen" +"dota_tooltip_ability_item_armlet_of_eternal_hunger_Lore" "The Armlet of Eternal Thirst was forged in the forgotten forges of ancient vampires, where blood and steel merged together. Its bearer feels an unquenchable thirst for life, capable of turning even mortal wounds into strength. But for every drop of power you have to pay with your own life — and only the most desperate heroes dare to put on this damned artifact." +"dota_tooltip_modifier_item_armlet_of_eternal_hunger_buff" "Armlet Of Eternal Hunger" +"dota_tooltip_modifier_item_armlet_of_eternal_hunger_buff_Description" "Damage increased by %dMODIFIER_PROPERTY_PREATTACK_BONUS_DAMAGE%. Strength increased by %dMODIFIER_PROPERTY_STATS_STRENGTH_BONUS%. Attack speed increased by %dMODIFIER_PROPERTY_ATTACKSPEED_BONUS_CONSTANT% and movement speed increased by %dMODIFIER_PROPERTY_MOVESPEED_BONUS_CONSTANT%. Loses 100 health every second." + +"dota_tooltip_ability_item_mini_bfury" "mini fury" +"dota_tooltip_ability_item_mini_bfury_bonus_damage" "+$damage" +"dota_tooltip_ability_item_mini_bfury_Description" "

Active: fury tree cutter

Destroys trees within a %tree% radius at a selected point. \n\n

Pasivnoe: fury beast

Hero attacks cut through the victim, inflicting an enemy within a %cleave_distance% radius around it %cleave_damage%%% physical damage (Melee only)" +"dota_tooltip_ability_item_mini_bfury_Lore" "One day a great Viking who died in a fierce battle left this ax on his grave, studying its sharpness people learned to make an analogue of its oldest weapon." + + +"dota_tooltip_ability_item_storm" "stun gun" +"dota_tooltip_ability_item_storm_bonus_damage" "+$damage" +"dota_tooltip_ability_item_storm_Description" "

Pasivnoe: Electro

When attacked with a chance of %chance%%, strikes the enemy with lightning: deals %bolt_damage% magic damage and stuns at %stun% sec. The effect works 3 times. Recharge: %ability_cooldown% sec." +"dota_tooltip_ability_item_storm_Lore" "This Taser was once part of an experimental weapon designed to hunt ancient monsters. Its handle is strewn with lightning marks, and inside is hidden a crystal that can accumulate and release destructive energy. It is said that each discharge of this device leaves on the enemy's body not only burns, but also the imprint of fear of the indomitable power of nature." + +"dota_tooltip_modifier_storm_dps" "Electrified" +"dota_tooltip_modifier_storm_dps_Description" "Gets %bolt_damage% magic damage and is stunned at %stun% sec each tick." + +"dota_tooltip_ability_item_balist_custom" "Electro Ballista" +"dota_tooltip_ability_item_balist_custom_bonus_damage" "+$damage" +"dota_tooltip_ability_item_balist_custom_crit_multiplier" "+$crit_multiplier" +"dota_tooltip_ability_item_balist_custom_crit_chance" "%+$crit_chance" +"dota_tooltip_ability_item_balist_custom_crit_mult" "%+$crit_mult" +"dota_tooltip_ability_item_balist_custom_Description" "

Passive: Electro

When attacked with a chance of %chance%%%, it strikes the main target with lightning 3 times. Each strike also moves to one new target within a %bolt_radius% radius (each additional target is hit only once). Deals damage to the owner + %bolt_damage% magic damage and stuns at %stun% sec. Recharge: %ability_cooldown% sec." +"dota_tooltip_ability_item_balist_custom_Lore" "An improved ballista that combines the power of a stun gun and dark crystallis. Its discharges not only paralyze the enemy, but also deal crushing blows. Designed for those who hunt the most dangerous creatures." +"dota_tooltip_ability_item_balist_custom_Note0" "Attack modifier works for lightning strikes." +"dota_tooltip_modifier_balist_custom_dps" "Electrified" +"dota_tooltip_modifier_balist_custom_dps_Description" "Gets %bolt_damage% magic damage and is stunned at %stun% sec each tick." + +"dota_tooltip_ability_item_battle_fury_custom" "Battle fury" +"dota_tooltip_ability_item_battle_fury_custom_bonus_damage" "+$damage" +"dota_tooltip_ability_item_battle_fury_custom_Description" "

Active: fury tree cutter

Destroys trees within %tree% at a selected point. \n\n

Pasivnoe: fury beast

Hero attacks cut through the victim, inflicting an enemy within a radius of %cleave_distance% around it %cleave_damage%%% physical damage (Melee only)" +"dota_tooltip_ability_item_battle_fury_custom_Lore" "One day a great Viking who died in a fierce battle left this ax on his grave, studying its sharpness people learned to make an analogue of its oldest weapon." + +"dota_tooltip_ability_item_mega_fury" "Mega fury" +"dota_tooltip_ability_item_mega_fury_bonus_damage" "+$damage" +"dota_tooltip_ability_item_mega_fury_attack_speed" "+$attack" +"dota_tooltip_ability_item_mega_fury_attack_range" "+$attack_range" +"dota_tooltip_ability_item_mega_fury_Description" "

Active: fury tree cutter

Destroys trees within a radius of %tree% at a selected point. \n\n

Passive: fury beast

Hero attacks cut through the victim, dealing %cleave_damage%%% physical damage to enemies within %cleave_distance% around the target (melee only). The hero's attacks cannot miss." +"dota_tooltip_ability_item_mega_fury_Lore" "One day a great Viking who died in a fierce battle left this ax on his grave, studying its sharpness people learned to make an analogue of its oldest weapon." + +//Masks of vampirism +"DOTA_Tooltip_ability_item_lifesteal_custom" "Morbid mask" +"DOTA_Tooltip_ability_item_lifesteal_custom_lifesteal" "%+$lifesteal" +"DOTA_Tooltip_ability_item_lifesteal_custom_Description" "

Pasive: Lifesteal

Cures the owner a share of the physical damage that his attacks cause." +"DOTA_Tooltip_ability_item_lifesteal_custom_Lore" "A mask that absorbs the energy of those who come into its sight." +"DOTA_Tooltip_modifier_modifier_item_lifesteal_custom" "Lifesteal" +"DOTA_Tooltip_modifier_modifier_item_lifesteal_custom_Description" "Heals the owner a share of the physical damage his attacks cause." + +"DOTA_Tooltip_ability_item_voodoo_mask_custom" "Voodoo mask" +"DOTA_Tooltip_ability_item_voodoo_mask_custom_lifesteal" "%+$spell_lifesteal_creep" +"DOTA_Tooltip_ability_item_voodoo_mask_custom_Description" "

Pasivnoe: Spell Lifesteal

Cures the owner a share of the magical damage he causes." +"DOTA_Tooltip_ability_item_voodoo_mask_custom_Lore" "A mask honed to absorb the magical bonds between a sorcerer and his enemy." +"DOTA_Tooltip_modifier_modifier_item_voodoo_mask_custom" "Spell Lifesteal" +"DOTA_Tooltip_modifier_modifier_item_voodoo_mask_custom_Description" "Heals the owner a share of the magical damage he causes." + +"DOTA_Tooltip_ability_item_mask_of_madness_custom" "Mask of madness" +"DOTA_Tooltip_ability_item_mask_of_madness_custom_lifesteal" "%+$lifesteal" +"DOTA_Tooltip_ability_item_mask_of_madness_custom_bonus_damage" "+$damage" +"DOTA_Tooltip_ability_item_mask_of_madness_custom_Description" "

Active: Berserk

Gives the owner %bonus_attackspeed_active% attack speed and %move_speed_active% movement speed, but reduces his armor by %armor_reduction_active% and prohibits him from using abilities. %duration% sec.\n\n

Pasive: Lifesteal

Cures the owner a share of the physical damage that his attacks cause." +"DOTA_Tooltip_ability_item_mask_of_madness_custom_Lore" "Wearing this mask, the warrior goes into unbridled rage." +"DOTA_Tooltip_modifier_modifier_item_mask_of_madness_custom" "Lifesteal" +"DOTA_Tooltip_modifier_modifier_item_mask_of_madness_custom_Description" "Heals the owner a share of the physical damage his attacks cause." +"DOTA_Tooltip_modifier_modifier_item_mask_of_madness_custom_buff" "Berserk" +"DOTA_Tooltip_modifier_modifier_item_mask_of_madness_custom_buff_Description" "Increases attack speed by %bonus_attackspeed_active% and movement by %move_speed_active%, but reduces armor by %armor_reduction_active% and disallows the use of abilities. Duration: %duration% sec." + +"DOTA_Tooltip_ability_item_vampire_claw" "Vampire Claw" +"DOTA_Tooltip_ability_item_vampire_claw_vampirism" "+$lifesteal" + "DOTA_Tooltip_ability_item_vampire_claw_Description" "

Active: Blood Rivers

Instantly restores %heal_per_charge% health for every charge stored. Gets %charges_for_attack_creep% charge per creep attack.

Maximum charges: %max_charges%" + "DOTA_Tooltip_ability_item_vampire_claw_bonus_damage" "+$damage" + "DOTA_Tooltip_ability_item_vampire_claw_bonus_str" "+$str" +"DOTA_Tooltip_ability_item_vampire_claw_Lore" "This claw once belonged to an ancient vampire whose thirst for blood was so great that he could heal his wounds by absorbing the life force of his enemies. It is said that every time the claw digs into the flesh, its owner feels a surge of strength and an unquenchable craving for a new battle. But with each healing, new blood veins appear on the blade, reminding: true strength requires sacrifice." + +"DOTA_Tooltip_ability_item_satanic_custom" "Satanic" +"DOTA_Tooltip_ability_item_satanic_custom_Description" "

Active: Unholy Rage

Increases attack lifesteal by %unholy_lifesteal%%% for %unholy_duration% sec. Costs %health_cost% health.\n\n

Passive: Lifesteal

Heals the wearer for a portion of physical damage dealt by their attacks." +"DOTA_Tooltip_ability_item_satanic_custom_Lore" "A powerful artifact filled with the blood of Roshan — a demon who fell in the First War." +"DOTA_Tooltip_ability_item_satanic_custom_bonus_strength" "+$str" +"DOTA_Tooltip_ability_item_satanic_custom_bonus_damage" "+$damage" +"DOTA_Tooltip_ability_item_satanic_custom_lifesteal" "%+$lifesteal" +"DOTA_Tooltip_modifier_item_satanic_custom_unholy" "Unholy Rage" +"DOTA_Tooltip_modifier_item_satanic_custom_unholy_Description" "Attack lifesteal is increased." + +"dota_tooltip_ability_item_king_fly" "Lepidoptera's Kiss" +"dota_tooltip_ability_item_king_fly_bonus_agility" "+$agi" +"dota_tooltip_ability_item_king_fly_bonus_evasion" "%+$evasion" +"dota_tooltip_ability_item_king_fly_bonus_attack_speed_pct" "%+$attack_pct" +"dota_tooltip_ability_item_king_fly_bonus_damage" "+$damage" +"dota_tooltip_ability_item_king_fly_Lore" "Blade forged from the essence of countless butterflies. Their iridescent wings and emerald pollen merged into weapons of ethereal beauty and deadly precision." + +"DOTA_Tooltip_ability_item_zombie_slayer" "Zombie slayer" +"DOTA_Tooltip_ability_item_zombie_slayer_bonus_damage" "+$damage" +"DOTA_Tooltip_ability_item_zombie_slayer_bonus_agility" "+$agi" +"DOTA_Tooltip_ability_item_zombie_slayer_bonus_health" "+$health" +"DOTA_Tooltip_ability_item_zombie_slayer_bonus_mana_regen" "+$mana_regen" +"DOTA_Tooltip_ability_item_zombie_slayer_Description" "

Pasivnoe: Corrosion

Attacks slow down target by %movespeed_reduction%%, reduce armor by %armor_reduction%, and reduce health recovery by %health_reduction%%%. Duration: %debuff_duration% sec. \n\n

Pasive: Undead execution

If the target — is undead, it takes %incress_damage%%% more damage. Duration: %debuff_duration% sec." +"DOTA_Tooltip_ability_item_zombie_slayer_Lore" "This blade once belonged to a hunter who single-handedly cleared entire lands of hordes of undead. Its steel is imbued with the malice of fallen zombies, and the handle is decorated with the teeth of the most dangerous of them. It is said that every blow with this weapon not only wounds the flesh, but also burns the remnants of dark magic in the enemy’s body. Those who wear Zombie Slayer become a nightmare for all creatures that have risen from the dead." + +"DOTA_Tooltip_ability_item_demon_slayer" "Demon Slayer" +"DOTA_Tooltip_ability_item_demon_slayer_bonus_damage" "+$damage" +"DOTA_Tooltip_ability_item_demon_slayer_bonus_agility" "+$agi" +"DOTA_Tooltip_ability_item_demon_slayer_bonus_health" "+$health" +"DOTA_Tooltip_ability_item_demon_slayer_bonus_mana_regen" "+$mana_regen" +"DOTA_Tooltip_ability_item_demon_slayer_bonus_health_regen" "+$hp_regen" +"DOTA_Tooltip_ability_item_demon_slayer_bonus_magic_resistance" "%+$spell_resist" +"DOTA_Tooltip_ability_item_demon_slayer_Description" "

Passive: Corrosion

Attacks slow the target by %movespeed_reduction%%%, reduce armor by %armor_reduction%, and reduce health restoration by %health_reduction%%%. Duration: %debuff_duration% sec.\n\n

Passive: Undead execution

If the target is undead (wave creeps), it takes %incress_damage%%% more damage. Duration: %debuff_duration% sec.\n\n

Passive: Mage Slayer

Attacks apply a debuff for %duration% sec.: the target loses %spell_amp_debuff%%% spell amplification and takes %dps% magical damage every second.\n\n

Passive: Demon pact

The bearer takes %incoming_damage_reduction%%% less damage and deals %outgoing_damage_bonus%%% more damage." +"DOTA_Tooltip_ability_item_demon_slayer_Lore" "A cursed union of the undead hunter's blade and a mage-killer's mantle: it drinks spellcraft, bleeds corpses dry, and turns its owner's wrath into a tide of ruin." + +"DOTA_Tooltip_ability_item_desolator_custom" "Desolator" +"DOTA_Tooltip_ability_item_desolator_custom_bonus_damage" "+$damage" +"DOTA_Tooltip_ability_item_desolator_custom_bonus_health" "+$health" +"DOTA_Tooltip_ability_item_desolator_custom_Description" "

Passive: Corruption

Attacks apply for %corruption_duration% sec.: %corruption_armor% armor, %corruption_movespeed_slow%%% movement slow, and %corruption_heal_reduction%%% reduced health regen (same rules as Zombie Slayer).\n\n

Passive: Hunter's Charges

Killing a unit under Corruption has a %chance_to_stack%%% chance to add +%bonus_damage_per_kill% charges (max %max_damage%). Each charge grants +1 damage and +1 health. Ranged attacks use Desolator's projectile." +"DOTA_Tooltip_ability_item_desolator_custom_Lore" "Forged to tear through faith in one's own armor. Every foe that dies under Corruption leaves another notch—until the steel forgets how to cool." +"DOTA_Tooltip_modifier_modifier_item_desolator_custom_debuff" "Corruption" +"DOTA_Tooltip_modifier_modifier_item_desolator_custom_debuff_Description" "Armor %corruption_armor%; movement speed %corruption_movespeed_slow%%% ; health regen reduced by %corruption_heal_reduction%%%." + +"DOTA_Tooltip_ability_item_desolator_custom_2" "Desolator II" +"DOTA_Tooltip_ability_item_desolator_custom_2_bonus_damage" "+$damage" +"DOTA_Tooltip_ability_item_desolator_custom_2_bonus_health" "+$health" +"DOTA_Tooltip_ability_item_desolator_custom_2_Description" "

Passive: Deepening Corruption

Hits apply/refresh for %corruption_duration% sec.: stacking armor (up to %corruption_armor_cap% total from stacks of −%corruption_armor_per_stack%), %corruption_movespeed_slow%%% movement slow, and %corruption_heal_reduction%%% reduced regen (Zombie Slayer style).\n\n

Passive: Hunter's Charges

Killing a unit under this Corruption has a %chance_to_stack%%% chance to add +%bonus_damage_per_kill% charges (max %max_damage%). Each charge: +1 damage and +1 health. Ranged attacks use Desolator's projectile." +"DOTA_Tooltip_ability_item_desolator_custom_2_Lore" "Same jagged doctrine—only the steel bites deeper into the idea of defense until stacks slam into the breaking point." +"DOTA_Tooltip_modifier_modifier_item_desolator_custom_2_debuff" "Deep Corruption" +"DOTA_Tooltip_modifier_modifier_item_desolator_custom_2_debuff_Description" "Armor reduced by min(stacks × %corruption_armor_per_stack%, %corruption_armor_cap%); movement %corruption_movespeed_slow%%% ; regen −%corruption_heal_reduction%%%." + +"DOTA_Tooltip_ability_item_poor_shield" "Stout Shield" +"DOTA_Tooltip_ability_item_poor_shield_Description" "

Passive: Damage Block

Grants a %damage_block_chance%%% chance when taking attack damage to block %damage_block% physical damage. On heroes, the final chance scales with Luck." +"DOTA_Tooltip_ability_item_poor_shield_Lore" "A battered plank and a scrap of iron—just enough to shrug off the first few swings. Don't expect Vanguard glamour." + +"DOTA_Tooltip_ability_item_warrior_shield" "Warrior's Shield" +"DOTA_Tooltip_ability_item_warrior_shield_bonus_health" "+$health" +"DOTA_Tooltip_ability_item_warrior_shield_bonus_health_regen" "+$hp_regen" +"DOTA_Tooltip_ability_item_warrior_shield_Description" "

Passive: Damage Block

Grants a %damage_block_chance%%% chance when taking attack damage to block %damage_block% physical damage. Melee heroes additionally block 50%% of their Strength. On heroes, the final chance scales with Luck." +"DOTA_Tooltip_ability_item_warrior_shield_Lore" "A reinforced rim and hide lining—soaks a hit and buys time while someone fishes for bandages." + +"dota_tooltip_ability_item_crystalys" "crystalys" +"dota_tooltip_ability_item_crystalys_bonus_damage" "+$damage" +"dota_tooltip_ability_item_crystalys_crit_chance" "%+$crit_chance" +"dota_tooltip_ability_item_crystalys_crit_mult" "%+$crit_mult" +"dota_tooltip_ability_item_crystalys_Lore" "An ancient artifact created by master smiths to enhance the lethality of weapons. Its sharp edges are capable of directing the energy of the strike to the most vulnerable points of the enemy." + +"dota_tooltip_ability_item_magical_crit" "Lia Magic Bow" +"dota_tooltip_ability_item_magical_crit_Description" "Passive: your spells have a chance to deal critical magical damage. Includes stats from Mystic Staff, Magic Stone, and Voodoo Mask." +"dota_tooltip_ability_item_magical_crit_bonus_intellect" "+$int" +"dota_tooltip_ability_item_magical_crit_bonus_strength" "+$str" +"dota_tooltip_ability_item_magical_crit_bonus_agility" "+$agi" +"dota_tooltip_ability_item_magical_crit_bonus_spell_amplify" "%+$spell_amp" +"dota_tooltip_ability_item_magical_crit_bonus_mana_regen" "+$mana_regen" +"dota_tooltip_ability_item_magical_crit_bonus_mana_pct" "%+$max_mana_percentage" +"dota_tooltip_ability_item_magical_crit_magical_vampirism" "%+$spell_lifesteal_hero" +"dota_tooltip_ability_item_magical_crit_spell_crit_chance" "%+$spell_crit_chance" +"dota_tooltip_ability_item_magical_crit_spell_crit_mult" "%+$spell_crit_mult" +"dota_tooltip_ability_item_magical_crit_Lore" "A crystal grown in mage towers: it catches streams of mana and, at the right moment, drives them into the weak point of a spell." + +"dota_tooltip_ability_item_ethereal_blade_custom" "Ethereal Blade" +"dota_tooltip_ability_item_ethereal_blade_custom_Description" "

Active: Ether Blast

Fires ethereal projectiles at enemies within %splash_radius% of the target. Enemies enter ethereal form for %duration% sec., take bonus magical damage, and are slowed. Damage: %blast_damage_base% + %blast_stat_multiplier% x primary attribute.

Can be cast on an ally: ethereal form for %duration_ally% sec. plus %ally_bonus_movespeed%%% move speed and %ally_bonus_attack_speed%%% attack speed." +"dota_tooltip_ability_item_ethereal_blade_custom_bonus_strength" "+$str" +"dota_tooltip_ability_item_ethereal_blade_custom_bonus_agility" "+$agi" +"dota_tooltip_ability_item_ethereal_blade_custom_bonus_intellect" "+$int" +"dota_tooltip_ability_item_ethereal_blade_custom_status_resistance" "%+$status_resist" +"dota_tooltip_ability_item_ethereal_blade_custom_bonus_attack_speed" "+$attack" +"dota_tooltip_ability_item_ethereal_blade_custom_movement_speed_percent_bonus" "%+$move_speed" +"dota_tooltip_ability_item_ethereal_blade_custom_hp_regen_amp" "%+$hp_regen" +"dota_tooltip_ability_item_ethereal_blade_custom_mana_regen_multiplier" "%+$mana_regen" +"dota_tooltip_ability_item_ethereal_blade_custom_spell_amp" "%+$spell_amp" +"dota_tooltip_ability_item_ethereal_blade_custom_magic_damage_attack" "+$damage" +"dota_tooltip_ability_item_ethereal_blade_custom_splash_radius" "BLAST RADIUS:" +"dota_tooltip_ability_item_ethereal_blade_custom_blast_damage_base" "BASE DAMAGE:" +"dota_tooltip_ability_item_ethereal_blade_custom_blast_stat_multiplier" "PRIMARY STAT MULTIPLIER:" +"dota_tooltip_ability_item_ethereal_blade_custom_ethereal_damage_bonus" "% INCREASED MAGIC DAMAGE TAKEN:" +"dota_tooltip_ability_item_ethereal_blade_custom_duration" "ENEMY DURATION:" +"dota_tooltip_ability_item_ethereal_blade_custom_duration_ally" "ALLY DURATION:" +"dota_tooltip_ability_item_ethereal_blade_custom_ally_bonus_movespeed" "% ALLY MOVE SPEED:" +"dota_tooltip_ability_item_ethereal_blade_custom_ally_bonus_attack_speed" "% ALLY ATTACK SPEED:" +"dota_tooltip_ability_item_ethereal_blade_custom_Lore" "A blade of solidified ether that cuts with magic and briefly pulls victims out of the world of blows." + +"dota_tooltip_ability_item_dark_crystalys" "daedalus" +"dota_tooltip_ability_item_dark_crystalys_bonus_damage" "+$damage" +"dota_tooltip_ability_item_dark_crystalys_crit_chance" "%+$crit_chance" +"dota_tooltip_ability_item_dark_crystalys_crit_mult" "%+$crit_mult" +"dota_tooltip_ability_item_dark_crystalys_Lore" "An improved version of Crystalis, this artifact guarantees fatal strikes in battle." + +"dota_tooltip_ability_item_rapier_custom" "Piercing blade" +"dota_tooltip_ability_item_rapier_custom_bonus_damage" "+$damage" +"dota_tooltip_ability_item_rapier_custom_Description" "

Pasivnoe: Divine Stab

When attacking %proc_damage%% of damage done will also be dealt in clean form." +"dota_tooltip_ability_item_rapier_custom_Lore" "After the fall of the holy warrior in a battle with demons, his spear was broken into many fragments. One of these fragments fell into the hands of a talented blacksmith, who, inspired by the divine power invested in metal, forged it into a sword of incredible sharpness." + +"dota_tooltip_ability_item_divine_rapier_custom" "Divine rapier" +"dota_tooltip_ability_item_divine_rapier_custom_bonus_damage" "+$damage" +"dota_tooltip_ability_item_divine_rapier_custom_Description" "

Pasivnoe: Divine Stab

When attacking %proc_damage%% of damage done will also be dealt in clean form." +"dota_tooltip_ability_item_divine_rapier_custom_Lore" "After the fall of a holy warrior in a battle with demons, his spear was broken into many fragments. One of these fragments fell into the hands of a talented blacksmith, who, inspired by the divine power invested in metal, forged it into a sword of incredible sharpness." + +"dota_tooltip_ability_item_mjolnir_custom" "Mjollnir" +"dota_tooltip_ability_item_mjolnir_custom_Description" "

Active: Chain Blessing

Surrounds the target with lightning chains for %duration% sec. The target deals %outgoing_pct%%% outgoing damage and receives %incoming_pct%%% less incoming damage.\n\n

Passive: Chain Lightning

Each attack has a %chain_chance%%% chance to release chain lightning that jumps %chain_strikes% times between enemies within %chain_radius% radius, dealing %chain_damage% + %chain_damage_self%%% of attack damage as magical damage per jump. Chain lightning cannot be evaded." +"dota_tooltip_ability_item_mjolnir_custom_Lore" "Thor's magic hammer forged for him by dwarves named Brock and Eitri." +"dota_tooltip_ability_item_mjolnir_custom_bonus_damage" "+$damage" +"dota_tooltip_ability_item_mjolnir_custom_bonus_attack_speed" "+$attack" + +"DOTA_Tooltip_ability_item_magic_stone" "Magic stone" +"DOTA_Tooltip_ability_item_magic_stone_Description" "

Pasivnoe: Magic Aura

Strengthens the owner's spells and restores the mana." +"DOTA_Tooltip_ability_item_magic_stone_Lore" "A large gem on several chains. Ancient magical energy pulsates inside." +"DOTA_Tooltip_ability_item_magic_stone_bonus_strength" "+$str" +"DOTA_Tooltip_ability_item_magic_stone_bonus_spell_amplify" "%+$spell_amp" +"DOTA_Tooltip_ability_item_magic_stone_bonus_agility" "+$agi" +"DOTA_Tooltip_ability_item_magic_stone_bonus_intellect" "+$int" +"DOTA_Tooltip_ability_item_magic_stone_bonus_mana_regen" "+$mana_regen" +"DOTA_Tooltip_ability_item_magic_stone_bonus_mana_pct" "%+$max_mana_percentage" + +"DOTA_Tooltip_ability_item_magic_rapier" "Magical dagger" +"DOTA_Tooltip_ability_item_magic_rapier_Description" "

Pasive: Divine Pierce

The next directional ability will deal the target additional magic damage equal to %damage_per_cast% plus %damage_per_cast_hand%%% of the owner's current mana (not attack damage), slow for %slow_duration% sec and reduce magic resistance by %magical_resist_reduction%%%.

Effect stacks." +"DOTA_Tooltip_ability_item_magic_rapier_Lore" "A legendary blade forged from fragments of stellar metal and tempered in the flames of ancient dragons. Its creator, Archmage Aelindor, is said to have spent three centuries searching for the perfect material for this weapon. When he finally found the shooting star, its core was saturated with the purest magical energy." +"DOTA_Tooltip_ability_item_magic_rapier_bonus_spell_amplify" "%+$spell_amp" +"DOTA_Tooltip_ability_item_magic_rapier_bonus_intellect" "+$int" +"DOTA_Tooltip_ability_item_magic_rapier_bonus_strength" "+$str" +"DOTA_Tooltip_ability_item_magic_rapier_bonus_agility" "+$agi" +"DOTA_Tooltip_ability_item_magic_rapier_bonus_mana_regen" "+$mana_regen" +"DOTA_Tooltip_ability_item_magic_rapier_bonus_mana_pct" "%+$max_mana_percentage" + +"DOTA_Tooltip_ability_item_magical_divine_rapier" "Magical divine blade" +"DOTA_Tooltip_ability_item_magical_divine_rapier_Description" "

Pasive: Divine Pierce

The following directional ability will deal targets with an additional %damage_per_cast% + %damage_per_cast_hand%%% of the owner's attack magic damage, slow down by %slow_duration% sec and reduce magic resistance by %magical_resist_reduction%%%.

Effect beats." +"DOTA_Tooltip_ability_item_magical_divine_rapier_Lore" "A legendary blade forged from fragments of stellar metal and tempered in the flames of ancient dragons. Its creator, Archmage Aelindor, is said to have spent three centuries searching for the perfect material for this weapon. When he finally found the shooting star, its core was saturated with the purest magical energy." +"DOTA_Tooltip_ability_item_magical_divine_rapier_bonus_spell_amplify" "%+$spell_amp" +"DOTA_Tooltip_ability_item_magical_divine_rapier_bonus_intellect" "+$int" +"DOTA_Tooltip_ability_item_magical_divine_rapier_bonus_strength" "+$str" +"DOTA_Tooltip_ability_item_magical_divine_rapier_bonus_agility" "+$agi" +"DOTA_Tooltip_ability_item_magical_divine_rapier_bonus_mana_regen" "+$mana_regen" +"DOTA_Tooltip_ability_item_magical_divine_rapier_bonus_mana_pct" "%+$max_mana_percentage" + +"DOTA_Tooltip_ability_item_soul_devourer_staff" "Soul Devourer Staff" +"DOTA_Tooltip_ability_item_soul_devourer_staff_Description" "

Pasivnoe: Soul Devourer

After a kill, restores %kill_mana%%% of maximum mana and increases maximum mana by %bonus_mana% per stack. Every %stack_growth_interval% sec., the blessing stack count increases by %stack_growth_percent%%% (rounded up)." +"DOTA_Tooltip_ability_item_soul_devourer_staff_Lore" "After the necromancer's defeat at the Battle of the Grave Hills, the rod was sealed in a crypt under the Temple of Light. But with the start of the zombie apocalypse, the ancient seals weakened, and the artifact regained its freedom, thirsting for new souls to be absorbed." +"DOTA_Tooltip_ability_item_soul_devourer_staff_bonus_intellect" "+$int" +"DOTA_Tooltip_ability_item_soul_devourer_staff_bonus_manacost_reduction" "%+$manacost_reduction" +"DOTA_Tooltip_ability_item_soul_devourer_staff_bonus_mana_regen" "+$mana_regen" +"DOTA_Tooltip_ability_item_soul_devourer_staff_bonus_mana_special" "+$mana" +"DOTA_Tooltip_ability_item_soul_devourer_staff_bonus_cast_range" "+$cast_range" + + +"DOTA_Tooltip_modifier_soul_devourer_staff_buff" "Soul Devourer Blessing" +"DOTA_Tooltip_modifier_soul_devourer_staff_buff_Description" "The hero received %dMODIFIER_PROPERTY_SPELL_AMPLIFY_PERCENTAGE%% additional magic power-up." + +"DOTA_Tooltip_ability_item_orb_of_fire" "Orb of Fire" +"DOTA_Tooltip_ability_item_orb_of_fire_Note0" "

Pasivnoe: Burn

With 0+ stacks, the owner begins to take damage per second from the number of stacks * 3.

With 70+ stacks, the effect owner loses 25% of the number of stacks of his armor and magician. resistance.

The effect does not affect FROSTBITE, which is why the BURN absorbs the effect of FROSTBITE." +"DOTA_Tooltip_ability_item_orb_of_fire_Description" "

Pasivnoe: Fire Aura

Every second within a radius of %radius% units applies %damage%, will apply %fire_stack% burn effect to everyone around." +"DOTA_Tooltip_ability_item_orb_of_fire_Lore" "A sphere consisting entirely of fire." + +"dota_tooltip_ability_item_ice_spine" "Ice spin" +"dota_tooltip_ability_item_ice_spine_Description" "

Pasivnoe: Ice fragility

All wearer healing spells restore %heal_amplify%% more health, also wearer of the item takes %incoming_dmg_pct%%% more damage." +"dota_tooltip_ability_item_ice_spine_Lore" "Ice needle created by sisters of ice, loving masochistic gamers." +"dota_tooltip_ability_item_ice_spine_spell_amplify" "%+$spell_amp" +"dota_tooltip_ability_item_ice_spine_movespeed_const" "+$move_speed" + +"DOTA_Tooltip_ability_item_fire_cape" "Blazing Shroud" +"DOTA_Tooltip_ability_item_fire_cape_Note0" "

Pasivnoe: Burn

With 0+ stacks, the owner begins to take damage per second from the number of stacks * 3.

With 70+ stacks, the effect owner loses 25% of the number of stacks of his armor and magician. resistance.

The effect does not affect FROSTBITE, which is why the BURN absorbs the effect of FROSTBITE." +"DOTA_Tooltip_ability_item_fire_cape_Description" "

Pasivnoe: Aura of greatness

Every second within a radius of %radius% units applies %damage% + %mana_damage_pct%%% of the owner's maximum mana, will apply %fire_stack% burn effect to everyone around. Also, everyone within a radius reduces their magic resistance and magic damage by %bonus_magic_resistance%%%/%bonus_spell_amp%%% respectively." +"DOTA_Tooltip_ability_item_fire_cape_Lore" "The fire cloak was forged from the flames, which does not go out even under water. Its heat not only burns enemies around, but also suppresses their magical power, removing resistance and reducing spell damage. It is said that anyone who dares to approach the owner of this artifact will feel the full power of an indomitable fire that can incinerate even the most enduring spell." +"DOTA_Tooltip_ability_item_fire_cape_bonus_damage" "+$damage" +"DOTA_Tooltip_ability_item_fire_cape_bonus_attack_speed" "+$attack" +"DOTA_Tooltip_ability_item_fire_cape_bonus_physical_armor" "+$armor" +"DOTA_Tooltip_ability_item_fire_cape_bonus_health_regen" "+$hp_regen" +"DOTA_Tooltip_ability_item_fire_cape_bonus_mana_regen" "+$mana_regen" +"DOTA_Tooltip_ability_item_fire_cape_bonus_magic_resistance" "%+$spell_resist" + +"DOTA_Tooltip_ability_item_radiance_custom" "Radiance" +"DOTA_Tooltip_ability_item_radiance_custom_Note0" "

Passive: Burn

With 0+ stacks, the target takes damage per second equal to stacks × 3.

With 70+ stacks, the target loses 25% of its armor and magic resistance based on stack count.

Burn does not stack with Frostbite — Burn consumes Frostbite." +"DOTA_Tooltip_ability_item_radiance_custom_Description" "

Passive: Burn Aura

Every %tick_interval% sec. within %radius% radius, deals enemies %damage% + %health_damage_pct%%% of the owner's max health + %health_regen_damage% per point of health regeneration per second as magical damage and applies %fire_stack% burn stacks." +"DOTA_Tooltip_ability_item_radiance_custom_Lore" "A sacred blade wreathed in flames that never die, even in the deepest abyss." +"DOTA_Tooltip_ability_item_radiance_custom_bonus_damage" "+$damage" +"DOTA_Tooltip_ability_item_radiance_custom_evasion" "%+$evasion" + +"DOTA_Tooltip_ability_item_blazing_radiance_custom" "Blazing guard" +"DOTA_Tooltip_ability_item_blazing_radiance_custom_Note0" "

Passive: Burn

With 0+ stacks, the target takes damage per second equal to stacks × 3.

With 70+ stacks, the target loses 25% of its armor and magic resistance based on stack count.

Burn does not stack with Frostbite — Burn consumes Frostbite." +"DOTA_Tooltip_ability_item_blazing_radiance_custom_Description" "

Passive: Infernal Aura

Every %tick_interval% sec. within %radius% radius, deals %damage% + %health_damage_pct%%% of max health + %health_regen_damage% per health regen per second + %mana_damage_pct%%% of max mana as magical damage and applies %fire_stack% burn stacks. Enemies in radius lose %aura_magic_resistance_reduction%%% magic resistance and %aura_spell_amp_reduction%%% spell amplification.

Additionally increases all base attributes by %all_stats_pct%%%." +"DOTA_Tooltip_ability_item_blazing_radiance_custom_Lore" "A cloak stitched from Radiance flame and Blazing Shroud heat: a sea of fire surrounds the wearer while enemy magic fades in the smoke." +"DOTA_Tooltip_ability_item_blazing_radiance_custom_bonus_damage" "+$damage" +"DOTA_Tooltip_ability_item_blazing_radiance_custom_evasion" "%+$evasion" +"DOTA_Tooltip_ability_item_blazing_radiance_custom_bonus_attack_speed" "+$attack" +"DOTA_Tooltip_ability_item_blazing_radiance_custom_bonus_physical_armor" "+$armor" +"DOTA_Tooltip_ability_item_blazing_radiance_custom_bonus_health_regen" "+$hp_regen" +"DOTA_Tooltip_ability_item_blazing_radiance_custom_bonus_mana_regen" "+$mana_regen" +"DOTA_Tooltip_ability_item_blazing_radiance_custom_bonus_magic_resistance" "%+$spell_resist" +"DOTA_Tooltip_ability_item_blazing_radiance_custom_bonus_all_stats" "+$all" + +"DOTA_Tooltip_ability_item_ultimate_crown" "Ultimate Crown" +"DOTA_Tooltip_ability_item_ultimate_crown_bonus_all_stats" "+$all" +"DOTA_Tooltip_ability_item_ultimate_crown_Description" "Additionally increases all base attributes by %all_stats_pct%%%." +"DOTA_Tooltip_ability_item_ultimate_crown_Lore" "Three orbs fused into a crown: strength, agility, and intellect grow by flat value and by a share of innate talent." + +"DOTA_Tooltip_ability_item_skadi_custom" "Eye of Skadi" +"DOTA_Tooltip_ability_item_skadi_custom_bonus_all_stats" "+$all" +"DOTA_Tooltip_ability_item_skadi_custom_Description" "

Passive: Cold Attack

Adds a slowing effect to the owner's attacks for %debuff_duration% sec. Reduces the victim's attack speed by %slow_attack_speed%%%. Movement slow depends on attack type: %slow_movespeed_melee%%% against melee units and %slow_movespeed_ranged%%% against ranged units. Also reduces the victim's health restoration by %health_restoration_reduction%%%.

Additionally increases all base attributes by %all_stats_pct%%%." +"DOTA_Tooltip_ability_item_skadi_custom_Lore" "An orb forged from the heart of an ancient wyrm: every strike binds the victim in frost until life drains from the wound." + +"DOTA_Tooltip_ability_item_crimson_shivas_custom" "Crimson Shiva's Guard" +"DOTA_Tooltip_ability_item_crimson_shivas_custom_Note0" "

Passive: Freezing Aura

Within %aura_radius% radius, reduces enemy attack speed by %aura_as_reduction%, increases their incoming damage by %aura_enemy_incoming_amp%%%, and reduces allies' incoming damage by %aura_ally_incoming_reduction%%%." +"DOTA_Tooltip_ability_item_crimson_shivas_custom_Description" "

Active: Arctic Bulwark

Releases a frost wave in %blast_radius% radius, dealing %blast_damage% magical damage and slowing enemies for %slow_duration% sec. Allies within %guard_radius% gain enhanced protection for %guard_duration% sec.: physical damage block (base %damage_block_active% + %block_strength_pct%%% of caster's Strength), bonus armor, attack speed, and movement speed; incoming damage against them is reduced by %aura_ally_incoming_reduction%%%.

Passive: Guardian Ward

Grants armor, stats, health, mana, and regeneration. Has a %damage_block_chance%%% chance to block %damage_block% physical attack damage. Melee heroes additionally block %block_strength_pct%%% of their Strength.

Additionally increases all base attributes by %all_stats_pct%%%." +"DOTA_Tooltip_ability_item_crimson_shivas_custom_Lore" "Ice and steel fused into one bulwark: foes freeze while allies hold the line." +"DOTA_Tooltip_ability_item_crimson_shivas_custom_bonus_stats" "+$all" +"DOTA_Tooltip_ability_item_crimson_shivas_custom_bonus_all_stats" "+$all" +"DOTA_Tooltip_ability_item_crimson_shivas_custom_bonus_health" "+$health" +"DOTA_Tooltip_ability_item_crimson_shivas_custom_bonus_mana" "+$mana" +"DOTA_Tooltip_ability_item_crimson_shivas_custom_bonus_armor" "+$armor" +"DOTA_Tooltip_ability_item_crimson_shivas_custom_bonus_hp_regen" "+$hp_regen" +"DOTA_Tooltip_ability_item_crimson_shivas_custom_bonus_mana_regen" "+$mana_regen" + +"DOTA_Tooltip_ability_item_blademail_2" "Blademail II" +"DOTA_Tooltip_ability_item_blademail_2_aura_armor" "+$armor" +"DOTA_Tooltip_ability_item_blademail_2_aura_attack_speed" "+$attack" +"DOTA_Tooltip_ability_item_blademail_2_Description" "

Active: Returning Spikes

For %active_duration% sec: reflects %active_reflect_pct%%% of damage taken from enemies (after mitigation); damage dealt by enemies during this effect restores the same amount of health. Blade Mail-style visuals.\n\n

Passive: Damage Return

Reflects %reflect_pct%%% of damage taken (after mitigation) to the attacker. Does not trigger on HP loss, allies, or reflected damage.\n\n

Passive: Barbed Aura

Within %radius% radius: allied heroes and creeps gain +%aura_armor% armor and +%aura_attack_speed% attack speed; enemies suffer the same values as penalties." +"DOTA_Tooltip_ability_item_blademail_2_Lore" "A mail woven from broken blades. It hits back as hard as it receives, and those who stand nearby either feel borrowed steel on their side or the crushing ring of a hostile cordon." +"DOTA_Tooltip_modifier_modifier_item_blademail_2_aura" "Blademail II (aura)" +"DOTA_Tooltip_modifier_modifier_item_blademail_2_aura_Description" "Allies of the carrier: +%aura_armor% armor and +%aura_attack_speed% attack speed. Enemies: −%aura_armor% armor and −%aura_attack_speed% attack speed." +"DOTA_Tooltip_modifier_modifier_item_blademail_2_active" "Returning Spikes" +"DOTA_Tooltip_modifier_modifier_item_blademail_2_active_Description" "Reflects %active_reflect_pct%%% of incoming damage and restores health from all damage dealt by enemies." + +"dota_tooltip_modifier_item_desolator_custom_debuff" "Corruption" +"dota_tooltip_modifier_item_desolator_custom_debuff_Description" "Armor %corruption_armor%; movement speed %corruption_movespeed_slow%%% ; health regen reduced by %corruption_heal_reduction%%%." +"dota_tooltip_modifier_item_desolator_custom_2_debuff" "Deep Corruption" +"dota_tooltip_modifier_item_desolator_custom_2_debuff_Description" "Armor reduced by min(stacks × %corruption_armor_per_stack%, %corruption_armor_cap%); movement %corruption_movespeed_slow%%% ; regen −%corruption_heal_reduction%%%." +"dota_tooltip_modifier_item_blademail_2_active" "Returning Spikes" +"dota_tooltip_modifier_item_blademail_2_active_Description" "Reflects %active_reflect_pct%%% of incoming damage and restores health from all damage dealt by enemies." +"dota_tooltip_modifier_item_blademail_2_aura" "Blademail II (aura)" +"dota_tooltip_modifier_item_blademail_2_aura_Description" "Allies of the carrier: +%aura_armor% armor and +%aura_attack_speed% attack speed. Enemies: −%aura_armor% armor and −%aura_attack_speed% attack speed." + +"dota_tooltip_ability_item_rom" "Empty flask" +"dota_tooltip_ability_item_rom_Description" "Slurp slurp bottle is a little empty." +"dota_tooltip_ability_item_rom_Lore" "Rumor has it that this rum was made from the ectoplasm of the pirate king." + +"dota_tooltip_ability_item_ent_heart" "Heart of Ent" +"dota_tooltip_ability_item_ent_heart_Description" "

Quest Item

Used to Pass Quest." +"dota_tooltip_ability_item_ent_heart_Lore" "The heart, torn out of the ancient ent, is still pulsating with the vital energy of the forest. Its owner is said to become invulnerable to disease and wound, and his body is filled with the power of nature itself." + +"DOTA_Tooltip_ability_item_wolf_hat" "Wolf Hat" +"DOTA_Tooltip_ability_item_wolf_hat_bonus_strength" "+$str" +"DOTA_Tooltip_ability_item_wolf_hat_bonus_move_speed" "+$move_speed" +"DOTA_Tooltip_ability_item_wolf_hat_lifesteal_pct" "%+$lifesteal" +"DOTA_Tooltip_ability_item_wolf_hat_low_hp_damage_bonus" "%+$damage" +"DOTA_Tooltip_ability_item_wolf_hat_Description" "

Active: Pack Howl

For %howl_duration% sec., grants %howl_attack_speed% attack speed and %howl_move_speed_pct%%% movement speed.

Passive: Alpha's Blood

Attacks against enemies below %low_hp_threshold%%% health deal %low_hp_damage_bonus%%% more damage." +"DOTA_Tooltip_ability_item_wolf_hat_Note0" "Kunkevich's reward for wolf claws. Cannot be sold." +"DOTA_Tooltip_ability_item_wolf_hat_Lore" "The captain stitched a hat from the claws you brought: it carries the pack's howl and the urge to finish wounded prey. Sailors say even the Kraken won't swim close while you wear it." + +"DOTA_Tooltip_modifier_item_wolf_hat_howl" "Pack Howl" +"DOTA_Tooltip_modifier_item_wolf_hat_howl_Description" "Increased attack speed and movement speed." + +"dota_tooltip_ability_item_wolf_claw" "Wolf Claw" +"dota_tooltip_ability_item_wolf_claw_Description" "

Quest Item

Used to Pass Quest." +"dota_tooltip_ability_item_wolf_claw_Lore" "A sharp claw that belonged to the legendary leader of the pack. His power is transferred to the new owner, filling him with savagery and a thirst for hunting." + +"dota_tooltip_ability_item_spider_legs_custom" "Spider feet" +"dota_tooltip_ability_item_spider_legs_custom_Description" "

Quest Item

Used to submit a quest." +"dota_tooltip_ability_item_spider_legs_custom_Lore" "Spider feet covered with poisonous villi. Even after the creature dies, they still tremble as if they are trying to cling to prey." + +"dota_tooltip_ability_item_poison" "Spider Venom" +"dota_tooltip_ability_item_poison_Description" "

Quest Item

Used to Pass Quest." +"dota_tooltip_ability_item_poison_Lore" "Thick dark liquid in a sealed bottle. Alchemists pay generously for it, because deadly mixtures can be made from this poison." + +"dota_tooltip_ability_item_frog_paw" "Frog legs" +"dota_tooltip_ability_item_frog_paw_Description" "

Quest Item

Used to Pass Quest." +"dota_tooltip_ability_item_frog_paw_Lore" "Small feet of a rare swamp frog. Their viscous juice adds the desired concentration and special aftertaste to the potions." + +"dota_tooltip_ability_item_lycan_horn" "Black Fang Horn" +"dota_tooltip_ability_item_lycan_horn_Description" "

Quest Item

Used to Pass Quest." +"dota_tooltip_ability_item_lycan_horn_Lore" "The horn covered with ancient runes belonged to Lycan himself — lord of the wolves. It is believed that its owner can summon the power of the full moon, becoming unstoppable in battle." + +"dota_tooltip_ability_item_kunkka_sword" "Pirate Saber" +"dota_tooltip_ability_item_kunkka_sword_bonus_damage" "+$damage" +"dota_tooltip_ability_item_kunkka_sword_move_speed_bonus" "%-to movement speed" +"dota_tooltip_ability_item_kunkka_sword_Description" "

Active: Wave Cuts

When activated, the following %attack_count% of owner attacks will cause %crit_mult%% damage." +"dota_tooltip_ability_item_kunkka_sword_Note0" "Not heavier than your motherboard, AZAZ." +"dota_tooltip_ability_item_kunkka_sword_Lore" "The sword from the hold of Kunka – a weapon that has covered itself with legends. Countless creatures were defeated by him: sea monsters, bloodthirsty pirates, and even Leviathan himself, the ruler of the abyss. They say that the blade is so heavy that a simple, trisky person is unable to lift it off the ground, let alone skid to hit it. But for a true sea wolf, its heaviness – is not a burden, but a reminder: in its hands – the rage of the ocean, tempered in the abyss." + +"dota_tooltip_modifier_item_kunkka_sword_active" "Alcohol intoxication" +"dota_tooltip_modifier_item_kunkka_sword_active_Description" "The next attack will have a critical effect." + +"dota_tooltip_ability_item_oldmen_amulet" "The Elder’s Amulet" +"dota_tooltip_ability_item_oldmen_amulet_Description" "

Relaxing mind

Recovers %mana_restore% of the target’s mana." +"dota_tooltip_ability_item_oldmen_amulet_spell_amplify_percentage" "%+$spell_amp" +"dota_tooltip_ability_item_oldmen_amulet_manacost_debuff" "%+to mana consumption" + +"dota_tooltip_ability_item_oldmen_amulet_Lore" "An ancient amulet that belonged to a sage who lived for a thousand years. It contains all the wisdom of the years he lived." +"dota_tooltip_ability_item_oldmen_amulet_Note0" "As the grandfather said: This is a charming amulet, stuffed with lard." +//utils + +"dota_tooltip_ability_item_meat" "Meat" +"dota_tooltip_ability_item_meat_Description" "

Bon appetit

When used, the target eats food and restores %hunger_bonus% satness and %heal% health." +"dota_tooltip_ability_item_meat_Lore" "A juicy piece of meat prepared according to an ancient recipe from local hunters. Its aroma awakens the wolfish appetite of even the most fastidious eaters." +"dota_tooltip_ability_item_meat_Note0" "Satitude gives the effect of saturation and for each satiety the hero receives a 1% bonus of his basic stats." + +"dota_tooltip_ability_item_bread" "Bread" +"dota_tooltip_ability_item_bread_Description" "

Bon appetit

When used, the target eats a warm piece of bread and restores %hunger_bonus% saturation and %heal% health." +"dota_tooltip_ability_item_bread_Lore" "Simple but precious bread in the zombie apocalypse. It smells like there is still a normal life left somewhere — without the dead, waves and endless quests." + +"dota_tooltip_ability_item_testo" "Dough" +"dota_tooltip_ability_item_testo_Description" "

Ingredient

Raw dough used only as cooking ingredient. Doesn't do anything on its own — but you can make something delicious out of it." +"dota_tooltip_ability_item_testo_Lore" "Collected according to the secret recipe of an old man who knows best how to survive a zombie apocalypse with a full stomach." + +"dota_tooltip_ability_item_testo_pizza" "Pizza Preparation" +"dota_tooltip_ability_item_testo_pizza_Description" "

Ingredient

Basis for pizza. It’s a preparation: it won’t satisfy your hunger, but it will make the final pizza." +"dota_tooltip_ability_item_testo_pizza_Lore" "Dough collected according to the recipe of someone who knows: the right sauce makes the dish a legend." + +"dota_tooltip_ability_item_wooden_katana" "Wooden katana" +"dota_tooltip_ability_item_wooden_katana_Description" "

Passive: Precise methodology

Increases damage done by %damage_outgoing_percentage%%%." +"dota_tooltip_ability_item_wooden_katana_Lore" "Old Master Katana Training. Light wood does not cut steel, but teaches you to deliver precise and strong impacts." + +"dota_tooltip_ability_item_firecore" "Prosperity Core" +"dota_tooltip_ability_item_firecore_Description" "

Prosperity

A core filled with pure fiery energy is suitable to accelerate the very matter of the universe." +"dota_tooltip_ability_item_firecore_Lore" "They say this crystal — frozen flame of an ancient dragon. His warmth does not fade even in the most severe cold, and the light is able to illuminate the path through any dark times." + + +"dota_tooltip_ability_item_mayonnaise" "Mayonnaise" +"dota_tooltip_ability_item_mayonnaise_Description" "

Ingredient

Cool sauce you need to make pizza." +"dota_tooltip_ability_item_mayonnaise_Lore" "Thick, delicate and stubbornly delicious. It looks like he survived the zombie apocalypse too and got even better." + +"dota_tooltip_ability_item_pizza" "Pizza" +"dota_tooltip_ability_item_pizza_Description" "

Bon appetit

When used, the target eats the pizza and restores %hunger_bonus% saturation. Also strengthens all character characteristics on %stack_count%." +"dota_tooltip_ability_item_pizza_Lore" "Pizza you can eat even when there are only waves and quests around. Incredibly delicious — proven in practice." + +"dota_tooltip_ability_item_cheese" "Cured cheese" +"dota_tooltip_ability_item_cheese_Description" "

Ingredient

Curd cheese used only as cooking ingredient. Doesn't do anything on its own — but you can make something delicious out of it." +"dota_tooltip_ability_item_cheese_Lore" "This cheese was once the top prize at the Survivors Festival. They say that whoever tries it at least once will gain the strength to withstand any zombie assault — or at least forget about hunger for the next 24 hours." + +"dota_tooltip_ability_item_grilled_meat" "Fried meat" +"dota_tooltip_ability_item_grilled_meat_Description" "

Bon appetit (premium)

When used, the target eats the juicy steak and restores %hunger_bonus% saturation and %heal% health." +"dota_tooltip_ability_item_grilled_meat_Lore" "Meat cooked to a perfect crust over a fire during a short respite between waves of zombies. Each piece is reminiscent of the times when the main enemy was not hunger, but a neighbor who finished his last barbecue." +"dota_tooltip_ability_item_grilled_meat_Note0" "Satitude gives the effect of saturation and for each satiety the hero receives a 1% bonus of his basic stats." + +"dota_tooltip_ability_item_milk" "Milk" +"dota_tooltip_ability_item_milk_Description" "

Bon appetit

When used, the target eats food and restores %hunger_bonus% saturation and %mana% man." +"dota_tooltip_ability_item_milk_Lore" "A juicy piece of meat prepared according to an ancient recipe from local hunters. Its aroma awakens the wolfish appetite of even the most fastidious eaters." +"dota_tooltip_ability_item_milk_Note0" "Satitude gives the effect of saturation and for each satiety the hero receives a 1% bonus of his basic stats." + +"dota_tooltip_ability_item_banana" "Banana" +"dota_tooltip_ability_item_banana_Description" "

Bon appetit

When used, the target eats the ripe banana and restores %mana% mana and %hunger_bonus% saturation." +"dota_tooltip_ability_item_banana_Lore" "A banana that survived a warehouse, a raid by looters and three waves of zombies. So charged with potassium and magic that he can inspire even the most tired caster to cast another spell." +"dota_tooltip_ability_item_banana_Note0" "Satitude gives the effect of saturation and for each satiety the hero receives a 1% bonus of his basic stats." + +"dota_tooltip_ability_item_sandwich" "Sandwich" +"dota_tooltip_ability_item_sandwich_Description" "

Bon appetit

When used, the target eats a huge sandwich, receiving %hunger_bonus% satiety and at %buff_duration% sec: +%bonus_damage% to damage, +%bonus_armor% to armor, but the speed of movement decreases by %move_speed_slow_pct%%%." +"dota_tooltip_ability_item_sandwich_Lore" "Three layers of meat, cheese, sauce and bread as thick as weight armor. After such a sandwich you become a walking tank —however, you move about as fast." +"dota_tooltip_ability_item_sandwich_Note0" "Satitude gives the effect of saturation and for each satiety the hero receives a 1% bonus of his basic stats." + +"dota_tooltip_modifier_item_sandwich_buff" "Sandwich" +"dota_tooltip_modifier_item_sandwich_buff_Description" "The hero has increased in size by %dMODIFIER_PROPERTY_MODEL_SCALE%%%, received a bonus of %dMODIFIER_PROPERTY_PREATTACK_BONUS_DAMAGE% to damage and %dMODIFIER_PROPERTY_PHYSICAL_ARMOR_BONUS% to armor, but the movement speed is reduced by %dMODIFIER_PROPERTY_MOVESPEED_BONUS_PERCENTAGE%%%." + +"dota_tooltip_ability_item_energy_drink" "Energy drink" +"dota_tooltip_ability_item_energy_drink_Description" "

ADRENALINE IN VEINS

When used, the target pours in a liter of pure madness, restores %hunger_bonus% satiety, %heal% health and %mana% man. At the next %buff_duration% sec, the hero receives %buff_move_speed_pct%%% to movement speed and %buff_attack_speed%% to attack speed, runs as if he was personally driven out by Gaben, and waves his arms faster than he thinks." +"dota_tooltip_ability_item_energy_drink_Lore" "Officially banned from all esports tournaments due to «too much buzz per square pixel»." +"dota_tooltip_ability_item_energy_drink_Note0" "Satitude gives the effect of saturation and for each satiety, the hero receives a 1% bonus of his basic stats." + +"dota_tooltip_modifier_energy_drink_buff" "Energy charge" +"dota_tooltip_modifier_energy_drink_buff_Description" "Movement speed increased by %dMODIFIER_PROPERTY_MOVESPEED_BONUS_PERCENTAGE%%% and attack speed increased by %dMODIFIER_PROPERTY_ATTACKSPEED_PERCENTAGE%%%." + +"dota_tooltip_ability_item_cocktail" "Fruit cocktail" +"dota_tooltip_ability_item_cocktail_Description" "

Active: Magic Kneading

When used, the target drinks a questionable cocktail, receiving %hunger_bonus% satiety and at %buff_duration% sec: %spell_lifesteal%%% spell vampirism, %spell_amp%%% spell boosts, but the mana consumption from the abilities increases by %manacost_increase%%%." +"dota_tooltip_ability_item_cocktail_Lore" "The bartender swore that this cocktail «definitely won’t kill, and if it does — it’s beautiful». And it’s true: after the first sip, your magical damage increases, and mana melts faster than respect for those who came up with it." +"dota_tooltip_ability_item_cocktail_Note0" "Satitude gives the effect of saturation and for each satiety the hero receives a 1% bonus of his basic stats." + +"dota_tooltip_modifier_item_cocktail_buff" "Fruit cocktail" +"dota_tooltip_modifier_item_cocktail_buff_Description" "The hero is intoxicated with a magic cocktail: gets %dMODIFIER_PROPERTY_SPELL_AMPLIFY_PERCENTAGE%% spell gain and magic vampirism effect, but abilities require %dMODIFIER_PROPERTY_MANACOST_PERCENTAGE_STACKING%%% more mana." + +"dota_tooltip_ability_item_coffee" "Coffee" +"dota_tooltip_ability_item_coffee_Description" "

Active: Turbo Espresso

When used, the target pours concentrated coffee into itself, obtaining %hunger_bonus% saturation and by %buff_duration% sec: reduces ability recharging by %cooldown_reduction%%% and reduces spell application time by %casttime_reduction%%%." +"dota_tooltip_ability_item_coffee_Lore" "Welded from grains that were fried to the screams of survivors and the howl of zombies. After such a cup, it seems that the spells themselves fly out of your hands faster than your thoughts." +"dota_tooltip_ability_item_coffee_Note0" "Satitude gives the effect of saturation and for each satiety the hero receives a 1% bonus of his basic stats." + +"dota_tooltip_ability_item_coffe_bean" "Coffein bean" +"dota_tooltip_ability_item_coffe_bean_Description" "

Active: Caffeine shot

Instantly recovers %mana_pct%%% of maximum target mana and %mana% mana. Doesn't take up buff food slots, fits perfectly on top of any snack." +"dota_tooltip_ability_item_coffe_bean_Lore" "Fried grains that survived more than one zombie apocalypse. So strong that they can wake up even the ancient creep that pretended to decorate the last three waves." + + + + + +"dota_tooltip_ability_item_bag_of_gold" "Bag of gold" +"dota_tooltip_ability_item_bag_of_gold_Description" "

Wealth

When raised, you will get 20-50 gold." + + + +"dota_tooltip_ability_item_rofl_for_kaban_pumba" "Boar hat staff" +"dota_tooltip_ability_item_rofl_for_kaban_pumba_Description" "

PUMBA????

Calls for a wild boar." +"dota_tooltip_ability_item_rofl_for_kaban_pumba_Lore" "You can beat him without thinking that you are a flayer." + +"dota_tooltip_ability_item_candy" "Candy:)" +"dota_tooltip_ability_item_candy_Description" "

Guess what you get

by eating this candy you will get a random effect, the effects add up and work together." +"dota_tooltip_ability_item_candy_Lore" "Sweet Tooth Here!?))" +"dota_tooltip_ability_item_candy_Note0" "Chocolate candy: the hero receives a 15% bonus to movement speed, but if he stops, he will begin to take damage.
Caramel candy: a hero with a 20% chance can take 100% more damage than necessary, but can also counterattack with a 20% chance, dealing his own damage and half of what he receives.
Dated candy: your health cannot fall below 1; however you have a 50% chance of getting a stun, slowed by 50%%, all your characteristics are reduced by 50%%, vision is reduced by 250 units.
Magic candy: increases magic damage by 15%%, reduces cooldowns by 15%%, all physical damage is reduced by 15%%." + +"dota_tooltip_modifier_chocolate_candy" "Chocolate candy" +"dota_tooltip_modifier_chocolate_candy_Description" "You are faster, but you better not stop." + +"dota_tooltip_modifier_caramel_candy" "Caramel candy" +"dota_tooltip_modifier_caramel_candy_Description" "you can take more damage or welloooooo... counterattack fuck his mouth...????" + +"dota_tooltip_modifier_mint_candy" "Dated candy" +"dota_tooltip_modifier_mint_candy_Description" "IT'S JUST FUCKING 0)" + +"dota_tooltip_modifier_magic_candy" "Magic candy" +"dota_tooltip_modifier_magic_candy_Description" "Your magician damage increased, recharge decreased, physical damage also decreased." + +"dota_tooltip_ability_item_easter_egg" "Easter egg" +"dota_tooltip_ability_item_easter_egg_Description" "

Golden fish

Goldfish are circling around you and throwing away bags of gold." +"dota_tooltip_ability_item_easter_egg_Lore" "And they said black caviar is expensive." +"dota_tooltip_ability_item_easter_egg_Note0" "10-50 golds per bag" + +"dota_tooltip_ability_item_egg" "Egg" +"dota_tooltip_ability_item_egg_Description" "

Ingredient

Egg. Used only as a cooking ingredient. Doesn't do anything on its own — but you can make something delicious out of it." +"dota_tooltip_ability_item_egg_Lore" "And they said black caviar is expensive." + +"dota_tooltip_modifier_orbiting_wisp" "Golden bast" +"dota_tooltip_modifier_orbiting_wisp_Description" "A skirt is circling around you and is bursting with manettes.~" + + + + +//blackshop +//common +"dota_tooltip_ability_item_blackshop_common_injector" "Injector" +"dota_tooltip_ability_item_blackshop_common_injector_Description" "

Last resort

Increases attack and movement speed by %attack_speed%.

Increases by another %attack_speed% for each subsequent use." +"dota_tooltip_ability_item_blackshop_common_injector_Lore" "An experimental stimulator developed by the alchemists of the Secret Store. Its creators are said to have disappeared after using their own invention too often." +"dota_tooltip_ability_item_blackshop_common_injector_Note0" "Can be retrieved multiple times." + +"dota_tooltip_ability_item_blackshop_common_bonus_stats_agi" "Agility Book" +"dota_tooltip_ability_item_blackshop_common_bonus_stats_agi_Description" "

Path of thousand steps

Increases agility by %bonus_agility%.

Increases by another %bonus_agility% for each subsequent use." +"dota_tooltip_ability_item_blackshop_common_bonus_stats_agi_Lore" "An ancient manuscript written by a legendary martial artist. Each page is imbued with the wisdom of movements and techniques honed through years of practice." +"dota_tooltip_ability_item_blackshop_common_bonus_stats_agi_Note0" "Can be received multiple times." + +"dota_tooltip_ability_item_blackshop_common_bonus_stats_str" "Strength Book" +"dota_tooltip_ability_item_blackshop_common_bonus_stats_str_Description" "

Titan's might

Increases strength by %bonus_strength%.

Increases by another %bonus_strength% for each subsequent use." +"dota_tooltip_ability_item_blackshop_common_bonus_stats_str_Lore" "Heavy tome found in the ruins of an ancient titan temple. Its pages are made of metal, and the letters are carved by the gods themselves." +"dota_tooltip_ability_item_blackshop_common_bonus_stats_str_Note0" "Can be retrieved multiple times." + +"dota_tooltip_ability_item_blackshop_common_bonus_stats_int" "Intelligence Book" +"dota_tooltip_ability_item_blackshop_common_bonus_stats_int_Description" "

Secrets of the universe

Increases intelligence by %bonus_intelligence%.

Increases intelligence by another %bonus_intelligence% for each subsequent use." +"dota_tooltip_ability_item_blackshop_common_bonus_stats_int_Lore" "A mysterious grimoire whose pages are constantly changing. They say that every reader sees different content in it, but they all become wiser." +"dota_tooltip_ability_item_blackshop_common_bonus_stats_int_Note0" "Can be retrieved multiple times." + +"dota_tooltip_ability_item_blackshop_common_king_crown" "King's Crown" +"dota_tooltip_ability_item_blackshop_common_king_crown_Description" "

Royal grandeur

Increases health by %bonus_health% and armor by %bonus_armor%.

For each subsequent use, increases by another %bonus_health% and %bonus_armor%." +"dota_tooltip_ability_item_blackshop_common_king_crown_Lore" "The crown that kings and kings wore. Her golden crowns and diamonds shine in the sun's rays as the king looks at her." +"dota_tooltip_ability_item_blackshop_common_king_crown_Note0" "Can be retrieved multiple times." + +"dota_tooltip_ability_item_blackshop_common_manaflare" "Mana flame" +"dota_tooltip_ability_item_blackshop_common_manaflare_Description" "

Energy flow

Increases mana by %bonus_mana% and mana regen by %bonus_mana_regen%.

Increases by another %bonus_mana% and %bonus_mana_regen%." +"dota_tooltip_ability_item_blackshop_common_manaflare_Lore" "Mana flyer that can be used to create mana illusions." +"dota_tooltip_ability_item_blackshop_common_manaflare_Note0" "Can be retrieved multiple times." + +"dota_tooltip_ability_item_blackshop_common_spell_mask" "Spell mask" +"dota_tooltip_ability_item_blackshop_common_spell_mask_Description" "

Secret art

Strengthens spells by %bonus_spell_amp%%% and mana regeneration by %bonus_mana_regen%.

For each subsequent use, increases by another %bonus_spell_amp%%% and %bonus_mana_regen%." +"dota_tooltip_ability_item_blackshop_common_spell_mask_Lore" "An ancient mask found in the ruins of a temple of magicians. She is said to have been created by a great archmage to strengthen her spells, but the cost of such power was too high." +"dota_tooltip_ability_item_blackshop_common_spell_mask_Note0" "Can be retrieved multiple times." + +"dota_tooltip_ability_item_blackshop_common_boo_stuff" "Staff of great Boo" +"dota_tooltip_ability_item_blackshop_common_boo_stuff_Description" "

Warrior's hand

Increases damage by %bonus_stats% and attack range by %bonus_attack_range%.

Increases by another %bonus_stats% and %bonus_attack_range%." +"dota_tooltip_ability_item_blackshop_common_boo_stuff_Lore" "Legendary weapon that belonged to the great warrior Boo. According to legend, he could hit the enemy at an incredible distance, and his blows shook the ground." +"dota_tooltip_ability_item_blackshop_common_boo_stuff_Note0" "Can be obtained multiple times.

The attack range is increased only for melee heroes." + +"dota_tooltip_ability_item_blackshop_common_stone_armor" "Stone armor" +"dota_tooltip_ability_item_blackshop_common_stone_armor_Description" "

Stone skin

Increases armor by %bonus_armor%.

Increases by another %bonus_armor% for each subsequent use." +"dota_tooltip_ability_item_blackshop_common_stone_armor_Lore" "A piece of ancient armor forged from a meteor stone. Even the sharpest blades can't leave scratches on it." +"dota_tooltip_ability_item_blackshop_common_stone_armor_Note0" "Can be retrieved multiple times." + +"dota_tooltip_ability_item_blackshop_common_vigor_tincture" "Vigor Tincture" +"dota_tooltip_ability_item_blackshop_common_vigor_tincture_Description" "

Second wind

Increases health regeneration by %bonus_hp_regen%.

Each subsequent use adds another %bonus_hp_regen%." +"dota_tooltip_ability_item_blackshop_common_vigor_tincture_Lore" "A black market blend of herbs and essences. It smells of iron and bitter root—but wounds close faster." +"dota_tooltip_ability_item_blackshop_common_vigor_tincture_Note0" "Can be retrieved multiple times." +"dota_tooltip_ability_item_blackshop_common_vigor_tincture_bonus_hp_regen" "+$hp_regen" + +"dota_tooltip_ability_item_blackshop_common_wind_dust" "Granite boots" +"dota_tooltip_ability_item_blackshop_common_wind_dust_Description" "

Light step

Increases movement speed by %bonus_movement_speed%.

Each subsequent use adds another %bonus_movement_speed%." +"dota_tooltip_ability_item_blackshop_common_wind_dust_Lore" "Tiny crystals carried from mountain passes. Scatter them—and your feet find their own way forward." +"dota_tooltip_ability_item_blackshop_common_wind_dust_Note0" "Can be retrieved multiple times." +"dota_tooltip_ability_item_blackshop_common_wind_dust_bonus_movement_speed" "+$move_speed" + +"dota_tooltip_ability_item_blackshop_common_blue_tallow" "Blue Tallow" +"dota_tooltip_ability_item_blackshop_common_blue_tallow_Description" "

Mana flow

Increases mana regeneration by %bonus_mana_regen%.

Each subsequent use adds another %bonus_mana_regen%." +"dota_tooltip_ability_item_blackshop_common_blue_tallow_Lore" "Solidified essence from a ley stream. It melts in the hand and cools the veins until thoughts run clear." +"dota_tooltip_ability_item_blackshop_common_blue_tallow_Note0" "Can be retrieved multiple times." +"dota_tooltip_ability_item_blackshop_common_blue_tallow_bonus_mana_regen" "+$mana_regen" + +//rare +"dota_tooltip_ability_item_blackshop_rare_agility_cape" "Agility cape" +"dota_tooltip_ability_item_blackshop_rare_agility_cape_Description" "

Light movement

Increases agility by %bonus_agility%.

Increases by another %bonus_agility% for each subsequent use." +"dota_tooltip_ability_item_blackshop_rare_agility_cape_Lore" "A cloak that allows its wearer to move faster than normal. Its creators, believing that this artifact could be dangerous, sealed it in a chest that was found in the ruins of an ancient temple." +"dota_tooltip_ability_item_blackshop_rare_agility_cape_Note0" "Can be retrieved multiple times." + +"dota_tooltip_ability_item_blackshop_rare_critical_havoc" "Critical soul destator" +"dota_tooltip_ability_item_blackshop_rare_critical_havoc_Description" "

Critical developer

Gives %crit_chance%%% a chance to strike critically with a force of %crit_mult%%%." +"dota_tooltip_ability_item_blackshop_rare_critical_havoc_Lore" "An ancient blade forged in the flames of a dying star. Each strike of this weapon carries within itself a particle of cosmic energy capable of tearing apart the very fabric of reality." +"dota_tooltip_ability_item_blackshop_rare_critical_havoc_Note0" "Can only be retrieved once.

Reset will allow the item to reappear." + +"dota_tooltip_ability_item_blackshop_rare_damage_dagger" "Blade of destruction" +"dota_tooltip_ability_item_blackshop_rare_damage_dagger_Description" "

Blade of destruction

Increases damage by %bonus_damage% per hit, then goes to recharge.

Increases by another %bonus_damage% for each subsequent use." +"dota_tooltip_ability_item_blackshop_rare_damage_dagger_Lore" "A legendary blade that can destroy not only enemies, but reality itself. Its creators, believing that this artifact could be dangerous, sealed it in a chest that was found in the ruins of an ancient temple." +"dota_tooltip_ability_item_blackshop_rare_damage_dagger_Note0" "Can be retrieved multiple times." + +"dota_tooltip_ability_item_blackshop_rare_egg_of_death" "Egg of death" +"dota_tooltip_ability_item_blackshop_rare_egg_of_death_Description" "

Egg of death

Increases agility, strength and intelligence by %bonus_all%.

Increases by another %bonus_all% for each subsequent use." +"dota_tooltip_ability_item_blackshop_rare_egg_of_death_Lore" "Egg that can be used to create illusions of death. Its creators, believing that this artifact could be dangerous, sealed it in a chest that was found in the ruins of an ancient temple." +"dota_tooltip_ability_item_blackshop_rare_egg_of_death_Note0" "Can be retrieved multiple times." + +"dota_tooltip_ability_item_blackshop_rare_granite_badge" "Granite Badge" +"dota_tooltip_ability_item_blackshop_rare_granite_badge_Description" "

Stone heart

Increases maximum health by %bonus_health%.

Each subsequent use adds another %bonus_health%." +"dota_tooltip_ability_item_blackshop_rare_granite_badge_Lore" "A metal plate flecked with rune-quarry granite. The bearer feels wrapped in a load-bearing wall." +"dota_tooltip_ability_item_blackshop_rare_granite_badge_Note0" "Can be retrieved multiple times." +"dota_tooltip_ability_item_blackshop_rare_granite_badge_bonus_health" "+$health" + +"dota_tooltip_ability_item_blackshop_rare_iron_resolve" "Iron Resolve" +"dota_tooltip_ability_item_blackshop_rare_iron_resolve_Description" "

Unyielding

Increases status resistance by %bonus_status_resist%%%.

Each subsequent use adds another %bonus_status_resist%%%." +"dota_tooltip_ability_item_blackshop_rare_iron_resolve_Lore" "A knight's oath-cloth, blood-seasoned and forge-hardened. Debuffs find less to hold on to." +"dota_tooltip_ability_item_blackshop_rare_iron_resolve_Note0" "Can be retrieved multiple times." + +"dota_tooltip_ability_item_blackshop_rare_silver_eye" "Hawk eye" +"dota_tooltip_ability_item_blackshop_rare_silver_eye_Description" "

Silver gaze

Increases view by %bonus_vision% units, also increases attack range and spell range by %bonus_vision% units.

For each subsequent use, increases by another %bonus_vision%." +"dota_tooltip_ability_item_blackshop_rare_silver_eye_Lore" "An artifact that can enhance the vision of its owner. Its creators, believing that this artifact could be dangerous, sealed it in a chest that was found in the ruins of an ancient temple." +"dota_tooltip_ability_item_blackshop_rare_silver_eye_Note0" "Can only be retrieved once.

Reset will allow the item to reappear.

Increasing the attack range does not work for melee heroes." + +//epic +"dota_tooltip_ability_item_blackshop_epic_power_of_grow" "Magic mushroom" +"dota_tooltip_ability_item_blackshop_epic_power_of_grow_Description" "

Power of growth

Increases character size by %bonus_stats%%% and increases damage for every extra percentage." +"dota_tooltip_ability_item_blackshop_epic_power_of_grow_Lore" "A magical mushroom grown in the secret gardens of ancient druids. They say that these mushrooms fed on the pure vitality of the earth, which is why they reached incredible sizes. Druids used them in their growth rituals, but eventually lost control of this power when their students began to abuse these gifts of nature in pursuit of power." +"dota_tooltip_ability_item_blackshop_epic_power_of_grow_Note0" "Can only be retrieved once.

Reset will allow the item to reappear." +"dota_tooltip_modifier_item_power_of_grow_scale" "Magic mushroom" +"dota_tooltip_modifier_item_power_of_grow_scale_Description" "Character size increased by %dMODIFIER_PROPERTY_MODEL_SCALE%%%" + +"dota_tooltip_ability_item_blackshop_epic_critical_paladin_sword" "Paladin blade" +"dota_tooltip_ability_item_blackshop_epic_critical_paladin_sword_Description" "

Paladin blade

Critical impact damage on %crit_multiplier%%%." +"dota_tooltip_ability_item_blackshop_epic_critical_paladin_sword_Lore" "A sword that can strike critically with great force. Its creators, believing that this artifact could be dangerous, sealed it in a chest that was found in the ruins of an ancient temple." +"dota_tooltip_ability_item_blackshop_epic_critical_paladin_sword_Note0" "Multiple times can be retrieved.

ITEM CAN ONLY BE TRIGGERED WITH A GUARANTEED CRITICAL IMPACT" + +"dota_tooltip_ability_item_blackshop_epic_trinity_seal" "Trinity Seal" +"dota_tooltip_ability_item_blackshop_epic_trinity_seal_Description" "

Threefold pattern

Increases Strength, Agility and Intelligence by %bonus_all% each.

Each subsequent use adds another %bonus_all% to all three attributes." +"dota_tooltip_ability_item_blackshop_epic_trinity_seal_Lore" "A metal seal of three interlocking rings. Forged in a workshop where cross-realm contracts were bound—the bearer absorbs a sliver of power from every facet of existence." +"dota_tooltip_ability_item_blackshop_epic_trinity_seal_Note0" "Can be retrieved multiple times." +"dota_tooltip_ability_item_blackshop_epic_trinity_seal_bonus_all" "+$all" + +"dota_tooltip_ability_item_blackshop_epic_bulwark_plate" "Bulwark Plate" +"dota_tooltip_ability_item_blackshop_epic_bulwark_plate_Description" "

Siegebreaker skin

Grants +%bonus_armor_per% armor and +%bonus_mr_per%%% magic resistance.

Each further use adds the same amount again." +"dota_tooltip_ability_item_blackshop_epic_bulwark_plate_Lore" "Runed steel sheeted for siege towers. On a hero it becomes a walking bastion." +"dota_tooltip_ability_item_blackshop_epic_bulwark_plate_Note0" "Can be retrieved multiple times." +"dota_tooltip_modifier_item_bulwark_plate_display" "Bulwark Plate" +"dota_tooltip_modifier_item_bulwark_plate_display_Description" "Bastion protection" + +//legendary +"dota_tooltip_ability_item_blackshop_legendary_restock" "Cycle of Prosperity" +"dota_tooltip_ability_item_blackshop_legendary_restock_Description" "

Reality is not the limit

After purchasing an item in a black store, another item from the current pool will appear in place of the purchased one." +"dota_tooltip_ability_item_blackshop_legendary_restock_Lore" "A mysterious artifact created by ancient traders to maintain an endless flow of goods. His magic is said to be capable of materializing any object that has ever existed in this world." +"dota_tooltip_ability_item_blackshop_legendary_restock_Note0" "Can only be retrieved once.

Reset will not allow the item to reappear." + +"dota_tooltip_ability_item_blackshop_legendary_fire_summoner" "Elemental Summon Staff" +"dota_tooltip_ability_item_blackshop_legendary_fire_summoner_Description" "

Elemental's Summon Staff

Calls upon the Fiery Elemental who will attack enemies." +"dota_tooltip_ability_item_blackshop_legendary_fire_summoner_Lore" "An ancient staff forged in the heart of a volcano from the purest obsidian and imbued with the energy of a primordial flame. Legends say that the first elementalist used him to make a pact with the Firelord, gaining power over his fiery servants." +"dota_tooltip_ability_item_blackshop_legendary_fire_summoner_Note0" "Can be retrieved multiple times.

Reset will allow the item to reappear.

Gets its owner's crete modifiers." + +"dota_tooltip_ability_item_blackshop_legendary_primordial_shard" "Primordial Shard" +"dota_tooltip_ability_item_blackshop_legendary_primordial_shard_Description" "

First matter

Increases Strength, Agility and Intelligence by %bonus_all% each.

Each subsequent use adds another %bonus_all% to all three attributes." +"dota_tooltip_ability_item_blackshop_legendary_primordial_shard_Lore" "A splinter of matter from before the elements split. It still whispers of a world with neither spell nor blade—only undivided power." +"dota_tooltip_ability_item_blackshop_legendary_primordial_shard_Note0" "Can be retrieved multiple times." +"dota_tooltip_ability_item_blackshop_legendary_primordial_shard_bonus_all" "+$all" +"dota_tooltip_modifier_item_primordial_shard" "Primordial Shard" +"dota_tooltip_modifier_item_primordial_shard_Description" "Bonus Strength, Agility and Intelligence" + +"dota_tooltip_ability_item_blackshop_legendary_twilight_mirror" "Twilight Mirror" +"dota_tooltip_ability_item_blackshop_legendary_twilight_mirror_Description" "

Reflected power

Permanently grants %spell_amp_pct%%% spell amplification and increases all incoming damage by %incoming_damage_pct%%%." +"dota_tooltip_ability_item_blackshop_legendary_twilight_mirror_Lore" "A disk of frozen gloom: spells come easier through it—and so do blades." +"dota_tooltip_ability_item_blackshop_legendary_twilight_mirror_Note0" "Once per hero: if you already have the effect, the item is not consumed." +"dota_tooltip_modifier_item_twilight_mirror" "Twilight Mirror" +"dota_tooltip_modifier_item_twilight_mirror_Description" "Spell amp and increased damage taken" + +"dota_tooltip_ability_item_blackshop_legendary_astral_anchor" "Astral Anchor" +"dota_tooltip_ability_item_blackshop_legendary_astral_anchor_Description" "

Reach without haste

Permanently grants +%cast_range_bonus% cast range but reduces movement speed by %move_speed_loss_pct%%%." +"dota_tooltip_ability_item_blackshop_legendary_astral_anchor_Lore" "A crystal grown where magic reaches farther than flesh—and legs struggle to follow." +"dota_tooltip_ability_item_blackshop_legendary_astral_anchor_Note0" "Once per hero: if you already have the effect, the item is not consumed." +"dota_tooltip_modifier_item_astral_anchor" "Astral Anchor" +"dota_tooltip_modifier_item_astral_anchor_Description" "Cast range and slower movement" + +"dota_tooltip_ability_item_blackshop_legendary_fated_die" "Fated Die" +"dota_tooltip_ability_item_blackshop_legendary_fated_die_Description" "

Fortune

Permanently increases the hero's Luck by %luck_bonus% (affects drop chances and other mode systems)." +"dota_tooltip_ability_item_blackshop_legendary_fated_die_Lore" "A die forged from meteor iron—it falls the way fortune wants, not gravity." +"dota_tooltip_ability_item_blackshop_legendary_fated_die_Note0" "Can be obtained multiple times; Luck stacks." + +//cursed +"dota_tooltip_ability_item_blackshop_cursed_the_hand_of_gluttony" "Eater's hand" +"dota_tooltip_ability_item_blackshop_cursed_the_hand_of_gluttony_Description" "

Eater Hand

When attacked, the hero is treated at %bonus_stats%% of damage dealt.

For each subsequent use, he increases by another %bonus_stats%%%.

Loss of control


When attacking, the hero can lose control of himself for 3 seconds.


Imbalance


The hero can attack allies and can be attacked by them himself.
" +"dota_tooltip_ability_item_blackshop_cursed_the_hand_of_gluttony_Lore" "A hand that can select a random ally and attack it. Its creators, believing that this artifact could be dangerous, sealed it in a chest that was found in the ruins of an ancient temple." +"dota_tooltip_ability_item_blackshop_cursed_the_hand_of_gluttony_Note0" "Can be retrieved multiple times.

Reset will not allow item to reappear." + +"dota_tooltip_ability_item_blackshop_cursed_martyrs_brand" "Martyr's Brand" +"dota_tooltip_ability_item_blackshop_cursed_martyrs_brand_Description" "

Blood for blows

Each use adds a stack: +%bonus_damage_per% attack damage and +%incoming_damage_per_stack%%% incoming damage per stack.

Stacks add up." +"dota_tooltip_ability_item_blackshop_cursed_martyrs_brand_Lore" "An iron sigil burned into the flesh: the harder you swing, the harder they swing back." +"dota_tooltip_ability_item_blackshop_cursed_martyrs_brand_Note0" "Can be obtained multiple times; stacks add up." +"dota_tooltip_modifier_item_martyrs_brand" "Martyr's Brand" +"dota_tooltip_modifier_item_martyrs_brand_Description" "Attack damage and incoming damage per stack" + +"dota_tooltip_ability_item_blackshop_cursed_widow_chain" "Widow's Chain" +"dota_tooltip_ability_item_blackshop_cursed_widow_chain_Description" "

Heavy blow

Each stack: +%bonus_outgoing_damage_pct%%% outgoing damage and −%attack_speed_loss_pct%%% attack speed. Each attack roots the hero for %root_duration% s." +"dota_tooltip_ability_item_blackshop_cursed_widow_chain_Lore" "The chain pulls you down: the strike promises blood, but your legs tangle in the links." +"dota_tooltip_ability_item_blackshop_cursed_widow_chain_Note0" "Can be obtained multiple times; stacks add up. Root applies to the attacker." +"dota_tooltip_modifier_item_widow_chain" "Widow's Chain" +"dota_tooltip_modifier_item_widow_chain_Description" "Damage, slower attacks, self-root on hit" +"dota_tooltip_modifier_item_widow_chain_root" "Chain snare" +"dota_tooltip_modifier_item_widow_chain_root_Description" "Rooted" + +"dota_tooltip_ability_item_blackshop_cursed_glass_pact" "Glass Pact" +"dota_tooltip_ability_item_blackshop_cursed_glass_pact_Description" "

Brittle might

Permanently increases damage you deal by %outgoing_damage_pct%%% and damage you take by %incoming_damage_pct%%%." +"dota_tooltip_ability_item_blackshop_cursed_glass_pact_Lore" "A pact etched on a clear blade—it mirrors your strike, and theirs." +"dota_tooltip_ability_item_blackshop_cursed_glass_pact_Note0" "Once per hero: if the pact is already active, the item is not consumed." +"dota_tooltip_modifier_item_glass_pact" "Glass Pact" +"dota_tooltip_modifier_item_glass_pact_Description" "More damage dealt and taken" + +//heavenly +"dota_tooltip_ability_item_blackshop_heavenly_reset_to_zero" "Start from end" +"dota_tooltip_ability_item_blackshop_heavenly_reset_to_zero_Description" "

Rebirth

Resets the entire pool of items allowing you to purchase those that have been removed from it." +"dota_tooltip_ability_item_blackshop_heavenly_reset_to_zero_Lore" "An ancient artifact created by a powerful creature who was looking for a way to start over. Its creator is said to have used the power of time itself to erase the past and start with a clean slate. However, such power comes at a price - once an artifact is used, its owner forever loses the opportunity to return to his previous choice." +"dota_tooltip_ability_item_blackshop_heavenly_reset_to_zero_Note0" "Can only be retrieved once. Reset will not allow the item to reappear." + +"dota_tooltip_ability_item_blackshop_heavenly_font_of_mercy" "Font of Mercy" +"dota_tooltip_ability_item_blackshop_heavenly_font_of_mercy_Description" "

Shared healing

Consumed on activation. Heals allied heroes within %radius% for %heal_flat% plus %heal_max_hp_pct%%% of their max health and purges debuffs. The blessing then stays on your hero while alive and will automatically trigger again once its cooldown is ready and a real allied hero inside the radius is below %crisis_hp_pct%%% health. If the hero dies, the Font blessing and all related effects are lost.

The first downtime is %hero_cooldown% s. Each later trigger halves the next downtime (from the base %hero_cooldown% s), down to a minimum of 12 s." +"dota_tooltip_ability_item_blackshop_heavenly_font_of_mercy_Lore" "A vessel filled with nothing but compassion—strong enough to wash over every defender nearby." +"dota_tooltip_ability_item_blackshop_heavenly_font_of_mercy_Note0" "Only one Font per hero—you cannot activate another item. Illusions do not count toward the health check. Buff stacks equal how many heals have fired. Hero death removes the Font blessing." +"dota_tooltip_modifier_item_heavenly_font_of_mercy_display" "Font of Mercy" +"dota_tooltip_modifier_item_heavenly_font_of_mercy_display_Description" "Font blessing. Stack count is how many mass heals have fired. Removed when the hero dies." +"dota_tooltip_modifier_item_heavenly_font_of_mercy_cooldown" "Font of Mercy: recharge" +"dota_tooltip_modifier_item_heavenly_font_of_mercy_cooldown_Description" "Time until the next automatic heal can trigger (when the health condition is met). Removed on death along with the blessing." + +"dota_tooltip_ability_item_blackshop_heavenly_sanctuary_veil" "Sanctuary Veil" +"dota_tooltip_ability_item_blackshop_heavenly_sanctuary_veil_Description" "

Veil

Purges debuffs. Each use adds the item's fixed bonuses: +%status_resist%%% status resistance and +%magic_resist%%% magic resistance. Further uses add the same constants again (totals stack). Effect does not expire or purge." +"dota_tooltip_ability_item_blackshop_heavenly_sanctuary_veil_Lore" "Cloth woven from temple silence—the more veils you lay on, the longer the calm stays with you." +"dota_tooltip_ability_item_blackshop_heavenly_sanctuary_veil_Note0" "Consumed on use. Persists through death." +"dota_tooltip_modifier_item_heavenly_sanctuary_veil_display" "Sanctuary Veil" +"dota_tooltip_modifier_item_heavenly_sanctuary_veil_display_Description" "Veil blessing. Stacks show how many times you used the item; status resist and magic resist accumulate from the item's fixed values each time." +"dota_tooltip_modifier_item_heavenly_sanctuary_veil_mr" "Veil: magic resist" +"dota_tooltip_modifier_item_heavenly_sanctuary_veil_mr_Description" "Total magic resistance accumulated from all veil uses (per use equals the item's magic resistance value)." + +"dota_tooltip_ability_item_blackshop_heavenly_dawn_chorus" "Dawn Chorus" +"dota_tooltip_ability_item_blackshop_heavenly_dawn_chorus_Description" "

Shared ward

Within %radius% radius, empowers allied heroes with +%status_resist%%% status resistance and +%magic_resist%%% magic resistance.

Each further use adds the same amount again to everyone in the area. Effect does not expire or purge." +"dota_tooltip_ability_item_blackshop_heavenly_dawn_chorus_Lore" "First light gathered into sound—it lingers while the flock remembers dawn." +"dota_tooltip_ability_item_blackshop_heavenly_dawn_chorus_Note0" "Consumed on use. Does not affect illusions. Persists through death." +"dota_tooltip_modifier_item_heavenly_dawn_chorus_display" "Dawn Chorus" +"dota_tooltip_modifier_item_heavenly_dawn_chorus_display_Description" "Echo of dawn" + +// -----------------Axe-------------------- +"dota_tooltip_ability_axe_berserkers_call_custom" "Berserker's Call" +"dota_tooltip_ability_axe_berserkers_call_custom_Description" "Taunts enemies within %radius% to attack Axe and grants him %bonus_armor% armor for %duration% sec." +"dota_tooltip_ability_axe_berserkers_call_custom_Lore" "When Axe roars, even fear learns to obey." +"dota_tooltip_ability_axe_berserkers_call_custom_Shard_Description" "With Aghanim's Shard, Berserker's Call also applies Battle Hunger to enemies, Axe, and allied heroes in the radius." +"dota_tooltip_ability_axe_berserkers_call_custom_radius" "RADIUS:" +"dota_tooltip_ability_axe_berserkers_call_custom_bonus_armor" "BONUS ARMOR:" +"dota_tooltip_ability_axe_berserkers_call_custom_duration" "DURATION:" + +"dota_tooltip_ability_axe_battle_hunger_custom" "Battle Hunger" +"dota_tooltip_ability_axe_battle_hunger_custom_Description" "On an enemy: applies hunger for %duration% sec, slowing them when they move away from Axe and dealing %damage_per_second% damage per second plus damage from Axe's armor. On an ally: grants 5 hunger stacks per second, heals, and increases outgoing damage by %speed_bonus%%%." +"dota_tooltip_ability_axe_battle_hunger_custom_Lore" "Axe's hunger is not about food. It is about a fight where no one leaves satisfied." +"dota_tooltip_ability_axe_battle_hunger_custom_slow" "%MOVE SLOW:" +"dota_tooltip_ability_axe_battle_hunger_custom_damage_per_second" "DAMAGE PER SECOND:" +"dota_tooltip_ability_axe_battle_hunger_custom_duration" "DURATION:" +"dota_tooltip_ability_axe_battle_hunger_custom_speed_bonus" "%OUTGOING DAMAGE:" + +"dota_tooltip_ability_axe_counter_helix_custom" "Counter Helix" +"dota_tooltip_ability_axe_counter_helix_custom_Description" "When damaged, Axe has a %trigger_chance%%% chance to Counter Helix, dealing %damage% pure damage to enemies within %radius% and striking them." +"dota_tooltip_ability_axe_counter_helix_custom_Lore" "The closer foes get, the faster they learn it was a mistake." +"dota_tooltip_ability_axe_counter_helix_custom_trigger_chance" "%PROC CHANCE:" +"dota_tooltip_ability_axe_counter_helix_custom_radius" "RADIUS:" +"dota_tooltip_ability_axe_counter_helix_custom_damage" "DAMAGE:" + +"dota_tooltip_ability_axe_culling_blade_custom" "Culling Blade" +"dota_tooltip_ability_axe_culling_blade_custom_Description" "Strikes enemies around the target point (radius %cull_radius%). Deals damage to each equal to Axe's current attack damage. On a kill, grants permanent armor: %armor_per_stack% per hero, %armor_per_creep_kill% per creep. On a successful kill, speeds up allies within %speed_aoe%." +"dota_tooltip_ability_axe_culling_blade_custom_Lore" "Axe's verdict is short: those who meet the axe have already lost." +"dota_tooltip_ability_axe_culling_blade_custom_Scepter_Description" "With Aghanim's Scepter, each successful kill with Culling Blade reduces this ability's remaining cooldown by 1 sec per victim." +"dota_tooltip_ability_axe_culling_blade_custom_Shard_Description" "With Aghanim's Shard, each creep kill by Axe reduces the remaining cooldown of all his abilities by 1 sec." +"dota_tooltip_ability_axe_culling_blade_custom_cull_radius" "RADIUS:" +"dota_tooltip_ability_axe_culling_blade_custom_speed_bonus" "%MOVE SPEED:" +"dota_tooltip_ability_axe_culling_blade_custom_attack_speed_bonus" "ATTACK SPEED:" +"dota_tooltip_ability_axe_culling_blade_custom_speed_duration" "BUFF DURATION:" +"dota_tooltip_ability_axe_culling_blade_custom_speed_aoe" "SPEED AURA RADIUS:" +"dota_tooltip_ability_axe_culling_blade_custom_armor_per_stack" "ARMOR PER HERO KILL:" +"dota_tooltip_ability_axe_culling_blade_custom_armor_per_creep_kill" "ARMOR PER CREEP KILL:" + +"dota_tooltip_ability_axe_one_man_army_custom" "One Man Army" +"dota_tooltip_ability_axe_one_man_army_custom_Description" "If no other allied heroes are within %radius%, Axe gains Strength from armor: %armor_to_str% Strength per 1 armor." +"dota_tooltip_ability_axe_one_man_army_custom_Lore" "Axe does not seek an army. Axe is the army." +"dota_tooltip_ability_axe_one_man_army_custom_radius" "CHECK RADIUS:" +"dota_tooltip_ability_axe_one_man_army_custom_armor_to_str" "STRENGTH PER ARMOR:" + +"dota_tooltip_ability_special_bonus_unique_axe_2" "+150 Culling Blade radius" +"dota_tooltip_ability_special_bonus_unique_axe_8" "+0.2 Strength per armor for One Man Army" +"dota_tooltip_ability_special_bonus_unique_axe" "+200 Counter Helix damage" +"dota_tooltip_ability_special_bonus_unique_axe_7" "+10 Berserker's Call armor" +"dota_tooltip_ability_special_bonus_unique_axe_4" "+8 Battle Hunger damage per second" +"dota_tooltip_ability_special_bonus_unique_axe_5" "+85 Berserker's Call radius" +"dota_tooltip_ability_special_bonus_unique_axe_culling_blade_speed_duration" "+3 sec. Culling Blade buff duration" + +//Maps +"card_1_name" "Bloodway" +"card_1_description" "+%vampirism_bonus%% to vampirism. Maximum health −%max_health_penalty_pct%%%.
+%damage_multiplier%% to all physical damage per unit of vampirism" +"card_1_vampirism_bonus" "VAMPIRISM:" +"card_1_damage_multiplier" "DAMAGE PER VAMPIRISM:" +"card_1_max_health_penalty_pct" "MAX HEALTH REDUCTION:" + +"card_2_name" "Light pen" +"card_2_description" "Grants %speed_bonus% movement speed." +"card_2_description_level_2" "Level 2: increases movement speed limit to %speed_limit%." +"card_2_description_level_3" "Level 3: the hero can pass through units; base attack time reduced by %attacktime%%%." + +"card_3_name" "Spirit power" +"card_3_description" "+%damage_pct%% to all physical and magical damage." + +"card_4_name" "Rampage of the spirit" +"card_4_description" "Mixes %card_bonus% of the Spirit Power card into the player pool." + +"card_5_name" "Card madness" +"card_5_description" "For each card taken before this one, grants %card_bonus_pct%% bonus Strength, Agility, and Intelligence." + +"card_6_name" "Happy hand" +"card_6_description" "When picked: an extra selection of %card_show_count% cards. Permanently raises the minimum cards shown in every selection to %min_card_choice%." +"card_6_description_level_2" "Level 2: +%free_reroll_charges% free reroll(s)." +"card_6_description_level_3" "Level 3: this selection includes Legendary cards only." +"card_6_card_show_count" "EXTRA SELECTION CARDS:" +"card_6_min_card_choice" "MIN CARDS PER SELECTION:" +"card_6_free_reroll_charges" "FREE REROLLS:" + +"card_7_name" "Curse
solar eclipse
" +"card_7_description" "At night:
+%daynight_bonus_pct%% to all characteristics.
During the day:
-%daynight_bonus_pct%% to all characteristics.
" + +"card_8_name" "Curse
spray heart
" +"card_8_description" "For each kill, the hero forever loses %damage_bonus_health_decress% of health, also permanently increases damage by %damage_bonus_health_decress%. Kill stacks are only gained while the owner's health is at least %stack_health_threshold%." + +"card_9_name" "Curse
alak midas
" +"card_9_description" "Grants +1 stack of shared Greed. On kills, grants bonus gold: +%kill_gold_bonus_pct%%% of the target bounty per card copy. Attribute bonus from net worth (see the Greed buff).
On death, the hero loses all gold and random main-inventory items based on card copies." + + +"card_10_name" "Ashen Seal" +"card_10_description" "Attacks apply a debuff for %debuff_duration% sec.
The target takes +%incoming_damage_per_fired_stack_pct%%% incoming damage per burn stack (modifier_general_fired), up to +%max_incoming_damage_bonus_pct%%%. This bonus works only for the card owner's damage." +"card_11_name" "Growth of Ambitions" +"card_11_description" "Grants a level-based bonus to the primary attribute.
If the primary attribute is not universal: +%primary_attribute_bonus_per_level% to the primary attribute per level. If the primary attribute is universal: +%universal_all_bonus_per_level% to all attributes per level." +"card_12_name" "Eye of the Night Abyss" +"card_12_description" "Every %scan_interval% sec marks the strongest enemy within %scan_radius% radius for %mark_duration% sec
Against marked target: +%bonus_damage_pct%%% damage. Killing a marked target restores %on_kill_heal_pct%%% of max health and grants +%on_kill_mana% mana. Bonus damage is reduced by %boss_penalty_pct%%% against bosses." +"card_13_name" "Eye of the Burning Abyss" +"card_13_description" "Each attack applies %fired_stacks_on_hit% burn stacks
With a %explosion_chance_pct%%% chance, the target explodes in a %explosion_radius% radius. Explosion damage: (burn stacks × %explosion_damage_per_stack%) + attacker's damage." +"card_14_name" "Infernal Ricochet" +"card_14_description" "With a %proc_chance_pct%%% chance, attacks ignite the target and launch ricochet projectiles within %ricochet_radius% radius
Main target: +%main_target_bonus_damage_pct%%% magical damage based on attack damage and %fired_stacks% burn stack(s). Each ricochet is a separate attack dealing %ricochet_damage_pct%%% attack damage and applying %fired_stacks% burn stack(s)." +"card_15_name" "Crimson Sickle" +"card_15_description" "Grants a fixed %base_crit_chance_pct%%% chance to deal a custom critical hit.
Critical damage scales with Luck: %crit_multiplier_pct%%% + %crit_multiplier_bonus_pct_per_luck%%% per 1 Luck." +"card_16_name" "Void Shards" +"card_16_description" "Each enemy kill grants 1 stack (up to %max_stacks%).
Each stack grants +%spell_amp_per_stack_pct%%% spell amplification." +"card_17_name" "Curse of the Bloody Hand" +"card_17_description" "On attack, if health is above %min_health_pct_to_activate%%% , the hero spends %health_cost_pct%%% of max health.
Spent health becomes pure damage; damage is further scaled by curse stacks: (1 + stacks × %curse_damage_pct_per_curse_stack%% / 100)." +"card_18_name" "Lucky Glasses" +"card_18_description" "Passively grants +%base_luck_bonus% Luck.
Additionally grants +%luck_per_card_taken% Luck for each card taken." +"card_19_name" "Empty Kimono" +"card_19_description" "Attacks have a %ghost_step_chance_pct%%% chance to trigger Ghost Step for %ghost_step_duration% sec.
While active: +%ghost_step_move_speed_pct%%% movement speed, +%ghost_step_evasion_pct%%% evasion, and unit phasing." +"card_20_name" "Golden Boots" +"card_20_description" "Grants +%move_speed_pct_per_step%%% movement speed for each %gold_per_step% gold.
Removes the maximum movement speed limit.
Greed: 1 stack per 250 net worth; +1 to all attributes per stack." +"card_21_name" "Solar Blade" +"card_21_description" "Each hit against npc_wave units deals additional pure damage.
Bonus damage: %wave_pure_damage_base% + owner's attack damage." +"card_22_name" "Coins of Dawn" +"card_22_description" "At each morning after a survived night, grants gold. After night 1 — %morning_gold_base%; %morning_gold_decay_per_night% less for each following night." +"card_22_description_level_2" "Level 2: when picked, +%pickup_gold_pct%%% of the next dawn payout." +"card_22_description_level_3" "Level 3: before night begins, another +%pre_night_gold_pct%%% of that same amount." +"card_23_name" "Golden Growth" +"card_23_description" "Every %gold_tick_interval_sec% seconds, grants bonus gold equal to %gold_income_pct_per_minute%%% of your current gold.
If you earned less than %min_earned_gold_pct_of_dividend_required%%% of the dividend during this interval, no payout is granted.
Greed: 1 stack per 250 net worth; +1 to all attributes per stack." +"card_24_name" "Golden Hand" +"card_24_description" "For every %gold_per_step% gold, the hero gains +%attack_damage_per_step%%% attack damage and +%spell_amp_per_step_pct%%% spell amplification.
Greed: 1 stack per 250 net worth; +1 to all attributes per stack." +"card_25_name" "Mana Core" +"card_25_description" "For every %mana_per_step% current mana, the hero gains +%spell_amp_per_step_pct%%% spell amplification and +%mana_regen_per_step% mana regeneration." +"card_25_description_level_3" "Level 3: +%max_mana_bonus_pct%%% maximum mana." +"card_25_max_mana_bonus_pct" "MAX MANA:" +"card_26_name" "Shadow Vow" +"card_26_description" "The hero's health cannot drop below 1.
If health is below %trigger_health_threshold%, the hero becomes invisible and restores %heal_pct_per_second%%% of max health per second. After the effect ends: %cooldown_seconds% sec cooldown." +"card_27_name" "Mana Blade" +"card_27_description" "%mana_to_damage_pct%%% of the hero's current mana is added to attack damage." +"card_28_name" "Blood Blade" +"card_28_description" "%health_to_damage_pct%%% of the hero's current health is added to attack damage." +"card_29_name" "Blood Moon" +"card_29_description" "At night grants +%night_vampirism_pct%%% physical lifesteal and reduces incoming damage by %night_incoming_damage_reduce_pct%%%." +"card_30_name" "Curse of Growth" +"card_30_description" "Per hero level: +%strength_per_level% Strength, but -%agility_penalty_per_level% Agility and -%intellect_penalty_per_level% Intellect.
Effect is multiplied by the number of curses taken." +"card_31_name" "Curse of Agility" +"card_31_description" "Per hero level: +%agility_per_level% Agility, but -%strength_penalty_per_level% Strength and -%intellect_penalty_per_level% Intellect.
Effect is multiplied by the number of curses taken." +"card_32_name" "Hourglass" +"card_32_description" "Grants +%base_cooldown_reduction_pct%%% cooldown reduction.
After each hero death, the effect is reduced by %cooldown_loss_per_death_pct%%%." +"card_33_name" "Mana Frenzy" +"card_33_description" "One time on pickup, increases maximum mana by %mana_increase_pct%%% of current maximum mana." +"card_33_description_level_3" "Level 3: also grants Minor Mana Frenzy." +"card_34_name" "Blood Harvest" +"card_34_description" "On enemy last-hit, restores health: %heal_from_enemy_max_hp_pct%%% of the target's max health + %heal_from_attack_damage_pct%%% of your attack damage." +"card_35_name" "Poisoned Wound" +"card_35_description" "Attacks poison the enemy. Poison deals %damage_current_hp_pct%%% of the target's current health.
Against npc_boss targets, poison damage is reduced to %boss_damage_current_hp_pct%%% of current health." +"card_36_name" "Crystal Tax" +"card_36_description" "For every %crystals_per_step% crystals spent, gain +%damage_pct_per_step%%% outgoing damage.
Maximum: %max_bonus_pct%%%." +"card_37_name" "Bag of Crystals" +"card_37_description" "On pickup, instantly gain %base_crystals% crystals + %crystals_per_night% for each current night." +"card_38_name" "Crystal Dawn" +"card_38_description" "At the start of each new day, your current crystals are multiplied by %multiplier%x." +"card_39_name" "Crystal Converter" +"card_39_description" "One time on pickup: all your crystals are exchanged for gold at %gold_per_crystal% gold per crystal." +"card_40_name" "Gold Converter" +"card_40_description" "One time on pickup: all your gold is exchanged for crystals at %gold_per_crystal% gold per crystal (gold below the exchange rate is kept)." +"card_41_name" "Twin Blades Reserve" +"card_41_description" "On pickup, shuffles Mana Blade and Blood Blade into your selection pool." +"card_41_description_level_2" "Level 2: immediately choose one of the two blades." +"card_41_description_level_3" "Level 3: additionally +1 Mana Blade and +1 Blood Blade in the pool (4 cards total)." +"card_41_pool_pairs" "BLADE PAIRS IN POOL:" +"card_42_name" "Thirst for Knowledge" +"card_42_description" "Increases experience gained by %exp_bonus_pct%%%." +"card_43_name" "Quick Hands" +"card_43_description" "Reduces base attack time by %bat_reduction%." +"card_43_description_level_2" "Level 2: ranged heroes gain +%projectile_speed_bonus% projectile speed." +"card_43_description_level_3" "Level 3: attack animation time reduced by %attack_anim_reduction_pct%%%." +"card_44_name" "Azure Gale" +"card_44_description" "+%bonus_move_speed%%% movement speed and +%bonus_evasion%%% evasion.
Each Luck: +%luck_bonus_pct_per_point%%% to both (evasion capped at %max_evasion_pct%%%)." +"card_45_name" "Energy Drink" +"card_45_description" "+%max_bonus_pct%%% movement speed, decreasing by %decay_pct_per_minute%%% every minute.
At each new morning, the bonus is restored to %max_bonus_pct%%%." +"card_46_name" "Mana Surge" +"card_46_description" "Restores mana equal to %damage_to_mana_pct%%% of damage taken." +"card_47_name" "Good Start" +"card_47_description" "At game start, your hero gains 1 level. For 3 minutes, increases experience gained by %exp_bonus_pct%%%." +"card_47_exp_bonus_pct" "EXPERIENCE BONUS:" +"card_47_exp_boost_duration_sec" "BONUS DURATION:" +"card_48_name" "Insurance Policy" +"card_48_description" "Once per night: when you drop below %trigger_hp_pct%%% HP after taking damage, restore %shield_heal_max_hp_pct%%% max health and gain %incoming_damage_reduction_pct%%% damage resistance for %insurance_duration% sec." +"card_49_name" "Second Opinion" +"card_49_description" "When picked: choose cards again once.
Each new morning: your first reroll that costs crystals is free." +"card_50_name" "Legendary Draft" +"card_50_description" "When picked: choose one of three random Legendary cards from the full catalog (non-inherent legendaries only)." +"card_51_name" "Midas Chestplate" +"card_51_description" "When you take damage, gain %gold_from_damage_taken_pct%%% gold from that damage. Incoming damage is increased by %incoming_damage_base_pct%%% plus %incoming_damage_per_100_taken%%% for every 100 damage you have taken.
Gold cap per morning: %morning_gold_cap% per copy (resets at dawn).
Greed: 1 stack per 250 net worth; +1 to all attributes per stack." +"card_51_morning_gold_cap" "GOLD CAP PER MORNING:" +"card_52_name" "Blood Contract" +"card_52_description" "Reduces each night duration by %night_duration_reduce_sec% sec per copy.
But all enemy stats are increased by %enemy_stats_bonus_pct%%% per copy." +"card_53_name" "Spirit Echo" +"card_53_description" "If you already own Spirit Power, picking it again immediately grants one extra card selection.
Extra choices: %extra_card_choices%." +"card_54_name" "Curse of Intellect" +"card_54_description" "Per hero level: +%intellect_per_level% Intellect, but -%strength_penalty_per_level% Strength and -%agility_penalty_per_level% Agility.
Effect is multiplied by the number of curses taken." +"card_55_name" "Homer's Vitality" +"card_55_description" "Increases npc_homer maximum health by %max_health_bonus_pct%%%." +"card_56_name" "Homer's Thorns" +"card_56_description" "When npc_homer takes damage from enemy creeps, reflects %creep_reflect_pct%%% of that damage back to the attacker." +"card_57_name" "Mirror of Fate" +"card_57_description" "One time on pickup: mirrors your current card pool and duplicates all cards in it." +"card_58_name" "Rampant Growth" +"card_58_description" "At game start grants %extra_selections_on_start% additional card selections. No standard card selection at dawn.
Each start bonus selection offers %cards_per_selection% cards." +"card_59_name" "Gift of Experience" +"card_59_description" "One time on pickup: grants %xp_reward% experience.
Only via Wish Card; cannot be added to deck." +"card_59_xp_reward" "EXPERIENCE:" +"card_60_name" "Gift of Gold" +"card_60_description" "One time on pickup: grants %gold_reward% gold.
Only via Wish Card; cannot be added to deck." +"card_60_gold_reward" "GOLD:" +"card_61_name" "Gift of Crystals" +"card_61_description" "One time on pickup: grants %crystals_reward% crystals.
Only via Wish Card; cannot be added to deck." +"card_61_crystals_reward" "CRYSTALS:" +"card_62_name" "Wish Card" +"card_62_description" "One time on pickup: opens a 3-card choice between Gift of Experience, Gift of Gold, and Gift of Crystals." +"card_63_name" "Survivor Contract" +"card_63_description" "If you survive the night without dying, at dawn you gain an additional selection of %bonus_card_choices% cards." +"card_63_bonus_card_choices" "EXTRA CARD CHOICES:" +"card_64_name" "Wolf Instinct" +"card_64_description" "Against targets below %hp_threshold_pct%%% health, your next hit is guaranteed to crit.
Uses your current critical strike sources." +"card_65_name" "Blood Mark" +"card_65_description" "If the target has %target_hp_full_pct%%% health, that hit is guaranteed to crit and deals %crit_multiplier_pct%%% damage." +"card_66_name" "Blade of Fate" +"card_66_description" "After casting an ability, your next attack deals bonus magical damage equal to %bonus_from_health_pct%%% of your current health and %bonus_from_mana_pct%%% of your current mana." +"card_67_name" "Titan's Heart" +"card_67_description" "Each point of Strength increases your health pool by %hp_per_strength% and outgoing damage by %outgoing_pct_per_strength%%%.
Attacks have a %proc_chance_pct%%% chance to deal bonus damage based on max health (%bonus_damage_from_max_hp_pct%%%) with a %proc_cooldown_sec%s cooldown." +"card_68_name" "Triple Debt" +"card_68_description" "Immediately applies %curse_stacks% curse stacks, shuffles this card back into your deck pool, then lets you choose 1 card from %cursed_offer_slots% random options.
Normally: random cursed cards from your deck pool; if none are available, the extra choice is skipped.
If you already have Cursed Bargain: options are any three random cards from your deck pool (not only cursed)." +"card_69_name" "Cursed Bargain" +"card_69_description" "All other cards: their numeric catalog values are reduced by %other_cards_effectiveness_pct%%%.
For each curse stack on your hero: +%outgoing_damage_per_curse_stack_pct%%% outgoing damage.
Does not weaken this card's own parameters. Effects that don't use catalog numbers are unchanged. After this card is active, every further card you take from a pick applies a curse stack (cards taken earlier do not retroactively count)." +"card_70_name" "Rage Ward" +"card_70_description" "Each point of current Rage reduces incoming damage by %incoming_reduction_per_rage_pct%%%.
Only works on heroes that use the Rage resource. No Rage — no reduction." +"card_71_name" "Curse Burden" +"card_71_description" "For each curse stack: +%max_health_pct_per_curse_stack%%% max health.
Every second, you take Pure damage equal to %self_damage_max_hp_pct_per_sec%%% of your max health." +"card_72_name" "Rush of Rage" +"card_72_description" "Cooldowns are %cooldown_reduction_pct%%% shorter.
Only for heroes that use Rage; no effect on others." +"card_73_name" "Admiral's Rum" +"card_73_description" "%deferred_damage_pct%%% of incoming damage is deferred and dealt as Pure damage in equal parts over %deferred_dot_duration_sec% seconds.
The rest still hits immediately. The deferred portion can still kill you." +"card_74_name" "Sniper Scope" +"card_74_description" "+%crit_mult_bonus_pct% to crit damage multiplier (added to the base 100%% in the crit multiplier formula).
Enemies within %aura_radius% radius lose %enemy_armor_reduction_pct%%% of their current armor.
The armor penalty updates while they stay in range." +"card_75_name" "Mana Surge" +"card_75_description" "Every %blast_interval_sec% sec., spends %mana_cost_pct%%% of max mana and after %blast_delay_sec% sec. deals %base_damage% + %damage_from_max_mana_pct%%% of max mana as pure damage to enemies in a %blast_radius% radius.
The blast center is chosen to hit as many enemies as possible within %search_radius% search radius." +"card_76_name" "Spell Resonance" +"card_76_description" "After casting an ability, strikes up to %target_count% nearest enemies within %radius% for %mana_damage_pct%%% of your current mana as magical damage each.
Heals you for the damage actually dealt.
Items and toggle abilities do not count. Targets must be visible." +"card_77_name" "Mana Shield" +"card_77_description" "Up to %damage_reduction_pct%%% of incoming damage is absorbed for %mana_per_mitigated_damage% mana per point of absorbed damage.
If mana is insufficient for full absorption, the shield mitigates as much as your mana allows." +"card_78_name" "Mana Pulse" +"card_78_description" "Each ability cast grants a stack for %stack_duration_sec% sec.: +%max_mana_pct_per_stack%%% max mana.
Stacks combine; items and toggles do not count. Bonus is based on max mana at cast time." +"card_79_name" "Echo Pick" +"card_79_description" "Your next card draft is duplicated: after you pick a card, you receive it again.
Triggers once. Not duplicated: empty card (404), Mythic cards, Frostmourne and its shards." + +"card_80_name" "Frostmourne" +"card_80_description" "Innate cursed blade. Immediately shuffles 3 shards (edge, hilt, soul) into your deck. Draft all three to fully reforge Frostmourne: curse no longer increases incoming damage and instead grants +%outgoing_damage_per_curse_stack_pct%%% outgoing per stack.
Each shard adds 1 curse when taken. Shards cannot be bought in the shop. Cannot be duplicated by Mirror of Fate or Echo Pick." + +"card_81_name" "Shard: Edge" +"card_81_description" "Frostmourne edge. +%outgoing_damage_pct%%% outgoing damage and +%spell_amp_pct%%% spell amp. Adds 1 curse when taken.
Collect with hilt and soul to complete the blade." + +"card_82_name" "Shard: Hilt" +"card_82_description" "Frostmourne hilt. +%max_health_pct%%% max health and +%max_mana_pct%%% max mana (mana bonus is fixed when taken). Adds 1 curse when taken.
Collect with edge and soul to complete the blade." + +"card_83_name" "Shard: Soul" +"card_83_description" "Frostmourne soul. +%model_scale_pct%%% model scale, +%physical_vampirism_pct%%% physical and +%magical_vampirism_pct%%% spell lifesteal. Adds 1 curse when taken.
Collect with edge and hilt to complete the blade." + +"card_84_name" "Minor Mana Frenzy" +"card_84_description" "One time on pickup: spell amplification equal to %spell_amp_from_mana_pct%%% of maximum mana at the moment you take this card. The value is locked forever.
Only via Mana Frenzy (level 3); cannot be added to deck." +"card_84_spell_amp_from_mana_pct" "SPELL AMP FROM MANA:" + +"card_85_name" "Fishing" +"card_85_description" "When picked, opens an extra draft with %bonus_card_choices% options, shuffles this card back into your deck pool, and mixes %slag_pool_copies% Slag into the pool — an empty card with no effect that only appears from the pool.
The number of options in the extra draft increases with card level. Slag cannot be added to your deck or upgraded." +"card_85_bonus_card_choices" "OPTIONS IN EXTRA DRAFT:" +"card_85_slag_pool_copies" "SLAG IN POOL:" + +"card_86_name" "Slag" +"card_86_description" "Empty card with no effect. Mixed into the pool only via Fishing. Picking Slag grants nothing, but its pool weight is consumed." + +"card_87_name" "Slag King" +"card_87_description" "For each unpicked Slag card in the pool: +%attr_pct_per_slag%%% Strength, Agility, and Intelligence." + +"card_88_name" "Chum" +"card_88_description" "Whenever you get Slag, you immediately gain %free_rerolls_on_slag% free rerolls." + +"card_404_name" "Empty card" +"card_404_description" "No valid effect" + +// Card modifier tooltips (dota_tooltip_modifier_*) +"dota_tooltip_modifier_modifier_card_1" "Bloodway" +"dota_tooltip_modifier_modifier_card_1_Description" "+%vampirism_bonus%% to vampirism
+%damage_multiplier%% to all physical damage per unit of vampirism" +"dota_tooltip_modifier_modifier_card_2" "Light pen" +"dota_tooltip_modifier_modifier_card_2_Description" "Movement speed: +%dMODIFIER_PROPERTY_MOVESPEED_BONUS_CONSTANT%. Move speed limit: %dMODIFIER_PROPERTY_MOVESPEED_LIMIT%.
Base attack time: %dMODIFIER_PROPERTY_BASE_ATTACK_TIME_CONSTANT%.
At level 3, the hero can pass through units." +"dota_tooltip_modifier_modifier_card_63" "Survivor Contract" +"dota_tooltip_modifier_modifier_card_63_Description" "Passive: if you survive the night without dying, at dawn you gain an additional selection of %bonus_card_choices% cards." +"dota_tooltip_modifier_modifier_card_64" "Wolf Instinct" +"dota_tooltip_modifier_modifier_card_64_Description" "Against targets below %hp_threshold_pct%%% health: your next hit is guaranteed to crit.
Uses your current critical strike sources." +"dota_tooltip_modifier_modifier_card_65" "Blood Mark" +"dota_tooltip_modifier_modifier_card_65_Description" "If the target has %target_hp_full_pct%%% health: your next hit is guaranteed to crit with %crit_multiplier_pct%%% multiplier." +"dota_tooltip_modifier_modifier_card_66" "Blade of Fate" +"dota_tooltip_modifier_modifier_card_66_Description" "Charges: %dMODIFIER_PROPERTY_TOOLTIP%.
After casting an ability, your next hit consumes 1 charge and deals bonus magical damage: %bonus_from_health_pct%%% of current health + %bonus_from_mana_pct%%% of current mana." +"dota_tooltip_modifier_modifier_card_69" "Cursed Bargain" +"dota_tooltip_modifier_modifier_card_69_Description" "Outgoing damage per curse stack: +%outgoing_damage_per_curse_stack_pct%%% (total scales with stacks on you)." +"dota_tooltip_modifier_modifier_card_70" "Rage Ward" +"dota_tooltip_modifier_modifier_card_70_Description" "Incoming damage reduction from Rage: %incoming_reduction_per_rage_pct%%% per current Rage (heroes with Rage only)." +"dota_tooltip_modifier_modifier_card_71" "Curse Burden" +"dota_tooltip_modifier_modifier_card_71_Description" "Max health per curse stack: +%max_health_pct_per_curse_stack%%%.
Every second: %self_damage_max_hp_pct_per_sec%%% of max HP as Pure damage to yourself." +"dota_tooltip_modifier_modifier_card_72" "Rush of Rage" +"dota_tooltip_modifier_modifier_card_72_Description" "Cooldown reduction: %cooldown_reduction_pct%%% (Rage heroes only)." +"dota_tooltip_modifier_modifier_card_73" "Admiral's Rum" +"dota_tooltip_modifier_modifier_card_73_Description" "Deferred damage: %deferred_damage_pct%%% of incoming is delayed; that portion is dealt as Pure damage over %deferred_dot_duration_sec% seconds." +"dota_tooltip_modifier_modifier_card_74" "Sniper Scope" +"dota_tooltip_modifier_modifier_card_74_Description" "Crit multiplier: +%crit_mult_bonus_pct%.
Aura radius: %aura_radius%. Enemies lose %enemy_armor_reduction_pct%%% of current armor." +"dota_tooltip_modifier_modifier_card_75" "Mana Surge" +"dota_tooltip_modifier_modifier_card_75_Description" "Every %blast_interval_sec% sec.: −%mana_cost_pct%%% mana, %base_damage% + %damage_from_max_mana_pct%%% of max mana as pure damage, %blast_radius% radius." +"dota_tooltip_modifier_modifier_card_76" "Spell Resonance" +"dota_tooltip_modifier_modifier_card_76_Description" "After an ability cast: up to %target_count% enemies in %radius%, %mana_damage_pct%%% of current mana as magical damage. Heal equals damage dealt." +"dota_tooltip_modifier_modifier_card_77" "Mana Shield" +"dota_tooltip_modifier_modifier_card_77_Description" "Absorption: up to %damage_reduction_pct%%% damage for %mana_per_mitigated_damage% mana per 1 absorbed damage." +"dota_tooltip_modifier_modifier_card_78" "Mana Pulse" +"dota_tooltip_modifier_modifier_card_78_Description" "Ability cast: stack +%max_mana_pct_per_stack%%% max mana for %stack_duration_sec% sec." +"dota_tooltip_modifier_modifier_card_78_mana_stack" "Mana Pulse" +"dota_tooltip_modifier_modifier_card_78_mana_stack_Description" "+%max_mana_pct_per_stack%%% max mana." +"dota_tooltip_modifier_modifier_card_79" "Echo Pick" +"dota_tooltip_modifier_modifier_card_79_Description" "Next draft pending: your picked card will be granted again (except empty, Mythic, and Frostmourne)." +"dota_tooltip_modifier_modifier_card_80" "Frostmourne (incomplete)" +"dota_tooltip_modifier_modifier_card_80_Description" "Shards collected: %dMODIFIER_PROPERTY_TOOLTIP% / 3. Draft edge, hilt, and soul." +"dota_tooltip_modifier_modifier_card_80_pact_complete" "Frostmourne" +"dota_tooltip_modifier_modifier_card_80_pact_complete_Description" "Blade reforged. Curse grants +%outgoing_damage_per_curse_stack_pct%%% outgoing per stack instead of incoming. Stacks: %dMODIFIER_PROPERTY_TOOLTIP%." +"dota_tooltip_modifier_modifier_card_81" "Frostmourne Edge" +"dota_tooltip_modifier_modifier_card_81_Description" "Outgoing damage: +%outgoing_damage_pct%%%. Spell amp: +%spell_amp_pct%%%." +"dota_tooltip_modifier_modifier_card_82" "Frostmourne Hilt" +"dota_tooltip_modifier_modifier_card_82_Description" "Max health: +%max_health_pct%%%. Max mana: +%max_mana_pct%%% (mana bonus fixed on pickup)." +"dota_tooltip_modifier_modifier_card_83" "Frostmourne Soul" +"dota_tooltip_modifier_modifier_card_83_Description" "Model scale: +%model_scale_pct%%%. Lifesteal: +%physical_vampirism_pct%%% physical, +%magical_vampirism_pct%%% spell." +"dota_tooltip_modifier_modifier_card_84" "Minor Mana Frenzy" +"dota_tooltip_modifier_modifier_card_84_Description" "Spell amplification (locked on pickup): +%dMODIFIER_PROPERTY_TOOLTIP%%%." +"dota_tooltip_modifier_modifier_card_3" "Spirit power" +"dota_tooltip_modifier_modifier_card_3_Description" "+%damage_pct%% to all physical and magical damage." +"dota_tooltip_modifier_modifier_card_4" "Rampage of the spirit" +"dota_tooltip_modifier_modifier_card_4_Description" "Mixes %card_bonus% of the Spirit Power card into the player pool." +"dota_tooltip_modifier_modifier_card_5" "Card madness" +"dota_tooltip_modifier_modifier_card_5_Description" "For each card taken before this one, grants %card_bonus_pct%% bonus Strength, Agility, and Intelligence." +"dota_tooltip_modifier_modifier_card_6" "Happy hand" +"dota_tooltip_modifier_modifier_card_6_Description" "Extra card selection. Minimum %min_card_choice% cards in every selection." +"dota_tooltip_modifier_modifier_card_7" "Curse solar eclipse" +"dota_tooltip_modifier_modifier_card_7_Description" "At night:
+%daynight_bonus_pct%% to all characteristics.
During the day:
-%daynight_bonus_pct%% to all characteristics.
" +"dota_tooltip_modifier_modifier_card_8" "Curse spray heart" +"dota_tooltip_modifier_modifier_card_8_Description" "For each kill, the hero forever loses %damage_bonus_health_decress% of health, also permanently increases damage by %damage_bonus_health_decress%. Kill stacks are only gained while the owner's health is at least %stack_health_threshold%.
Card stacks: %dMODIFIER_PROPERTY_TOOLTIP%" +"dota_tooltip_modifier_modifier_card_9" "Curse alak midas" +"dota_tooltip_modifier_modifier_card_9_Description" "Grants +1 Greed stack. Kill bonus: +%kill_gold_bonus_pct%%% of target bounty per card copy. Attribute bonus is on the Greed buff.
On death: lose all gold and random items based on card copies." +"dota_tooltip_modifier_modifier_card_10" "Ashen Seal" +"dota_tooltip_modifier_modifier_card_10_Description" "Attacks apply a debuff for %debuff_duration% sec.
The target takes +%incoming_damage_per_fired_stack_pct%%% incoming damage per burn stack (modifier_general_fired), up to +%max_incoming_damage_bonus_pct%%%. This bonus works only for the card owner's damage." +"dota_tooltip_modifier_modifier_card_11" "Growth of Ambitions" +"dota_tooltip_modifier_modifier_card_11_Description" "Grants a level-based bonus to the primary attribute.
If the primary attribute is not universal: +%primary_attribute_bonus_per_level% to the primary attribute per level. If the primary attribute is universal: +%universal_all_bonus_per_level% to all attributes per level." +"dota_tooltip_modifier_modifier_card_12" "Eye of the Night Abyss" +"dota_tooltip_modifier_modifier_card_12_Description" "Every %scan_interval% sec marks the strongest enemy within %scan_radius% radius for %mark_duration% sec
Against marked target: +%bonus_damage_pct%%% damage. Killing a marked target restores %on_kill_heal_pct%%% of max health and grants +%on_kill_mana% mana. Bonus damage is reduced by %boss_penalty_pct%%% against bosses." +"dota_tooltip_modifier_modifier_card_13" "Eye of the Burning Abyss" +"dota_tooltip_modifier_modifier_card_13_Description" "Each attack applies %fired_stacks_on_hit% burn stacks
With a %explosion_chance_pct%%% chance, the target explodes in a %explosion_radius% radius. Explosion damage: (burn stacks × %explosion_damage_per_stack%) + attacker's damage." +"dota_tooltip_modifier_modifier_card_14" "Infernal Ricochet" +"dota_tooltip_modifier_modifier_card_14_Description" "With a %proc_chance_pct%%% chance, attacks ignite the target and launch ricochet projectiles within %ricochet_radius% radius
Main target: +%main_target_bonus_damage_pct%%% magical damage based on attack damage and %fired_stacks% burn stack(s). Each ricochet is a separate attack dealing %ricochet_damage_pct%%% attack damage and applying %fired_stacks% burn stack(s)." +"dota_tooltip_modifier_modifier_card_15" "Crimson Sickle" +"dota_tooltip_modifier_modifier_card_15_Description" "Grants a fixed %base_crit_chance_pct%%% chance to deal a custom critical hit.
Critical damage scales with Luck: %crit_multiplier_pct%%% + %crit_multiplier_bonus_pct_per_luck%%% per 1 Luck." +"dota_tooltip_modifier_modifier_card_16" "Void Shards" +"dota_tooltip_modifier_modifier_card_16_Description" "Each enemy kill grants 1 stack (up to %max_stacks%).
Each stack grants +%spell_amp_per_stack_pct%%% spell amplification.
Card stacks: %dMODIFIER_PROPERTY_TOOLTIP%" +"dota_tooltip_modifier_modifier_card_17" "Curse of the Bloody Hand" +"dota_tooltip_modifier_modifier_card_17_Description" "On attack, if health is above %min_health_pct_to_activate%%% , the hero spends %health_cost_pct%%% of max health.
Spent health becomes pure damage; damage is further scaled by curse stacks: (1 + stacks × %curse_damage_pct_per_curse_stack%% / 100)." +"dota_tooltip_modifier_modifier_card_18" "Lucky Glasses" +"dota_tooltip_modifier_modifier_card_18_Description" "Passively grants +%base_luck_bonus% Luck.
Additionally grants +%luck_per_card_taken% Luck for each card taken." +"dota_tooltip_modifier_modifier_card_19" "Empty Kimono" +"dota_tooltip_modifier_modifier_card_19_Description" "Attacks have a %ghost_step_chance_pct%%% chance to trigger Ghost Step for %ghost_step_duration% sec.
While active: +%ghost_step_move_speed_pct%%% movement speed, +%ghost_step_evasion_pct%%% evasion, and unit phasing." +"dota_tooltip_modifier_modifier_card_20" "Golden Boots" +"dota_tooltip_modifier_modifier_card_20_Description" "Grants +%move_speed_pct_per_step%%% movement speed for each %gold_per_step% gold.
Removes the maximum movement speed limit." +"dota_tooltip_modifier_modifier_card_21" "Solar Blade" +"dota_tooltip_modifier_modifier_card_21_Description" "Each hit against npc_wave units deals additional pure damage.
Bonus damage: %wave_pure_damage_base% + owner's attack damage." +"dota_tooltip_modifier_modifier_card_22" "Coins of Dawn" +"dota_tooltip_modifier_modifier_card_22_Description" "Next dawn: %dMODIFIER_PROPERTY_TOOLTIP% gold.
Amount decreases each night." +"dota_tooltip_modifier_modifier_card_23" "Golden Growth" +"dota_tooltip_modifier_modifier_card_23_Description" "Every %gold_tick_interval_sec% seconds, grants bonus gold equal to %gold_income_pct_per_minute%%% of your current gold." +"dota_tooltip_modifier_modifier_card_24" "Golden Hand" +"dota_tooltip_modifier_modifier_card_24_Description" "For every %gold_per_step% gold, the hero gains +%attack_damage_per_step%%% attack damage and +%spell_amp_per_step_pct%%% spell amplification." +"dota_tooltip_modifier_modifier_card_25" "Mana Core" +"dota_tooltip_modifier_modifier_card_25_Description" "For every %mana_per_step% current mana, the hero gains +%spell_amp_per_step_pct%%% spell amplification and +%mana_regen_per_step% mana regeneration." +"dota_tooltip_modifier_modifier_card_26" "Shadow Vow" +"dota_tooltip_modifier_modifier_card_26_Description" "The hero's health cannot drop below 1.
If health is below %trigger_health_threshold%, the hero becomes invisible and restores %heal_pct_per_second%%% of max health per second. After the effect ends: %cooldown_seconds% sec cooldown." +"dota_tooltip_modifier_modifier_card_27" "Mana Blade" +"dota_tooltip_modifier_modifier_card_27_Description" "%mana_to_damage_pct%%% of the hero's current mana is added to attack damage." +"dota_tooltip_modifier_modifier_card_28" "Blood Blade" +"dota_tooltip_modifier_modifier_card_28_Description" "%health_to_damage_pct%%% of the hero's current health is added to attack damage." +"dota_tooltip_modifier_modifier_card_29" "Blood Moon" +"dota_tooltip_modifier_modifier_card_29_Description" "At night grants +%night_vampirism_pct%%% physical lifesteal and reduces incoming damage by %night_incoming_damage_reduce_pct%%%." +"dota_tooltip_modifier_modifier_card_30" "Curse of Growth" +"dota_tooltip_modifier_modifier_card_30_Description" "Per hero level: +%strength_per_level% Strength, but -%agility_penalty_per_level% Agility and -%intellect_penalty_per_level% Intellect.
Effect is multiplied by the number of curses taken." +"dota_tooltip_modifier_modifier_card_31" "Curse of Agility" +"dota_tooltip_modifier_modifier_card_31_Description" "Per hero level: +%agility_per_level% Agility, but -%strength_penalty_per_level% Strength and -%intellect_penalty_per_level% Intellect.
Effect is multiplied by the number of curses taken." +"dota_tooltip_modifier_modifier_card_32" "Hourglass" +"dota_tooltip_modifier_modifier_card_32_Description" "Grants +%base_cooldown_reduction_pct%%% cooldown reduction.
After each hero death, the effect is reduced by %cooldown_loss_per_death_pct%%%.
Deaths since pickup: %dMODIFIER_PROPERTY_TOOLTIP%" +"dota_tooltip_modifier_modifier_card_33" "Mana Frenzy" +"dota_tooltip_modifier_modifier_card_33_Description" "One time on pickup, increases maximum mana by %mana_increase_pct%%% of current maximum mana.
Bonus mana (stack): %dMODIFIER_PROPERTY_TOOLTIP%" +"dota_tooltip_modifier_modifier_card_34" "Blood Harvest" +"dota_tooltip_modifier_modifier_card_34_Description" "On enemy last-hit, restores health: %heal_from_enemy_max_hp_pct%%% of the target's max health + %heal_from_attack_damage_pct%%% of your attack damage." +"dota_tooltip_modifier_modifier_card_35" "Poisoned Wound" +"dota_tooltip_modifier_modifier_card_35_Description" "Attacks poison the enemy. Poison deals %damage_current_hp_pct%%% of the target's current health.
Against npc_boss targets, poison damage is reduced to %boss_damage_current_hp_pct%%% of current health." +"dota_tooltip_modifier_modifier_card_36" "Crystal Tax" +"dota_tooltip_modifier_modifier_card_36_Description" "For every %crystals_per_step% crystals spent, gain +%damage_pct_per_step%%% outgoing damage.
Maximum: %max_bonus_pct%%%.
Card stacks: %dMODIFIER_PROPERTY_TOOLTIP%" +"dota_tooltip_modifier_modifier_card_37" "Bag of Crystals" +"dota_tooltip_modifier_modifier_card_37_Description" "On pickup, instantly gain %base_crystals% crystals + %crystals_per_night% for each current night." +"dota_tooltip_modifier_modifier_card_38" "Crystal Dawn" +"dota_tooltip_modifier_modifier_card_38_Description" "At the start of each new day, your current crystals are multiplied by %multiplier%x." +"dota_tooltip_modifier_modifier_card_39" "Crystal Converter" +"dota_tooltip_modifier_modifier_card_39_Description" "One time on pickup: all your crystals are exchanged for gold at %gold_per_crystal% gold per crystal." +"dota_tooltip_modifier_modifier_card_40" "Gold Converter" +"dota_tooltip_modifier_modifier_card_40_Description" "One time on pickup: all your gold is exchanged for crystals at %gold_per_crystal% gold per crystal (gold below the exchange rate is kept)." +"dota_tooltip_modifier_modifier_card_41" "Twin Blades Reserve" +"dota_tooltip_modifier_modifier_card_41_Description" "Shuffles blades into your pool. Level 2: instant blade pick; Level 3: +1 more of each blade in the pool." +"dota_tooltip_modifier_modifier_card_42" "Thirst for Knowledge" +"dota_tooltip_modifier_modifier_card_42_Description" "Increases experience gained by %exp_bonus_pct%%%." +"dota_tooltip_modifier_modifier_card_43" "Quick Hands" +"dota_tooltip_modifier_modifier_card_43_Description" "Base attack time: %dMODIFIER_PROPERTY_BASE_ATTACK_TIME_CONSTANT%" +"dota_tooltip_modifier_modifier_card_44" "Azure Gale" +"dota_tooltip_modifier_modifier_card_44_Description" "+%bonus_move_speed%%% movement speed and +%bonus_evasion%%% evasion.
Each Luck: +%luck_bonus_pct_per_point%%% to both (evasion capped at %max_evasion_pct%%%)." +"dota_tooltip_modifier_modifier_card_45" "Energy Drink" +"dota_tooltip_modifier_modifier_card_45_Description" "+%max_bonus_pct%%% movement speed, decreasing by %decay_pct_per_minute%%% every minute.
At each new morning, the bonus is restored to %max_bonus_pct%%%.
Current move speed bonus: %dMODIFIER_PROPERTY_TOOLTIP%%%" +"dota_tooltip_modifier_modifier_card_46" "Mana Surge" +"dota_tooltip_modifier_modifier_card_46_Description" "Restores mana equal to %damage_to_mana_pct%%% of damage taken." +"dota_tooltip_modifier_modifier_card_47" "Good Start" +"dota_tooltip_modifier_modifier_card_47_Description" "Experience gained bonus: +%dMODIFIER_PROPERTY_TOOLTIP%%%. Remaining time is shown on the buff bar." +"dota_tooltip_modifier_modifier_card_48" "Insurance Policy" +"dota_tooltip_modifier_modifier_card_48_Description" "Once per night: when you drop below %trigger_hp_pct%%% HP after taking damage, restore %shield_heal_max_hp_pct%%% max health and gain %incoming_damage_reduction_pct%%% damage resistance for %insurance_duration% sec." +"dota_tooltip_modifier_modifier_card_49" "Second Opinion" +"dota_tooltip_modifier_modifier_card_49_Description" "When picked: choose cards again once.
Each new morning: your first reroll that costs crystals is free." +"dota_tooltip_modifier_modifier_card_50" "Legendary Draft" +"dota_tooltip_modifier_modifier_card_50_Description" "When picked: choose one of three random Legendary cards from the full catalog (non-inherent legendaries only)." +"dota_tooltip_modifier_modifier_card_51" "Midas Chestplate" +"dota_tooltip_modifier_modifier_card_51_Description" "Incoming damage: +%dMODIFIER_PROPERTY_INCOMING_DAMAGE_PERCENTAGE%%%.
Gold on damage taken (per-morning cap, resets at dawn); every 100 damage taken increases incoming damage further." +"dota_tooltip_modifier_modifier_card_52" "Blood Contract" +"dota_tooltip_modifier_modifier_card_52_Description" "Global card effect: each copy reduces night duration by 30 sec.
Side effect: all enemy stats are increased by 8% per copy." +"dota_tooltip_modifier_modifier_card_53" "Spirit Echo" +"dota_tooltip_modifier_modifier_card_53_Description" "If you already own Spirit Power: picking this card again immediately grants an extra selection of %extra_card_choices% cards." +"dota_tooltip_modifier_modifier_card_54" "Curse of Intellect" +"dota_tooltip_modifier_modifier_card_54_Description" "Per hero level: +%intellect_per_level% Intellect, but -%strength_penalty_per_level% Strength and -%agility_penalty_per_level% Agility.
Effect is multiplied by the number of curses taken." +"dota_tooltip_modifier_modifier_card_55" "Homer's Vitality" +"dota_tooltip_modifier_modifier_card_55_Description" "Passive: increases npc_homer maximum health by %max_health_bonus_pct%%%." +"dota_tooltip_modifier_modifier_card_56" "Homer's Thorns" +"dota_tooltip_modifier_modifier_card_56_Description" "Passive: when npc_homer takes damage from enemy creeps, reflects %creep_reflect_pct%%% of that damage back to the attacker." +"dota_tooltip_modifier_modifier_card_57" "Mirror of Fate" +"dota_tooltip_modifier_modifier_card_57_Description" "One time on pickup: mirrors your current card pool and duplicates all cards in it." +"dota_tooltip_modifier_modifier_card_58" "Rampant Growth" +"dota_tooltip_modifier_modifier_card_58_Description" "At game start: %extra_selections_on_start% bonus selections with %cards_per_selection% cards each. At dawn you skip the standard card pick." +"dota_tooltip_modifier_modifier_card_10_debuff" "Ashen Seal (debuff)" +"dota_tooltip_modifier_modifier_card_10_debuff_Description" "Attacks apply a debuff for %debuff_duration% sec.
The target takes +%incoming_damage_per_fired_stack_pct%%% incoming damage per burn stack (modifier_general_fired), up to +%max_incoming_damage_bonus_pct%%%. This bonus works only for the card owner's damage." +"dota_tooltip_modifier_modifier_card_12_marked" "Eye of the Night Abyss (mark)" +"dota_tooltip_modifier_modifier_card_12_marked_Description" "Every %scan_interval% sec marks the strongest enemy within %scan_radius% radius for %mark_duration% sec
Against marked target: +%bonus_damage_pct%%% damage. Killing a marked target restores %on_kill_heal_pct%%% of max health and grants +%on_kill_mana% mana. Bonus damage is reduced by %boss_penalty_pct%%% against bosses." +"dota_tooltip_modifier_modifier_card_19_ghost_step" "Empty Kimono (ghost step)" +"dota_tooltip_modifier_modifier_card_19_ghost_step_Description" "Attacks have a %ghost_step_chance_pct%%% chance to trigger Ghost Step for %ghost_step_duration% sec.
While active: +%ghost_step_move_speed_pct%%% movement speed, +%ghost_step_evasion_pct%%% evasion, and unit phasing." +"dota_tooltip_modifier_modifier_card_26_invisibility" "Shadow Vow (invis)" +"dota_tooltip_modifier_modifier_card_26_invisibility_Description" "The hero's health cannot drop below 1.
If health is below %trigger_health_threshold%, the hero becomes invisible and restores %heal_pct_per_second%%% of max health per second. After the effect ends: %cooldown_seconds% sec cooldown." +"dota_tooltip_modifier_modifier_card_35_poison" "Poisoned Wound (poison)" +"dota_tooltip_modifier_modifier_card_35_poison_Description" "Attacks poison the enemy. Poison deals %damage_current_hp_pct%%% of the target's current health.
Against npc_boss targets, poison damage is reduced to %boss_damage_current_hp_pct%%% of current health." +"dota_tooltip_modifier_modifier_card_48_insurance_buff" "Insurance Policy (shield)" +"dota_tooltip_modifier_modifier_card_48_insurance_buff_Description" "Once per night: when you drop below %trigger_hp_pct%%% HP after taking damage, restore %shield_heal_max_hp_pct%%% max health and gain %incoming_damage_reduction_pct%%% damage resistance for %insurance_duration% sec." +"dota_tooltip_modifier_modifier_card_cursed" "Curse (count)" +"dota_tooltip_modifier_modifier_card_cursed_Description" "Cursed cards taken: %dMODIFIER_PROPERTY_TOOLTIP%. Each curse: +5%% incoming damage." +"dota_tooltip_modifier_modifier_card_greed" "Greed" +"dota_tooltip_modifier_modifier_card_greed_Description" "Greed stacks: icon count — how many cards with Greed you have taken. Bonus to all attributes: %dMODIFIER_PROPERTY_TOOLTIP% (+1 to Strength, Agility, and Intellect per 250 net worth; multiplied by stacks)." + +//Shop + +"store_title" "Shop" +"store_tab_items" "Items" +"store_tab_cards" "Card" +"store_tab_arcade" "Arcade" +"store_tab_upgrades" "Effects" +"store_tab_chatwheel" "Chat Wheel" +"store_tab_marketplace" "Marketplace" +"store_tab_promotions" "Promotions" +"store_promotions_title" "Available deals" +"store_promotions_bundles_title" "Bundles & offers" +"store_promotions_bundles_hint" "Special offers paid in rubles. Each bundle can be purchased once per account." +"store_promotions_weekly_title" "This week's offer" +"store_promocode_button" "PROMOCODE" +"store_promocode_title" "Promocode" +"store_promocode_desc" "Enter a code and get shop currency" +"store_promocode_placeholder" "For example: ZOMBIENEW" +"store_promocode_apply" "APPLY" +"store_promocode_close" "CLOSE" +"store_deal_timer_prefix" "Refreshes in" +"store_deal_timer_loading" "Loading timer…" +"store_deals_daily_badge" "Deal of the day" +"store_deals_daily_empty" "Loading daily deal…" +"store_deals_weekly_badge" "Weekly deal" +"store_deals_weekly_slot" "Week" +"store_deal_item_owned" "Already owned" +"store_deal_used" "Deal already used" +"store_deal_buy" "Buy with discount" +"store_bundle_owned" "Already purchased" +"store_bundle_buy" "Buy" +"store_bundle_timer_urgency" "Hurry up!" +"store_bundle_timer_ends_in" "Offer ends in" +"store_bundle_timer_prefix" "Offer ends in" +"store_promo_sound_click" "Click to preview" +"store_bundle_starter_zaika_500_title" "Starter Bundle «Zaika»" +"store_bundle_starter_zaika_500_description" "Battle Pass Premium, «Zaika» chat wheel sound, 20,000 dust, 300 donate shards, and 250,000 zombie shards." +"store_bundle_starter_zaika_500_reward_battle_pass_premium" "Battle Pass — Premium" +"store_bundle_starter_zaika_500_reward_sound_zaika" "«Zaika» chat wheel sound" +"store_bundle_starter_zaika_500_reward_dust" "20,000 dust" +"store_bundle_starter_zaika_500_reward_donate" "300 donate shards" +"store_bundle_starter_zaika_500_reward_free" "250,000 zombie shards" +"store_bundle_newbie_card_packs_399_title" "Newbie Card Bundle" +"store_bundle_newbie_card_packs_399_description" "8 standard and 2 premium arcade card packs — open them in the Arcade tab." +"store_bundle_newbie_card_packs_399_reward_arcade_pack_standard" "8 standard card packs" +"store_bundle_newbie_card_packs_399_reward_arcade_pack_premium" "2 premium card packs" + +"store_subscription_monthly_subscription_199_title" "Monthly subscription" +"store_subscription_monthly_subscription_199_description" "Each day on first login: 30 donate shards and 5,000 zombie shards. Buy again to extend." +"store_subscription_monthly_subscription_199_reward_daily_donate" "30 donate shards per day" +"store_subscription_monthly_subscription_199_reward_daily_free" "5,000 zombie shards per day" +"store_subscription_monthly_subscription_199_reward_duration" "+30 days per purchase" +"store_subscription_buy" "Subscribe" +"store_subscription_extend" "Extend" +"store_subscription_active" "Subscription active" +"store_subscription_active_until" "Active until" +"store_subscription_daily_claimed" "Today's reward already claimed" +"store_subscription_timer_prefix" "Valid until" +"store_subscription_daily_reward_toast" "Subscription: +{d} donate, +{s} zombie shards" +"store_subscription_daily_welcome_title" "Welcome back!" +"store_subscription_daily_welcome_kicker" "Monthly subscription" +"store_subscription_daily_welcome_body" "You receive your daily reward:" +"store_subscription_daily_reward_line_donate" "+{amount} donate shards" +"store_subscription_daily_reward_line_free" "+{amount} zombie shards" +"store_subscription_daily_ok" "Great!" +"store_exchange_title" "Currency Exchange" +"store_exchange_desc" "Convert donate shards into free shards at a fixed rate." +"store_exchange_rate_prefix" "Rate" +"store_exchange_apply" "EXCHANGE" +"store_exchange_spend" "Spend" +"store_exchange_get" "receive" +"store_chatwheel_title" "CHAT WHEEL" +"store_marketplace_title" "MARKETPLACE" +"store_marketplace_hint" "List arsenal items for zombie shards and buy offers from other players." +"store_marketplace_refresh" "Refresh" +"store_marketplace_your_inventory" "Your items for listing" +"store_marketplace_active_listings" "Active listings" +"store_marketplace_my_listings" "My listings" +"store_marketplace_mine_active_slots_hint" "Active listings (8 slots)" +"store_marketplace_mine_buy_slots_hint" "Buy extra slots (donate)" +"store_marketplace_slot_buy_donate" "Buy for %PRICE% donate currency" +"store_marketplace_seller" "Seller" +"store_marketplace_listing_id" "Listing" +"store_marketplace_buy" "Buy for" +"store_marketplace_cancel" "Cancel" +"store_marketplace_list" "List" +"store_marketplace_subtab_browse" "Marketplace" +"store_marketplace_subtab_mine" "My listings" +"store_marketplace_subtab_sales_history" "Sales history" +"store_marketplace_history_col_item" "Item" +"store_marketplace_history_col_buyer" "Buyer" +"store_marketplace_history_col_price" "Price" +"store_marketplace_history_col_fee" "Fee" +"store_marketplace_history_col_received" "You get" +"store_marketplace_history_col_date" "Date" +"store_marketplace_history_empty" "No sales yet. Sold listings will appear here." +"store_marketplace_history_buyer_id" "Steam: %s" +"store_marketplace_listing_details" "Listing details" +"store_marketplace_stats_filter" "Stat filters" +"store_marketplace_slot_filter" "Slot filter" +"store_marketplace_item_stats" "Stats" +"store_marketplace_listing_price" "Listing price" +"store_marketplace_set_price" "Set price" +"store_marketplace_commission_note" "Marketplace fee: 20% is deducted from the seller." +"store_marketplace_seller_prefix" "Seller" +"store_marketplace_first_dropper_prefix" "First dropped by" +"store_marketplace_confirm_wallet_title" "Battle shards" +"store_marketplace_confirm_balance_was" "Current" +"store_marketplace_confirm_balance_after" "After trade" +"store_marketplace_confirm_spend" "Deducted" +"store_marketplace_confirm_insufficient" "Not enough battle shards" +"store_marketplace_take_from_auction" "Remove from market" +"store_marketplace_min_level_1_short" "Requires level 1+" +"store_marketplace_price_min_short" "Min: 5000" +"store_marketplace_no_listing_selected" "Select a listing in the grid on the left" +"store_marketplace_sort_label" "Sort:" +"store_marketplace_sort_price_asc" "Price ↑" +"store_marketplace_sort_price_desc" "Price ↓" +"store_marketplace_sort_time_desc" "Newest first" +"store_marketplace_empty_browse" "No listings match the current stat filters. Press Refresh or change filters." +"store_marketplace_empty_inventory" "No arsenal items to list yet. Open the arsenal or wait for sync." +"store_marketplace_slots" "Listing slots" +"store_marketplace_buy_slot" "Buy slot" +"store_marketplace_slots_max" "Slots maxed" +"store_marketplace_col_item" "Item" +"store_marketplace_col_score" "Power" +"store_marketplace_col_price" "Price" +"store_marketplace_col_time" "Listed" +"store_marketplace_col_action" "Action" +"store_marketplace_error_fetch" "Failed to load listings." +"store_marketplace_error_invalid_data" "Invalid request data." +"store_marketplace_error_locked_item" "Pinned and favorite items cannot be listed." +"store_marketplace_error_min_price" "Minimum listing price is 5000." +"store_marketplace_error_slots_full" "No free listing slots available." +"store_marketplace_error_create" "Failed to create listing." +"store_marketplace_error_buy" "Failed to buy listing." +"store_marketplace_error_buy_in_progress" "Please wait: a marketplace purchase is still processing." +"store_marketplace_error_create_in_progress" "Please wait: your previous listing request is still processing." +"store_marketplace_error_cancel" "Failed to cancel listing." +"store_marketplace_error_buy_slot" "Failed to buy listing slot." +"store_marketplace_success_create" "Listing created." +"store_marketplace_success_buy" "Purchase completed." +"store_marketplace_success_cancel" "Listing cancelled." +"store_marketplace_success_buy_slot" "Listing slot purchased." +"store_items_title" "ITEMS" +"store_cards_title" "CARDS" +"store_tab_arcade" "Arcade" +"store_arcade_title" "ARCADE" +"store_arcade_subtitle" "A pack of 3 random cards. Click a card to reveal it." +"store_item_arcade_pack_standard_name" "Standard Pack" +"store_item_arcade_pack_standard_description" "3 random cards from the catalog. Duplicates are refunded as upgrade dust for that card." +"store_item_arcade_pack_premium_name" "Premium Pack" +"store_item_arcade_pack_premium_description" "3 random cards from the catalog. Duplicates are refunded as upgrade dust for that card." +"store_arcade_free_pack_badge_prefix" "Free" +"store_arcade_reveal_title" "Reveal your cards" +"store_arcade_reveal_hint" "Click each card to see what you got" +"store_arcade_reveal_progress" "Revealed" +"store_arcade_reveal_done" "All cards revealed!" +"store_arcade_reveal_close" "Close" +"store_arcade_duplicate_dust" "Duplicate — dust" +"store_arcade_duplicate_max" "Duplicate (copy limit)" +"store_arcade_flip_error" "Could not reveal card" +"store_arcade_purchase_failed" "Could not buy pack" +"store_arcade_pity_mythic" "Mythic" +"store_arcade_pity_epic" "Epic" +"store_upgrades_title" "EFFECTS" + +"filter_purchased" "Purchased" +"filter_not_purchased" "Not purchased" +"filter_price_range" "Price range" +"filter_price_min" "Min:" +"filter_price_max" "Max:" +"search_and_filters" "Search and Filters" +"filter_all" "Everything" +"filter_donate_currency" "Donat currency" +"filter_free_currency" "Free currency" +"filter_cheap" "Cheap" +"filter_expensive" "Dear Ones" +"filter_common" "Regular" +"filter_rare" "Rare" +"filter_epic" "Epic" +"filter_legendary" "Legendary" +"filter_mythic" "Mythical" +"purcase_confirm" "Purchase confirmation" +"confirm" "Are you sure you want to buy this item?" +"accept_buying" "Buy" +"cancel_buying" "Cancel" +"select_currency" "Select the currency to pay:" +"store_buy" "BUY" +"store_purchased" "BUYED" +"store_card_copies_label" "Copies:" +"store_card_limit_reached" "LIMIT" +"store_cancel" "CANCEL" +"store_close" "CLOSE" +"store_rarity_common" "Regular" +"store_rarity_rare" "RARE" +"store_rarity_epic" "EPIC" +"store_rarity_legendary" "LEGENDARY" +"store_rarity_mythic" "MYTHICAL" +"store_unique" "UNIQUE" +"store_equip" "DRESS" +"store_unequip" "REMOVE" +"store_slot_effect" "SLOT: EFFECT" +"store_slot_wings" "SLOT: WINGS" +"store_slot_model" "SLOT: MODEL" + + +//Deck editor +"deck_builder_open_button" "Decks" +"deck_builder_title" "DECK EDITOR" +"deck_builder_close" "×" +"deck_builder_dust_label" "Dust" +"deck_builder_decks_section" "DECKS" +"deck_builder_new_deck" "+ NEW DECK" +"deck_builder_default_name" "New Deck" +"deck_builder_save" "SAVE" +"deck_builder_use" "USE" +"deck_builder_delete" "DELETE" +"deck_builder_available_cards" "AVAILABLE CARDS" +"deck_builder_card_hint" "Shift + LMB — detailed card view" +"deck_builder_deck_hint" "Don't forget to activate your deck! Double-click a deck to set it as active for the match." +"deck_builder_card_purchased" "Owned" +"deck_builder_card_not_purchased" "Not owned" +"deck_builder_card_default" "Default" +"deck_builder_search_placeholder" "SEARCH" +"deck_builder_filter_all" "ALL" +"deck_builder_filter_common" "COMMON" +"deck_builder_filter_rare" "RARE" +"deck_builder_filter_epic" "EPIC" +"deck_builder_filter_legendary" "LEGENDARY" +"deck_builder_filter_mythic" "MYTHIC" +"deck_builder_import_export_open" "Import / Export" +"deck_builder_no_decks" "No decks yet. Create one!" +"deck_builder_auto_deck" "New Deck" +"deck_builder_new_deck_name" "New Deck" +"deck_builder_new_deck_name_with_number" "New Deck" +"deck_builder_deck_name" "Deck" +"deck_builder_selected" "ACTIVE" +"deck_builder_close_fullscreen" "CLOSE" +"deck_builder_import_export_title" "DECK IMPORT / EXPORT" +"deck_builder_export" "EXPORT" +"deck_builder_import" "IMPORT" +"deck_builder_card_level" "Level: {level}/{max}" +"deck_builder_card_level_plain" "Level: {level}" +"deck_builder_upgrade_for_cost" "Upgrade for {cost}" +"deck_builder_upgrade_max_level" "Max level" +"deck_builder_upgrade_unavailable" "Upgrade unavailable" +"deck_builder_card_shard_only" "Not for decks · battle only" +"deck_builder_deck_slots_tooltip" "Uses {count} {slots_word} in your deck" +"deck_builder_slot_word_one" "slot" +"deck_builder_slot_word_few" "slots" +"deck_builder_slot_word_many" "slots" +"deck_builder_deck_incomplete_warning" "Incomplete deck: {used}/{required} slots filled. Missing cards will be added from the default deck at match start." +"deck_builder_deck_activate_incomplete" "Deck uses {used}/{required} slots. Missing cards will be added from the default deck." +"deck_builder_status_loading" "Decks are still loading" +"deck_builder_status_select_deck" "Select or create a deck first" +"deck_builder_status_no_deck_slot" "No free slot for a new deck" +"deck_builder_status_create_failed" "Could not create a new deck" +"deck_builder_card_limit_reached" "Copy limit reached" +"deck_builder_card_copies_label" "Copies:" +"deck_builder_import_export_status_exported" "Deck string exported" +"deck_builder_import_export_status_imported" "Deck imported successfully" +"deck_builder_import_export_copy_code" "Copy code" +"deck_builder_import_export_load_code" "Load from code" +"deck_builder_import_export_apply" "Apply to deck" +"deck_builder_import_export_new_deck" "New deck" +"deck_builder_import_export_status_loaded" "Deck loaded from code" +"deck_builder_import_export_status_applied" "Deck updated" +"deck_builder_import_error_non_deckable" "This card cannot be added to a deck (inventory upgrade only)." +"deck_builder_import_error_empty" "Enter a deck string first" +"deck_builder_import_error_format" "Invalid format. Use id,id,id" +"deck_builder_import_error_deck_size" "Deck is too large (max {max} slots)" +"deck_builder_import_error_unknown_card" "Unknown card in the string" +"deck_builder_import_error_max_copies" "Card copy limit exceeded" +"deck_builder_import_error_not_owned" "Not enough owned copies" +"card_remaining_cards_title" "Remaining cards" +"card_remaining_cards" "Remaining number of cards:" +"inherent_card_tooltip" "Inherent cards are applied automatically at the beginning of the game and do not occupy slots in the deck." +"inherent_card" "Inherent" + +//card selection +"card_reroll_button" "Update cost:" +"card_reroll_free_second_opinion" "Free" +"card_hide_button" "Hide cards" +"card_show_button" "Show maps" + +//Cooking Panel +"cooking_panel_search" "Search" +"cooking_panel_recipe_details" "Recipe Details" +"cooking_panel_ready_dishes" "Ready meals" +"cooking_panel_no_recipes_found" "No recipes found" +"cooking_panel_cooking_time" "To be prepared: {time}s" +"cooking_panel_select_recipe" "Select a recipe" +"cooking_panel_recipe_not_found" "Recipe not found" +"cooking_panel_ingredients" "Ingredients:" +"cooking_panel_cook" "Cook" +"cooking_panel_not_enough_ingredients" "Not enough ingredients" +"campfire_already_occupied" "Bonfire already busy with another player" +"cooking_panel_no_ready_dishes" "No ready-made meals" +"cooking_panel_charges" "Quantity: {charges}" +"cooking_panel_dish_count" "Quantity: x{count}" +"cooking_panel_cooking_duration" "Cooking time: {duration} seconds" +"cooking_intro_message" "Welcome to the cooking system!" + +//Chatvilla sounds +"chat_wheel_donate_sound_i_am_sad" "The man we lost." +"chat_wheel_donate_sound_jump" "Jump the fool!" +"chat_wheel_donate_sound_empty" "Empty slot" +"chat_wheel_donate_sound_agent_gabena" "Gaben Agent" +"chat_wheel_donate_sound_byd_dobr_idi" "Kindly go..." +"chat_wheel_donate_sound_cat_shnapy" "Schni schna..." +"chat_wheel_donate_sound_dobro_pozhalovat_v_club" "Welcome to the club" +"chat_wheel_donate_sound_eto_prosto_okhueno" "That's just incredible" +"chat_wheel_donate_sound_get_out" "GET OUT" +"chat_wheel_donate_sound_ia_vas_unichtozhu" "I will destroy you" +"chat_wheel_donate_sound_kak_rulit" "How to steer?" +"chat_wheel_donate_sound_kto_myaukaet" "Who's meowing?" +"chat_wheel_donate_sound_Muhehehehe" "Muhehehe" +"chat_wheel_donate_sound_ne_tvoy_uroven_dorogoy" "Not your level, dear." +"chat_wheel_donate_sound_ne_ponimaiu_karina_strimersha_slozhno_slozhno" "I don't get it (Karina: so complicated)" +"chat_wheel_donate_sound_nikhuia_ne_ponial_no_ochen_interesno" "No idea what that was—still fascinating" +"chat_wheel_donate_sound_oi_tak_nravitsa" "Oh, I love it" +"chat_wheel_donate_sound_olyhi_bezdari_ogyzki" "OOOOOgni" +"chat_wheel_donate_sound_ou_mai" "Oh my…" +"chat_wheel_donate_sound_po_syobam" "Let’s leave." +"chat_wheel_donate_sound_poshel_process" "The process has begun." +"chat_wheel_donate_sound_posledniy_ponedelnik_zivesh" "Last Monday you live." +"chat_wheel_donate_sound_rot_etogo_kazino" "The mouth of this casino" +"chat_wheel_donate_sound_s_kakoy_stati" "Why on earth" +"chat_wheel_donate_sound_sir_no_sir" "Sir, no sir" +"chat_wheel_donate_sound_sir_yes_sir" "Sir, yes sir" +"chat_wheel_donate_sound_stop_mne_ne_priyatno" "Stop, I'm not pleased." +"chat_wheel_donate_sound_stoyat_ya_yzhe_eto_sosal" "Stop, I already sucked it" +"chat_wheel_donate_sound_ura_pobeda" "Hurray for victory" +"chat_wheel_donate_sound_uvorot_ot_spelov" "Spell Dodge" +"chat_wheel_donate_sound_v_komp_igri_igral" "The game computer played." +"chat_wheel_donate_sound_vot_eto_nikhuia_sebe" "Holy smokes" +"chat_wheel_donate_sound_zachem_ya_suda_prishel" "Why did I come here?" +"chat_wheel_donate_sound_zaika" "Bunny" +"chat_wheel_donate_sound_kitty_flex" "Kitty flex" +"chat_wheel_donate_sound_eblo_razraba" "Dev face meme" +"chat_wheel_donate_sound_kuda" "Where to" +"chat_wheel_donate_sound_bruh" "Bruh" +"chat_wheel_donate_sound_shizofreniya" "Schizophrenia" +"chat_wheel_donate_sound_vot_eto_povorot" "Well, that's a twist" +"chat_wheel_donate_sound_fbi_open_up" "FBI, open up!" +"chat_wheel_donate_sound_nepravilno_poprobuy_esche_raz" "Wrong, try again" +"chat_wheel_donate_sound_dobro_pozhalovat_na_server_shizofreniya" "Welcome to the schizophrenia server" +"chat_wheel_donate_sound_nya" "Nya" +"chat_wheel_donate_sound_na_nas_napali" "We're under attack" +"chat_wheel_donate_sound_murlok" "Murloc" +"chat_wheel_donate_sound_kak_zhit_to_a" "How do you even live" + +//Chat villa interface +"chat_wheel_tab_settings" "Settings" +"chat_wheel_tab_shop" "Purchase" +"chat_wheel_shop_title" "Available sounds for purchase" +"chat_wheel_no_sounds" "You don't have any sounds available for the chat wheel" +"chat_wheel_sound_sad_description" "Sad sound for chat wheel" +"dota_error_cooldown_chat_wheel" "Sound on recharge" +"chat_wheel_settings_hint" "Select a slot on the wheel, then click on the sound" + +// Match end screen +"match_end_title" "Match results" +"match_end_title_win" "Victory!" +"match_end_title_loss" "Defeat" +"match_end_bp_caption" "Your Battle Pass" +"match_end_bp_level_short" "Lv." +"match_end_bp_xp_short" "XP" +"match_end_col_hero" "Hero" +"match_end_col_player" "Player" +"match_end_col_kills" "Creep kills" +"match_end_col_deaths" "Deaths" +"match_end_col_dmg_out" "Damage dealt" +"match_end_col_dmg_in" "Damage taken" +"match_end_col_incoming_events" "Incoming (events)" +"match_end_col_currency" "Battle shards" +"match_end_anim_caption" "Reward: BP XP and battle shards" +"match_end_anim_difficulty" "Difficulty" +"match_end_anim_creeps" "Creeps" +"match_end_anim_quests" "Quests" +"match_end_anim_deaths" "Deaths" +"match_end_anim_total" "Total" +"match_end_close" "Close" + + +} +} \ No newline at end of file diff --git a/resource/addon_russian.txt b/resource/addon_russian.txt new file mode 100644 index 0000000..2667acf --- /dev/null +++ b/resource/addon_russian.txt @@ -0,0 +1,5160 @@ +"lang" +{ + "Language" "russian" + "Tokens" + { + + //FOR ME + // To create a new key ( like $health ) just add the entry here + "dota_ability_variable_health" "к здоровью" // $health + "dota_ability_variable_mana" "к мане" // $mana + "dota_ability_variable_armor" "к броне" // $armor + "dota_ability_variable_damage" "к урону" // $damage + "dota_ability_variable_str" "к силе" // $str + "dota_ability_variable_int" "к интеллекту" // $int + "dota_ability_variable_agi" "к ловкости" // $agi + "dota_ability_variable_all" "ко всем атрибутам" // $all + "dota_ability_variable_primary_attribute" "к основному атрибуту" // $primary_attribute + "dota_ability_variable_attack" "к скорости атаки" // $attack + "dota_ability_variable_attack_pct" "к базовой скорости атаки" // $attack_pct + "dota_ability_variable_hp_regen" "к восстановлению здоровья" // $hp_regen + "dota_ability_variable_lifesteal" "к вампиризму" // $lifesteal + "dota_ability_variable_mana_regen" "к восстановлению маны" // $mana_regen + "dota_ability_variable_mana_regen_aura" "к восстановлению маны в ауре" // $mana_regen_aura + "dota_ability_variable_spell_amp" "к урону от заклинаний" // $spell_amp + "dota_ability_variable_debuff_amp" "к длительности отрицательных эффектов" // $debuff_amp + "dota_ability_variable_move_speed" "к скорости передвижения" // $move_speed + "dota_ability_variable_evasion" "к уклонению" // $evasion + "dota_ability_variable_spell_resist" "к сопротивлению магии" // $spell_resist + "dota_ability_variable_spell_lifesteal" "к вампиризму заклинаниями" // $spell_lifesteal + "dota_ability_variable_spell_lifesteal_creep" "к вампиризму заклинаниями (от крипов)" // $spell_lifesteal_creep + "dota_ability_variable_spell_lifesteal_hero" "к вампиризму заклинаниями (от героев)" // $spell_lifesteal_hero + "dota_ability_variable_selected_attrib" "к выбранному атрибуту" // $selected_attribute + "dota_ability_variable_attack_range" "к дальности атаки (в дальнем бою)" // $attack_range + "dota_ability_variable_attack_range_melee" "к дальности атаки (в ближнем бою)" // $attack_range_melee + "dota_ability_variable_cast_range" "к дальности применения заклинаний" // $cast_range + "dota_ability_variable_status_resist" "к сопротивлению эффектам" // $status_resist + "dota_ability_variable_projectile_speed" "к скорости снарядов" // $projectile_speed + "dota_ability_variable_manacost_reduction" "к уменьшению расхода маны" // $manacost_reduction + "dota_ability_variable_cooldown_reduction" "к уменьшению перезарядок заклинаний" // $cooldown_reduction + "dota_ability_variable_max_mana_percentage" "к максимальной мане" // $max_mana_percentage + "dota_ability_variable_slow_resistance" "к сопротивлению замедлениям" // $slow_resistance + "dota_ability_variable_aoe_bonus" "к радиусу заклинаний" // $aoe_bonus + "dota_ability_variable_crit_multiplier" "к множителю крита" // $crit_multiplier (кастомный стат) + "dota_ability_variable_crit_chance" "к шансу крита" // $crit_chance + "dota_ability_variable_crit_mult" "к урону крита" // $crit_mult + "dota_ability_variable_spell_crit_mult" "к урону маг. крита" + "dota_ability_variable_spell_crit_chance" "к шансу маг. крита" + + + + "addon_game_name" "ZOMBIE INVASION 2" + "cooldown_message" "Перезарядка:" + + "zinv_item_build_night_1" "1 ночь" + "zinv_item_build_night_2" "2 ночь" + "zinv_item_build_night_3" "3 ночь" + "zinv_item_build_night_4" "4 ночь" + + "ZOMIEINVASION_NEWS_TITLE" "ZOMBIE INVASION 2 ЛЕНТА НОВОСТЕЙ" + + "ZINV_NEWS_DEMO_TITLE" "Демо: все фичи новостей" + "ZINV_NEWS_DEMO_BODY" "Пример карточки: текст, картинки, ряды и иконки предметов." + "ZINV_NEWS_DEMO_DETAILS" + "Добро пожаловать в демо-пост! Ниже показаны все варианты вёрстки. + \n\n Обычный блок текста с переносами строк. Можно писать списки: + \n - Пункт 1\n- Пункт 2\n\nВставка большой картинки по месту: + \n [img=file://{images}/custom_game/loading_screen/loadscreen.png] + \n\n Ряд: картинка слева, текст справа:\n[imgrow=file://{images}/custom_game/loading_screen/loadscreen.png] + \n Это пример длинного описания справа от изображения. Здесь можно\nрассказать о фичах, привести цифры, дать примеры использования + \n и любую дополнительную информацию.\n[/imgrow]\n\nИконки предметов с размерами:\n[item=blink] — обновлён кд. + \n [item=ultimate_scepter] — переработан бонус.\n[item=aghanims_shard] — новые апгрейды. + \n\n Если в тексте нет [img=...], сработает fallback из поля image. Спасибо за внимание!" + + "ZINV_NEWS_START_TITLE" "Добро пожаловать в ленту новостей!" + "ZINV_NEWS_START_BODY" "Мы запустили новостную ленту. Следите за апдейтами здесь." + "ZINV_NEWS_START_DETAILS" "Добро пожаловать в ленту новостей!\n\n Теперь все важные изменения, фиксы и планы будут появляться здесь. + \n\n Будущие планы: + \n - Улучшение UI + \n - Правки баланса + \n - Новые игровые фишки + \n - Создание новых персонажей + \n + \n\n Спасибо, что играете!" + + // additional + "additional" "ДОПОЛНИТЕЛЬНО" + "additional_luck" "Удача:" + "additional_vampirism" "Физ. Вампиризм:" + "additional_magic_vampirism" "Маг. Вампиризм:" + "additional_crit_mult" "Множитель крита:" + + + "difficulty_select_title" "Выбор сложности:" + "difficulty_selected" "Выбрана сложность" + "#easy" "Лёгкая" + "#normal" "Обычная" + "#hard" "Сложная" + "#impossible" "Невозможная" + "easy" "Лёгкая" + "normal" "Обычная" + "hard" "Сложная" + "impossible" "Невозможная" + "death_sentence" "Смертельный приговор" + "contract_vote_slots_title" "Приговоры в голосовании" + "contract_menu_title" "Мои приговоры" + "contract_menu_title_mine" "Ваши приговоры:" + "contract_menu_empty" "Приговоров пока нет" + "contract_stats_multiplier" "Множитель статов" + "contract_reward_bonus" "Бонус награды" + + "death_sentence_contracts_title" "Приговоры Смертельного приговора" + "death_sentence_contracts_hint" "" + "death_sentence_contracts_inventory_section" "Инвентарь Контрактов" + "death_sentence_contracts_tooltip_kind" "Приговор" + "death_sentence_contracts_tooltip_reward" "Множитель награды:" + "death_sentence_contracts_tooltip_multiplier" "Множитель:" + "death_sentence_contracts_tooltip_durability" "Прочность:" + "death_sentence_complication_explode" "При появлении с шансом 25% враг получит: При смерти взрывается нанося физический урон в радиусе." + "death_sentence_complication_creep_stats" "Макс. здоровье, урон атаки и регенерация здоровья крипов ×1.25." + "death_sentence_complication_brutal_strikes" "базовый урон атак врагов в 2 раза сильнее." + "death_sentence_complication_zombie_virus" "При появлении с шансом 25% враг получит: При ударе у врагов есть шанс наложить яд и замедление при атаке." + "death_sentence_complication_ghost_evasive" "При появлении с шансом 25% враг получит: У всех врагов есть свободное перемещение и повышено уклонение на 33%" + "death_sentence_complication_phasing_march" "При появлении с шансом 25% враг получит: Проход сквозь других существ и +100 к скорости передвижения." + "death_sentence_complication_zombie_armor_decress" "При появлении с шансом 25% враг получит: Удары накладывают краткое снижение брони цели." + "death_sentence_complication_toxin" "При появлении с шансом 25% враг получит: после смерти оставляет нейтральный нейротоксин в радиусе." + "death_sentence_complication_full_brutality" "При появлении с шансом 25% враг получит: Крипы резко становятся сильнее в 2 раза." + "death_sentence_complication_desperate_vampirism" "При появлении с шансом 25% враг получит: При здоровье ниже половины враг восстанавливает здоровье за удар." + "death_sentence_complication_head_leap" "При появлении с шансом 25% враг получит: Враги периодически прыгают дугой к точке, где стоял герой, нанося урон по зоне приземления." + "death_sentence_complication_bone_armor" "При появлении с шансом 25% враг получит: Бонусная броня. Против героев дальнего боя: если в радиусе 450 нет героя, атакующий получает ×5 от нанесённого урона; иначе шанс отразить часть урона." + "death_sentence_complication_short_day" "Дневная фаза короче на минуту." + "death_sentence_complication_scarce_loot" "Шанс выпадения предметов с убитых существ из общей таблицы дропа в 2 раза ниже." + + + "death_sentence_dismantle_ok" "Приговор разобран: +{d} пыли." + "death_sentence_dismantle_fail" "Не удалось разобрать приговор." + "death_sentence_dismantle_batch_ok" "Разобрано приговоров: {n}, +{d} пыли." + "death_sentence_dismantle_batch_skipped_pinned" "({n} пропущено — закреплены.)" + "death_sentence_repair_ok" "Прочность приговора восстановлена до максимума." + "death_sentence_repair_fail" "Не удалось восстановить прочность." + "death_sentence_repair_fail_funds" "Недостаточно донат-осколков." + "death_sentence_repair_fail_full" "Прочность уже полная." + "death_sentence_repair_fail_state" "Можно только в лобби до начала матча." + "death_sentence_repair_fail_not_found" "Приговор не найден." + "death_sentence_repair_fail_save" "Не удалось сохранить изменения. Донат-осколки возвращены." + "death_sentence_repair_confirm_body" "Потратить {d} донат-осколков и восстановить прочность этого приговора до максимума?" + "death_sentence_repair_confirm_yes" "Починить" + "death_sentence_repair_confirm_cancel" "Отмена" + "death_sentence_contracts_context_dismantle" "" + "death_sentence_detail_vote" "Выставить" + "death_sentence_detail_unvote" "Снять голос" + "death_sentence_detail_lobby_showcase_set" "В ряд выбора" + "death_sentence_detail_lobby_showcase_clear" "Убрать из ряда" + "death_sentence_detail_dismantle" "Разобрать" + "death_sentence_detail_repair" "Починить" + "death_sentence_detail_actions_hint" "" + "death_sentence_detail_debuff_count" "Слотов усложнений волн: %d1" + "death_sentence_contract_tile_hover_hint" "" + "death_sentence_contracts_grid_lore_hint" "" + "death_sentence_rarity_common" "Обычный" + "death_sentence_rarity_rare" "Редкий" + "death_sentence_rarity_epic" "Эпический" + "death_sentence_rarity_legendary" "Легендарный" + "death_sentence_rarity_mythic" "Мифический" + "ds_sentence_title_001" "Приговор — о полном уничтожении" + "ds_sentence_title_002" "Распоряжение — о полном уничтожении" + "ds_sentence_title_003" "Указ — о полном уничтожении" + "ds_sentence_title_004" "Вердикт — о полном уничтожении" + "ds_sentence_title_005" "Декрет — о полном уничтожении" + "ds_sentence_title_006" "Постановление — о полном уничтожении" + "ds_sentence_title_007" "Приказ — о полном уничтожении" + "ds_sentence_title_008" "Грамота — о полном уничтожении" + "ds_sentence_title_009" "Устав — о полном уничтожении" + "ds_sentence_title_010" "Решение — о полном уничтожении" + "ds_sentence_title_011" "Приговор — о вечной осаде" + "ds_sentence_title_012" "Распоряжение — о вечной осаде" + "ds_sentence_title_013" "Указ — о вечной осаде" + "ds_sentence_title_014" "Вердикт — о вечной осаде" + "ds_sentence_title_015" "Декрет — о вечной осаде" + "ds_sentence_title_016" "Постановление — о вечной осаде" + "ds_sentence_title_017" "Приказ — о вечной осаде" + "ds_sentence_title_018" "Грамота — о вечной осаде" + "ds_sentence_title_019" "Устав — о вечной осаде" + "ds_sentence_title_020" "Решение — о вечной осаде" + "ds_sentence_title_021" "Приговор — о красной зоне" + "ds_sentence_title_022" "Распоряжение — о красной зоне" + "ds_sentence_title_023" "Указ — о красной зоне" + "ds_sentence_title_024" "Вердикт — о красной зоне" + "ds_sentence_title_025" "Декрет — о красной зоне" + "ds_sentence_title_026" "Постановление — о красной зоне" + "ds_sentence_title_027" "Приказ — о красной зоне" + "ds_sentence_title_028" "Грамота — о красной зоне" + "ds_sentence_title_029" "Устав — о красной зоне" + "ds_sentence_title_030" "Решение — о красной зоне" + "ds_sentence_title_031" "Приговор — о чёрной блокаде" + "ds_sentence_title_032" "Распоряжение — о чёрной блокаде" + "ds_sentence_title_033" "Указ — о чёрной блокаде" + "ds_sentence_title_034" "Вердикт — о чёрной блокаде" + "ds_sentence_title_035" "Декрет — о чёрной блокаде" + "ds_sentence_title_036" "Постановление — о чёрной блокаде" + "ds_sentence_title_037" "Приказ — о чёрной блокаде" + "ds_sentence_title_038" "Грамота — о чёрной блокаде" + "ds_sentence_title_039" "Устав — о чёрной блокаде" + "ds_sentence_title_040" "Решение — о чёрной блокаде" + "ds_sentence_title_041" "Приговор — о тотальном карантине" + "ds_sentence_title_042" "Распоряжение — о тотальном карантине" + "ds_sentence_title_043" "Указ — о тотальном карантине" + "ds_sentence_title_044" "Вердикт — о тотальном карантине" + "ds_sentence_title_045" "Декрет — о тотальном карантине" + "ds_sentence_title_046" "Постановление — о тотальном карантине" + "ds_sentence_title_047" "Приказ — о тотальном карантине" + "ds_sentence_title_048" "Грамота — о тотальном карантине" + "ds_sentence_title_049" "Устав — о тотальном карантине" + "ds_sentence_title_050" "Решение — о тотальном карантине" + "ds_sentence_title_051" "Приговор — об абсолютной изоляции" + "ds_sentence_title_052" "Распоряжение — об абсолютной изоляции" + "ds_sentence_title_053" "Указ — об абсолютной изоляции" + "ds_sentence_title_054" "Вердикт — об абсолютной изоляции" + "ds_sentence_title_055" "Декрет — об абсолютной изоляции" + "ds_sentence_title_056" "Постановление — об абсолютной изоляции" + "ds_sentence_title_057" "Приказ — об абсолютной изоляции" + "ds_sentence_title_058" "Грамота — об абсолютной изоляции" + "ds_sentence_title_059" "Устав — об абсолютной изоляции" + "ds_sentence_title_060" "Решение — об абсолютной изоляции" + "ds_sentence_title_061" "Приговор — о немилосердной зачистке" + "ds_sentence_title_062" "Распоряжение — о немилосердной зачистке" + "ds_sentence_title_063" "Указ — о немилосердной зачистке" + "ds_sentence_title_064" "Вердикт — о немилосердной зачистке" + "ds_sentence_title_065" "Декрет — о немилосердной зачистке" + "ds_sentence_title_066" "Постановление — о немилосердной зачистке" + "ds_sentence_title_067" "Приказ — о немилосердной зачистке" + "ds_sentence_title_068" "Грамота — о немилосердной зачистке" + "ds_sentence_title_069" "Устав — о немилосердной зачистке" + "ds_sentence_title_070" "Решение — о немилосердной зачистке" + "ds_sentence_title_071" "Приговор — о ликвидации фронта" + "ds_sentence_title_072" "Распоряжение — о ликвидации фронта" + "ds_sentence_title_073" "Указ — о ликвидации фронта" + "ds_sentence_title_074" "Вердикт — о ликвидации фронта" + "ds_sentence_title_075" "Декрет — о ликвидации фронта" + "ds_sentence_title_076" "Постановление — о ликвидации фронта" + "ds_sentence_title_077" "Приказ — о ликвидации фронта" + "ds_sentence_title_078" "Грамота — о ликвидации фронта" + "ds_sentence_title_079" "Устав — о ликвидации фронта" + "ds_sentence_title_080" "Решение — о ликвидации фронта" + "ds_sentence_title_081" "Приговор — о запрете пощады" + "ds_sentence_title_082" "Распоряжение — о запрете пощады" + "ds_sentence_title_083" "Указ — о запрете пощады" + "ds_sentence_title_084" "Вердикт — о запрете пощады" + "ds_sentence_title_085" "Декрет — о запрете пощады" + "ds_sentence_title_086" "Постановление — о запрете пощады" + "ds_sentence_title_087" "Приказ — о запрете пощады" + "ds_sentence_title_088" "Грамота — о запрете пощады" + "ds_sentence_title_089" "Устав — о запрете пощады" + "ds_sentence_title_090" "Решение — о запрете пощады" + "ds_sentence_title_091" "Приговор — о сожжении мостов" + "ds_sentence_title_092" "Распоряжение — о сожжении мостов" + "ds_sentence_title_093" "Указ — о сожжении мостов" + "ds_sentence_title_094" "Вердикт — о сожжении мостов" + "ds_sentence_title_095" "Декрет — о сожжении мостов" + "ds_sentence_title_096" "Постановление — о сожжении мостов" + "ds_sentence_title_097" "Приказ — о сожжении мостов" + "ds_sentence_title_098" "Грамота — о сожжении мостов" + "ds_sentence_title_099" "Устав — о сожжении мостов" + "ds_sentence_title_100" "Решение — о сожжении мостов" + + "death_sentence_contracts_compact_hint" "" + "death_sentence_contracts_ds_row_tooltip" "" + "death_sentence_contracts_preview_empty_tooltip" "" + "death_sentence_contracts_preview_lobby_slot_empty" "" + "death_sentence_contracts_click_to_change" "" + "death_sentence_contracts_open_btn" "§" + "death_sentence_contracts_open_tooltip" "" + "death_sentence_contracts_propose_btn" "Предложить приговор" + "death_sentence_contracts_valid_short" "Зачёт:" + "death_sentence_contracts_total_short" "Всего:" + "death_sentence_contracts_tooltip_votes_inline" "Голоса" + "death_sentence_contracts_votes_legend" "зачёт · всего" + "death_sentence_contracts_slot_not_owned_tooltip" "" + "ds_contract_iron" "Железный завет" + "ds_contract_iron_desc" "Базовый контракт: без дополнительных модификаторов на крипов сверх режима «Смертельный приговор». Итоговый множитель награды матча: ×3.5 (полоса обычного: 3–4)." + "ds_contract_blood" "Кровавый пакт" + "ds_contract_blood_desc" "Враги получают +1 к базовой броне при появлении. Итоговый множитель награды матча: ×4.5 (редкий: 4–5)." + "death_sentence_contracts_effect_iron_1" "Без дополнительных модификаторов на крипов сверх режима «Смертельный приговор»." + "death_sentence_contracts_effect_iron_2" "Чистый Death Sentence: множитель врагов ×6." + "death_sentence_contracts_effect_blood_1" "Вражеские юниты получают +1 к базовой броне при появлении." + "death_sentence_contracts_effect_blood_2" "Сложнее ранняя зачистка волн." + "ds_contract_shadow" "Завеса тени" + "ds_contract_shadow_desc" "Враги получают +300 к базовой скорости передвижения при появлении. Итоговый множитель награды матча: ×5.5 (эпический: 5–6)." + "ds_contract_fury" "Ярость стаи" + "ds_contract_fury_desc" "Легендарный приговор: итоговый множитель награды матча ×6.5 (6–7). Число случайных усложнений волн зависит от редкости: обычный 0, редкий 1, эпический 2, легендарный 3, мифический 4." + "death_sentence_contracts_effect_shadow_1" "Вражеские юниты появляются с +300 к базовой скорости передвижения." + "death_sentence_contracts_effect_shadow_2" "Сложнее расчленять крупные толпы без контроля." + "death_sentence_contracts_effect_fury_1" "Минимальный и максимальный базовый урон атак врагов умножен примерно на 1.08." + "death_sentence_contracts_effect_fury_2" "Сильнее давит фронтлайн и добивание без геройского контроля." + + "difficulty_description_easy" "♡Лёгкая сложность: ──────────────────────────────────────────

Отлично подходит для Ознакомления.

─────────────────────────────────────────

Особенности:

-50% Здоровья, урона, восстановления здоровья всех врагов.

+60/-60 секунд дневной фазы и ночной фазы

─────────────────────────────────────────

Для большего погружения рекомендуется выбрать сложность повыше.

─────────────────────────────────────────

" + "difficulty_description_death_sentence" "Смертельный приговор:

Режим контрактов. Выберите свой контракт ниже и выставьте его в общее голосование. Контракт повышает множитель статов врагов, накладывает дебаффы на команду и даёт дополнительную награду." + "difficulty_description_normal" "Нормальная сложность: ──────────────────────────────────────────

Отлично подходит для новичков.

─────────────────────────────────────────

Особенности:

Статы врагов не изменены.

─────────────────────────────────────────

Рекомендуется играть именно на этой сложности.
─────────────────────────────────────────" + "difficulty_description_hard" "Тяжёлая сложность: ──────────────────────────────────────────

Отлично подходит для уже подготовленных игроков.

─────────────────────────────────────────

Особенности:

+200% Здоровья, урона, восстановления здоровья всех врагов.

-60/+60 секунд дневной фазы и ночной фазы

─────────────────────────────────────────

Запускайте эту сложность лишь в том случае если знаете что делаете.

─────────────────────────────────────────

" + "difficulty_description_impossible" "AHAHAHAHAHAHAHAHAHAAHAHAHA
AHAHAHAHAHAHAHAHAHAAHAHAHA
AHAHAHAHAHAHAHAHAHAAHAHAHA
AHAHAHAHAHAHAHAHAHAAHAHAHA
AHAHAHAHAHAHAHAHAHAAHAHAHA
AHAHAHAHAHAHAHAHAHAAHAHAHA
AHAHAHAHAHAHAHAHAHAAHAHAHA
AHAHAHAHAHAHAHAHAHAAHAHAHA
AHAHAHAHAHAHAHAHAHAAHAHAHA AHAHAHAHAHAHAHAHAHAAHAHAHA AHAHAHAHAHAHAHAHAHAAHAHAHA AHAHAHAHAHAHAHAHAHAAHAHAHA AHAHAHAHAHAHAHAHAHAAHAHAHA AHAHAHAHAHAHAHAHAHAAHAHAHA AHAHAHAHAHAHAHAHAHAAHAHAHA AHAHAHAHAHAHAHAHAHAAHAHAHA AHAHAHAHAHAHAHAHAHAAHAHAHA AHAHAHAHAHAHAHAHAHAAHAHAHA AHAHAHAHAHAHAHAHAHAAHAHAHA AHAHAHAHAHAHAHAHAHAAHAHAHA AHAHAHAHAHAHAHAHAHAAHAHAHA AHAHAHAHAHAHAHAHAHAAHAHAHA AHAHAHAHAHAHAHAHAHAAHAHAHA AHAHAHAHAHAHAHAHAHAAHAHAHA AHAHAHAHAHAHAHAHAHAAHAHAHA AHAHAHAHAHAHAHAHAHAAHAHAHA AHAHAHAHAHAHAHAHAHAAHAHAHA AHAHAHAHAHAHAHAHAHAAHAHAHA " + + // Социальные ссылки + "social_discord" "Discord" + "social_boosty" "Boosty" + + "start_game" "Начать игру" + "cancel_game" "Отменить" + "ready_status_ready" "Готов" + "ready_status_not_ready" "Не готов" + "ready_button_ready" "Не готов" + "ready_button_not_ready" "Готов" + "setup_hints_title" "Подсказки" + "setup_hint_fallback_empty" "Подсказка пока не заполнена." + "setup_hint_1" "Каждая дневная секунда на счету, попытайся как можно больше успеть сделать за отведённое время." + "setup_hint_2" "Каждую минуту враги становятся сильнее на 2% поэтому тебе нельзя уступать им в силе ни на секунду." + "setup_hint_3" "Попытайся собрать карты которые хорошо комбинируют между собой, но не забывай что все ресурсы строго ограничены." + "setup_hint_4" "Если зажать ctrl то герой будет автоматически подбирать лежащие на полу предметы." + "setup_hint_5" "Не забывай что Zombie invasion 3 - командная игра и стоит сделать упор на игру с друзьями и хорошее настроение." + "setup_hint_6" "Если нажать таймер сверху то можно запустить голосование о наступлении ночи (Если так сделать то враги не получат дополнительное усиление с течением времени)" + "setup_hint_7" "Каждый герой по своему хорош, как по отдельности так и в команде! так что не забывайте помогать вашим танкам и дд чтобы стать сильнее если вы их саппорт!" + "setup_hint_8" "Стаки холода и огня гасят друг друга: если на цели уже много огня, новый холод сначала снимет часть этих стаков." + "setup_hint_9" "Холод: каждый стак снижает скорость передвижения и атаки на 1%; с 30+ стаков цель получает дополнительный входящий урон, а на 100 стаках замораживается." + "setup_hint_10" "Голод — это бафф от еды: стаки (до 100) дают бонус к базовым атрибутам и постепенно спадают со временем." + "setup_hint_11" "Не забывай тратить кристаллы в чёрном рынке, там герой может получить как карты, так и полезные баффы, также у торговца имеются интересные предложения для тебя." + "setup_hint_12" "Помощь за помощь: Помогать нпс очень полезно, так как это лёгкий заработок и возможность получить особенные награды, так что не советую пропускать это из виду!" + "setup_hint_13" "В этой кастомке имеются кастомные статы, например удача которая даёт процент для других процентов!" + "setup_hint_14" "Если тяжело побеждать, попробуй для начала изучить карту запустив игру вместе с sv_cheats!" + "setup_hint_15" "Часто появляются новые промокоды которые бывают опубликованы как в discord, так в telegramm, так что советую отслеживать это, если хочешь залутать халявной валюты." + "setup_hint_16" "Если в начале игры у тебя не так много карт, советую обратить внимание на другие аспекты игры которые позволяют играть даже без них." + "setup_hint_17" "В игре используется свой вампиризм, поэтому он работает чутка иначе как в доте и не имеет таких же ограничений как в обычной игре." + "setup_hint_18" "Если нужен совет по игре от профессионалов советую зайти в discord канал и поспрашивать совета для себя (Его можно найти на главной странице кастомки)" + "setup_hint_19" "Заметил баг? Не будь бякой напиши в bug-reports в discord канале!" + "setup_hint_20" "Ярость - то чем обладают некоторые герои, она даёт 0.25% доп исходящему урону за единицу ярости, также герои с яростью используют предметы не тратя маны!" + "setup_hint_21" "Самый простой способ победить играть до победного, однажды у тебя всё получится, если просто постараться." + "setup_hint_22" "Очень часто бывают проблемы с локализацией, так что если вы столкнулись с нрими вы можете написать о них в discord и обещаю они будут рано или поздно исправлены!" + "setup_hint_23" "На сложности impossible волновые враги с 15% шансом получают дополнительную способность." + "setup_hint_24" "Относитесь к новичкам с пониманием и также не ливайте с игр при возможности." + "setup_hint_25" "Можно получить дополнительные награды если вырваться в топ, чтобы глубже понять о чём речь советую посетить таблицу рейтинга в самой игре!" + + + + + "ending_cutscene_ready_title" "Готовность к финальной катсцене" + "ending_cutscene_ready_names_prefix" "Готовы:" + "ending_cutscene_ready_names_default" "Готовы: -" + "ending_cutscene_ready_timer_prefix" "Начало через:" + "ending_cutscene_ready_timer_seconds" "сек" + "ending_cutscene_ready_waiting_first" "Ожидание первого готового..." + "ending_cutscene_ready_toggle_collapse" "Скрыть панель" + "ending_cutscene_ready_toggle_expand" "Показать панель" + "skip_night_vote_title" "Пропустить день и начать ночь?" + "skip_night_vote_yes" "Согласен" + "skip_night_vote_no" "Отказаться" + + // Мини-профиль + "profile_title" "Профиль игрока" + "profile_loading" "Загрузка профиля..." + "profile_error" "Ошибка загрузки" + "profile_steam_id" "Steam ID:" + "profile_level" "Уровень" + "profile_stats_title" "Статистика" + "profile_games_played" "Игр сыграно:" + "profile_winrate" "Процент побед:" + "profile_wins" "Побед:" + "profile_losses" "Поражений:" + "profile_playtime" "Время игры:" + "profile_first_game" "Первая игра:" + "profile_first_game_unknown" "Неизвестно" + "profile_recent_games" "Последние игры" + "profile_no_games" "Игр ещё не было" + "profile_game_win" "Победа" + "profile_game_loss" "Поражение" + "profile_game_result" "Результат" + "profile_game_date" "Дата" + "profile_game_duration" "Время" + "profile_game_kd" "K/D" + "profile_game_hero" "Герой" + "profile_description" "Профиль игрока" + "mini_profile_tab_stats" "Статистика" + "mini_profile_tab_history" "История" + "mini_profile_tab_rewards" "Награды" + "mini_profile_tab_achievements" "Достижения" + "mini_profile_rewards_title" "Награды за уровень профиля" + "mini_profile_achievements_title" "Достижения героев" + "mini_profile_achievements_search_placeholder" "Поиск героя..." + "mini_profile_filter_all" "Все" + "mini_profile_filter_completed" "Пройдены" + "mini_profile_filter_impossible" "Невозможная" + "mini_profile_filter_unplayed" "Не играл" + "mini_profile_stats_line" "Игр: {games} Побед: {wins} WR: {wr}%" + "mini_profile_rewards_granted_now" "Получено сейчас: +{value} осколков" + "mini_profile_rewards_auto_hint" "Награды выдаются автоматически при достижении уровня" + "mini_profile_reward_level" "Уровень {level}" + "mini_profile_reward_value" "+{value} осколков" + "mini_profile_reward_status_granted_now" "Выдано сейчас" + "mini_profile_reward_status_claimed" "Получено" + "mini_profile_reward_status_locked" "Не достигнуто" + "hero_rank_tooltip_title" "Ранги героя" + "hero_rank_tooltip_subtitle" "Побеждай на более высокой сложности и получай осколки" + "hero_rank_tooltip_header_rank" "Ранг" + "hero_rank_tooltip_header_condition" "Условие" + "hero_rank_tooltip_header_reward" "Награда" + "hero_rank_name_rank0" "Никто" + "hero_rank_name_rank1" "Новичок" + "hero_rank_name_rank3" "Любитель" + "hero_rank_name_rank6" "Ветеран" + "hero_rank_name_rank8a" "Профи" + "hero_rank_name_rank8b" "Эксперт" + "hero_rank_name_rank8c" "Бессмертный" + "hero_rank_name_master" "Мастер" + "hero_rank_tooltip_condition_rank0" "Не проходил на герое" + "hero_rank_tooltip_condition_rank1" "Победа на легкой" + "hero_rank_tooltip_condition_rank3" "Победа на нормальной" + "hero_rank_tooltip_condition_rank6" "Победа на сложной" + "hero_rank_tooltip_condition_rank8a" "Победа на невозможной" + "hero_rank_tooltip_condition_rank8b" "Невозможная: 10+ побед" + "hero_rank_tooltip_condition_rank8c" "Невозможная: 100+ побед" + + // Battle Pass + "battle_pass_title" "BATTLE PASS" + "battle_pass_level" "Уровень:" + "battle_pass_xp" "XP" + "battle_pass_claim_all" "Забрать всё" + "battle_pass_loading" "Загрузка Battle Pass..." + "battle_pass_tab_rewards" "Награды" + "battle_pass_tab_quests" "Задания" + "battle_pass_track_free" "БЕСПЛАТНО" + "battle_pass_track_premium" "ПРЕМИУМ" + "battle_pass_premium_match_reward_bonus" "PREMIUM: бонус +50% к награде за матч активен" + "battle_pass_buy_button" "КУПИТЬ BATTLE PASS" + "battle_pass_claim" "Забрать" + "battle_pass_buy_dialog_title" "BATTLE PASS PREMIUM" + "battle_pass_buy_dialog_desc" "Получите доступ к эксклюзивным наградам премиум-трека!" + "battle_pass_buy_dialog_feature_1" "• Уникальные косметические предметы" + "battle_pass_buy_dialog_feature_2" "• Удвоенные награды на каждом уровне" + "battle_pass_buy_dialog_feature_3" "• Эксклюзивные эффекты и карточки" + "battle_pass_buy_dialog_feature_4" "• +50% к получаемому BP XP" + "battle_pass_buy_dialog_feature_5" "• +6 дополнительных ежедневных заданий" + "battle_pass_buy_dialog_price" "Стоимость:" + "battle_pass_buy_dialog_confirm" "Приобрести" + "battle_pass_buy_dialog_cancel" "Отмена" + "battle_pass_quests_empty" "Задания появятся скоро..." + "battle_pass_quests_loading" "Задания загружаются..." + "battle_pass_quests_daily" "ЕЖЕДНЕВНЫЕ ЗАДАНИЯ" + "battle_pass_quests_completed" "из" + "battle_pass_quests_completed_of" "выполнено" + "battle_pass_quests_refresh" "Обновление через:" + "battle_pass_quest_reward_xp" "BP XP" + "battle_pass_quest_completed" "Выполнено" + "battle_pass_error_purchase" "Ошибка покупки" + "battle_pass_description" "Боевой пропуск" + "battle_pass_no_reward" "Нет наград" + + // Battle Pass Quests ({target} подставляется из quest.target автоматически) + + "battle_pass_quest_kill_zombies_100_name" "Охотник" + "battle_pass_quest_kill_zombies_100_description" "Убейте {target} существ" + "battle_pass_quest_kill_zombies_300_name" "Истребитель" + "battle_pass_quest_kill_zombies_300_description" "Убейте {target} существ" + "battle_pass_quest_kill_zombies_1000_name" "Геноцид" + "battle_pass_quest_kill_zombies_1000_description" "Убейте {target} существ" + "battle_pass_quest_survive_10min_name" "Выживший" + "battle_pass_quest_survive_10min_description" "Проведите {target} минут в игре" + "battle_pass_quest_survive_20min_name" "Ветеран" + "battle_pass_quest_survive_20min_description" "Проведите {target} минут в игре" + "battle_pass_quest_buy_black_shop_3_name" "Покупатель" + "battle_pass_quest_buy_black_shop_3_description" "Купите {target} предмета на Чёрном рынке" + "battle_pass_quest_buy_black_shop_5_name" "Постоянный клиент" + "battle_pass_quest_buy_black_shop_5_description" "Купите {target} предметов на Чёрном рынке" + "battle_pass_quest_complete_quest_1_name" "Помощник" + "battle_pass_quest_complete_quest_1_description" "Выполните {target} задание у NPC" + "battle_pass_quest_complete_quest_3_name" "Герой деревни" + "battle_pass_quest_complete_quest_3_description" "Выполните {target} задания у NPC" + "battle_pass_quest_earn_gold_5000_name" "Золотодобытчик" + "battle_pass_quest_earn_gold_5000_description" "Заработайте {target} золота" + "battle_pass_quest_earn_gold_15000_name" "Банкир" + "battle_pass_quest_earn_gold_15000_description" "Заработайте {target} золота" + "battle_pass_quest_hero_level_10_name" "Рост героя" + "battle_pass_quest_hero_level_10_description" "Достигните {target} уровня героя" + "battle_pass_quest_hero_level_25_name" "Мастер" + "battle_pass_quest_hero_level_25_description" "Достигните {target} уровня героя" + "battle_pass_quest_survive_waves_5_name" "Стойкий" + "battle_pass_quest_survive_waves_5_description" "Выживите {target} волн" + "battle_pass_quest_survive_waves_10_name" "Несокрушимый" + "battle_pass_quest_survive_waves_10_description" "Выживите {target} волн" + "battle_pass_quest_buy_black_shop_10_name" "Верный клиент" + "battle_pass_quest_buy_black_shop_10_description" "Купите {target} предметов на Чёрном рынке" + "battle_pass_quest_buy_black_shop_common_name" "Начинающий коллекционер" + "battle_pass_quest_buy_black_shop_common_description" "Купите {target} предметов качества Common" + "battle_pass_quest_buy_black_shop_rare_name" "Ценитель редкостей" + "battle_pass_quest_buy_black_shop_rare_description" "Купите {target} предмета качества Rare" + "battle_pass_quest_buy_black_shop_epic_name" "Охотник за эпиками" + "battle_pass_quest_buy_black_shop_epic_description" "Купите {target} предмет качества Epic" + "battle_pass_quest_buy_black_shop_legendary_name" "Легенда рынка" + "battle_pass_quest_buy_black_shop_legendary_description" "Купите {target} предмет качества Legendary" + "battle_pass_quest_complete_quest_5_name" "Великий помощник" + "battle_pass_quest_complete_quest_5_description" "Выполните {target} заданий у NPC" + "battle_pass_quest_complete_cooking_3_name" "Повар" + "battle_pass_quest_complete_cooking_3_description" "Приготовьте {target} блюда на костре" + "battle_pass_quest_cook_grilled_meat_name" "Шеф-повар" + "battle_pass_quest_cook_grilled_meat_description" "Приготовьте жареное мясо" + "battle_pass_quest_tip_teammate_3_name" "Щедрый" + "battle_pass_quest_tip_teammate_3_description" "Отправьте {target} типа союзникам" + "battle_pass_quest_tip_teammate_5_name" "Великодушный" + "battle_pass_quest_tip_teammate_5_description" "Отправьте {target} типов союзникам" + "battle_pass_quest_no_death_15min_name" "Неуязвимый" + "battle_pass_quest_no_death_15min_description" "Выживите {target} минут без смерти" + "battle_pass_quest_heal_ally_2000_name" "Целитель" + "battle_pass_quest_heal_ally_2000_description" "Исцелите союзников на {target} HP" + "battle_pass_quest_deal_damage_50000_name" "Разрушитель" + "battle_pass_quest_deal_damage_50000_description" "Нанесите {target} урона" + "battle_pass_quest_collect_item_meat_20_name" "Мясник" + "battle_pass_quest_collect_item_meat_20_description" "Соберите {target} мяса" + "battle_pass_quest_collect_item_milk_20_name" "Дояр" + "battle_pass_quest_collect_item_milk_20_description" "Соберите {target} молока" + "battle_pass_quest_collect_item_wolf_claw_15_name" "Охотник на волков" + "battle_pass_quest_collect_item_wolf_claw_15_description" "Соберите {target} когтей волка" + "battle_pass_quest_collect_item_ent_heart_10_name" "Охотник на энтов" + "battle_pass_quest_collect_item_ent_heart_10_description" "Соберите {target} сердец энта" + "battle_pass_quest_earn_gold_30000_name" "Магнат" + "battle_pass_quest_earn_gold_30000_description" "Заработайте {target} золота" + "battle_pass_quest_survive_30min_name" "Легенда выживания" + "battle_pass_quest_survive_30min_description" "Проведите {target} минут в игре" + "battle_pass_quest_survive_waves_15_name" "Непробиваемый" + "battle_pass_quest_survive_waves_15_description" "Выживите {target} волн" + "battle_pass_quest_heal_ally_5000_name" "Великий целитель" + "battle_pass_quest_heal_ally_5000_description" "Исцелите союзников на {target} HP" + "battle_pass_quest_deal_damage_100000_name" "Разрушитель миров" + "battle_pass_quest_deal_damage_100000_description" "Нанесите {target} урона" + "battle_pass_quest_play_different_hero_3_name" "Разнообразие" + "battle_pass_quest_play_different_hero_3_description" "Сыграйте {target} разными героями" + "battle_pass_quest_play_different_hero_5_name" "Мастер всех" + "battle_pass_quest_play_different_hero_5_description" "Сыграйте {target} разными героями" + + // Admin menu UI + "admin_menu_description" "Админ-меню" + "admin_menu_section_create" "СОЗДАТЬ" + "admin_menu_change_hero" "ПОМЕНЯТЬ ГЕРОЯ" + "admin_menu_add_ally" "+СОЮЗНИК" + "admin_menu_add_enemy" "+ВРАГ" + "admin_menu_add_dummy" "+МИШЕНЬ" + + "admin_menu_section_selected_units" "ВЫБРАННЫЕ СУЩЕСТВА" + "admin_menu_level_plus_1" "+1 УР." + "admin_menu_level_plus_30" "+30 УР." + "admin_menu_spells" "ЗАКЛ." + "admin_menu_hp" "ЗДОРОВЬЕ" + "admin_menu_invulnerable" "НЕУЯЗВ." + "admin_menu_add_scepter" "+SCEPTER" + "admin_menu_add_shard" "+SHARD" + "admin_menu_add_card" "+КАРТА" + "admin_menu_side" "СТОРОНА" + "admin_menu_reset" "СБРОСИТЬ" + "admin_menu_delete" "УДАЛИТЬ" + + "admin_menu_section_give" "ФУНКЦИИ" + "admin_menu_give_items" "ВЫДАТЬ ПРЕДМЕТЫ" + "admin_menu_give_stats" "ВЫДАТЬ СТАТЫ" + "admin_menu_spawn_zones" "ЗОНЫ СПАВНА" + + "admin_menu_game_controls" "ИГРОВЫЕ ЭЛЕМЕНТЫ:" + "admin_menu_pause" "ПАУЗА" + "admin_menu_apply" "ПРИМЕНИТЬ" + "admin_menu_script_reload" "SCRIPT RELOAD" + "admin_menu_spawn_zones_debug" "+ЗОНЫ СПАВНА" + + "admin_menu_luck" "УДАЧА" + "admin_menu_phys_vamp" "ФИЗ. ВАМПИРИЗМ" + "admin_menu_magic_vamp" "МАГ. ВАМПИРИЗМ" + "admin_menu_gold" "ЗОЛОТО" + "admin_menu_crystals" "КРИСТАЛЛЫ" + "admin_menu_give" "ВЫДАТЬ" + + "admin_menu_spawn_shape" "ФОРМА ЗОНЫ" + "admin_menu_spawn_shape_circle" "КРУГ" + "admin_menu_spawn_shape_square" "КВАДРАТ" + "admin_menu_spawn_radius" "РАДИУС (КРУГ)" + "admin_menu_spawn_size" "ШИРИНА/ВЫСОТА (КВАДРАТ)" + "admin_menu_spawn_units_label" "ЮНИТЫ (npc_name:count;npc_name2:count2)" + "admin_menu_spawn_create_here" "СОЗДАТЬ ЗОНУ ПОД ГЕРОЕМ" + "admin_menu_spawn_delete_here" "УДАЛИТЬ ЗОНУ ПОД ГЕРОЕМ" + "admin_menu_spawn_behavior" "ПОВЕДЕНИЕ ЗОНЫ" + "admin_menu_spawn_behavior_neutral" "НЕЙТРАЛЬНЫЙ" + "admin_menu_spawn_behavior_friendly" "ДРУЖЕЛЮБНЫЙ" + "admin_menu_spawn_behavior_aggressive" "АГРЕССИВНЫЙ" + + "admin_menu_items_tab_default" "ОБЫЧНЫЕ" + "admin_menu_items_tab_blackshop" "BLACKSHOP" + "admin_menu_search_item" "Поиск предмета..." + + "admin_spawn_units_help_title" "Доступные юниты" + + + "hero_selection_time_remaining" "До штрафного времени" + "hero_selection_gold_lost" "потерянного золота" + + // Match Details (детали матча) + "match_details_title" "Детали матча" + "match_details_info" "Информация о матче" + "match_details_stats" "Статистика" + "match_details_items" "Предметы" + "match_details_permanent_effects" "Постоянные эффекты" + "match_details_result_win" "победа" + "match_details_result_loss" "Поражение" + "match_details_duration" "Длительность" + "match_details_date" "Дата" + "match_details_hero" "Герой" + "match_details_level" "Уровень героя" + "match_details_kills" "Убийств" + "match_details_deaths" "Смертей" + "match_details_gold" "Золота заработано" + "match_details_no_items" "Предметы не найдены" + "match_details_no_modifiers" "Постоянные эффекты отсутствуют" + "match_details_upgrades_section" "Постоянные эффекты героя" + "match_details_aghanim_scepter" "Скипетр Аганима" + "match_details_aghanim_shard" "Осколок Аганима" + "match_details_stat_kd" "Убийства / Смерти" + "match_details_stat_damage" "Итоговый подсчёт урона:" + "match_details_stat_outgoing" "Исходящий урон:" + "match_details_stat_incoming" "Входящий урон:" + "match_details_stat_gold_earned" "Заработано золота:" + "match_details_loading_players" "Загрузка состава матча..." + "match_details_solo_queue" "Матч соло" + + "leaderboard_title" "Таблица лидеров" + "leaderboard_loading" "Загрузка таблицы лидеров..." + "leaderboard_col_player" "Игрок" + "leaderboard_col_rank" "Ранг" + "leaderboard_col_rating" "Рейтинг" + "leaderboard_col_level" "Уровень" + "leaderboard_col_games" "Игр" + "leaderboard_error_prefix" "Ошибка:" + "leaderboard_request_failed" "Запрос не выполнен" + "leaderboard_no_data" "Данные лидерборда отсутствуют" + "leaderboard_invalid_format" "Неверный формат данных лидерборда" + "leaderboard_process_error" "Ошибка обработки данных" + "leaderboard_your_position" "Ваша позиция:" + "leaderboard_rank_novice" "Новичок" + "leaderboard_tooltip" "Таблица рейтинга" + + "store_donation_title" "Пополнение донат-осколков" + "store_donation_desc" "Укажи сумму в рублях — откроется оплата Robokassa (карта, СБП)." + "store_donation_rate" "Курс: 1 ₽ = 2 донат-осколка" + "store_donation_amount_placeholder" "Сумма в рублях" + "store_donation_pay" "Оплатить" + "store_donation_loading" "Создаём ссылку на оплату…" + "store_donation_preview_get" "Получишь" + "store_donation_preview_empty" "Укажите сумму в рублях" + "store_donation_amount_range" "Сумма от 10 до 50000 ₽" + "store_donation_pay_card" "Оплатить на сайте (Robokassa)" + + "admin_menu_search_hero" "Поиск героя..." + "admin_menu_spawn_without_zone" "Заспавнить без зоны" + "admin_menu_night_wave_title" "Ночь / волна" + "admin_menu_force_night" "Сделать ночь" + "admin_menu_force_morning" "Сделать утро" + "admin_menu_day_night_timer_title" "Таймер дня / ночи" + "admin_menu_set_timer" "Выставить" + "admin_menu_endless_day" "Бесконечный день" + "admin_menu_items_tab_util" "УТИЛ. ПРЕДМЕТЫ" + "admin_menu_items_tab_quest" "КВЕСТОВЫЕ" + "admin_menu_items_tab_admin" "АДМИН" + "admin_menu_time_scale_title" "Скорость времени" + + "battle_pass_reward_battle_shards" "Боевые осколки" + "battle_pass_reward_zombie_shards" "Зомби осколки" + "battle_pass_reward_chest" "Сундук" + "battle_pass_reward_card" "Карта" + "battle_pass_reward_lightning_effect" "Эффект молнии" + "battle_pass_reward_dust" "пыль" + "battle_pass_reward_dust_title" "Пыль" + "battle_pass_reward_arcade_pack_standard" "станд. пак" + "battle_pass_time_abbr_hours" "ч" + "battle_pass_time_abbr_minutes" "м" + "battle_pass_quest_progress_min_suffix" "мин" + + "mmr_rank_immortal" "Бессмертный" + "mmr_rank_divine" "Божество" + "mmr_rank_ancient" "Властелин" + "mmr_rank_legend" "Легенда" + "mmr_rank_archon" "Архонт" + "mmr_rank_crusader" "Рыцарь" + "mmr_rank_guardian" "Страж" + "mmr_rank_herald" "Рекрут" + "mmr_rank_uncalibrated" "Новичок" + + "profile_exp_abbr" "опыт" + "mini_profile_error_processing" "Ошибка обработки данных" + + "cooking_panel_tab_ingredients" "Ингредиенты" + "cooking_panel_tab_dishes" "Блюда" + + "deck_builder_no_deck_selected" "Нет колоды" + "deck_builder_slot_empty" "Пусто" + "deck_builder_card_in_deck_label" "В колоде" + "deck_builder_quality_common" "Обычная" + "deck_builder_quality_rare" "Редкая" + "deck_builder_quality_epic" "Эпическая" + "deck_builder_quality_legendary" "Легендарная" + "deck_builder_quality_mythic" "Мифическая" + + // -----------------Arsenal-------------------- + "arsenal_open_button" "Арсенал" + "arsenal_title" "АРСЕНАЛ" + "arsenal_heroes_section" "Герои" + "arsenal_catalog_title" "Доступная экипировка" + "arsenal_filter_slot_label" "Слот" + "arsenal_filter_rarity_label" "Редкость" + "arsenal_filter_stat_label" "Статы" + "arsenal_filter_stats_toggle_show" "Фильтр по статам…" + "arsenal_filter_stats_toggle_hide" "Скрыть фильтр по статам" + "arsenal_equipped_stats_title" "БОНУСЫ ОТ ЭКИПИРОВКИ" + "arsenal_equipped_stats_empty" "Нет надетых предметов" + "arsenal_incoming_reduction_linear_sum" "сумма по предметам" + "arsenal_batch_selected_prefix" "Выбрано" + "arsenal_batch_selected_empty" "Выбрано: 0" + "arsenal_batch_alt_hint" "Alt + ЛКМ для множественного выбора" + "arsenal_batch_select_all" "Выбрать всё" + "arsenal_batch_favorite" "В любимые" + "arsenal_catalog_empty" "Здесь будут предметы из дропа." + "arsenal_select_hero_hint" "Выбери героя слева" + "arsenal_no_heroes" "Нет доступных героев" + "arsenal_clear" "Снять всё" + "arsenal_filter_all" "Все" + "arsenal_slot_weapon" "Оружие" + "arsenal_slot_armor" "Броня" + "arsenal_slot_helmet" "Шлем" + "arsenal_slot_boots" "Ботинки" + "arsenal_slot_necklace" "Ожерелье" + "arsenal_slot_ring" "Кольцо" + "arsenal_locked_in_game" "Арсенал можно менять только на экране настройки игры (до старта матча)" + "arsenal_item_not_owned" "У тебя нет этого предмета" + "arsenal_rarity_common" "Обычный" + "arsenal_rarity_rare" "Редкий" + "arsenal_rarity_epic" "Эпический" + "arsenal_rarity_legendary" "Легендарный" + "arsenal_rarity_mythic" "Мифический" + "arsenal_tooltip_owned" "В наличии: {d:arsenal_owned_qty}" + "arsenal_tooltip_owned_unique" "Уникальный экземпляр в инвентаре" + "arsenal_tooltip_not_owned" "Нет в инвентаре" + "arsenal_tooltip_serial_prefix" "Серийный №" + "arsenal_tooltip_stats_title" "Случайные статы" + "arsenal_tooltip_stats_unavailable" "Статы недоступны" + "arsenal_tooltip_unknown_fifth" "5-й стат: ??? (откроется на 5 уровне)" + "arsenal_detail_pin" "Закрепить" + "arsenal_detail_unpin" "Снять закреп" + "arsenal_detail_disassemble" "Разобрать" + "arsenal_detail_upgrade" "Улучшить" + "arsenal_detail_upgrade_max" "Макс. уровень" + "arsenal_detail_pinned_mark" "закреплён" + "arsenal_disassemble_pinned" "Нельзя разобрать закреплённый предмет" + "arsenal_disassemble_equipped" "Нельзя разобрать экипированный предмет (сначала снимите со слота)" + "arsenal_tooltip_equipped_header" "Экипировано на героях:" + "arsenal_upgrade_not_enough_shards" "Недостаточно зомби-осколков для улучшения" + "arsenal_stat_bonus_damage" "к урону" + "arsenal_stat_base_damage_pct" "к базовому урону" + "arsenal_stat_outgoing_damage_pct" "к исходящему урону" + "arsenal_stat_attack_speed" "к скорости атаки" + "arsenal_stat_attack_speed_pct" "к скорости атаки" + "arsenal_stat_bonus_armor" "к броне" + "arsenal_stat_max_health" "к здоровью" + "arsenal_stat_max_mana" "к мане" + "arsenal_stat_health_regen" "к регену здоровья" + "arsenal_stat_mana_regen" "к регену маны" + "arsenal_stat_move_speed" "к скорости передвижения" + "arsenal_stat_move_speed_pct" "к скорости передвижения" + "arsenal_stat_magic_resist" "к сопротивлению магии" + "arsenal_stat_spell_amp" "к усилению заклинаний" + "arsenal_stat_all_stats" "ко всем атрибутам" + "arsenal_stat_all_stats_pct" "ко всем атрибутам" + "arsenal_stat_bonus_strength" "к силе" + "arsenal_stat_bonus_agility" "к ловкости" + "arsenal_stat_bonus_intellect" "к интеллекту" + "arsenal_stat_strength_pct" "к силе" + "arsenal_stat_agility_pct" "к ловкости" + "arsenal_stat_intellect_pct" "к интеллекту" + "arsenal_stat_luck" "к удаче" + "arsenal_stat_incoming_damage_reduction_pct" "к снижению входящего урона" + "arsenal_stat_crit_mult" "к множителю крита" + "arsenal_stat_battle_level" "боевой уровень" + "DOTA_Tooltip_Ability_item_arsenal_weapon_iron_blade" "Железный кортик" + "DOTA_Tooltip_Ability_item_arsenal_weapon_iron_blade_Description" "Простой клинок, выкованный из железа." + "DOTA_Tooltip_Ability_item_arsenal_weapon_storm_edge" "Грозовой край" + "DOTA_Tooltip_Ability_item_arsenal_weapon_storm_edge_Description" "Лезвие, гудящее от молний." + "DOTA_Tooltip_Ability_item_arsenal_weapon_sunfang_saber" "Сабля Солнечного Клыка" + "DOTA_Tooltip_Ability_item_arsenal_weapon_sunfang_saber_Description" "Раскалённая сабля, оставляющая за собой огненный след." + "DOTA_Tooltip_Ability_item_arsenal_armor_chain_mail" "Кольчуга" + "DOTA_Tooltip_Ability_item_arsenal_armor_chain_mail_Description" "Лёгкий доспех." + "DOTA_Tooltip_Ability_item_arsenal_armor_dragon_plate" "Драконья кираса" + "DOTA_Tooltip_Ability_item_arsenal_armor_dragon_plate_Description" "Скованная из чешуи древнего дракона." + "DOTA_Tooltip_Ability_item_arsenal_armor_vanguard_shell" "Панцирь Авангарда" + "DOTA_Tooltip_Ability_item_arsenal_armor_vanguard_shell_Description" "Массивный панцирь, созданный для лобового штурма." + "DOTA_Tooltip_Ability_item_arsenal_helmet_leather_cap" "Кожаная шапка" + "DOTA_Tooltip_Ability_item_arsenal_helmet_leather_cap_Description" "Скромная защита для головы." + "DOTA_Tooltip_Ability_item_arsenal_helmet_arcane_circlet" "Магический венец" + "DOTA_Tooltip_Ability_item_arsenal_helmet_arcane_circlet_Description" "Усиливает связь с маной." + "DOTA_Tooltip_Ability_item_arsenal_helmet_warcrown_mask" "Боевая корона-маска" + "DOTA_Tooltip_Ability_item_arsenal_helmet_warcrown_mask_Description" "Маска полководца, внушающая страх на поле боя." + "DOTA_Tooltip_Ability_item_arsenal_boots_swift_boots" "Лёгкие сапоги" + "DOTA_Tooltip_Ability_item_arsenal_boots_swift_boots_Description" "Удобная обувь для долгих переходов." + "DOTA_Tooltip_Ability_item_arsenal_boots_phase_treads" "Фазовые сапоги" + "DOTA_Tooltip_Ability_item_arsenal_boots_phase_treads_Description" "Сапоги, шагающие сквозь пространство." + "DOTA_Tooltip_Ability_item_arsenal_boots_hermes_greaves" "Поножи Гермеса" + "DOTA_Tooltip_Ability_item_arsenal_boots_hermes_greaves_Description" "Крылатые поножи, дарующие молниеносный рывок." + "DOTA_Tooltip_Ability_item_arsenal_necklace_amber_pendant" "Янтарный кулон" + "DOTA_Tooltip_Ability_item_arsenal_necklace_amber_pendant_Description" "Тёплый янтарь поглощает заклинания." + "DOTA_Tooltip_Ability_item_arsenal_necklace_soul_locket" "Медальон душ" + "DOTA_Tooltip_Ability_item_arsenal_necklace_soul_locket_Description" "В медальоне заточены души павших магов." + "DOTA_Tooltip_Ability_item_arsenal_necklace_prism_choker" "Призматический ошейник" + "DOTA_Tooltip_Ability_item_arsenal_necklace_prism_choker_Description" "Кристаллы ошейника преломляют и усиливают магию." + "DOTA_Tooltip_Ability_item_arsenal_ring_copper_band" "Медное кольцо" + "DOTA_Tooltip_Ability_item_arsenal_ring_copper_band_Description" "Простой ободок медного цвета." + "DOTA_Tooltip_Ability_item_arsenal_ring_void_signet" "Печать пустоты" + "DOTA_Tooltip_Ability_item_arsenal_ring_void_signet_Description" "Кольцо, наполненное силой бездны." + "DOTA_Tooltip_Ability_item_arsenal_ring_titan_loop" "Кольцо Титана" + "DOTA_Tooltip_Ability_item_arsenal_ring_titan_loop_Description" "Древний перстень с печатью исполинов." + + "card_selection_card_word" "Карта" + "card_selection_no_description" "Описание недоступно" + "card_selection_error_hero_dead" "Нельзя выбрать карту, пока герой мёртв" + + "store_item_hero_drow_ranger_name" "Тракса" + "store_item_hero_drow_ranger_description" "Легендарная лучница с невероятной точностью. Наносит колоссальный физический урон." + "store_item_hero_lina_name" "Лина" + "store_item_hero_lina_description" "Маг огня с разрушительными заклинаниями. Наносит огромный магический урон." + "store_item_hero_vengefulspirit_name" "Vengeful Spirit" + "store_item_hero_vengefulspirit_description" "Мстительный дух поддержки: аура урона и вампиризма, волна ужаса с резкой брони, обмен жизнями и пожирание духов павших врагов." + "store_item_hero_phantom_assassin_name" "Фантомка" + "store_item_hero_phantom_assassin_description" "Смертоносная убийца. Наносит огромный физический урон одиночным целям." + "store_item_hero_sven_name" "Свен" + "store_item_hero_sven_description" "Могучий воин. Готов выдержать толпы врагов и нанести им огромный физический урон." + "store_item_skin_effect_fire_name" "Заколённый огнём" + "store_item_skin_effect_ice_name" "Закованный льдом" + "store_item_skin_effect_heaven_name" "Эффект божества" + "store_item_skin_effect_fall_2021_emblem_name" "Сияние аганима" + "store_item_skin_effect_blue_gems_name" "Сияние сапфиров" + "store_item_skin_effect_sponsor_name" "Эффект спонсора" + "store_item_skin_effect_lotus_name" "Эффект лотоса" + "store_item_skin_effect_bp_red_name" "Батл-пасс: багряная аура" + "store_item_skin_effect_bp_garden_name" "Батл-пасс: сад" + "store_item_skin_effect_bp_golden_name" "Батл-пасс: золото" + "store_item_skin_effect_bp_diretide_emblem_name" "Батл-пасс: эмблема Diretide" + "store_item_skin_effect_bp_diretide_emblem_v1_name" "Батл-пасс: эмблема Diretide I" + "store_item_skin_effect_bp_diretide_emblem_v3_name" "Батл-пасс: эмблема Diretide III" + "store_item_chat_wheel_sound_jump_name" "Прыгай, дура" + + "store_item_hero_bloodhunter_name" "Охотник за кровью" + "store_item_hero_bloodhunter_description" "Кастомный кэри в духе Bloodseeker: кровь, иллюзии и добивание." + "store_item_hero_sand_king_name" "Sand King" + "store_item_hero_sand_king_description" "Король пустыни: контроль, урон по площади и выживаемость в волнах." + "store_item_hero_nagash_name" "Нагаш" + "store_item_hero_nagash_description" "Кастомный силовик: тёмные союзники, големы и баффы армии." + "store_item_hero_yuki_onna_name" "Юки-онна" + "store_item_hero_yuki_onna_description" "Кастомный дух льда: метели, ритуалы и призывы." + "store_item_hero_sargatanas_name" "Саргатанас" + "store_item_hero_sargatanas_description" "Кастомный демон: шаги ада, рассекание, призывы и Метаморфоза." + "store_item_hero_elder_dragon_smaug_name" "Смауг, древний дракон" + "store_item_hero_elder_dragon_smaug_description" "Кастомный герой на все атрибуты: огонь, аура страха и ярость." + "store_item_hero_mirana_name" "Mirana" + "store_item_hero_mirana_description" "Лунная охотница: стрелы, звёздный дождь и синергия с Луной. Урон растёт от маны." + "store_item_hero_spectre_name" "Spectre" + "store_item_hero_spectre_description" "Тень на поле боя: кинжал, отражение Dispersion, тени Echo и ульта Haunt с Reality." + "store_item_hero_silencer_name" "Сайленсер" + "store_item_hero_silencer_description" "Интеллектуальный керри-контроллер: безмолвие, орбы чистого урона и глобальный сайленс." + "store_item_hero_keeper_of_the_light_name" "Keeper of the Light" + "store_item_hero_keeper_of_the_light_description" "Сильный маг поддержки: лечит союзников, ослепляет врагов и контролирует темп боя." + + "hud_button_skip" "Голосовать за пропуск" + + // Квесты + "quest_kill_pigs_title" "Убить свиней" + "quest_kill_pigs_description" "Огнезвёзд просит вас помочь ему добыть еды для его племени." + + "quest_firestar_gourmet_title" "Гурман" + "quest_firestar_gourmet_description" "Приготовьте 5 порций жареного мяса для Огнезвёзда, чтобы он устроил настоящий пир." + "quest_firestar_leader_horn_title" "Рог вожака" + "quest_firestar_leader_horn_description" "Огнезвёзд просит принести рог вожака ликанов. Говорит, для очень важного ритуала." + "quest_firestar_fishing_title" "Рыбный должок" + "quest_firestar_fishing_description" "Огнезвёзд признался, что спиздил удочку у Кунки. Лови 10 рыбин и принеси ему, чтобы он не получил по усам." + + "firestar_quest_1_message_1" "Огнезвёзд: Мррряу! Мяу-мяу мур-мур!" + "firestar_quest_1_message_2" "Огнезвёзд: РРРР! Мяу мур-мур 10 мяу! Мррряу мяу мяу!" + "firestar_quest_1_message_3" "Огнезвёзд: Мур-мур! РРРР! Мяяяяу!" + + "firestar_quest_1_message_complete_1" "Огнезвёзд: Мяур мяу--муя мяу муа мяэур!" + + "firestar_quest_gourmet_message_1" "Огнезвёзд: Мррр... Пахнет жареным мясом! Приготовишь ещё пару порций специально для меня?" + "firestar_quest_gourmet_message_complete_1" "Огнезвёзд: Мяу! Вот это пир! Такое мясо грех есть одному — забирай награду, повар." + "firestar_quest_leader_horn_message_complete_1" "Огнезвёзд: Рог вожака у меня! Отлично. Теперь есть дело посложнее..." + "firestar_quest_fishing_message_1" "Огнезвёзд: Ладно, признаюсь... я спиздил удочку у Кунки. Держи её и принеси мне 10 рыб, пока он не заметил!" + "firestar_quest_fishing_message_complete_1" "Огнезвёзд: Мяу! И рыба есть, и Кунка не в курсе. Ты меня выручил!" + + "quest_kill_sheep_title" "Убийство овец" + "quest_kill_sheep_description" "Помоги деду добыть молока и принесите его ему." + "quest_oldmen_cheesemaker_title" "Сыровар" + "quest_oldmen_cheesemaker_description" "Приготовьте и принесите деду кілька головок сыра, чтобы он смог сделать свои любимые вареники." + + "lina_quest_ent_heart_title" "Сердце энта" + "lina_quest_ent_heart_description" "Лина просит принести ей сердце энта, взамен она даст что-то." + "lina_quest_ent_heart_message_complete_1" "Лина: Отличная работа! Пламя в этом сердце еще живо. Возьми это ядро, оно мне не нужно." + + + "dota_tooltip_ability_item_pollen_keeper" "Сборщик пыльцы" + "dota_tooltip_ability_item_pollen_keeper_Description" "

Пассивное: Сбор пыльцы

Атакуя Сгустки энергии, с шансом %chance%%% получает 1 заряд пыльцы (максимум: 20)." + + "lina_quest_bottle_title" "Пыльца для Лины" + "lina_quest_bottle_description" "Соберите 20 зарядов пыльцы в бутылке и вернитесь к Лине." + "lina_quest_bottle_message_1" "Лина: Вот бутылка. Наполни ее пыльцой огненных цветов и принеси обратно 20 зарядов." + "lina_quest_bottle_message_complete_1" "Лина: Прекрасно! Пыльца собрана, пламя стало сильнее. Спасибо за помощь." + + "largo_quest_kill_frogs_title" "Охота на лягушек" + "largo_quest_kill_frogs_description" "Ларго просит очистить болото: уничтожьте мини-лягушек, лягушат и лягушек-магистров." + "largo_quest_kill_frogs_message_1" "Ларго: Болото снова кишит тварями. Убей 20 лягушек и вернись ко мне." + "largo_quest_kill_frogs_message_complete_1" "Ларго: Отличная работа! Болото стало тише. Держи свою награду." + + "largo_quest_spider_legs_title" "Паучьи лапки" + "largo_quest_spider_legs_description" "Ларго просит принести 20 паучьих лапок для охотничьих трофеев." + "largo_quest_spider_legs_message_1" "Ларго: После зачистки болот нужны трофеи с пауков. Принеси 20 паучьих лапок." + "largo_quest_spider_legs_message_complete_1" "Ларго: Отличные трофеи. Эти лапки пригодятся в ремесле и для приманок." + + "maiden_quest_eggs_title" "Яйца для ЦМки" + "maiden_quest_eggs_description" "Кристал Мейден просит принести 20 яиц." + "maiden_quest_eggs_message_1" "ЦМка: Мне нужны яйца для ритуала холода. Принеси 20 штук, и я щедро отблагодарю." + "maiden_quest_eggs_message_complete_1" "ЦМка: Отлично! Как раз столько, сколько нужно. Спасибо, держи награду." + + "doctor_quest_frog_paws_title" "Лягушачьи лапки" + "doctor_quest_frog_paws_description" "Доктор просит принести 20 лягушачьих лапок для своих зелий." + "doctor_quest_frog_paws_message_1" "Доктор: Мне нужны свежие лягушачьи лапки. Принеси 20 штук для моих зелий." + "doctor_quest_frog_paws_message_complete_1" "Доктор: Прекрасный материал. Этого хватит на целую партию зелий. Держи награду." + + "doctor_quest_poison_title" "Токсичный экстракт" + "doctor_quest_poison_description" "Доктору нужен яд пауков. Принеси 20 порций, чтобы он завершил формулу." + "doctor_quest_poison_message_1" "Доктор: Отлично, теперь мне нужен чистый паучий яд. Принеси 20 порций." + "doctor_quest_poison_message_complete_1" "Доктор: Идеально. Яд нужной концентрации. На его основе я закончу сыворотку." + + "friend_quest_pizza_prep_title" "Заготовка для пиццы" + "friend_quest_pizza_prep_description" "Гурд просит приготовить заготовку для пиццы." + "friend_quest_pizza_prep_message_1" "Гурд: Искатель! Держи рецепт на заготовку для пиццы, а после я дам тебе свой крутой соус. Будет самая вкусная штука в мире! Пальчики оближешь!" + + "friend_quest_pizza_prep_message_complete_1" "Гурд: Используй мой крутой соус. Это будет самая вкусная штука в мире!" + "friend_quest_pizza_prep_message_complete_2" "Гурд: Спасибо что поделился со мной, держи мой дар! Я уверен он тебе поможет." + + "oldmen_quest_1_message_1" "Дід: Ой, шо ж ты, хлопче, допоможи старому діду, будь ласка!" + "oldmen_quest_1_message_2" "Дід: Молочка б мені, бо без нього як без сала - ніяк! Роздобудь трохи, га?" + "oldmen_quest_1_message_3" "Дід: Я тобі за це такого гостинця дам - мама не горюй!" + + "oldmen_quest_1_message_complete_1" "Дід: Ой, дякую тобі, синку! Оце молочко - саме те, що треба!" + "oldmen_quest_1_message_complete_2" "Дід: Тепер я зможу зробити вареники з сиром, як моя бабуся колись робила!" + "oldmen_quest_1_message_complete_3" "Дід: На тобі, козаче, за твою доброту! Це чарівное тесто, він тобі у пригоді стане!" + + "oldmen_quest_cheesemaker_message_1" "Дід: Ой, синку, молочко — то добре, але без справжнього сиру вареники не ті!" + "oldmen_quest_cheesemaker_message_2" "Дід: Зроби мені трохи сиру, га? Хоч кілька головок принеси — я тобі такого частування влаштую!" + "oldmen_quest_cheesemaker_message_complete_1" "Дід: Оце так сир! От тепер будуть вареники — пальці оближеш! Дякую тобі, хлопче." + + "quest_give_claw_title" "Пирату нужны когти?" + "quest_give_claw_description" "Пират просит вас достать для него когти волков. Он не объясняет зачем, но обещает заплатить щедро." + + "kunkka_quest_give_claw_message_1" "Кункевич: Эй, морячок! Мне нужны когти волков, и побыстрее!" + "kunkka_quest_give_claw_message_2" "Кункевич: Не спрашивай зачем — просто принеси. Считай, что это часть пиратского обряда!" + "kunkka_quest_give_claw_message_3" "Кункевич: За каждый коготь получишь золота больше, чем за бочку рома!" + + "kunkka_quest_give_claw_message_complete_1" "Кункевич: Вот это да! Ты и правда достал их!" + "kunkka_quest_give_claw_message_complete_2" "Кункевич: Из этих когтей сшью тебе волчью шапку — носи, как знак вожака стаи!" + "kunkka_quest_give_claw_message_complete_3" "Кункевич: Держи награду, ты её заслужил!" + "kunkka_quest_give_claw_message_complete_4" "Кункевич: Если попадёшь в передрягу — зови меня, я теперь с удачей!" + + "quest_found_rom_title" "В поисках рома" + "quest_found_rom_description" "Помогите пирату отыскать его сокровище в бутылочке" + + "kunkka_quest_find_rom_message_1" "Кункевич: Чёрт возьми, где мой ром?! Кто-то из вас, крыс, стырил его из трюма?" + "kunkka_quest_find_rom_message_2" "Кункевич: Если через пять минут бутыль не окажется у меня в руках, начну вешать через одного! " + "kunkka_quest_find_rom_message_3" "Кункевич: Ищите, сволочи, этот ром дороже ваших жалких жизней!" + + "kunkka_quest_find_rom_message_complete_1" "Кункевич: ХА-ХА-ХА! ДА ВЫ ПОСМОТРИТЕ НА ЭТОГО ГЕНИЯ!" + "kunkka_quest_find_rom_message_complete_2" "Кункевич: Ты, дружок, только что купил себе место в раю" + "kunkka_quest_find_rom_message_complete_3" "Кункевич: Ну, или хотя бы отсрочку от ада на этом гребаном судне!" + "kunkka_quest_find_rom_message_complete_4" "Кункевич: Возьми мой запасной клинок, пес! Пусть теперь служит тому, кто не боится его тяжести!" + + "quest_kunkka_skeletons_title" "Очиститель кладбища часть 1" + "quest_kunkka_skeletons_description" "Пирата уже заманали скелеты на кладбище и он попросил вас избавиться от парочки из их вида." + + "zone_skeletons_clean" "Очистка кладбища" + + "quest_kunkka_clean_harbor_title" "Чистая гавань" + "quest_kunkka_clean_harbor_description" "Выставь флаг в мёртвой воде и зачисти гавань от костяной мрази." + + "quest_kunkka_kill_lycan_title" "Охота на Чёрного клыка" + "quest_kunkka_kill_lycan_description" "Выслеживайте и убейте босса. Его вой уже слишком давно тревожит эти земли." + "quest_kunkka_kill_satyr_demon_title" "Охота на демона-сатира" + "quest_kunkka_kill_satyr_demon_description" "Найдите и убейте демона-сатира. Вернитесь к Кункевичу за наградой." + + "kunkka_quest_2_message_1" "Кункевич: ЧТОБ ИХ ВСЕХ КРАКЕН СОЖРАЛ! Проклятые скелеты! Развелось их тут, как крыс в трюме, чтоб им пусто было!" + "kunkka_quest_2_message_2" "Кункевич: ЭЙ, ОТВАЖНЫЙ МОРСКОЙ ВОЛК! Золотишком не хочешь разжиться?! Спаси старого пирата от этой костлявой напасти!" + "kunkka_quest_2_message_3" "Кункевич: РАЗНЕСИ В ЩЕПКИ хотя бы десяток этих ходячих мертвецов, и КЛЯНУСЬ МОРСКИМ ДЬЯВОЛОМ, я озолочу тебя по самые уши!" + + "kunkka_quest_kill_lycan_message_1" "Кункевич: Слышал вой Ликана? Этот зверюга портит мне весь вид на море. Уложишь его — получишь награду, как истинный охотник." + "kunkka_quest_kill_lycan_message_complete_1" "Кункевич: Вот это добыча! Такой зверь не падает от слабой руки. Держи плату, ты её честно заслужил." + "kunkka_quest_kill_satyr_demon_message_1" "Кункевич: Демон-сатир бродит рядом и пугает весь берег. Найди его и отправь на дно." + "kunkka_quest_kill_satyr_demon_message_complete_1" "Кункевич: Вот это дело! Демон-сатир пал, а море снова дышит спокойно. Держи свою награду." + + "kunkka_quest_2_message_complete_1" "Кункевич: ХА-ХА-ХА! ВЕЛИКОЛЕПНО, морячок! Да ты настоящий истребитель нежити!" + "kunkka_quest_2_message_complete_2" "Кункевич: СЛАВА МОРСКИМ БОГАМ! Наконец-то можно вздохнуть полной грудью без этого костяного отребья!" + + "kunkka_quest_clean_harbor_message_1" "Кункевич: Воняет мертвячиной в моей гавани. Вот тебе флаг — втыкай между их логовищами и держи точку." + "kunkka_quest_clean_harbor_message_complete_1" "Кункевич: Вот это работа, моряк! Вода снова чистая, можно и ромом запить. Лови свой куш." + "kunkka_quest_clean_harbor_message_fail_1" "Кункевич: Флаг снесли, гавань опять кишит костями. Подберём другой курс и попробуем ещё раз." + + "quest_kunkka_find_anchor_title" "Клад под крестом" + "quest_kunkka_find_anchor_description" "Кункевич говорит, что закопал сокровище у этой чёртовой ведьмы. Найди клад — и награду можешь забрать себе." + "kunkka_quest_find_anchor_message_1" "Кункевич: Держи лопату! Ищи по карте кресты — встань на них и копай. Золото само вылезет, если не лениться." + "kunkka_quest_find_anchor_message_complete_1" "Кункевич: Ха! Слышу звон монет — значит, копал не зря. Держи награду, морской волк!" + "kunkka_quest_find_anchor_message_fail_1" "Кункевич: Лопата в песке, а клада нет… Ладно, вернёмся к этому позже." + "DOTA_Tooltip_ability_item_shovel" "Волшебная лопата" + "DOTA_Tooltip_ability_item_shovel_Description" "

Активная: Копать

Копает под ногами на кресте. Выкапывает от %gold_min% до %gold_max% золота.

Пассивная: Прочная рукоять

Даёт дополнительное здоровье." + "DOTA_Tooltip_ability_item_shovel_Lore" "Волшебная лопата — находка Кункевича с магнитных кладов." + "DOTA_Tooltip_ability_item_shovel_bonus_health" "+$health" + + "DOTA_Tooltip_ability_item_shameful_pipe" "Позорная труба" + "DOTA_Tooltip_ability_item_shameful_pipe_Description" "Даёт %bonus_attack_range% к дальности атаки дальнобойным героям. Владелец не получает урон и отражение от костяной брони крипов." + "DOTA_Tooltip_ability_item_shameful_pipe_Lore" "Труба, что торчала из пиратского клада. Длинная, как честь капитана, и бесполезная в ближнем бою — зато стрелы летят дальше, а кости крипов на неё не действуют." + "DOTA_Tooltip_ability_item_shameful_pipe_bonus_attack_range" "+$attack_range" + "DOTA_Tooltip_modifier_item_shameful_pipe" "Позорная труба" + "DOTA_Tooltip_modifier_item_shameful_pipe_Description" "+%fMODIFIER_VALUE_ATTACK_RANGE_BONUS% к дальности атаки. Иммунитет к костяной броне крипов." + + "clean_harbor_no_hero_near_flag" "Никого нет у флага — Скорее вернитесь в круг." + + "DOTA_Tooltip_ability_item_clean_harbor" "Флаг очистки гавани" + "DOTA_Tooltip_ability_item_clean_harbor_Description" "Одноразовый пиратский флаг, который ставится между двумя логовищами скелетов. Если флаг простоит 60 секунд под защитой героя, спавн скелетов на кладбище прекращается, а все уже вылезшие мертвецы рассеиваются по ветру." + + "DOTA_Tooltip_ability_item_fishing_rod" "Удочка" + "DOTA_Tooltip_ability_item_fishing_rod_Description" "

Активное: Заброс крюка

Бросает крюк в указанном направлении на расстояние до %hook_distance%. При попадании во врага наносит %Damage% физического урона плюс долю от вашей атаки, даёт обзор радиусом %vision_radius% на %vision_duration% сек., накладывает замедление %slow_movespeed%%% и периодический урон %damage_per_tick% каждые %tick% сек. Пока крюк летит и возвращается, герой стоит на месте. Цель притягивается к вам при откате крюка.

Если поймана рыба (npc_fish), после возврата крюка она исчезает, а награда летит к герою." + "DOTA_Tooltip_ability_item_fishing_rod_Lore" "Снасть, с которой и тролль на берегу — лишь бы клевало." + "DOTA_Tooltip_ability_item_fish" "Рыба" + "DOTA_Tooltip_ability_item_fish_Description" "

Активное: Съесть рыбу

Лечит цель на %heal% здоровья и даёт %hunger_bonus% стаков голода." + "DOTA_Tooltip_ability_item_fish_Lore" "Свежий улов. Особенно полезен после тяжёлой волны." + "dota_tooltip_ability_item_fish_heal" "+$health" + "dota_tooltip_ability_item_fish_hunger_bonus" "+$all" + "DOTA_Tooltip_modifier_bad_cast_debuff" "Плохой каст" + "DOTA_Tooltip_modifier_bad_cast_debuff_Description" "Ваши направленные способности летят в противоположную сторону." + "DOTA_Tooltip_modifier_blind_debuff" "Ослепление" + "DOTA_Tooltip_modifier_blind_debuff_Description" "Экран застилает помехами, мешая ориентироваться." + "DOTA_Tooltip_modifier_stats_multiplier" "Множитель характеристик" + "DOTA_Tooltip_modifier_stats_multiplier_Description" "Суммарный бонус от карт к основным характеристикам. Текущий множитель: %dMODIFIER_PROPERTY_TOOLTIP%%%" + + "quest_denny_quest_kill_sheeps_title" "Денни и его учения" + "quest_denny_quest_kill_sheeps_description" "Денни учит вас и даже платит за то чтобы обучить вас, прося убить овец для дальнейшего развития навыков." + + "denny_quest_kill_sheeps_message_1" "Денни: Эй, герой! Я стал намного сильнее после этих тренировок!" + "denny_quest_kill_sheeps_message_2" "Денни: Знаешь тебе нужно больше практики! Можешь убить 20 овец?" + "denny_quest_kill_sheeps_message_3" "Денни: Я хочу показать всем, что ты не слабак! Вместе мы справимся!" + + "denny_quest_kill_sheeps_message_complete_1" "Денни: Вау! Мы справились! Я чувствую, что мы становимся настоящими воинами!" + "denny_quest_kill_sheeps_message_complete_2" "Денни: Теперь ты готов к более серьёзным испытаниям!" + + "quest_denny_quest_kill_thieves_title" "Разбойники на дороге" + "quest_denny_quest_kill_thieves_description" "Денни уверен, что дорога станет безопасной только после того, как ты уничтожишь всех разбойников." + + "denny_quest_kill_thieves_message_1" "Денни: На дороге орудуют разбойники! Убей главаря и всех его подручных!" + "denny_quest_kill_thieves_message_complete_1" "Денни: Отлично! Разбойники повержены — теперь по дороге можно идти спокойно!" + "denny_quest_kill_thieves_message_complete_2" "Денни: Я ценю твою смелость. Берёшь следующую награду!" + + + "quest_denny_need_a_pet_title" "Денни и его МАЛЕНЬКИЙ друг" + "quest_denny_need_a_pet_description" "Денни попросил вас найти для него особого друга. Он говорит, что хочет кого-то быстрого и ловкого, или может быть сильного и храброго... Странно, что он так настойчив в этом вопросе во время зомби-апокалипсиса." + "denny_pet" "Питомец для Денни" + "npc_pig_event" "Турбосвин" + "npc_wolf_event" "Волколак" + "npc_squirrel_event" "Белка" + + + "dota_tooltip_modifier_pet_buff_npc_pig_event" "Pig Madness" + "dota_tooltip_modifier_pet_buff_npc_pig_event_Description" "Весь входящий урон уменьшен на 10%%" + + "dota_tooltip_modifier_pet_buff_npc_wolf_event" "Wolf power" + "dota_tooltip_modifier_pet_buff_npc_wolf_event_Description" "Весь исходящий урон увеличен на 15%%, также атаки имеют шанс нанести 160%% урона с вероятностью в 15%%" + + "dota_tooltip_modifier_pet_buff_npc_squirrel_event" "Squirrel Wisdom" + "dota_tooltip_modifier_pet_buff_npc_squirrel_event_Description" "Получаемый опыт увеличен на 35%%" + + "dota_tooltip_ability_item_pet" "How to tame anyone?" + "dota_tooltip_ability_item_pet_Description" "

Активное: Use

Если правильно использовать, то можно добиться какого-то результата, он вам пока-что не известен, однако книга может сломаться." + "dota_tooltip_ability_item_pet_Lore" "Древний фолиант, написанный легендарным дрессировщиком зверей Элдриком Звереговым. Говорят, что он мог приручить любое существо - от крошечной белки до свирепого волка. Его секреты были записаны в этой книге, но с годами страницы стали хрупкими, а некоторые рецепты приручения были утеряны. Теперь книга может помочь найти верного друга, но только тому, кто сумеет правильно её использовать." + + "denny_quest_find_pet_message_1" "Денни: Эй, герой! У меня есть одна просьба... Можешь помочь мне найти особого друга?" + "denny_quest_find_pet_message_2" "Денни: Я мечтаю о ком-то, кто будет со мной всегда. Может быть, быстрого и ловкого, как те белки, что прыгают по деревьям... или сильного и храброго, как те волки, что воют на луну..." + "denny_quest_find_pet_message_3" "Денни: А может быть, даже кого-то особенного, кто умеет рыть землю и находить сокровища! Знаешь, я слышал, что такие существа очень умные и преданные!" + + "denny_quest_find_pet_message_complete_1" "Денни: Ого! Ты нашёл... это же... это же настоящий питомец! Я так давно мечтал о таком!" + "denny_quest_find_pet_message_complete_2" "Денни: Знаешь, я всегда хотел себе друга... такого, который будет со мной в любую погоду. Может быть, быстрого и ловкого, как ветер, или сильного и храброго, как настоящий воин..." + "denny_quest_find_pet_message_complete_3" "Денни: Спасибо тебе огромное! Теперь у меня есть настоящий компаньон! Я буду тренироваться с ним каждый день и стану самым сильным стражником!" + + + "quest_denny_training_title" "Путь стражника часть 1" + "quest_denny_training_description" "Денни очень хочет стать стражником и попросил вас помочь ему с этим, для этого он пойдёт сражаться с овцами и попросил вас помочь ему с этим." + + "Denny_quest_1_message_1" "Денни: Эм... я это... хочу быть как настоящие стражники! Но меня все в школе обижают, потому что я слабый..." + "Denny_quest_1_message_2" "Денни: Пожалуйста, научи меня драться! Я даже с овцами справиться не могу... Я... я заплачу чем смогу!" + "Denny_quest_1_message_3" "Денни: Правда поможешь? Ура! Только... не бей сильно, ладно?" + + "Denny_quest_1_rofl_message_1" "Денни: Хия! Вот тебе!" + "Denny_quest_1_rofl_message_2" "Денни: Получай! Я сильный!" + "Denny_quest_1_rofl_message_3" "Денни: Ха! Я как настоящий воин!" + "Denny_quest_1_rofl_message_4" "Денни: Бам! Бам! Бам!" + "Denny_quest_1_rofl_message_5" "Денни: Я непобедим! Ура!" + + + "Denny_quest_1_message_complete_1" "Денни: Спасибо тебе, герой! Я стал намного сильнее благодаря тебе. Вот, возьми свой меч обратно - он очень помог мне в тренировках." + "Denny_quest_1_message_complete_2" "Денни: Но знаешь... У меня есть ещё одна просьба. Приходи, когда сможешь помочь мне стать ещё сильнее! Удачи тебе, герой!" + "Denny_quest_1_message_complete_3" "Денни: Я обязательно стану настоящим стражником, как и мечтал! И это всё благодаря тебе!" + + "Denny_quest_1_message_fail_1" "Денни: *всхлипывает* Я... я не справился... Я такой слабый... если бы у меня был меч, как у кунки." + "Denny_quest_1_message_fail_2" "Денни: Наверное, мне правда не стоит мечтать о том, чтобы стать стражником..." + "Denny_quest_1_message_fail_3" "Денни: Спасибо, что пытался помочь мне... Но, видимо, это не моё..." + + + "quest_collect_items_title" "Собрать предметы" + "quest_collect_items_description" "Соберите три железные ветки для усиления" + + //Дефолт функционал + + "text_no_alphatest_detected" "Обнаружен пидор не с альфа теста, потом поиграешь иди уроки делай чмо." + + "music_on" "Включить музыку" + "music_off" "Выключить музыку" + "dota_game_end_victory_title_radiant" "Деревня спасена!" + "dota_game_end_victory_title_dire" "Ой в пизду, я в тильте..(((" + + "black_shop_refreshed" "Магазин обновлен!" + "black_shop_title" "Черный рынок" + "FREE" "бесплатно" + "rarity_common" "Обычный" + "rarity_rare" "Редкий" + "rarity_epic" "Эпический" + "rarity_legendary" "Легендарный" + "rarity_heavenly" "Божественный" + "rarity_cursed" "Проклятый" + "time_now" "Время текущей игры: " + "seconds" "секунд" + "duration" "Длительность" + "day_time" "До ночи" + "night_time" "До дня" + "night_begin" "Наступила ночь " + "be_ready" " Приготовьтесь к обороне!" + "night_begin_1" "Команда, поторапливайтесь! Ночь уже совсем близко!" + "NIGHT_BEGIN_2" "АЛЛО, УЕБАНЫ ГДЕ ВЫ!???!?!?!? УЖЕ НОЧЬ НАСТУПИЛА!" + + "boss_spawned" "К ВАМ ПРИБЛИЖАЕТСЯ САМ БОСС!" + "dota_tooltip_ability_ghost_evasive" "Призрачная уклончивость" + "dota_tooltip_ability_ghost_evasive_Description" "Пассивно позволяет летать и проходить сквозь юнитов." + "dota_tooltip_ability_ghost_evasive_evasive" "$evasion" + "dota_tooltip_ability_wave_phasing_march" "Фазовый марш" + "dota_tooltip_ability_wave_phasing_march_Description" "Пассивно: проход сквозь других существ и %bonus_movement_speed% к скорости передвижения." + "dota_tooltip_ability_wave_phasing_march_bonus_movement_speed" "+$move_speed" + + "1minfornight" "До наступления ночи осталась 1 минута!" + "30secfornight" "До наступления ночи осталось 30 секунд!" + "dota_tooltip_ability_item_test" "Test item" + "nearby_item_distance" "единиц" + + "no_quests_available" "В данный момент нет доступных кветов. Проверьте позже." + "quests" "Доска заданий" + "quest_accept_button" "Принять" + "quest_rewards_title" "Награды:" + "quest_reward_gold" "золота" + "quest_reward_experience" "опыта" + "quest_reward_crystals" "Кристаллов" + "quest_reward_team_split" "на команду" + "quest_state_available" "Доступно" + "quest_state_in_progress" "В процессе" + "quest_state_completed" "Завершено" + "quest_state_locked" "Заблокировано" + "quest_state_failed" "Провалено" + + + + + + // Черный рынок + "black_shop_refresh_description" "Хочешь обновить содержимое магазина?" + "black_shop_refresh_button" "ОБНОВИТЬ" + "black_shop_buy_card_description" "Также у меня продаются карты, но цена на них будет расти." + "black_shop_buy_card_button" "КУПИТЬ КАРТУ" + "black_shop_not_enough_crystals" "Недостаточно Кристаллов" + "black_shop_not_enough_gold" "Недостаточно Деняг" + "black_shop_teleport_section_description" "Продавец кивает на карту" + "black_shop_teleport_menu_open" "Телепортируй меня в…" + "black_shop_teleport_title" "Куда тебе нужно?" + "black_shop_teleport_cost_label" "Стоимость телепорта:" + "black_shop_teleport_to_grove" "Телепортируй меня в рощу" + "black_shop_teleport_to_two_sisters" "Телепортируй меня к двум сёстрам" + "black_shop_teleport_to_village_waterfall" "Телепортируй меня в деревню за водопадом" + "black_shop_teleport_to_ancient_ridge" "Телепортируй меня на древний хребет" + "black_shop_back_button" "Назад" + "black_shop_teleport_dead" "Пока ты мёртв, телепорт не работает." + "black_shop_card_purchased" "Карта куплена" + "black_shop_exchange_section_description" "Обмен валют: курс 100 золота = 1 кристалл." + "black_shop_exchange_open_button" "Открыть обмен валют" + "black_shop_exchange_title" "Обмен валют" + "black_shop_exchange_hint" "Выбери направление, выстави количество и подтверждай обмен." + "black_shop_exchange_summary_default" "Обмен 1 кристалла за 100 золота" + "black_shop_exchange_max_default" "Макс: 1" + "black_shop_exchange_apply_button" "Обменять" + "black_shop_exchange_not_enough" "Недостаточно ресурсов" + "black_shop_exchange_max_prefix" "Макс:" + "black_shop_exchange_summary_spend" "Потратишь" + "black_shop_exchange_summary_get" "получишь" + "black_shop_exchange_result_spent" "Обмен: потрачено" + "black_shop_exchange_result_received" "получено" + "black_shop_gold_unit" "золота" + "black_shop_crystal_unit_short" "крист." + + // default_gameplay_modifiers + + "dota_tooltip_modifier_glyph_custom_passive" "Blessing ready" + "dota_tooltip_modifier_glyph_custom_passive_Description" "В любую секунду защитит от смерти" + "dota_tooltip_modifier_glyph_custom" "Blessing" + "dota_tooltip_modifier_glyph_custom_Description" "Герой одарён защитой с небес." + "dota_tooltip_modifier_glyph_custom_passive_cd" "Blessing recharging" + "dota_tooltip_modifier_glyph_custom_passive_cd_Description" "Blessing ушло на перезарядку." + + "dota_tooltip_modifier_general_fired" "BURN" + "dota_tooltip_modifier_general_fired_Description" "Существо горит." + + "dota_tooltip_modifier_general_froze" "COOLING" + "dota_tooltip_modifier_general_froze_Description" "Существу холодно." + + "dota_tooltip_modifier_general_hunger" "Saturation" + "dota_tooltip_modifier_general_hunger_Description" "Герой получил бонус ко всем базовым атрибутам." + + "dota_tooltip_modifier_item_pizza" "Пицца" + "dota_tooltip_modifier_item_pizza_Description" "Увеличивает Силу, Ловкость и Интеллект на количество стаков." + + + "dota_tooltip_modifier_swamp_slow" "swamp fatigue" + "dota_tooltip_modifier_swamp_slow_Description" "Герой погряз в грязи из-за чего замедлен на %MODIFIER_PROPERTY_MOVESPEED_BONUS_CONSTANT% единиц и получает урон." + + // zapret + + "dota_hud_error_cheese_bad_target" "Не верная цель." + "wait_some_time_before_tree_growth" "Я думаю мне стоит вернуться сюда чуточку позже." + "dota_hud_error_full_inventory" "Цель имеет полный инвентарь." + "dota_hud_error_ability_not_ready" "Герой уже наелся, подождите пока он проголодается." + "dota_hud_error_havent_charges" "Предмет не имеет зарядов." + "dota_hud_error_havent_entities" "Не имеет призванных существ." + "dota_hud_error_rubick_spellsteal_self" "Нельзя украсть способность у самого себя." + "dota_hud_error_rubick_spellsteal_no_ability" "У цели нет подходящей способности для кражи." + "dota_hud_error_rubick_spellsteal_unstealable" "Эту способность нельзя украсть." + "dota_hud_error_not_in_zone" "Герой находится не в центре кладбища." + "dota_hud_error_shovel_not_on_cross" "Подойди ближе к отмеченному кладу." + "dota_hud_error_shovel_broken" "Лопата сломалась — все клады вскопаны." + "dota_hud_error_font_of_mercy_cd" "Купель милости ещё наполняется: подождите." + "dota_hud_error_font_of_mercy_no_crisis" "Нет союзного героя в радиусе с здоровьем ниже порога." + + //События + "event_gold_rush" "Золотая лихорадка" + "event_midas_hands" "Мидасовое безумие" + "event_gold_rush_description" "Игровая фауна:

• Рядом с героем в пределха 800 единиц падают мешки с золотом

• Подобрав их вы получите от 100 до 1000 золота
" + "event_midas_hands_description" "Благославление Мидаса:

• С шансом в 15% при убийстве врага выпадает мешочек с золотом" + + //npc_dota_creature + "npc_pig" "Свинья" + "npc_sheep" "Овца" + "npc_wolf" "Волк" + "npc_boss_lycan" "Черный Клык" + "npc_demon_dragon_satyr" "Демон-сатир дракон" + + "npc_wave_boss_suicide_zombie" "Подрывник" + "npc_wave_boss_zombie_king" "Король зомбят" + "npc_boss_nevermore" "ДЕМОНЮГА ЕБАНАЯ" + "npc_ent" "Мелкий древний энт" + "npc_skeleton_zombie_undead" "Скелет" + "npc_dead_skeleton_archer_undead" "Скелет лучник" + "npc_skeleton_zombie_half_undead" "Скелет падальщик" + "npc_dead_skeleton_undead" "Скелет мечник" + "npc_witch" "Ведьма" + "npc_chicken" "Курица" + "npc_thief_leader" "Главарь воров" + "npc_thief_archer" "Вор-лучник" + "npc_thief_backer" "Вор" + "npc_black_dragon" "Черный дракон" + "npc_red_dragon" "Красный дракон" + "npc_blue_dragon" "Синий дракон" + "npc_blue_dragon_small" "Малый синий дракон" + "npc_red_dragon_small" "Малый красный дракон" + "npc_lycosidae_stalker" "Маленький павучонок" + "npc_venomancer_brute" "Большая змеюка" + "npc_ravenous_woodfang" "Проклятые кусатели" + + "npc_campfire" "Костёр" + "npc_wisps" "Сгусток энергии" + + "npc_frop_tadpole" "Мини-лягушка" + "npc_small_frog_froglet" "Лягушонок" + "npc_mini_frog" "Головастик" + "npc_frogman_magi" "Зачарованная лягушка" + + "npc_sakura_tree" "Древо сакуры" + "npc_mound" "Кучка" + + + "npc_dummy_test" "sosal for what?" + + "npc_attack_box" "коробочка" + + // rename heroes + "npc_dota_hero_sargatanas" "Sargatanas" + "npc_dota_hero_elder_dragon_smaug" "Elder Dragon Smaug" + "npc_dota_hero_yuki_onna" "Yuki-onna" + "npc_dota_hero_bloodhunter" "Bloodhunter" + "npc_dota_hero_nagash" "Nagash" + + + "npc_dota_melee_nagash_summon" "Marduk" + "npc_dota_ranged_nagash_summon" "Appolon" + "npc_dota_mage_nagash_summon" "Odin" + "npc_dota_shield_nagash_summon" "Anubis" + "npc_dota_nagash_soul_eater" "Souleater" + + //npc + "npc_market_blackshop" "Нелегальный лавочник" + "npc_homer" "Глава деревни" + "homer_defend_toast_kicker" "Оборона базы" + "homer_defend_toast_title" "{hom_name} под атакой" + "homer_defend_toast_hint" "Вернитесь и отбейте врагов от точки спавна." + "homer_defend_toast_fallback" "{hom_name} под атакой! Защитите базу." + "homer_defend_toast_name_fallback" "Глава деревни" + "npc_quest_giver_kunkka" "Кункка" + "npc_quest_giver_denny" "Денни" + + // Катсцена camera_start_ending (реплики Денни) + "cutscene_camera_start_ending_1" "Герои скорее, мы настроили ваши телепорты в логово этой нечисти" + "cutscene_camera_start_ending_2" "Соберитесь этот путь окажется последним, вам нужно будет убить его." + "cutscene_camera_start_ending_3" "Дело осталось за вами мы верим в вас герои." + "cutscene_camera_start_ending_4" "И это тот самый «хозяин» тьмы?…" + "cutscene_camera_start_ending_5" "С богом вас, герои." + "cutscene_camera_start_ending_6" "" + + "npc_quest_giver_oldmen" "Старик" + "npc_quest_giver_firestar" "Огнезвёзд" + "npc_quest_giver_doctor" "Травник Азаз" + "npc_quest_giver_friend" "Гурд" + "npc_quest_giver_largo" "Писатель Ларго" + "npc_quest_giver_maiden" "Дружелюбная Цмочка" + "npc_quest_giver_lina" "Токсичная Лина" + + + + + "npc_kot_roflik_0" "МёрсиК" + "npc_kot_roflik_1" "боробобрик" + "npc_kot_roflik_2" "КулСторибобик" + "npc_dota_rofl_kaban_pumba" "румба" + "npc_dota_golden_fish" "золотая лыбка" + + + //summon + + "npc_dota_fire_summon" "Огненный элементаль" + + + + //npc_wave_creeps + "npc_wave_zombie" "Зонбу-Ходун" + "npc_wave_half_zombie" "Зонбу-падальщик" + "npc_wave_toxin_zombie" "Зонбу-токсик" + "npc_wave_bearst_zombie" "Зонбу-Зверь" + "npc_wave_ghost_ranged" "Призрачный стрелок" + "npc_wave_ghoul" "Упырь" + "npc_wave_skeleton_warrior" "Скелет-воин" + "npc_wave_skeleton_assassin" "Скелет-ассасин" + "npc_wave_boss_death_prophet" "Босс-пророк смерти" + "npc_wave_boss_lifestealer" "Босс-пожиратель жизни" + "npc_wave_boss_skeleton" "Босс-скелет" + "npc_wave_boss_zombie" "Босс-зомби" + + "npc_wave_dead_enchantress" "Атрофия" + + + //creep_abilities + "dota_tooltip_ability_sheep_coil" "Волшебные рога" + "dota_tooltip_ability_sheep_coil_description" "Выпускает заряд молнии, который наносит урон и замедляет врагов в радиусе %radius% единиц от своего недоброжелателя." + "dota_tooltip_ability_sheep_coil_damage" "УРОН:" + "dota_tooltip_ability_sheep_coil_slow_duration" "ДЛИТЕЛЬНОСТЬ:" + "dota_tooltip_ability_sheep_coil_movement_slow" "%ЗАМЕДЛЕНИЕ:" + "dota_tooltip_ability_sheep_coil_lore" "Рогатое уёбище, своими волшебными рогами даёт пизды своему недоброжелателю." + "dota_tooltip_modifier_sheep_coil_slow" "Sluggish magic" + "dota_tooltip_modifier_sheep_coil_slow_Description" "Вы обляпаны магической жидкостью, из-за чего замедлены на %dMODIFIER_PROPERTY_MOVESPEED_BONUS_PERCENTAGE%%%" + + "dota_tooltip_ability_frogmen_acid_jump" "Кислотный прыжок" + "dota_tooltip_ability_frogmen_acid_jump_description" "Лягушка подпрыгивает к выбранной точке и обрушивается на землю, нанося физический урон врагам в радиусе и оглушая их." + "dota_tooltip_ability_frogmen_acid_jump_radius" "РАДИУС:" + "dota_tooltip_ability_frogmen_acid_jump_stun_duration" "ДЛИТЕЛЬНОСТЬ ОГЛУШЕНИЯ:" + "dota_tooltip_ability_frogmen_acid_jump_land_damage" "УРОН ПРИ ПРИЗЕМЛЕНИИ:" + "dota_tooltip_ability_frogmen_acid_jump_lore" "Жабы, воспитанные в мутировавших болотах, научились обрушивать на врагов волны едкой слизи с оглушительным ударом." + + "dota_tooltip_ability_pig_charge" "Свиной врыв" + "dota_tooltip_ability_pig_charge_description" "Свин делает рывок в сторону своей цели, нанося урон, если заденит и отталкивает оглушая на небольшой промежуток времени." + "dota_tooltip_ability_pig_charge_knockback_damage" "УРОН:" + "dota_tooltip_ability_pig_charge_stun_duration" "ОГЛУШЕНИЕ:" + "dota_tooltip_ability_pig_charge_lore" "Свинья, которая не может понять, что ей делать, решила попробовать всё, что может." + + "dota_tooltip_ability_thief_arrow" "Стрела" + "dota_tooltip_ability_thief_arrow_description" "Вор выпускает стрелу, летящую по прямой и наносящую физический урон первому врагу на своём пути." + "dota_tooltip_ability_thief_arrow_arrow_speed" "СКОРОСТЬ СТРЕЛЫ:" + "dota_tooltip_ability_thief_arrow_arrow_width" "ШИРИНА ПОЛЁТА:" + "dota_tooltip_ability_thief_arrow_arrow_range" "ДАЛЬНОСТЬ ПОЛЁТА:" + "dota_tooltip_ability_thief_arrow_lore" "Те, кто зазевался в тени, редко успевают заметить откуда прилетел удар." + + "dota_tooltip_ability_thief_charge" "Рывок тени" + "dota_tooltip_ability_thief_charge_description" "Вор выбирает цель и мчится к ней с огромной скоростью, оглушая и отбрасывая врагов в небольшой области по прибытии." + "dota_tooltip_ability_thief_charge_movement_speed" "СКОРОСТЬ ПЕРЕМЕЩЕНИЯ:" + "dota_tooltip_ability_thief_charge_stun_duration" "ДЛИТЕЛЬНОСТЬ ОГЛУШЕНИЯ:" + "dota_tooltip_ability_thief_charge_bash_radius" "РАДИУС ВОЗДЕЙСТВИЯ:" + "dota_tooltip_ability_thief_charge_lore" "Для настоящего вора расстояние между добычей и клинком — лишь вопрос решимости." + + "dota_tooltip_ability_agro_leader" "Крик" + "dota_tooltip_ability_agro_leader_description" "Издаёт яростный клич, заставляя врагов в радиусе %radius% атаковать его в течение %duration% секунд." + "dota_tooltip_ability_agro_leader_radius" "РАДИУС:" + "dota_tooltip_ability_agro_leader_duration" "ДЛИТЕЛЬНОСТЬ:" + "dota_tooltip_ability_agro_leader_lore" "Тот, кто осмелился бросить вызов Лидеру воров, неизбежно окажется в центре внимания." + + //wave_creep_abilities + "dota_tooltip_ability_zombie_virus" "Зомби-вирус" + "dota_tooltip_ability_zombie_virus_description" "С шансом %chance%%% атака накладывает на врага отдельный слой яда (не более 9 слоёв на цели). Каждый слой наносит %damage% урона в секунду, снижает скорость на %slow_movespeed%%% и броню на %armor%. Длительность одного слоя — %duration% сек. Слой пропадает, если погибает зомби, наложивший его." + "dota_tooltip_ability_zombie_virus_damage" "УРОН В СЕКУНДУ:" + "dota_tooltip_ability_zombie_virus_slow_movespeed" "%ЗАМЕДЛЕНИЕ:" + "dota_tooltip_ability_zombie_virus_armor" "УМЕНЬШЕНИЕ БРОНИ:" + "dota_tooltip_ability_zombie_virus_duration" "ДЛИТЕЛЬНОСТЬ СЛОЯ:" + "dota_tooltip_ability_zombie_virus_chance" "%ШАНС:" + "dota_tooltip_ability_zombie_virus_lore" "Древний вирус, мутировавший в телах мертвецов, превращает их в ходячих разносчиков заразы." + + "dota_tooltip_modifier_zombie_virus_debuff" "Зомби-вирус" + "dota_tooltip_modifier_zombie_virus_debuff_description" "Слой яда: периодический урон, замедление и снижение брони. Несколько слоёв суммируются. %dMODIFIER_PROPERTY_MOVESPEED_BONUS_PERCENTAGE%%% к скорости передвижения." + + "dota_tooltip_ability_bone_armor" "Костяная броня" + "dota_tooltip_ability_bone_armor_description" "Даёт бонусную броню. Против героев дальнего боя: если в радиусе %hero_proximity_radius% нет героя, атакующий получает урон в %isolated_damage_multiplier% раз больше нанесённого. Иначе с шансом %damage_reflect_chance%%% отражает %damage_reflect_pct%%% урона обратно." + "dota_tooltip_ability_bone_armor_armor_bonus" "БРОНЯ:" + "dota_tooltip_ability_bone_armor_hero_proximity_radius" "РАДИУС ПРОВЕРКИ ГЕРОЯ:" + "dota_tooltip_ability_bone_armor_isolated_damage_multiplier" "МНОЖИТЕЛЬ УРОНА БЕЗ ГЕРОЯ РЯДОМ:" + "dota_tooltip_ability_bone_armor_lore" "Кости, закалённые в огне преисподней, становятся прочнее стали и способны отражать удары обратно в нападающего." + + "dota_tooltip_ability_toxin" "Токсин" + "dota_tooltip_ability_toxin_description" "После смерти оставляет лужу токсина, которая отключает пассивные способности врага который находится в радиусе %radius% единиц. Пересекающиеся лужи сливаются в одну: их урон суммируется, радиус увеличивается на %merge_radius_bonus% за каждое слияние, длительность продлевается на %merge_duration_bonus% с за каждое слияние." + "dota_tooltip_ability_toxin_damage" "УРОН:" + "dota_tooltip_ability_toxin_radius" "РАДИУС:" + "dota_tooltip_ability_toxin_duration" "ДЛИТЕЛЬНОСТЬ:" + "dota_tooltip_ability_toxin_merge_radius_bonus" "РАДИУС ЗА СЛИЯНИЕ:" + "dota_tooltip_ability_toxin_merge_duration_bonus" "ПРОДЛЕНИЕ ЗА СЛИЯНИЕ (СЕК.):" + "dota_tooltip_ability_toxin_lore" "Смертоносный токсин, выделяемый разлагающимися телами зомби, отравляет всё живое в округе и нарушает работу магических способностей." + + "dota_tooltip_ability_skeleton_archer_fire_arrow" "Огненные стрелы" + "dota_tooltip_ability_skeleton_archer_fire_arrow_description" "Скелет-лучник выпускает огненные стрелы, которые наносят урон и оглушают врагов на своём пути." + "dota_tooltip_ability_skeleton_archer_fire_arrow_bonus_damage" "УРОН:" + "dota_tooltip_ability_skeleton_archer_fire_arrow_lore" "Стрелы, закалённые в огне преисподней, становятся прочнее стали и способны отражать удары обратно в нападающего." + + "dota_tooltip_ability_weaking_impetus" "Ослабляющий резонанс" + "dota_tooltip_ability_weaking_impetus_description" "При атаке накладывает на цель эффект, снижающий исходящий урон на %damage_reduction%%% за каждый стак. Эффект складывается до %max_stacks% раз." + "dota_tooltip_ability_weaking_impetus_damage_reduction" "%СНИЖЕНИЕ УРОНА ЗА СТАК:" + "dota_tooltip_ability_weaking_impetus_mana_hit" "МАНА НА ХИТ:" + "dota_tooltip_ability_weaking_impetus_debuff_duration" "ДЛИТЕЛЬНОСТЬ:" + "dota_tooltip_ability_weaking_impetus_lore" "Зачарованные стрелы мёртвой лесной нимфы высасывают силу из своих жертв, делая их атаки всё слабее с каждым попаданием." + + "dota_tooltip_ability_zombie_armor_decress" "Разъедающая броню атака" + "dota_tooltip_ability_zombie_armor_decress_description" "Каждая атака снижает броню цели на %armor_debuff% на %corruption_duration% сек." + "dota_tooltip_ability_zombie_armor_decress_armor_debuff" "СНИЖЕНИЕ БРОНИ:" + "dota_tooltip_ability_zombie_armor_decress_corruption_duration" "ДЛИТЕЛЬНОСТЬ:" + + "dota_tooltip_ability_zombie_rage" "Ярость зомби" + "dota_tooltip_ability_zombie_rage_description" "Входит в ярость на %duration% сек., получая сопротивление магии и эффекты контроля." + + "dota_tooltip_ability_zombie_feast" "Пир" + "dota_tooltip_ability_zombie_feast_description" "Пассивно крадет здоровье цели при атаке. Против крипов эффективность снижена." + + "dota_tooltip_ability_zombie_open_wounds" "Кровавая рана" + "dota_tooltip_ability_zombie_open_wounds_description" "Замедляет цель на %duration% сек. и позволяет лечиться от нанесенного урона." + "dota_tooltip_ability_wave_desperate_vampirism" "Отчаянная жажда" + "dota_tooltip_ability_wave_desperate_vampirism_description" "Пока здоровье ниже %hp_threshold_pct%%% от максимума, каждая успешная атака восстанавливает %vamp_pct%%% от нанесённого ею урона." + "dota_tooltip_ability_wave_desperate_vampirism_hp_threshold_pct" "ПОРОГ (%% от max HP):" + "dota_tooltip_ability_wave_desperate_vampirism_vamp_pct" "ВОСПОЛНЕНИЕ (%% урона попадания):" + + "dota_tooltip_ability_skeleton_blast" "Адский взрыв" + "dota_tooltip_ability_skeleton_blast_description" "Оглушает цель и наносит урон: %damage% сразу и %blast_dot_damage% периодически." + + "dota_tooltip_ability_skeleton_king_mortal_strike" "Смертельный удар" + "dota_tooltip_ability_skeleton_king_mortal_strike_description" "Периодически наносит критический удар с множителем %crit_mult%%." + + "dota_tooltip_modifier_weaking_impetus_debuff" "Weakening impetus" + "dota_tooltip_modifier_weaking_impetus_debuff_description" "Ваш исходящий урон снижен на %dMODIFIER_PROPERTY_DAMAGEOUTGOING_PERCENTAGE%%% и магическое усиление снижено на %dMODIFIER_PROPERTY_SPELL_AMPLIFY_PERCENTAGE%%%." + + //sven + "dota_tooltip_ability_ability_sven_storm_hammer_custom" "Штормовой молот" + "dota_tooltip_ability_ability_sven_storm_hammer_custom_Lore" "Железная рукавица мятежного рыцаря, позаимствованная в отцовской школе, выбивает дух из любого врага." + "dota_tooltip_ability_ability_sven_storm_hammer_custom_radius" "РАДИУС:" + "dota_tooltip_ability_ability_sven_storm_hammer_custom_stun_duration" "ДЛИТЕЛЬНОСТЬ ОГЛУШЕНИЯ:" + "dota_tooltip_ability_ability_sven_storm_hammer_custom_stun_duration_for_boss_mult" "МНОЖИТЕЛЬ ДЛИТЕЛЬНОСТИ СТАНА ПО БОССАМ:" + "dota_tooltip_ability_ability_sven_storm_hammer_custom_damage" "УРОН:" + "dota_tooltip_ability_ability_sven_storm_hammer_custom_Description" "Герой бросает молот по вражескому юниту или в точку: при взрыве наносит урон и оглушает врагов в радиусе %radius%. При включённом автокасте по основной цели снимает с неё положительные эффекты, выполняет по ней дополнительную атаку и подтягивает Свена к снаряду за время полёта." + + "dota_tooltip_ability_ability_sven_great_cleave_custom" "Великий раскол" + "dota_tooltip_ability_ability_sven_great_cleave_custom_Description" "Пассивно: атаки наносят раскол по конусу перед Свеном (%cleave_damage_pct%%% урона от атаки; ширина от %cleave_starting_width% до %cleave_ending_width%, дальность %cleave_radius%). Активно: тратит %health_cost_pct%%% текущего здоровья и готовит следующий удар — он гарантированно критует и наносит раскол с силой %cleave_damage_multiple_facet%%% от обычного." + "dota_tooltip_ability_ability_sven_great_cleave_custom_Lore" "Мощные удары Свена способны поразить сразу нескольких противников." + "dota_tooltip_ability_ability_sven_great_cleave_custom_cleave_damage_pct" "%УРОН РАСКОЛА:" + "dota_tooltip_ability_ability_sven_great_cleave_custom_cleave_radius" "ДИСТАНЦИЯ:" + "dota_tooltip_ability_ability_sven_great_cleave_custom_health_cost_pct" "%ТЕКУЩЕГО ЗДОРОВЬЯ ЗА АКТИВ:" + "dota_tooltip_ability_ability_sven_great_cleave_custom_cleave_damage_multiple_facet" "%СИЛА РАСКОЛА В УСИЛЕННОМ УДАРЕ:" + + "dota_tooltip_ability_ability_sven_gods_strength_custom" "Божья мощь" + "dota_tooltip_ability_ability_sven_gods_strength_custom_Description" "Свен призывает силу древних богов, значительно увеличивая свой урон. С талантом дополнительно даёт %gods_strength_damage_bonus_per_health%%% урона за каждую единицу недостающего здоровья." + "dota_tooltip_ability_ability_sven_gods_strength_custom_Lore" "Древние боги даруют Свену невероятную силу в бою." + "dota_tooltip_ability_ability_sven_gods_strength_custom_gods_strength_damage_bonus" "%БОНУС К УРОНУ:" + "dota_tooltip_ability_ability_sven_gods_strength_custom_duration" "ДЛИТЕЛЬНОСТЬ:" + + "dota_tooltip_ability_ability_sven_gods_strength_custom_Scepter_Description" "Свен получает дополнительно %gods_strength_bonus_strength_pct% единиц силы за каждую единицу брони на время действия способности." + "dota_tooltip_ability_ability_sven_gods_strength_custom_Shard_Description" "Пока действует Божья мощь, Свен получает %gods_strength_shard_magic_resist%%% сопротивления магии." + + + "dota_tooltip_modifier_sven_gods_strength_custom" "God's Strength" + "dota_tooltip_modifier_sven_gods_strength_custom_Description" "Урон увеличен на %dMODIFIER_PROPERTY_DAMAGEOUTGOING_PERCENTAGE%%%" + + "dota_tooltip_ability_ability_sven_warcry_custom" "Боевой клич" + "dota_tooltip_ability_ability_sven_warcry_custom_Description" "Свен издаёт боевой клич, который даёт ему и всем союзным героям поблизости бонус к броне, скорости передвижения и атаки. За каждую единицу брони могучий рыцарь получает дополнительный урон в размере %damage_bonus_per_armor%." + "dota_tooltip_ability_ability_sven_warcry_custom_Lore" "Боевой клич Свена вселяет храбрость в сердца союзников и страх во врагов." + "dota_tooltip_ability_ability_sven_warcry_custom_armor_bonus" "ДОП. БРОНЯ:" + "dota_tooltip_ability_ability_sven_warcry_custom_movespeed_bonus" "%ДОП. СКОРОСТЬ ПЕРЕДВИЖЕНИЯ:" + "dota_tooltip_ability_ability_sven_warcry_custom_attackspeed_bonus" "%ДОП. СКОРОСТЬ АТАКИ:" + "dota_tooltip_ability_ability_sven_warcry_custom_duration" "ДЛИТЕЛЬНОСТЬ:" + "dota_tooltip_ability_ability_sven_warcry_custom_radius" "РАДИУС:" + + "dota_tooltip_modifier_sven_warcry_custom_active" "Warcry" + "dota_tooltip_modifier_sven_warcry_custom_active_Description" "Warcry увеличивает вашу броню, скорость передвижения и скорость атаки на %dMODIFIER_PROPERTY_PHYSICAL_ARMOR_BONUS%%%dMODIFIER_PROPERTY_MOVESPEED_BONUS_PERCENTAGE%%% и %dMODIFIER_PROPERTY_ATTACKSPEED_PERCENTAGE%%% соответственно." + + "dota_tooltip_modifier_sven_warcry_custom" "Warcry" + "dota_tooltip_modifier_sven_warcry_custom_Description" "Броня увеличена на %dMODIFIER_PROPERTY_PHYSICAL_ARMOR_BONUS%, скорость передвижения увеличена на %dMODIFIER_PROPERTY_MOVESPEED_BONUS_PERCENTAGE%%%" + + + // -----------------Vengeful Spirit-------------------- + "dota_tooltip_ability_vengefulspirit_magic_missile_custom" "Magic Missile" + "dota_tooltip_ability_vengefulspirit_magic_missile_custom_Description" "Выпускает во врага магический снаряд: оглушает, наносит урон и сжигает ману. После попадания снаряд отскакивает к ближайшему врагу в радиусе применения." + "dota_tooltip_ability_vengefulspirit_magic_missile_custom_Lore" "Простейший скайрасский приём — магический снаряд — основной инструмент мести Шендел." + "dota_tooltip_ability_vengefulspirit_magic_missile_custom_stun_duration" "ДЛИТЕЛЬНОСТЬ ОГЛУШЕНИЯ:" + "dota_tooltip_ability_vengefulspirit_magic_missile_custom_damage" "УРОН:" + "dota_tooltip_ability_vengefulspirit_magic_missile_custom_mana_burn" "СЖИГАНИЕ МАНЫ:" + + "dota_tooltip_ability_vengefulspirit_wave_of_terror_custom" "Wave of Terror" + "dota_tooltip_ability_vengefulspirit_wave_of_terror_custom_Description" "Герой издаёт жуткий клич: волна раскрывает туман войны, наносит урон и снижает броню (плоско и на %armor_reduction_pct%%% от базовой) и урон от атак врагов на пути." + "dota_tooltip_ability_vengefulspirit_wave_of_terror_custom_Lore" "Душераздирающий голос Шендел предупреждает о её приближении." + "dota_tooltip_ability_vengefulspirit_wave_of_terror_custom_armor_reduction" "СНИЖЕНИЕ БРОНИ:" + "dota_tooltip_ability_vengefulspirit_wave_of_terror_custom_armor_reduction_pct" "% СНИЖЕНИЯ БАЗОВОЙ БРОНИ:" + "dota_tooltip_ability_vengefulspirit_wave_of_terror_custom_attack_reduction" "%СНИЖЕНИЕ УРОНА ОТ АТАК:" + "dota_tooltip_ability_vengefulspirit_wave_of_terror_custom_damage" "УРОН:" + "dota_tooltip_ability_vengefulspirit_wave_of_terror_custom_wave_width" "ШИРИНА ВОЛНЫ:" + "dota_tooltip_ability_vengefulspirit_wave_of_terror_custom_vision_duration" "ДЛИТЕЛЬНОСТЬ ОБЗОРА:" + + "dota_tooltip_modifier_vengefulspirit_wave_of_terror_custom" "Wave of Terror" + "dota_tooltip_modifier_vengefulspirit_wave_of_terror_custom_Description" "Броня снижена (в том числе на долю от базовой). Урон от атак уменьшен." + + "dota_tooltip_modifier_vengefulspirit_wave_of_terror_custom_buff" "Wave of Terror" + "dota_tooltip_modifier_vengefulspirit_wave_of_terror_custom_buff_Description" "Украдены броня и урон от атак врагов." + + "dota_tooltip_ability_ability_vengefulspirit_command_aura_custom" "Command Aura" + "dota_tooltip_ability_ability_vengefulspirit_command_aura_custom_Description" "Пассивная аура: союзники в радиусе получают дополнительный базовый урон, физический и магический вампиризм." + "dota_tooltip_ability_ability_vengefulspirit_command_aura_custom_Lore" "Шендел делится яростью с теми, кто сражается рядом с ней." + "dota_tooltip_ability_ability_vengefulspirit_command_aura_custom_bonus_base_damage" "ДОП. УРОН:" + "dota_tooltip_ability_ability_vengefulspirit_command_aura_custom_physical_vampirism" "%ФИЗ. ВАМПИРИЗМ:" + "dota_tooltip_ability_ability_vengefulspirit_command_aura_custom_magical_vampirism" "%МАГ. ВАМПИРИЗМ:" + "dota_tooltip_ability_ability_vengefulspirit_command_aura_custom_aura_radius" "РАДИУС:" + + "dota_tooltip_ability_ability_vengefulspirit_spirit_debt_innate" "Spirit Feast" + "dota_tooltip_ability_ability_vengefulspirit_spirit_debt_innate_Description" "Способности помечают врагов «долгом духа». Когда помеченный враг умирает, Шендел восстанавливает здоровье и ману; союзники в радиуре Command Aura получают часть лечения." + "dota_tooltip_ability_ability_vengefulspirit_spirit_debt_innate_Lore" "Духи павших кормят месть живых." + "dota_tooltip_ability_ability_vengefulspirit_spirit_debt_innate_mark_duration" "ДЛИТЕЛЬНОСТЬ МЕТКИ:" + "dota_tooltip_ability_ability_vengefulspirit_spirit_debt_innate_heal_on_kill_pct" "%ЛЕЧЕНИЕ ОТ МАКС. ЗДОРОВЬЯ ЦЕЛИ:" + "dota_tooltip_ability_ability_vengefulspirit_spirit_debt_innate_mana_restore" "ВОССТАНОВЛЕНИЕ МАНЫ:" + "dota_tooltip_ability_ability_vengefulspirit_spirit_debt_innate_ally_heal_share_pct" "% ЛЕЧЕНИЯ СОЮЗНИКАМ:" + + "dota_tooltip_modifier_vengefulspirit_spirit_debt_mark" "Долг духа" + "dota_tooltip_modifier_vengefulspirit_spirit_debt_mark_Description" "При смерти накормит дух Шендел и её союзников." + + "dota_tooltip_ability_vengefulspirit_nether_swap_custom" "Nether Swap" + "dota_tooltip_ability_vengefulspirit_nether_swap_custom_Description" "Меняет доли здоровья героя и цели; часть здоровья остаётся на месте. По союзнику передаёт все свои эффекты с ограниченной длительностью." + "dota_tooltip_ability_vengefulspirit_nether_swap_custom_Lore" "Мученичество — совсем не большое воздаяние за месть." + "dota_tooltip_ability_vengefulspirit_nether_swap_custom_hit_point_minimum_pct" "%МИН. ЗДОРОВЬЯ ПОСЛЕ ОБМЕНА:" + + "dota_tooltip_ability_vengefulspirit_revenge" "Revenge" + "dota_tooltip_ability_vengefulspirit_revenge_Description" "Герой уходит в мир теней: его нельзя выбрать целью, каждая атака наносит критический урон." + "dota_tooltip_ability_vengefulspirit_revenge_Lore" "Месть не знает пощады." + "dota_tooltip_ability_vengefulspirit_revenge_duration" "ДЛИТЕЛЬНОСТЬ:" + "dota_tooltip_ability_vengefulspirit_revenge_crit_bonus" "%КРИТИЧЕСКИЙ УРОН:" + + "DOTA_Tooltip_ability_special_bonus_unique_vengefulspirit_4_1" "+{s:bonus_heal_or_damage}% урона или лечения от макс. здоровья по цели Nether Swap" + "DOTA_Tooltip_ability_special_bonus_unique_vengefulspirit_4_2" "Nether Swap накладывает нормальное развеивание" + + //talents sven + "DOTA_Tooltip_ability_special_bonus_unique_sven_warcry_duration_base" "+{s:bonus_duration} сек к длительности Warcry" + "DOTA_Tooltip_ability_special_bonus_unique_sven_storm_hammer_stun_duration" "+{s:bonus_stun_duration} сек к оглушению storm hammer" + "DOTA_Tooltip_ability_special_bonus_unique_sven_gods_strength_duration_base" "+{s:bonus_duration} сек к длительности God's strength" + "DOTA_Tooltip_ability_special_bonus_unique_sven_warcry_damage_bonus_per_armor" "+{s:bonus_damage_bonus_per_armor} к урону за каждую единицу брони от Warcry" + "DOTA_Tooltip_ability_special_bonus_unique_sven_gods_strength_damage_bonus_base" "+{s:bonus_gods_strength_damage_bonus}% к урону от God's strength" + "DOTA_Tooltip_ability_special_bonus_unique_sven_gods_strength_damage_per_health" "+{s:bonus_gods_strength_damage_bonus_per_health} к урону за каждую единицу недостающего здоровья во время God's strength" + "DOTA_Tooltip_ability_special_bonus_unique_sven_great_cleave_damage_pct" "+{s:bonus_cleave_damage_pct}% к урону от great cleave" + + // -----------------Sand King-------------------- + "dota_tooltip_ability_sandking_burrowstrike_custom" "Burrowstrike" + "dota_tooltip_ability_sandking_burrowstrike_custom_Description" "Ныряет под землю и выныривает в выбранной точке. Враги на пути получают магический урон и оглушение на %stun_duration% сек. Часть урона зависит от вашего максимального здоровья." + "dota_tooltip_ability_sandking_burrowstrike_custom_Lore" "Из Скрытого рода предков Чёрных врат он приносит свою месть." + "dota_tooltip_ability_sandking_burrowstrike_custom_burrow_anim_time" "ДЛИТЕЛЬНОСТЬ ЗАХОДА ПОД ЗЕМЛЮ:" + "dota_tooltip_ability_sandking_burrowstrike_custom_burrow_width" "ШИРИНА ТУННЕЛЯ:" + "dota_tooltip_ability_sandking_burrowstrike_custom_burrow_speed" "СКОРОСТЬ ПОДЗЕМНОГО ХОДА:" + "dota_tooltip_ability_sandking_burrowstrike_custom_AbilityCastRange" "ДАЛЬНОСТЬ:" + "dota_tooltip_ability_sandking_burrowstrike_custom_stun_duration" "ДЛИТЕЛЬНОСТЬ ОГЛУШЕНИЯ:" + "dota_tooltip_ability_sandking_burrowstrike_custom_burrow_bonus_damage_max_hp_pct" "%УРОНА ОТ МАКС. ЗДОРОВЬЯ:" + + "dota_tooltip_ability_sandking_sand_storm_custom" "Sand Storm" + "dota_tooltip_ability_sandking_sand_storm_custom_Description" "Поднимает вокруг себя песчаную бурю на %duration% сек. Она следует за вами и каждые %damage_tick_rate% сек. бьёт врагов в радиусе %sand_storm_radius% магией, замедляя их на %sand_storm_move_speed%%%. Базовая сила бури — %sand_storm_damage% в секунду; дополнительный урон растёт от вашего максимального здоровья и умножается на восстановление здоровья." + "dota_tooltip_ability_sandking_sand_storm_custom_Lore" "В Сияющих песках бури не различают друга и врага — только того, кто стоит на пути вихря." + "dota_tooltip_ability_sandking_sand_storm_custom_duration" "ДЛИТЕЛЬНОСТЬ:" + "dota_tooltip_ability_sandking_sand_storm_custom_damage_tick_rate" "ИНТЕРВАЛ УРОНА:" + "dota_tooltip_ability_sandking_sand_storm_custom_sand_storm_radius" "РАДИУС:" + "dota_tooltip_ability_sandking_sand_storm_custom_sand_storm_damage" "УРОН В СЕКУНДУ:" + "dota_tooltip_ability_sandking_sand_storm_custom_sand_storm_regen_damage_pct" "%УРОНА ОТ МАКС. ЗДОРОВЬЯ:" + "dota_tooltip_ability_sandking_sand_storm_custom_sand_storm_move_speed" "%ЗАМЕДЛЕНИЕ ПЕРЕДВИЖЕНИЯ:" + + "dota_tooltip_ability_sandking_scorpion_strike_custom" "Scorpion Strike" + "dota_tooltip_ability_sandking_scorpion_strike_custom_Description" "Удар хвостом по земле: физический урон всем врагам в радиусе %radius%. Во внутреннем круге %inner_radius% урон выше на %inner_radius_bonus_damage_pct%%%. Накладывает замедление %strike_slow%%% на %debuff_duration% сек. К урону добавляются сила удара, доля от вашей атаки и часть от вашего максимального здоровья." + "dota_tooltip_ability_sandking_scorpion_strike_custom_Lore" "Один удар жалом — и пустыня запомнит, кто здесь хозяин." + "dota_tooltip_ability_sandking_scorpion_strike_custom_Scepter_Description" "Сокращает перезарядку на %scepter_cd_pct%%%. По каждому задетому врагу несколько раз накладывается яд Caustic Finale (%scepter_stack% раз)." + "dota_tooltip_ability_sandking_scorpion_strike_custom_radius" "РАДИУС:" + "dota_tooltip_ability_sandking_scorpion_strike_custom_inner_radius" "ВНУТРЕННИЙ РАДИУС:" + "dota_tooltip_ability_sandking_scorpion_strike_custom_inner_radius_bonus_damage_pct" "%ДОП. УРОНА ВО ВНУТРЕННЕЙ ЗОНЕ:" + "dota_tooltip_ability_sandking_scorpion_strike_custom_attack_damage" "ДОП. УРОН:" + "dota_tooltip_ability_sandking_scorpion_strike_custom_attack_damage_pct_from_attack" "%УРОНА ОТ АТАКИ:" + "dota_tooltip_ability_sandking_scorpion_strike_custom_scorpion_bonus_damage_max_hp_pct" "%УРОНА ОТ МАКС. ЗДОРОВЬЯ:" + "dota_tooltip_ability_sandking_scorpion_strike_custom_debuff_duration" "ДЛИТЕЛЬНОСТЬ ЗАМЕДЛЕНИЯ:" + "dota_tooltip_ability_sandking_scorpion_strike_custom_strike_slow" "%ЗАМЕДЛЕНИЕ:" + "dota_tooltip_ability_sandking_scorpion_strike_custom_scepter_stack" "ЗАРЯДОВ ЯДА НА ЦЕЛЬ:" + + "dota_tooltip_ability_sandking_caustic_finale_custom" "Caustic Finale" + "dota_tooltip_ability_sandking_caustic_finale_custom_Description" "Атаки накладывают яд. После %max_attacks% попаданий цель взрывается в радиусе %caustic_finale_radius%, нанося магический урон и замедляя на %explosion_slow_pct%%%. Сила взрыва растёт с вашим уровнем. Если отравленный враг умирает раньше, взрыв сильнее — часть урона считается от его максимального здоровья и вашего уровня." + "dota_tooltip_ability_sandking_caustic_finale_custom_Lore" "В яде караванов тонет не один купец — и каждый шаг по жаркой земле может стать последним." + "dota_tooltip_ability_sandking_caustic_finale_custom_caustic_finale_radius" "РАДИУС ВЗРЫВА:" + "dota_tooltip_ability_sandking_caustic_finale_custom_explosion_damage_per_hero_level" "УРОН ВЗРЫВА ЗА УРОВЕНЬ:" + "dota_tooltip_ability_sandking_caustic_finale_custom_kill_explosion_max_hp_pct_base" "ДОП. ОТ МАКС. HP ЦЕЛИ:" + "dota_tooltip_ability_sandking_caustic_finale_custom_kill_explosion_max_hp_pct_per_level" "ДОП. ЗА УРОВЕНЬ:" + "dota_tooltip_ability_sandking_caustic_finale_custom_caustic_finale_duration" "ДЛИТЕЛЬНОСТЬ ЯДА:" + "dota_tooltip_ability_sandking_caustic_finale_custom_max_attacks" "УДАРОВ ДО ВЗРЫВА:" + "dota_tooltip_ability_sandking_caustic_finale_custom_explosion_slow_pct" "%ЗАМЕДЛЕНИЕ ОТ ВЗРЫВА:" + + "dota_tooltip_ability_sandking_epicenter_custom" "Epicenter" + "dota_tooltip_ability_sandking_epicenter_custom_Description" "После короткого применения в течение %pulse_phase_duration% сек. выпускает %epicenter_pulses% расширяющихся волн из-под ног. Вы можете свободно двигаться. Каждая волна наносит %epicenter_damage% магического урона, замедляет передвижение на %epicenter_slow%%% и атаку на %epicenter_slow_as% на %slow_duration% сек. Радиус волн растёт от %epicenter_radius_base% с шагом %epicenter_radius_increment%." + "dota_tooltip_ability_sandking_epicenter_custom_Lore" "Земля дрожит, песок встаёт стеной — и пустыня отвечает на зов короля." + "dota_tooltip_ability_sandking_epicenter_custom_Scepter_Description" "Пока идут волны, %scepter_rolls_per_second% раз в секунду идёт отсчёт времени: каждые %scepter_proc_every_n_time_checks% такта, если в текущем кольце есть враги, под ними срабатывает полный Scorpion Strike. Базово один удар по врагу; за каждые %scepter_luck_per_extra_tail% удачи добавляется ещё один удар. Нужен изученный Scorpion Strike." + "dota_tooltip_ability_sandking_epicenter_custom_Shard_Description" "Время применения снижается на %shard_cast_reduction% сек. Каждые %shard_break_count% импульса оглушает врагов в радиусе %shard_break_radius% на %shard_break_duration% сек." + "dota_tooltip_ability_sandking_epicenter_custom_epicenter_pulses" "ЧИСЛО ИМПУЛЬСОВ:" + "dota_tooltip_ability_sandking_epicenter_custom_epicenter_damage" "УРОН ЗА ИМПУЛЬС:" + "dota_tooltip_ability_sandking_epicenter_custom_epicenter_radius_base" "НАЧАЛЬНЫЙ РАДИУС:" + "dota_tooltip_ability_sandking_epicenter_custom_epicenter_radius_increment" "ПРИРОСТ РАДИУСА:" + "dota_tooltip_ability_sandking_epicenter_custom_epicenter_slow" "%ЗАМЕДЛЕНИЕ ПЕРЕДВИЖЕНИЯ:" + "dota_tooltip_ability_sandking_epicenter_custom_epicenter_slow_as" "ЗАМЕДЛЕНИЕ АТАКИ:" + "dota_tooltip_ability_sandking_epicenter_custom_slow_duration" "ДЛИТЕЛЬНОСТЬ ЗАМЕДЛЕНИЯ:" + "dota_tooltip_ability_sandking_epicenter_custom_pulse_phase_duration" "ДЛИТЕЛЬНОСТЬ ФАЗЫ ИМПУЛЬСОВ:" + "dota_tooltip_ability_sandking_epicenter_custom_scepter_rolls_per_second" "ТАКТОВ ВРЕМЕНИ В СЕКУНДУ:" + "dota_tooltip_ability_sandking_epicenter_custom_scepter_proc_every_n_time_checks" "ПРОК ХВОСТА КАЖДЫЕ N ТАКТОВ:" + "dota_tooltip_ability_sandking_epicenter_custom_scepter_luck_per_extra_tail" "УДАЧИ ЗА +1 ДОП. УДАР:" + "dota_tooltip_ability_sandking_epicenter_custom_shard_cast_reduction" "СОКРАЩЕНИЕ ПРИМЕНЕНИЯ:" + "dota_tooltip_ability_sandking_epicenter_custom_shard_break_duration" "ДЛИТЕЛЬНОСТЬ ОГЛУШЕНИЯ:" + "dota_tooltip_ability_sandking_epicenter_custom_shard_break_count" "ИМПУЛЬСОВ МЕЖДУ ОГЛУШЕНИЯМИ:" + "dota_tooltip_ability_sandking_epicenter_custom_shard_break_radius" "РАДИУС ОГЛУШЕНИЯ:" + + "DOTA_Tooltip_ability_special_bonus_unique_sandking_burrow_range" "+{s:bonus_AbilityCastRange} к дальности Burrowstrike" + "DOTA_Tooltip_ability_special_bonus_unique_sandking_burrow_stun" "+{s:bonus_stun_duration} сек оглушения от Burrowstrike" + "DOTA_Tooltip_ability_special_bonus_unique_sandking_storm_damage" "+{s:bonus_sand_storm_damage} к маг. урону Sand Storm в секунду" + "DOTA_Tooltip_ability_special_bonus_unique_sandking_scorpion_cd" "-{s:bonus_AbilityCooldown} сек перезарядки Scorpion Strike" + "DOTA_Tooltip_ability_special_bonus_unique_sandking_epicenter_pulses" "+{s:bonus_epicenter_pulses} импульсов Epicenter" + "DOTA_Tooltip_ability_special_bonus_unique_sandking_epicenter_damage" "+{s:bonus_epicenter_damage} к урону Epicenter за импульс" + "DOTA_Tooltip_ability_special_bonus_unique_sandking_finale_radius" "+{s:bonus_caustic_finale_radius} к радиусу Caustic Finale" + + + // -----------------Juggernaut-------------------- + "dota_tooltip_ability_ability_juggernaut_blade_fury_custom" "Blade Fury" + "dota_tooltip_ability_ability_juggernaut_blade_fury_custom_Description" "Джагернаут вращается вокруг себя, создавая вихрь клинков, который наносит собственный урон + магический врагам в радиусе %radius% единиц. Во время действия способности герой получает иммунитет к магии.

При альтернативном применении отправляет вихрь в указанную точку." + "dota_tooltip_ability_ability_juggernaut_blade_fury_custom_Lore" "Мастерство Джагернаута с клинком позволяет ему создавать смертоносный вихрь, сметающий всех врагов на своём пути." + "dota_tooltip_ability_ability_juggernaut_blade_fury_custom_duration" "ДЛИТЕЛЬНОСТЬ:" + "dota_tooltip_ability_ability_juggernaut_blade_fury_custom_radius" "РАДИУС:" + "dota_tooltip_ability_ability_juggernaut_blade_fury_custom_abilitycastrange" "ДАЛЬНОСТЬ ПРИМЕНЕНИЯ ВИХРЯ:" + "dota_tooltip_ability_ability_juggernaut_blade_fury_custom_damage_per_tick" "УРОН ЗА ТИК:" + "dota_tooltip_ability_ability_juggernaut_blade_fury_custom_Shard_Description" "Увеличивает урон и длительность способности. Добавляет пассивный эффект: с шансом %proc_chance%%% (учитывается удача) атака даёт один тик Blade Fury или продлевает действующий вихрь." + + + "dota_tooltip_modifier_juggernaut_blade_fury_custom" "Blade Fury" + "dota_tooltip_modifier_juggernaut_blade_fury_custom_Description" "Герой вращается вокруг себя, нанося магический урон врагам в радиусе. Иммунитет к магии активен, но урон от атак снижен." + + "dota_tooltip_ability_ability_juggernaut_healing_ward_custom" "Healing Ward" + "dota_tooltip_ability_ability_juggernaut_healing_ward_custom_Description" "Джагернаут призывает лечебный вард, который следует за героем и исцеляет всех союзников в радиусе %heal_radius% единиц на %heal_per_second%%% от их максимального здоровья в секунду. Вард существует %ward_duration% секунд." + "dota_tooltip_ability_ability_juggernaut_healing_ward_custom_Lore" "Древний вард, созданный мастерами-целителями, несёт в себе силу жизни, способную исцелить даже самые тяжёлые раны." + "dota_tooltip_ability_ability_juggernaut_healing_ward_custom_ward_duration" "ДЛИТЕЛЬНОСТЬ:" + "dota_tooltip_ability_ability_juggernaut_healing_ward_custom_heal_radius" "РАДИУС ЛЕЧЕНИЯ:" + "dota_tooltip_ability_ability_juggernaut_healing_ward_custom_heal_per_second" "%ЛЕЧЕНИЕ В СЕКУНДУ:" + + "dota_tooltip_modifier_juggernaut_healing_ward_custom" "Healing Ward" + "dota_tooltip_modifier_juggernaut_healing_ward_custom_Description" "Вард исцеляет всех союзников в радиусе." + + "dota_tooltip_ability_ability_juggernaut_blade_dance_custom" "Blade Dance" + "dota_tooltip_ability_ability_juggernaut_blade_dance_custom_Description" "Даёт шанс нанести критический удар.

Active — Astral Slash:

Герой совершает рывок в указанную точку (от %min_travel_distance% до %max_travel_distance% единиц), поражая всех врагов в линии шириной %astral_slash_radius% единиц. Наносит %astral_slash_attack_count% ударов по врагам (используются ваши криты); каждый разрез накладывает на цель на %astral_slash_debuff_duration% сек. дебафф, снижающий броню на %juggernaut_blade_dance_jugg_step_armor_reduce%%%.

При альтернативном применении через 0.25 сек герой таким же рывком возвращается в точку старта, снова нанося урон по линии." + "dota_tooltip_ability_ability_juggernaut_blade_dance_custom_Lore" "Искусство владения клинком Джагернаута позволяет ему находить уязвимые места врагов и резать всё на своём пути." + "dota_tooltip_ability_ability_juggernaut_blade_dance_custom_crit_chance" "%ШАНС КРИТИЧЕСКОГО УДАРА:" + "dota_tooltip_ability_ability_juggernaut_blade_dance_custom_crit_mult" "%КРИТИЧЕСКИЙ УРОН:" + "dota_tooltip_ability_ability_juggernaut_blade_dance_custom_lifesteal_percent" "%ВАМПИРИЗМ:" + "dota_tooltip_ability_ability_juggernaut_blade_dance_custom_AbilityCastRange" "ДАЛЬНОСТЬ РЫВКА:" + "dota_tooltip_ability_ability_juggernaut_blade_dance_custom_AbilityCooldown" "ПЕРЕЗАРЯДКА:" + "dota_tooltip_ability_ability_juggernaut_blade_dance_custom_AbilityManaCost" "РАСХОД МАНЫ:" + "dota_tooltip_ability_ability_juggernaut_blade_dance_custom_min_travel_distance" "МИН. ДИСТАНЦИЯ РЫВКА:" + "dota_tooltip_ability_ability_juggernaut_blade_dance_custom_max_travel_distance" "МАКС. ДИСТАНЦИЯ РЫВКА:" + "dota_tooltip_ability_ability_juggernaut_blade_dance_custom_astral_slash_radius" "ШИРИНА ЛИНИИ:" + + "dota_tooltip_modifier_juggernaut_blade_dance_astral_slash_debuff" "Astral Slash" + "dota_tooltip_modifier_juggernaut_blade_dance_astral_slash_debuff_Description" "Поражён ударом от Astral Slash." + + + "dota_tooltip_ability_ability_juggernaut_omnislash_custom" "Omnislash" + "dota_tooltip_ability_ability_juggernaut_omnislash_custom_Description" "Джагернаут прыгает к выбранной цели и наносит серию атак, нанося %damage%%% урона от атаки. Способность продолжается %duration% секунд, прыгая между врагами в радиусе %bounce_radius% единиц. Во время действия герой неуязвим и не может быть остановлен." + "dota_tooltip_ability_ability_juggernaut_omnislash_custom_Lore" "Величайшая техника Джагернаута, позволяющая ему наносить множество ударов за мгновение, превращая врагов в пыль." + "dota_tooltip_ability_ability_juggernaut_omnislash_custom_duration" "ДЛИТЕЛЬНОСТЬ:" + "dota_tooltip_ability_ability_juggernaut_omnislash_custom_damage" "%УРОН ОТ АТАКИ:" + "dota_tooltip_ability_ability_juggernaut_omnislash_custom_bounce_radius" "РАДИУС ПРЫЖКА:" + "dota_tooltip_ability_ability_juggernaut_omnislash_custom_slash_interval_mult" "МНОЖИТЕЛЬ СКОРОСТИ АТАКИ:" + "dota_tooltip_ability_ability_juggernaut_omnislash_custom_Scepter_Description" "Дополнительно даёт способность Swift Slash, которая наносит быстрый удар по цели." + + "dota_tooltip_ability_ability_juggernaut_miniomnislash_custom" "Swift Slash" + "dota_tooltip_ability_ability_juggernaut_miniomnislash_custom_duration" "ДЛИТЕЛЬНОСТЬ:" + "dota_tooltip_ability_ability_juggernaut_miniomnislash_custom_damage" "%УРОН ОТ АТАКИ:" + "dota_tooltip_ability_ability_juggernaut_miniomnislash_custom_bounce_radius" "РАДИУС ПРЫЖКА:" + "dota_tooltip_ability_ability_juggernaut_miniomnislash_custom_slash_interval_mult" "МНОЖИТЕЛЬ СКОРОСТИ АТАКИ:" + "dota_tooltip_ability_ability_juggernaut_miniomnislash_custom_Description" "Джагернаут совершает быстрый прыжок к цели, нанося %damage%%% урона от атаки. Длительность: %duration% секунд." + "dota_tooltip_ability_ability_juggernaut_miniomnislash_custom_Lore" "Упрощённая версия Omnislash, позволяющая быстро нанести удар по цели." + + "dota_tooltip_modifier_juggernaut_omnislash_custom" "Omnislash" + "dota_tooltip_modifier_juggernaut_omnislash_custom_Description" "Герой неуязвим и прыгает между врагами, нанося серию атак." + + "dota_tooltip_ability_ability_juggernaut_samurai_soul" "Samurai Soul" + "dota_tooltip_ability_ability_juggernaut_samurai_soul_Description" "Когда герой должен умереть, он мгновенно восстанавливает своё здоровье до максимума и получает стак слабости. За каждый стак герой теряет %debuff_pct%%% базовых характеристик (сила, ловкость, интеллект). Максимум 4 стака, после получения 4 стака герой становится смертным. Если героя не атаковали %cooldown% секунд, все стаки сбрасываются." + "dota_tooltip_ability_ability_juggernaut_samurai_soul_Lore" "Душа самурая Джагернаута позволяет ему выживать в самых отчаянных ситуациях, но цена за это - постепенное ослабление." + "dota_tooltip_ability_ability_juggernaut_samurai_soul_cooldown" "ВРЕМЯ ДО СБРОСА СТАКОВ:" + "dota_tooltip_ability_ability_juggernaut_samurai_soul_debuff_pct" "%ОСЛАБЛЕНИЕ ХАРАКТЕРИСТИК:" + + "dota_tooltip_modifier_juggernaut_samurai_soul" "Samurai Soul" + "dota_tooltip_modifier_juggernaut_samurai_soul_Description" "За каждый стак теряется 25%% от базовых характеристик. Если героя не атаковали некоторое время, стаки сбрасываются." + + + + + + + + + + // Talents Juggernaut + "dota_tooltip_ability_special_bonus_unique_juggernaut_healing_ward_duration_custom" "+{s:bonus_ward_duration} секунд к длительности Healing Ward" + "dota_tooltip_ability_special_bonus_unique_juggernaut_blade_dance_lifesteal_custom" "+{s:bonus_lifesteal_percent}% физического вампиризма у Blade Dance" + "dota_tooltip_ability_special_bonus_unique_juggernaut_blade_fury_radius_custom" "+{s:bonus_radius} единиц к радиусу Blade Fury" + + + + //Phantom Assassin + "dota_tooltip_ability_ability_phantom_assassin_stifling_dagger_custom" "Stifling Dagger" + "dota_tooltip_ability_ability_phantom_assassin_stifling_dagger_custom_Description" "Бросает кинжал, который замедляет скорость передвижения врага, наносит ему физический урон в размере %damage% + %attack_factor%%% от атаки героя, а также накладывает эффекты предметов и способностей. С талантом кинжал дополнительно поражает до 2 врагов рядом с основной целью." + "dota_tooltip_ability_ability_phantom_assassin_stifling_dagger_custom_Note0" "Эффекты атаки срабатывают с обычной для них вероятностью." + "dota_tooltip_ability_ability_phantom_assassin_stifling_dagger_custom_Note1" "Способность даёт обзор вокруг кинжала во время полёта и после попадания по цели." + "dota_tooltip_ability_ability_phantom_assassin_stifling_dagger_custom_Lore" "Первый навык, изучаемый Сёстрами вуали, часто предвещает скорый удар." + "dota_tooltip_ability_ability_phantom_assassin_stifling_dagger_custom_slow_movespeed" "%ЗАМЕДЛЕНИЕ ПЕРЕДВИЖЕНИЯ:" + "dota_tooltip_ability_ability_phantom_assassin_stifling_dagger_custom_duration" "ДЛИТЕЛЬНОСТЬ ЗАМЕДЛЕНИЯ:" + "dota_tooltip_ability_ability_phantom_assassin_stifling_dagger_custom_damage" "БАЗОВЫЙ УРОН:" + "dota_tooltip_ability_ability_phantom_assassin_stifling_dagger_custom_attack_factor" "%УРОН ОТ АТАКИ:" + "dota_tooltip_ability_ability_phantom_assassin_stifling_dagger_custom_abilitycastrange" "ДАЛЬНОСТЬ ПРИМЕНЕНИЯ:" + + + + + "dota_tooltip_modifier_ability_phantom_assassin_stifling_dagger_slow" "Stifling Dagger" + "dota_tooltip_modifier_ability_phantom_assassin_stifling_dagger_slow_Description" "Замедление скорости передвижения на %dMODIFIER_PROPERTY_MOVESPEED_BONUS_PERCENTAGE%%%" + + //Phantom Strike + "dota_tooltip_ability_ability_phantom_assassin_phantom_strike_custom" "Phantom strike" + "dota_tooltip_ability_ability_phantom_assassin_phantom_strike_custom_Description" "Герой телепортируется к цели и получает дополнительную скорость атаки на %bonus_duration% секунд. С талантом на крит перезарядка снимается, каст доступен по врагу или в точку, а эффект даёт критический удар с множителем %crit_mult%%%." + "dota_tooltip_ability_ability_phantom_assassin_phantom_strike_custom_Note0" "Может быть использована как на врагов, так и на союзников." + "dota_tooltip_ability_ability_phantom_assassin_phantom_strike_custom_Note1" "Бонус к скорости атаки работает только при атаке вражеских существ." + "dota_tooltip_ability_ability_phantom_assassin_phantom_strike_custom_attack_speed_bonus" "БОНУС К СКОРОСТИ АТАКИ:" + "dota_tooltip_ability_ability_phantom_assassin_phantom_strike_custom_bonus_duration" "ДЛИТЕЛЬНОСТЬ:" + "dota_tooltip_ability_ability_phantom_assassin_phantom_strike_custom_Lore" "Техника, отточенная годами тренировок, позволяет Мортред исчезать и появляться рядом с целью в мгновение ока." + + "dota_tooltip_modifier_phantom_assassin_phantom_strike_custom" "Phantom Strike" + "dota_tooltip_modifier_phantom_assassin_phantom_strike_custom_Description" "Ваша скорость атаки увеличена на %dMODIFIER_PROPERTY_ATTACKSPEED_BONUS_CONSTANT%" + + //Blur + "dota_tooltip_ability_ability_phantom_assassin_blur_custom" "Blur" + "dota_tooltip_ability_ability_phantom_assassin_blur_custom_Description" "Герой становится невидимым и получает бонус к ловкости и скорости передвижения на %duration% секунд." + "dota_tooltip_ability_ability_phantom_assassin_blur_custom_Lore" "Мортред настолько искусна в сокрытии своего присутствия, что может стать невидимой даже для самых зорких глаз." + "dota_tooltip_ability_ability_phantom_assassin_blur_custom_bonus_agility_pct" "%БОНУС К ЛОВКОСТИ:" + "dota_tooltip_ability_ability_phantom_assassin_blur_custom_move_speed_pct" "%БОНУС К СКОРОСТИ ПЕРЕДВИЖЕНИЯ:" + "dota_tooltip_ability_ability_phantom_assassin_blur_custom_bonus_agility" "БОНУС К ЛОВКОСТИ:" + "dota_tooltip_ability_ability_phantom_assassin_blur_custom_duration" "ДЛИТЕЛЬНОСТЬ:" + + "dota_tooltip_modifier_phantom_assassin_blur_active_custom" "Blink" + "dota_tooltip_modifier_phantom_assassin_blur_active_custom_Description" "Вы стали невидимы и ускорились на %dMODIFIER_PROPERTY_MOVESPEED_BONUS_PERCENTAGE%%%" + "dota_tooltip_ability_ability_phantom_assassin_blur_custom_Shard_Description" "На месте применения способности появляется иллюзия мортред, которая умрёт после окончания действия способности. Иллюзия наносит на %illusion_outgoing_damage%%% урона от атаки, а также получает на %illusion_incoming_damage%%% больше урона." + + //Coup de Grace + "dota_tooltip_ability_ability_phantom_assassin_coup_de_grace_custom" "Coup de grace" + "dota_tooltip_ability_ability_phantom_assassin_coup_de_grace_custom_Description" "Герой оттачивает свои боевые навыки, с каждой атакой имея шанс гарантированно нанести критический урон" + "dota_tooltip_ability_ability_phantom_assassin_coup_de_grace_custom_Lore" "Мортред нередко наносит удар смерти, когда враг уже не в силах противостоять его атаке." + "dota_tooltip_ability_ability_phantom_assassin_coup_de_grace_custom_crit_chance" "%ШАНС КРИТИЧЕСКОЙ АТАКИ:" + "dota_tooltip_ability_ability_phantom_assassin_coup_de_grace_custom_crit_mult" "%КРИТИЧЕСКИЙ УРОН:" + "dota_tooltip_ability_ability_phantom_assassin_coup_de_grace_custom_damage_mult" "%БОНУС УРОН:" + "dota_tooltip_ability_ability_phantom_assassin_coup_de_grace_custom_health_mult_decrease" "%ПОТЕРЯ ЗДОРОВЬЯ:" + "dota_tooltip_ability_ability_phantom_assassin_coup_de_grace_custom_Scepter_Description" "Способность становится Активной. Активируя эту способность, на %duration% секунд каждая атака будет критической и снизит броню цели на %armor_reduction% + %armor_reduction_agility_pct%%% от ловкости, после чего уйдёт на перезарядку на %ability_cooldown% секунд." + "dota_tooltip_ability_ability_phantom_assassin_coup_de_grace_custom_Note0" "Первая атака по цели в течение действия способности снижает её броню." + + "dota_tooltip_modifier_phantom_assassin_coup_de_grace_custom_armor_reduction" "Weakness" + "dota_tooltip_modifier_phantom_assassin_coup_de_grace_custom_armor_reduction_Description" "Цель потеряла %dMODIFIER_PROPERTY_PHYSICAL_ARMOR_BONUS% брони." + + "dota_tooltip_modifier_phantom_assassin_coup_de_grace_custom_active" "Coup de Grace" + "dota_tooltip_modifier_phantom_assassin_coup_de_grace_custom_active_Description" "Каждая атака критическая и снижает броню цели." + + //phantom bash + "dota_tooltip_ability_ability_phantom_assassin_phantom_bash_custom" "Фантомное ошеломление" + "dota_tooltip_ability_ability_phantom_assassin_phantom_bash_custom_Description" "Фантом Ассасин наносит удар, оглушая цель на %bash_duration% секунд, после чего перезаряжается %cooldown% секунд." + "dota_tooltip_ability_ability_phantom_assassin_phantom_bash_custom_Note0" "Перезарядка уменьшается на %bash_cd_level% секунд за уровень героя." + + "dota_tooltip_modifier_cooldown" "Phantom Stun" + "dota_tooltip_modifier_cooldown_Description" "Phantom Stun перезаряжается." + + //talents phantom assassin + "dota_tooltip_ability_special_bonus_unique_assassin_blur_duration_base" "+{s:bonus_duration} секунд blur." + "dota_tooltip_ability_special_bonus_unique_assassin_blur_agility_base" "+{s:bonus_bonus_agility} ловкости в эффекте blur." + "dota_tooltip_ability_special_bonus_unique_assassin_stifling_dagger_damage_base" "+{s:bonus_attack_factor}% урона от атаки Stifling dagger." + "dota_tooltip_ability_special_bonus_unique_assassin_stifling_dagger_chance_again" "Атака с {s:bonus_chance}% шансом выпустит кинжал Stifling dagger." + "dota_tooltip_ability_special_bonus_unique_assassin_stifling_dagger_chance_again_Description" "★Кинжал использует псевдорандом для определения критического удара. шанс + 2% с каждым ударом, пока не сработает." + "dota_tooltip_ability_special_bonus_unique_assassin_phantom_strike_lifesteal" "+{s:bonus_lifesteal_percent}% Вампиризма под действием Phantom strike." + "dota_tooltip_ability_special_bonus_unique_assassin_stifling_dagger_max_targets" "+{s:bonus_max_targets} дополнительных целей для Stifling Dagger" + "dota_tooltip_ability_special_bonus_unique_assassin_phantom_strike_crit" "Phantom Strike: {s:bonus_crit_chance}% шанс крита, множитель {s:bonus_crit_mult}%" + "dota_tooltip_ability_special_bonus_unique_assassin_phantom_strike_crit_Description" "Также позволяет телепортироваться в любую нажатую точку." + + + + // -----------------Templar Assassin-------------------- + "dota_tooltip_ability_ability_templar_assassin_refraction_custom" "Refraction" + "dota_tooltip_ability_ability_templar_assassin_refraction_custom_Description" "Щит блокирует %instances% входящих атак и усиливает следующие атаки на %bonus_damage% урона в течение %duration% сек." + "dota_tooltip_ability_ability_templar_assassin_refraction_custom_instances" "ЧИСЛО ЗАРЯДОВ:" + "dota_tooltip_ability_ability_templar_assassin_refraction_custom_bonus_damage" "БОНУСНЫЙ УРОН:" + "dota_tooltip_ability_ability_templar_assassin_refraction_custom_duration" "ДЛИТЕЛЬНОСТЬ:" + "dota_tooltip_ability_ability_templar_assassin_refraction_custom_Shard_Description" "Увеличивает количество зарядов Refraction на %shard_instances_bonus%." + "dota_tooltip_ability_ability_templar_assassin_refraction_custom_Lore" "Секретная техника, позволяющая предвидеть момент удара и ответить мгновенно." + + "dota_tooltip_ability_ability_templar_assassin_psi_blades_custom" "Psi Blades" + "dota_tooltip_ability_ability_templar_assassin_psi_blades_custom_Description" "Врождённая способность. Атаки пробивают цель и наносят врагам позади %attack_spill_pct%%% урона. Дополнительная дальность атаки растёт с уровнем героя." + "dota_tooltip_ability_ability_templar_assassin_psi_blades_custom_bonus_attack_range" "БАЗОВАЯ ДОП. ДАЛЬНОСТЬ:" + "dota_tooltip_ability_ability_templar_assassin_psi_blades_custom_bonus_attack_range_per_hero_level" "ДОП. ДАЛЬНОСТЬ ЗА УРОВЕНЬ ГЕРОЯ:" + "dota_tooltip_ability_ability_templar_assassin_psi_blades_custom_attack_spill_range" "ДАЛЬНОСТЬ ПРОБИТИЯ:" + "dota_tooltip_ability_ability_templar_assassin_psi_blades_custom_attack_spill_pct" "%УРОН ПРОБИТИЯ:" + "dota_tooltip_ability_ability_templar_assassin_psi_blades_custom_Lore" "Пси-энергия проходит сквозь цель, словно клинок через тень, поражая тех, кто прячется за спиной жертвы." + + "dota_tooltip_ability_ability_templar_assassin_meld_custom" "Meld" + "dota_tooltip_ability_ability_templar_assassin_meld_custom_Description" "Два режима. Обычный: невидимость на %duration% сек. Альтернативный: рывок и атаки-снаряды по до %blink_attack_targets% врагам в радиусе (снижение брони на %armor_reduction%)." + "dota_tooltip_ability_ability_templar_assassin_meld_custom_blink_attack_targets" "ЧИСЛО ЦЕЛЕЙ:" + "dota_tooltip_ability_ability_templar_assassin_meld_custom_meld_damage_deal" "БОНУС К УРОНУ ПЕРВОЙ АТАКИ:" + "dota_tooltip_ability_ability_templar_assassin_meld_custom_armor_reduction" "СНИЖЕНИЕ БРОНИ:" + "dota_tooltip_ability_ability_templar_assassin_meld_custom_radius" "РАДИУС ПОИСКА:" + "dota_tooltip_ability_ability_templar_assassin_meld_custom_duration" "ДЛИТЕЛЬНОСТЬ НЕВИДИМОСТИ:" + "dota_tooltip_ability_ability_templar_assassin_meld_custom_Shard_Description" "Увеличивает число целей в альтернативном режиме на %shard_bonus_targets%." + "dota_tooltip_ability_ability_templar_assassin_meld_custom_Lore" "Один вдох, один шаг в пустоту, и враг уже не понимает, откуда пришёл смертельный удар." + + "dota_tooltip_ability_ability_templar_assassin_templar_secret_custom" "Templar Secret" + "dota_tooltip_ability_ability_templar_assassin_templar_secret_custom_Description" "Пассивно даёт кастомный критический удар с шансом %temp_crit_chance%%% и множителем %temp_crit_mult%%%." + "dota_tooltip_ability_ability_templar_assassin_templar_secret_custom_temp_crit_chance" "%ШАНС КРИТА:" + "dota_tooltip_ability_ability_templar_assassin_templar_secret_custom_temp_crit_mult" "%МНОЖИТЕЛЬ КРИТА:" + "dota_tooltip_ability_ability_templar_assassin_templar_secret_custom_Lore" "Тайны Вуали раскрываются лишь избранным: каждый точный выпад может стать приговором." + + "dota_tooltip_ability_ability_templar_assassin_bedlam_custom" "Bedlam" + "dota_tooltip_ability_ability_templar_assassin_bedlam_custom_Description" "Вокруг героя вращается спутник и периодически выпускает снаряды. При попадании снаряда Темпларка выполняет обычную атаку по цели." + "dota_tooltip_ability_ability_templar_assassin_bedlam_custom_attack_interval" "ИНТЕРВАЛ АТАК:" + "dota_tooltip_ability_ability_templar_assassin_bedlam_custom_attack_radius" "РАДИУС АТАК:" + "dota_tooltip_ability_ability_templar_assassin_bedlam_custom_attack_targets" "ЧИСЛО ЦЕЛЕЙ:" + "dota_tooltip_ability_ability_templar_assassin_bedlam_custom_Scepter_Description" "Увеличивает число целей на %scepter_bonus_targets% и ускоряет атаки спутника до %scepter_interval_mult_pct%%% от базового интервала." + "dota_tooltip_ability_ability_templar_assassin_bedlam_custom_Lore" "Спутник из глубин древних тайн кружит рядом, разрывая строй врага шквалом пси-снарядов." + + "dota_tooltip_ability_ability_templar_assassin_refusion_trap_custom" "Refusion Trap" + "dota_tooltip_ability_ability_templar_assassin_refusion_trap_custom_Description" "Устанавливает ловушку. При атаке ловушки Темпларкой она выпускает снаряды в ближайших врагов." + "dota_tooltip_ability_ability_templar_assassin_refusion_trap_custom_radius" "РАДИУС ЛОВУШКИ:" + "dota_tooltip_ability_ability_templar_assassin_refusion_trap_custom_count" "ЧИСЛО СНАРЯДОВ:" + "dota_tooltip_ability_ability_templar_assassin_refusion_trap_custom_duration" "ДЛИТЕЛЬНОСТЬ ЛОВУШКИ:" + "dota_tooltip_ability_ability_templar_assassin_refusion_trap_custom_Lore" "Заранее приготовленная западня Вуали терпеливо ждёт касания хозяйки, чтобы обрушить на врагов пси-гнев." + + "dota_tooltip_ability_special_bonus_unique_templar_assassin_4" "+{s:bonus_bonus_damage} к урону атаки от Refraction" + "dota_tooltip_ability_special_bonus_unique_templar_assassin_8" "+{s:bonus_instances} заряда Refraction" + "dota_tooltip_ability_special_bonus_unique_templar_assassin_3" "+{s:bonus_radius} к радиусу поиска Meld" + "dota_tooltip_ability_special_bonus_unique_templar_assassin_2" "Bedlam: +{s:bonus_attack_targets} цель" + "dota_tooltip_ability_special_bonus_unique_templar_assassin_7" "+{s:bonus_attack_spill_pct}% к урону пробития Psi Blades" + + + //Crystal Maiden + "dota_tooltip_ability_ability_crystal_maiden_crystal_nova_custom" "Crystal nova" + "dota_tooltip_ability_ability_crystal_maiden_crystal_nova_custom_Description" "Создаёт взрыв кристальной энергии в указанной точке, нанося магический урон врагам и исцеляя союзников в радиусе %radius% единиц. Урон и исцеление увеличиваются от интеллекта героя. Враги получают стаки заморозки. При исцелении союзника сверх максимума здоровья избыточное исцеление превращается в щит (не более %max_shield_pct%%% от макс. здоровья цели, %shield_duration% сек.)." + "dota_tooltip_ability_ability_crystal_maiden_crystal_nova_custom_Lore" "Древняя магия ледяных кристаллов, переданная Райлэй от её сестры, способна как заморозить врагов, так и исцелить союзников." + "dota_tooltip_ability_ability_crystal_maiden_crystal_nova_custom_damage" "УРОН:" + "dota_tooltip_ability_ability_crystal_maiden_crystal_nova_custom_heal" "ИСЦЕЛЕНИЕ:" + "dota_tooltip_ability_ability_crystal_maiden_crystal_nova_custom_radius" "РАДИУС:" + "dota_tooltip_ability_ability_crystal_maiden_crystal_nova_custom_intellect_per_damage" "ЛЕЧЕНИЕ/УРОН ОТ ИНТЕЛЛЕКТА:" + "dota_tooltip_ability_ability_crystal_maiden_crystal_nova_custom_frost_stacks_per_level" "СТАКИ ЗАМОРОЗКИ:" + "dota_tooltip_ability_ability_crystal_maiden_crystal_nova_custom_slow_duration" "ДЛИТЕЛЬНОСТЬ ЗАМЕДЛЕНИЯ:" + "dota_tooltip_ability_ability_crystal_maiden_crystal_nova_custom_max_shield_pct" "МАКС. ЩИТА (% ОТ МАКС. ЗДОРОВЬЯ):" + "dota_tooltip_ability_ability_crystal_maiden_crystal_nova_custom_shield_duration" "ДЛИТЕЛЬНОСТЬ ЩИТА:" + + "dota_tooltip_ability_ability_crystal_maiden_frostbite_custom" "Frostbite" + "dota_tooltip_ability_ability_crystal_maiden_frostbite_custom_Description" "Обездвиживает цель в радиусе %radius% единиц на %duration% секунд. Враги получают периодический магический урон, а союзники — исцеление и снижение входящего урона на %incoming_damage_pct%%%. При альтернативном касте урон удваивается и применяется только к врагам. Враги с здоровьем ниже %execute_threshold_pct%%% мгновенно убиваются: на месте остаётся ледяная копия, её можно ударить — она отлетит и при столкновении взорвётся в радиусе %explosion_radius%, нанося урон (%damage_per_mana% за единицу маны кастера)." + "dota_tooltip_ability_ability_crystal_maiden_frostbite_custom_Lore" "Ледяные оковы Райлэй сковывают врагов, не давая им двигаться, пока холод не проникнет в их кости." + "dota_tooltip_ability_ability_crystal_maiden_frostbite_custom_damage" "УРОН В СЕКУНДУ:" + "dota_tooltip_ability_ability_crystal_maiden_frostbite_custom_heal_per_second" "ИСЦЕЛЕНИЕ В СЕКУНДУ:" + "dota_tooltip_ability_ability_crystal_maiden_frostbite_custom_incoming_damage_pct" "%СНИЖЕНИЕ ВХОДЯЩЕГО УРОНА (СОЮЗНИК):" + "dota_tooltip_ability_ability_crystal_maiden_frostbite_custom_duration" "ДЛИТЕЛЬНОСТЬ:" + "dota_tooltip_ability_ability_crystal_maiden_frostbite_custom_radius" "РАДИУС:" + "dota_tooltip_ability_ability_crystal_maiden_frostbite_custom_shard_Description" "Усиляет уменьшение входящего урона" + "dota_tooltip_ability_ability_crystal_maiden_frostbite_custom_frost_stacks_per_level" "СТАКИ ЗАМОРОЗКИ В СЕКУНДУ:" + "dota_tooltip_ability_ability_crystal_maiden_frostbite_custom_execute_threshold_pct" "%ПОРОГ КАЗНИ:" + "dota_tooltip_ability_ability_crystal_maiden_frostbite_custom_explosion_radius" "РАДИУС ВЗРЫВА КОПИИ:" + "dota_tooltip_ability_ability_crystal_maiden_frostbite_custom_damage_per_mana" "УРОН ВЗРЫВА ЗА 1 МАНЫ:" + "dota_tooltip_ability_ability_crystal_maiden_frostbite_custom_Note0" "При альтернативном касте урон удваивается и применяется только к врагам." + + "dota_tooltip_ability_ability_crystal_maiden_brilliance_aura_custom" "Brilliance aura" + "dota_tooltip_ability_ability_crystal_maiden_brilliance_aura_custom_Description" "Пассивная аура, увеличивающая регенерацию маны союзникам в радиусе %aura_radius% единиц на %mana_regen_pct%%%. Активно: наносит магический урон врагам в радиусе %active_radius% и накладывает заморозку; при альтернативном касте — телепорт на %teleport_range% единиц. Пока активен эффект Brilliance, при каждом применении любой способности появляется снежинка (до %max_crystals%): она наносит %crystal_damage% урона + %pct_25_mana_damage%%% от недостающей маны врагов в радиусе и добавляет %crystal_frost_stacks% стаков заморозки за касание (%crystal_duration% сек. жизни снежинки)." + "dota_tooltip_ability_ability_crystal_maiden_brilliance_aura_custom_Lore" "Магическая аура Райлэй наполняет союзников энергией, позволяя им чаще использовать способности." + "dota_tooltip_ability_ability_crystal_maiden_brilliance_aura_custom_mana_regen_pct" "%РЕГЕНЕРАЦИЯ МАНЫ:" + "dota_tooltip_ability_ability_crystal_maiden_brilliance_aura_custom_aura_radius" "РАДИУС АУРЫ:" + "dota_tooltip_ability_ability_crystal_maiden_brilliance_aura_custom_active_radius" "РАДИУС АКТИВНОГО ЭФФЕКТА:" + "dota_tooltip_ability_ability_crystal_maiden_brilliance_aura_custom_damage" "УРОН:" + "dota_tooltip_ability_ability_crystal_maiden_brilliance_aura_custom_duration" "ДЛИТЕЛЬНОСТЬ ЭФФЕКТА:" + "dota_tooltip_ability_ability_crystal_maiden_brilliance_aura_custom_frost_stacks_per_level" "СТАКИ ЗАМОРОЗКИ:" + "dota_tooltip_ability_ability_crystal_maiden_brilliance_aura_custom_intellect_per_damage" "УРОН ОТ ИНТЕЛЛЕКТА:" + "dota_tooltip_ability_ability_crystal_maiden_brilliance_aura_custom_teleport_range" "ДАЛЬНОСТЬ ТЕЛЕПОРТАЦИИ:" + "dota_tooltip_ability_ability_crystal_maiden_brilliance_aura_custom_mana_cost_pct" "%СТОИМОСТЬ МАНЫ:" + "dota_tooltip_ability_ability_crystal_maiden_brilliance_aura_custom_crystal_damage" "УРОН СНЕЖИНКИ:" + "dota_tooltip_ability_ability_crystal_maiden_brilliance_aura_custom_pct_25_mana_damage" "%УРОНА ОТ НЕДОСТАЮЩЕЙ МАНЫ:" + "dota_tooltip_ability_ability_crystal_maiden_brilliance_aura_custom_crystal_frost_stacks" "СТАКОВ ЗА КАСАНИЕ:" + "dota_tooltip_ability_ability_crystal_maiden_brilliance_aura_custom_max_crystals" "МАКС. СНЕЖИНОК:" + "dota_tooltip_ability_ability_crystal_maiden_brilliance_aura_custom_crystal_duration" "ДЛИТЕЛЬНОСТЬ СНЕЖИНКИ:" + "dota_tooltip_ability_ability_crystal_maiden_brilliance_aura_custom_Note0" "Урон увеличивается от недостающей маны героя." + "dota_tooltip_ability_ability_crystal_maiden_brilliance_aura_custom_Note1" "При альтернативном касте герой телепортируется на указанную точку." + + "dota_tooltip_ability_ability_crystal_maiden_freezing_field_custom" "Freezing Field" + "dota_tooltip_ability_ability_crystal_maiden_freezing_field_custom_Description" "Канальная способность. Создаёт взрывы ледяной энергии вокруг героя в радиусе %radius% единиц, нанося магический урон врагам и применяя заморозку. Урон увеличивается от интеллекта героя. При наличии скипетра периодически кастует мини-версию Кристальной новеллы." + "dota_tooltip_ability_ability_crystal_maiden_freezing_field_custom_Lore" "Величайшее заклинание Райлэй, способное заморозить целое поле битвы, превращая врагов в ледяные статуи." + "dota_tooltip_ability_ability_crystal_maiden_freezing_field_custom_radius" "РАДИУС:" + "dota_tooltip_ability_ability_crystal_maiden_freezing_field_custom_explosion_radius" "РАДИУС ВЗРЫВА:" + "dota_tooltip_ability_ability_crystal_maiden_freezing_field_custom_explosion_damage" "УРОН ВЗРЫВА:" + "dota_tooltip_ability_ability_crystal_maiden_freezing_field_custom_explosion_interval" "ИНТЕРВАЛ ВЗРЫВОВ:" + "dota_tooltip_ability_ability_crystal_maiden_freezing_field_custom_frost_stacks_per_explosion" "СТАКИ ЗАМОРОЗКИ ЗА ВЗРЫВ:" + "dota_tooltip_ability_ability_crystal_maiden_freezing_field_custom_intellect_per_damage" "ЛЕЧЕНИЕ/УРОН ОТ ИНТЕЛЛЕКТА:" + "dota_tooltip_ability_ability_crystal_maiden_freezing_field_custom_slow_duration" "ДЛИТЕЛЬНОСТЬ ЗАМЕДЛЕНИЯ:" + "dota_tooltip_ability_ability_crystal_maiden_freezing_field_custom_Scepter_Description" "Периодически кастует мини-версию Crystal Nova, при попадании вешает на врагов или союзников Frostbite, которая исцеляет союзников и наносит урон врагам. Также способность можно использовать во время движения." + + + //Модификаторы Crystal Maiden + "dota_tooltip_modifier_crystal_maiden_frostbite_ally" "Frostbite (ally)" + "dota_tooltip_modifier_crystal_maiden_frostbite_ally_Description" "Обездвижен, получает исцеление и снижение входящего урона." + + "dota_tooltip_modifier_crystal_maiden_frostbite_enemy" "Frostbite (enemy)" + "dota_tooltip_modifier_crystal_maiden_frostbite_enemy_Description" "Обездвижен и получает периодический магический урон. Также получает стаки заморозки." + + "dota_tooltip_modifier_crystal_maiden_brilliance_aura_buff" "Passive Brilliance aura" + "dota_tooltip_modifier_crystal_maiden_brilliance_aura_buff_Description" "Регенерация маны увеличена на %dMODIFIER_PROPERTY_MANA_REGEN_TOTAL_PERCENTAGE%%%." + + "dota_tooltip_modifier_crystal_maiden_brilliance_crystals" "Active Brilliance aura" + "dota_tooltip_modifier_crystal_maiden_brilliance_crystals_Description" "Снежинки кружатся и атакуют всех приблизившихся врагов" + + "dota_tooltip_modifier_crystal_maiden_freezing_field_custom" "Freezing Field" + "dota_tooltip_modifier_crystal_maiden_freezing_field_custom_Description" "Создаёт взрывы ледяной энергии вокруг героя, нанося урон врагам и применяя заморозку." + + //Drow ranger + "dota_tooltip_ability_ability_drow_ranger_frost_arrows_custom" "Frost arrows" + "dota_tooltip_ability_ability_drow_ranger_frost_arrows_custom_Description" "Траксекс наполняет стрелы морозной энергией: дополнительный урон (%damage_pct%%% от атаки) и стаки охлаждения за выстрел." + "dota_tooltip_ability_ability_drow_ranger_frost_arrows_custom_Scepter_Description" "С шансом %ricochet_chance%%% стрела после попадания отскакивает к ближайшему другому врагу в радиусе 500 и накладывает охлаждение." + "dota_tooltip_ability_ability_drow_ranger_frost_arrows_custom_damage_pct" "%ДОПОЛНИТЕЛЬНЫЙ УРОН:" + "dota_tooltip_ability_ability_drow_ranger_frost_arrows_custom_frost_stacks_per_level" "СТАКИ ОХЛАЖДЕНИЯ:" + "dota_tooltip_ability_ability_drow_ranger_frost_arrows_custom_ricochet_chance" "%ШАНС РИКОШЕТА:" + "dota_tooltip_ability_ability_drow_ranger_frost_arrows_custom_Lore" "Ледяные стрелы Траксекс, созданные из чистейшего льда Ледяного Предела, способны заморозить даже самое горячее сердце." + + "dota_tooltip_ability_ability_drow_ranger_gust_custom" "Gust" + "dota_tooltip_ability_ability_drow_ranger_gust_custom_Description" "Траксекс создаёт мощный порыв ветра, который отталкивает врагов, наносит урон и накладывает стаки охлаждения. Получившие урон враги дополнительно замораживаются на %frozen_son_duration% сек." + "dota_tooltip_ability_ability_drow_ranger_gust_custom_gust_speed" "СКОРОСТЬ ПОРЫВА:" + "dota_tooltip_ability_ability_drow_ranger_gust_custom_gust_width" "ШИРИНА ПОРЫВА:" + "dota_tooltip_ability_ability_drow_ranger_gust_custom_gust_distance" "ДАЛЬНОСТЬ ПОРЫВА:" + "dota_tooltip_ability_ability_drow_ranger_gust_custom_knockback_distance" "ДАЛЬНОСТЬ ОТТАЛКИВАНИЯ:" + "dota_tooltip_ability_ability_drow_ranger_gust_custom_knockback_duration" "ДЛИТЕЛЬНОСТЬ ОТТАЛКИВАНИЯ:" + "dota_tooltip_ability_ability_drow_ranger_gust_custom_damage" "УРОН:" + "dota_tooltip_ability_ability_drow_ranger_gust_custom_frost_stacks_per_level" "СТАКИ ОХЛАЖДЕНИЯ:" + "dota_tooltip_ability_ability_drow_ranger_gust_custom_frozen_son_duration" "ДЛИТЕЛЬНОСТЬ ЗАМОРОЗКИ ПОСЛЕ УРОНА:" + "dota_tooltip_ability_ability_drow_ranger_gust_custom_Lore" "Холодные ветра Ледяного Предела научили Траксекс управлять стихией, превращая её в смертоносное оружие против врагов." + + "dota_tooltip_ability_ability_drow_ranger_multishot_custom" "Multishot" + "dota_tooltip_ability_ability_drow_ranger_multishot_custom_Description" "ПРЕРЫВАЕМАЯ — герой выпускает один залп стрел за другим, нанося врагам долю от базового урона и накладывая стаки охлаждения. Стрелы пробивают цели насквозь. Длительность каста 1,75 сек." + "dota_tooltip_ability_ability_drow_ranger_multishot_custom_arrow_count" "КОЛИЧЕСТВО СТРЕЛ:" + "dota_tooltip_ability_ability_drow_ranger_multishot_custom_arrow_damage_pct" "%ДОЛЯ БАЗОВОГО УРОНА:" + "dota_tooltip_ability_ability_drow_ranger_multishot_custom_arrow_width" "ШИРИНА СТРЕЛЫ:" + "dota_tooltip_ability_ability_drow_ranger_multishot_custom_frost_stacks_per_level" "СТАКИ ОХЛАЖДЕНИЯ:" + "dota_tooltip_ability_ability_drow_ranger_multishot_custom_Lore" "Годы тренировок в суровых условиях Ледяного Предела позволили Траксекс выпускать множество стрел с невероятной скоростью и точностью." + + "dota_tooltip_ability_ability_drow_ranger_marksmanship_custom" "Marksmanship" + "dota_tooltip_ability_ability_drow_ranger_marksmanship_custom_Description" "Пока в радиусе %disable_range% от Траксекс нет вражеских героев, она получает %bonus_agility_pct%%% ловкости. Каждая %hits%-я успешная атака наносит дополнительно %bonus_damage% физического урона; удар срабатывания игнорирует базовую броню при получении урона от этого выстрела." + "dota_tooltip_ability_ability_drow_ranger_marksmanship_custom_bonus_damage" "ДОП. УРОН ПРИ СРАБАТЫВАНИИ:" + "dota_tooltip_ability_ability_drow_ranger_marksmanship_custom_bonus_agility_pct" "%БОНУС ЛОВКОСТИ:" + "dota_tooltip_ability_ability_drow_ranger_marksmanship_custom_disable_range" "РАДИУС ОТКЛЮЧЕНИЯ:" + + "dota_tooltip_ability_ability_drow_ranger_marksmanship_custom_hits" "УДАРОВ ДО СРАБАТЫВАНИЯ:" + "dota_tooltip_ability_ability_drow_ranger_marksmanship_custom_Lore" "Непревзойденное мастерство Траксекс в стрельбе из лука делает её одним из самых опасных стрелков во всём мире." + + + // drow ranger talents + "dota_tooltip_ability_special_bonus_unique_drow_ranger_frost_arrow_damage_pct" "+{s:bonus_damage_pct}% урона от Морозных стрел" + "dota_tooltip_ability_special_bonus_unique_drow_ranger_arrow_damage_pct" "+{s:bonus_arrow_damage_pct}% урона от Морозного шквала" + "dota_tooltip_ability_special_bonus_unique_drow_ranger_AbilityCooldown" "-{s:bonus_AbilityCooldown} сек перезарядки способности Морозный шквал" + "dota_tooltip_ability_special_bonus_unique_drow_ranger_arrow_count_per_wave" "+{s:bonus_arrow_count_per_wave} стрел в Морозном шквале" + "dota_tooltip_ability_special_bonus_unique_drow_ranger_gust_frozen_stack" "+{s:bonus_frost_stacks_per_level} стаков охлаждения от Порыва ветра" + + + // lina + + "dota_tooltip_ability_ability_lina_dragon_slave_custom" "Огненная плеть" + "dota_tooltip_ability_ability_lina_dragon_slave_custom_Note0" "

Пасивное: Burn

С 0+ стаков владелец начинает получать урон в секунду от количество стаков * 3.

С 70+ стаков владелец эффекта теряет 25% от количества стаков своей брони и маг. сопротивления.

Эффект не стакается с ОБМОРОЖЕНИЕМ из-за чего ОЖОГ поглащает эффект ОБМОРОЖЕНИЯ." + "dota_tooltip_ability_ability_lina_dragon_slave_custom_Description" "Лина выпускает волну огня, которая наносит урон врагам на своём пути и дополнительно — %mana_damage_from_current_pct%%% от текущей маны. При атаке с шансом %proc_chance%%% может автоматически выпустить Огненную плеть в текущую цель атаки." + "dota_tooltip_ability_ability_lina_dragon_slave_custom_Scepter_Description" "Урон увеличивается на %damage_mult%%% от интеллекта Лины." + "dota_tooltip_ability_ability_lina_dragon_slave_custom_proc_chance" "%ШАНС ПРОКА С АТАКИ:" + "dota_tooltip_ability_ability_lina_dragon_slave_custom_dragon_slave_damage" "УРОН:" + "dota_tooltip_ability_ability_lina_dragon_slave_custom_mana_damage_from_current_pct" "% МАНЫ В ДОП. УРОН:" + "dota_tooltip_ability_ability_lina_dragon_slave_custom_fire_stacks_per_level" "ОЖОГОВ:" + "dota_tooltip_ability_ability_lina_dragon_slave_custom_dragon_slave_distance" "ДАЛЬНОСТЬ:" + "dota_tooltip_ability_ability_lina_dragon_slave_custom_dragon_slave_width_initial" "НАЧАЛЬНАЯ ШИРИНА:" + "dota_tooltip_ability_ability_lina_dragon_slave_custom_dragon_slave_width_end" "КОНЕЧНАЯ ШИРИНА:" + "dota_tooltip_ability_ability_lina_dragon_slave_custom_Lore" "Первое заклинание в арсенале Лины, Огненный шквал, сжигает всё на своём пути." + + "dota_tooltip_ability_ability_lina_light_strike_array_custom" "Поток взрывов" + "dota_tooltip_ability_ability_lina_light_strike_array_custom_Note0" "

Пасивное: Burn

С 0+ стаков владелец начинает получать урон в секунду от количество стаков * 3.

С 70+ стаков владелец эффекта теряет 25% от количества стаков своей брони и маг. сопротивления.

Эффект не стакается с ОБМОРОЖЕНИЕМ из-за чего ОЖОГ поглащает эффект ОБМОРОЖЕНИЯ." + + "dota_tooltip_ability_ability_lina_light_strike_array_custom_Description" "Лина вызывает столб пламени, который оглушает и наносит урон врагам в указанной области, плюс %mana_damage_from_current_pct%%% от текущей маны. После первого взрыва за каждый уровень способности выше первого отдельно проверяется шанс дополнительного такого же удара в той же точке (задержка между взрывами как у основного каста)." + "dota_tooltip_ability_ability_lina_light_strike_array_custom_shard_Description" "Урон увеличивается на количество интеллекта Лины." + "dota_tooltip_ability_ability_lina_light_strike_array_custom_light_strike_array_aoe" "РАДИУС:" + "dota_tooltip_ability_ability_lina_light_strike_array_custom_fire_stacks_per_level" "ОЖОГОВ:" + "dota_tooltip_ability_ability_lina_light_strike_array_custom_light_strike_array_damage" "УРОН:" + "dota_tooltip_ability_ability_lina_light_strike_array_custom_mana_damage_from_current_pct" "% МАНЫ В ДОП. УРОН:" + "dota_tooltip_ability_ability_lina_light_strike_array_custom_light_strike_array_stun_duration" "ДЛИТЕЛЬНОСТЬ ОГЛУШЕНИЯ:" + "dota_tooltip_ability_ability_lina_light_strike_array_custom_Lore" "Хотя Лина предпочитает сжигать своих врагов, она не против и оглушить их." + + "dota_tooltip_ability_ability_lina_flame_cloak_custom_Note0" "

Пасивное: Burn

С 0+ стаков владелец начинает получать урон в секунду от количество стаков * 3.

С 70+ стаков владелец эффекта теряет 25% от количества стаков своей брони и маг. сопротивления.

Эффект не стакается с ОБМОРОЖЕНИЕМ из-за чего ОЖОГ поглащает эффект ОБМОРОЖЕНИЯ." + "dota_tooltip_ability_ability_lina_flame_cloak_custom" "Огненный плащ" + "dota_tooltip_ability_ability_lina_flame_cloak_custom_Description" "Лина окружает себя огненным плащом: наносит урон врагам в радиусе (каждый тик + %mana_damage_from_current_pct%%% от текущей маны) и накладывает ожог. За стаки пассивного эффекта даёт бонус к скорости передвижения и атаки; за каждый стак также %spell_amplify%%% усиления заклинаний и %magical_resistance%%% сопротивления магии." + "dota_tooltip_ability_ability_lina_flame_cloak_custom_damage_per_second" "УРОН В СЕКУНДУ:" + "dota_tooltip_ability_ability_lina_flame_cloak_custom_mana_damage_from_current_pct" "% МАНЫ В ДОП. УРОН ЗА ТИК:" + "dota_tooltip_ability_ability_lina_flame_cloak_custom_radius" "РАДИУС:" + "dota_tooltip_ability_ability_lina_flame_cloak_custom_fire_stacks_per_level" "ОЖОГОВ:" + "dota_tooltip_ability_ability_lina_flame_cloak_custom_duration" "ДЛИТЕЛЬНОСТЬ:" + "dota_tooltip_ability_ability_lina_flame_cloak_custom_attackspeed_bonus" "%СКОРОСТЬ АТАКИ ЗА СТАК:" + "dota_tooltip_ability_ability_lina_flame_cloak_custom_movespeed_bonus" "%СКОРОСТЬ ПЕРЕДВИЖЕНИЯ ЗА СТАК:" + "dota_tooltip_ability_ability_lina_flame_cloak_custom_max_stacks" "МАКСИМУМ СТАКОВ:" + "dota_tooltip_ability_ability_lina_flame_cloak_custom_Lore" "Лина окружает себя огненным плащом, который сжигает всех, кто осмелится подойти слишком близко." + + "dota_tooltip_ability_ability_lina_laguna_blade_custom" "Клинок Лагуны" + "dota_tooltip_ability_ability_lina_laguna_blade_custom_Description" "Лина выпускает мощный разряд молнии, который наносит огромный урон указанной цели и дополнительно — %mana_damage_from_current_pct%%% от текущей маны." + "dota_tooltip_ability_ability_lina_laguna_blade_custom_damage" "УРОН:" + "dota_tooltip_ability_ability_lina_laguna_blade_custom_mana_damage_from_current_pct" "% МАНЫ В ДОП. УРОН:" + "dota_tooltip_ability_ability_lina_laguna_blade_custom_Lore" "Самое мощное заклинание в арсенале Лины, Клинок Лагуны, способно испепелить практически любого врага." + "dota_tooltip_ability_ability_lina_laguna_blade_custom_Scepter_Description" "Урон становится чистым и дополнительно увеличивается от интеллекта. Расход маны пересчитывается: часть маны списывается по формуле с параметрами ниже; после каста на %duration% сек. %spell_amplify%%% недостающей маны конвертируется в усиление заклинаний (1 ед. за каждую недостающую единицу маны)." + + "dota_tooltip_modifier_lina_flame_cloak_custom_passive_buff" "Flame Cloak" + "dota_tooltip_modifier_lina_flame_cloak_custom_passive_buff_Description" "Герой получает бонус к скорости передвижения %dMODIFIER_PROPERTY_MOVESPEED_BONUS_PERCENTAGE%%% и %dMODIFIER_PROPERTY_ATTACKSPEED_PERCENTAGE%%% скорости атаки от Flame Cloak." + + "dota_tooltip_modifier_lina_laguna_blade_custom_buff" "Laguna Blade" + "dota_tooltip_modifier_lina_laguna_blade_custom_buff_Description" "Увеличивает усиление заклинаний на %dMODIFIER_PROPERTY_SPELL_AMPLIFY_PERCENTAGE%%%." + + // lina talents + "dota_tooltip_ability_special_bonus_unique_lina_light_strike_array_five" "+{s:bonus_array_five} Раскалённых ударов" + "dota_tooltip_ability_special_bonus_unique_lina_laguna_blade_custom_cd" "-{s:bonus_AbilityCooldown} сек перезарядки Клинка Лагуны" + "dota_tooltip_ability_special_bonus_unique_lina_flame_cloak_custom_damage_bonus" "+{s:bonus_damage_per_second} урона в секунду от Огненного плаща" + "dota_tooltip_ability_special_bonus_unique_lina_flame_cloak_custom_bonus_movespeed_attack_speed_pct" "+{s:bonus_movespeed_bonus}% скорости атаки и передвижения за стак от Огненного плаща" + + "dota_tooltip_ability_ability_lina_flame_cloak_custom_spell_amplify" "%УСИЛЕНИЕ ЗАКЛИНАНИЙ ЗА СТАК:" + "dota_tooltip_ability_ability_lina_flame_cloak_custom_magical_resistance" "%СОПРОТИВЛЕНИЕ МАГИИ ЗА СТАК:" + + "dota_tooltip_ability_ability_lina_scorch_affinity_innate" "Scorch Affinity" + "dota_tooltip_ability_ability_lina_scorch_affinity_innate_Description" "За каждого юнита в радиусе %radius%, на котором действует ожог, Лина получает +%bonus_pct_base%%% к исходящему урону и столько же снижения входящего, плюс ещё %bonus_pct_per_hero_level%%% за уровень героя за каждого такого юнита." + "dota_tooltip_ability_ability_lina_scorch_affinity_innate_Lore" "Чем больше вокруг пылает пламя, тем сильнее Лина питается жаром — и тем меньше ранит её чужой огонь." + "dota_tooltip_ability_ability_lina_scorch_affinity_innate_radius" "РАДИУС:" + "dota_tooltip_ability_ability_lina_scorch_affinity_innate_bonus_pct_base" "БОНУС ЗА ЮНИТА С ОЖОГОМ:" + "dota_tooltip_ability_ability_lina_scorch_affinity_innate_bonus_pct_per_hero_level" "ДОП. БОНУС ЗА УРОВЕНЬ ГЕРОЯ:" + + // -----------------Keeper of the Light-------------------- + "DOTA_Tooltip_ability_keeper_of_the_light_illuminate_custom" "Illuminate" + "DOTA_Tooltip_ability_keeper_of_the_light_illuminate_custom_Description" "ПРЕРЫВАЕМАЯ — герой создаёт волну света по направлению к цели. Враги на пути получают урон, дополнительно равный %mana_damage_from_current_pct%%% от текущей маны Эзалора на момент выпуска, а союзники — лечение, зависящее от нанесённого урона." + "DOTA_Tooltip_ability_keeper_of_the_light_illuminate_custom_Lore" "Свет Эзалора сметает тьму и возвращает надежду тем, кто стоит рядом." + "DOTA_Tooltip_ability_keeper_of_the_light_illuminate_custom_heal_percent" "%ЛЕЧЕНИЕ ОТ НАНЕСЁННОГО УРОНА:" + "DOTA_Tooltip_ability_keeper_of_the_light_illuminate_custom_max_damage" "МАКС. УРОН:" + "DOTA_Tooltip_ability_keeper_of_the_light_illuminate_custom_min_damage" "МИН. УРОН:" + "DOTA_Tooltip_ability_keeper_of_the_light_illuminate_custom_radius" "ШИРИНА:" + "DOTA_Tooltip_ability_keeper_of_the_light_illuminate_custom_mana_damage_from_current_pct" "% МАНЫ В ДОП. УРОН:" + + "DOTA_Tooltip_ability_keeper_of_the_light_blinding_light_custom" "Blinding Light" + "DOTA_Tooltip_ability_keeper_of_the_light_blinding_light_custom_Description" "Создаёт в указанной области вспышку света, которая отталкивает врагов и ослепляет их. Под ослеплением атаки всегда промахиваются." + "DOTA_Tooltip_ability_keeper_of_the_light_blinding_light_custom_Lore" "Ослепительный всполох выжигает решимость врагов раньше, чем их оружие находит цель." + "DOTA_Tooltip_ability_keeper_of_the_light_blinding_light_custom_miss_stack" "КОЛ-ВО ПРОМАХОВ:" + "DOTA_Tooltip_ability_keeper_of_the_light_blinding_light_custom_miss_stack_boss" "КОЛ-ВО ПРОМАХОВ ПО БОССУ:" + "DOTA_Tooltip_ability_keeper_of_the_light_blinding_light_custom_duration_stack" "ДЛИТЕЛЬНОСТЬ:" + "DOTA_Tooltip_ability_keeper_of_the_light_blinding_light_custom_radius" "РАДИУС:" + "DOTA_Tooltip_ability_keeper_of_the_light_blinding_light_custom_damage" "УРОН:" + + "DOTA_Tooltip_ability_keeper_of_the_light_chakra_magic_custom" "Chakra Magic" + "DOTA_Tooltip_ability_keeper_of_the_light_chakra_magic_custom_Description" "Восстанавливает ману союзнику, уменьшает оставшуюся перезарядку его способностей и на время снижает получаемый физический и магический урон." + "DOTA_Tooltip_ability_keeper_of_the_light_chakra_magic_custom_Lore" "Эзалор делится частью своего внутреннего сияния, ускоряя мысль и заклинания союзника." + "DOTA_Tooltip_ability_keeper_of_the_light_chakra_magic_custom_mana_restore" "ВОССТАНОВЛЕНИЕ МАНЫ:" + "DOTA_Tooltip_ability_keeper_of_the_light_chakra_magic_custom_cooldown_reduction" "СОКРАЩЕНИЕ ПЕРЕЗАРЯДКИ:" + "DOTA_Tooltip_ability_keeper_of_the_light_chakra_magic_custom_incoming_damage_reduction_pct" "СНИЖЕНИЕ ВХОДЯЩЕГО УРОНА:" + "DOTA_Tooltip_ability_keeper_of_the_light_chakra_magic_custom_damage_reduction_duration" "ДЛИТЕЛЬНОСТЬ ЗАЩИТЫ:" + + "DOTA_Tooltip_ability_keeper_of_the_light_recall_custom" "Recall" + "DOTA_Tooltip_ability_keeper_of_the_light_recall_custom_Description" "После задержки телепортирует цель к владельцу способности. После телепортации оба героя получают бонус скорости передвижения." + "DOTA_Tooltip_ability_keeper_of_the_light_recall_custom_Lore" "Тех, кто слышит его зов, свет всегда приводит туда, где они нужнее всего." + "DOTA_Tooltip_ability_keeper_of_the_light_recall_custom_teleport_delay" "ЗАДЕРЖКА ПЕРЕД ТЕЛЕПОРТОМ:" + "DOTA_Tooltip_ability_keeper_of_the_light_recall_custom_ally_movespeed_pct" "%БОНУС СКОРОСТИ ПЕРЕДВИЖЕНИЯ:" + + "DOTA_Tooltip_ability_keeper_of_the_light_will_o_wisp_custom" "Will-O-Wisp" + "DOTA_Tooltip_ability_keeper_of_the_light_will_o_wisp_custom_Description" "Призывает блуждающий огонёк. Во время активации он наносит урон врагам в радиусе, дополнительно равный 25% от текущей маны Эзалора, и сильно их замедляет." + "DOTA_Tooltip_ability_keeper_of_the_light_will_o_wisp_custom_Lore" "Старый огонёк Эзалора пляшет на поле боя, уводя врагов в гибельный свет." + "DOTA_Tooltip_ability_keeper_of_the_light_will_o_wisp_custom_radius" "РАДИУС:" + "DOTA_Tooltip_ability_keeper_of_the_light_will_o_wisp_custom_slow_movespeed" "%ЗАМЕДЛЕНИЕ:" + "DOTA_Tooltip_ability_keeper_of_the_light_will_o_wisp_custom_hit_count" "КОЛ-ВО АКТИВАЦИЙ:" + "DOTA_Tooltip_ability_keeper_of_the_light_will_o_wisp_custom_delay" "ИНТЕРВАЛ:" + "DOTA_Tooltip_ability_keeper_of_the_light_will_o_wisp_custom_damage" "УРОН:" + + "DOTA_Tooltip_ability_keeper_of_the_light_radiant_bind_custom" "Solar Bind" + "DOTA_Tooltip_ability_keeper_of_the_light_radiant_bind_custom_Description" "Понижает сопротивление магии и всё сильнее замедляет цель по мере её передвижения. На союзнике эффект работает в обратную сторону: даёт ускорение и магическое сопротивление." + "DOTA_Tooltip_ability_keeper_of_the_light_radiant_bind_custom_Lore" "Путы света одинаково безжалостны к врагам и милосердны к тем, кто сражается рядом с Эзалором." + "DOTA_Tooltip_ability_keeper_of_the_light_radiant_bind_custom_duration" "ДЛИТЕЛЬНОСТЬ:" + "DOTA_Tooltip_ability_keeper_of_the_light_radiant_bind_custom_slow_pct_per_distance" "%ЗАМЕДЛЕНИЕ ЗА КАЖДЫЕ 100 ЕД:" + "DOTA_Tooltip_ability_keeper_of_the_light_radiant_bind_custom_magres_pct" "%ИЗМЕНЕНИЕ СОПРОТИВЛЕНИЯ МАГИИ:" + + "DOTA_Tooltip_Ability_special_bonus_unique_keeper_of_the_light_4_1" "+{s:bonus_radius} к радиусу Will-O-Wisp" + "DOTA_Tooltip_Ability_special_bonus_unique_keeper_of_the_light_1_1" "+{s:bonus_heal_percent}% к лечению Illuminate" + "DOTA_Tooltip_Ability_special_bonus_unique_keeper_of_the_light_3_1" "Chakra Magic восстанавливает 1 заряд способности" + "DOTA_Tooltip_Ability_special_bonus_unique_keeper_of_the_light_4_2" "+{s:bonus_hit_count} к числу активаций Will-O-Wisp" + + // -----------------Silencer-------------------- + "dota_tooltip_ability_silencer_curse_of_the_silent" "Arcane Curse" + "dota_tooltip_ability_silencer_curse_of_the_silent_Description" "Проклинает врагов в области, нанося периодический урон и замедляя их. Урон способности дополнительно усиливается от интеллекта Сайленсера. Каждое применение способности под действием проклятия продлевает его длительность." + "dota_tooltip_ability_silencer_curse_of_the_silent_Lore" "Порча Сайленсера глушит волю и голос одновременно." + "dota_tooltip_ability_silencer_curse_of_the_silent_damage" "УРОН В СЕКУНДУ:" + "dota_tooltip_ability_silencer_curse_of_the_silent_radius" "РАДИУС:" + "dota_tooltip_ability_silencer_curse_of_the_silent_duration" "ДЛИТЕЛЬНОСТЬ:" + "dota_tooltip_ability_silencer_curse_of_the_silent_penalty_duration" "ПРОДЛЕНИЕ ЗА КАСТ:" + "dota_tooltip_ability_silencer_curse_of_the_silent_movespeed" "%ЗАМЕДЛЕНИЕ:" + + "dota_tooltip_ability_glaives_of_wisdom" "Glaives of Wisdom" + "dota_tooltip_ability_glaives_of_wisdom_Description" "усиленная атака, наносящая дополнительный чистый урон от интеллекта владельца." + "dota_tooltip_ability_glaives_of_wisdom_Lore" "Каждое лезвие несёт приговор, написанный разумом." + "dota_tooltip_ability_glaives_of_wisdom_intellect_damage_pct" "%УРОН ОТ ИНТЕЛЛЕКТА:" + + "dota_tooltip_ability_razor_eye_of_the_storm_lua" "Mind Storm" + "dota_tooltip_ability_razor_eye_of_the_storm_lua_Description" "Создаёт шторм над героем, который периодически поражает ближайших врагов, нанося физический урон и снижая броню. Урон шторма дополнительно усиливается от интеллекта Сайленсера." + "dota_tooltip_ability_razor_eye_of_the_storm_lua_Scepter_Description" "Шторм дополнительно поражает %scepter_bonus_targets% целей за каждый интервал." + "dota_tooltip_ability_razor_eye_of_the_storm_lua_Lore" "Безмолвная буря точит доспехи и нервы противника." + "dota_tooltip_ability_razor_eye_of_the_storm_lua_radius" "РАДИУС:" + "dota_tooltip_ability_razor_eye_of_the_storm_lua_duration" "ДЛИТЕЛЬНОСТЬ:" + "dota_tooltip_ability_razor_eye_of_the_storm_lua_strike_interval" "ИНТЕРВАЛ УДАРОВ:" + "dota_tooltip_ability_razor_eye_of_the_storm_lua_armor_reduction" "СНИЖЕНИЕ БРОНИ ЗА УДАР:" + "dota_tooltip_ability_razor_eye_of_the_storm_lua_damage" "УРОН:" + + "dota_tooltip_ability_int" "Int Collection" + "dota_tooltip_ability_int_Description" "Пассивно накапливает стаки интеллекта с течением времени." + "dota_tooltip_ability_int_grow_int" "ИНТЕЛЛЕКТ ЗА СТАК:" + "dota_tooltip_ability_int_stack_interval" "ИНТЕРВАЛ ПОЛУЧЕНИЯ СТАКА:" + + "dota_tooltip_ability_ability_last_word" "Last Word" + "dota_tooltip_ability_ability_last_word_Description" "Накладывает на цель эффект, который при срабатывании накладывает безмолвие и наносит магический урон." + "dota_tooltip_ability_ability_last_word_Shard_Description" "Увеличивает долю интеллекта в уроне Last Word на %shard_bonus_int%." + "dota_tooltip_ability_ability_last_word_Lore" "Последнее слово за тем, кто умеет заставить молчать." + "dota_tooltip_ability_ability_last_word_damage" "БАЗОВЫЙ УРОН:" + "dota_tooltip_ability_ability_last_word_debuff_duration" "ЗАДЕРЖКА СРАБАТЫВАНИЯ:" + "dota_tooltip_ability_ability_last_word_duration" "ДЛИТЕЛЬНОСТЬ САЙЛЕНСА:" + "dota_tooltip_ability_ability_last_word_int" "%ИНТЕЛЛЕКТА В УРОН:" + + "dota_tooltip_ability_ability_global_silence" "Global Silence" + "dota_tooltip_ability_ability_global_silence_Description" "Накладывает безмолвие на всех врагов на карте и увеличивает входящий по ним урон." + "dota_tooltip_ability_ability_global_silence_Lore" "Когда мир затихает, слышен только приговор Сайленсера." + "dota_tooltip_ability_ability_global_silence_tooltip_duration" "ДЛИТЕЛЬНОСТЬ:" + "dota_tooltip_ability_ability_global_silence_icnoming_enemy" "%ВХОДЯЩИЙ УРОН:" + + "dota_tooltip_ability_special_bonus_unique_silencer_storm_interval" "-{s:bonus_strike_interval} к интервалу ударов Mind Storm" + "dota_tooltip_ability_special_bonus_unique_silencer_grow_int" "+{s:bonus_grow_int} интеллекта за стак Int Collection" + "dota_tooltip_ability_special_bonus_unique_silencer_storm_duration" "+{s:bonus_duration} сек к длительности Mind Storm" + "dota_tooltip_ability_special_bonus_unique_silencer_intellect_damage_pct" "+{s:bonus_intellect_damage_pct}% урона от интеллекта Glaives of Wisdom" + "dota_tooltip_ability_special_bonus_unique_silencer_glaives_bounces" "+{s:bonus_bounce_count} рикошета Glaives of Wisdom" + "dota_tooltip_ability_special_bonus_unique_silencer" "+{s:bonus_damage} урона Arcane Curse" + "dota_tooltip_ability_special_bonus_unique_silencer_stack_interval" "-{s:bonus_stack_interval} сек до стака Int Collection" + + // nevermore + "dota_tooltip_ability_nevermore_shadowraze1_custom" "Shadowraze" + "dota_tooltip_ability_nevermore_shadowraze1_custom_Description" "Герой опустошает участок земли прямо перед собой, нанося урон (владельца + количество его маны) всем врагам в зоне действия. Каждое попадание накладывает эффект, который усиливает урон следующих койлов и дополнительно замедляет цель." + "dota_tooltip_ability_nevermore_shadowraze1_custom_shadowraze_range" "ДАЛЬНОСТЬ:" + "dota_tooltip_ability_nevermore_shadowraze1_custom_stack_bonus_damage" "%ДОП. УРОН ЗА СТАК:" + "dota_tooltip_ability_nevermore_shadowraze1_custom_duration" "ДЛИТЕЛЬНОСТЬ ЭФФЕКТА:" + "dota_tooltip_ability_nevermore_shadowraze1_custom_movement_speed_debuff" "%ЗАМЕДЛЕНИЕ ЗА СТАК:" + + "dota_tooltip_ability_nevermore_shadowraze2_custom" "Shadowraze" + "dota_tooltip_ability_nevermore_shadowraze2_custom_Description" "Герой опустошает участок земли прямо перед собой, нанося урон (владельца + количество его маны) всем врагам в зоне действия. Каждое попадание накладывает эффект, который усиливает урон следующих койлов и дополнительно замедляет цель." + "dota_tooltip_ability_nevermore_shadowraze2_custom_shadowraze_range" "ДАЛЬНОСТЬ:" + "dota_tooltip_ability_nevermore_shadowraze2_custom_stack_bonus_damage" "%ДОП. УРОН ЗА СТАК:" + "dota_tooltip_ability_nevermore_shadowraze2_custom_duration" "ДЛИТЕЛЬНОСТЬ ЭФФЕКТА:" + "dota_tooltip_ability_nevermore_shadowraze2_custom_movement_speed_debuff" "%ЗАМЕДЛЕНИЕ ЗА СТАК:" + + "dota_tooltip_ability_nevermore_shadowraze3_custom" "Shadowraze" + "dota_tooltip_ability_nevermore_shadowraze3_custom_Description" "Герой опустошает участок земли прямо перед собой, нанося урон (владельца + количество его маны) всем врагам в зоне действия. Каждое попадание накладывает эффект, который усиливает урон следующих койлов и дополнительно замедляет цель." + "dota_tooltip_ability_nevermore_shadowraze3_custom_shadowraze_range" "ДАЛЬНОСТЬ:" + "dota_tooltip_ability_nevermore_shadowraze3_custom_stack_bonus_damage" "%ДОП. УРОН ЗА СТАК:" + "dota_tooltip_ability_nevermore_shadowraze3_custom_duration" "ДЛИТЕЛЬНОСТЬ ЭФФЕКТА:" + "dota_tooltip_ability_nevermore_shadowraze3_custom_movement_speed_debuff" "%ЗАМЕДЛЕНИЕ ЗА СТАК:" + + "dota_tooltip_ability_nevermore_necromastery_custom" "Necromastery" + "dota_tooltip_ability_nevermore_necromastery_custom_Description" "Пассивно: за каждую накопленную душу герой получает дополнительный урон от атак (%necromastery_damage_per_soul% за душу, не выше %necromastery_max_souls% душ без учёта переполнения). За убийство врага добавляет %souls_per_kill% душу (не выше текущего лимита). Бонус урона не действует, если пассивные способности отключены.\n\nПри смерти герой теряет %necromastery_soul_pct_release%%% текущих душ (остальные сохраняются). Если изучен Requiem of Souls и он не заблокирован, способность автоматически срабатывает.\n\nАктивно при максимуме душ по базовому лимиту можно на %active_duration% с увеличить максимум душ на %active_bonus_soul_cap% (переполнение душ)." + "dota_tooltip_ability_nevermore_necromastery_custom_necromastery_max_souls" "МАКС. ДУШ:" + "dota_tooltip_ability_nevermore_necromastery_custom_necromastery_damage_per_soul" "УРОН ЗА ДУШУ:" + "dota_tooltip_ability_nevermore_necromastery_custom_necromastery_soul_pct_release" "% ДУШ, ТЕРЯЕМЫХ ПРИ СМЕРТИ:" + "dota_tooltip_ability_nevermore_necromastery_custom_souls_per_kill" "ДУШ ЗА УБИЙСТВО:" + "dota_tooltip_ability_nevermore_necromastery_custom_active_duration" "ДЛИТЕЛЬНОСТЬ ПЕРЕПОЛНЕНИЯ:" + "dota_tooltip_ability_nevermore_necromastery_custom_active_bonus_soul_cap" "ПРИРОСТ ЛИМИТА ДУШ:" + + "dota_tooltip_ability_nevermore_dark_lord_custom" "Presence of the Dark Lord" + "dota_tooltip_ability_nevermore_dark_lord_custom_Description" "Аура героя снижает броню врагов в большом радиусе в зависимости от душ. С Aghanim's Shard союзники в ауре (кроме самого героя) получают дополнительную броню." + "dota_tooltip_ability_nevermore_dark_lord_custom_armor_reduction_per_soul" "СНИЖЕНИЕ БРОНИ ЗА 1 ДУШУ:" + "dota_tooltip_ability_nevermore_dark_lord_custom_presence_radius" "РАДИУС:" + "dota_tooltip_ability_nevermore_dark_lord_custom_shard_ally_armor_bonus" "БОНУС БРОНИ СОЮЗНИКАМ ОТ ШАРДА:" + "dota_tooltip_ability_nevermore_dark_lord_custom_Shard_Description" "Также увеличивает броню союзных героев но не самого Shadow fiend." + + "dota_tooltip_ability_nevermore_deadly_strike_custom" "Deadly Strike" + "dota_tooltip_ability_nevermore_deadly_strike_custom_Description" "После каждого успешного удара герой с шансом подготавливает следующий удар как смертельный крит. Критический урон зависит от текущего количества душ." + "dota_tooltip_ability_nevermore_deadly_strike_custom_deadly_strike_chance" "ШАНС СРАБАТЫВАНИЯ:" + "dota_tooltip_ability_nevermore_deadly_strike_custom_deadly_strike_crit_per_soul" "КРИТ. УРОН ЗА 1 ДУШУ:" + + "dota_tooltip_ability_nevermore_requiem_custom" "Requiem of Souls" + "dota_tooltip_ability_nevermore_requiem_custom_Description" "Герой выпускает %requiem_soul_pct_release%%% накопленных душ волнами энергии. Каждая волна наносит урон (%requiem_soul_pct_release%%% урона владельца и его маны) и накладывает страх и ослабление на врагов. При смерти способность срабатывает автоматически." + "dota_tooltip_ability_nevermore_requiem_custom_Scepter_Description" "Каждая выпущенная волна, попавшая по врагу, возвращается обратно к герою, повторно нанося %requiem_damage_pct_scepter%%% от урона волны." + "dota_tooltip_ability_nevermore_requiem_custom_damage" "УРОН:" + "dota_tooltip_ability_nevermore_requiem_custom_requiem_radius" "РАДИУС:" + "dota_tooltip_ability_nevermore_requiem_custom_requiem_reduction_ms" "%ЗАМЕДЛЕНИЕ:" + "dota_tooltip_ability_nevermore_requiem_custom_requiem_reduction_mres" "%СНИЖЕНИЕ СОПРОТИВЛЕНИЯ МАГИИ:" + "dota_tooltip_ability_nevermore_requiem_custom_requiem_slow_duration" "ДЛИТЕЛЬНОСТЬ ЗА ВОЛНУ:" + "dota_tooltip_ability_nevermore_requiem_custom_requiem_slow_duration_max" "МАКС. ДЛИТЕЛЬНОСТЬ:" + + "dota_tooltip_ability_special_bonus_unique_nevermore_custom_1" "+{s:bonus_deadly_strike_chance}% к шансу Deadly Strike" + "dota_tooltip_ability_special_bonus_unique_nevermore_custom_2" "+{s:bonus_deadly_strike_crit_per_soul}% к крит. урону Deadly Strike за душу" + "dota_tooltip_ability_special_bonus_unique_nevermore_custom_3" "+{s:bonus_necromastery_max_souls} к макс. душам Necromastery" + "dota_tooltip_ability_special_bonus_unique_nevermore_custom_4" "+{s:bonus_necromastery_damage_per_soul} к урону Necromastery за душу" + "dota_tooltip_ability_special_bonus_unique_nevermore_custom_5" "+{s:bonus_armor_reduction_per_soul} к снижению брони Presence of the Dark Lord за душу" + "dota_tooltip_ability_special_bonus_unique_nevermore_custom_6" "+{s:bonus_presence_radius} к радиусу Presence of the Dark Lord" + "dota_tooltip_ability_special_bonus_unique_nevermore_custom_7" "+{s:bonus_damage} к урону Requiem of Souls" + "dota_tooltip_ability_special_bonus_unique_nevermore_custom_8" "+{s:bonus_stack_bonus_damage}% к бонусному урону Shadowraze за стак" + + // -----------------Troll Warlord-------------------- + "dota_tooltip_ability_troll_warlord_switch_stance_custom" "Berserker's Rage" + "dota_tooltip_ability_troll_warlord_switch_stance_custom_Description" "Переключает ближний бой: меняет дальность атаки, интервал атак, броню и скорость передвижения. В дальнем бою атаки с шансом %chance_ensnare%%% могут выпустить сеть (перезарядка эффекта %cooldown_ensnare% сек.)." + "dota_tooltip_ability_troll_warlord_switch_stance_custom_Lore" "Ярость — это тоже дисциплина, только громче." + "dota_tooltip_ability_troll_warlord_switch_stance_custom_bonus_armor" "БОНУС БРОНИ:" + "dota_tooltip_ability_troll_warlord_switch_stance_custom_bonus_move_speed" "БОНУС СКОРОСТИ:" + "dota_tooltip_ability_troll_warlord_switch_stance_custom_base_attack_time" "ИНТЕРВАЛ АТАК:" + "dota_tooltip_ability_troll_warlord_switch_stance_custom_chance_ensnare" "%ШАНС СЕТИ (ДАЛЬНИЙ БОЙ):" + "dota_tooltip_ability_troll_warlord_switch_stance_custom_cooldown_ensnare" "ПЕРЕЗАРЯДКА СЕТИ:" + "dota_tooltip_ability_troll_warlord_switch_stance_custom_duration_ensnare" "ДЛИТЕЛЬНОСТЬ СВЯЗЫВАНИЯ:" + "dota_tooltip_ability_troll_warlord_switch_stance_custom_ensnare_speed" "СКОРОСТЬ СЕТИ:" + "dota_tooltip_ability_troll_warlord_switch_stance_custom_split_radius" "РАДИУС ПОИСКА ДОП. ЦЕЛЕЙ:" + "dota_tooltip_ability_troll_warlord_switch_stance_custom_max_split_attack" "МАКС. ДОП. АТАК:" + "dota_tooltip_ability_troll_warlord_switch_stance_custom_Shard_Description" "Дальние атаки поражают до нескольких врагов рядом с целью." + + "dota_tooltip_ability_troll_warlord_active_axes_ranged" "Whirling Axes (Ranged)" + "dota_tooltip_ability_troll_warlord_active_axes_ranged_Description" "Выпускает веер топоров в выбранном направлении, нанося магический урон и дополнительный урон от атаки владельца (%attack_damage_pct%%%), и замедляет врагов на %axe_slow_duration% сек." + "dota_tooltip_ability_troll_warlord_active_axes_ranged_Lore" "Топор летит туда, куда укажет рука." + "dota_tooltip_ability_troll_warlord_active_axes_ranged_axe_damage" "УРОН:" + "dota_tooltip_ability_troll_warlord_active_axes_ranged_attack_damage_pct" "%УРОНА ОТ АТАКИ ВЛАДЕЛЬЦА:" + "dota_tooltip_ability_troll_warlord_active_axes_ranged_axe_slow_duration" "ДЛИТЕЛЬНОСТЬ ЗАМЕДЛЕНИЯ:" + "dota_tooltip_ability_troll_warlord_active_axes_ranged_movement_speed" "%ЗАМЕДЛЕНИЕ:" + "dota_tooltip_ability_troll_warlord_active_axes_ranged_axe_width" "ШИРИНА:" + "dota_tooltip_ability_troll_warlord_active_axes_ranged_axe_range" "ДАЛЬНОСТЬ:" + + "dota_tooltip_ability_troll_warlord_active_axes_melee" "Whirling Axes (Melee)" + "dota_tooltip_ability_troll_warlord_active_axes_melee_Description" "Вращающиеся топоры вокруг героя наносят магический урон и дополнительный урон от атаки владельца (%attack_damage_pct%%%), а также обезоруживают врагов на время." + "dota_tooltip_ability_troll_warlord_active_axes_melee_Lore" "Ближний круг — чужая зона поражения." + "dota_tooltip_ability_troll_warlord_active_axes_melee_damage" "УРОН:" + "dota_tooltip_ability_troll_warlord_active_axes_melee_attack_damage_pct" "%УРОНА ОТ АТАКИ ВЛАДЕЛЬЦА:" + "dota_tooltip_ability_troll_warlord_active_axes_melee_debuff_duration" "ДЛИТЕЛЬНОСТЬ ОБЕЗОРУЖИВАНИЯ:" + "dota_tooltip_ability_troll_warlord_active_axes_melee_hit_radius" "РАДИУС ПОПАДАНИЯ:" + "dota_tooltip_ability_troll_warlord_active_axes_melee_max_range" "МАКС. РАДИУС:" + + "dota_tooltip_ability_troll_warlord_fervor_custom" "Fervor" + "dota_tooltip_ability_troll_warlord_fervor_custom_Description" "Каждое успешное попадание атакой даёт стак скорости атаки (до %max_stacks%), цель не важна. Пока между попаданиями проходит не больше %stack_linger_duration% сек., стаки держатся; если дольше не попадаешь — стаки обнуляются." + "dota_tooltip_ability_troll_warlord_fervor_custom_Lore" "Один враг — один ритм." + "dota_tooltip_ability_troll_warlord_fervor_custom_attack_speed" "СКОРОСТЬ АТАКИ ЗА СТАК:" + "dota_tooltip_ability_troll_warlord_fervor_custom_max_stacks" "МАКС. СТАКОВ:" + "dota_tooltip_ability_troll_warlord_fervor_custom_stack_linger_duration" "СЕКУНД ДО СБРОСА СТАКОВ:" + "dota_tooltip_ability_troll_warlord_fervor_custom_locked_attack_speed" "ПОРОГ СКОРОСТИ АТАКИ:" + "dota_tooltip_ability_troll_warlord_fervor_custom_pct_damage_per_attack_speed" "%УРОНА ЗА ЕД. СКОРОСТИ АТАКИ СВЕРХ ПОРОГА:" + "dota_tooltip_ability_troll_warlord_fervor_custom_Scepter_Description" "Скорость атаки выше порога усиливает урон от атак." + + "dota_tooltip_ability_troll_warlord_battle_trance_custom" "Battle Trance" + "dota_tooltip_ability_troll_warlord_battle_trance_custom_Description" "Вводит героя в боевой транс на %trance_duration% сек.: бонус к скорости атаки, скорости передвижения и %lifesteal%%% вампиризма от атак." + "dota_tooltip_ability_troll_warlord_battle_trance_custom_Lore" "Пусть кровь врагов подпитывает шаг." + "dota_tooltip_ability_troll_warlord_battle_trance_custom_trance_duration" "ДЛИТЕЛЬНОСТЬ:" + "dota_tooltip_ability_troll_warlord_battle_trance_custom_lifesteal" "%ВАМПИРИЗМА:" + "dota_tooltip_ability_troll_warlord_battle_trance_custom_attack_speed" "СКОРОСТЬ АТАКИ:" + "dota_tooltip_ability_troll_warlord_battle_trance_custom_movement_speed" "БОНУС СКОРОСТИ:" + + "dota_tooltip_ability_special_bonus_unique_troll_warlord_1_1" "+{s:bonus_bonus_move_speed} к бонусу скорости от Berserker's Rage" + "dota_tooltip_ability_special_bonus_unique_troll_warlord_1_2" "+{s:bonus_base_attack_time} сек. к модификатору интервала атак от Berserker's Rage" + "dota_tooltip_ability_special_bonus_unique_troll_warlord_2_1" "+{s:bonus_axe_slow_duration}% к длительности замедления от Whirling Axes" + "dota_tooltip_ability_special_bonus_unique_troll_warlord_4_1" "-{s:bonus_AbilityCooldown} сек. перезарядки Battle Trance" + "dota_tooltip_ability_special_bonus_unique_troll_warlord_1_3" "+{s:bonus_bonus_armor} к броне от Berserker's Rage" + "dota_tooltip_ability_special_bonus_unique_troll_warlord_3" "Усиливает магический урон Whirling Axes (дальний и ближний режим)." + "dota_tooltip_ability_special_bonus_unique_troll_warlord_5" "+{s:bonus_attack_speed} к скорости атаки за стак от Fervor" + "dota_tooltip_ability_special_bonus_unique_troll_warlord_1_4" "+{s:bonus_chance_ensnare}% к шансу сети в дальнем бою" + "dota_tooltip_ability_special_bonus_unique_troll_warlord_battle_trance_movespeed" "+{s:bonus_movement_speed} к бонусу скорости от Battle Trance" + + // -----------------Ogre Magi-------------------- + "dota_tooltip_ability_ability_ogre_magi_fireblast_custom" "Fireblast" + "dota_tooltip_ability_ability_ogre_magi_fireblast_custom_Description" "Огненный взрыв в выбранной точке: враги в радиусе %blast_radius% получают %fireblast_damage% магического урона + %max_health_damage_pct%%% от макс. здоровья Огра и оглушение на %stun_duration% сек. Может сработать Multicast." + "dota_tooltip_ability_ability_ogre_magi_fireblast_custom_fireblast_damage" "УРОН:" + "dota_tooltip_ability_ability_ogre_magi_fireblast_custom_max_health_damage_pct" "% МАКС. ЗДОРОВЬЯ ОГРА:" + "dota_tooltip_ability_ability_ogre_magi_fireblast_custom_stun_duration" "ОГЛУШЕНИЕ:" + "dota_tooltip_ability_ability_ogre_magi_fireblast_custom_blast_radius" "РАДИУС ВЗРЫВА:" + "dota_tooltip_ability_ability_ogre_magi_fireblast_custom_Lore" "Два огня — один взрыв. Третий огонь — уже Multicast." + + "dota_tooltip_ability_ability_ogre_magi_ignite_custom" "Ignite" + "dota_tooltip_ability_ability_ogre_magi_ignite_custom_Description" "Бросает горящую смесь: поджигает цель и врагов в радиусе %ignite_radius% на %duration% сек., каждую секунду нанося %burn_damage% магического урона + %max_health_damage_pct%%% от макс. здоровья Огра и замедляя на %slow_movement_speed_pct%%%. Может сработать Multicast." + "dota_tooltip_ability_ability_ogre_magi_ignite_custom_duration" "ДЛИТЕЛЬНОСТЬ:" + "dota_tooltip_ability_ability_ogre_magi_ignite_custom_burn_damage" "УРОН В СЕКУНДУ:" + "dota_tooltip_ability_ability_ogre_magi_ignite_custom_max_health_damage_pct" "% МАКС. ЗДОРОВЬЯ ОГРА В СЕК:" + "dota_tooltip_ability_ability_ogre_magi_ignite_custom_ignite_radius" "РАДИУС СПЛЕША:" + "dota_tooltip_ability_ability_ogre_magi_ignite_custom_Lore" "Слизь горит дольше, чем враг успевает пожалеть." + + "dota_tooltip_ability_ability_ogre_magi_bloodlust_custom" "Bloodlust" + "dota_tooltip_ability_ability_ogre_magi_bloodlust_custom_Description" "На %duration% сек. усиливает союзника: +%bonus_attack_speed% к скорости атаки и +%bonus_movement_speed%%% к скорости передвижения. На себя даёт +%self_bonus% к скорости атаки. С автокастом накладывает на ближайшего союзника без эффекта. Может сработать Multicast." + "dota_tooltip_ability_ability_ogre_magi_bloodlust_custom_bonus_attack_speed" "СКОРОСТЬ АТАКИ (СОЮЗНИК):" + "dota_tooltip_ability_ability_ogre_magi_bloodlust_custom_self_bonus" "СКОРОСТЬ АТАКИ (НА СЕБЯ):" + "dota_tooltip_ability_ability_ogre_magi_bloodlust_custom_bonus_movement_speed" "СКОРОСТЬ ПЕРЕДВИЖЕНИЯ:" + "dota_tooltip_ability_ability_ogre_magi_bloodlust_custom_Lore" "Когда Огр радуется — все бьют быстрее." + + "dota_tooltip_ability_ability_ogre_magi_multicast_custom" "Multicast" + "dota_tooltip_ability_ability_ogre_magi_multicast_custom_Description" "Пассивно: способности Огра могут сработать несколько раз подряд — шанс %chance_2x%%% на двойной каст, на 2 уровне ещё %chance_3x%%% на тройной, на 3 — %chance_4x%%% на четверной. Удача влияет на проверки. Отключается истощением." + "dota_tooltip_ability_ability_ogre_magi_multicast_custom_chance_2x" "ШАНС ×2:" + "dota_tooltip_ability_ability_ogre_magi_multicast_custom_chance_3x" "ШАНС ×3:" + "dota_tooltip_ability_ability_ogre_magi_multicast_custom_chance_4x" "ШАНС ×4:" + "dota_tooltip_ability_ability_ogre_magi_multicast_custom_Lore" "Иногда магия срабатывает столько раз, что сам Огр удивляется." + "dota_tooltip_ability_ability_ogre_magi_multicast_custom_Shard_Description" "Активно: накладывает на союзников в радиусе %shard_radius% стаки огненного щита — их способности тоже могут сработать несколько раз. Длительность %shard_duration% сек." + + "dota_tooltip_ability_ability_ogre_magi_unrefined_fireblast_custom" "Unrefined Fireblast" + "dota_tooltip_ability_ability_ogre_magi_unrefined_fireblast_custom_Description" "Огненный взрыв в точке, как Fireblast: в радиусе %blast_radius% — %blast_damage% магического урона + %max_health_damage_pct%%% от макс. здоровья Огра и оглушение на %stun_duration% сек. Расходует %mana_cost_pct%%% текущей маны." + "dota_tooltip_ability_ability_ogre_magi_unrefined_fireblast_custom_blast_radius" "РАДИУС ВЗРЫВА:" + "dota_tooltip_ability_ability_ogre_magi_unrefined_fireblast_custom_blast_damage" "УРОН:" + "dota_tooltip_ability_ability_ogre_magi_unrefined_fireblast_custom_max_health_damage_pct" "% МАКС. ЗДОРОВЬЯ ОГРА:" + "dota_tooltip_ability_ability_ogre_magi_unrefined_fireblast_custom_mana_cost_pct" "РАСХОД МАНЫ:" + "dota_tooltip_ability_ability_ogre_magi_unrefined_fireblast_custom_Lore" "Когда маны много — бьёт особенно больно." + + "dota_tooltip_ability_ability_ogre_magi_innate_custom" "Dumb Luck" + "dota_tooltip_ability_ability_ogre_magi_innate_custom_Description" "Врождённо: +%luck_per_level% удачи за уровень героя. Отключается истощением." + "dota_tooltip_ability_ability_ogre_magi_innate_custom_luck_per_level" "УДАЧА ЗА УРОВЕНЬ:" + "dota_tooltip_ability_ability_ogre_magi_innate_custom_Lore" "Глупость и удача — родные братья." + + "dota_tooltip_modifier_modifier_ogre_magi_ignite_debuff" "Ignite" + "dota_tooltip_modifier_modifier_ogre_magi_ignite_debuff_Description" "Периодический урон и замедление. Стаки усиливают урон." + + "dota_tooltip_modifier_modifier_ogre_magi_bloodlust_buff" "Bloodlust" + "dota_tooltip_modifier_modifier_ogre_magi_bloodlust_buff_Description" "Повышенная скорость атаки и передвижения." + + "dota_tooltip_modifier_modifier_ogre_magi_multicast_stack" "Fire Shield" + "dota_tooltip_modifier_modifier_ogre_magi_multicast_stack_Description" "Следующие применения способностей могут сработать несколько раз." + + "dota_tooltip_ability_special_bonus_unique_ogre_magi_fireblast_damage" "+{s:bonus_fireblast_damage} к урону Fireblast" + "dota_tooltip_ability_special_bonus_unique_ogre_magi_fireblast_attack_proc" "{s:bonus_attack_proc_chance}% шанс (с учётом удачи): при атаке Fireblast в точке перед Огром" + "dota_tooltip_ability_special_bonus_unique_ogre_magi_ignite_damage" "+{s:bonus_burn_damage} к урону Ignite в секунду" + "dota_tooltip_ability_special_bonus_unique_ogre_magi_bloodlust_as" "+{s:bonus_bonus_attack_speed} к скорости атаки и +{s:bonus_physical_vampirism} вампиризма от Bloodlust" + "dota_tooltip_ability_special_bonus_unique_ogre_magi_ignite_stacks" "Стаки Ignite усиливают периодический урон" + "dota_tooltip_ability_special_bonus_unique_ogre_magi_ignite_on_cast" "20% шанс выпустить Ignite по ближайшему врагу при любом касте" + "dota_tooltip_ability_special_bonus_unique_ogre_magi_ignite_proc_damage" "+30% к физическому урону атак по целям под Ignite" + "dota_tooltip_ability_special_bonus_unique_ogre_magi_luck" "+{s:bonus_luck_per_level} удачи за уровень (врождёнка)" + + // -----------------Spectre-------------------- + "dota_tooltip_ability_ability_spectre_spectral_dagger_custom" "Spectral Dagger" + "dota_tooltip_ability_ability_spectre_spectral_dagger_custom_Description" "Выпускает кинжал в выбранную точку или врага: %damage% магического урона + %health_cost_pct%%% от макс. здоровья Spectre, замедление %slow_pct%%% и след на %buff_persistence% сек. На время эффекта Spectre получает +%bonus_movespeed%%% к скорости." + "dota_tooltip_ability_ability_spectre_spectral_dagger_custom_damage" "УРОН:" + "dota_tooltip_ability_ability_spectre_spectral_dagger_custom_health_cost_pct" "% МАКС. ЗДОРОВЬЯ:" + "dota_tooltip_ability_ability_spectre_spectral_dagger_custom_slow_pct" "ЗАМЕДЛЕНИЕ:" + "dota_tooltip_ability_ability_spectre_spectral_dagger_custom_bonus_movespeed" "СКОРОСТЬ (НА СЕБЯ):" + "dota_tooltip_ability_ability_spectre_spectral_dagger_custom_buff_persistence" "ДЛИТЕЛЬНОСТЬ СЛЕДА:" + "dota_tooltip_ability_ability_spectre_spectral_dagger_custom_Lore" "Тень режет плоть — и память о пути назад." + + "dota_tooltip_ability_ability_spectre_spectral_echo_custom" "Spectral Echo" + "dota_tooltip_ability_ability_spectre_spectral_echo_custom_Description" "Пассивно: с шансом %proc_chance%%% (с учётом удачи) при атаке появляется %shadows_per_proc% бессмертная чёрная тень рядом с целью — после %shadow_attacks% ударов исчезает. Если рядом нет врагов, тень идёт за Spectre, пока не найдёт цель в радиусе %search_radius%. Одновременно не больше %shadows_per_proc% тени. Урон — %illusion_outgoing_damage%%%." + "dota_tooltip_ability_ability_spectre_spectral_echo_custom_proc_chance" "ШАНС:" + "dota_tooltip_ability_ability_spectre_spectral_echo_custom_shadows_per_proc" "ТЕНЕЙ ЗА СРАБАТЫВАНИЕ:" + "dota_tooltip_ability_ability_spectre_spectral_echo_custom_spawn_radius_near_target" "РАДИУС ПОЯВЛЕНИЯ У ЦЕЛИ:" + "dota_tooltip_ability_special_bonus_unique_spectre_dagger_cooldown" "-{s:bonus_AbilityCooldown} сек. перезарядки Spectral Dagger" + "dota_tooltip_ability_special_bonus_unique_spectre_echo_proc_chance" "+{s:bonus_proc_chance}% к шансу Spectral Echo" + "dota_tooltip_ability_special_bonus_unique_spectre_spectral_echo_twin" "+{s:bonus_shadows_per_proc} тень Spectral Echo за срабатывание (всего 2)" + "dota_tooltip_ability_special_bonus_unique_spectre_echo_double_strike" "+{s:bonus_shadow_attacks} удар тени Spectral Echo перед исчезновением (всего 2)" + "dota_tooltip_ability_special_bonus_unique_spectre_dispersion_pct" "+{s:bonus_damage_reflection_pct}% к Dispersion" + "dota_tooltip_ability_special_bonus_unique_spectre_desolate_hp_pct" "+{s:bonus_health_damage_pct}% к чистому урону Desolate от макс. HP" + "dota_tooltip_ability_special_bonus_unique_spectre_echo_shadow_damage" "+{s:bonus_illusion_outgoing_damage}% урона теней Spectral Echo" + "dota_tooltip_ability_special_bonus_unique_spectre_reality_all_haunts" "Reality: Spectral Dagger из каждой иллюзии Haunt, затем обмен с ближайшей" + "dota_tooltip_ability_ability_spectre_spectral_echo_custom_search_radius" "РАДИУС ПОИСКА ЦЕЛИ:" + "dota_tooltip_ability_ability_spectre_spectral_echo_custom_shadow_attacks" "УДАРОВ ДО ИСЧЕЗНОВЕНИЯ:" + "dota_tooltip_ability_ability_spectre_spectral_echo_custom_illusion_outgoing_damage" "УРОН ИЛЛЮЗИИ:" + "dota_tooltip_ability_ability_spectre_spectral_echo_custom_illusion_attack_speed" "СКОРОСТЬ АТАКИ ИЛЛЮЗИИ:" + "dota_tooltip_ability_ability_spectre_spectral_echo_custom_Lore" "Каждый удар отзывается тенью — и она тоже режет." + + "dota_tooltip_ability_ability_spectre_desolate_custom" "Desolate" + "dota_tooltip_ability_ability_spectre_desolate_custom_Description" "Врождённо: каждая атака наносит дополнительный чистый урон в размере %health_damage_pct%%% от макс. здоровья Spectre (1%% на 1 уровне, +0.1%% за уровень врождёнки)." + "dota_tooltip_ability_ability_spectre_desolate_custom_health_damage_pct" "% МАКС. ЗДОРОВЬЯ В УРОН:" + "dota_tooltip_ability_ability_spectre_desolate_custom_Lore" "Тень бьёт с той силой, что таит в себе носитель." + + "dota_tooltip_ability_ability_spectre_dispersion_custom" "Dispersion" + "dota_tooltip_ability_ability_spectre_dispersion_custom_Description" "Пассивно: снижает входящий урон на %damage_reflection_pct%%% и отражает часть полученного урона врагам в радиусе %max_radius% (сильнее вблизи, максимум на дистанции %min_radius%). По целям под следом кинжала отражение усилено." + "dota_tooltip_ability_ability_spectre_dispersion_custom_damage_reflection_pct" "СНИЖЕНИЕ ВХОДЯЩЕГО:" + "dota_tooltip_ability_ability_spectre_dispersion_custom_min_radius" "РАДИУС МАКС. ОТРАЖЕНИЯ:" + "dota_tooltip_ability_ability_spectre_dispersion_custom_max_radius" "РАДИУС ОТРАЖЕНИЯ:" + "dota_tooltip_ability_ability_spectre_dispersion_custom_Lore" "Боль разлетается по всем, кто осмелился приблизиться." + "dota_tooltip_ability_ability_spectre_dispersion_custom_Shard_Description" "Активно: на %activation_duration% сек. усиливает Dispersion — +%activation_bonus_pct%%% к отражению. Перезарядка %activation_cooldown% сек., расход маны %activation_manacost%." + + "dota_tooltip_ability_ability_spectre_haunt_custom" "Haunt" + "dota_tooltip_ability_ability_spectre_haunt_custom_Description" "Создаёт %illusion_count% иллюзий Spectre на %duration% сек. (%illusion_damage_outgoing%%% исходящего урона). Их атаки срабатывают как Spectral Echo. Reality меняет местами с ближайшей иллюзией и выпускает Spectral Dagger по ближайшему врагу." + "dota_tooltip_ability_ability_spectre_haunt_custom_illusion_count" "ЧИСЛО ИЛЛЮЗИЙ:" + "dota_tooltip_ability_ability_spectre_haunt_custom_duration" "ДЛИТЕЛЬНОСТЬ:" + "dota_tooltip_ability_ability_spectre_haunt_custom_Lore" "Тень выходит из тела — и ждёт момента удара." + + "dota_tooltip_ability_ability_spectre_reality_custom" "Reality" + "dota_tooltip_ability_ability_spectre_reality_custom_Description" "Меняется местами с иллюзией Haunt и выпускает Spectral Dagger по ближайшему врагу в радиусе каста кинжала." + + "dota_tooltip_ability_ability_spectre_shadow_step_custom" "Shadow Step" + "dota_tooltip_ability_ability_spectre_shadow_step_custom_Description" "С Aghanim's Scepter: каждая атака на %duration_steal% сек. крадёт %health_steal% макс. здоровья у цели и даёт Spectre %health_bonus_self% за стак (у иллюзий эффект снижен на %illusion_decrease%%%)." + "dota_tooltip_ability_ability_spectre_shadow_step_custom_health_steal" "КРАЖА ЗДОРОВЬЯ ЗА СТАК:" + "dota_tooltip_ability_ability_spectre_shadow_step_custom_health_bonus_self" "БОНУС ЗДОРОВЬЯ ЗА СТАК:" + "dota_tooltip_ability_ability_spectre_shadow_step_custom_duration_steal" "ДЛИТЕЛЬНОСТЬ СТАКА:" + "dota_tooltip_ability_ability_spectre_shadow_step_custom_Lore" "Каждый удар — кусочек чужой жизни в твоих руках." + + "dota_tooltip_modifier_modifier_spectre_dagger_debuff_custom" "Spectral Dagger" + "dota_tooltip_modifier_modifier_spectre_dagger_debuff_custom_Description" "Замедление и след — усиливает Desolate и Dispersion." + "dota_tooltip_modifier_modifier_spectre_dagger_buff_custom" "Spectral Dagger" + "dota_tooltip_modifier_modifier_spectre_dagger_buff_custom_Description" "Повышенная скорость передвижения." + "dota_tooltip_modifier_modifier_spectre_dispersion_custom_boosted" "Dispersion" + "dota_tooltip_modifier_modifier_spectre_dispersion_custom_boosted_Description" "Усиленное отражение урона." + "dota_tooltip_modifier_modifier_spectre_stack_buff" "Shadow Step" + "dota_tooltip_modifier_modifier_spectre_stack_buff_Description" "Дополнительное здоровье за стак." + "dota_tooltip_modifier_modifier_spectre_stack_debuff" "Shadow Step" + "dota_tooltip_modifier_modifier_spectre_stack_debuff_Description" "Потеря макс. здоровья за стак." + + // -----------------Bristleback-------------------- + "dota_tooltip_ability_bristleback_viscous_nasal_goo_custom" "Viscous Nasal Goo" + "dota_tooltip_ability_bristleback_viscous_nasal_goo_custom_Description" "Сморкается на врага: в радиусе %radius% снижает базовую броню на %base_armor_pct%%% + %armor_per_stack_pct%%% за стак и скорость на %goo_duration% сек. Стаки суммируются, длительность обновляется." + "dota_tooltip_ability_bristleback_viscous_nasal_goo_custom_Lore" "Простуда из снегов — оружие Ригварла." + "dota_tooltip_ability_bristleback_viscous_nasal_goo_custom_radius" "РАДИУС:" + "dota_tooltip_ability_bristleback_viscous_nasal_goo_custom_base_armor_pct" "%СНИЖЕНИЕ БАЗОВОЙ БРОНИ:" + "dota_tooltip_ability_bristleback_viscous_nasal_goo_custom_armor_per_stack_pct" "%СНИЖЕНИЕ БРОНИ ЗА СТАК:" + "dota_tooltip_ability_bristleback_viscous_nasal_goo_custom_base_move_slow" "%БАЗОВОЕ ЗАМЕДЛЕНИЕ:" + "dota_tooltip_ability_bristleback_viscous_nasal_goo_custom_move_slow_per_stack" "%ЗАМЕДЛЕНИЕ ЗА СТАК:" + + "dota_tooltip_ability_bristleback_quill_spray_custom" "Quill Spray" + "dota_tooltip_ability_bristleback_quill_spray_custom_Description" "Иглы во все стороны: физический урон плюс %attack_damage_bonus_pct%%% урона от атаки владельца по каждой цели. Бонус за прошлые попадания за %quill_stack_duration% сек." + "dota_tooltip_ability_bristleback_quill_spray_custom_Lore" "Честь вышибалы — острый вопрос. Как и шипы." + "dota_tooltip_ability_bristleback_quill_spray_custom_Note0" "Урон не режется блоком урона." + "dota_tooltip_ability_bristleback_quill_spray_custom_attack_damage_bonus_pct" "%ДОЛЯ УРОНА ОТ АТАКИ:" + "dota_tooltip_ability_bristleback_quill_spray_custom_radius" "РАДИУС:" + "dota_tooltip_ability_bristleback_quill_spray_custom_quill_base_damage" "БАЗОВЫЙ УРОН:" + "dota_tooltip_ability_bristleback_quill_spray_custom_quill_stack_damage" "ДОП. УРОН ЗА СТАК:" + "dota_tooltip_ability_bristleback_quill_spray_custom_quill_stack_duration" "ДЛИТЕЛЬНОСТЬ СТАКА:" + "dota_tooltip_ability_bristleback_quill_spray_custom_max_damage" "МАКС. УРОН ЗА УДАР:" + "dota_tooltip_ability_bristleback_quill_spray_custom_scepter_description" "Больше урона и длительности стаков" + + "dota_tooltip_ability_bristleback_bristleback_custom" "Bristleback" + "dota_tooltip_ability_bristleback_bristleback_custom_Description" "Меньше урона со спины и с боков. После %quill_release_threshold% урона со спины — Quill Spray текущего уровня." + "dota_tooltip_ability_bristleback_bristleback_custom_Lore" "Иногда выгодно повернуться спиной." + "dota_tooltip_ability_bristleback_bristleback_custom_Note0" "Спина: 70° от центра спины." + "dota_tooltip_ability_bristleback_bristleback_custom_Note1" "Бока: 110° от центра спины." + "dota_tooltip_ability_bristleback_bristleback_custom_side_damage_reduction" "%СНИЖЕНИЕ С БОКОВ:" + "dota_tooltip_ability_bristleback_bristleback_custom_back_damage_reduction" "%СНИЖЕНИЕ СО СПИНЫ:" + "dota_tooltip_ability_bristleback_bristleback_custom_quill_release_threshold" "ПОРОГ УРОНА СО СПИНЫ:" + "dota_tooltip_ability_bristleback_bristleback_custom_goo_radius" "РАДИУС БРЫЗГ:" + "dota_tooltip_ability_bristleback_bristleback_custom_scepter_description" "При большем уроне со спины выпускает несколько Quill Spray подряд" + + "dota_tooltip_ability_bristleback_hairball_custom" "Hairball" + "dota_tooltip_ability_bristleback_hairball_custom_Description" "Комок игл в точку: при приземлении — Goo и несколько Quill Spray по врагам рядом." + "dota_tooltip_ability_bristleback_hairball_custom_Lore" "Всё тело — оружие, даже случайно проглоченное." + "dota_tooltip_ability_bristleback_hairball_custom_radius" "РАДИУС:" + "dota_tooltip_ability_bristleback_hairball_custom_quill_stacks" "ЧИСЛО QUILL SPRAY:" + "dota_tooltip_ability_bristleback_hairball_custom_goo_stacks" "ПРИМЕНЕНИЙ GOO:" + + "dota_tooltip_ability_bristleback_warpath" "Warpath" + "dota_tooltip_ability_bristleback_warpath_Description" "Врождённая. За полученный урон получает стак (длительность и максимум растут с уровнем героя). Каждый стак даёт %damage_per_stack_base% + %damage_per_stack_per_hero_level% урона за уровень героя и %move_speed_per_stack_base% + %move_speed_per_stack_per_hero_level% скорости передвижения за уровень героя." + "dota_tooltip_ability_bristleback_warpath_damage_per_stack_base" "БАЗОВЫЙ УРОН ЗА СТАК:" + "dota_tooltip_ability_bristleback_warpath_damage_per_stack_per_hero_level" "УРОН ЗА СТАК / УР. ГЕРОЯ:" + "dota_tooltip_ability_bristleback_warpath_move_speed_per_stack_base" "БАЗ. СКОРОСТЬ ЗА СТАК:" + "dota_tooltip_ability_bristleback_warpath_move_speed_per_stack_per_hero_level" "СКОРОСТЬ ЗА СТАК / УР. ГЕРОЯ:" + "dota_tooltip_ability_bristleback_warpath_max_stacks_base" "БАЗ. МАКС. СТАКОВ:" + "dota_tooltip_ability_bristleback_warpath_max_stacks_per_hero_level" "МАКС. СТАКОВ / УР. ГЕРОЯ:" + "dota_tooltip_ability_bristleback_warpath_stack_duration_base" "БАЗ. ДЛИТЕЛЬНОСТЬ СТАКА:" + "dota_tooltip_ability_bristleback_warpath_stack_duration_per_hero_level" "ДЛИТ. СТАКА / УР. ГЕРОЯ:" + + "dota_tooltip_ability_bristleback_rage_fortitude_custom" "Battle Fortitude" + "dota_tooltip_ability_bristleback_rage_fortitude_custom_Description" "При ярости выше %rage_threshold% получает иммунитет к отрицательным эффектам, дополнительную броню и магическое сопротивление." + "dota_tooltip_ability_bristleback_rage_fortitude_custom_Lore" "Когда кровь Ригварла закипает, шипы и шкура становятся почти непробиваемыми." + "dota_tooltip_ability_bristleback_rage_fortitude_custom_rage_threshold" "ПОРОГ ЯРОСТИ:" + "dota_tooltip_ability_bristleback_rage_fortitude_custom_bonus_armor" "ДОП. БРОНЯ:" + "dota_tooltip_ability_bristleback_rage_fortitude_custom_bonus_magic_resist" "ДОП. МАГ. СОПР:" + "dota_tooltip_modifier_bristleback_rage_fortitude_buff" "Battle Fortitude" + "dota_tooltip_modifier_bristleback_rage_fortitude_buff_Description" "Иммунитет к дебаффам, дополнительная броня и магическое сопротивление." + + "dota_tooltip_ability_special_bonus_unique_bristleback_custom_3" "+{s:bonus_quill_base_damage} / +{s:bonus_max_damage} к базовому/макс. урону Quill Spray" + "dota_tooltip_ability_special_bonus_unique_bristleback_custom_5" "+{s:bonus_quill_stack_damage} к урону за стак Quill Spray" + "dota_tooltip_ability_special_bonus_unique_bristleback_custom_6" "+{s:bonus_radius} к радиусу Viscous Nasal Goo" + "dota_tooltip_ability_special_bonus_unique_bristleback_3" "+{s:bonus_damage_per_stack} к урону Warpath за стак" + + // -----------------Legion Commander-------------------- + "dota_tooltip_ability_ability_legion_commander_overwhelming_odds_custom" "Overwhelming Odds" + "dota_tooltip_ability_ability_legion_commander_overwhelming_odds_custom_Description" "Тресдин наносит по выбранной области магический урон: чем больше вражеских героев и крипов попало в зону, тем выше суммарный урон. Поражённые враги замедлены; за каждого задетого врага Тресдин получает броню; на короткое время у неё растут скорость атаки и передвижение." + "dota_tooltip_ability_ability_legion_commander_overwhelming_odds_custom_Lore" "Чем плотнее строй врагов, тем яснее путь к победе." + "dota_tooltip_ability_ability_legion_commander_overwhelming_odds_custom_radius" "РАДИУС:" + "dota_tooltip_ability_ability_legion_commander_overwhelming_odds_custom_damage_base" "БАЗОВЫЙ УРОН:" + "dota_tooltip_ability_ability_legion_commander_overwhelming_odds_custom_damage_per_enemy" "ДОП. УРОН ЗА ЮНИТА:" + "dota_tooltip_ability_ability_legion_commander_overwhelming_odds_custom_armor_buff_duration" "ДЛИТЕЛЬНОСТЬ БРОНИ:" + "dota_tooltip_ability_ability_legion_commander_overwhelming_odds_custom_armor_per_enemy" "БРОНИ ЗА ВРАГА:" + "dota_tooltip_ability_ability_legion_commander_overwhelming_odds_custom_debuff_duration" "ДЛИТЕЛЬНОСТЬ ЗАМЕДЛЕНИЯ:" + "dota_tooltip_ability_ability_legion_commander_overwhelming_odds_custom_enemy_movespeed_slow" "%ЗАМЕДЛЕНИЕ:" + "dota_tooltip_ability_ability_legion_commander_overwhelming_odds_custom_self_buff_duration" "ДЛИТЕЛЬНОСТЬ УСИЛЕНИЯ:" + "dota_tooltip_ability_ability_legion_commander_overwhelming_odds_custom_self_bonus_attack_speed" "ДОП. СКОРОСТЬ АТАКИ (СЕБЯ):" + "dota_tooltip_ability_ability_legion_commander_overwhelming_odds_custom_self_bonus_movespeed" "ДОП. СКОРОСТЬ (СЕБЯ):" + + "dota_tooltip_ability_ability_legion_commander_press_the_attack_custom" "Press the Attack" + "dota_tooltip_ability_ability_legion_commander_press_the_attack_custom_Description" "Тресдин накладывает эффект на союзного героя или на себя: выполняется сильный развеиватель, затем на %duration% сек. повышаются регенерация здоровья, скорость атаки и передвижение цели." + "dota_tooltip_ability_ability_legion_commander_press_the_attack_custom_Lore" "Один удар по плечу — и союзник снова в строю." + "dota_tooltip_ability_ability_legion_commander_press_the_attack_custom_duration" "ДЛИТЕЛЬНОСТЬ:" + "dota_tooltip_ability_ability_legion_commander_press_the_attack_custom_attack_speed_bonus" "ДОП. СКОРОСТЬ АТАКИ:" + "dota_tooltip_ability_ability_legion_commander_press_the_attack_custom_hp_regen" "РЕГЕНЕРАЦИЯ ЗДОРОВЬЯ:" + "dota_tooltip_ability_ability_legion_commander_press_the_attack_custom_movespeed_bonus" "ДОП. СКОРОСТЬ ПЕРЕДВИЖЕНИЯ:" + + "dota_tooltip_ability_ability_legion_commander_moment_of_courage_custom" "Moment of Courage" + "dota_tooltip_ability_ability_legion_commander_moment_of_courage_custom_Description" "Пассивно. При вражеской автоатаке по Тресдин с шансом %trigger_chance_pct%%% она мгновенно контратакует, восстанавливает здоровье в размере части урона контрудара и ненадолго ускоряется. Повторная проверка срабатывания не чаще одного раза в %proc_cooldown% сек." + "dota_tooltip_ability_ability_legion_commander_moment_of_courage_custom_Lore" "Страх отступает раньше, чем меч успевает ответить — но не у Тресдина." + "dota_tooltip_ability_ability_legion_commander_moment_of_courage_custom_trigger_chance_pct" "%ШАНС СРАБАТЫВАНИЯ:" + "dota_tooltip_ability_ability_legion_commander_moment_of_courage_custom_proc_cooldown" "ИНТЕРВАЛ ПРОВЕРКИ:" + "dota_tooltip_ability_ability_legion_commander_moment_of_courage_custom_buff_duration" "ДЛИТЕЛЬНОСТЬ УСКОРЕНИЯ:" + "dota_tooltip_ability_ability_legion_commander_moment_of_courage_custom_buff_bonus_movespeed" "ДОП. СКОРОСТЬ ПОСЛЕ ПРОКА:" + "dota_tooltip_ability_ability_legion_commander_moment_of_courage_custom_counter_lifesteal_pct" "%ВОССТАНОВЛЕНИЯ ОТ УРОНА КОНТРУДАРА:" + "dota_tooltip_ability_ability_legion_commander_moment_of_courage_custom_attack_count" "ЧИСЛО МГНОВЕННЫХ АТАК:" + + "dota_tooltip_ability_ability_legion_commander_duel_custom" "Duel" + "dota_tooltip_ability_ability_legion_commander_duel_custom_Description" "Тресдин вызывает врага на дуэль на %duration% сек.: оба участника не могут использовать способности и активные предметы, но получают дополнительную скорость атаки; на вражеских героях эти ограничения длятся короче из‑за сопротивления эффектам. Победа навсегда увеличивает урон от атаки (больше за героя, меньше за крипа; суммарный бонус не выше %max_stack_damage%) и даёт %reward_random_stat% к случайному атрибуту. Если расстояние между дуэлянами превышает %victory_range%, дуэль обрывается без награды." + "dota_tooltip_ability_ability_legion_commander_duel_custom_Lore" "Славу не оспаривают — с ней сражаются." + "dota_tooltip_ability_ability_legion_commander_duel_custom_duration" "ДЛИТЕЛЬНОСТЬ:" + "dota_tooltip_ability_ability_legion_commander_duel_custom_victory_range" "РАССТОЯНИЕ РАЗРЫВА:" + "dota_tooltip_ability_ability_legion_commander_duel_custom_reward_damage" "БОНУС УРОНА ЗА ПОБЕДУ НАД ГЕРОЕМ:" + "dota_tooltip_ability_ability_legion_commander_duel_custom_reward_damage_creep" "БОНУС УРОНА ЗА ПОБЕДУ НАД КРИПОМ:" + "dota_tooltip_ability_ability_legion_commander_duel_custom_reward_random_stat" "СЛУЧАЙНЫЙ АТРИБУТ ЗА ПОБЕДУ:" + "dota_tooltip_ability_ability_legion_commander_duel_custom_max_stack_damage" "МАКС. СУММА БОНУСА:" + "dota_tooltip_ability_ability_legion_commander_duel_custom_bonus_attack_speed" "ДОП. СКОРОСТЬ АТАКИ:" + "dota_tooltip_ability_ability_legion_commander_duel_custom_scepter_max_charges" "ЧИСЛО ЗАРЯДОВ:" + "dota_tooltip_ability_ability_legion_commander_duel_custom_scepter_cooldown_reduction" "СОКРАЩЕНИЕ ПЕРЕЗАРЯДКИ:" + "dota_tooltip_ability_ability_legion_commander_duel_custom_scepter_magic_resist" "СОПРОТИВЛЕНИЕ МАГИИ:" + "dota_tooltip_ability_ability_legion_commander_duel_custom_Scepter_Description" "Увеличивает максимум зарядов Duel до %scepter_max_charges%, сокращает перезарядку на %scepter_cooldown_reduction% сек. и на время действия дуэли даёт Тресдин %scepter_magic_resist%%% сопротивления магии." + "dota_tooltip_ability_ability_legion_commander_duel_custom_Shard_Description" "Пока для Тресдин активен Duel, каждые %shard_overwhelming_interval% сек. у неё под ногами срабатывает Overwhelming Odds с силой текущего уровня навыка. На эти срабатывания не тратится мана и не накладывается перезарядка Overwhelming Odds; сама способность должна быть изучена хотя бы на 1 уровень." + "dota_tooltip_ability_ability_legion_commander_duel_custom_shard_overwhelming_interval" "ПЕРИОД ВСПЛЕШКИ:" + "dota_tooltip_ability_ability_legion_commander_duel_custom_Note0" "Накопленный бонус урона от побед в дуэлях сохраняется до конца матча." + + "dota_tooltip_ability_special_bonus_unique_legion_commander_odds_radius" "+70 к радиусу Overwhelming Odds" + "dota_tooltip_ability_special_bonus_unique_legion_commander_odds_damage" "+60 к базовому урону Overwhelming Odds" + "dota_tooltip_ability_special_bonus_unique_legion_commander_pta_duration" "+2 сек. к Press the Attack" + "dota_tooltip_ability_special_bonus_unique_legion_commander_pta_regen" "+12 к регенерации от Press the Attack" + "dota_tooltip_ability_special_bonus_unique_legion_commander_moc_chance" "+10% к шансу Moment of Courage" + "dota_tooltip_ability_special_bonus_unique_legion_commander_moc_lifesteal" "+25% к восстановлению от контрудара" + "dota_tooltip_ability_special_bonus_unique_legion_commander_duel_duration" "+2 сек. к Duel" + "dota_tooltip_ability_special_bonus_unique_legion_commander_duel_reward" "+10 к бонусному урону Duel за победу над героем" + + // -----------------Queen of Pain-------------------- + + "DOTA_Tooltip_ability_queenofpain_shadow_strike_custom" "Shadow Strike" + "DOTA_Tooltip_ability_queenofpain_shadow_strike_custom_Description" "Бросает кинжал по врагам в радиусе %radius% вокруг цели: при попадании наносит магический урон и отравляет на %duration% сек. Жертва медленнее передвигается и каждые %damage_interval% сек. получает магический урон от яда. К урону первого удара и каждого тика яда прибавляется доп. урон, равный %mana_damage_from_current_pct%%% от текущей маны." + "DOTA_Tooltip_ability_queenofpain_shadow_strike_custom_Scepter_Description" "Когда эффект этой способности на враге заканчивается или обновляется, он издаёт крик от способности Scream of Pain." + "DOTA_Tooltip_ability_queenofpain_shadow_strike_custom_Lore" "Акаша обожает растягивать страдания жертвы, пронзив её отравленным кинжалом." + "DOTA_Tooltip_ability_queenofpain_shadow_strike_custom_movement_slow" "%ЗАМЕДЛЕНИЕ ПЕРЕДВИЖЕНИЯ:" + "DOTA_Tooltip_ability_queenofpain_shadow_strike_custom_strike_damage" "УРОН ОТ ПОПАДАНИЯ:" + "DOTA_Tooltip_ability_queenofpain_shadow_strike_custom_duration_damage" "ПЕРИОДИЧЕСКИЙ УРОН:" + "DOTA_Tooltip_ability_queenofpain_shadow_strike_custom_duration" "ДЛИТЕЛЬНОСТЬ:" + "DOTA_Tooltip_ability_queenofpain_shadow_strike_custom_damage_interval" "ИНТЕРВАЛ:" + "DOTA_Tooltip_ability_queenofpain_shadow_strike_custom_radius" "РАДИУС:" + "DOTA_Tooltip_ability_queenofpain_shadow_strike_custom_mana_damage_from_current_pct" "% МАНЫ В ДОП. УРОН:" + + "DOTA_Tooltip_ability_queenofpain_blink_custom" "Blink" + "DOTA_Tooltip_ability_queenofpain_blink_custom_Description" "Перемещение на короткую дистанцию: в начальной и конечной точке происходит звуковой удар, нанося магический урон врагам в радиусе %radius% и накладывая безмолвие. К урону в каждой зоне прибавляется доп. урон, равный %mana_damage_from_current_pct%%% от текущей маны." + "DOTA_Tooltip_ability_queenofpain_blink_custom_Lore" "Негласная королева сделала себе имя, не оставляя без боли ни одного подданного." + "DOTA_Tooltip_ability_queenofpain_blink_custom_Note0" "Позволяет сбить с героя многие направленные в него снаряды." + "DOTA_Tooltip_ability_queenofpain_blink_custom_damage" "УРОН:" + "DOTA_Tooltip_ability_queenofpain_blink_custom_duration" "ДЛИТЕЛЬНОСТЬ БЕЗМОЛВИЯ:" + "DOTA_Tooltip_ability_queenofpain_blink_custom_radius" "РАДИУС:" + "DOTA_Tooltip_ability_queenofpain_blink_custom_cast_range" "ДАЛЬНОСТЬ:" + "DOTA_Tooltip_ability_queenofpain_blink_custom_mana_damage_from_current_pct" "% МАНЫ В ДОП. УРОН:" + + "DOTA_Tooltip_ability_queenofpain_scream_of_pain_custom" "Scream of Pain" + "DOTA_Tooltip_ability_queenofpain_scream_of_pain_custom_Description" "Герой издаёт пронзительный вопль, нанося магический урон всем врагам в радиусе %radius%. К урону прибавляется доп. урон, равный %mana_damage_from_current_pct%%% от текущей маны." + "DOTA_Tooltip_ability_queenofpain_scream_of_pain_custom_Shard_Description" "Враги, задетые волной на %shard_mark_duration% сек. получают уязвимость: им наносится на %shard_incoming_damage_pct%%% больше урона. Эффект может быть развеян." + "DOTA_Tooltip_ability_queenofpain_scream_of_pain_custom_Lore" "Акаша одурманивает противников своим сладострастным голосом, когда крадёт их души." + "DOTA_Tooltip_ability_queenofpain_scream_of_pain_custom_Note0" "Наносит урон невидимым врагам." + "DOTA_Tooltip_ability_queenofpain_scream_of_pain_custom_Note1" "Снаряд способности нельзя сбить с цели." + "DOTA_Tooltip_ability_queenofpain_scream_of_pain_custom_radius" "РАДИУС:" + "DOTA_Tooltip_ability_queenofpain_scream_of_pain_custom_damage" "УРОН:" + "DOTA_Tooltip_ability_queenofpain_scream_of_pain_custom_shard_mark_duration" "ДЛИТЕЛЬНОСТЬ УЯЗВИМОСТИ:" + "DOTA_Tooltip_ability_queenofpain_scream_of_pain_custom_shard_incoming_damage_pct" "ДОП. ВХОДЯЩИЙ УРОН (%):" + "DOTA_Tooltip_ability_queenofpain_scream_of_pain_custom_mana_damage_from_current_pct" "% МАНЫ В ДОП. УРОН:" + + "DOTA_Tooltip_Ability_special_bonus_unique_queen_of_pain_2_1" "+{s:bonus_damage} к урону от Blink" + "DOTA_Tooltip_Ability_special_bonus_unique_queen_of_pain_8" "+{s:bonus_mitigation_pct_per_level}% снижения урона по себе от Masochistic Agony " + + "DOTA_Tooltip_ability_queenofpain_agony_innate" "Masochistic Agony" + "DOTA_Tooltip_ability_queenofpain_agony_innate_Description" "Урон по врагам вашими способностями оборачивается против вас: вы получаете чистый урон в размере %self_damage_pct_per_level%%% от фактического урона по цели за каждый ваш уровень. Даёт +%spell_amp_bonus%%% к усилению заклинаний. Также дополнительно даёт +%spell_amp_per_missing_hp_pct%%% к усилению заклинаний за каждый 1% недостающего здоровья. После убийства любого юнита восстанавливает 5% от максимального здоровья." + "DOTA_Tooltip_ability_queenofpain_agony_innate_Lore" "Наслаждение врага болью не отменяет цену — но Акаша готова платить, лишь бы магия резала глубже." + "DOTA_Tooltip_ability_queenofpain_agony_innate_self_damage_pct_per_level" "%ДОЛЯ УРОНА ПО СЕБЕ ЗА УРОВЕНЬ:" + "DOTA_Tooltip_ability_queenofpain_agony_innate_spell_amp_bonus" "%УСИЛЕНИЕ ЗАКЛИНАНИЙ:" + "DOTA_Tooltip_ability_queenofpain_agony_innate_spell_amp_per_missing_hp_pct" "%УСИЛЕНИЕ ЗАКЛИНАНИЙ ЗА 1% НЕДОСТАЮЩЕГО ЗДОРОВЬЯ:" + + // -----------------Skywrath Mage-------------------- + + "DOTA_Tooltip_ability_skywrath_mage_arcane_bolt_custom" "Arcane Bolt" + "DOTA_Tooltip_ability_skywrath_mage_arcane_bolt_custom_Description" "Запускает в цель медленно летящий сгусток магии, который наносит %bolt_damage% магического урона и дополнительно %int_multiplier% от интеллекта героя." + "DOTA_Tooltip_ability_skywrath_mage_arcane_bolt_custom_Shard_Description" "Даёт всем активным способностям Skywrath Mage по +1 заряду." + "DOTA_Tooltip_ability_skywrath_mage_arcane_bolt_custom_Lore" "Служители Небесного Гнева верят: одна безупречная формула всегда бьёт точнее любой грубой силы." + "DOTA_Tooltip_ability_skywrath_mage_arcane_bolt_custom_bolt_damage" "БАЗОВЫЙ УРОН:" + "DOTA_Tooltip_ability_skywrath_mage_arcane_bolt_custom_int_multiplier" "МНОЖИТЕЛЬ ИНТЕЛЛЕКТА:" + "DOTA_Tooltip_ability_skywrath_mage_arcane_bolt_custom_bolt_speed" "СКОРОСТЬ СНАРЯДА:" + "DOTA_Tooltip_ability_skywrath_mage_arcane_bolt_custom_bolt_vision" "РАДИУС ОБЗОРА:" + + "DOTA_Tooltip_ability_skywrath_mage_concussive_shot_custom" "Concussive Shot" + "DOTA_Tooltip_ability_skywrath_mage_concussive_shot_custom_Description" "Выпускает самонаводящийся импульс в ближайшего врага в радиусе %launch_radius%. При взрыве в зоне %slow_radius% наносит %damage% магического урона и замедляет на %slow_duration% сек." + "DOTA_Tooltip_ability_skywrath_mage_concussive_shot_custom_Shard_Description" "Даёт всем активным способностям Skywrath Mage по +1 заряду." + "DOTA_Tooltip_ability_skywrath_mage_concussive_shot_custom_Lore" "Воздушные потоки Обители Высших превращаются в приговор, когда их направляет воля Драгонуса." + "DOTA_Tooltip_ability_skywrath_mage_concussive_shot_custom_launch_radius" "РАДИУС ПОИСКА ЦЕЛИ:" + "DOTA_Tooltip_ability_skywrath_mage_concussive_shot_custom_slow_radius" "РАДИУС ВЗРЫВА:" + "DOTA_Tooltip_ability_skywrath_mage_concussive_shot_custom_damage" "УРОН:" + "DOTA_Tooltip_ability_skywrath_mage_concussive_shot_custom_slow_duration" "ДЛИТЕЛЬНОСТЬ ЗАМЕДЛЕНИЯ:" + "DOTA_Tooltip_ability_skywrath_mage_concussive_shot_custom_slow" "%ЗАМЕДЛЕНИЕ:" + "DOTA_Tooltip_ability_skywrath_mage_concussive_shot_custom_int_multiplier" "МНОЖИТЕЛЬ ИНТЕЛЛЕКТА:" + + "DOTA_Tooltip_ability_skywrath_mage_ancient_seal_custom" "Ancient Seal" + "DOTA_Tooltip_ability_skywrath_mage_ancient_seal_custom_Description" "Поражает всех врагов в радиусе %radius% вокруг цели: накладывает безмолвие на %seal_duration% сек. и снижает сопротивление магии на %resist_debuff%%%." + "DOTA_Tooltip_ability_skywrath_mage_ancient_seal_custom_Shard_Description" "Даёт всем активным способностям Skywrath Mage по +1 заряду." + "DOTA_Tooltip_ability_skywrath_mage_ancient_seal_custom_Lore" "Печать, высеченная в старших рунах, гасит чужое колдовство быстрее, чем рождается заклинание." + "DOTA_Tooltip_ability_skywrath_mage_ancient_seal_custom_seal_duration" "ДЛИТЕЛЬНОСТЬ:" + "DOTA_Tooltip_ability_skywrath_mage_ancient_seal_custom_resist_debuff" "%СНИЖЕНИЕ СОПРОТИВЛЕНИЯ МАГИИ:" + "DOTA_Tooltip_ability_skywrath_mage_ancient_seal_custom_radius" "РАДИУС:" + "DOTA_Tooltip_ability_skywrath_mage_ancient_seal_custom_int_multiplier" "МНОЖИТЕЛЬ ИНТЕЛЛЕКТА:" + + "DOTA_Tooltip_ability_skywrath_mage_mystic_flare_custom" "Mystic Flare" + "DOTA_Tooltip_ability_skywrath_mage_mystic_flare_custom_Description" "Создаёт в выбранной точке поле на %duration% сек., которое наносит магический урон всем врагам в области." + "DOTA_Tooltip_ability_skywrath_mage_mystic_flare_custom_Shard_Description" "Даёт всем активным способностям Skywrath Mage по +1 заряду." + "DOTA_Tooltip_ability_skywrath_mage_mystic_flare_custom_Lore" "Там, где вспыхивает небесный разлом, сам воздух становится клинком из чистой арканы." + "DOTA_Tooltip_ability_skywrath_mage_mystic_flare_custom_radius" "РАДИУС:" + "DOTA_Tooltip_ability_skywrath_mage_mystic_flare_custom_duration" "ДЛИТЕЛЬНОСТЬ:" + "DOTA_Tooltip_ability_skywrath_mage_mystic_flare_custom_damage" "ОБЩИЙ УРОН:" + "DOTA_Tooltip_ability_skywrath_mage_mystic_flare_custom_int_multiplier" "МНОЖИТЕЛЬ ИНТЕЛЛЕКТА:" + "DOTA_Tooltip_ability_skywrath_mage_mystic_flare_custom_damage_interval" "ИНТЕРВАЛ СРАБАТЫВАНИЯ:" + + "DOTA_Tooltip_ability_skywrath_mage_innate_custom" "Ruin And Restoration" + "DOTA_Tooltip_ability_skywrath_mage_innate_custom_Description" "После применения любой способности герой выпускает до %max_targets% Arcane Bolt в ближайших врагов в радиусе %search_radius%. Каждый снаряд наносит %damage_pct%%% урона Arcane Bolt." + "DOTA_Tooltip_ability_skywrath_mage_innate_custom_Lore" "Для Драгонуса разрушение и возрождение — две стороны одного небесного закона." + "DOTA_Tooltip_ability_skywrath_mage_innate_custom_search_radius" "РАДИУС ПОИСКА:" + "DOTA_Tooltip_ability_skywrath_mage_innate_custom_max_targets" "ЦЕЛЕЙ:" + "DOTA_Tooltip_ability_skywrath_mage_innate_custom_damage_pct" "% УРОНА ARCANE BOLT:" + + "DOTA_Tooltip_ability_skywrath_mage_staff_of_the_scion_custom" "Staff of the Scion" + "DOTA_Tooltip_ability_skywrath_mage_staff_of_the_scion_custom_Description" "Каждая его способность отдельно имеет шанс мгновенно откатиться:

50% -> 25% -> 12.5% -> 6.25%

Для каждой способности своя цепочка шансов. Цепочка сбрасывается после %max_successes% срабатываний или через %reset_duration% сек. без новых срабатываний." + "DOTA_Tooltip_ability_skywrath_mage_staff_of_the_scion_custom_Lore" "Реликвия Верховного Дома отзывается на безупречные чары и снова открывает путь к их повторению." + "DOTA_Tooltip_ability_skywrath_mage_staff_of_the_scion_custom_reset_duration" "СБРОС ЧЕРЕЗ:" + "DOTA_Tooltip_ability_skywrath_mage_staff_of_the_scion_custom_max_successes" "СРАБАТЫВАНИЙ ДО СБРОСА:" + + "DOTA_Tooltip_Ability_special_bonus_unique_skywrath_6" "+{s:bonus_AbilityCastRange} к дальности Arcane Bolt" + "DOTA_Tooltip_Ability_special_bonus_unique_skywrath_concussive_shot_slow" "+{s:bonus_slow}% к замедлению Concussive Shot" + "DOTA_Tooltip_Ability_special_bonus_unique_skywrath" "-{s:bonus_AbilityCooldown} сек. перезарядки Ancient Seal" + "DOTA_Tooltip_Ability_special_bonus_unique_skywrath_2" "+{s:bonus_int_multiplier} к множителю интеллекта Arcane Bolt" + "DOTA_Tooltip_Ability_special_bonus_unique_skywrath_4" "+{s:bonus_launch_radius} к радиусу поиска Concussive Shot" + "DOTA_Tooltip_Ability_special_bonus_unique_skywrath_3" "{s:bonus_resist_debuff} к снижению маг. сопротивления Ancient Seal" + "DOTA_Tooltip_Ability_special_bonus_unique_skywrath_5" "+{s:bonus_damage} к урону Mystic Flare" + + "dota_tooltip_modifier_modifier_skywrath_mage_staff_of_the_scion_arcane_tracker" "Staff of the Scion: Arcane Bolt" + "dota_tooltip_modifier_modifier_skywrath_mage_staff_of_the_scion_arcane_tracker_Description" "Цепочка мгновенного отката для Arcane Bolt активна. Индикатор показывает число успешных срабатываний до сброса." + "dota_tooltip_modifier_modifier_skywrath_mage_staff_of_the_scion_concussive_tracker" "Staff of the Scion: Concussive Shot" + "dota_tooltip_modifier_modifier_skywrath_mage_staff_of_the_scion_concussive_tracker_Description" "Цепочка мгновенного отката для Concussive Shot активна. Индикатор показывает число успешных срабатываний до сброса." + "dota_tooltip_modifier_modifier_skywrath_mage_staff_of_the_scion_seal_tracker" "Staff of the Scion: Ancient Seal" + "dota_tooltip_modifier_modifier_skywrath_mage_staff_of_the_scion_seal_tracker_Description" "Цепочка мгновенного отката для Ancient Seal активна. Индикатор показывает число успешных срабатываний до сброса." + "dota_tooltip_modifier_modifier_skywrath_mage_staff_of_the_scion_flare_tracker" "Staff of the Scion: Mystic Flare" + "dota_tooltip_modifier_modifier_skywrath_mage_staff_of_the_scion_flare_tracker_Description" "Цепочка мгновенного отката для Mystic Flare активна. Индикатор показывает число успешных срабатываний до сброса." + + + // -----------------Luna-------------------- + + "dota_tooltip_ability_ability_luna_lucent_beam_custom" "Lucent Beam" + "dota_tooltip_ability_ability_luna_lucent_beam_custom_Description" "Обрушивает на вражеский юнит луч лунной силы, наносящий %lucent_beam_damage% магического урона и оглушающий его на %stun_duration% сек. Дополнительно поражает врагов в радиусе %lucent_beam_aoe_radius% от главной цели: %lucent_beam_aoe_damage_pct%%% урона и длительности оглушения от основного удара (тот же эффект используют лучи Eclipse)." + "dota_tooltip_ability_ability_luna_lucent_beam_custom_lucent_beam_damage" "УРОН:" + "dota_tooltip_ability_ability_luna_lucent_beam_custom_stun_duration" "ОГЛУШЕНИЕ:" + "dota_tooltip_ability_ability_luna_lucent_beam_custom_lucent_beam_aoe_radius" "РАДИУС ДОП. УРОНА ВОКРУГ ЦЕЛИ:" + "dota_tooltip_ability_ability_luna_lucent_beam_custom_lucent_beam_aoe_damage_pct" "ДОЛЯ УРОНА И СТАНА ПО СОСЕДЯМ (%):" + "dota_tooltip_ability_ability_luna_lucent_beam_custom_AbilityCastRange" "ДАЛЬНОСТЬ ПРИМЕНЕНИЯ:" + "dota_tooltip_ability_ability_luna_lucent_beam_custom_Lore" "Селемена не прощает тех, кто стоит на пути её воительницы: один луч — и тьма сомкнулась над врагом." + + "dota_tooltip_ability_ability_luna_twin_glaives_custom" "Twin Glaives" + "dota_tooltip_ability_ability_luna_twin_glaives_custom_Description" "Луна бросает две глефы с размаха: они летят дугой к цели, сходятся у неё, затем возвращаются, пересекая траектории. Урон в широкой зоне вдоль пути; на пути «туда» и «обратно» цель может получить урон дважды. Каждое попадание наносит %glaive_damage% плюс долю урона от атаки Луны. Поражённые враги на %twin_facet_vuln_duration% сек. получают повышенный входящий физический и магический урон: %twin_facet_pct%%% за стак дебаффа." + "dota_tooltip_ability_ability_luna_twin_glaives_custom_glaive_damage" "БАЗОВЫЙ УРОН ГЛЕФЫ:" + "dota_tooltip_ability_ability_luna_twin_glaives_custom_projectile_speed" "СКОРОСТЬ ПОЛЁТА:" + "dota_tooltip_ability_ability_luna_twin_glaives_custom_projectile_range" "МАКС. ДАЛЬНОСТЬ:" + "dota_tooltip_ability_ability_luna_twin_glaives_custom_projectile_width" "РАДИУС УРОНА:" + "dota_tooltip_ability_ability_luna_twin_glaives_custom_axe_spread" "РАЗНОС ПРИ БРОСКЕ:" + "dota_tooltip_ability_ability_luna_twin_glaives_custom_twin_facet_vuln_duration" "ДЛИТЕЛЬНОСТЬ УЯЗВИМОСТИ:" + "dota_tooltip_ability_ability_luna_twin_glaives_custom_twin_facet_pct" "% УВЕЛИЧЕНИЯ ВХОДЯЩЕГО УРОНА ЗА СТАК:" + "dota_tooltip_ability_ability_luna_twin_glaives_custom_AbilityCastRange" "ДАЛЬНОСТЬ ПРИМЕНЕНИЯ:" + "dota_tooltip_ability_ability_luna_twin_glaives_custom_Lore" "Клинок лунного диска не знает пощады: там, где светит луна, падает сталь." + + "dota_tooltip_ability_ability_luna_moon_glaive_custom" "Moon Glaives" + "dota_tooltip_ability_ability_luna_moon_glaive_custom_Description" "Пассивная способность. Атаки Луны выпускают глефы, что перескакивают на других врагов в радиусе %bounce_range%. Первый отскок наносит %bounce_damage_pct%%% урона от атаки; каждый следующий — %bounce_falloff_pct%%% от урона предыдущего отскока (на ранних уровнях способности падение сильнее). Талант может убрать это ослабление. С талантом после попадания Lucent Beam или луча Eclipse по врагу Луна дважды мгновенно атакует эту цель." + "dota_tooltip_ability_ability_luna_moon_glaive_custom_bounce_range" "РАДИУС ОТСКОКА:" + "dota_tooltip_ability_ability_luna_moon_glaive_custom_moon_glaive_bounce_count" "ОТСКОКОВ:" + "dota_tooltip_ability_ability_luna_moon_glaive_custom_bounce_falloff_pct" "%ДОЛЯ УРОНА ОТСКОКА:" + "dota_tooltip_ability_ability_luna_moon_glaive_custom_Lore" "Охота не кончается с первым ударом: пока враги стоят рядом, лунная сталь найдёт каждого." + + "dota_tooltip_ability_ability_luna_lunar_blessing_custom" "Lunar Blessing" + "dota_tooltip_ability_ability_luna_lunar_blessing_custom_Description" "Врождённая сила, связанная с Eclipse. Союзники в радиусе %blessing_radius% получают дополнительный урон к атакам: %blessing_bonus_damage% за каждый уровень героя под баффом. Пока союзная Мирана в радиусе %moon_sisters_radius%, Luna и Мирана получают на %moon_sisters_damage_reduction_pct%%% меньше входящего урона." + "dota_tooltip_ability_ability_luna_lunar_blessing_custom_blessing_radius" "РАДИУС:" + "dota_tooltip_ability_ability_luna_lunar_blessing_custom_blessing_bonus_damage" "БОНУС УРОНА ЗА УРОВЕНЬ:" + "dota_tooltip_ability_ability_luna_lunar_blessing_custom_moon_sisters_radius" "РАДИУС СВЯЗИ С МИРАНОЙ:" + "dota_tooltip_ability_ability_luna_lunar_blessing_custom_moon_sisters_damage_reduction_pct" "СНИЖЕНИЕ ВХОДЯЩЕГО УРОНА (СЁСТРЫ):" + "dota_tooltip_ability_ability_luna_lunar_blessing_custom_Shard_Description" "У союзников под эффектом Lunar Blessing дополнительно растёт усиление заклинаний: %blessing_shard_spell_amp_per_level%%% за каждый уровень героя." + "dota_tooltip_ability_ability_luna_lunar_blessing_custom_Lore" "Луна несёт волю Селемены: лунный свет касается и клинков союзников, не только её собственной стали." + + "dota_tooltip_ability_ability_luna_eclipse_custom" "Eclipse" + "dota_tooltip_ability_ability_luna_eclipse_custom_Description" "Вызывает затмение: %eclipse_duration% сек. в радиусе %eclipse_radius% с интервалом %beam_interval% сек. с неба бьют лучи. Без Аганима за каждый интервал выбирается одна случайная цель в зоне; таких ударов всего %eclipse_beam_count%. Каждый луч использует урон и оглушение Lucent Beam текущего уровня; если Lucent Beam не изучен, урон не наносится." + "dota_tooltip_ability_ability_luna_eclipse_custom_eclipse_duration" "ДЛИТЕЛЬНОСТЬ:" + "dota_tooltip_ability_ability_luna_eclipse_custom_beam_interval" "ИНТЕРВАЛ ЛУЧЕЙ:" + "dota_tooltip_ability_ability_luna_eclipse_custom_eclipse_beam_count" "ЧИСЛО ЛУЧЕЙ:" + "dota_tooltip_ability_ability_luna_eclipse_custom_eclipse_radius" "РАДИУС:" + "dota_tooltip_ability_ability_luna_eclipse_custom_eclipse_facet_wave_count" "ВОЛН (С АГАНИМОМ):" + "dota_tooltip_ability_ability_luna_eclipse_custom_Scepter_Description" "Вместо случайных целей Eclipse выпускает %eclipse_facet_wave_count% волн: на каждой волне каждый враг в области получает полный удар луча (как Lucent Beam)." + "dota_tooltip_ability_ability_luna_eclipse_custom_Lore" "Когда луна закрывает солнце, небо само становится оружием Селемены — и никто не уйдёт из-под её суда." + + "dota_tooltip_modifier_modifier_luna_lunar_blessing_buff" "Lunar Blessing" + "dota_tooltip_modifier_modifier_luna_lunar_blessing_buff_Description" "Лунный свет усиливает урон ваших атак: бонус растёт с уровнем героя под баффом и с уровнем Луны. При осколке Аганима у Луны — дополнительное усиление заклинаний от вашего уровня." + "dota_tooltip_modifier_modifier_luna_lunar_blessing_aura" "Lunar Blessing" + "dota_tooltip_modifier_modifier_luna_lunar_blessing_aura_Description" "Аура: союзники поблизости получают эффект Lunar Blessing." + + "dota_tooltip_modifier_modifier_moon_sisters_innate" "Moon Sisters" + "dota_tooltip_modifier_modifier_moon_sisters_innate_Description" "Рядом сестра по луне: входящий урон снижен." + + "dota_tooltip_modifier_modifier_luna_eclipse_active" "Eclipse" + "dota_tooltip_modifier_modifier_luna_eclipse_active_Description" "Лунные лучи бьют по врагам в области: магический урон и короткое оглушение (случайные цели или все враги за волну — в зависимости от Аганима)." + + "dota_tooltip_ability_special_bonus_unique_luna_lucent_beam_damage" "+{s:bonus_lucent_beam_damage} к урону Lucent Beam" + "dota_tooltip_ability_special_bonus_unique_luna_orbit_radius" "+{s:bonus_cast_range} к дальности применения и полёта Twin Glaives" + "dota_tooltip_ability_special_bonus_unique_luna_glaive_extra_bounce" "+{s:bonus_moon_glaive_bounce_count} отскок Moon Glaives" + "dota_tooltip_ability_special_bonus_unique_luna_blessing_aura" "+{s:bonus_blessing_radius} к радиусу Lunar Blessing" + "dota_tooltip_ability_special_bonus_unique_luna_lucent_beam_cooldown" "-{s:bonus_AbilityCooldown} сек. перезарядки Lucent Beam" + "dota_tooltip_ability_special_bonus_unique_luna_orbit_damage" "+{s:bonus_glaive_damage} к урону Twin Glaives" + "dota_tooltip_ability_special_bonus_unique_luna_eclipse_power" "+{s:bonus_eclipse_beam_count} лучей Eclipse" + "dota_tooltip_ability_special_bonus_unique_luna_glaives_no_falloff" "Moon Glaives: урон отскоков не ослабляется ({s:bonus_bounce_falloff_pct}% от предыдущего)" + "dota_tooltip_ability_special_bonus_unique_luna_moon_glaive_beam_attack" "После Lucent Beam и луча Eclipse Luna мгновенно атакует цель дважды" + + // -----------------Mirana-------------------- + + "dota_tooltip_ability_ability_mirana_starstorm_custom" "Starstorm" + "dota_tooltip_ability_ability_mirana_starstorm_custom_Description" "Призывает звёздный дождь вокруг Мираны: метеоры падают на врагов в радиусе %radius% в точке каста, через %meteor_impact_delay% сек. после появления нанося %damage% магического урона плюс %mana_damage_pct%%% от максимальной маны. Через %secondary_delay% сек. ближайший враг в той же области получает второй метеор с %secondary_damage_pct%%% урона от основного (ещё через %meteor_impact_delay% сек.)." + "dota_tooltip_ability_ability_mirana_starstorm_custom_damage" "УРОН:" + "dota_tooltip_ability_ability_mirana_starstorm_custom_mana_damage_pct" "% УРОНА ОТ МАНЫ:" + "dota_tooltip_ability_ability_mirana_starstorm_custom_radius" "РАДИУС:" + "dota_tooltip_ability_ability_mirana_starstorm_custom_meteor_impact_delay" "ВРЕМЯ ПОЛЁТА МЕТЕОРА:" + "dota_tooltip_ability_ability_mirana_starstorm_custom_secondary_delay" "ЗАДЕРЖКА ВТОРОГО МЕТЕОРА:" + "dota_tooltip_ability_ability_mirana_starstorm_custom_secondary_damage_pct" "%УРОН ДОП. УДАРОВ:" + "dota_tooltip_ability_ability_mirana_starstorm_custom_Lore" "Небо отвечает на зов лунной охотницы — и враги не успевают спрятаться в тени." + + "dota_tooltip_ability_ability_mirana_sacred_arrow_custom" "Sacred Arrow" + "dota_tooltip_ability_ability_mirana_sacred_arrow_custom_Description" "Выпускает священную стрелу по направлению курсора. Наносит %arrow_damage% магического урона плюс %mana_damage_pct%%% от максимальной маны каждому врагу на пути и останавливается только на боссах. Оглушение по боссу растёт с дистанцией полёта: %stun_per_100_distance% сек. за каждые 100 единиц, максимум %max_stun_duration% сек." + "dota_tooltip_ability_ability_mirana_sacred_arrow_custom_arrow_damage" "УРОН:" + "dota_tooltip_ability_ability_mirana_sacred_arrow_custom_mana_damage_pct" "% УРОНА ОТ МАНЫ:" + "dota_tooltip_ability_ability_mirana_sacred_arrow_custom_arrow_range" "ДАЛЬНОСТЬ:" + "dota_tooltip_ability_ability_mirana_sacred_arrow_custom_arrow_speed" "СКОРОСТЬ:" + "dota_tooltip_ability_ability_mirana_sacred_arrow_custom_stun_per_100_distance" "ОГЛУШЕНИЕ ЗА 100 ДИСТ.:" + "dota_tooltip_ability_ability_mirana_sacred_arrow_custom_max_stun_duration" "МАКС. ОГЛУШЕНИЕ:" + "dota_tooltip_ability_ability_mirana_sacred_arrow_custom_Shard_Description" "Пока летит стрела, на каждого врага в радиусе %shard_starfall_radius% от её траектории один раз падает Starstorm: %shard_starfall_damage_pct_first%%% и %shard_starfall_damage_pct_second%%% урона Sacred Arrow (база + мана)." + "dota_tooltip_ability_ability_mirana_sacred_arrow_custom_shard_starfall_radius" "РАДИУС STARSTORM:" + "dota_tooltip_ability_ability_mirana_sacred_arrow_custom_Lore" "Чем дольше летит стрела, тем сильнее кара Селемены для того, кто не увернулся." + + "dota_tooltip_ability_ability_mirana_leap_custom" "Leap" + "dota_tooltip_ability_ability_mirana_leap_custom_Description" "Мирана прыгает вперёд по направлению взгляда на расстояние до %leap_distance%. Стоимость прыжка: %mana_cost_pct%%% максимальной маны. В полёте получает на %leap_damage_reduction_pct%%% меньше урона; повторный прыжок недоступен до приземления. При приземлении враги в радиусе %leap_landing_radius% получают %leap_landing_damage_pct%%% урона атаки Мираны и дополнительный магический урон в размере %mana_damage_pct%%% от максимальной маны. %max_charges% заряда (4 + 1 за уровень героя), восстановление одного заряда %AbilityChargeRestoreTime% сек. Сразу получает +%leap_speedbonus%%% к скорости передвижения и +%leap_speedbonus_as% к скорости атаки на %leap_bonus_duration% сек. На время баффа урон врождёнки ×%innate_damage_multiplier%." + "dota_tooltip_ability_ability_mirana_leap_custom_mana_cost_pct" "% МАНЫ ОТ МАКСИМУМА:" + "dota_tooltip_ability_ability_mirana_leap_custom_leap_damage_reduction_pct" "%СНИЖЕНИЕ УРОНА В ПОЛЁТЕ:" + "dota_tooltip_ability_ability_mirana_leap_custom_leap_landing_radius" "РАДИУС УРОНА ПРИ ПРИЗЕМЛЕНИИ:" + "dota_tooltip_ability_ability_mirana_leap_custom_leap_landing_damage_pct" "%УРОН ПРИ ПРИЗЕМЛЕНИИ ОТ АТАКИ:" + "dota_tooltip_ability_ability_mirana_leap_custom_mana_damage_pct" "% УРОНА ОТ МАНЫ ПРИ ПРИЗЕМЛЕНИИ:" + "dota_hud_error_mirana_leap_in_air" "Нельзя прыгнуть снова, пока не приземлились." + "dota_tooltip_ability_ability_mirana_leap_custom_innate_damage_multiplier" "МНОЖИТЕЛЬ УРОНА ВРОЖДЁНКИ:" + "dota_tooltip_ability_ability_mirana_leap_custom_max_charges" "ЗАРЯДОВ:" + "dota_tooltip_ability_ability_mirana_leap_custom_AbilityChargeRestoreTime" "ВОССТАНОВЛЕНИЕ ЗАРЯДА:" + "dota_tooltip_ability_ability_mirana_leap_custom_leap_distance" "ДАЛЬНОСТЬ ПРЫЖКА:" + "dota_tooltip_ability_ability_mirana_leap_custom_leap_speed" "СКОРОСТЬ ПРЫЖКА:" + "dota_tooltip_ability_ability_mirana_leap_custom_leap_height" "ВЫСОТА ДУГИ:" + "dota_tooltip_ability_ability_mirana_leap_custom_leap_speedbonus" "СКОРОСТЬ ПЕРЕДВИЖЕНИЯ:" + "dota_tooltip_ability_ability_mirana_leap_custom_leap_speedbonus_as" "СКОРОСТЬ АТАКИ:" + "dota_tooltip_ability_ability_mirana_leap_custom_leap_bonus_duration" "ДЛИТЕЛЬНОСТЬ БАФФА:" + "dota_tooltip_ability_ability_mirana_leap_custom_Lore" "Она не убегает — она выбирает, с какой высоты нанести следующий удар." + + "dota_tooltip_ability_ability_mirana_innate_custom" "Celestial Quiver" + "dota_tooltip_ability_ability_mirana_innate_custom_Description" "Врождённая способность: дальние атаки наносят дополнительный магический урон в размере %mana_damage_pct%%% от максимальной маны. Под баффом Leap урон врождёнки увеличен в 4 раза. Пока союзная Luna в радиусе %moon_sisters_radius%, Мирана и Luna получают на %moon_sisters_damage_reduction_pct%%% меньше входящего урона. Отключается истощением." + "dota_tooltip_ability_ability_mirana_innate_custom_mana_damage_pct" "% УРОНА ОТ МАНЫ:" + "dota_tooltip_ability_ability_mirana_innate_custom_moon_sisters_radius" "РАДИУС СВЯЗИ С LUNA:" + "dota_tooltip_ability_ability_mirana_innate_custom_moon_sisters_damage_reduction_pct" "СНИЖЕНИЕ ВХОДЯЩЕГО УРОНА (СЁСТРЫ):" + "dota_tooltip_ability_ability_mirana_innate_custom_Lore" "Лунный колчан Селемены: каждая стрела хранит каплю небесного огня." + + "dota_tooltip_ability_ability_mirana_moonlight_shadow_custom" "Moonlight Shadow" + "dota_tooltip_ability_ability_mirana_moonlight_shadow_custom_Description" "На %duration% сек. все союзные герои не могут умереть (здоровье не опускается ниже 1) и получают +%bonus_movement_speed%%% к скорости передвижения. Мирана дополнительно получает +%damage_bonus_base%%% к урону и ещё +%damage_bonus_per_second%%% за каждую секунду действия." + "dota_tooltip_ability_ability_mirana_moonlight_shadow_custom_duration" "ДЛИТЕЛЬНОСТЬ:" + "dota_tooltip_ability_ability_mirana_moonlight_shadow_custom_bonus_movement_speed" "СКОРОСТЬ ПЕРЕДВИЖЕНИЯ:" + "dota_tooltip_ability_ability_mirana_moonlight_shadow_custom_damage_bonus_base" "БАЗОВЫЙ БОНУС УРОНА (МИРАНА):" + "dota_tooltip_ability_ability_mirana_moonlight_shadow_custom_damage_bonus_per_second" "БОНУС УРОНА ЗА СЕКУНДУ (МИРАНА):" + "dota_tooltip_ability_ability_mirana_moonlight_shadow_custom_scepter_ally_damage_share_pct" "ДОЛЯ УРОНА СОЮЗНИКАМ (%):" + "dota_tooltip_ability_ability_mirana_moonlight_shadow_custom_Scepter_Description" "Союзники под Moonlight Shadow получают %scepter_ally_damage_share_pct%%% от растущего бонуса урона Мираны." + "dota_tooltip_ability_ability_mirana_moonlight_shadow_custom_Lore" "Лунный свет оберегает своих — пока охота не закончена." + + "dota_tooltip_ability_special_bonus_unique_mirana_starstorm_damage" "+{s:bonus_damage} к урону Starstorm" + "dota_tooltip_ability_special_bonus_unique_mirana_starstorm_cooldown" "-{s:bonus_AbilityCooldown} сек. перезарядки Starstorm" + "dota_tooltip_ability_special_bonus_unique_mirana_starstorm_attack_proc" "{s:bonus_attack_proc_chance}% шанс (с учётом удачи): атака вызывает Starstorm на атакованного врага" + "dota_tooltip_ability_special_bonus_unique_mirana_sacred_arrow_side_arrows" "Sacred Arrow выпускает 2 дополнительные стрелы под углом 25° слева и справа от основной." + "dota_tooltip_ability_special_bonus_unique_mirana_sacred_arrow_damage" "+{s:bonus_arrow_damage} к урону Sacred Arrow" + "dota_tooltip_ability_special_bonus_unique_mirana_leap_charge_time" "-{s:bonus_AbilityChargeRestoreTime} сек. восстановления заряда Leap" + "dota_tooltip_ability_special_bonus_unique_mirana_leap_air_strike" "В полёте Leap: в радиусе своей атаки выпускает по снаряду в каждого врага вокруг себя" + "dota_tooltip_ability_special_bonus_unique_mirana_leap_landing_radius" "+{s:bonus_leap_landing_radius} к радиусу урона при приземлении Leap" + "dota_tooltip_ability_special_bonus_unique_mirana_moonlight_duration" "+{s:bonus_duration} сек. длительности Moonlight Shadow" + "dota_tooltip_ability_special_bonus_unique_mirana_innate_mana_damage" "+{s:bonus_mana_damage_pct}% урона от маны" + + "dota_tooltip_modifier_mirana_leap_custom_buff" "Leap" + "dota_tooltip_modifier_mirana_leap_custom_buff_Description" "Повышенная скорость передвижения и атаки. Урон врождёнки Celestial Quiver увеличен в 4 раза." + + "dota_tooltip_modifier_mirana_moonlight_shadow_custom" "Moonlight Shadow" + "dota_tooltip_modifier_mirana_moonlight_shadow_custom_Description" "Не может умереть. Ускорение под лунным покровом. Бонус исходящего урона растёт со временем (у союзников — доля от бонуса Мираны при Аганиме)." + + + + // -----------------Pudge-------------------- + "dota_tooltip_ability_ability_pudge_meat_hook_custom" "Meat Hook" + "dota_tooltip_ability_ability_pudge_meat_hook_custom_Description" "Пудж бросает крюк: чистый урон %hook_damage% + %max_health_damage_pct%%% от максимального здоровья Пуджа, затягивает врагов." + "dota_tooltip_ability_ability_pudge_meat_hook_custom_Lore" "Старый ржавый крюк Пуджа не знает промахов, когда мясник чувствует запах свежей добычи." + "dota_tooltip_ability_ability_pudge_meat_hook_custom_hook_damage" "УРОН:" + "dota_tooltip_ability_ability_pudge_meat_hook_custom_hook_distance" "ДАЛЬНОСТЬ КРЮКА:" + "dota_tooltip_ability_ability_pudge_meat_hook_custom_hook_speed" "СКОРОСТЬ ПОЛЁТА:" + "dota_tooltip_ability_ability_pudge_meat_hook_custom_hook_width" "ШИРИНА КРЮКА:" + "dota_tooltip_ability_ability_pudge_meat_hook_custom_max_health_damage_pct" "% МАКС. ЗДОРОВЬЯ ПРИ ПОПАДАНИИ:" + + "dota_tooltip_ability_ability_pudge_rot_custom" "Rot" + "dota_tooltip_ability_ability_pudge_rot_custom_Description" "Пудж отравляет воздух вокруг себя: каждые %tick_interval% сек. враги в радиусе %rot_radius% получают магический урон (%rot_damage_per_sec% в сек. + рост %rot_damage_increase_per_sec%, +%max_health_damage_pct%%% от макс. здоровья Пуджа в сек.), замедление и самоурон." + "dota_tooltip_ability_ability_pudge_rot_custom_Lore" "Тлетворный смрад вокруг Пуджа выедает плоть врагов быстрее, чем они успевают сделать вдох." + "dota_tooltip_ability_ability_pudge_rot_custom_rot_radius" "РАДИУС:" + "dota_tooltip_ability_ability_pudge_rot_custom_rot_damage_per_sec" "УРОН В СЕКУНДУ:" + "dota_tooltip_ability_ability_pudge_rot_custom_Scepter_Description" "Увеличивает радиус Rot на %scepter_bonus_radius% и уменьшает восстановление здоровья врагов в радиусе на %scepter_enemy_hp_regen_reduction_pct%%%." + "dota_tooltip_ability_ability_pudge_rot_custom_self_damage_pct_per_sec" "%САМОУРОН В СЕКУНДУ ОТ МАКС. ЗДОРОВЬЯ:" + "dota_tooltip_ability_ability_pudge_rot_custom_rot_slow_pct" "%ЗАМЕДЛЕНИЕ:" + "dota_tooltip_ability_ability_pudge_rot_custom_scepter_enemy_hp_regen_reduction_pct" "%СНИЖЕНИЕ ВОССТАНОВЛЕНИЯ ЗДОРОВЬЯ ВРАГОВ:" + "dota_tooltip_ability_ability_pudge_rot_custom_rot_damage_increase_per_sec" "РОСТ УРОНА В СЕКУНДУ:" + "dota_tooltip_ability_ability_pudge_rot_custom_max_health_damage_pct" "% МАКС. ЗДОРОВЬЯ В СЕК:" + + "dota_tooltip_ability_ability_pudge_flesh_heap_custom" "Flesh Heap" + "dota_tooltip_ability_ability_pudge_flesh_heap_custom_Description" "Врождённая пассивная способность. Когда враг умирает под действием Dismember, Пудж получает стак Flesh Heap. Каждый стак даёт силы и сопротивления магии." + "dota_tooltip_ability_ability_pudge_flesh_heap_custom_Lore" "Каждый павший враг становится частью чудовищной коллекции Пуджа, делая его только толще и крепче." + "dota_tooltip_ability_ability_pudge_flesh_heap_custom_stack_range" "РАДИУС НАБОРА СТАКОВ:" + "dota_tooltip_ability_ability_pudge_flesh_heap_custom_strength_per_stack" "СИЛА ЗА СТАК:" + "dota_tooltip_ability_ability_pudge_flesh_heap_custom_magic_resist_per_stack" "%ДОП. МАГ. СОПРОТИВЛЕНИЕ ЗА СТАК:" + + "dota_tooltip_ability_ability_pudge_meat_shield_custom" "Meat Shield" + "dota_tooltip_ability_ability_pudge_meat_shield_custom_Description" "Плоть Пуджа постоянно блокирует входящий урон." + "dota_tooltip_ability_ability_pudge_meat_shield_custom_Lore" "Годы бойни превратили тушу Пуджа в живой щит, который гасит удары одним своим весом." + "dota_tooltip_ability_ability_pudge_meat_shield_custom_block_damage" "БЛОК УРОНА:" + "dota_tooltip_ability_ability_pudge_meat_shield_custom_shard_block_per_strength" "БЛОК ЗА ЕДИНИЦУ СИЛЫ:" + "dota_tooltip_ability_ability_pudge_meat_shield_custom_Shard_Description" "Meat Shield дополнительно блокирует %shard_block_per_strength% урона за каждую единицу силы Пуджа." + + "dota_tooltip_ability_ability_pudge_dismember_custom" "Dismember" + "dota_tooltip_ability_ability_pudge_dismember_custom_Description" "Враги в радиусе %width% стягиваются к центру и получают периодический магический урон. Урон в секунду: базовый %damage_per_second%, +%strength_damage_pct%%% от силы, +%max_health_damage_pct%%% от максимального здоровья Пуджа. %heal_from_damage_pct%%% нанесённого урона возвращается лечением; за тик сытости +%hunger_bonus%." + "dota_tooltip_ability_ability_pudge_dismember_custom_Lore" "Когда Пудж начинает разделывать добычу, вокруг слышны только хруст костей и его довольное урчание." + "dota_tooltip_ability_ability_pudge_dismember_custom_damage_per_second" "БАЗОВЫЙ УРОН В СЕКУНДУ:" + "dota_tooltip_ability_ability_pudge_dismember_custom_strength_damage_pct" "% УРОНА ОТ СИЛЫ В СЕК:" + "dota_tooltip_ability_ability_pudge_dismember_custom_max_health_damage_pct" "% МАКС. ЗДОРОВЬЯ В СЕК:" + "dota_tooltip_ability_ability_pudge_dismember_custom_heal_from_damage_pct" "%ЛЕЧЕНИЕ ОТ НАНЕСЁННОГО УРОНА:" + "dota_tooltip_ability_ability_pudge_dismember_custom_pull_speed" "СКОРОСТЬ СТЯГИВАНИЯ:" + "dota_tooltip_ability_ability_pudge_dismember_custom_pulse_interval" "ИНТЕРВАЛ ТИКА:" + + // pudge talents + "dota_tooltip_ability_special_bonus_unique_pudge_hook_range" "+{s:bonus_hook_distance} к дальности Meat Hook" + "dota_tooltip_ability_special_bonus_unique_pudge_hook_damage" "+{s:bonus_hook_damage} урона Meat Hook" + "dota_tooltip_ability_special_bonus_unique_pudge_rot_radius" "+{s:bonus_rot_radius} к радиусу Rot" + "dota_tooltip_ability_special_bonus_unique_pudge_rot_damage" "+{s:bonus_rot_damage_per_sec} урона в секунду Rot" + "dota_tooltip_ability_special_bonus_unique_pudge_heap_range" "+{s:bonus_stack_range} к радиусу Flesh Heap" + "dota_tooltip_ability_special_bonus_unique_pudge_heap_strength" "+{s:bonus_block_damage} к блоку урона Meat Shield" + "dota_tooltip_ability_special_bonus_unique_pudge_dismember_range" "+{s:bonus_cast_range} к дальности Dismember" + "dota_tooltip_ability_special_bonus_unique_pudge_dismember_damage" "+{s:bonus_damage_per_second} урона в секунду Dismember" + + // pudge modifiers + "dota_tooltip_modifier_pudge_meat_hook_bleed_custom" "Кровотечение от крюка" + "dota_tooltip_modifier_pudge_meat_hook_bleed_custom_Description" "Цель истекает кровью и получает периодический физический урон от силы Пуджа." + "dota_tooltip_modifier_pudge_dismember_growth_custom" "Раздувшийся мясник" + "dota_tooltip_modifier_pudge_dismember_growth_custom_Description" "Во время Dismember размер Пуджа увеличен в зависимости от его максимального здоровья." + "dota_tooltip_modifier_pudge_rot_slow_custom" "Rot" + "dota_tooltip_modifier_pudge_rot_slow_custom_Description" "Скорость передвижения снижена, а при Аганиме у Пуджа уменьшено восстановление здоровья." + "dota_tooltip_modifier_pudge_dismember_custom" "Dismember" + "dota_tooltip_modifier_pudge_dismember_custom_Description" "Цель обездвижена и получает периодический урон." + "dota_tooltip_modifier_pudge_rot_custom" "Rot (аура)" + "dota_tooltip_modifier_pudge_rot_custom_Description" "Пудж распространяет отравляющую ауру вокруг себя, нанося врагам периодический урон, но теряя здоровье." + "dota_tooltip_modifier_pudge_meat_hook_pull" "Meat Hook" + "dota_tooltip_modifier_pudge_meat_hook_pull_Description" "Цель зацеплена крюком и насильно перемещается к Пуджу." + "dota_tooltip_modifier_pudge_flesh_heap_custom" "Flesh Heap" + "dota_tooltip_modifier_pudge_flesh_heap_custom_Description" "Постоянный эффект от стаков: увеличивает силу и сопротивление магии Пуджа." + "dota_tooltip_modifier_pudge_meat_shield_custom" "Meat Shield" + "dota_tooltip_modifier_pudge_meat_shield_custom_Description" "Пудж блокирует часть входящего урона и отражает урон по атакующему от своей силы." + + // -----------------Yuki-onna-------------------- + + "DOTA_Tooltip_ability_ability_yuki_pohela" "Pohela" + "DOTA_Tooltip_ability_ability_yuki_pohela_Description" "Pohela концентрирует свою силу льда, покрывая союзника ледяной корочкой, увеличивая его сопротивление к входящему урону и замедляет скорость атаки врагов которые атакуют его." + "DOTA_Tooltip_ability_ability_yuki_pohela_AbilityCastRange" "РАДИУС:" + "DOTA_Tooltip_ability_ability_yuki_pohela_AbilityChannelTime" "ДЛИТЕЛЬНОСТЬ ПРИМЕНЕНИЯ:" + "DOTA_Tooltip_ability_ability_yuki_pohela_debuff_duration" "ДЛИТЕЛЬНОСТЬ ДЕБАФА:" + "DOTA_Tooltip_ability_ability_yuki_pohela_bonus_resistance" "%РЕЗИСТ УРОНА:" + "DOTA_Tooltip_ability_ability_yuki_pohela_reduce_atack_speed" "УМЕНЬШ.СКОРОСТИ АТАКИ:" + + "DOTA_Tooltip_modifier_ability_pohela_buff" "Pohela buff" + "DOTA_Tooltip_modifier_ability_pohela_buff_Description" "Герой под надёжной защитой^^" + + + "DOTA_Tooltip_ability_ability_yuki_frostshtorm" "Frostshtorm" + "DOTA_Tooltip_ability_ability_yuki_frostshtorm_Description" "Герой оскверняет своим льдом область, заходящие в неё наносят меньше урона, а союзники получают прибавку к урону наполовину от уменьшения." + "DOTA_Tooltip_ability_ability_yuki_frostshtorm_AbilityCastRange" "РАДИУС ПРИМЕНЕНИЯ:" + "DOTA_Tooltip_ability_ability_yuki_frostshtorm_radius" "РАДИУС" + "DOTA_Tooltip_ability_ability_yuki_frostshtorm_duration" "ДЛИТЕЛЬНОСТЬ:" + "DOTA_Tooltip_ability_ability_yuki_frostshtorm_dmg_reduced" "УМЕНЬШЕНИЕ УРОНА ВРАГАМ:" + "DOTA_Tooltip_ability_ability_yuki_frostshtorm_dmg_increese" "УВЕЛИЧЕНИЕ УРОНА СОЮЗНИКАМ:" + + "DOTA_Tooltip_ability_ability_yuki_revenge" "Revenge" + "DOTA_Tooltip_ability_ability_yuki_revenge_Description" "Аура Yuki-onna подмораживает противников: они получают %mana_damage_pct%%% от её максимальной маны плюс %base_damage% магического урона в секунду; союзники получают %heal% лечения в секунду." + "DOTA_Tooltip_ability_ability_yuki_revenge_mana_damage_pct" "% МАКС. МАНЫ УРОНОМ В СЕКУНДУ:" + "DOTA_Tooltip_ability_ability_yuki_revenge_radius" "РАДИУС" + "DOTA_Tooltip_ability_ability_yuki_revenge_heal" "ЛЕЧЕНИЯ В СЕКУНДУ:" + "DOTA_Tooltip_ability_ability_yuki_revenge_base_damage" "БАЗОВЫЙ УРОН В СЕКУНДУ:" + + "DOTA_Tooltip_modifier_ability_revenge_buff" "Revenge Aura" + "DOTA_Tooltip_modifier_ability_revenge_buff_Description" "Холод вас согревает." + + + + + + "DOTA_Tooltip_ability_ability_yuki_ritual" "Ritual" + "DOTA_Tooltip_ability_ability_yuki_ritual_Description" "Герой проводит ритуал Холода, увеличиваю всю броню и регенерацию союзных героев в области. Замедляет передвижение врагов и скорость атаки." + "DOTA_Tooltip_ability_ability_yuki_ritual_AbilityCastRange" "ДАЛЬНОСТЬ ПРИМЕНЕНИЯ:" + "DOTA_Tooltip_ability_ability_yuki_ritual_duration" "ДЛИТЕЛЬНОСТЬ:" + "DOTA_Tooltip_ability_ability_yuki_ritual_bonus_armor" "%ДОП.БРОНЯ:" + "DOTA_Tooltip_ability_ability_yuki_ritual_bonus_hp_regen" "%ДОП.ВОССТАНОВЛЕНИЕ ЗДОРОВЬЯ:" + "DOTA_Tooltip_ability_ability_yuki_ritual_bonus_hp_regen_pct" "%ВОССТАНОВЛЕНИЕ ЗДОРОВЬЯ ОТ МАКС. ЗДОРОВЬЯ:" + + "DOTA_Tooltip_modifier_ability_ritual_buff" "Ritual buff" + "DOTA_Tooltip_modifier_ability_ritual_buff_Description" "Благославлён холодом." + + + "DOTA_Tooltip_ability_ability_yuki_snowman" "Snowman" + "DOTA_Tooltip_ability_ability_yuki_snowman_Description" "Yuki-onna ставит милого Снеговичка, который кидается снежками в союзников. Снежки хилят в радиусе всех союзных героев и замедляет скорость атаки вражеских крипов." + "DOTA_Tooltip_ability_ability_yuki_snowman_bonus_health" "ЗДОРОВЬЕ:" + "DOTA_Tooltip_ability_ability_yuki_snowman_max_snowman" "МАКС.СНЕГОВИКОВ:" + "DOTA_Tooltip_ability_ability_yuki_snowmanl_slow_as" "ЗАМЕДЛЕНИЕ СКОРОСТИ АТАКИ:" + "DOTA_Tooltip_ability_ability_yuki_snowball" "Snowball" + "DOTA_Tooltip_ability_ability_yuki_snowball_Description" "Снежный шарик><" + "DOTA_Tooltip_ability_ability_yuki_snowball_radius" "РАДИУС:" + "DOTA_Tooltip_ability_ability_yuki_snowball_heal" "ЛЕЧЕНИЕ:" + "DOTA_Tooltip_ability_ability_yuki_snowball_slow_as" "ЗАМЕДЛЕНИЕ СКОРОСТИ АТАКИ:" + + "DOTA_Tooltip_ability_ability_yuki_challenge" "Challenge from yuki" + "DOTA_Tooltip_ability_ability_yuki_challenge_Description" "Yuki-onna проверяет прочность союзного героя: на %duration% сек. накладывает немоту, оглушение, обезоруживание и периодический чистый урон от основного атрибута. Если герой выживает, навсегда получает %bonus_main%%% к основному атрибуту за каждое пройденное испытание. За каждую смерть на испытании урон от следующих вызовов снижается на %dmg_reduce%%%, при успехе — увеличивается на %dmg_incr%%%." + "DOTA_Tooltip_ability_ability_yuki_challenge_Scepter_Description" "Увеличивает основной атрибут ещё на %main_pct%" + "DOTA_Tooltip_ability_ability_yuki_challenge_Shard_Description" "Уменьшает урон за атрибут" + "DOTA_Tooltip_ability_ability_yuki_challenge_duration" "ДЛИТЕЛЬНОСТЬ:" + "DOTA_Tooltip_ability_ability_yuki_challenge_dmg_per_atr" "УРОН ЗА АТРИБУТ:" + "DOTA_Tooltip_ability_ability_yuki_challenge_interval" "ИНТЕРВАЛ ПОЛУЧЕНИЯ УРОНА:" + "DOTA_Tooltip_ability_ability_yuki_challenge_bonus_main" "%bonus_main%%% К ОСН. АТРИБУТУ ЗА ИСПЫТАНИЕ:" + "DOTA_Tooltip_ability_ability_yuki_challenge_dmg_incr" "УВЕЛЕЧЕНИЕ УРОНА:" + "DOTA_Tooltip_ability_ability_yuki_challenge_dmg_reduce" "СНИЖЕНИЕ ПОЛУЧАЕМОГО:" + "DOTA_Tooltip_ability_ability_yuki_challenge_Note0" "Суммарный выданный бонус к основному атрибуту цели показывается в тултипе баффа испытания." + + + "DOTA_Tooltip_modifier_ability_challenge_buff" "Challenge from yuki" + "DOTA_Tooltip_modifier_ability_challenge_buff_Description" "Вы гуйлан." + "dota_tooltip_modifier_challenge_buff" "Испытание Юки" + "dota_tooltip_modifier_challenge_buff_Description" "Суммарный бонус к основному атрибуту: +%dMODIFIER_PROPERTY_TOOLTIP%." + "dota_tooltip_modifier_challenge_debuff" "Испытание Юки" + "dota_tooltip_modifier_challenge_debuff_Description" "Герой проходит испытание: контроль, без лечения и периодический чистый урон." + + + "DOTA_Tooltip_ability_special_bonus_unique_yuki_pohela_resistance" "+{s:bonus_bonus_resistance}% к резизсту от способности Pohela" + "DOTA_Tooltip_ability_special_bonus_unique_yuki_frostshtorm_duration" "+{s:bonus_duration} секунд к длительности FrostShtorm" + "DOTA_Tooltip_ability_special_bonus_unique_yuki_revenge_damage" "+{s:bonus_base_damage} к БАЗОВОМУ УРОНУ от способности Revenge" + "DOTA_Tooltip_ability_special_bonus_unique_yuki_revenge_heal" "+{s:bonus_heal} к лечению от способности Revenge" + "DOTA_Tooltip_ability_special_bonus_unique_yuki_snowman_max" "+{s:bonus_max_snowman} заряд способности snowman" + + + // -----------------Sargatanas------------------- + + "DOTA_Tooltip_ability_ability_star_devour" "Hell devour" + "DOTA_Tooltip_ability_ability_star_devour_Description" "Герой может поглотить крипа и получить 1 стак способности Hell devour" + "DOTA_Tooltip_ability_ability_star_devour_creep_level" "МАКСИМАЛЬНЫЙ УРОВЕНЬ КРИПА:" + "DOTA_Tooltip_ability_ability_star_devour_max_stack" "МАКСИМАЛЬНОЕ КОЛ-ВО СТАКОВ:" + + + "DOTA_Tooltip_ability_ability_hellstep" "Hell step" + "DOTA_Tooltip_ability_ability_hellstep_Description" "Герой извергает из себя поток огня, который обжигает тело его противников всё сильнее и сильнее с каждой секундой, также исцеляет союзников, увеличивает урон и самого Sargatanas в радиусе способности" + "DOTA_Tooltip_ability_ability_hellstep_min_damage" "МИН.УРОН:" + "DOTA_Tooltip_ability_ability_hellstep_max_damage" "МАКС.УРОН:" + "DOTA_Tooltip_ability_ability_hellstep_max_duration" "СЕК.МАКСИМАЛЬНОГО УРОНА:" + "DOTA_Tooltip_ability_ability_hellstep_radius" "РАДИУС:" + "DOTA_Tooltip_ability_ability_hellstep_duration" "ДЛИТЕЛЬНОСТЬ" + "DOTA_Tooltip_ability_ability_hellstep_stack_overhell" "КОЛ-ВО СТАКОВ" + "DOTA_Tooltip_ability_ability_hellstep_bonus_damage" "БОНУСНЫЙ УРОН:" + "DOTA_Tooltip_ability_ability_hellstep_bonus_attack_speed" "БОНУСНАЯ СКОРОСТЬ АТАКИ:" + + "DOTA_Tooltip_ability_ability_firecleave_creep" "Fire cleave" + "DOTA_Tooltip_ability_ability_firecleave_creep_fire_damage" "УРОН ОТ ОГНЯ:" + + "DOTA_Tooltip_ability_ability_firecleave" "Fire cleave" + "DOTA_Tooltip_ability_ability_firecleave_Description" "Герой необузданной мощью атакуя одного противника атакует противников позади и поджигает оригинальную цель атаки" + "DOTA_Tooltip_ability_ability_firecleave_great_cleave_damage" "%ПРОРУБАЮЩИЙ УРОН:" + "DOTA_Tooltip_ability_ability_firecleave_fire_damage" "УРОН ОТ ОГНЯ:" + "DOTA_Tooltip_ability_ability_firecleave_duration" "ДЛИТЕЛЬНОСТЬ ОГНЯ:" + + "DOTA_Tooltip_ability_ability_sunflame" "Sun flame" + "DOTA_Tooltip_ability_ability_sunflame_Description" "Герой извергает из себя потоки демонического огня, который наносит урон и стакает дебафф от перегрева, увеличивая урон от огня" + "DOTA_Tooltip_ability_ability_sunflame_range" "РАДИУС:" + "DOTA_Tooltip_ability_ability_sunflame_stack_overhell" "СТАКОВ ПЕРЕГРЕВА:" + "DOTA_Tooltip_ability_ability_sunflame_damage" "УРОН:" + "DOTA_Tooltip_ability_ability_sunflame_unduced" "%УМЕНЬШЕНИЕ УРОНА:" + "DOTA_Tooltip_ability_ability_sunflame_damage_meta" "УРОН ПОД УЛЬТОЙ:" + "DOTA_Tooltip_ability_ability_sunflame_unduced_meta" "%УМЕНЬШЕНИЕ УРОНА:" + + "DOTA_Tooltip_ability_ability_hell_summon" "Hell summon" + "DOTA_Tooltip_ability_ability_hell_summon_Description" "Урон зависит от характеристик персонажа. Сила – хп и дамаг. +Игрок может управлять призываемыми крипами." + "DOTA_Tooltip_ability_ability_hell_summon_str_dmg" "УРОН ЗА 1 СИЛУ" + "DOTA_Tooltip_ability_ability_hell_summon_str_hp" "ХП ЗА 1 СИЛУ:" + + + "DOTA_Tooltip_ability_ability_metamorphosis" "Revolve metamorphosis" + "DOTA_Tooltip_ability_ability_metamorphosis_Description" "Демон облачается в свою истинную форму усиливая свои скилы и получая меньше урона(процент блока урона зависит от силы демона, но должен быть ограничен каким-то значением)." + "DOTA_Tooltip_ability_ability_metamorphosis_duration" "ДЛИТЕЛЬНОСТЬ:" + "DOTA_Tooltip_ability_ability_metamorphosis_bonus_resistance" "БОНУСНЫЙ РЕЗИСТ К УРОНУ ЗА СИЛУ:" + "DOTA_Tooltip_ability_ability_metamorphosis_max_resistance" "МАКСИМАЛЬНЫЙ РЕЗИСТ К УРОНУ:" + "DOTA_Tooltip_ability_ability_metamorphosis_radius" "РАДИУС:" + "DOTA_Tooltip_ability_ability_metamorphosis_armor_ag" "АРМОР ЗА АГИЛУ:" + "DOTA_Tooltip_ability_ability_metamorphosis_attack_speed_ag" "СКОРОСТЬ АТАКИ ЗА АГИЛУ:" + + "DOTA_Tooltip_Ability_special_bonus_unique_sargatanas_max_stack" "+{s:bonus_max_stack} к количеству стков способности Devour" + + "DOTA_Tooltip_Ability_special_bonus_unique_sargatanas_str_dmg" "+{s:bonus_str_dmg} урона призванных существ способности hell summon" + + "DOTA_Tooltip_Ability_special_bonus_unique_sargatanas_duration_1" "+{s:bonus_duration} длительности способности hellstep" + + + + // -----------------Rubick-------------------- + + "DOTA_Tooltip_ability_ability_rubick_telekinesis_custom" "telekinesis" + "DOTA_Tooltip_ability_ability_rubick_telekinesis_custom_Description" "Рубик поднимает цель в воздух. Враги при приземлении получают урон и оглушение в радиусе. Союзники могут перемещаться в воздухе, получают лечение, бонус к дальности атаки, скорости атаки и магическому урону." + "DOTA_Tooltip_ability_ability_rubick_telekinesis_custom_Scepter_Description" "На союзников действует неограниченное количество времени. Положение цели в воздухе обновляется каждые 2 секунды. Если цель выходит за пределы области, эффект немедленно заканчивается." + "DOTA_Tooltip_ability_ability_rubick_telekinesis_custom_AbilityCastRange" "ДАЛЬНОСТЬ:" + "DOTA_Tooltip_ability_ability_rubick_telekinesis_custom_land_damage" "УРОН ПРИ ПРИЗЕМЛЕНИИ:" + "DOTA_Tooltip_ability_ability_rubick_telekinesis_custom_land_stun_duration" "ДЛИТЕЛЬНОСТЬ ОГЛУШЕНИЯ:" + "DOTA_Tooltip_ability_ability_rubick_telekinesis_custom_land_stun_radius" "РАДИУС:" + "DOTA_Tooltip_ability_ability_rubick_telekinesis_custom_throw_distance" "ДАЛЬНОСТЬ БРОСКА:" + "DOTA_Tooltip_ability_ability_rubick_telekinesis_custom_ally_heal_per_second" "ЛЕЧЕНИЕ В СЕКУНДУ:" + "DOTA_Tooltip_ability_ability_rubick_telekinesis_custom_ally_attack_range_bonus" "ДОП. ДАЛЬНОСТИ АТАКИ:" + "DOTA_Tooltip_ability_ability_rubick_telekinesis_custom_ally_attack_speed_bonus" "доп. СКОРОСТИ АТАКИ:" + "DOTA_Tooltip_ability_ability_rubick_telekinesis_custom_ally_bonus_magic_damage" "доп. МАГ. УРОНА:" + "DOTA_Tooltip_ability_ability_rubick_telekinesis_custom_ally_bonus_magic_damage_per_int" "МАГ. УРОН ЗА ИНТЕЛЛЕКТ:" + "DOTA_Tooltip_ability_ability_rubick_telekinesis_custom_Lore" "Когда мир кажется скованным законами гравитации, только истинный мастер арканы способен поднять пленника ввысь и распоряжаться его судьбой. Излюбленный трюк Рубика — нарушить привычное течение магии, превратив хаос в искусство телекинетического контроля." + "dota_hud_error_rubick_telekinesis_boss" "Нельзя применить на боссов." + "dota_hud_error_rubick_telekinesis_homer" "Нельзя применить на главу деревни." + + "DOTA_Tooltip_ability_ability_rubick_fade_bolt_custom" "Fade Bolt" + "DOTA_Tooltip_ability_ability_rubick_fade_bolt_custom_Description" "Рубик создаёт поток арканной энергии, который перескакивает между врагами, нанося %damage% магического урона и снижая их урон от атак на %attack_damage_reduction%%%. Каждый прыжок уменьшает урон болта на %jump_damage_reduction_pct%%%." + "DOTA_Tooltip_ability_ability_rubick_fade_bolt_custom_Shard_Description" "Позволяет болту повторно поразить цель, уже задетую этой же цепочкой." + "DOTA_Tooltip_ability_ability_rubick_fade_bolt_custom_AbilityCastRange" "ДАЛЬНОСТЬ:" + "DOTA_Tooltip_ability_ability_rubick_fade_bolt_custom_damage" "УРОН:" + "DOTA_Tooltip_ability_ability_rubick_fade_bolt_custom_radius" "РАДИУС ПРЫЖКА:" + "DOTA_Tooltip_ability_ability_rubick_fade_bolt_custom_attack_damage_reduction" "%СНИЖЕНИЯ УРОНА АТАКИ:" + "DOTA_Tooltip_ability_ability_rubick_fade_bolt_custom_jump_damage_reduction_pct" "%СНИЖЕНИЯ УРОНА ЗА ПРЫЖОК:" + "DOTA_Tooltip_ability_ability_rubick_fade_bolt_custom_duration" "ДЛИТЕЛЬНОСТЬ ДЕБАФФА:" + "DOTA_Tooltip_ability_ability_rubick_fade_bolt_custom_Lore" "Любимое заклинание Рубика для расправы с наёмными убийцами — простая, но эффективная конъюнктура." + + "DOTA_Tooltip_ability_ability_rubick_arcane_supremacy" "Arcane supremacy" + "DOTA_Tooltip_ability_ability_rubick_arcane_supremacy_Description" "Часть магического урона, который Рубик или союзники в радиусе наносят врагам, распределяется между всеми остальными врагами в радиусе. Урон делится поровну и применяется с учётом процента способности." + "DOTA_Tooltip_ability_ability_rubick_arcane_supremacy_aura_radius" "РАДИУС:" + "DOTA_Tooltip_ability_ability_rubick_arcane_supremacy_damage_pct" "%РАСПРЕДЕЛЯЕМОГО УРОНА:" + "DOTA_Tooltip_ability_ability_rubick_arcane_supremacy_spell_amp" "%ДОП УРОН ОТ ЗАКЛИНАНИЙ:" + "DOTA_Tooltip_ability_ability_rubick_arcane_supremacy_spell_amp_pct_lvl" "%ДОП УРОН ОТ ЗАКЛИНАНИЙ ЗА УРОВЕНЬ:" + "DOTA_Tooltip_ability_ability_rubick_arcane_supremacy_Lore" "Постижение самых сокровенных тайн магии позволило Рубику не только усиливать свои заклинания, но и извращать саму суть магического урона вокруг него. Врагам лучше держаться подальше — ведь даже если чудом устоят, часть разрушительной энергии найдёт их всё равно." + + "DOTA_Tooltip_ability_ability_rubick_spellsteal_custom" "Spell steal" + "DOTA_Tooltip_ability_ability_rubick_spellsteal_custom_Description" "Рубик крадёт последнюю использованную способность союзника. Украденная способность заменяет текущую украденную способность и остаётся до следующей кражи." + "DOTA_Tooltip_ability_ability_rubick_spellsteal_custom_AbilityCastRange" "ДАЛЬНОСТЬ:" + "DOTA_Tooltip_ability_ability_rubick_spellsteal_custom_Lore" "Истинный маг не создаёт заклинания — он заимствует их у тех, кто недостаточно осторожен. Рубик впитывает магию врага, делая её своей." + + "DOTA_Tooltip_ability_special_bonus_unique_rubick_telekinesis_stun" "+{s:bonus_land_stun_duration} сек к оглушению при приземлении Telekinesis" + "DOTA_Tooltip_ability_special_bonus_unique_rubick_telekinesis_land_radius" "+{s:bonus_land_stun_radius} к радиусу приземления Telekinesis" + "DOTA_Tooltip_ability_special_bonus_unique_rubick_fade_bolt_damage" "+{s:bonus_damage} урона Fade Bolt" + "DOTA_Tooltip_ability_special_bonus_unique_rubick_spellsteal_cooldown" "-{s:bonus_AbilityCooldown} сек перезарядки Spell Steal" + "DOTA_Tooltip_ability_special_bonus_unique_rubick_arcane_supremacy_damage_pct" "+{s:bonus_damage_pct}% распределяемого урона Arcane Supremacy" + "DOTA_Tooltip_ability_special_bonus_unique_rubick_telekinesis_charges" "+{s:bonus_AbilityCharges} заряда Telekinesis" + "DOTA_Tooltip_ability_special_bonus_unique_rubick_fade_bolt_convert" "Fade bolt также даёт магическое усиление завсящее от уменьшенного урона этой способностью" + + // -----------------Elder dragon smaug-------------------- + + "DOTA_Tooltip_ability_ability_fire_punishment" "fire punishment" + "DOTA_Tooltip_ability_ability_fire_punishment_Description" "Smaug поджигает свою цель на %AbilityDuration% секунд нанося ей переодический урон %damage% + %pct_dmg%%% своего урона, также снижая её сопротивление магии на %debuff_magic%%%." + "DOTA_Tooltip_ability_ability_fire_punishment_Lore" "Обжигает да?" + "DOTA_Tooltip_ability_ability_fire_punishment_radius" "РАДИУС ПОДЖОГА:" + + "DOTA_Tooltip_ability_ability_jakiro_buff_1" "Fly to moon" + "DOTA_Tooltip_ability_ability_jakiro_buff_1_Description" "

Пасивное: Dragon fly

Smaug взлетает вверх из-за чего его манёвренность теряется на 25% но, из-за этого он может проходить сквозь припядствия." + "DOTA_Tooltip_ability_ability_jakiro_buff_1_Lore" "Крылатый" + + "DOTA_Tooltip_ability_ability_incandescent_fury" "incandescent fury" + "DOTA_Tooltip_ability_ability_incandescent_fury_Description" "Smaug извергает из себя лаву нанося урон %damage% + %pct_dmg%%% своего урона противникам с интервалом %burn_interval% сек." + "DOTA_Tooltip_ability_ability_incandescent_fury_Lore" "Самая ужасная смерть сгореть заживо, но мне об этом не нужно беспокоится - Smaug." + "DOTA_Tooltip_ability_ability_incandescent_fury_Scepter_Description" "Уменьшает перезарядку до %AbilityCooldown% секунд" + "DOTA_Tooltip_ability_ability_incandescent_fury_cast_range" "РАДИУС:" + "DOTA_Tooltip_ability_ability_incandescent_fury_duration" "ДЛИТЕЛЬНОСТЬ:" + + "DOTA_Tooltip_ability_ability_dragon_fear_aura" "dragon fear" + "DOTA_Tooltip_ability_ability_dragon_fear_aura_Description" "Враги атакуя Smaug не могут бороться с собственным страхом, что заставляет их слабее наносить свои удары, а сам Smaug получает дополнительный магический урон." + "DOTA_Tooltip_ability_ability_dragon_fear_aura_Lore" "Страшная аура Smaug не позволяет им даже перестать дрожать." + "DOTA_Tooltip_ability_ability_dragon_fear_aura_outgoing" "%ИСХОДЯЩИЙ УРОН:" + "DOTA_Tooltip_ability_ability_dragon_fear_aura_incoming" "%ВХОДЯЩИЙ УРОН:" + "DOTA_Tooltip_ability_ability_dragon_fear_aura_duration" "ДЛИТЕЛЬНОСТЬ:" + + "DOTA_Tooltip_ability_ability_dragon_gold_deal" "Dragon gold deal" + "DOTA_Tooltip_ability_ability_dragon_gold_deal_Description" "Smaug очень ценит свои вещи и извлекает из них больше пользы, получая дополнительно броню, урон и здоровье." + "DOTA_Tooltip_ability_ability_dragon_gold_deal_Lore" "Smaug - золото это то, что делает богаче, однако для меня это сила." + "DOTA_Tooltip_ability_ability_dragon_gold_deal_gold" "КОЛИЧЕСТВО ЗОЛОТА ДЛЯ СТАКА:" + "DOTA_Tooltip_ability_ability_dragon_gold_deal_physical_armor" "ДОП. БРОНЯ:" + "DOTA_Tooltip_ability_ability_dragon_gold_deal_spell_amp" "ДОП. МАГ. УРОН:" + "DOTA_Tooltip_ability_ability_dragon_gold_deal_health_bonus" "ДОП. ЗДОРОВЬЯ:" + "DOTA_Tooltip_ability_modifier_dragon_gold_deal_buff" "Золотое безумие" + "DOTA_Tooltip_ability_modifier_dragon_gold_deal_buff_Description" "Благодаря своим Еврейским наклоностям герой получает бонусом: урон, здоровье и броню." + + "DOTA_Tooltip_ability_ability_dragon_reward" "dragon reward" + "DOTA_Tooltip_ability_ability_dragon_reward_Description" "Smaug использует души умерших для своих целей" + "DOTA_Tooltip_ability_ability_dragon_reward_Lore" "Коллекция душ, тут у меня есть и рыбак, и охотник, и даже душа жабы? Ну ладно. Сойдёт." + "DOTA_Tooltip_ability_ability_dragon_reward_max" "МАКСИМУМ ДУШ:" + "DOTA_Tooltip_ability_ability_dragon_reward_bonus_attributes" "БОНУС АТРИБУТОВ ЗА СТАК:" + "DOTA_Tooltip_ability_modifier_dragon_reward" "Накопленные души" + "DOTA_Tooltip_ability_modifier_dragon_reward_Description" "Герой получил бонусных атрибутов." + + "DOTA_Tooltip_ability_ability_dragon_scales" "Dragon scale" + "DOTA_Tooltip_ability_ability_dragon_scales_Description" "Smaug окутывает своё тело чешуёй благодаря которой он избегает любого урона и отражает его в противника." + "DOTA_Tooltip_ability_ability_dragon_scales_Lore" "Из этой чешуи можно выковать непробиваемые доспехи, но чешуёй Smaug делится не планирует." + "DOTA_Tooltip_ability_ability_dragon_scales_reflect" "%ОТРАЖЕНИЕ:" + "DOTA_Tooltip_ability_ability_dragon_scales_duration" "ДЛИТЕЛЬНОСТЬ:" + "DOTA_Tooltip_ability_ability_dragon_scales_max_radius" "РАДИУС:" + "DOTA_Tooltip_ability_modifier_dragon_scales" "Чешуя дракона" + "DOTA_Tooltip_ability_modifier_dragon_scales_Description" "Ваше тело стало крепче и начало отражать урон." + + "DOTA_Tooltip_ability_special_bonus_unique_smaug_dragon_scales_reflect" "+{s:bonus_reflect}% к отражению scale reflect" + "DOTA_Tooltip_ability_special_bonus_unique_smaug_dragon_scales_cd" "-{s:bonus_AbilityCooldown} секунд перезарядки scale reflect" + "DOTA_Tooltip_ability_special_bonus_unique_smaug_dragon_gold_deal" "-{s:bonus_gold} золота для получения dragon gold deal" + "DOTA_Tooltip_ability_special_bonus_unique_smaug_incandescent_fury" "+{s:bonus_damage} урона способности incandescent fury" + "DOTA_Tooltip_ability_special_bonus_unique_smaug_fire_punishment" "+100% к способности fire punishment" + + // -----------------Nagash-------------------- + + "DOTA_Tooltip_ability_dark_friends" "dark friends" + "DOTA_Tooltip_ability_dark_friends_Description" "Призывает темного союзника на каждый уровень способности. Каждый призванный юнит даёт ауру союзникам в радиусе %aura_radius%, Эффекты ауры складываются от каждого призванного юнита." + "DOTA_Tooltip_ability_dark_friends_Lore" "В тени древних катакомб, где покоятся души павших воинов, Нагаш научился призывать верных слуг из мира мертвых. Эти темные друзья, некогда великие воины, теперь служат ему в вечной битве, неся с собой силу и мудрость прошлых эпох." + "DOTA_Tooltip_ability_dark_friends_Note0" "Это существо получает модификаторы крита своего владельца." + "DOTA_Tooltip_ability_dark_friends_aura_bonus_damage" "ДОП УРОНА" + "DOTA_Tooltip_ability_dark_friends_aura_bonus_attack_speed" "ДОП СКОРОСТИ АТАКИ" + "DOTA_Tooltip_ability_dark_friends_aura_bonus_movespeed_pct" "%ДОП СКОРОСТИ ПЕРЕДВИЖЕНИЯ" + "DOTA_Tooltip_ability_dark_friends_aura_bonus_armor" "ДОП БРОНИ" + + "DOTA_Tooltip_modifier_dark_friends_create" "Dark friends beyond" + "DOTA_Tooltip_modifier_dark_friends_create_Description" "герой получает %dMODIFIER_PROPERTY_PREATTACK_BONUS_DAMAGE% урону, %dMODIFIER_PROPERTY_ATTACKSPEED_BONUS_CONSTANT% скорости атаки, %dMODIFIER_PROPERTY_MOVESPEED_BONUS_PERCENTAGE%%% скорости передвижения и %dMODIFIER_PROPERTY_PHYSICAL_ARMOR_BONUS% брони." + + "DOTA_Tooltip_ability_world_destroyer" "world destroyer" + "DOTA_Tooltip_ability_world_destroyer_Description" "Нагаш призывает разрушительную силу из глубин ада, создавая взрыв в указанной точке. Взрыв высасывает души врагов и они начинают сражаться за его сторону на %dominate_duration% секунд. Высасыватель душ дополнительно получает 50% здоровья владельца." + "DOTA_Tooltip_ability_world_destroyer_Lore" "Когда мир трещит по швам, Нагаш знает, как использовать эти трещины в свою пользу." + "DOTA_Tooltip_ability_world_destroyer_Note0" "Может приручить лишь существ уровнем ниже своего." + "DOTA_Tooltip_ability_world_destroyer_radius" "РАДИУС:" + "DOTA_Tooltip_ability_world_destroyer_health_soul_eater" "ЗДОРОВЬЕ ВЫСАСЫВАТЕЛЯ ДУШ:" + "DOTA_Tooltip_ability_world_destroyer_dominate_duration" "ДЛИТЕЛЬНОСТЬ ДУШ:" + "DOTA_Tooltip_ability_world_destroyer_bonus_damage" "УРОН ВЗРЫВА:" + "DOTA_Tooltip_ability_world_destroyer_bonus_attack_speed" "ДОП. СКОРОСТЬ АТАКИ:" + "DOTA_Tooltip_ability_world_destroyer_bonus_armor" "ДОП. БРОНЯ:" + "DOTA_Tooltip_ability_world_destroyer_Shard_Description" "Нагаш становится неуязвимым на %shard_invulnerable_duration% сек. при использовании World Destroyer или Leader Call." + + "DOTA_Tooltip_ability_leader_call" "leader call" + "DOTA_Tooltip_ability_leader_call_Description" "Нагаш жертвует часть своего здоровья, чтобы призвать взрывную силу в своих подченённых душах. Взрыв наносит %explosion_damage% урона в радиусе %explosion_radius% и даёт бонусы к характеристикам за каждую взорванную или погибшую когда-либо душу. При смерти герой теряет %lost_souls_pct%%% своих накопленных душ." + "DOTA_Tooltip_ability_leader_call_Lore" "Лидер должен быть готов пожертвовать всем ради победы." + "DOTA_Tooltip_ability_leader_call_explosion_radius" "РАДИУС ВЗРЫВА:" + "DOTA_Tooltip_ability_leader_call_explosion_damage" "УРОН ВЗРЫВА:" + "DOTA_Tooltip_ability_leader_call_bonus_stats_per_soul" "БОНУС К ХАРАКТЕРИСТИКАМ ЗА ДУШУ:" + + "DOTA_Tooltip_modifier_leader_token" "Soul tokens" + "DOTA_Tooltip_modifier_leader_token_Description" "Ничего особого, просто количество душ которые герой успел поглатить за всё время." + + "DOTA_Tooltip_ability_fighting_up" "fighting up" + "DOTA_Tooltip_ability_fighting_up_Description" "Нагаш впадает в боевую ярость, увеличивая урон, скорость передвижения и скорость атаки для всех союзников в радиусе %radius% на %duration% секунд." + "DOTA_Tooltip_ability_fighting_up_Lore" "В бою нет места сомнениям - только ярость и решимость." + "DOTA_Tooltip_ability_fighting_up_radius" "РАДИУС:" + "DOTA_Tooltip_ability_fighting_up_duration" "ДЛИТЕЛЬНОСТЬ:" + "DOTA_Tooltip_ability_fighting_up_bonus_damage_pct" "%ДОП. УРОНА:" + "DOTA_Tooltip_ability_fighting_up_bonus_movespeed_pct" "%ДОП. СКОРОСТИ ПЕРЕДВИЖЕНИЯ:" + "DOTA_Tooltip_ability_fighting_up_bonus_attackspeed_pct" "%ДОП. СКОРОСТИ АТАКИ:" + + "DOTA_Tooltip_ability_war_for_life" "war for life" + "DOTA_Tooltip_ability_war_for_life_Description" "Нагаш объявляет войну смерти, воскрешая всех умерших союзников в радиусе %radius% с %health_res_pct%%% здоровьем и даруя временное %inc_damage_res%%% сопротивления к урону на %duration% секунд." + "DOTA_Tooltip_ability_war_for_life_Lore" "Жизнь - это величайшая битва, и Нагаш готов сражаться до конца." + "DOTA_Tooltip_ability_war_for_life_radius" "РАДИУС:" + "DOTA_Tooltip_ability_war_for_life_health_res_pct" "%ЗДОРОВЬЕ ВОСКРЕСНУВШЕГО:" + "DOTA_Tooltip_ability_war_for_life_inc_damage_res" "%СОПРОТИВЛЕНИЕ К УРОНУ:" + "DOTA_Tooltip_ability_war_for_life_duration" "ДЛИТЕЛЬНОСТЬ:" + + "DOTA_Tooltip_ability_dark_golem" "dark golem" + "DOTA_Tooltip_ability_dark_golem_Description" "Нагаш становится могущественным темным големом, который атакует с прорубанием на %cleave_damage%%% урона в радиусе %cleave_distance%. Голем получает бонусы к урону, скорости атаки и скорости передвижения за каждую душу на %duration% секунды." + "DOTA_Tooltip_ability_dark_golem_Lore" "Из глубин ада приходит величайший слуга тьмы - голем, выкованный из душ павших воинов." + "DOTA_Tooltip_ability_dark_golem_cleave_damage" "%УРОН ПРОРУБАНИЯ:" + "DOTA_Tooltip_ability_dark_golem_cleave_distance" "РАССТОЯНИЕ ПРОРУБАНИЯ:" + "DOTA_Tooltip_ability_dark_golem_cleave_starting_width" "НАЧАЛЬНАЯ ШИРИНА ПРОРУБАНИЯ:" + "DOTA_Tooltip_ability_dark_golem_cleave_ending_width" "КОНЕЧНАЯ ШИРИНА ПРОРУБАНИЯ:" + "DOTA_Tooltip_ability_dark_golem_bonus_damage_pct_per_token" "%УРОНА ЗА ДУШУ:" + "DOTA_Tooltip_ability_dark_golem_bonus_attack_speed_pct_per_token" "%СКОРОСТИ АТАКИ ЗА ДУШУ:" + "DOTA_Tooltip_ability_dark_golem_bonus_movespeed_pct_per_token" "%СКОРОСТИ ПЕРЕДВИЖЕНИЯ ЗА ДУШУ:" + "DOTA_Tooltip_ability_dark_golem_duration" "ДЛИТЕЛЬНОСТЬ:" + + "DOTA_Tooltip_modifier_dark_golem_buff" "Dark golem" + "DOTA_Tooltip_modifier_dark_golem_buff_Description" "Вас не остановить." + + "DOTA_Tooltip_ability_special_bonus_unique_nagash_leader_call_damage" "+{s:bonus_unit_health_for_damage}% урона от максимального здоровья завербованного существа leader call" + "DOTA_Tooltip_ability_special_bonus_unique_nagash_dark_friend_damage_pct" "+{s:bonus_outgoing_damage_pct}% урона существам dark friends" + + + + + + // -----------------Blood hunter-------------------- + + "DOTA_Tooltip_ability_ability_bloodrage" "Blood rage" + "DOTA_Tooltip_ability_ability_bloodrage_Description" "Blood Hunter разрывает свою жертву на части, поглощая её кровь и получая стаки в колчичестве %stack% на %stack_duration% секунд. За каждый стак герой получает бонусный урон и скорость атаки." + "DOTA_Tooltip_ability_ability_bloodrage_physical_vampirism" "%ДОП.ВАМПИРИЗМ:" + "DOTA_Tooltip_ability_ability_bloodrage_Lore" "Его когти острее любого клинка." + "DOTA_Tooltip_ability_ability_bloodrage_bonus_damage" "ДОП.УРОН:" + "DOTA_Tooltip_ability_ability_bloodrage_bonus_attack_speed" "ДОП.СКОРОСТЬ АТАКИ:" + "DOTA_Tooltip_ability_ability_bloodrage_stack_damage" "УРОН ЗА СТАК:" + "DOTA_Tooltip_ability_ability_bloodrage_stack_attackspeed" "СКОРОСТЬ АТАКИ ЗА СТАК:" + "DOTA_Tooltip_ability_ability_bloodrage_duration" "ДЛИТЕЛЬНОСТЬ:" + "DOTA_Tooltip_ability_ability_bloodrage_scepter_Description" "Герой получает по %stack% стаков." + + "DOTA_Tooltip_ability_ability_bloodstained_memory" "bloodstained memory" + "DOTA_Tooltip_ability_ability_bloodstained_memory_Description" "Сила находится в сознании твоего предрасудка и Blood Hunter знает как эту силу вызволить наружу." + "DOTA_Tooltip_ability_ability_bloodstained_memory_healthbonus" "ДОП. ЗДОРОВЬЕ ЗА СТАК:" + "DOTA_Tooltip_ability_ability_bloodstained_memory_movespeed" "СКОРОСТЬ ВОСПРИЯТИЯ РЕАЛЬНОСТИ ЗА СТАК:" + "DOTA_Tooltip_ability_ability_bloodstained_memory_Lore" "Нет ничего сильнее моего разума -- Blood Hunter." + + + + "DOTA_Tooltip_ability_ability_illusion_of_blood" "illusion of blood" + "DOTA_Tooltip_ability_ability_illusion_of_blood_Description" "Blood Hunter обращается за помощью к мертвецам жертвуя 15 стаки крови, призывает свои копии в мир для защиты себя." + "DOTA_Tooltip_ability_ability_illusion_of_blood_Lore" "Нет ничего лучше чем я - egoist Blood Hunter" + "DOTA_Tooltip_ability_ability_illusion_of_blood_images_count" "КОЛИЧЕСТВО ИЛЛЮЗИЙ:" + "DOTA_Tooltip_ability_ability_illusion_of_blood_blood" "ЗАТРАТА КРОВИ:" + "DOTA_Tooltip_ability_ability_illusion_of_blood_illusion_duration" "ВРЕМЯ ЖИЗНИ ИЛЛЮЗИИ:" + "DOTA_Tooltip_ability_ability_illusion_of_blood_outgoing_damage" "%УРОН ОТ ИЛЛЮЗИИ:" + "DOTA_Tooltip_ability_ability_illusion_of_blood_incoming_damage" "%УРОН ПО ИЛЛЮЗИИ:" + "DOTA_Tooltip_ability_ability_illusion_of_blood_magic_resistance" "%МАГ.СОПР.ИЛЛЮЗИИ:" + + "DOTA_Tooltip_ability_ability_sacrifice_revenge" "sacrifice revenge" + "DOTA_Tooltip_ability_ability_sacrifice_revenge_Description" "Впадая в ярость Blood hunter показывает свою сущность и его сила вырывается наружу на %duration% секунд." + "DOTA_Tooltip_ability_ability_sacrifice_revenge_Lore" "Всегда нужно чем-то жертвовать, Blood Hunter это знал с рождения." + "DOTA_Tooltip_ability_ability_sacrifice_revenge_base_attack_time" "БАЗОВЫЙ ИНТЕРВАЛ АТАК:" + "DOTA_Tooltip_ability_ability_sacrifice_revenge_bonus_armor" "МНОЖИТЕЛЬ БРОНИ:" + "DOTA_Tooltip_ability_ability_sacrifice_revenge_shard_Description" "Способность также наносит %pure_damage%%% чистого урона" + + "DOTA_Tooltip_ability_ability_ring_of_corrosive" "ring of corrosive" + "DOTA_Tooltip_ability_ability_ring_of_corrosive_Description" "Blood Hunter проводит ритуал крови, во имя Кровавого братства. Герой создаёт кровавый круг на %duration% секунд, все противники в котором получают каждые %tick_rate% секунды чистый урон, равный %damage% + %damage_mult%%% от своего урона." + "DOTA_Tooltip_ability_ability_ring_of_corrosive_Lore" "Безобидный круг в который лучше не ступать." + "DOTA_Tooltip_ability_ability_ring_of_corrosive_radius" "РАДИУС:" + + + + "DOTA_Tooltip_ability_special_bonus_unique_blood_hunter_bloodstained_health" "+{s:bonus_healthbonus} бонусного здоровья bloodstained memory" + "DOTA_Tooltip_ability_special_bonus_unique_blood_hunter_duration_blood" "+{s:bonus_duration} секунд длительности bloodrage" + "DOTA_Tooltip_ability_special_bonus_unique_blood_hunter_blood_of_illusions_count" "+{s:bonus_images_count} количеству вызываемых иллюзий" + "DOTA_Tooltip_ability_special_bonus_unique_blood_hunter_blood_of_illusions_incoming_damage" "-{s:bonus_incoming_damage}% входящего урона по иллюзиям" + "DOTA_Tooltip_ability_special_bonus_unique_blood_hunter_pure_damage_bonus" "+{s:bonus_pure_damage}% чистого урона sacrifice revenge" + "DOTA_Tooltip_ability_special_bonus_unique_blood_hunter_sacrifice_revenge_duration" "+{s:bonus_duration} к длительности sacrifice revenge" + + + "DOTA_Tooltip_modifier_bloodrage_buff" "Bloodrage" + "DOTA_Tooltip_modifier_bloodrage_buff_Description" "Вы ощущаете себя животным." + "DOTA_Tooltip_modifier_sacrifice_revenge_buff" "Madness" + "DOTA_Tooltip_modifier_sacrifice_revenge_buff_Description" "В своей истинной форме" + "DOTA_Tooltip_modifier_bloodstained_memory" "Bloodstained Memory" + "DOTA_Tooltip_modifier_bloodstained_memory_Description" "Ваш разум быстрее скорости света." + + + + + + + // -----------------Sniper-------------------- + + "DOTA_Tooltip_ability_ability_sniper_shrapnel_custom" "Shrapnel" + "DOTA_Tooltip_ability_ability_sniper_shrapnel_custom_Description" "Выстрел в выбранную точку: через %damage_delay% сек. появляется облако шрапнели радиусом %radius% на %duration% сек. Враги в зоне каждые %tick_interval% сек. получают физический урон в размере %attack_damage_pct%%% от атаки Снайпера, замедление передвижения на %slow_movement_speed%%% и получают на %incoming_damage_pct%%% больше урона." + "DOTA_Tooltip_ability_ability_sniper_shrapnel_custom_Lore" "Карл спешит поделиться с врагами металлоломом." + "DOTA_Tooltip_ability_ability_sniper_shrapnel_custom_radius" "РАДИУС:" + "DOTA_Tooltip_ability_ability_sniper_shrapnel_custom_damage_delay" "ЗАДЕРЖКА ДО ЗОНЫ:" + "DOTA_Tooltip_ability_ability_sniper_shrapnel_custom_duration" "ДЛИТЕЛЬНОСТЬ ЗОНЫ:" + "DOTA_Tooltip_ability_ability_sniper_shrapnel_custom_attack_damage_pct" "% УРОНА ОТ АТАКИ ЗА ТИК:" + "DOTA_Tooltip_ability_ability_sniper_shrapnel_custom_tick_interval" "ИНТЕРВАЛ УРОНА:" + "DOTA_Tooltip_ability_ability_sniper_shrapnel_custom_slow_movement_speed" "%ЗАМЕДЛЕНИЕ ПЕРЕДВИЖЕНИЯ:" + "DOTA_Tooltip_ability_ability_sniper_shrapnel_custom_incoming_damage_pct" "%УСИЛЕНИЕ ПОЛУЧАЕМОГО УРОНА:" + + "DOTA_Tooltip_ability_ability_sniper_critical_focus_custom" "Critical Focus" + "DOTA_Tooltip_ability_ability_sniper_critical_focus_custom_Description" "На %duration% сек. каждая ваша атака становится критическим ударом, суммируя все источники критов; дополнительно добавляет к пулу критов %crit_chance%%% шанс и множитель %crit_mult%%%." + "DOTA_Tooltip_ability_ability_sniper_critical_focus_custom_Lore" "На время — только в яблочко." + "DOTA_Tooltip_ability_ability_sniper_critical_focus_custom_duration" "ДЛИТЕЛЬНОСТЬ:" + "DOTA_Tooltip_ability_ability_sniper_critical_focus_custom_crit_chance" "%ШАНС ДОП. КРИТА:" + "DOTA_Tooltip_ability_ability_sniper_critical_focus_custom_crit_mult" "%МНОЖИТЕЛЬ ДОП. КРИТА:" + + "DOTA_Tooltip_ability_ability_sniper_keen_eye_custom" "Keen Eye" + "DOTA_Tooltip_ability_ability_sniper_keen_eye_custom_Description" "Пассивно: +%bonus_attack_range% к дальности атаки. С шансом %proc_chance%%% (с учётом удачи) удар наносит дополнительный физический урон %headshot_bonus_damage%, отбрасывает цель и на %slow_duration% сек. замедляет передвижение на %slow_movement_pct%%% и атаку на %slow_attack_speed%. Макс. отталкивание — %knockback_distance%." + "DOTA_Tooltip_ability_ability_sniper_keen_eye_custom_Lore" "Глаз не моргнёт." + "DOTA_Tooltip_ability_ability_sniper_keen_eye_custom_bonus_attack_range" "ДОП. ДАЛЬНОСТЬ АТАКИ:" + "DOTA_Tooltip_ability_ability_sniper_keen_eye_custom_proc_chance" "%ШАНС ХЕДШОТА:" + "DOTA_Tooltip_ability_ability_sniper_keen_eye_custom_headshot_bonus_damage" "ДОП. УРОН ХЕДШОТА:" + "DOTA_Tooltip_ability_ability_sniper_keen_eye_custom_slow_duration" "ДЛИТЕЛЬНОСТЬ ЗАМЕДЛЕНИЯ:" + "DOTA_Tooltip_ability_ability_sniper_keen_eye_custom_knockback_distance" "ОТТАЛКИВАНИЕ (МАКС.):" + "DOTA_Tooltip_ability_ability_sniper_keen_eye_custom_slow_movement_pct" "%ЗАМЕДЛ. ПЕРЕДВИЖЕНИЯ:" + "DOTA_Tooltip_ability_ability_sniper_keen_eye_custom_slow_attack_speed" "ЗАМЕДЛ. АТАКИ:" + + "DOTA_Tooltip_ability_ability_sniper_assassinate_custom" "Assassinate" + "DOTA_Tooltip_ability_ability_sniper_assassinate_custom_Description" "В фазу прицеливания враги в радиусе %aoe_radius% у курсора получают метку; затем по каждой отмеченной цели (или по выбранной цели, если в зоне никого нет) вылетает снаряд. При попадании — стан, гарантированный крит, атака, бонусный урон %attack_damage_bonus% и метка брони на %armor_mark_duration% сек. Аганим: дольше стан и ниже перезарядка." + "DOTA_Tooltip_ability_ability_sniper_assassinate_custom_scepter_Description" "Сокращённая перезарядка и длительнее оглушение." + "DOTA_Tooltip_ability_ability_sniper_assassinate_custom_Lore" "Один выстрел — один вопрос меньше." + "DOTA_Tooltip_ability_ability_sniper_assassinate_custom_aoe_radius" "РАДИУС МЕТКИ:" + "DOTA_Tooltip_ability_ability_sniper_assassinate_custom_debuff_duration" "ДЛИТЕЛЬНОСТЬ МЕТКИ:" + "DOTA_Tooltip_ability_ability_sniper_assassinate_custom_projectile_speed" "СКОРОСТЬ СНАРЯДА:" + "DOTA_Tooltip_ability_ability_sniper_assassinate_custom_ministun_duration" "ДЛИТЕЛЬНОСТЬ ОГЛУШЕНИЯ:" + "DOTA_Tooltip_ability_ability_sniper_assassinate_custom_scepter_stun_duration" "СТАН С АГАНИМОМ:" + "DOTA_Tooltip_ability_ability_sniper_assassinate_custom_scepter_cooldown" "ПЕРЕЗАРЯДКА С АГАНИМОМ:" + "DOTA_Tooltip_ability_ability_sniper_assassinate_custom_attack_damage_bonus" "БОНУСНЫЙ УРОН:" + "DOTA_Tooltip_ability_ability_sniper_assassinate_custom_armor_mark_duration" "ДЛИТЕЛЬНОСТЬ МЕТКИ БРОНИ:" + + "DOTA_Tooltip_modifier_sniper_critical_focus_active" "Critical Focus" + "DOTA_Tooltip_modifier_sniper_critical_focus_active_Description" "Каждая атака критует с суммированием всех источников критов." + "DOTA_Tooltip_modifier_sniper_assassinate_mark" "Assassinate" + "DOTA_Tooltip_modifier_sniper_assassinate_mark_Description" "Броня снижена примерно наполовину для эффекта пробития." + + "dota_tooltip_ability_special_bonus_unique_sniper_shrapnel_radius" "+{s:bonus_radius} к радиусу Shrapnel" + "dota_tooltip_ability_special_bonus_unique_sniper_keen_range" "+{s:bonus_bonus_attack_range} к дальности атаки от Keen Eye" + "dota_tooltip_ability_special_bonus_unique_sniper_shrapnel_cooldown" "{s:bonus_AbilityCooldown} сек. перезарядки Shrapnel" + "dota_tooltip_ability_special_bonus_unique_sniper_critical_focus_duration" "+{s:bonus_duration} сек. к длительности Critical Focus" + "dota_tooltip_ability_special_bonus_unique_sniper_critical_focus_crit_mult" "+{s:bonus_crit_mult} к множителю крита Critical Focus" + "dota_tooltip_ability_special_bonus_unique_sniper_keen_proc" "+{s:bonus_proc_chance}% к шансу хедшота Keen Eye" + "dota_tooltip_ability_special_bonus_unique_sniper_assassinate_aoe" "+{s:bonus_aoe_radius} к радиусу метки Assassinate" + "dota_tooltip_ability_special_bonus_unique_sniper_assassinate_damage" "+{s:bonus_attack_damage_bonus} к бонусному урону Assassinate" + + + // -----------------Hoodwink-------------------- + + "DOTA_Tooltip_ability_hoodwink_acorn_shot_custom" "Acorn Shot" + "DOTA_Tooltip_ability_hoodwink_acorn_shot_custom_Description" "Герой совершает атаку зарядив особый жёлудь, который отскакивает во врагов неподалёку, а также замедляет всех жертв и наносит им базовый урон, к которому прибавляется доля урона от атаки.\nМожно применить на точку, чтобы создать дерево, после чего жёлудь отскочит в ближайших врагов." + "DOTA_Tooltip_ability_hoodwink_acorn_shot_custom_scepter_Description" "При обычной атаке у героя есть шанс выстрелить жёлудем с уменьшенным количеством отскоков." + "DOTA_Tooltip_ability_hoodwink_acorn_shot_custom_Lore" "Прохвостка совсем не привередливая, но из желудей дубового и железного дерева получаются самые эффективные снаряды - их она и припасла больше всего." + "DOTA_Tooltip_ability_hoodwink_acorn_shot_custom_bounce_range" "РАДИУС ОТСКОКА:" + "DOTA_Tooltip_ability_hoodwink_acorn_shot_custom_bounce_count" "ОТСКОКОВ:" + "DOTA_Tooltip_ability_hoodwink_acorn_shot_custom_debuff_duration" "ДЛИТЕЛЬНОСТЬ ЗАМЕДЛЕНИЯ:" + "DOTA_Tooltip_ability_hoodwink_acorn_shot_custom_slow" "%ЗАМЕДЛЕНИЕ ПЕРЕДВИЖЕНИЯ:" + "DOTA_Tooltip_ability_hoodwink_acorn_shot_custom_acorn_shot_damage" "БАЗОВЫЙ УРОН:" + "DOTA_Tooltip_ability_hoodwink_acorn_shot_custom_base_damage_pct" "%ДОЛЯ УРОНА ОТ АТАКИ:" + "DOTA_Tooltip_ability_hoodwink_acorn_shot_custom_bounce_count_scepter" "ОТСКОКОВ ОТ АТАКИ:" + "DOTA_Tooltip_ability_hoodwink_acorn_shot_custom_scepter_chance" "%ШАНС ВЫПУСТИТЬ ЖЁЛУДЬ:" + "DOTA_Tooltip_ability_hoodwink_acorn_shot_custom_Note0" "Жёлудь может отскочить в одну и ту же цель несколько раз." + "DOTA_Tooltip_ability_hoodwink_acorn_shot_custom_Note1" "Жёлудь не может отскочить в невидимое существо." + "DOTA_Tooltip_ability_hoodwink_acorn_shot_custom_Note2" "Дерево существует %tree_duration% секунд." + + "DOTA_Tooltip_ability_hoodwink_bushwhack_custom" "Bushwhack" + "DOTA_Tooltip_ability_hoodwink_bushwhack_custom_Description" "Герой бросает сеть-ловушку, которая оглушает врагов в выбранной области, если в ней есть деревья. Жертвы получают урон, притягиваются к ближайшему дереву и лишаются обзора на время оглушения." + "DOTA_Tooltip_ability_hoodwink_bushwhack_custom_Lore" "Прохвостка предпочитает устраивать засады в зарослях железных деревьев, но самые осмотрительные гости туманной рощи не теряют бдительности никогда." + "DOTA_Tooltip_ability_hoodwink_bushwhack_custom_radius" "РАДИУС:" + "DOTA_Tooltip_ability_hoodwink_bushwhack_custom_duration" "ДЛИТЕЛЬНОСТЬ ОГЛУШЕНИЯ:" + "DOTA_Tooltip_ability_hoodwink_bushwhack_custom_total_damage" "СУММАРНЫЙ УРОН:" + "DOTA_Tooltip_ability_hoodwink_bushwhack_custom_Shard_Description" "Враги попавшие в сеть дополнительно получают %damage_multiplier%%% урона владельца." + + "DOTA_Tooltip_ability_hoodwink_scurry_custom" "Scurry" + "DOTA_Tooltip_ability_hoodwink_scurry_custom_Description" "Если Hoodwink находится рядом с деревом в радиусе %radius%, то её силы сильно возрастают: дополнительный урон, скорость атаки и снижение входящего урона на %incoming_damage_reduction_pct%%%. При активации ускоряет передвижение, а также сохраняет бонусы даже если деревьев нет рядом. Дополнительно способность даёт %luck_evasion_multiplier%%% уклонения за каждую единицу удачи, но не более %max_luck_evasion%%%." + "DOTA_Tooltip_ability_hoodwink_scurry_custom_Lore" "В роще Томо'кан нет ни норки, ни веточки, куда не дотянулись бы лапки Прохвостки." + "DOTA_Tooltip_ability_hoodwink_scurry_custom_bonus_damage" "ДОП. УРОН:" + "DOTA_Tooltip_ability_hoodwink_scurry_custom_bonus_attack_speed" "ДОП. СКОРОСТЬ АТАКИ:" + "DOTA_Tooltip_ability_hoodwink_scurry_custom_movement_speed_pct" "%ДОП. СКОРОСТЬ ПЕРЕДВИЖЕНИЯ:" + "DOTA_Tooltip_ability_hoodwink_scurry_custom_duration" "ДЛИТЕЛЬНОСТЬ:" + "DOTA_Tooltip_ability_hoodwink_scurry_custom_luck_evasion_multiplier" "%УКЛОНЕНИЕ ЗА 1 УДАЧУ:" + "DOTA_Tooltip_ability_hoodwink_scurry_custom_max_luck_evasion" "%МАКС. УКЛОНЕНИЕ:" + "DOTA_Tooltip_ability_hoodwink_scurry_custom_incoming_damage_reduction_pct" "%СНИЖЕНИЕ ВХОДЯЩЕГО УРОНА:" + + "DOTA_Tooltip_ability_hoodwink_lucky_innate_custom" "Lucky Instinct" + "DOTA_Tooltip_ability_hoodwink_lucky_innate_custom_Description" "Врожденная способность. Hoodwink получает удачу за каждый уровень героя." + "DOTA_Tooltip_ability_hoodwink_lucky_innate_custom_luck_per_level" "УДАЧИ ЗА УРОВЕНЬ:" + + "DOTA_Tooltip_modifier_modifier_hoodwink_scurry_custom_buff" "Scurry" + "DOTA_Tooltip_modifier_modifier_hoodwink_scurry_custom_buff_Description" "Дает бонус к скорости атаки и урону, снижает входящий урон. Уклонение зависит от текущей удачи героя. Текущее значение уклонения показано в подсказке модификатора." + + "DOTA_Tooltip_ability_hoodwink_sharpshooter_custom" "Sharpshooter" + "DOTA_Tooltip_ability_hoodwink_sharpshooter_custom_Description" "Герой готовит смертельный выстрел из своего арбалета. Он проходит сквозь всех врагов, замедляет их и накладывает истощение. Урон и длительность эффекта становятся максимальными спустя %max_charge_time% сек. подготовки, а выстрел происходит автоматически спустя %misfire_time% сек.\nОтдача отталкивает героя назад на расстояние %recoil_distance%.\nДополнительно выстрел наносит урон от атаки владельца." + "DOTA_Tooltip_ability_hoodwink_sharpshooter_custom_Lore" "Первый арбалет-самострел Прохвостка нашла по случайности, но теперь она холит и лелеет свой собственный - и, разумеется, некоторые его детали она у кого-то стянула или добыла другим способом." + "DOTA_Tooltip_ability_hoodwink_sharpshooter_custom_max_charge_time" "МАКС. ВРЕМЯ ПОДГОТОВКИ:" + "DOTA_Tooltip_ability_hoodwink_sharpshooter_custom_max_damage" "МАКС. УРОН:" + "DOTA_Tooltip_ability_hoodwink_sharpshooter_custom_max_slow_debuff_duration" "МАКС. ДЛИТЕЛЬНОСТЬ ЭФФЕКТА:" + "DOTA_Tooltip_ability_hoodwink_sharpshooter_custom_slow_move_pct" "%ЗАМЕДЛЕНИЕ ПЕРЕДВИЖЕНИЯ:" + "DOTA_Tooltip_ability_hoodwink_sharpshooter_custom_arrow_speed" "СКОРОСТЬ СНАРЯДА:" + "DOTA_Tooltip_ability_hoodwink_sharpshooter_custom_damage_multiplier" "%УРОН ОТ АТАКИ:" + "DOTA_Tooltip_ability_hoodwink_sharpshooter_custom_Note0" "Выстрел дополнительно наносит %damage_multiplier%%% от среднего урона атаки владельца." + + "DOTA_Tooltip_ability_hoodwink_sharpshooter_release_custom" "End Sharpshooter" + "DOTA_Tooltip_ability_hoodwink_sharpshooter_release_custom_Description" "Совершить выстрел и вернуть способность передвигаться и атаковать." + "DOTA_Tooltip_ability_hoodwink_sharpshooter_release_custom_Lore" "Огонь!" + + "DOTA_Tooltip_ability_special_bonus_unique_hoodwink_scurry_charges" "+{s:bonus_AbilityCharges} заряд Scurry" + "DOTA_Tooltip_ability_special_bonus_unique_hoodwink_bushwhack_damage" "+{s:bonus_total_damage} к урону Bushwhack" + "DOTA_Tooltip_ability_special_bonus_unique_hoodwink_1_1" "Снаряд от Acorn Shot может отскочить от дерева" + "DOTA_Tooltip_ability_special_bonus_unique_hoodwink_4_1" "-{s:bonus_reduce_cooldown_ultimate} сек. от Sharpshooter за каждое попадание Acorn Shot" + "DOTA_Tooltip_ability_special_bonus_unique_hoodwink_acorn_shot_bounces" "+{s:bonus_bounce_count} отскока Acorn Shot" + "DOTA_Tooltip_ability_special_bonus_unique_hoodwink_3_1" "+{s:bonus_bonus_attack_speed}% к урону и скорости атаки от Scurry" + "DOTA_Tooltip_ability_special_bonus_unique_hoodwink_sharpshooter_damage" "+{s:bonus_max_damage} к урону Sharpshooter" + "DOTA_Tooltip_ability_special_bonus_unique_hoodwink_scurry_duration" "+{s:bonus_duration} сек. длительности Scurry" + + //default items + "dota_tooltip_ability_item_mega_treads" "Reinforced leather boot" + "dota_tooltip_ability_item_mega_treads_bonus_stat" "+к основному атрибуту" + "dota_tooltip_ability_item_mega_treads_bonus_other_stat" "+к остальным атрибутам" + "dota_tooltip_ability_item_mega_treads_bonus_movement_speed" "+$move_speed" + "dota_tooltip_ability_item_mega_treads_Description" "

Attribute Switching

Даёт %bonus_stat% к выбранному атрибуту и %bonus_other_stat% ко всем остальным. \n\n

Bonus of selected attribute:

Strength: +%bonus_magical_resistance%%% к магическому сопротивлению.

Intelligence: +%spell_amplify%%% к урону от заклинаний.

Agility: +%bonus_attackspeed% к скорости атаки." + "dota_tooltip_ability_item_mega_treads_Lore" "Эти ботинки были созданы легендарным кузнецом, который хотел превзойти обычные силовые ботинки. Говорят, что он экспериментировал с древней магией и редкими материалами, пока не создал этот могущественный артефакт." + "dota_tooltip_ability_item_mega_treads_Note0" "Можно получить несколько раз." + + "dota_tooltip_ability_item_bloodstone_magical" "Magical Bloodstone" + "dota_tooltip_ability_item_bloodstone_magical_bonus_health" "+$health" + "dota_tooltip_ability_item_bloodstone_magical_bonus_mana_special" "+$mana" + "dota_tooltip_ability_item_bloodstone_magical_bonus_health_regen" "+$hp_regen" + "dota_tooltip_ability_item_bloodstone_magical_spell_lifesteal" "%+$spell_lifesteal" + "dota_tooltip_ability_item_bloodstone_magical_Description" "

Magical Stone

При активации дополнительно даёт %spell_lifesteal_active%%% вампиризма заклинаний на %duration% секунд, а также %cooldown_reduction_active%%% сокращение перезарядки и %casttime_reduction%%% сокращение времени применения заклинаний." + "dota_tooltip_ability_item_bloodstone_magical_Lore" "Этот камень был найден в древнем храме, который скрывался под землей. Говорят, что он обладает магической силой, которая может исцелить и усилить." + + "dota_tooltip_modifier_item_bloodstone_magical_vampirism" "Blood weave" + "dota_tooltip_modifier_item_bloodstone_magical_vampirism_Description" "Герой получил %dMODIFIER_PROPERTY_CASTTIME_PERCENTAGE%%% сокращение времени применения заклинаний и %dMODIFIER_PROPERTY_COOLDOWN_PERCENTAGE%%% сокращение перезарядки способностей." + + "dota_tooltip_ability_item_bearstbow" "Bearstbow" + "dota_tooltip_ability_item_bearstbow_attack_speed" "+$attack" + "dota_tooltip_ability_item_bearstbow_attack_range" "+$attack_range" + "dota_tooltip_ability_item_bearstbow_damage" "+$damage" + "dota_tooltip_ability_item_bearstbow_strength" "+$str" + "dota_tooltip_ability_item_bearstbow_agility" "+$agi" + "dota_tooltip_ability_item_bearstbow_intellect" "+$int" + "dota_tooltip_ability_item_bearstbow_health" "+$health" + "dota_tooltip_ability_item_bearstbow_Description" "

Bearstbow

Атакует дополнительные %attack_count% цели в радиусе своей атаки." + "dota_tooltip_ability_item_bearstbow_Lore" "Лук, который был найден в древнем храме, который скрывался под землей. Говорят, что он обладает магической силой, которая может сломить даже льва." + + "DOTA_Tooltip_ability_item_magical_quiver" "Magical quiver" + "DOTA_Tooltip_ability_item_magical_quiver_Description" "

Пасивное: Magic Arrows

Атаки дальнего боя наносят дополнительно %proc_damage_magical% магического урона." + "DOTA_Tooltip_ability_item_magical_quiver_Lore" "Колчан, наполненный стрелами, пропитанными древней магией. Каждый выстрел из него несёт частицу разрушительной энергии." + + "DOTA_Tooltip_ability_item_demonic_bow" "Demonic bow" + "DOTA_Tooltip_ability_item_demonic_bow_attack_speed" "+$attack" + "DOTA_Tooltip_ability_item_demonic_bow_attack_range" "+$attack_range" + "DOTA_Tooltip_ability_item_demonic_bow_damage" "+$damage" + "DOTA_Tooltip_ability_item_demonic_bow_strength" "+$str" + "DOTA_Tooltip_ability_item_demonic_bow_agility" "+$agi" + "DOTA_Tooltip_ability_item_demonic_bow_intellect" "+$int" + "DOTA_Tooltip_ability_item_demonic_bow_health" "+$health" + "DOTA_Tooltip_ability_item_demonic_bow_Description" "

Пасивное: Multi-shot

Атаки дальнего боя поражают до %attack_count% дополнительных целей в радиусе атаки. Каждая атака наносит дополнительно %proc_damage_magical% магического урона. Атаки героя не могут промахнуться." + "DOTA_Tooltip_ability_item_demonic_bow_Lore" "Лук, выкованный из рогов падших демонов. Его тетива натянута жилами древнего чудовища, а стрелы несут проклятие, сжигающее души врагов." + + "dota_tooltip_ability_item_armlet_of_eternal_hunger" "armlet of eternal hunger" + "dota_tooltip_ability_item_armlet_of_eternal_hunger_Description" "

Активное: Eternal hunger

Если включена, то герой становиться безмолвлен, увеличивает урон на +%bonus_damage_active%, силу на +%bonus_str_active%, скорость атаки на %bonus_attack_speed_active% и скорость передвижения на %movespeed_active%.
Когда включена теряет %health_per_sec% здоровья в сек." + "dota_tooltip_ability_item_armlet_of_eternal_hunger_bonus_armor" "-к броне" + "dota_tooltip_ability_item_armlet_of_eternal_hunger_lifesteal" "%+$lifesteal" + "dota_tooltip_ability_item_armlet_of_eternal_hunger_bonus_damage" "+$damage" + "dota_tooltip_ability_item_armlet_of_eternal_hunger_bonus_attack_speed" "+$attack" + "dota_tooltip_ability_item_armlet_of_eternal_hunger_bonus_hp_regen" "+$hp_regen" + "dota_tooltip_ability_item_armlet_of_eternal_hunger_Lore" "Армлет Вечной Жажды был выкован в забытых кузницах древних вампиров, где кровь и сталь сливались воедино. Его носитель ощущает неутолимую жажду жизни, способную обратить даже смертельные раны в силу. Но за каждую каплю силы приходится платить собственной жизнью — и лишь самые отчаянные герои осмеливаются надеть этот проклятый артефакт." + "dota_tooltip_modifier_item_armlet_of_eternal_hunger_buff" "Armlet Of Eternal Hunger" + "dota_tooltip_modifier_item_armlet_of_eternal_hunger_buff_Description" "Урон увеличен на %dMODIFIER_PROPERTY_PREATTACK_BONUS_DAMAGE%. Сила увеличена на %dMODIFIER_PROPERTY_STATS_STRENGTH_BONUS%. Скорость атаки увеличена на %dMODIFIER_PROPERTY_ATTACKSPEED_BONUS_CONSTANT% и скорость передвижения увеличена на %dMODIFIER_PROPERTY_MOVESPEED_BONUS_CONSTANT%. Каждую секунду теряет 100 здоровья." + + "dota_tooltip_ability_item_mini_bfury" "mini fury" + "dota_tooltip_ability_item_mini_bfury_bonus_damage" "+$damage" + "dota_tooltip_ability_item_mini_bfury_Description" "

Активное: fury tree cutter

Разрушает в выбранной точке деревья в радиусе %tree%. \n\n

Пасивное: fury beast

Атаки героя прорубают жертву, нанося врагав в радиусе %cleave_distance% вокруг неё %cleave_damage%%% физического урона (Только в ближнем бою)" + "dota_tooltip_ability_item_mini_bfury_Lore" "Однажды великий викинг погибщий в ожесточайщей битве оставил этот топор на своей могиле, изучая его остроту люди научились делать аналог его древнейшего орудия." + + + "dota_tooltip_ability_item_storm" "stun gun" + "dota_tooltip_ability_item_storm_bonus_damage" "+$damage" + "dota_tooltip_ability_item_storm_Description" "

Пасивное: Electro

При атаке с шансом %chance%% поражает врага молнией: наносит %bolt_damage% магического урона и оглушает на %stun% сек. Эффект срабатывает 3 раза. Перезарядка: %ability_cooldown% сек." + "dota_tooltip_ability_item_storm_Lore" "Когда-то этот электрошокер был частью экспериментального вооружения, созданного для охоты на древних чудовищ. Его рукоять усеяна следами от молний, а внутри спрятан кристалл, способный накапливать и высвобождать разрушительную энергию. Говорят, что каждый разряд этого устройства оставляет на теле врага не только ожоги, но и отпечаток страха перед неукротимой силой природы." + + "dota_tooltip_modifier_storm_dps" "Electrified" + "dota_tooltip_modifier_storm_dps_Description" "Получает %bolt_damage% магического урона и оглушается на %stun% сек каждый тик." + + "dota_tooltip_ability_item_balist_custom" "Electro Ballista" + "dota_tooltip_ability_item_balist_custom_bonus_damage" "+$damage" + "dota_tooltip_ability_item_balist_custom_crit_multiplier" "+$crit_multiplier" + "dota_tooltip_ability_item_balist_custom_crit_chance" "%+$crit_chance" + "dota_tooltip_ability_item_balist_custom_crit_mult" "%+$crit_mult" + "dota_tooltip_ability_item_balist_custom_Description" "

Passive: Electro

При атаке с шансом %chance%%% поражает основную цель молнией 3 раза. Каждый удар также переходит на одну новую цель в радиусе %bolt_radius% (каждая доп. цель поражается только 1 раз). Наносит урон владельца + %bolt_damage% магического урона и оглушает на %stun% сек. Перезарядка: %ability_cooldown% сек." + "dota_tooltip_ability_item_balist_custom_Lore" "Улучшенная баллиста, объединяющая мощь электрошокера и тёмного кристаллиса. Её разряды не только парализуют врага, но и наносят сокрушительные удары. Создана для тех, кто охотится на самых опасных существ." + "dota_tooltip_ability_item_balist_custom_Note0" "На удар молний работает модификатор атаки." + "dota_tooltip_modifier_balist_custom_dps" "Electrified" + "dota_tooltip_modifier_balist_custom_dps_Description" "Получает %bolt_damage% магического урона и оглушается на %stun% сек каждый тик." + + "dota_tooltip_ability_item_battle_fury_custom" "Battle fury" + "dota_tooltip_ability_item_battle_fury_custom_bonus_damage" "+$damage" + "dota_tooltip_ability_item_battle_fury_custom_Description" "

Активное: fury tree cutter

Разрушает в выбранной точке деревья в радиусе %tree%. \n\n

Пасивное: fury beast

Атаки героя прорубают жертву, нанося врагав в радиусе %cleave_distance% вокруг неё %cleave_damage%%% физического урона (Только в ближнем бою)" + "dota_tooltip_ability_item_battle_fury_custom_Lore" "Однажды великий викинг погибщий в ожесточайщей битве оставил этот топор на своей могиле, изучая его остроту люди научились делать аналог его древнейшего орудия." + + "dota_tooltip_ability_item_mega_fury" "Mega fury" + "dota_tooltip_ability_item_mega_fury_bonus_damage" "+$damage" + "dota_tooltip_ability_item_mega_fury_attack_speed" "+$attack" + "dota_tooltip_ability_item_mega_fury_attack_range" "+$attack_range_melee" + "dota_tooltip_ability_item_mega_fury_Description" "

Активное: fury tree cutter

Разрушает в выбранной точке деревья в радиусе %tree%. \n\n

Пасивное: fury beast

Атаки героя прорубают жертву, нанося врагам в радиусе %cleave_distance% вокруг неё %cleave_damage%%% физический урон (только в ближнем бою). Атаки героя не могут промахнуться." + "dota_tooltip_ability_item_mega_fury_Lore" "Однажды великий викинг погибщий в ожесточайщей битве оставил этот топор на своей могиле, изучая его остроту люди научились делать аналог его древнейшего орудия." + + // Маски вампиризма + "DOTA_Tooltip_ability_item_lifesteal_custom" "Morbid mask" + "DOTA_Tooltip_ability_item_lifesteal_custom_lifesteal" "%+$lifesteal" + "DOTA_Tooltip_ability_item_lifesteal_custom_Description" "

Пасивное: Lifesteal

Излечивает владельца на долю от физического урона, который наносят его атаки." + "DOTA_Tooltip_ability_item_lifesteal_custom_Lore" "Маска, поглощающая энергию тех, кто попался ей на глаза." + "DOTA_Tooltip_modifier_modifier_item_lifesteal_custom" "Lifesteal" + "DOTA_Tooltip_modifier_modifier_item_lifesteal_custom_Description" "Излечивает владельца на долю от физического урона, который наносят его атаки." + + "DOTA_Tooltip_ability_item_voodoo_mask_custom" "Voodoo mask" + "DOTA_Tooltip_ability_item_voodoo_mask_custom_lifesteal" "%+$spell_lifesteal_creep" + "DOTA_Tooltip_ability_item_voodoo_mask_custom_Description" "

Пасивное: Spell Lifesteal

Излечивает владельца на долю от наносимого им магического урона." + "DOTA_Tooltip_ability_item_voodoo_mask_custom_Lore" "Маска, отточенная для поглощения магических уз между колдуном и его врагом." + "DOTA_Tooltip_modifier_modifier_item_voodoo_mask_custom" "Spell Lifesteal" + "DOTA_Tooltip_modifier_modifier_item_voodoo_mask_custom_Description" "Излечивает владельца на долю от наносимого им магического урона." + + "DOTA_Tooltip_ability_item_mask_of_madness_custom" "Mask of madness" + "DOTA_Tooltip_ability_item_mask_of_madness_custom_lifesteal" "%+$lifesteal" + "DOTA_Tooltip_ability_item_mask_of_madness_custom_bonus_damage" "+$damage" + "DOTA_Tooltip_ability_item_mask_of_madness_custom_Description" "

Активное: Berserk

Даёт владельцу %bonus_attackspeed_active% скорости атаки и %move_speed_active% скорости передвижения, но уменьшает его броню на %armor_reduction_active% и запрещает ему применять способности. Действует %duration% сек.\n\n

Пасивное: Lifesteal

Излечивает владельца на долю от физического урона, который наносят его атаки." + "DOTA_Tooltip_ability_item_mask_of_madness_custom_Lore" "Надев эту маску, воин впадает в безудержную ярость." + "DOTA_Tooltip_modifier_modifier_item_mask_of_madness_custom" "Lifesteal" + "DOTA_Tooltip_modifier_modifier_item_mask_of_madness_custom_Description" "Излечивает владельца на долю от физического урона, который наносят его атаки." + "DOTA_Tooltip_modifier_modifier_item_mask_of_madness_custom_buff" "Berserk" + "DOTA_Tooltip_modifier_modifier_item_mask_of_madness_custom_buff_Description" "Увеличивает скорость атаки на %bonus_attackspeed_active% и передвижения на %move_speed_active%, но снижает броню на %armor_reduction_active% и запрещает использовать способности. Длительность: %duration% сек." + + "DOTA_Tooltip_ability_item_vampire_claw" "Vampire Claw" + "DOTA_Tooltip_ability_item_vampire_claw_vampirism" "%+$lifesteal" + "DOTA_Tooltip_ability_item_vampire_claw_Description" "

Активное: Blood Rivers

Мгновенно восстанавливает %heal_per_charge% здоровья за каждый сохранённый заряд. Получает %charges_for_attack_creep% заряда за атаку по крипу.

Максимум зарядов: %max_charges%" + "DOTA_Tooltip_ability_item_vampire_claw_bonus_damage" "+$damage" + "DOTA_Tooltip_ability_item_vampire_claw_bonus_str" "+$str" + "DOTA_Tooltip_ability_item_vampire_claw_Lore" "Когда-то этот коготь принадлежал древнему вампиру, чья жажда крови была столь велика, что он мог исцелять свои раны, поглощая жизненную силу врагов. Говорят, что каждый раз, когда коготь впивается в плоть, его владелец ощущает прилив сил и неутолимую тягу к новой битве. Но с каждым исцелением на клинке проступают новые кровавые прожилки, напоминая: истинная сила требует жертв" + + "DOTA_Tooltip_ability_item_satanic_custom" "Satanic" + "DOTA_Tooltip_ability_item_satanic_custom_Description" "

Активное: Unholy Rage

Увеличивает вампиризм от атак на %unholy_lifesteal%%% на %unholy_duration% сек. Стоит %health_cost% здоровья.\n\n

Пасивное: Lifesteal

Излечивает владельца на долю от физического урона, который наносят его атаки." + "DOTA_Tooltip_ability_item_satanic_custom_Lore" "Мощный артефакт, наполненный кровью древнего рогара — демона, павшего в Первой войне." + "DOTA_Tooltip_ability_item_satanic_custom_bonus_strength" "+$str" + "DOTA_Tooltip_ability_item_satanic_custom_bonus_damage" "+$damage" + "DOTA_Tooltip_ability_item_satanic_custom_lifesteal" "%+$lifesteal" + "DOTA_Tooltip_modifier_item_satanic_custom_unholy" "Unholy Rage" + "DOTA_Tooltip_modifier_item_satanic_custom_unholy_Description" "Вампиризм от атак увеличен." + + "dota_tooltip_ability_item_king_fly" "Lepidoptera's Kiss" + "dota_tooltip_ability_item_king_fly_bonus_agility" "+$agi" + "dota_tooltip_ability_item_king_fly_bonus_evasion" "%+$evasion" + "dota_tooltip_ability_item_king_fly_bonus_attack_speed_pct" "%+$attack_pct" + "dota_tooltip_ability_item_king_fly_bonus_damage" "+$damage" + "dota_tooltip_ability_item_king_fly_Lore" "Клинок, выкованный из сущности бесчисленных бабочек. Их переливающиеся крылья и изумрудная пыльца слились в оружие эфирной красоты и смертельной точности." + + "DOTA_Tooltip_ability_item_zombie_slayer" "Zombie slayer" + "DOTA_Tooltip_ability_item_zombie_slayer_bonus_damage" "+$damage" + "DOTA_Tooltip_ability_item_zombie_slayer_bonus_agility" "+$agi" + "DOTA_Tooltip_ability_item_zombie_slayer_bonus_health" "+$health" + "DOTA_Tooltip_ability_item_zombie_slayer_bonus_mana_regen" "+$mana_regen" + "DOTA_Tooltip_ability_item_zombie_slayer_Description" "

Пасивное: Corrosion

Атаки замедляют цель на %movespeed_reduction%%%, снижают броню на %armor_reduction% и уменьшают восстановление здоровья на %health_reduction%%%. Длительность: %debuff_duration% сек. \n\n

Пасивное: Undead execution

Если цель — нежить, она получает на %incress_damage%%% больше урона. Длительность: %debuff_duration% сек." + "DOTA_Tooltip_ability_item_zombie_slayer_Lore" "Когда-то этот клинок принадлежал охотнику, который в одиночку очистил целые земли от полчищ нежити. Его сталь пропитана злобой павших зомби, а рукоять украшена зубами самых опасных из них. Говорят, что каждый удар этим оружием не только ранит плоть, но и сжигает остатки тёмной магии в теле врага. Те, кто носит Zombie Slayer, становятся кошмаром для всех созданий, восставших из мёртвых." + + "DOTA_Tooltip_ability_item_demon_slayer" "Demon Slayer" + "DOTA_Tooltip_ability_item_demon_slayer_bonus_damage" "+$damage" + "DOTA_Tooltip_ability_item_demon_slayer_bonus_agility" "+$agi" + "DOTA_Tooltip_ability_item_demon_slayer_bonus_health" "+$health" + "DOTA_Tooltip_ability_item_demon_slayer_bonus_mana_regen" "+$mana_regen" + "DOTA_Tooltip_ability_item_demon_slayer_bonus_health_regen" "+$hp_regen" + "DOTA_Tooltip_ability_item_demon_slayer_bonus_magic_resistance" "%+$spell_resist" + "DOTA_Tooltip_ability_item_demon_slayer_Description" "

Пассивное: Corrosion

Атаки замедляют цель на %movespeed_reduction%%%, снижают броню на %armor_reduction% и уменьшают восстановление здоровья на %health_reduction%%%. Длительность: %debuff_duration% сек.\n\n

Пассивное: Undead execution

Если цель — нежить (волновые крипы), она получает на %incress_damage%%% больше урона. Длительность: %debuff_duration% сек.\n\n

Пассивное: Mage Slayer

Атаки накладывают эффект на %duration% сек.: цель теряет %spell_amp_debuff%%% усиления заклинаний и получает %dps% магического урона каждую секунду.\n\n

Пассивное: Demon pact

Носитель получает на %incoming_damage_reduction%%% меньше урона и наносит на %outgoing_damage_bonus%%% больше урона." + "DOTA_Tooltip_ability_item_demon_slayer_Lore" "Проклятое слияние клинка охотника на нежить и плаща убийцы магов: пьёт заклинания, кромсает мертвецов и превращает ярость владельца в лавину разрушения." + + "DOTA_Tooltip_ability_item_desolator_custom" "Desolator" + "DOTA_Tooltip_ability_item_desolator_custom_bonus_damage" "+$damage" + "DOTA_Tooltip_ability_item_desolator_custom_bonus_health" "+$health" + "DOTA_Tooltip_ability_item_desolator_custom_Description" "

Пассивное: Разрушение брони

Атаки накладывают на цель на %corruption_duration% сек.: уменьшая %corruption_armor% брони, замедляя на %corruption_movespeed_slow%%% скорости, снижение восстановления здоровья на %corruption_heal_reduction%%% \n\n

Пассивное: Заряды охотника

Убийство цели под коррупцией с шансом %chance_to_stack%%% даёт предмету +%bonus_damage_per_kill% зарядов (макс. %max_damage%). Каждый заряд: +1 урон и +1 здоровье." + "DOTA_Tooltip_ability_item_desolator_custom_Lore" "Клинок, выкованный, чтобы рвать не только плоть, но и веру в собственной броне. Каждый поверженный под коррупцией враг оставляет ещё одну зазубрину — пока сталь не остынет." + "DOTA_Tooltip_modifier_modifier_item_desolator_custom_debuff" "Коррупция" + "DOTA_Tooltip_modifier_modifier_item_desolator_custom_debuff_Description" "Броня изменена на %corruption_armor%. Замедление %corruption_movespeed_slow%%%, восстановление здоровья — на %corruption_heal_reduction%%%." + + "DOTA_Tooltip_ability_item_desolator_custom_2" "Desolator II" + "DOTA_Tooltip_ability_item_desolator_custom_2_bonus_damage" "+$damage" + "DOTA_Tooltip_ability_item_desolator_custom_2_bonus_health" "+$health" + "DOTA_Tooltip_ability_item_desolator_custom_2_Description" "

Пассивное: Нарастающая коррупция

Попадания накладывают/продлевают на %corruption_duration% сек.: стакающаяся эффект уменьшения брони %corruption_armor_per_stack% вплоть до %corruption_armor_cap% единиц, %corruption_movespeed_slow%%% замедление скорости и −%corruption_heal_reduction%%% к пополнению здоровья \n\n

Пассивное: Заряды охотника

Убийство цели под этой коррупцией с шансом %chance_to_stack%%% даёт +%bonus_damage_per_kill% зарядов (макс. %max_damage%). Каждый заряд: +1 урон и +1 здоровье." + "DOTA_Tooltip_ability_item_desolator_custom_2_Lore" "Тот же зубчатый принцип — только сталь глубже врезается в саму идею защиты, пока стаки не упираются в предел разрушения." + "DOTA_Tooltip_modifier_modifier_item_desolator_custom_2_debuff" "Глубокая коррупция" + "DOTA_Tooltip_modifier_modifier_item_desolator_custom_2_debuff_Description" "Броня до min(стаки×%corruption_armor_per_stack%, %corruption_armor_cap%); скорость %corruption_movespeed_slow%%% ; реген −%corruption_heal_reduction%%%." + + "DOTA_Tooltip_ability_item_poor_shield" "Stout Shield" + "DOTA_Tooltip_ability_item_poor_shield_Description" "

Пассивное: Блок урона

С шансом %damage_block_chance%%% при получении урона от атаки блокирует %damage_block% физического урона. У героев итоговый шанс выше при большей удаче (Luck)." + "DOTA_Tooltip_ability_item_poor_shield_Lore" "Потрёпанная доска и обрубок железа — мало, но от первых ударов хватает. Главное — не ревновать к чужим вангардам, бака." + + "DOTA_Tooltip_ability_item_warrior_shield" "Warrior's Shield" + "DOTA_Tooltip_ability_item_warrior_shield_bonus_health" "+$health" + "DOTA_Tooltip_ability_item_warrior_shield_bonus_health_regen" "+$hp_regen" + "DOTA_Tooltip_ability_item_warrior_shield_Description" "

Пассивное: Блок урона

С шансом %damage_block_chance%%% при получении урона от атаки блокирует %damage_block% физического урона. Герои ближнего боя дополнительно блокируют 50%% от своей силы. У героев итоговый шанс выше при большей удаче (Luck)." + "DOTA_Tooltip_ability_item_warrior_shield_Lore" "Укреплённый обод и подкладка из шкуры: держит удар и даёт передышку, пока лекарь тащит бинты." + + "dota_tooltip_ability_item_crystalys" "crystalys" + "dota_tooltip_ability_item_crystalys_bonus_damage" "+$damage" + "dota_tooltip_ability_item_crystalys_crit_chance" "%+$crit_chance" + "dota_tooltip_ability_item_crystalys_crit_mult" "%+$crit_mult" + "dota_tooltip_ability_item_crystalys_Lore" "Древний артефакт, созданный мастерами-кузнецами для усиления смертоносности оружия. Его острые грани способны направить энергию удара в самые уязвимые точки противника." + + "dota_tooltip_ability_item_magical_crit" "Lia Magic Bow" + "dota_tooltip_ability_item_magical_crit_Description" "Пассивно: заклинания с шансом наносят критический магический урон. Включает свойства Mystic Staff, Magic Stone и Voodoo Mask." + "dota_tooltip_ability_item_magical_crit_bonus_intellect" "+$int" + "dota_tooltip_ability_item_magical_crit_bonus_strength" "+$str" + "dota_tooltip_ability_item_magical_crit_bonus_agility" "+$agi" + "dota_tooltip_ability_item_magical_crit_bonus_spell_amplify" "%+$spell_amp" + "dota_tooltip_ability_item_magical_crit_bonus_mana_regen" "+$mana_regen" + "dota_tooltip_ability_item_magical_crit_bonus_mana_pct" "%+$max_mana_percentage" + "dota_tooltip_ability_item_magical_crit_magical_vampirism" "%+$spell_lifesteal_hero" + "dota_tooltip_ability_item_magical_crit_spell_crit_chance" "%+$spell_crit_chance" + "dota_tooltip_ability_item_magical_crit_spell_crit_mult" "%+$spell_crit_mult" + "dota_tooltip_ability_item_magical_crit_Lore" "Кристалл, выращенный в башнях магов: он ловит потоки маны и в нужный миг обрушивает их на слабое место заклинания." + + "dota_tooltip_ability_item_ethereal_blade_custom" "Ethereal Blade" + "dota_tooltip_ability_item_ethereal_blade_custom_Description" "

Активное: Ether Blast

Выпускает эфирные снаряды по врагам в радиусе %splash_radius% вокруг цели. Враги переходят в эфирное состояние на %duration% сек., получают дополнительный магический урон и замедляются. Урон: %blast_damage_base% + %blast_stat_multiplier% от основного атрибута.

Можно применить на союзника: эфирное состояние на %duration_ally% сек. и ускорение на %ally_bonus_movespeed%%% к скорости передвижения и %ally_bonus_attack_speed%%% к скорости атаки." + "dota_tooltip_ability_item_ethereal_blade_custom_bonus_strength" "+$str" + "dota_tooltip_ability_item_ethereal_blade_custom_bonus_agility" "+$agi" + "dota_tooltip_ability_item_ethereal_blade_custom_bonus_intellect" "+$int" + "dota_tooltip_ability_item_ethereal_blade_custom_status_resistance" "%+$status_resist" + "dota_tooltip_ability_item_ethereal_blade_custom_bonus_attack_speed" "+$attack" + "dota_tooltip_ability_item_ethereal_blade_custom_movement_speed_percent_bonus" "%+$move_speed" + "dota_tooltip_ability_item_ethereal_blade_custom_hp_regen_amp" "%+$hp_regen" + "dota_tooltip_ability_item_ethereal_blade_custom_mana_regen_multiplier" "%+$mana_regen" + "dota_tooltip_ability_item_ethereal_blade_custom_spell_amp" "%+$spell_amp" + "dota_tooltip_ability_item_ethereal_blade_custom_magic_damage_attack" "+$damage" + "dota_tooltip_ability_item_ethereal_blade_custom_splash_radius" "РАДИУС ЗАЛПА:" + "dota_tooltip_ability_item_ethereal_blade_custom_blast_damage_base" "БАЗОВЫЙ УРОН:" + "dota_tooltip_ability_item_ethereal_blade_custom_blast_stat_multiplier" "МНОЖИТЕЛЬ ОСН. АТРИБУТА:" + "dota_tooltip_ability_item_ethereal_blade_custom_ethereal_damage_bonus" "% УСИЛЕНИЯ МАГ. УРОНА ПО ЦЕЛИ:" + "dota_tooltip_ability_item_ethereal_blade_custom_duration" "ДЛИТЕЛЬНОСТЬ (ВРАГ):" + "dota_tooltip_ability_item_ethereal_blade_custom_duration_ally" "ДЛИТЕЛЬНОСТЬ (СОЮЗНИК):" + "dota_tooltip_ability_item_ethereal_blade_custom_ally_bonus_movespeed" "% УСКОРЕНИЕ (СОЮЗНИК):" + "dota_tooltip_ability_item_ethereal_blade_custom_ally_bonus_attack_speed" "% СКОРОСТИ АТАКИ (СОЮЗНИК):" + "dota_tooltip_ability_item_ethereal_blade_custom_Lore" "Клинок из застывшего эфира: режет плоть магией и на миг выводит жертву из мира ударов." + + "dota_tooltip_ability_item_dark_crystalys" "daedalus" + "dota_tooltip_ability_item_dark_crystalys_bonus_damage" "+$damage" + "dota_tooltip_ability_item_dark_crystalys_crit_chance" "%+$crit_chance" + "dota_tooltip_ability_item_dark_crystalys_crit_mult" "%+$crit_mult" + "dota_tooltip_ability_item_dark_crystalys_Lore" "Улучшенная версия Кристалиса, этот артефакт гарантирует смертельные удары в бою." + + "dota_tooltip_ability_item_rapier_custom" "Piercing blade" + "dota_tooltip_ability_item_rapier_custom_bonus_damage" "+$damage" + "dota_tooltip_ability_item_rapier_custom_Description" "

Пасивное: Divine Stab

При атаке %proc_damage%%% от нанесённого урона также будут нанесены в чистом виде." + "dota_tooltip_ability_item_rapier_custom_Lore" "После падения святого воина в битве с демонами, его копьё было разбито на множество осколков. Один из этих осколков попал в руки талантливого кузнеца, который, вдохновлённый божественной силой, вложенной в металл, перековал его в меч невероятной остроты." + + "dota_tooltip_ability_item_divine_rapier_custom" "Divine rapier" + "dota_tooltip_ability_item_divine_rapier_custom_bonus_damage" "+$damage" + "dota_tooltip_ability_item_divine_rapier_custom_Description" "

Пасивное: Divine Stab

При атаке %proc_damage%%% от нанесённого урона также будут нанесены в чистом виде." + "dota_tooltip_ability_item_divine_rapier_custom_Lore" "После падения святого воина в битве с демонами, его копьё было разбито на множество осколков. Один из этих осколков попал в руки талантливого кузнеца, который, вдохновлённый божественной силой, вложенной в металл, перековал его в меч невероятной остроты." + + "dota_tooltip_ability_item_mjolnir_custom" "Mjollnir" + "dota_tooltip_ability_item_mjolnir_custom_Description" "

Активное: Chain blessing

окружает цель цепями молний на %duration% секунд. Из-за чего тот получает %outgoing_pct%%% урона и получает на %incoming_pct%%% меньше урона. \n\n

Пасивное: Chain Lightning

Каждая атака с вероятностью в %chain_chance%%% может создать разряд цепной молнии, которая %chain_strikes% раз перескочит между случайными врагами в радиусе %chain_radius%, нанося каждому магический урон в размере %chain_damage% + %chain_damage_self%% от атаки. От атаки с цепной молнией нельзя уклониться." + "dota_tooltip_ability_item_mjolnir_custom_Lore" "Магический молот Тора, выкованный для него гномами по имени Брок и Эйтри." + "dota_tooltip_ability_item_mjolnir_custom_bonus_damage" "+$damage" + "dota_tooltip_ability_item_mjolnir_custom_bonus_attack_speed" "+$attack" + + "DOTA_Tooltip_ability_item_magic_stone" "Magic stone" + "DOTA_Tooltip_ability_item_magic_stone_Description" "

Пасивное: Magic Aura

Усиливает заклинания владельца и восстанавливает ману." + "DOTA_Tooltip_ability_item_magic_stone_Lore" "Большой драгоценный камень на нескольких цепочках. Внутри пульсирует древняя магическая энергия." + "DOTA_Tooltip_ability_item_magic_stone_bonus_strength" "+$str" + "DOTA_Tooltip_ability_item_magic_stone_bonus_spell_amplify" "%+$spell_amp" + "DOTA_Tooltip_ability_item_magic_stone_bonus_agility" "+$agi" + "DOTA_Tooltip_ability_item_magic_stone_bonus_intellect" "+$int" + "DOTA_Tooltip_ability_item_magic_stone_bonus_mana_regen" "+$mana_regen" + "DOTA_Tooltip_ability_item_magic_stone_bonus_mana_pct" "%+$max_mana_percentage" + + "DOTA_Tooltip_ability_item_magic_rapier" "Magical dagger" + "DOTA_Tooltip_ability_item_magic_rapier_Description" "

Пасивное: Divine Pierce

Следующая направленная способность нанесёт цели дополнительный магический урон: %damage_per_cast% плюс %damage_per_cast_hand%%% от текущей маны владельца (не от силы атаки), замедлит на %slow_duration% сек. и снизит магическое сопротивление на %magical_resist_reduction%%%.

Эффект стакается." + "DOTA_Tooltip_ability_item_magic_rapier_Lore" "Легендарный клинок, выкованный из осколков звёздного металла и закалённый в пламени древних драконов. Говорят, что его создатель, архимаг Аэлиндор, потратил три столетия на поиски идеального материала для этого оружия. Когда он наконец нашёл падающую звезду, её сердцевина оказалась пропитана чистейшей магической энергией." + "DOTA_Tooltip_ability_item_magic_rapier_bonus_spell_amplify" "%+$spell_amp" + "DOTA_Tooltip_ability_item_magic_rapier_bonus_intellect" "+$int" + "DOTA_Tooltip_ability_item_magic_rapier_bonus_strength" "+$str" + "DOTA_Tooltip_ability_item_magic_rapier_bonus_agility" "+$agi" + "DOTA_Tooltip_ability_item_magic_rapier_bonus_mana_regen" "+$mana_regen" + "DOTA_Tooltip_ability_item_magic_rapier_bonus_mana_pct" "%+$max_mana_percentage" + + "DOTA_Tooltip_ability_item_magical_divine_rapier" "Magical divine blade" + "DOTA_Tooltip_ability_item_magical_divine_rapier_Description" "

Пасивное: Divine Pierce

Следующая направленная способность нанесёт цели дополнительно %damage_per_cast% + %damage_per_cast_hand%%% от атаки владельца магическим уроном, замедлит на %slow_duration% сек и снизит магическое сопротивление на %magical_resist_reduction%%%.

Эффект стакается." + "DOTA_Tooltip_ability_item_magical_divine_rapier_Lore" "Легендарный клинок, выкованный из осколков звёздного металла и закалённый в пламени древних драконов. Говорят, что его создатель, архимаг Аэлиндор, потратил три столетия на поиски идеального материала для этого оружия. Когда он наконец нашёл падающую звезду, её сердцевина оказалась пропитана чистейшей магической энергией." + "DOTA_Tooltip_ability_item_magical_divine_rapier_bonus_spell_amplify" "%+$spell_amp" + "DOTA_Tooltip_ability_item_magical_divine_rapier_bonus_intellect" "+$int" + "DOTA_Tooltip_ability_item_magical_divine_rapier_bonus_strength" "+$str" + "DOTA_Tooltip_ability_item_magical_divine_rapier_bonus_agility" "+$agi" + "DOTA_Tooltip_ability_item_magical_divine_rapier_bonus_mana_regen" "+$mana_regen" + "DOTA_Tooltip_ability_item_magical_divine_rapier_bonus_mana_pct" "%+$max_mana_percentage" + + "DOTA_Tooltip_ability_item_soul_devourer_staff" "Soul Devourer Staff" + "DOTA_Tooltip_ability_item_soul_devourer_staff_Description" "

Пасивное: Soul Devourer

После убийства восстанавливает %kill_mana%%% от максимальной маны и увеличивает максимальную ману на %bonus_mana% за каждый стак. Каждые %stack_growth_interval% сек. число стаков благословения растёт на %stack_growth_percent%%% (с округлением вверх)." + "DOTA_Tooltip_ability_item_soul_devourer_staff_Lore" "После поражения некроманта в битве при Могильных Холмах, жезл был запечатан в крипте под храмом Света. Но с началом зомби-апокалипсиса древние печати ослабли, и артефакт вновь обрел свободу, жаждущий новых душ для поглощения." + "DOTA_Tooltip_ability_item_soul_devourer_staff_bonus_intellect" "+$int" + "DOTA_Tooltip_ability_item_soul_devourer_staff_bonus_manacost_reduction" "%+$manacost_reduction" + "DOTA_Tooltip_ability_item_soul_devourer_staff_bonus_mana_regen" "+$mana_regen" + "DOTA_Tooltip_ability_item_soul_devourer_staff_bonus_mana_special" "+$mana" + "DOTA_Tooltip_ability_item_soul_devourer_staff_bonus_cast_range" "+$cast_range" + + "DOTA_Tooltip_modifier_soul_devourer_staff_buff" "Soul Devourer Blessing" + "DOTA_Tooltip_modifier_soul_devourer_staff_buff_Description" "Герой получил %dMODIFIER_PROPERTY_SPELL_AMPLIFY_PERCENTAGE%%% дополнительного магического усиления." + + "DOTA_Tooltip_ability_item_orb_of_fire" "Orb of Fire" + "DOTA_Tooltip_ability_item_orb_of_fire_Note0" "

Пасивное: Burn

С 0+ стаков владелец начинает получать урон в секунду от количество стаков * 3.

С 70+ стаков владелец эффекта теряет 25% от количества стаков своей брони и маг. сопротивления.

Эффект не стакается с ОБМОРОЖЕНИЕМ из-за чего ОЖОГ поглащает эффект ОБМОРОЖЕНИЯ." + "DOTA_Tooltip_ability_item_orb_of_fire_Description" "

Пасивное: Fire Aura

Каждую секунду в радиусе %radius% единиц наносит %damage%, наложит %fire_stack% эффекта ожога всем вокруг." + "DOTA_Tooltip_ability_item_orb_of_fire_Lore" "Сфера состоящая полностью из огня." + + "dota_tooltip_ability_item_ice_spine" "Ice spine" + "dota_tooltip_ability_item_ice_spine_Description" "

Пасивное: Ice fragility

Все лечащие заклинания владельца восстанавливают на %heal_amplify%%% больше здоровья, также владелец предмета получает на %incoming_dmg_pct%%% больше урона." + "dota_tooltip_ability_item_ice_spine_Lore" "Ледяная игла созданная сёстрами льда, любящие мазохистские игрули." + "dota_tooltip_ability_item_ice_spine_spell_amplify" "%+$spell_amp" + "dota_tooltip_ability_item_ice_spine_movespeed_const" "+$move_speed" + + "DOTA_Tooltip_ability_item_fire_cape" "Blazing Shroud" + "DOTA_Tooltip_ability_item_fire_cape_Note0" "

Пасивное: Burn

С 0+ стаков владелец начинает получать урон в секунду от количество стаков * 3.

С 70+ стаков владелец эффекта теряет 25% от количества стаков своей брони и маг. сопротивления.

Эффект не стакается с ОБМОРОЖЕНИЕМ из-за чего ОЖОГ поглащает эффект ОБМОРОЖЕНИЯ." + "DOTA_Tooltip_ability_item_fire_cape_Description" "

Пасивное: Aura of greatness

Каждую секунду в радиусе %radius% единиц наносит %damage% + %mana_damage_pct%%% от максимальной маны владельца, наложит %fire_stack% эффекта ожога всем вокруг. Также всем кто находится в радиусе, уменьшает их магическое сопротивление и магический урон на %bonus_magic_resistance%%% / %bonus_spell_amp%%% соответственно." + "DOTA_Tooltip_ability_item_fire_cape_Lore" "Огненный плащ был выкован из пламени, что не гаснет даже под водой. Его жар не только сжигает врагов вокруг, но и подавляет их магическую силу, лишая сопротивления и уменьшая урон от заклинаний. Говорят, что каждый, кто осмелится приблизиться к владельцу этого артефакта, почувствует на себе всю мощь неукротимого огня, способного испепелить даже самые стойкие чары." + "DOTA_Tooltip_ability_item_fire_cape_bonus_damage" "+$damage" + "DOTA_Tooltip_ability_item_fire_cape_bonus_attack_speed" "+$attack" + "DOTA_Tooltip_ability_item_fire_cape_bonus_physical_armor" "+$armor" + "DOTA_Tooltip_ability_item_fire_cape_bonus_health_regen" "+$hp_regen" + "DOTA_Tooltip_ability_item_fire_cape_bonus_mana_regen" "+$mana_regen" + "DOTA_Tooltip_ability_item_fire_cape_bonus_magic_resistance" "%+$spell_resist" + + "DOTA_Tooltip_ability_item_radiance_custom" "Radiance" + "DOTA_Tooltip_ability_item_radiance_custom_Note0" "

Пасивное: Burn

С 0+ стаков владелец начинает получать урон в секунду от количество стаков * 3.

С 70+ стаков владелец эффекта теряет 25% от количества стаков своей брони и маг. сопротивления.

Эффект не стакается с ОБМОРОЖЕНИЕМ из-за чего ОЖОГ поглащает эффект ОБМОРОЖЕНИЯ." + "DOTA_Tooltip_ability_item_radiance_custom_Description" "

Пасивное: Burn Aura

Каждые %tick_interval% сек. в радиусе %radius% наносит врагам %damage% + %health_damage_pct%%% от максимального здоровья владельца умноженного на %health_regen_damage% за каждую единицу восстановления здоровья в секунду магического урона и накладывает %fire_stack% стаков ожога." + "DOTA_Tooltip_ability_item_radiance_custom_Lore" "Священный меч, окутанный пламенем, что не гаснет даже в глубинах бездны." + "DOTA_Tooltip_ability_item_radiance_custom_bonus_damage" "+$damage" + "DOTA_Tooltip_ability_item_radiance_custom_evasion" "%+$evasion" + + "DOTA_Tooltip_ability_item_blazing_radiance_custom" "Blazing guard" + "DOTA_Tooltip_ability_item_blazing_radiance_custom_Note0" "

Пасивное: Burn

С 0+ стаков владелец начинает получать урон в секунду от количество стаков * 3.

С 70+ стаков владелец эффекта теряет 25% от количества стаков своей брони и маг. сопротивления.

Эффект не стакается с ОБМОРОЖЕНИЕМ из-за чего ОЖОГ поглащает эффект ОБМОРОЖЕНИЯ." + "DOTA_Tooltip_ability_item_blazing_radiance_custom_Description" "

Пасивное: Infernal Aura

Каждые %tick_interval% сек. в радиусе %radius% наносит %damage% + %health_damage_pct%%% от макс. здоровья + %health_regen_damage% за ед. регена здоровья в сек. + %mana_damage_pct%%% от макс. маны магического урона и накладывает %fire_stack% стаков ожога. Враги в радиусе теряют %aura_magic_resistance_reduction%%% маг. сопротивления и %aura_spell_amp_reduction%%% маг. усиления.

Дополнительно увеличивает все базовые атрибуты на %all_stats_pct%%%." + "DOTA_Tooltip_ability_item_blazing_radiance_custom_Lore" "Плащ, сшитый из пламени Радианса и жара Blazing Shroud: вокруг владельца горит море, а чужая магия тускнеет в дыму." + "DOTA_Tooltip_ability_item_blazing_radiance_custom_bonus_damage" "+$damage" + "DOTA_Tooltip_ability_item_blazing_radiance_custom_evasion" "%+$evasion" + "DOTA_Tooltip_ability_item_blazing_radiance_custom_bonus_attack_speed" "+$attack" + "DOTA_Tooltip_ability_item_blazing_radiance_custom_bonus_physical_armor" "+$armor" + "DOTA_Tooltip_ability_item_blazing_radiance_custom_bonus_health_regen" "+$hp_regen" + "DOTA_Tooltip_ability_item_blazing_radiance_custom_bonus_mana_regen" "+$mana_regen" + "DOTA_Tooltip_ability_item_blazing_radiance_custom_bonus_magic_resistance" "%+$spell_resist" + "DOTA_Tooltip_ability_item_blazing_radiance_custom_bonus_all_stats" "+$all" + + "DOTA_Tooltip_ability_item_ultimate_crown" "Ultimate Crown" + "DOTA_Tooltip_ability_item_ultimate_crown_bonus_all_stats" "+$all" + "DOTA_Tooltip_ability_item_ultimate_crown_Description" "Дополнительно увеличивает все базовые атрибуты на %all_stats_pct%%%." + "DOTA_Tooltip_ability_item_ultimate_crown_Lore" "Три сферы слились в корону: сила, ловкость и разум усиливаются и числом, и долей от врождённого дара." + + "DOTA_Tooltip_ability_item_skadi_custom" "Eye of Skadi" + "DOTA_Tooltip_ability_item_skadi_custom_bonus_all_stats" "+$all" + "DOTA_Tooltip_ability_item_skadi_custom_Description" "

Пассивное: Cold Attack

Добавляет к атакам владельца замедляющий эффект, действующий %debuff_duration% сек. Скорость атаки жертвы снижается на %slow_attack_speed%%%, а замедление передвижения зависит от её типа атаки: у существ ближнего боя оно составляет %slow_movespeed_melee%%%, а у существ дальнего боя — %slow_movespeed_ranged%%%. Также уменьшает пополнение здоровья жертвы на %health_restoration_reduction%%%.

Дополнительно увеличивает все базовые атрибуты на %all_stats_pct%%%." + "DOTA_Tooltip_ability_item_skadi_custom_Lore" "Ледяной шар, выкованный из сердца древнего змея: каждый удар владельца сковывает жертву холодом, пока жизнь не уходит из ран." + + "DOTA_Tooltip_ability_item_crimson_shivas_custom" "Crimson Shiva's Guard" + "DOTA_Tooltip_ability_item_crimson_shivas_custom_Note0" "

Пасивное: Freezing Aura

В радиусе %aura_radius% снижает скорость атаки врагов на %aura_as_reduction%, увеличивает их входящий урон на %aura_enemy_incoming_amp%%% и уменьшает входящий урон союзников на %aura_ally_incoming_reduction%%%." + "DOTA_Tooltip_ability_item_crimson_shivas_custom_Description" "

Активное: Arctic Bulwark

Выпускает волну холода в радиусе %blast_radius%, нанося %blast_damage% магического урона и замедляя врагов на %slow_duration% сек. Союзники в радиусе %guard_radius% на %guard_duration% сек. получают усиленную защиту: блокируют физический урон (базово %damage_block_active% + %block_strength_pct%%% от силы кастера), а также бонусную броню, скорость атаки и передвижения; входящий урон по ним снижен на %aura_ally_incoming_reduction%%%.

Пассивное: Guardian Ward

Даёт броню, характеристики, здоровье, ману и регенерацию. С шансом %damage_block_chance%%% блокирует %damage_block% физического урона от атак. Герои ближнего боя дополнительно блокируют %block_strength_pct%%% от своей силы.

Дополнительно увеличивает все базовые атрибуты на %all_stats_pct%%%." + "DOTA_Tooltip_ability_item_crimson_shivas_custom_Lore" "Лёд и сталь слились в одном щите: враги замерзают, союзники выстоят до последнего." + "DOTA_Tooltip_ability_item_crimson_shivas_custom_bonus_stats" "+$all" + "DOTA_Tooltip_ability_item_crimson_shivas_custom_bonus_all_stats" "+$all" + "DOTA_Tooltip_ability_item_crimson_shivas_custom_bonus_health" "+$health" + "DOTA_Tooltip_ability_item_crimson_shivas_custom_bonus_mana" "+$mana" + "DOTA_Tooltip_ability_item_crimson_shivas_custom_bonus_armor" "+$armor" + "DOTA_Tooltip_ability_item_crimson_shivas_custom_bonus_hp_regen" "+$hp_regen" + "DOTA_Tooltip_ability_item_crimson_shivas_custom_bonus_mana_regen" "+$mana_regen" + + "DOTA_Tooltip_ability_item_blademail_2" "Blademail 2" + "DOTA_Tooltip_ability_item_blademail_2_bonus_armor" "+$armor" + "DOTA_Tooltip_ability_item_blademail_2_attack_speed" "+$attack" + "DOTA_Tooltip_ability_item_blademail_2_Description" "

Активное: Зеркальный шквал

На %active_duration% сек.: отражается %active_reflect_pct%%% полученного от врагов урона (после расчёта); любой урон, который враги наносят за это время, полностью превращается в восстановление здоровья. \n\n

Пассивное: Отражение

Возвращает атакующему %reflect_pct%%% от полученного урона (после расчёта урона). Не срабатывает на потерю HP, союзников и отражённый урон.\n\n

Пассивное: Колючая аура

В радиусе %radius%: союзным героям и крипам даёт +%aura_armor% брони и +%aura_attack_speed% скорости атаки; врагам — столько же со знаком минус." + "DOTA_Tooltip_ability_item_blademail_2_Lore" "Кольчуга, переплетённая из сломанных клинков. Бьёт в ответ так же больно, как вперёд, а тот, кто стоит рядом, чувствует либо чужую сталь на своей стороне, либо давящий холод вражеского кольца." + "DOTA_Tooltip_modifier_modifier_item_blademail_2_aura" "Blademail II — аура" + "DOTA_Tooltip_modifier_modifier_item_blademail_2_aura_Description" "Союзникам носителя: +%aura_armor% брони и +%aura_attack_speed% скорости атаки. Врагам: −%aura_armor% брони и −%aura_attack_speed% скорости атаки." + "DOTA_Tooltip_modifier_modifier_item_blademail_2_active" "Зеркальный шквал" + "DOTA_Tooltip_modifier_modifier_item_blademail_2_active_Description" "Отражение %active_reflect_pct%%% входящего урона и полное восстановление здоровья от урона врагов." + + "dota_tooltip_modifier_item_desolator_custom_debuff" "armor corruption" + "dota_tooltip_modifier_item_desolator_custom_debuff_Description" "Броня, регенерация и скорость передвижения уменьшены." + "dota_tooltip_modifier_item_desolator_custom_2_debuff" "Глубокая коррупция" + "dota_tooltip_modifier_item_desolator_custom_2_debuff_Description" "Броня, регенерация и скорость передвижения уменьшены." + "dota_tooltip_modifier_item_blademail_2_active" "Зеркальный шквал" + "dota_tooltip_modifier_item_blademail_2_active_Description" "Отражение входящего урона и полное восстановление здоровья от урона врагов." + "dota_tooltip_modifier_item_blademail_2_aura" "Blademail" + "dota_tooltip_modifier_item_blademail_2_aura_Description" "Увеличена/снижена скорость атаки и броня." + + "dota_tooltip_ability_item_rom" "Пустая фляга" + "dota_tooltip_ability_item_rom_Description" "Хлюп хлюп флакон чутка пустоват." + "dota_tooltip_ability_item_rom_Lore" "Поговаривают, что этот ром производился из эктоплазмы короля пиратов." + + "dota_tooltip_ability_item_ent_heart" "Сердце Энта" + "dota_tooltip_ability_item_ent_heart_Description" "

Предмет для квеста

Используется для сдачи квеста." + "dota_tooltip_ability_item_ent_heart_Lore" "Сердце, вырванное из древнего энта, всё ещё пульсирует жизненной энергией леса. Говорят, что его обладатель становится неуязвимым для болезней и ран, а его тело наполняется силой самой природы." + + "DOTA_Tooltip_ability_item_wolf_hat" "Wolf Hat" + "DOTA_Tooltip_ability_item_wolf_hat_bonus_strength" "+$str" + "DOTA_Tooltip_ability_item_wolf_hat_bonus_move_speed" "+$move_speed" + "DOTA_Tooltip_ability_item_wolf_hat_lifesteal_pct" "%+$lifesteal" + "DOTA_Tooltip_ability_item_wolf_hat_low_hp_damage_bonus" "%+$damage" + "DOTA_Tooltip_ability_item_wolf_hat_Description" "

Активное: Вой стаи

На %howl_duration% сек. даёт %howl_attack_speed% к скорости атаки и %howl_move_speed_pct%%% к скорости передвижения.

Пасивное: Кровь вожака

Атаки по врагам с здоровьем ниже %low_hp_threshold%%% наносят на %low_hp_damage_bonus%%% больше урона." + "DOTA_Tooltip_ability_item_wolf_hat_Note0" "Награда Кункевича за когти волков. Не продаётся." + "DOTA_Tooltip_ability_item_wolf_hat_Lore" "Капитан сшил шапку из когтей, что ты принёс: в ней слышен вой стаи и жажда добивать раненую добычу. Моряки шутят, что в такой шапке и Кракен не осмелится подплыть близко." + + "DOTA_Tooltip_modifier_item_wolf_hat_howl" "Вой стаи" + "DOTA_Tooltip_modifier_item_wolf_hat_howl_Description" "Повышены скорость атаки и передвижения." + + "dota_tooltip_ability_item_wolf_claw" "Волчий коготь" + "dota_tooltip_ability_item_wolf_claw_Description" "

Предмет для квеста

Используется для сдачи квеста." + "dota_tooltip_ability_item_wolf_claw_Lore" "Острый коготь, принадлежавший легендарному вожаку стаи. Его сила передаётся новому владельцу, наполняя его дикостью и жаждой охоты." + + "dota_tooltip_ability_item_spider_legs_custom" "Паучьи лапки" + "dota_tooltip_ability_item_spider_legs_custom_Description" "

Предмет для квеста

Используется для сдачи квеста." + "dota_tooltip_ability_item_spider_legs_custom_Lore" "Лапки паука, покрытые ядовитыми ворсинками. Даже после смерти твари они всё ещё подрагивают, будто пытаются уцепиться за добычу." + + "dota_tooltip_ability_item_poison" "Паучий яд" + "dota_tooltip_ability_item_poison_Description" "

Предмет для квеста

Используется для сдачи квеста." + "dota_tooltip_ability_item_poison_Lore" "Густая тёмная жидкость в запечатанном флаконе. Алхимики платят за неё щедро, ведь из этого яда можно варить смертельно опасные смеси." + + "dota_tooltip_ability_item_frog_paw" "Лягушачьи лапки" + "dota_tooltip_ability_item_frog_paw_Description" "

Предмет для квеста

Используется для сдачи квеста." + "dota_tooltip_ability_item_frog_paw_Lore" "Небольшие лапки редкой болотной лягушки. Их вязкий сок добавляют зельям нужную концентрацию и особое послевкусие." + + "dota_tooltip_ability_item_lycan_horn" "Рог Чёрного клыка" + "dota_tooltip_ability_item_lycan_horn_Description" "

Предмет для квеста

Используется для сдачи квеста." + "dota_tooltip_ability_item_lycan_horn_Lore" "Рог, покрытый древними рунами, принадлежал самому Ликану — повелителю волков. Считается, что его обладатель может вызывать силу полной луны, становясь неудержимым в бою." + + "dota_tooltip_ability_item_kunkka_sword" "Пиратская Сабля" + "dota_tooltip_ability_item_kunkka_sword_bonus_damage" "+$damage" + "dota_tooltip_ability_item_kunkka_sword_move_speed_bonus" "%-к скорости передвижения" + "dota_tooltip_ability_item_kunkka_sword_Description" "

Активное: Wave Cuts

При активации следующие %attack_count% атак владельца нанесут %crit_mult%%% урона." + "dota_tooltip_ability_item_kunkka_sword_Note0" "Не тяжелее твоей мамки, АЗАЗ." + "dota_tooltip_ability_item_kunkka_sword_Lore" "Меч из трюма Кунки – оружие, покрывшее себя легендами. Им повержены несчётные твари: морские чудовища, кровожадные пираты и даже сам Левиафан, владыка пучин. Говорят, клинок столь тяжёл, что простой трейзвый человек не в силах оторвать его от земли, не то что занестись для удара. Но для истинного морского волка его тяжесть – не бремя, а напоминание: в руках у него – ярость океана, закалённая в бездне." + + "dota_tooltip_modifier_item_kunkka_sword_active" "Алкогольно опьянение" + "dota_tooltip_modifier_item_kunkka_sword_active_Description" "Следующая атака будет иметь критический эффект." + + "dota_tooltip_ability_item_oldmen_amulet" "Амулет старца" + "dota_tooltip_ability_item_oldmen_amulet_Description" "

Relaxing mind

Восстанавливает %mana_restore% маны выбранной цели." + "dota_tooltip_ability_item_oldmen_amulet_spell_amplify_percentage" "%+$spell_amp" + "dota_tooltip_ability_item_oldmen_amulet_manacost_debuff" "%+к расходу маны" + + "dota_tooltip_ability_item_oldmen_amulet_Lore" "Древний амулет, принадлежавший мудрецу, прожившему тысячу лет. В нём заключена вся мудрость прожитых им лет." + "dota_tooltip_ability_item_oldmen_amulet_Note0" "Как сказал дед: Це чарівний амулет, исапчканный салом." + // utils + + "dota_tooltip_ability_item_meat" "Мяско" + "dota_tooltip_ability_item_meat_Description" "

Bon appetit

При использовании цель съедает еду и восстановливает %hunger_bonus% сытости и %heal% здоровья." + "dota_tooltip_ability_item_meat_Lore" "Сочный кусок мяса, приготовленный по древнему рецепту местных охотников. Его аромат пробуждает волчий аппетит даже у самых привередливых едоков." + "dota_tooltip_ability_item_meat_Note0" "Сытость даёт эффект насыщения и за каждый стак насыщения герой получает 1% бонуса своих базовых статов." + + "dota_tooltip_ability_item_bread" "Хлеб" + "dota_tooltip_ability_item_bread_Description" "

Bon appetit

При использовании цель съедает тёплый кусок хлеба и восстановливает %hunger_bonus% сытости и %heal% здоровья." + "dota_tooltip_ability_item_bread_Lore" "Простой, но драгоценный в зомби-апокалипсисе хлеб. Пахнет так, будто где-то ещё осталась нормальная жизнь — без мёртвых, волн и бесконечных квестов." + + "dota_tooltip_ability_item_testo" "Тесто" + "dota_tooltip_ability_item_testo_Description" "

Ингредиент

Сырое тесто, используемое только как ингредиент для готовки. Само по себе ничего не делает — но из него можно приготовить что-то вкусное." + "dota_tooltip_ability_item_testo_Lore" "Собрано по секретному рецепту старика, который лучше всех знает, как пережить зомби-апокалипсис с полным желудком." + + "dota_tooltip_ability_item_testo_pizza" "Заготовка для пиццы" + "dota_tooltip_ability_item_testo_pizza_Description" "

Ингредиент

Основа для пиццы. Это заготовка: сама по себе не утолит голод, но из неё получится финальная пицца." + "dota_tooltip_ability_item_testo_pizza_Lore" "Тесто, собранное по рецепту того, кто знает: правильный соус делает блюдо легендой." + + "dota_tooltip_ability_item_wooden_katana" "Деревянная катана" + "dota_tooltip_ability_item_wooden_katana_Description" "

Пассивное: Точная методика

Увеличивает наносимый урон на %damage_outgoing_percentage%%%." + "dota_tooltip_ability_item_wooden_katana_Lore" "Тренировочная катана старого мастера. Легкая древесина не режет сталь, но учит наносить точные и сильные удары." + + "dota_tooltip_ability_item_firecore" "Ядро процветания" + "dota_tooltip_ability_item_firecore_Description" "

Процветание

Ядро, наполненное чистой огненной энергие, подходит для того чтобы ускорить саму материю мироздания." + "dota_tooltip_ability_item_firecore_Lore" "Говорят, этот кристалл — застывшее пламя древнего дракона. Его тепло не угасает даже в самой лютой стуже, а свет способен освещать путь сквозь любые тёмные времена." + + + "dota_tooltip_ability_item_mayonnaise" "Майонез" + "dota_tooltip_ability_item_mayonnaise_Description" "

Ингредиент

Крутой соус, который нужен для приготовления пиццы." + "dota_tooltip_ability_item_mayonnaise_Lore" "Густой, нежный и упрямо вкусный. Похоже, он тоже пережил зомби-апокалипсис и стал ещё лучше." + + "dota_tooltip_ability_item_pizza" "Пицца" + "dota_tooltip_ability_item_pizza_Description" "

Bon appetit

При использовании цель съедает пиццу и восстанавливает %hunger_bonus% сытости. Также усиливает все характеристики персонажа на %stack_count%." + "dota_tooltip_ability_item_pizza_Lore" "Пицца, которую можно есть даже тогда, когда вокруг только волны и квесты. Невероятно вкусно — проверено на практике." + + "dota_tooltip_ability_item_cheese" "Твороженный сыр" + "dota_tooltip_ability_item_cheese_Description" "

Ингредиент

Твороженный сыр, используемый только как ингредиент для готовки. Сам по себе ничего не делает — но из него можно приготовить что-то вкусное." + "dota_tooltip_ability_item_cheese_Lore" "Этот сыр когда-то был главным призом на фестивале выживших. Говорят, кто попробует его хоть раз, обретёт силу выдержать любой зомби-штурм — или хотя бы забудет о голоде на ближайшие сутки." + + "dota_tooltip_ability_item_grilled_meat" "Жаренное мяско" + "dota_tooltip_ability_item_grilled_meat_Description" "

Bon appetit (premium)

При использовании цель съедает сочный стейк и восстановливает %hunger_bonus% сытости и %heal% здоровья." + "dota_tooltip_ability_item_grilled_meat_Lore" "Мясо, прожаренное до идеальной корочки на костре во время недолгой передышки между волнами зомби. Каждый кусок напоминает о тех временах, когда главным врагом был не голод, а сосед, который доедал последний шашлык." + "dota_tooltip_ability_item_grilled_meat_Note0" "Сытость даёт эффект насыщения и за каждый стак насыщения герой получает 1% бонуса своих базовых статов." + + "dota_tooltip_ability_item_milk" "Молочко" + "dota_tooltip_ability_item_milk_Description" "

Bon appetit

При использовании цель съедает еду и восстановливает %hunger_bonus% сытости и %mana% маны." + "dota_tooltip_ability_item_milk_Lore" "Сочный кусок мяса, приготовленный по древнему рецепту местных охотников. Его аромат пробуждает волчий аппетит даже у самых привередливых едоков." + "dota_tooltip_ability_item_milk_Note0" "Сытость даёт эффект насыщения и за каждый стак насыщения герой получает 1% бонуса своих базовых статов." + + "dota_tooltip_ability_item_banana" "Banana" + "dota_tooltip_ability_item_banana_Description" "

Bon appetit

При использовании цель съедает спелый банан и восстановливает %mana% маны и %hunger_bonus% сытости." + "dota_tooltip_ability_item_banana_Lore" "Банан, переживший склад, рейд мародёров и три волны зомби. Настолько заряжен калием и магией, что способен вдохновить даже самого уставшего кастера на ещё одно заклинание." + "dota_tooltip_ability_item_banana_Note0" "Сытость даёт эффект насыщения и за каждый стак насыщения герой получает 1% бонуса своих базовых статов." + + "dota_tooltip_ability_item_sandwich" "Sandwich" + "dota_tooltip_ability_item_sandwich_Description" "

Bon appetit

При использовании цель съедает огромный сэндвич, получая %hunger_bonus% сытости и на %buff_duration% сек: +%bonus_damage% к урону, +%bonus_armor% к броне, но скорость передвижения уменьшается на %move_speed_slow_pct%%%." + "dota_tooltip_ability_item_sandwich_Lore" "Три слоя мяса, сыр, соус и хлеб толщиной с броню гири. После такого сэндвича ты становишься ходячим танком — правда, двигаешься примерно так же быстро." + "dota_tooltip_ability_item_sandwich_Note0" "Сытость даёт эффект насыщения и за каждый стак насыщения герой получает 1% бонуса своих базовых статов." + + "dota_tooltip_modifier_item_sandwich_buff" "Sandwich" + "dota_tooltip_modifier_item_sandwich_buff_Description" "Герой увеличился в размерах на %dMODIFIER_PROPERTY_MODEL_SCALE%%%, получил бонус %dMODIFIER_PROPERTY_PREATTACK_BONUS_DAMAGE% к урону и %dMODIFIER_PROPERTY_PHYSICAL_ARMOR_BONUS% к броне, но скорость передвижения снижена на %dMODIFIER_PROPERTY_MOVESPEED_BONUS_PERCENTAGE%%%." + + "dota_tooltip_ability_item_energy_drink" "Energy drink" + "dota_tooltip_ability_item_energy_drink_Description" "

АДРЕНАЛИН В ЖИЛАХ

При использовании цель залпом вливает в себя литр чистого безумия, восстанавливает %hunger_bonus% сытости, %heal% здоровья и %mana% маны. На ближайшие %buff_duration% сек герой получает %buff_move_speed_pct%%% к скорости передвижения и %buff_attack_speed%%% к скорости атаки, бегает так, будто за ним лично выехал Gaben, и махает руками быстрее, чем думает." + "dota_tooltip_ability_item_energy_drink_Lore" "Официально запрещён на всех киберспортивных турнирах по причине «слишком много кайфа на квадратный пиксель»." + "dota_tooltip_ability_item_energy_drink_Note0" "Сытость даёт эффект насыщения и за каждый стак насыщения герой получает 1% бонуса своих базовых статов." + + "dota_tooltip_modifier_energy_drink_buff" "Энергетический заряд" + "dota_tooltip_modifier_energy_drink_buff_Description" "Скорость передвижения увеличена на %dMODIFIER_PROPERTY_MOVESPEED_BONUS_PERCENTAGE%%% и скорость атаки увеличена на %dMODIFIER_PROPERTY_ATTACKSPEED_PERCENTAGE%%%." + + "dota_tooltip_ability_item_cocktail" "Fruit cocktail" + "dota_tooltip_ability_item_cocktail_Description" "

Активное: Магический замес

При использовании цель выпивает сомнительный коктейль, получая %hunger_bonus% сытости и на %buff_duration% сек: %spell_lifesteal%%% вампиризма от заклинаний, %spell_amp%%% усиления заклинаний, но расход маны от способностей увеличивается на %manacost_increase%%%." + "dota_tooltip_ability_item_cocktail_Lore" "Бармен клялся, что этот коктейль «точно не убьёт, а если и убьёт — то красиво». И правда: после первого глотка ваш магический урон растёт, а мана тает быстрее, чем уважение к тем, кто это придумал." + "dota_tooltip_ability_item_cocktail_Note0" "Сытость даёт эффект насыщения и за каждый стак насыщения герой получает 1% бонуса своих базовых статов." + + "dota_tooltip_modifier_item_cocktail_buff" "Fruit cocktail" + "dota_tooltip_modifier_item_cocktail_buff_Description" "Герой опьянён магическим коктейлем: получает %dMODIFIER_PROPERTY_SPELL_AMPLIFY_PERCENTAGE%%% усиления заклинаний и эффекта магического вампиризма, но способности требуют на %dMODIFIER_PROPERTY_MANACOST_PERCENTAGE_STACKING%%% больше маны." + + "dota_tooltip_ability_item_coffee" "Coffee" + "dota_tooltip_ability_item_coffee_Description" "

Активное: Турбо-Эспрессо

При использовании цель заливает в себя концентрированный кофе, получая %hunger_bonus% сытости и на %buff_duration% сек: уменьшает перезарядку способностей на %cooldown_reduction%%% и сокращает время применения заклинаний на %casttime_reduction%%%." + "dota_tooltip_ability_item_coffee_Lore" "Сварен из зёрен, которые обжаривали под крики выживших и вой зомби. После такой чашки кажется, что заклинания сами вылетают из рук быстрее, чем мысли." + "dota_tooltip_ability_item_coffee_Note0" "Сытость даёт эффект насыщения и за каждый стак насыщения герой получает 1% бонуса своих базовых статов." + + "dota_tooltip_ability_item_coffe_bean" "Coffein bean" + "dota_tooltip_ability_item_coffe_bean_Description" "

Активное: Кофеиновый шот

Мгновенно восстанавливает %mana_pct%%% от максимальной маны цели и %mana% маны. Не занимает слоты бафф-еды, идеально заходит поверх любого перекуса." + "dota_tooltip_ability_item_coffe_bean_Lore" "Жареные зёрна, пережившие не один зомби-апокалипсис. Настолько крепкие, что способны разбудить даже древнего крипа, который притворялся декорацией последние три волны." + + + + + + "dota_tooltip_ability_item_bag_of_gold" "Bag of gold" + "dota_tooltip_ability_item_bag_of_gold_Description" "

Wealth

При поднятии вы получите 20-50 голды." + + + + "dota_tooltip_ability_item_rofl_for_kaban_pumba" "Boar hat staff" + "dota_tooltip_ability_item_rofl_for_kaban_pumba_Description" "

PUMBA????

Призывает кабана." + "dota_tooltip_ability_item_rofl_for_kaban_pumba_Lore" "Его можно бить не думая о том что ты живодёр." + + "dota_tooltip_ability_item_candy" "Candy :)" + "dota_tooltip_ability_item_candy_Description" "

Guess what you get

съев эту конфету вы получите рандомный эффект, эффекты складываются и работают вместе." + "dota_tooltip_ability_item_candy_Lore" "Сладкоежки тут!?))" + "dota_tooltip_ability_item_candy_Note0" "Шоколадная конфета: герой получает 15% бонуса к скорости передвижения, однако если остановится, то начнёт получать урон + Карамельная конфета: герой с шансом в 20% может получить на 100% урона больше чем нужно, однако также с шансом 20% может контратаковать нанеся свой урон и половину полученного. + Просроченная конфета Ваше здоровье не может опуститься ниже 1, однако вы с шансом 50% получаете станитесь, замедляетесь на 50%, все ваши характеристики уменьшены на 50%, вижен снижен на 250 единиц. + Волшебная конфета увеличивает урон с магии на 15%, уменьшает перезарядку на 15%, весь физический урон уменьшен на 15% " + + "dota_tooltip_modifier_chocolate_candy" "Шоколадная конфетка" + "dota_tooltip_modifier_chocolate_candy_Description" "Вы быстрее, но вам лучше не останавливаться." + + "dota_tooltip_modifier_caramel_candy" "Карамельная конфетка" + "dota_tooltip_modifier_caramel_candy_Description" "вы можете получить больше урона или нууууууу... контратаковать ебать его рот...????" + + "dota_tooltip_modifier_mint_candy" "Просроченная конфетка" + "dota_tooltip_modifier_mint_candy_Description" "ЭТО ПРОСТО ПИЗДЕЦЦ)0)" + + "dota_tooltip_modifier_magic_candy" "Волшебная конфетка" + "dota_tooltip_modifier_magic_candy_Description" "Ваш маг урон увеличен, перезарядка снижена, также снижен физ урон." + + "dota_tooltip_ability_item_easter_egg" "Easter egg" + "dota_tooltip_ability_item_easter_egg_Description" "

Golden fish

Вокруг вас кружит золотая рыбка и выбрасывает мешки с золотом" + "dota_tooltip_ability_item_easter_egg_Lore" "А говорили чёрная икра дорогая." + "dota_tooltip_ability_item_easter_egg_Note0" "10-50 голды с мешка" + + "dota_tooltip_ability_item_egg" "Яйцо" + "dota_tooltip_ability_item_egg_Description" "

Ингредиент

Яйцо. Используется только как ингредиент для готовки. Само по себе ничего не делает — но из него можно приготовить что-то вкусное." + "dota_tooltip_ability_item_egg_Lore" "А говорили чёрная икра дорогая." + + "dota_tooltip_modifier_orbiting_wisp" "Золотая лыбка" + "dota_tooltip_modifier_orbiting_wisp_Description" "Вокруг вас клужит лыбка и лазбласывается манетками..~" + + + + + //blackshop + //common + "dota_tooltip_ability_item_blackshop_common_injector" "Injector" + "dota_tooltip_ability_item_blackshop_common_injector_Description" "

Last resort

Увеличивает скорость атаки и передвижения на %attack_speed%.

За каждое последующее использование увеличивает ещё на %attack_speed%." + "dota_tooltip_ability_item_blackshop_common_injector_Lore" "Экспериментальный стимулятор, разработанный алхимиками Тайного Магазина. Говорят, что его создатели исчезли после слишком частого использования собственного изобретения." + "dota_tooltip_ability_item_blackshop_common_injector_Note0" "Можно получить несколько раз." + + "dota_tooltip_ability_item_blackshop_common_bonus_stats_agi" "Agility Book" + "dota_tooltip_ability_item_blackshop_common_bonus_stats_agi_Description" "

Path of thousand steps

Увеличивает ловкость на %bonus_agility%.

За каждое последующее использование увеличивает ещё на %bonus_agility%." + "dota_tooltip_ability_item_blackshop_common_bonus_stats_agi_Lore" "Древний манускрипт, написанный легендарным мастером боевых искусств. Каждая страница пропитана мудростью движений и техник, отточенных годами практики." + "dota_tooltip_ability_item_blackshop_common_bonus_stats_agi_Note0" "Можно получить несколько раз." + + "dota_tooltip_ability_item_blackshop_common_bonus_stats_str" "Strength Book" + "dota_tooltip_ability_item_blackshop_common_bonus_stats_str_Description" "

Titan's might

Увеличивает силу на %bonus_strength%.

За каждое последующее использование увеличивает ещё на %bonus_strength%." + "dota_tooltip_ability_item_blackshop_common_bonus_stats_str_Lore" "Тяжелый фолиант, найденный в руинах древнего храма титанов. Его страницы сделаны из металла, а буквы высечены самими богами." + "dota_tooltip_ability_item_blackshop_common_bonus_stats_str_Note0" "Можно получить несколько раз." + + "dota_tooltip_ability_item_blackshop_common_bonus_stats_int" "Intelligence Book" + "dota_tooltip_ability_item_blackshop_common_bonus_stats_int_Description" "

Secrets of the universe

Увеличивает интеллект на %bonus_intelligence%.

За каждое последующее использование увеличивает ещё на %bonus_intelligence%." + "dota_tooltip_ability_item_blackshop_common_bonus_stats_int_Lore" "Загадочный гримуар, чьи страницы постоянно меняются. Говорят, что каждый читатель видит в нём разное содержание, но все они становятся мудрее." + "dota_tooltip_ability_item_blackshop_common_bonus_stats_int_Note0" "Можно получить несколько раз." + + "dota_tooltip_ability_item_blackshop_common_king_crown" "King's Crown" + "dota_tooltip_ability_item_blackshop_common_king_crown_Description" "

Royal grandeur

Увеличивает здоровье на %bonus_health% и броню на %bonus_armor%.

За каждое последующее использование увеличивает ещё на %bonus_health% и %bonus_armor%." + "dota_tooltip_ability_item_blackshop_common_king_crown_Lore" "Корона, которую носили короли и цари. Её золотые короны и бриллианты сияют в лучах солнца, когда на неё смотрит король." + "dota_tooltip_ability_item_blackshop_common_king_crown_Note0" "Можно получить несколько раз." + + "dota_tooltip_ability_item_blackshop_common_manaflare" "Mana flare" + "dota_tooltip_ability_item_blackshop_common_manaflare_Description" "

Energy flow

Увеличивает ману на %bonus_mana% и реген маны на %bonus_mana_regen%.

За каждое последующее использование увеличивает ещё на %bonus_mana% и %bonus_mana_regen%." + "dota_tooltip_ability_item_blackshop_common_manaflare_Lore" "Флаер маны, который может быть использован для создания илюзий маны." + "dota_tooltip_ability_item_blackshop_common_manaflare_Note0" "Можно получить несколько раз." + + "dota_tooltip_ability_item_blackshop_common_spell_mask" "Spell mask" + "dota_tooltip_ability_item_blackshop_common_spell_mask_Description" "

Secret art

Усиливает заклинания на %bonus_spell_amp%%% и регенерацию маны на %bonus_mana_regen%.

За каждое последующее использование увеличивает ещё на %bonus_spell_amp%%% и %bonus_mana_regen%." + "dota_tooltip_ability_item_blackshop_common_spell_mask_Lore" "Древняя маска, найденная в руинах храма магов. Говорят, что она была создана великим архимагом для усиления своих заклинаний, но цена такой силы оказалась слишком высока." + "dota_tooltip_ability_item_blackshop_common_spell_mask_Note0" "Можно получить несколько раз." + + "dota_tooltip_ability_item_blackshop_common_boo_stuff" "Staff of great Boo" + "dota_tooltip_ability_item_blackshop_common_boo_stuff_Description" "

Warrior's hand

Увеличивает урон на %bonus_stats% и дальность атаки на %bonus_attack_range%.

За каждое последующее использование увеличивает ещё на %bonus_stats% и %bonus_attack_range%." + "dota_tooltip_ability_item_blackshop_common_boo_stuff_Lore" "Легендарное оружие, принадлежавшее великому воину Боо. По легенде, он мог поразить врага на невероятном расстоянии, а его удары сотрясали землю." + "dota_tooltip_ability_item_blackshop_common_boo_stuff_Note0" "Можно получить несколько раз.

Дальность атаки увеличивается только у героев ближнего боя." + + "dota_tooltip_ability_item_blackshop_common_stone_armor" "Stone armor" + "dota_tooltip_ability_item_blackshop_common_stone_armor_Description" "

Stone skin

Увеличивает броню на %bonus_armor%.

За каждое последующее использование увеличивает ещё на %bonus_armor%." + "dota_tooltip_ability_item_blackshop_common_stone_armor_Lore" "Кусок древней брони, выкованной из метеоритного камня. Даже самые острые клинки не могут оставить на нём царапины." + "dota_tooltip_ability_item_blackshop_common_stone_armor_Note0" "Можно получить несколько раз." + + "dota_tooltip_ability_item_blackshop_common_vigor_tincture" "Salve of strength" + "dota_tooltip_ability_item_blackshop_common_vigor_tincture_Description" "

Второе дыхание

Увеличивает восстановление здоровья на %bonus_hp_regen%.

За каждое последующее использование добавляет ещё %bonus_hp_regen%." + "dota_tooltip_ability_item_blackshop_common_vigor_tincture_Lore" "Смесь трав и эссенций из чёрного рынка. Пахнет железом и терпким корнем — зато раны затягиваются быстрее." + "dota_tooltip_ability_item_blackshop_common_vigor_tincture_Note0" "Можно получить несколько раз." + "dota_tooltip_ability_item_blackshop_common_vigor_tincture_bonus_hp_regen" "+$hp_regen" + + "dota_tooltip_ability_item_blackshop_common_wind_dust" "Granite boots" + "dota_tooltip_ability_item_blackshop_common_wind_dust_Description" "

Лёгкий шаг

Увеличивает скорость передвижения на %bonus_movement_speed%.

За каждое последующее использование добавляет ещё %bonus_movement_speed%." + "dota_tooltip_ability_item_blackshop_common_wind_dust_Lore" "Крошечные кристаллы, несущиеся с горных перевалов. Развеял — и ноги сами несут вперёд." + "dota_tooltip_ability_item_blackshop_common_wind_dust_Note0" "Можно получить несколько раз." + "dota_tooltip_ability_item_blackshop_common_wind_dust_bonus_movement_speed" "+$move_speed" + + "dota_tooltip_ability_item_blackshop_common_blue_tallow" "Синий воск" + "dota_tooltip_ability_item_blackshop_common_blue_tallow_Description" "

Поток маны

Увеличивает восстановление маны на %bonus_mana_regen%.

За каждое последующее использование добавляет ещё %bonus_mana_regen%." + "dota_tooltip_ability_item_blackshop_common_blue_tallow_Lore" "Застывшая эссенция манового ручья. Тает на ладони и стекает по жилам прохладой, от которой яснеет голова." + "dota_tooltip_ability_item_blackshop_common_blue_tallow_Note0" "Можно получить несколько раз." + "dota_tooltip_ability_item_blackshop_common_blue_tallow_bonus_mana_regen" "+$mana_regen" + + //rare + "dota_tooltip_ability_item_blackshop_rare_agility_cape" "Agility cape" + "dota_tooltip_ability_item_blackshop_rare_agility_cape_Description" "

Light movement

Увеличивает ловкость на %bonus_agility%.

За каждое последующее использование увеличивает ещё на %bonus_agility%." + "dota_tooltip_ability_item_blackshop_rare_agility_cape_Lore" "Плащ, который позволяет своему владельцу двигаться быстрее, чем обычно. Его создатели, полагая, что этот артефакт может быть опасен, запечатали его в сундук, который был найден в руинах древнего храма." + "dota_tooltip_ability_item_blackshop_rare_agility_cape_Note0" "Можно получить несколько раз." + + "dota_tooltip_ability_item_blackshop_rare_critical_havoc" "Critical soul devastator" + "dota_tooltip_ability_item_blackshop_rare_critical_havoc_Description" "

Critical devastator

Даёт шанс %crit_chance%%% нанести критический удар с силой в %crit_mult%%%." + "dota_tooltip_ability_item_blackshop_rare_critical_havoc_Lore" "Древний клинок, выкованный в пламени умирающей звезды. Каждый удар этого оружия несёт в себе частицу космической энергии, способной разрывать саму ткань реальности." + "dota_tooltip_ability_item_blackshop_rare_critical_havoc_Note0" "Можно получить только один раз.

Сброс позволит предмету появится вновь." + + "dota_tooltip_ability_item_blackshop_rare_damage_dagger" "Blade of destruction" + "dota_tooltip_ability_item_blackshop_rare_damage_dagger_Description" "

Blade of destruction

Увеличивает урон на %bonus_damage% на удар, после чего уходит на перезарчядку.

За каждое последующее использование увеличивает ещё на %bonus_damage%." + "dota_tooltip_ability_item_blackshop_rare_damage_dagger_Lore" "Легендарный клинок, который может разрушить не только врагов, но и саму реальность. Его создатели, полагая, что этот артефакт может быть опасен, запечатали его в сундук, который был найден в руинах древнего храма." + "dota_tooltip_ability_item_blackshop_rare_damage_dagger_Note0" "Можно получить несколько раз." + + "dota_tooltip_ability_item_blackshop_rare_egg_of_death" "Egg of death" + "dota_tooltip_ability_item_blackshop_rare_egg_of_death_Description" "

Egg of death

Увеличивает ловкость, силу и интеллект на %bonus_all%.

За каждое последующее использование увеличивает ещё на %bonus_all%." + "dota_tooltip_ability_item_blackshop_rare_egg_of_death_Lore" "Яйцо, которое может быть использовано для создания иллюзий смерти. Его создатели, полагая, что этот артефакт может быть опасен, запечатали его в сундук, который был найден в руинах древнего храма." + "dota_tooltip_ability_item_blackshop_rare_egg_of_death_Note0" "Можно получить несколько раз." + + "dota_tooltip_ability_item_blackshop_rare_granite_badge" "Гранитный жетон" + "dota_tooltip_ability_item_blackshop_rare_granite_badge_Description" "

Каменное сердце

Увеличивает максимальное здоровье на %bonus_health%.

За каждое последующее использование добавляет ещё %bonus_health%." + "dota_tooltip_ability_item_blackshop_rare_granite_badge_Lore" "Металлическая пластина с вкраплениями гранита рунического карьера. Носитель будто окутан слоем несущей стены." + "dota_tooltip_ability_item_blackshop_rare_granite_badge_Note0" "Можно получить несколько раз." + "dota_tooltip_ability_item_blackshop_rare_granite_badge_bonus_health" "+$health" + + "dota_tooltip_ability_item_blackshop_rare_iron_resolve" "Железная воля" + "dota_tooltip_ability_item_blackshop_rare_iron_resolve_Description" "

Несгибаемость

Увеличивает сопротивление эффектам на %bonus_status_resist%%%.

За каждое последующее использование добавляет ещё %bonus_status_resist%%%." + "dota_tooltip_ability_item_blackshop_rare_iron_resolve_Lore" "Клочок клятвы рыцаря, пропитанный кровью и закалённый в горне. Дебафы цепляются слабее." + "dota_tooltip_ability_item_blackshop_rare_iron_resolve_Note0" "Можно получить несколько раз." + + "dota_tooltip_ability_item_blackshop_rare_silver_eye" "Hawk eye" + "dota_tooltip_ability_item_blackshop_rare_silver_eye_Description" "

Silver gaze

Увеличивает обзор на %bonus_vision% единиц, также увеличивает дальность атаки и дальность заклинаний на %bonus_vision% единиц.

За каждое последующее использование увеличивает ещё на %bonus_vision%." + "dota_tooltip_ability_item_blackshop_rare_silver_eye_Lore" "Артефакт, который может усилить зрение своего владельца. Его создатели, полагая, что этот артефакт может быть опасен, запечатали его в сундук, который был найден в руинах древнего храма." + "dota_tooltip_ability_item_blackshop_rare_silver_eye_Note0" "Можно получить только один раз.

Сброс позволит предмету появится вновь.

Увеличение дальности атаки не работает у героев ближнего боя." + + //epic + "dota_tooltip_ability_item_blackshop_epic_power_of_grow" "Magic mushroom" + "dota_tooltip_ability_item_blackshop_epic_power_of_grow_Description" "

Power of growth

Увеличивает размер персонажа на %bonus_stats%%% и за каждый лишний процент увеличивает урон." + "dota_tooltip_ability_item_blackshop_epic_power_of_grow_Lore" "Волшебный гриб, выращенный в тайных садах древних друидов. Говорят, что эти грибы питались чистой жизненной силой земли, отчего достигали невероятных размеров. Друиды использовали их в своих ритуалах роста, но со временем утратили контроль над этой мощью, когда их ученики в погоне за силой начали злоупотреблять этими дарами природы." + "dota_tooltip_ability_item_blackshop_epic_power_of_grow_Note0" "Можно получить только один раз.

Сброс позволит предмету появится вновь." + "dota_tooltip_modifier_item_power_of_grow_scale" "Magic mushroom" + "dota_tooltip_modifier_item_power_of_grow_scale_Description" "Размер персонажа увеличен на %dMODIFIER_PROPERTY_MODEL_SCALE%%%" + + "dota_tooltip_ability_item_blackshop_epic_critical_paladin_sword" "Paladin blade" + "dota_tooltip_ability_item_blackshop_epic_critical_paladin_sword_Description" "

Paladin blade

Урон при критическом ударе на %crit_multiplier%%%." + "dota_tooltip_ability_item_blackshop_epic_critical_paladin_sword_Lore" "Меч, который может нанести критический удар с огромной силой. Его создатели, полагая, что этот артефакт может быть опасен, запечатали его в сундук, который был найден в руинах древнего храма." + "dota_tooltip_ability_item_blackshop_epic_critical_paladin_sword_Note0" "Можно получить несколько раз.

ПРЕДМЕТ МОЖЕТ СРАБОТАТЬ ТОЛЬКО ПРИ ГАРАНТИРОВАННОМ КРИТИЧЕСКОМ УДАРЕ" + + "dota_tooltip_ability_item_blackshop_epic_trinity_seal" "Печать тройственности" + "dota_tooltip_ability_item_blackshop_epic_trinity_seal_Description" "

Триединый узор

Увеличивает силу, ловкость и интеллект на %bonus_all% каждый.

За каждое последующее использование добавляет ещё по %bonus_all% ко всем трём атрибутам." + "dota_tooltip_ability_item_blackshop_epic_trinity_seal_Lore" "Металлическая печать с тремя переплетёнными кольцами. Высечена в мастерской, где ковались контракты между мирами — носитель впитывает каплю силы из каждой грани бытия." + "dota_tooltip_ability_item_blackshop_epic_trinity_seal_Note0" "Можно получить несколько раз." + "dota_tooltip_ability_item_blackshop_epic_trinity_seal_bonus_all" "+$all" + + "dota_tooltip_ability_item_blackshop_epic_bulwark_plate" "Пластина бастиона" + "dota_tooltip_ability_item_blackshop_epic_bulwark_plate_Description" "

Осада не пройдёт

Увеличивает броню на %bonus_armor_per% и магическое сопротивление на %bonus_mr_per%%%.

За каждое последующее применение добавляет столько же." + "dota_tooltip_ability_item_blackshop_epic_bulwark_plate_Lore" "Лист металла с рунами стойкости, кованный для осадных башен. На герое превращается в подвижный бастион." + "dota_tooltip_ability_item_blackshop_epic_bulwark_plate_Note0" "Можно получить несколько раз." + "dota_tooltip_modifier_item_bulwark_plate_display" "Пластина бастиона" + "dota_tooltip_modifier_item_bulwark_plate_display_Description" "Защита бастиона" + + //legendary + "dota_tooltip_ability_item_blackshop_legendary_restock" "Cycle of Prosperity" + "dota_tooltip_ability_item_blackshop_legendary_restock_Description" "

Reality is not the limit

После покупки предмета в чёрном магазине, на месте купленного появится другой предмет из текущего пула." + "dota_tooltip_ability_item_blackshop_legendary_restock_Lore" "Загадочный артефакт, созданный древними торговцами для поддержания бесконечного потока товаров. Говорят, что его магия способна материализовать любой предмет, который когда-либо существовал в этом мире." + "dota_tooltip_ability_item_blackshop_legendary_restock_Note0" "Можно получить только один раз.

Сброс не позволит предмету появится вновь." + + "dota_tooltip_ability_item_blackshop_legendary_fire_summoner" "Посох призыва Элементаля" + "dota_tooltip_ability_item_blackshop_legendary_fire_summoner_Description" "

Посох призыва Элементаля

Призывает огненного элементаля, который будет атаковать врагов." + "dota_tooltip_ability_item_blackshop_legendary_fire_summoner_Lore" "Древний посох, выкованный в сердце вулкана из чистейшего обсидиана и напитанный энергией первородного пламени. Легенды гласят, что первый элементалист использовал его для заключения договора с Повелителем Огня, получив власть над его пламенными слугами." + "dota_tooltip_ability_item_blackshop_legendary_fire_summoner_Note0" "Можно получить несколько раз.

Сброс позволит предмету появится вновь.

Получает модификаторы крита своего владельца." + + "dota_tooltip_ability_item_blackshop_legendary_primordial_shard" "Осколок прародины" + "dota_tooltip_ability_item_blackshop_legendary_primordial_shard_Description" "

Первозданная сущность

Увеличивает силу, ловкость и интеллект на %bonus_all% каждый.

За каждое последующее использование добавляет ещё по %bonus_all% ко всем трём атрибутам." + "dota_tooltip_ability_item_blackshop_legendary_primordial_shard_Lore" "Осколок материи до разделения стихий. В нём ещё слышен шёпот мира, где не было ни магии, ни меча — только цельная мощь." + "dota_tooltip_ability_item_blackshop_legendary_primordial_shard_Note0" "Можно получить несколько раз." + "dota_tooltip_ability_item_blackshop_legendary_primordial_shard_bonus_all" "+$all" + "dota_tooltip_modifier_item_primordial_shard" "Осколок прародины" + "dota_tooltip_modifier_item_primordial_shard_Description" "Бонус к силе, ловкости и интеллекту" + + "dota_tooltip_ability_item_blackshop_legendary_twilight_mirror" "Зеркало сумерек" + "dota_tooltip_ability_item_blackshop_legendary_twilight_mirror_Description" "

Отражение силы

Навсегда усиливает магию на %spell_amp_pct%%% и увеличивает получаемый урон на %incoming_damage_pct%%%." + "dota_tooltip_ability_item_blackshop_legendary_twilight_mirror_Lore" "Полированный диск из застывшего сумрака: через него проще читать заклинания, но и врагу проще пробить тебя до ядра." + "dota_tooltip_ability_item_blackshop_legendary_twilight_mirror_Note0" "Один раз на героя: если эффект уже есть, предмет не расходуется." + "dota_tooltip_modifier_item_twilight_mirror" "Зеркало сумерек" + "dota_tooltip_modifier_item_twilight_mirror_Description" "Усиление магии и уязвимость" + + "dota_tooltip_ability_item_blackshop_legendary_astral_anchor" "Якорь звёздной тверди" + "dota_tooltip_ability_item_blackshop_legendary_astral_anchor_Description" "

Дальний жест

Навсегда увеличивает дальность применения способностей на %cast_range_bonus%, но снижает скорость передвижения на %move_speed_loss_pct%%%." + "dota_tooltip_ability_item_blackshop_legendary_astral_anchor_Lore" "Кристалл, выросший в точке, где магия тянется дальше плоти — и ноги не успевают за волей." + "dota_tooltip_ability_item_blackshop_legendary_astral_anchor_Note0" "Один раз на героя: если эффект уже есть, предмет не расходуется." + "dota_tooltip_modifier_item_astral_anchor" "Якорь звёздной тверди" + "dota_tooltip_modifier_item_astral_anchor_Description" "Дальность кастов и замедление" + + "dota_tooltip_ability_item_blackshop_legendary_fated_die" "Жребий судьбы" + "dota_tooltip_ability_item_blackshop_legendary_fated_die_Description" "

Удача

Навсегда увеличивает удачу героя на %luck_bonus% (влияет на шансы дропа и прочие системы режима)." + "dota_tooltip_ability_item_blackshop_legendary_fated_die_Lore" "Игральная кость из метеоритного железа: падает не так, как хочет гравитация, а как хочет фортуна." + "dota_tooltip_ability_item_blackshop_legendary_fated_die_Note0" "Можно получить несколько раз; удача суммируется." + + //cursed + "dota_tooltip_ability_item_blackshop_cursed_the_hand_of_gluttony" "Длань пожирателя" + "dota_tooltip_ability_item_blackshop_cursed_the_hand_of_gluttony_Description" "

Длань пожирателя

При атаке, герой лечится на %bonus_stats%%% от нанесённого урона

За каждое последующее использование увеличивает ещё на %bonus_stats%%%.
+

Потеря контроля


При атаке, герой может потерять контроль над собой на 3 секунд.


Неуравновешенность


Герой может атаковать союзников и сам может быть атакован ими.
" + "dota_tooltip_ability_item_blackshop_cursed_the_hand_of_gluttony_Lore" "Рука, которая может выбрать случайного союзника и атаковать его. Её создатели, полагая, что этот артефакт может быть опасен, запечатали его в сундук, который был найден в руинах древнего храма." + "dota_tooltip_ability_item_blackshop_cursed_the_hand_of_gluttony_Note0" "Можно получить несколько раз.

Сброс не позволит предмету появится вновь." + + "dota_tooltip_ability_item_blackshop_cursed_martyrs_brand" "Клеймо мученика" + "dota_tooltip_ability_item_blackshop_cursed_martyrs_brand_Description" "

Кровь за удар

Каждое использование добавляет слой: +%bonus_damage_per%%% к урону от атак и +%incoming_damage_per_stack%%% к получаемому урону за слой.

Слои суммируются." + "dota_tooltip_ability_item_blackshop_cursed_martyrs_brand_Lore" "Железный знак, выжженный на плоти обещанием: чем громче бьёшь — тем больнее получаешь." + "dota_tooltip_ability_item_blackshop_cursed_martyrs_brand_Note0" "Можно получить несколько раз; слои суммируются." + "dota_tooltip_modifier_item_martyrs_brand" "Клеймо мученика" + "dota_tooltip_modifier_item_martyrs_brand_Description" "Бонус к урону от атак и к получаемому урону за слой" + + "dota_tooltip_ability_item_blackshop_cursed_widow_chain" "Цепь вдовы" + "dota_tooltip_ability_item_blackshop_cursed_widow_chain_Description" "

Тяжёлый удар

Каждый слой: +%bonus_outgoing_damage_pct%%% к исходящему урону и −%attack_speed_loss_pct%%% к скорости атаки. С каждой атакой герой получает обездвиживание на %root_duration% с." + "dota_tooltip_ability_item_blackshop_cursed_widow_chain_Lore" "Цепь тянет к земле: удар обещает кровь, но ноги запутываются в звеньях." + "dota_tooltip_ability_item_blackshop_cursed_widow_chain_Note0" "Можно получить несколько раз; слои суммируются. Рут накладывается на атакующего." + "dota_tooltip_modifier_item_widow_chain" "Цепь вдовы" + "dota_tooltip_modifier_item_widow_chain_Description" "Урон, медленные атаки и само-рут при ударе" + "dota_tooltip_modifier_item_widow_chain_root" "Опутывание цепи" + "dota_tooltip_modifier_item_widow_chain_root_Description" "Обездвиживание" + + "dota_tooltip_ability_item_blackshop_cursed_glass_pact" "Стеклянный пакт" + "dota_tooltip_ability_item_blackshop_cursed_glass_pact_Description" "

Хрупкая сила

Навсегда увеличивает наносимый урон на %outgoing_damage_pct%%% и получаемый урон на %incoming_damage_pct%%%." + "dota_tooltip_ability_item_blackshop_cursed_glass_pact_Lore" "Договор, выгравированный на прозрачном клинке: отражает твой удар — и чужой." + "dota_tooltip_ability_item_blackshop_cursed_glass_pact_Note0" "Один раз на героя: если пакт уже заключён, предмет не расходуется." + "dota_tooltip_modifier_item_glass_pact" "Стеклянный пакт" + "dota_tooltip_modifier_item_glass_pact_Description" "Больше урона в обе стороны" + + //heavenly + "dota_tooltip_ability_item_blackshop_heavenly_reset_to_zero" "Начало с конца" + "dota_tooltip_ability_item_blackshop_heavenly_reset_to_zero_Description" "

Перерождение

Сбрасывает весь пул предметов позволяя приобрести те, что были удалены из него." + "dota_tooltip_ability_item_blackshop_heavenly_reset_to_zero_Lore" "Древний артефакт, созданный могущественным существом, который искал способ начать всё сначала. Говорят, что его создатель использовал силу самого времени, чтобы стереть прошлое и начать с чистого листа. Однако за такую силу приходится платить - использовав артефакт однажды, его владелец навсегда теряет возможность вернуться к прежнему выбору." + "dota_tooltip_ability_item_blackshop_heavenly_reset_to_zero_Note0" "Можно получить только один раз. Сброс не позволит предмету появится вновь." + + "dota_tooltip_ability_item_blackshop_heavenly_font_of_mercy" "Купель милости" + "dota_tooltip_ability_item_blackshop_heavenly_font_of_mercy_Description" "

Общее исцеление

Расходуется при активации. Исцеляет союзных героев в радиусе %radius% на %heal_flat% плюс %heal_max_hp_pct%%% от максимального здоровья и снимает с них негативные эффекты. После этого благословение купели остаётся на герое, пока он жив: оно само снова сработает, когда перезарядка закончится и в зоне окажется союзный герой (не иллюзия) с здоровьем ниже %crisis_hp_pct%%%. При смерти героя благословение и все связанные с ним эффекты пропадают.

Первая перезарядка — %hero_cooldown% сек. Каждое следующее срабатывание сокращает длительность следующей перезарядки вдвое (от базового %hero_cooldown% сек.), но не ниже 12 сек." + "dota_tooltip_ability_item_blackshop_heavenly_font_of_mercy_Lore" "Сосуд, наполненный одним только состраданием — настолько сильным, что льётся на всех защитников рядом." + "dota_tooltip_ability_item_blackshop_heavenly_font_of_mercy_Note0" "Только одна купель на героя: повторно активировать предмет нельзя. Иллюзии не учитываются в проверке порога здоровья. Стаки бафа — сколько раз сработало исцеление. Смерть героя снимает благословение купели." + "dota_tooltip_modifier_item_heavenly_font_of_mercy_display" "Купель милости" + "dota_tooltip_modifier_item_heavenly_font_of_mercy_display_Description" "Благословение купели. Число стаков — сколько раз сработало массовое исцеление. Снимается при смерти героя." + "dota_tooltip_modifier_item_heavenly_font_of_mercy_cooldown" "Купель милости: перезарядка" + "dota_tooltip_modifier_item_heavenly_font_of_mercy_cooldown_Description" "До следующего возможного автоматического исцеления по условию порога здоровья. Снимается при смерти героя вместе с благословением." + + "dota_tooltip_ability_item_blackshop_heavenly_sanctuary_veil" "Покров святилища" + "dota_tooltip_ability_item_blackshop_heavenly_sanctuary_veil_Description" "

Покров

Снимает негативные эффекты. Одно применение добавляет фиксированные значения из предмета: +%status_resist%%% к сопротивлению эффектам и +%magic_resist%%% к магическому сопротивлению. Каждое следующее применение снова добавляет те же константы (сумма копится). Эффект не истекает и не развеивается." + "dota_tooltip_ability_item_blackshop_heavenly_sanctuary_veil_Lore" "Ткань, вытканная из тишины храмов: чем чаще накидываешь покров — тем дольше с тобой остаётся ровный свет." + "dota_tooltip_ability_item_blackshop_heavenly_sanctuary_veil_Note0" "Расходуется. Сохраняется после смерти." + "dota_tooltip_modifier_item_heavenly_sanctuary_veil_display" "Покров святилища" + "dota_tooltip_modifier_item_heavenly_sanctuary_veil_display_Description" "Благословение покрова. Слои — сколько раз применяли предмет; сопротивление эффектам и маг. сопротивление копятся константами из предмета за каждый раз." + "dota_tooltip_modifier_item_heavenly_sanctuary_veil_mr" "Покров: магическое сопротивление" + "dota_tooltip_modifier_item_heavenly_sanctuary_veil_mr_Description" "Накопленное магическое сопротивление от всех применений покрова (за раз столько, сколько в константе «магическое сопротивление» у предмета)." + + "dota_tooltip_ability_item_blackshop_heavenly_dawn_chorus" "Рассветный хор" + "dota_tooltip_ability_item_blackshop_heavenly_dawn_chorus_Description" "

Общий покров

В радиусе %radius% усиливает союзных героев: +%status_resist%%% к сопротивлению эффектам и +%magic_resist%%% к магическому сопротивлению.

За каждое последующее применение добавляет столько же каждому в зоне. Эффект не истекает и не развеивается." + "dota_tooltip_ability_item_blackshop_heavenly_dawn_chorus_Lore" "Первые лучи, собранные в звук: не гаснут, пока стая помнит рассвет." + "dota_tooltip_ability_item_blackshop_heavenly_dawn_chorus_Note0" "Расходуется. Не действует на иллюзии. Сохраняется после смерти." + "dota_tooltip_modifier_item_heavenly_dawn_chorus_display" "Рассветный хор" + "dota_tooltip_modifier_item_heavenly_dawn_chorus_display_Description" "Отголосок рассвета" + + //Axe + "dota_tooltip_ability_axe_berserkers_call_custom" "Berserker's Call" + "dota_tooltip_ability_axe_berserkers_call_custom_Description" "Провоцирует врагов в радиусе %radius% атаковать Акса и даёт ему %bonus_armor% брони на %duration% сек." + "dota_tooltip_ability_axe_berserkers_call_custom_Lore" "Когда Акс орёт, даже страх начинает подчиняться приказу." + "dota_tooltip_ability_axe_berserkers_call_custom_Shard_Description" "С Aghanim's Shard Berserker's Call дополнительно накладывает Battle Hunger на врагов, а также на Акса и союзных героев в радиусе." + "dota_tooltip_ability_axe_berserkers_call_custom_radius" "РАДИУС:" + "dota_tooltip_ability_axe_berserkers_call_custom_bonus_armor" "БОНУС К БРОНЕ:" + "dota_tooltip_ability_axe_berserkers_call_custom_duration" "ДЛИТЕЛЬНОСТЬ:" + + "dota_tooltip_ability_axe_battle_hunger_custom" "Battle Hunger" + "dota_tooltip_ability_axe_battle_hunger_custom_Description" "На враге: накладывает голод на %duration% сек, замедляя при движении от Акса и нанося %damage_per_second% урона в секунду плюс урон от брони Акса. На союзнике: каждую секунду даёт 5 стаков голода, лечит и увеличивает исходящий урон на %speed_bonus%%%." + "dota_tooltip_ability_axe_battle_hunger_custom_Lore" "Голод Акса не про еду. Это про битву, в которой никто не уходит сытым." + "dota_tooltip_ability_axe_battle_hunger_custom_slow" "%ЗАМЕДЛЕНИЕ ПЕРЕДВИЖЕНИЯ:" + "dota_tooltip_ability_axe_battle_hunger_custom_damage_per_second" "УРОН В СЕКУНДУ:" + "dota_tooltip_ability_axe_battle_hunger_custom_duration" "ДЛИТЕЛЬНОСТЬ:" + "dota_tooltip_ability_axe_battle_hunger_custom_speed_bonus" "%БОНУС К УРОНУ:" + + "dota_tooltip_ability_axe_counter_helix_custom" "Counter Helix" + "dota_tooltip_ability_axe_counter_helix_custom_Description" "При получении урона Акc с шансом %trigger_chance%%% выполняет контрудар по врагам в радиусе %radius%, нанося %damage% + урон владельца чистого урона." + "dota_tooltip_ability_axe_counter_helix_custom_Lore" "Чем ближе враг подходит, тем быстрее понимает, что это была плохая идея." + "dota_tooltip_ability_axe_counter_helix_custom_trigger_chance" "%ШАНС СРАБАТЫВАНИЯ:" + "dota_tooltip_ability_axe_counter_helix_custom_radius" "РАДИУС:" + "dota_tooltip_ability_axe_counter_helix_custom_damage" "УРОН:" + + "dota_tooltip_ability_axe_culling_blade_custom" "Culling Blade" + "dota_tooltip_ability_axe_culling_blade_custom_Description" "Поражает врагов в области вокруг выбранной точки (радиус %cull_radius%). По каждой цели наносит урон, равный текущему урону атаки Акса с руки. При убийстве даёт постоянную броню: за героя %armor_per_stack%, за крипа %armor_per_creep_kill%. При успешном убийстве ускоряет союзников в радиусе %speed_aoe%." + "dota_tooltip_ability_axe_culling_blade_custom_Lore" "Приговор Акса короткий: кто попал под топор, уже проиграл." + "dota_tooltip_ability_axe_culling_blade_custom_Scepter_Description" "С Aghanim's Scepter успешные убийства Culling Blade дополнительно сокращают оставшееся время перезарядки этой способности на 1 сек за каждую жертву." + "dota_tooltip_ability_axe_culling_blade_custom_Shard_Description" "С Aghanim's Shard каждое убийство крипа от рук Акса сокращает оставшееся время перезарядки всех его способностей на 1 сек." + "dota_tooltip_ability_axe_culling_blade_custom_cull_radius" "РАДИУС:" + "dota_tooltip_ability_axe_culling_blade_custom_speed_bonus" "%БОНУС К СКОРОСТИ ПЕРЕДВИЖЕНИЯ:" + "dota_tooltip_ability_axe_culling_blade_custom_attack_speed_bonus" "БОНУС К СКОРОСТИ АТАКИ:" + "dota_tooltip_ability_axe_culling_blade_custom_speed_duration" "ДЛИТЕЛЬНОСТЬ БОНУСА:" + "dota_tooltip_ability_axe_culling_blade_custom_speed_aoe" "РАДИУС АУРЫ УСКОРЕНИЯ:" + "dota_tooltip_ability_axe_culling_blade_custom_armor_per_stack" "БРОНИ ЗА УБИЙСТВО ГЕРОЯ:" + "dota_tooltip_ability_axe_culling_blade_custom_armor_per_creep_kill" "БРОНИ ЗА УБИЙСТВО КРИПА:" + + "dota_tooltip_ability_axe_one_man_army_custom" "One Man Army" + "dota_tooltip_ability_axe_one_man_army_custom_Description" "Если рядом нет других союзных героев в радиусе %radius%, Акс получает силу от брони: %armor_to_str% силы за 1 брони." + "dota_tooltip_ability_axe_one_man_army_custom_Lore" "Акс не ищет армию. Акс и есть армия." + "dota_tooltip_ability_axe_one_man_army_custom_radius" "РАДИУС ПРОВЕРКИ:" + "dota_tooltip_ability_axe_one_man_army_custom_armor_to_str" "СИЛЫ ЗА 1 БРОНИ:" + + "dota_tooltip_ability_special_bonus_unique_axe_2" "+150 к радиусу Culling Blade" + "dota_tooltip_ability_special_bonus_unique_axe_8" "+0.2 силы за 1 брони для One Man Army" + "dota_tooltip_ability_special_bonus_unique_axe" "+200 урона Counter Helix" + "dota_tooltip_ability_special_bonus_unique_axe_7" "+10 брони Berserker's Call" + "dota_tooltip_ability_special_bonus_unique_axe_4" "+8 урона в секунду Battle Hunger" + "dota_tooltip_ability_special_bonus_unique_axe_5" "+85 к радиусу Berserker's Call" + "dota_tooltip_ability_special_bonus_unique_axe_culling_blade_speed_duration" "+3 сек. длительности баффа Culling Blade" + + // Карты + "card_1_name" "Кровавый путь" + "card_1_description" "+%vampirism_bonus%% к вампиризму. Максимальное здоровье уменьшено на %max_health_penalty_pct%%%.
+%damage_multiplier%% ко всему физическому урону за единицу вампиризма" + "card_1_vampirism_bonus" "ВАМПИРИЗМ:" + "card_1_damage_multiplier" "УРОН ЗА ЕД. ВАМПИРИЗМА:" + "card_1_max_health_penalty_pct" "СНИЖЕНИЕ МАКС. ЗДОРОВЬЯ:" + + "card_2_name" "Лёгкость пера" + "card_2_description" "Даёт %speed_bonus% к скорости передвижения." + "card_2_description_level_2" "Уровень 2: увеличивает лимит скорости передвижения до %speed_limit%." + "card_2_description_level_3" "Уровень 3: герой проходит сквозь существ; базовое время атаки снижается на %attacktime%%%." + + "card_3_name" "Сила духа" + "card_3_description" "+%damage_pct%% ко всему физическому и магическому урону." + + "card_4_name" "Буйство духа" + "card_4_description" "Замешивает %card_bonus% карты Сила духа в пул игрока." + + "card_5_name" "Карточное безумие" + "card_5_description" "За каждую карту, взятую до этой, даёт %card_bonus_pct%% к силе, ловкости и интеллекту." + + "card_6_name" "Счастливая рука" + "card_6_description" "После взятия даёт дополнительный выбор из %card_show_count% карт. Навсегда повышает минимальное число карт в любом выборе до %min_card_choice%." + "card_6_description_level_2" "Уровень 2: +%free_reroll_charges% бесплатных реролла." + "card_6_description_level_3" "Уровень 3: выбор из этих карт будет включать только легендарные карты." + "card_6_card_show_count" "КАРТ В ДОП. ВЫБОРЕ:" + "card_6_min_card_choice" "МИН. КАРТ В ВЫБОРЕ:" + "card_6_free_reroll_charges" "БЕСПЛАТНЫХ РЕРОЛЛА:" + + "card_7_name" "Проклятие
солнечного затмения
" + "card_7_description" "Ночью:
+%daynight_bonus_pct%% ко всем характеристикам.
Днём:
-%daynight_bonus_pct%% ко всем характеристикам.
" + + "card_8_name" "Проклятие
распылающего сердца
" + "card_8_description" "За каждое убийство герой навсегда теряет %damage_bonus_health_decress% здоровья, также навсегда увеличивает урон на %damage_bonus_health_decress%. Стаки за убийства начисляются только пока здоровье владельца не ниже %stack_health_threshold% единиц." + + "card_9_name" "Проклятие
алачного мидаса
" + "card_9_description" "Даёт +1 стак общей алчности. За убийства даёт дополнительное золото: +%kill_gold_bonus_pct%%% от награды за цель за каждую копию карты. Бонус к атрибутам от нетворса (см. бафф «Алчность»).
При смерти герой теряет всё золото и случайные предметы из основного инвентаря по числу копий карты." + + "card_10_name" "Печать Пепла" + "card_10_description" "Атаки накладывают метку на %debuff_duration% сек.
За каждый стак ожога цель получает +%incoming_damage_per_fired_stack_pct%%% входящего урона (до +%max_incoming_damage_bonus_pct%%%). Бонус урона действует только для владельца карты." + + "card_11_name" "Рост амбиций" + "card_11_description" "Даёт бонус от уровня к основному атрибуту.
Если основной атрибут не универсал: +%primary_attribute_bonus_per_level% к основному атрибуту за уровень. Если основной атрибут универсал: +%universal_all_bonus_per_level% ко всем атрибутам за уровень." + + "card_12_name" "Око Ночной Бездны" + "card_12_description" "Каждые %scan_interval% сек помечает сильнейшего врага в радиусе %scan_radius% на %mark_duration% сек
По помеченной цели: +%bonus_damage_pct%% урона. За добивание цели под меткой: лечение на %on_kill_heal_pct%%% от максимального здоровья и +%on_kill_mana% маны. По боссам бонус урона ослаблен на %boss_penalty_pct%%." + + "card_13_name" "Око Пылающей Бездны" + "card_13_description" "Каждая атака накладывает %fired_stacks_on_hit% стаков ожога
С шансом %explosion_chance_pct%%% цель взрывается в радиусе %explosion_radius%. Урон взрыва: (стаки ожога × %explosion_damage_per_stack%) + урон атакующего." + + "card_14_name" "Инфернальный Рикошет" + "card_14_description" "С шансом %proc_chance_pct%%% атака поджигает цель и запускает рикошетные снаряды в радиусе %ricochet_radius%
Основной цели: +%main_target_bonus_damage_pct%%% магического урона от урона атаки и %fired_stacks% стака(ов) ожога. Каждый рикошет — отдельная атака с %ricochet_damage_pct%%% урона атаки и наложением %fired_stacks% стака(ов) ожога." + + "card_15_name" "Алый Серп" + "card_15_description" "Даёт %base_crit_chance_pct%%% шанс крита.
Урон крита растёт от удачи: %crit_multiplier_pct%%% + %crit_multiplier_bonus_pct_per_luck%%% за 1 Удачу." + + "card_16_name" "Осколки Пустоты" + "card_16_description" "За каждое убийство врага герой получает 1 стак (макс. %max_stacks%).
Каждый стак даёт +%spell_amp_per_stack_pct%%% к усилению способностей." + + "card_17_name" "Проклятие кровавой длани" + "card_17_description" "При атаке, если здоровье выше %min_health_pct_to_activate%%% , герой тратит %health_cost_pct%%% от максимального здоровья.
Потраченное здоровье переносится в чистый урон по цели; за каждый стак проклятия урон умножается дополнительно на %curse_damage_pct_per_curse_stack%%." + + "card_18_name" "Удачливые очки" + "card_18_description" "Пассивно даёт +%base_luck_bonus% Удачи.
Дополнительно: +%luck_per_card_taken% Удачи за каждую взятую карту." + + "card_19_name" "Пустое кимоно" + "card_19_description" "Атаки имеют %ghost_step_chance_pct%%% шанс активировать «Призрачный шаг» на %ghost_step_duration% сек.
Во время эффекта: +%ghost_step_move_speed_pct%%% к скорости передвижения, +%ghost_step_evasion_pct%%% к уклонению и проход сквозь юнитов." + + "card_20_name" "Золотые ботинки" + "card_20_description" "Даёт +%move_speed_pct_per_step%%% к скорости передвижения за каждые %gold_per_step% золота.
Снимает ограничение максимальной скорости передвижения.
Алчность: 1 стак за каждые 250 нетворса; +1 ко всем атрибутам за стак." + + "card_21_name" "Солнечный клинок" + "card_21_description" "При каждом ударе по юнитам типа нежить наносит дополнительный чистый урон.
Доп. урон: %wave_pure_damage_base% + %wave_pure_damage_base_hand% урона атаки владельца." + + "card_22_name" "Монеты рассвета" + "card_22_description" "На рассвете после каждой пережитой ночи выдаёт золото. После 1-й ночи — %morning_gold_base%; с каждой следующей на %morning_gold_decay_per_night% меньше." + "card_22_description_level_2" "Уровень 2: при взятии карты +%pickup_gold_pct%%% от суммы следующего рассвета." + "card_22_description_level_3" "Уровень 3: перед наступлением ночи ещё +%pre_night_gold_pct%%% от той же суммы." + + "card_23_name" "Золотой рост" + "card_23_description" "Каждые %gold_tick_interval_sec% сек начисляет дополнительное золото: %gold_income_pct_per_minute%%% от текущего золота героя.
Если за этот интервал добыто меньше %min_earned_gold_pct_of_dividend_required%%% от дивидендов, выплата не начисляется.
Алчность: 1 стак за каждые 250 нетворса; +1 ко всем атрибутам за стак." + + "card_24_name" "Золотая длань" + "card_24_description" "За каждые %gold_per_step% золота герой получает +%attack_damage_per_step%%% к урону атаки и +%spell_amp_per_step_pct%%% к усилению способностей.
Алчность: 1 стак за каждые 250 нетворса; +1 ко всем атрибутам за стак." + + "card_25_name" "Ядро маны" + "card_25_description" "За каждые %mana_per_step% текущей маны герой получает +%spell_amp_per_step_pct%%% к усилению способностей и +%mana_regen_per_step% к восстановлению маны." + "card_25_description_level_3" "Уровень 3: +%max_mana_bonus_pct%%% к максимальной мане." + "card_25_max_mana_bonus_pct" "К МАКС. МАНЕ:" + + "card_26_name" "Теневой обет" + "card_26_description" "Минимальное здоровье героя не может опуститься ниже 1.
Если здоровье ниже %trigger_health_threshold%, герой уходит в невидимость и восстанавливает %heal_pct_per_second%%% от максимального здоровья в секунду. После завершения эффекта: КД %cooldown_seconds% сек." + + "card_27_name" "Лезвие маны" + "card_27_description" "%mana_to_damage_pct%%% от текущей маны героя добавляется к урону атаки." + + "card_28_name" "Лезвие крови" + "card_28_description" "%health_to_damage_pct%%% от текущего здоровья героя добавляется к урону атаки." + + "card_29_name" "Кровавая луна" + "card_29_description" "Ночью даёт +%night_vampirism_pct%%% физического вампиризма и снижает входящий урон на %night_incoming_damage_reduce_pct%%%." + + "card_30_name" "Проклятие роста" + "card_30_description" "За каждый уровень героя: +%strength_per_level% силы, но -%agility_penalty_per_level% ловкости и -%intellect_penalty_per_level% интеллекта.
Эффект умножается на количество взятых проклятий." + + "card_31_name" "Проклятие ловкости" + "card_31_description" "За каждый уровень героя: +%agility_per_level% ловкости, но -%strength_penalty_per_level% силы и -%intellect_penalty_per_level% интеллекта.
Эффект умножается на количество взятых проклятий." + + "card_32_name" "Песочные часы" + "card_32_description" "Даёт +%base_cooldown_reduction_pct%%% к сокращению перезарядки способностей.
После каждой смерти героя эффект уменьшается на %cooldown_loss_per_death_pct%%%." + + "card_33_name" "Буйство маны" + "card_33_description" "Один раз при взятии увеличивает максимум маны на %mana_increase_pct%%% от текущего максимума." + "card_33_description_level_3" "Уровень 3: дополнительно получаете «Маленькое буйство маны»." + + "card_34_name" "Жатва крови" + "card_34_description" "При добивании врага восстанавливает здоровье: %heal_from_enemy_max_hp_pct%%% от его максимального здоровья + %heal_from_attack_damage_pct%%% от урона вашей атаки." + + "card_35_name" "Ядовитая рана" + "card_35_description" "Атаки отравляют врага. Яд наносит %damage_current_hp_pct%%% от текущего здоровья цели.
По боссам урон снижен до %boss_damage_current_hp_pct%%% от текущего здоровья." + + "card_36_name" "Кристальный налог" + "card_36_description" "За каждые %crystals_per_step% потраченных кристаллов получаете +%damage_pct_per_step%%% к исходящему урону.
Максимум: %max_bonus_pct%%%." + + "card_37_name" "Мешок с кристаллами" + "card_37_description" "При взятии карты сразу получаете %base_crystals% кристаллов + %crystals_per_night% за каждую текущую ночь." + + "card_38_name" "Кристальный рассвет" + "card_38_description" "С наступлением каждого нового дня ваши текущие кристаллы умножаются на %multiplier%x." + + "card_39_name" "Конвертер кристаллов" + "card_39_description" "Один раз при получении: все ваши кристаллы обмениваются на золото по курсу %gold_per_crystal% за 1 кристалл." + + "card_40_name" "Конвертер золота" + "card_40_description" "Один раз при получении: всё ваше золото обменивается на кристаллы по курсу %gold_per_crystal% золота за 1 кристалл (остаток золота ниже курса сохраняется)." + + "card_41_name" "Резерв лезвий" + "card_41_description" "При получении замешивает в пул «Лезвие маны» и «Лезвие крови»." + "card_41_description_level_2" "Уровень 2: сразу выбираете одно из двух лезвий." + "card_41_description_level_3" "Уровень 3: дополнительно +1 «Лезвие маны» и +1 «Лезвие крови» в пул (всего 4 карты)." + "card_41_pool_pairs" "ПАР ЛЕЗВИЙ В ПУЛ:" + + "card_42_name" "Жажда знаний" + "card_42_description" "Увеличивает получаемый опыт на %exp_bonus_pct%%%." + + "card_43_name" "Быстрые руки" + "card_43_description" "Снижает базовое время атаки на %bat_reduction%." + "card_43_description_level_2" "Уровень 2: дальнобойным героям +%projectile_speed_bonus% к скорости снарядов." + "card_43_description_level_3" "Уровень 3: время анимации атаки снижается на %attack_anim_reduction_pct%%%." + + "card_44_name" "Ветрянная дева" + "card_44_description" "+ %bonus_move_speed%%% к скорости передвижения и + %bonus_evasion%%% к уклонению.
За каждый пункт удачи: ещё +%luck_bonus_pct_per_point%%% к обоим (уклонение не выше %max_evasion_pct%%%)." + + "card_45_name" "Энергетик" + "card_45_description" "+%max_bonus_pct%%% к скорости передвижения; каждую минуту бонус уменьшается на %decay_pct_per_minute%%%.
С каждым новым утром снова восстанавливается до %max_bonus_pct%%%." + + "card_46_name" "Прилив маны" + "card_46_description" "Восстанавливает ману в размере %damage_to_mana_pct%%% от полученного урона." + + "card_47_name" "Хороший старт" + "card_47_description" "При старте игры уровень героя повышается на 1. На 3 мин. увеличивает получаемый опыт на %exp_bonus_pct%%%." + "card_47_exp_bonus_pct" "БОНУС К ОПЫТУ:" + "card_47_exp_boost_duration_sec" "ДЛИТЕЛЬНОСТЬ БОНУСА:" + + "card_48_name" "Страховой полис" + "card_48_description" "Раз за ночь: если после урона здоровье падает ниже %trigger_hp_pct%%% , восстанавливает %shield_heal_max_hp_pct%%% от макс. HP и даёт сопротивление урону %incoming_damage_reduction_pct%%% на %insurance_duration% сек." + + "card_49_name" "Второе мнение" + "card_49_description" "При взятии: даёт ещё один выбор из карт.
С каждым утром: первый реролл за кристаллы в этом дне — бесплатно." + + "card_50_name" "Легендарный жребий" + "card_50_description" "При взятии: предлагается выбор из трёх случайных легендарных карт из полного набора игры (только не врождённые легендарки)." + + "card_51_name" "Midas Chestplate" + "card_51_description" "При получении урона даёт %gold_from_damage_taken_pct%%% золота от полученного урона. Входящий урон увеличен на %incoming_damage_base_pct%%% и ещё на %incoming_damage_per_100_taken%%% за каждые 100 ед. полученного урона.
Лимит золота за утро: %morning_gold_cap% за каждую копию (сбрасывается на рассвете).
Алчность: 1 стак за каждые 250 нетворса; +1 ко всем атрибутам за стак." + "card_51_morning_gold_cap" "ЛИМИТ ЗОЛОТА ЗА УТРО:" + + "card_52_name" "Кровавый контракт" + "card_52_description" "Сокращает каждую ночь на %night_duration_reduce_sec% сек. за копию карты.
Но все статы врагов увеличиваются на %enemy_stats_bonus_pct%%% за копию." + + "card_53_name" "Эхо духа" + "card_53_description" "Если у вас уже есть Сила духа, повторный выбор этой карты сразу даёт ещё один выбор карт.
Дополнительных вариантов: %extra_card_choices%." + + "card_54_name" "Проклятие интеллекта" + "card_54_description" "За каждый уровень героя: +%intellect_per_level% интеллекта, но -%strength_penalty_per_level% силы и -%agility_penalty_per_level% ловкости.
Эффект умножается на количество взятых проклятий." + + "card_55_name" "Живучесть Гомера" + "card_55_description" "Увеличивает максимальное здоровье npc_homer на %max_health_bonus_pct%%%." + + "card_56_name" "Шипы Гомера" + "card_56_description" "Когда npc_homer получает урон от вражеских крипов, отражает %creep_reflect_pct%%% этого урона обратно атакующему." + + "card_57_name" "Зеркало судьбы" + "card_57_description" "Один раз при получении зеркалит ваш текущий пул карт и дублирует все карты в нём." + + "card_58_name" "Буйный рост" + "card_58_description" "В начале игры даёт %extra_selections_on_start% дополнительных выбора карт. На рассвете обычный выбор карт не показывается.
В каждом дополнительном выборе в начале игры — по %cards_per_selection% карт." + + "card_59_name" "Дар опыта" + "card_59_description" "Разово при получении: вы получаете %xp_reward% опыта.
Только через «Карту желания»; в колоду не ставится." + "card_59_xp_reward" "ОПЫТ:" + + "card_60_name" "Дар золота" + "card_60_description" "Разово при получении: вы получаете %gold_reward% золота.
Только через «Карту желания»; в колоду не ставится." + "card_60_gold_reward" "ЗОЛОТО:" + + "card_61_name" "Дар кристаллов" + "card_61_description" "Разово при получении: вы получаете %crystals_reward% кристаллов.
Только через «Карту желания»; в колоду не ставится." + "card_61_crystals_reward" "КРИСТАЛЛЫ:" + + "card_62_name" "Карта желания" + "card_62_description" "Разово при получении: открывает выбор из 3 карт — Дар опыта, Дар золота или Дар кристаллов." + + "card_63_name" "Контракт выжившего" + "card_63_description" "Если вы пережили ночь без смертей, на рассвете получаете дополнительный выбор из %bonus_card_choices% карт." + "card_63_bonus_card_choices" "ДОП. ВЫБОР КАРТ:" + + "card_64_name" "Волчий инстинкт" + "card_64_description" "По целям ниже %hp_threshold_pct%%% здоровья следующий удар гарантированно становится критическим.
Использует ваши текущие источники критического удара." + + "card_65_name" "Кровавая засечка" + "card_65_description" "Если цель имеет %target_hp_full_pct%%% здоровья, этот удар гарантированно становится критическим и наносит %crit_multiplier_pct%%% урона." + + "card_66_name" "Клинок судьбы" + "card_66_description" "После применения способности следующая атака дополнительно наносит магический урон, равный %bonus_from_health_pct%%% от текущего здоровья и %bonus_from_mana_pct%%% от текущей маны." + + "card_67_name" "Сердце титана" + "card_67_description" "Каждая единица силы увеличивает запас здоровья на %hp_per_strength% и исходящий урон на %outgoing_pct_per_strength%%%.
Атаки с шансом %proc_chance_pct%%% наносят дополнительный урон от максимального здоровья (%bonus_damage_from_max_hp_pct%%%) с перезарядкой %proc_cooldown_sec% сек." + + "card_68_name" "Три долга" + "card_68_description" "Сразу накладывает на героя %curse_stacks% проклятия, снова замешивает эту карту в пул колоды и открывает выбор одной карты из %cursed_offer_slots% вариантов.
Обычно — случайные проклятые карты из твоей колоды; если нет подходящих, выбор не показывается.
Если у тебя уже взята «Проклятая сделка»: варианты — любые три случайные карты из твоей колоды (не только проклятые)." + + "card_69_name" "Проклятая сделка" + "card_69_description" "Все остальные карты: их числовые параметры из каталога ослаблены на %other_cards_effectiveness_pct%%%.
За каждый стак проклятия на герое: +%outgoing_damage_per_curse_stack_pct%%% исходящего урона.
Не ослабляет параметры этой карты. Эффекты без чисел из каталога не меняются. После того как у тебя активна эта карта, каждая следующая карта из выбора даёт проклятие (карты, взятые ранее, не считаются)." + + "card_70_name" "Яростный оберег" + "card_70_description" "Каждая единица текущей ярости снижает получаемый урон на %incoming_reduction_per_rage_pct%%%.
Только для героев с ресурсом ярости; если ярости у героя нет, защита не действует." + + "card_71_name" "Тяжесть долга" + "card_71_description" "За каждый стак проклятия: +%max_health_pct_per_curse_stack%%% к максимальному здоровью.
Раз в секунду наносишь себе чистый урон, равный %self_damage_max_hp_pct_per_sec%%% от максимального здоровья." + + "card_72_name" "Яростный темп" + "card_72_description" "Перезарядка сокращена на %cooldown_reduction_pct%%%.
Только для героев с ресурсом ярости; у остальных карта не ускоряет перезарядку." + + "card_73_name" "Адмиральский ром" + "card_73_description" "%deferred_damage_pct%%% входящего урона откладывается и наносится чистым уроном равными долями за %deferred_dot_duration_sec% сек.
Остальной урон приходит сразу. Отложенная часть всё равно может добить героя." + + "card_74_name" "Снайперский прицел" + "card_74_description" "+%crit_mult_bonus_pct% к множителю критического урона
Враги в радиусе %aura_radius% теряют %enemy_armor_reduction_pct%%% брони.
" + + "card_75_name" "Мановой разряд" + "card_75_description" "Раз в %blast_interval_sec% сек. тратит %mana_cost_pct%%% максимальной маны и через %blast_delay_sec% сек. наносит %base_damage% + %damage_from_max_mana_pct%%% от максимальной маны чистым уроном врагам в радиусе %blast_radius%.
Точка взрыва выбирается так, чтобы задеть как можно больше врагов в радиусе поиска %search_radius%." + + "card_76_name" "Резонанс заклинаний" + "card_76_description" "После применения способности поражает до %target_count% ближайших врагов в радиусе %radius%, нанося %mana_damage_pct%%% от текущей маны магическим уроном каждому.
Восстанавливает здоровье в объёме фактически нанесённого урона.
Предметы и переключаемые способности не считаются. Цели должны быть видимы." + + "card_77_name" "Мана-щит" + "card_77_description" "До %damage_reduction_pct%%% входящего урона поглощается за %mana_per_mitigated_damage% маны за каждую единицу поглощённого урона.
Если маны не хватает на полное поглощение, щит ослабляет удар настолько, насколько хватает маны." + + "card_78_name" "Пульс маны" + "card_78_description" "За каждый каст способности даёт стак на %stack_duration_sec% сек.: +%max_mana_pct_per_stack%%% к максимальной мане.
Стаки суммируются; предметы и переключаемые способности не считаются. Бонус считается от макс. маны на момент каста." + + "card_79_name" "Эхо выбора" + "card_79_description" "Следующий выбор карты из окна дублируется: после того как вы возьмёте карту, она выдаётся ещё раз.
Срабатывает один раз. Не дублируются: пустая карта (404), мифические карты, Фростморн и его осколки." + + "card_80_name" "Фростморн" + "card_80_description" "Врождённый проклятый клинок. Сразу замешивает в колоду 3 осколка (лезвие, рукоять, душа). Собери все три из выбора карт — и Фростморн собран целиком: проклятие больше не усиливает входящий урон, а даёт +%outgoing_damage_per_curse_stack_pct%%% исходящего за стак.
Каждый осколок при взятии накладывает 1 проклятие. Осколки нельзя купить в магазине. Не дублируется «Зеркалом судьбы» и «Эхом выбора»." + + "card_81_name" "Осколок: лезвие" + "card_81_description" "Лезвие Фростморна. +%outgoing_damage_pct%%% исходящего урона и +%spell_amp_pct%%% усиления заклинаний. При взятии — 1 проклятие.
Собери с рукоятью и душой, чтобы завершить клинок." + + "card_82_name" "Осколок: рукоять" + "card_82_description" "Рукоять Фростморна. +%max_health_pct%%% макс. здоровья и +%max_mana_pct%%% макс. маны (мана считается от значения на момент взятия). При взятии — 1 проклятие.
Собери с лезвием и душой, чтобы завершить клинок." + + "card_83_name" "Осколок: душа клинка" + "card_83_description" "Душа Фростморна. +%model_scale_pct%%% размера героя, +%physical_vampirism_pct%%% физ. и +%magical_vampirism_pct%%% маг. вампиризма. При взятии — 1 проклятие.
Собери с лезвием и рукоятью, чтобы завершить клинок." + + "card_84_name" "Маленькое буйство маны" + "card_84_description" "Один раз при получении: усиление заклинаний равно %spell_amp_from_mana_pct%%% от максимальной маны на момент взятия. Значение больше не меняется.
Только через «Буйство маны» (ур. 3); в колоду не ставится." + "card_84_spell_amp_from_mana_pct" "УСИЛЕНИЕ ОТ МАНЫ:" + + "card_85_name" "Рыбаловство" + "card_85_description" "При взятии открывает дополнительный выбор из %bonus_card_choices% карт, снова замешивает «Рыбаловство» в пул колоды и добавляет в пул %slag_pool_copies% шлака — пустую карту без эффекта, доступную только из пула.
Число вариантов в доп. выборе растёт с уровнем карты. Шлак нельзя добавить в колоду и нельзя улучшить." + "card_85_bonus_card_choices" "ВАРИАНТОВ В ДОП. ВЫБОРЕ:" + "card_85_slag_pool_copies" "ШЛАК В ПУЛ:" + + "card_86_name" "Шлак" + "card_86_description" "Пустая карта без эффекта. Замешивается в пул только через «Рыбаловство». При выборе шлака вы ничего не получаете, но вес шлака в пуле тратится." + + "card_87_name" "Шлаковый король" + "card_87_description" "За каждую невзятую карту «Шлак» в пуле: +%attr_pct_per_slag%%% к силе, ловкости и интеллекту." + + "card_88_name" "Прикормка" + "card_88_description" "Когда вы получаете «Шлак», вы сразу получаете %free_rerolls_on_slag% бесплатных реролла." + + "card_404_name" "Пустая карта" + "card_404_description" "Нет действующего эффекта" + + // --- Карты: тултипы модификаторов (dota_tooltip_modifier_*) --- + "dota_tooltip_modifier_card_1" "Кровавый путь" + "dota_tooltip_modifier_card_1_Description" "Бонус к наносимому урону: +%dMODIFIER_PROPERTY_TOOLTIP%%%.
Снижает максимальное здоровье. Усиливает физический вампиризм." + "dota_tooltip_modifier_card_2" "Лёгкость пера" + "dota_tooltip_modifier_card_2_Description" "Скорость передвижения: +%dMODIFIER_PROPERTY_MOVESPEED_BONUS_CONSTANT%. Лимит скорости: %dMODIFIER_PROPERTY_MOVESPEED_LIMIT%.
Базовое время атаки: %dMODIFIER_PROPERTY_BASE_ATTACK_TIME_CONSTANT%.
На 3 уровне герой проходит сквозь существ." + "dota_tooltip_modifier_card_3" "Сила духа" + "dota_tooltip_modifier_card_3_Description" "Усиление заклинаний: +%dMODIFIER_PROPERTY_SPELL_AMPLIFY_PERCENTAGE%%%.
Исходящий урон (в т.ч. физический): +%dMODIFIER_PROPERTY_DAMAGEOUTGOING_PERCENTAGE%%%" + "dota_tooltip_modifier_card_4" "Буйство духа" + "dota_tooltip_modifier_card_4_Description" "При получении добавляет в будущие выборы дополнительные карты «Сила духа»." + "dota_tooltip_modifier_card_5" "Карточное безумие" + "dota_tooltip_modifier_card_5_Description" "Усиливает силу, ловкость и интеллект за каждую карту, взятую до этой." + "dota_tooltip_modifier_card_6" "Счастливая рука" + "dota_tooltip_modifier_card_6_Description" "Дополнительный выбор карт. Минимум %min_card_choice% карт в любом выборе." + "dota_tooltip_modifier_card_7" "Проклятие солнечного затмения" + "dota_tooltip_modifier_card_7_Description" "Ночью усиливает, а днём ослабляет все характеристики." + "dota_tooltip_modifier_card_8" "Проклятие распылающего сердца" + "dota_tooltip_modifier_card_8_Description" "Бонус к урону атаки: +%dMODIFIER_PROPERTY_PREATTACK_BONUS_DAMAGE%. Изменение макс. здоровья: %dMODIFIER_PROPERTY_EXTRA_HEALTH_BONUS%.
Стаки карты: %dMODIFIER_PROPERTY_TOOLTIP%" + "dota_tooltip_modifier_card_9" "Проклятие алчного мидаса" + "dota_tooltip_modifier_card_9_Description" "Даёт +1 стак алчности. Доп. золото за убийства: +%kill_gold_bonus_pct%%% от награды за цель за копию карты. Бонус к атрибутам — в баффе «Алчность».
При смерти: теряется всё золото и случайные предметы по числу копий карты." + "dota_tooltip_modifier_card_10" "Печать Пепла" + "dota_tooltip_modifier_card_10_Description" "Атаки накладывают метку.
Метка повышает входящий урон по цели от владельца карты." + "dota_tooltip_modifier_card_11" "Рост амбиций" + "dota_tooltip_modifier_card_11_Description" "Сила: +%dMODIFIER_PROPERTY_STATS_STRENGTH_BONUS%. Ловкость: +%dMODIFIER_PROPERTY_STATS_AGILITY_BONUS%. Интеллект: +%dMODIFIER_PROPERTY_STATS_INTELLECT_BONUS%.
Значения зависят от уровня и основного атрибута." + "dota_tooltip_modifier_card_12" "Око Ночной Бездны" + "dota_tooltip_modifier_card_12_Description" "Периодически помечает сильнейшего врага в радиусе.
Добивание отмеченной цели даёт лечение и ману." + "dota_tooltip_modifier_card_13" "Око Пылающей Бездны" + "dota_tooltip_modifier_card_13_Description" "Атаки накладывают ожог и с шансом вызывают взрыв цели." + "dota_tooltip_modifier_card_14" "Инфернальный Рикошет" + "dota_tooltip_modifier_card_14_Description" "Урон рикошетной атаки: %dMODIFIER_PROPERTY_DAMAGEOUTGOING_PERCENTAGE%%%.
Рикошеты также накладывают ожог." + "dota_tooltip_modifier_card_15" "Алый Серп" + "dota_tooltip_modifier_card_15_Description" "Даёт шанс критического удара.
Сила крита дополнительно растёт от Удачи." + "dota_tooltip_modifier_card_16" "Осколки Пустоты" + "dota_tooltip_modifier_card_16_Description" "Усиление заклинаний от стаков: +%dMODIFIER_PROPERTY_SPELL_AMPLIFY_PERCENTAGE%%%.
Стаки карты: %dMODIFIER_PROPERTY_TOOLTIP%" + "dota_tooltip_modifier_card_17" "Проклятие кровавой длани" + "dota_tooltip_modifier_card_17_Description" "Дополнительный чистый урон при атаке: %dMODIFIER_PROPERTY_PROCATTACK_BONUS_DAMAGE_PURE%.
Урон растёт от числа проклятых карт." + "dota_tooltip_modifier_card_18" "Удачливые очки" + "dota_tooltip_modifier_card_18_Description" "Повышает показатель Удачи.
Дополнительно даёт Удачу за каждую взятую карту." + "dota_tooltip_modifier_card_19" "Пустое кимоно" + "dota_tooltip_modifier_card_19_Description" "Атаки могут активировать «Призрачный шаг».
Во время шага герой быстрее двигается и уклоняется от атак." + "dota_tooltip_modifier_card_20" "Золотые ботинки" + "dota_tooltip_modifier_card_20_Description" "Скорость передвижения (%): +%dMODIFIER_PROPERTY_MOVESPEED_BONUS_PERCENTAGE%%%. Лимит скорости: %dMODIFIER_PROPERTY_MOVESPEED_LIMIT%. Игнор лимита: %dMODIFIER_PROPERTY_IGNORE_MOVESPEED_LIMIT%." + "dota_tooltip_modifier_card_21" "Солнечный клинок" + "dota_tooltip_modifier_card_21_Description" "Дополнительный чистый урон по врагам-нежити: %dMODIFIER_PROPERTY_PROCATTACK_BONUS_DAMAGE_PURE%" + "dota_tooltip_modifier_card_22" "Монеты рассвета" + "dota_tooltip_modifier_card_22_Description" "Следующий рассвет: %dMODIFIER_PROPERTY_TOOLTIP% золота.
Сумма уменьшается с каждой ночью." + "dota_tooltip_modifier_card_23" "Золотой рост" + "dota_tooltip_modifier_card_23_Description" "Периодически приносит золото в зависимости от текущего запаса." + "dota_tooltip_modifier_card_24" "Золотая длань" + "dota_tooltip_modifier_card_24_Description" "Исходящий урон: +%dMODIFIER_PROPERTY_DAMAGEOUTGOING_PERCENTAGE%%%. Усиление заклинаний: +%dMODIFIER_PROPERTY_SPELL_AMPLIFY_PERCENTAGE%%%" + "dota_tooltip_modifier_card_25" "Ядро маны" + "dota_tooltip_modifier_card_25_Description" "Усиление заклинаний: +%dMODIFIER_PROPERTY_SPELL_AMPLIFY_PERCENTAGE%%%. Восстановление маны: +%dMODIFIER_PROPERTY_MANA_REGEN_CONSTANT%" + "dota_tooltip_modifier_card_26" "Теневой обет" + "dota_tooltip_modifier_card_26_Description" "Не даёт умереть от смертельного урона и уводит в невидимость при низком здоровье." + "dota_tooltip_modifier_card_27" "Лезвие маны" + "dota_tooltip_modifier_card_27_Description" "К урону атаки добавляется: +%dMODIFIER_PROPERTY_PREATTACK_BONUS_DAMAGE% (от текущей маны по формуле карты)" + "dota_tooltip_modifier_card_28" "Лезвие крови" + "dota_tooltip_modifier_card_28_Description" "К урону атаки добавляется: +%dMODIFIER_PROPERTY_PREATTACK_BONUS_DAMAGE% (от текущего здоровья по формуле карты)" + "dota_tooltip_modifier_card_29" "Кровавая луна" + "dota_tooltip_modifier_card_29_Description" "Изменение входящего урона ночью: %dMODIFIER_PROPERTY_INCOMING_DAMAGE_PERCENTAGE%%%.
Ночью также увеличивается физический вампиризм." + "dota_tooltip_modifier_card_30" "Проклятие роста" + "dota_tooltip_modifier_card_30_Description" "Сила: +%dMODIFIER_PROPERTY_STATS_STRENGTH_BONUS%. Ловкость: %dMODIFIER_PROPERTY_STATS_AGILITY_BONUS%. Интеллект: %dMODIFIER_PROPERTY_STATS_INTELLECT_BONUS%.
Масштаб от уровня и числа проклятий." + "dota_tooltip_modifier_card_31" "Проклятие ловкости" + "dota_tooltip_modifier_card_31_Description" "Ловкость: +%dMODIFIER_PROPERTY_STATS_AGILITY_BONUS%. Сила: %dMODIFIER_PROPERTY_STATS_STRENGTH_BONUS%. Интеллект: %dMODIFIER_PROPERTY_STATS_INTELLECT_BONUS%.
Масштаб от уровня и числа проклятий." + "dota_tooltip_modifier_card_32" "Песочные часы" + "dota_tooltip_modifier_card_32_Description" "Сокращение перезарядки: %dMODIFIER_PROPERTY_COOLDOWN_PERCENTAGE%%%.
После каждой смерти бонус уменьшается.
Смертей с момента взятия: %dMODIFIER_PROPERTY_TOOLTIP%" + "dota_tooltip_modifier_card_33" "Буйство маны" + "dota_tooltip_modifier_card_33_Description" "Бонус к максимальной мане: +%dMODIFIER_PROPERTY_MANA_BONUS%." + "dota_tooltip_modifier_card_34" "Жатва крови" + "dota_tooltip_modifier_card_34_Description" "Добивание врага восстанавливает здоровье." + "dota_tooltip_modifier_card_35" "Ядовитая рана" + "dota_tooltip_modifier_card_35_Description" "Атаки накладывают яд.
Яд периодически наносит магический урон от текущего здоровья цели." + "dota_tooltip_modifier_card_36" "Кристальный налог" + "dota_tooltip_modifier_card_36_Description" "Бонус к наносимому урону: +%dMODIFIER_PROPERTY_DAMAGEOUTGOING_PERCENTAGE%%%.
Стаки накапливаются за потраченные кристаллы.
Стаки: %dMODIFIER_PROPERTY_TOOLTIP%" + "dota_tooltip_modifier_card_37" "Мешок с кристаллами" + "dota_tooltip_modifier_card_37_Description" "Сразу после получения даёт кристаллы." + "dota_tooltip_modifier_card_38" "Кристальный рассвет" + "dota_tooltip_modifier_card_38_Description" "На каждом рассвете умножает текущее количество кристаллов." + "dota_tooltip_modifier_card_39" "Конвертер кристаллов" + "dota_tooltip_modifier_card_39_Description" "Один раз обменивает все кристаллы на золото." + "dota_tooltip_modifier_card_40" "Конвертер золота" + "dota_tooltip_modifier_card_40_Description" "Один раз обменивает всё золото на кристаллы." + "dota_tooltip_modifier_card_41" "Резерв лезвий" + "dota_tooltip_modifier_card_41_Description" "Замешивает «Лезвие маны» и «Лезвие крови» в пул выбора. На 2 ур. — мгновенный выбор лезвия; на 3 ур. — ещё по одному лезвию каждого типа в пул." + "dota_tooltip_modifier_card_42" "Жажда знаний" + "dota_tooltip_modifier_card_42_Description" "Бонус к получаемому опыту: +%dMODIFIER_PROPERTY_EXP_RATE_BOOST%%%" + "dota_tooltip_modifier_card_43" "Быстрые руки" + "dota_tooltip_modifier_card_43_Description" "Базовое время атаки: %dMODIFIER_PROPERTY_BASE_ATTACK_TIME_CONSTANT%" + "dota_tooltip_modifier_card_44" "Ветрянная дева" + "dota_tooltip_modifier_card_44_Description" "Скорость передвижения (%): +%dMODIFIER_PROPERTY_MOVESPEED_BONUS_PERCENTAGE%%%. Уклонение: +%dMODIFIER_PROPERTY_EVASION_CONSTANT%%%" + "dota_tooltip_modifier_card_45" "Энергетик" + "dota_tooltip_modifier_card_45_Description" "Скорость передвижения (%): +%dMODIFIER_PROPERTY_MOVESPEED_BONUS_PERCENTAGE%%%.
Бонус постепенно уменьшается со временем и обновляется на рассвете." + "dota_tooltip_modifier_card_46" "Прилив маны" + "dota_tooltip_modifier_card_46_Description" "Восстанавливает ману от полученного урона." + "dota_tooltip_modifier_card_47" "Хороший старт" + "dota_tooltip_modifier_card_47_Description" "Бонус к получаемому опыту: +%dMODIFIER_PROPERTY_TOOLTIP%%%. Оставшееся время — в полоске над героем." + "dota_tooltip_modifier_card_48" "Страховой полис" + "dota_tooltip_modifier_card_48_Description" "Раз за ночь спасает при опасно низком здоровье, лечит и даёт защиту от урона." + "dota_tooltip_modifier_card_49" "Второе мнение" + "dota_tooltip_modifier_card_49_Description" "Даёт дополнительный выбор карт.
Каждое утро первый реролл за кристаллы бесплатный." + "dota_tooltip_modifier_card_50" "Легендарный жребий" + "dota_tooltip_modifier_card_50_Description" "При получении предлагает выбор из трёх случайных легендарных карт." + "dota_tooltip_modifier_card_51" "Midas Chestplate" + "dota_tooltip_modifier_card_51_Description" "Входящий урон: +%dMODIFIER_PROPERTY_INCOMING_DAMAGE_PERCENTAGE%%%.
За удар — золото от полученного урона (лимит за утро, сброс на рассвете); за каждые 100 ед. полученного урона бонус к входящему растёт." + "dota_tooltip_modifier_card_52" "Кровавый контракт" + "dota_tooltip_modifier_card_52_Description" "Каждая копия сокращает длительность ночи.
Но одновременно усиливает всех врагов." + "dota_tooltip_modifier_card_53" "Эхо духа" + "dota_tooltip_modifier_card_53_Description" "Если у вас уже есть Сила духа: повторный выбор этой карты сразу даёт дополнительный выбор из %extra_card_choices% карт." + "dota_tooltip_modifier_card_54" "Проклятие интеллекта" + "dota_tooltip_modifier_card_54_Description" "За каждый уровень героя: +%intellect_per_level% интеллекта, но -%strength_penalty_per_level% силы и -%agility_penalty_per_level% ловкости.
Эффект умножается на количество взятых проклятий." + "dota_tooltip_modifier_card_55" "Живучесть Гомера" + "dota_tooltip_modifier_card_55_Description" "Пассивно: увеличивает максимальное здоровье npc_homer на %max_health_bonus_pct%%%." + "dota_tooltip_modifier_card_56" "Шипы Гомера" + "dota_tooltip_modifier_card_56_Description" "Пассивно: когда npc_homer получает урон от вражеских крипов, отражает %creep_reflect_pct%%% этого урона атакующему." + "dota_tooltip_modifier_card_57" "Зеркало судьбы" + "dota_tooltip_modifier_card_57_Description" "Разово при получении: зеркалит ваш текущий пул карт и дублирует все карты в нём." + "dota_tooltip_modifier_card_58" "Буйный рост" + "dota_tooltip_modifier_card_58_Description" "В начале игры: %extra_selections_on_start% дополнительных выбора по %cards_per_selection% карт. На рассвете стандартный выбор карт не даётся." + "dota_tooltip_modifier_card_10_debuff" "Печать Пепла (метка)" + "dota_tooltip_modifier_card_10_debuff_Description" "Входящий урон по цели (от ожога и владельца карты): +%dMODIFIER_PROPERTY_INCOMING_DAMAGE_PERCENTAGE%%%" + "dota_tooltip_modifier_card_12_marked" "Око Ночной Бездны (метка)" + "dota_tooltip_modifier_card_12_marked_Description" "Входящий урон по помеченной цели от владельца карты: +%dMODIFIER_PROPERTY_INCOMING_DAMAGE_PERCENTAGE%%%" + "dota_tooltip_modifier_card_19_ghost_step" "Пустое кимоно (шаг)" + "dota_tooltip_modifier_card_19_ghost_step_Description" "Скорость передвижения (%): +%dMODIFIER_PROPERTY_MOVESPEED_BONUS_PERCENTAGE%%%. Уклонение: +%dMODIFIER_PROPERTY_EVASION_CONSTANT%%%" + "dota_tooltip_modifier_card_26_invisibility" "Теневой обет (невидимость)" + "dota_tooltip_modifier_card_26_invisibility_Description" "Минимальное здоровье: %dMODIFIER_PROPERTY_MIN_HEALTH%. Регенерация здоровья: +%dMODIFIER_PROPERTY_HEALTH_REGEN_CONSTANT%. Уровень невидимости: %dMODIFIER_PROPERTY_INVISIBILITY_LEVEL%." + "dota_tooltip_modifier_card_35_poison" "Ядовитая рана (яд)" + "dota_tooltip_modifier_card_35_poison_Description" "Периодический магический урон от текущего здоровья цели." + "dota_tooltip_modifier_card_48_insurance_buff" "Страховой полис (защита)" + "dota_tooltip_modifier_card_48_insurance_buff_Description" "Входящий урон: %dMODIFIER_PROPERTY_INCOMING_DAMAGE_PERCENTAGE%%% (отрицательное — снижение)" + + "dota_tooltip_modifier_card_63" "Контракт выжившего" + "dota_tooltip_modifier_card_63_Description" "Пассивно: если вы пережили ночь без смертей, на рассвете получаете дополнительный выбор из %bonus_card_choices% карт." + "dota_tooltip_modifier_card_64" "Волчий инстинкт" + "dota_tooltip_modifier_card_64_Description" "По целям ниже %hp_threshold_pct%%% здоровья: следующая атака гарантированно критует.
Использует ваши текущие источники критического урона." + "dota_tooltip_modifier_card_65" "Кровавая засечка" + "dota_tooltip_modifier_card_65_Description" "Если цель имеет %target_hp_full_pct%%% здоровья: следующая атака гарантированно критует с множителем %crit_multiplier_pct%%%." + + "dota_tooltip_modifier_card_66" "Клинок судьбы" + "dota_tooltip_modifier_card_66_Description" "Зарядов: %dMODIFIER_PROPERTY_TOOLTIP%.
После применения способности следующий удар расходует 1 заряд и наносит дополнительный магический урон: %bonus_from_health_pct%%% от текущего здоровья + %bonus_from_mana_pct%%% от текущей маны." + + + + "dota_tooltip_modifier_modifier_card_cursed" "Проклятие (счётчик)" + "dota_tooltip_modifier_modifier_card_cursed_Description" "Взятых проклятых карт: %dMODIFIER_PROPERTY_TOOLTIP%. За каждую: +25%% к получаемому урону." + "dota_tooltip_modifier_modifier_card_greed" "Алчность" + "dota_tooltip_modifier_modifier_card_greed_Description" "Стаки алчности: число на иконке — сколько карт с алчностью у тебя взято. Бонус ко всем атрибутам: %dMODIFIER_PROPERTY_TOOLTIP% (+1 к силе, ловкости и интеллекту за каждые 250 нетворса; умножается на стаки)." + "dota_tooltip_modifier_card_greed" "Алчность" + "dota_tooltip_modifier_card_greed_Description" "Стаки алчности: число на иконке — сколько карт с алчностью у тебя взято. Бонус ко всем атрибутам: %dMODIFIER_PROPERTY_TOOLTIP% (+1 к силе, ловкости и интеллекту за каждые 250 нетворса; умножается на стаки)." + "dota_tooltip_modifier_modifier_card_69" "Проклятая сделка" + "dota_tooltip_modifier_modifier_card_69_Description" "За каждый стак проклятия: +%outgoing_damage_per_curse_stack_pct%%% к исходящему урону." + "dota_tooltip_modifier_modifier_card_70" "Яростный оберег" + "dota_tooltip_modifier_modifier_card_70_Description" "Снижение входящего урона от ярости: по %incoming_reduction_per_rage_pct%%% за единицу текущей ярости (если у героя есть ярость)." + "dota_tooltip_modifier_modifier_card_71" "Тяжесть долга" + "dota_tooltip_modifier_modifier_card_71_Description" "Макс. здоровье за проклятия: +%max_health_pct_per_curse_stack%%% за стак.
Каждую секунду: %self_damage_max_hp_pct_per_sec%%% макс. HP чистым уроном по себе." + "dota_tooltip_modifier_modifier_card_72" "Яростный темп" + "dota_tooltip_modifier_modifier_card_72_Description" "Сокращение перезарядки: %cooldown_reduction_pct%%% (только герои с яростью)." + "dota_tooltip_modifier_modifier_card_73" "Адмиральский ром" + "dota_tooltip_modifier_modifier_card_73_Description" "Откладывается %deferred_damage_pct%%% входящего урона; эта часть наносится чистым уроном за %deferred_dot_duration_sec% сек." + "dota_tooltip_modifier_modifier_card_74" "Снайперский прицел" + "dota_tooltip_modifier_modifier_card_74_Description" "Крит-мульт: +%crit_mult_bonus_pct%.
Аура: радиус %aura_radius%; враги теряют %enemy_armor_reduction_pct%%% брони от текущей." + "dota_tooltip_modifier_modifier_card_75" "Мановой разряд" + "dota_tooltip_modifier_modifier_card_75_Description" "Раз в %blast_interval_sec% сек.: −%mana_cost_pct%%% маны, %base_damage% + %damage_from_max_mana_pct%%% макс. маны чистым уроном, радиус %blast_radius%." + "dota_tooltip_modifier_modifier_card_76" "Резонанс заклинаний" + "dota_tooltip_modifier_modifier_card_76_Description" "После каста способности: до %target_count% врагов в %radius% получают %mana_damage_pct%%% текущей маны магическим уроном. Лечение = нанесённый урон." + "dota_tooltip_modifier_modifier_card_77" "Мана-щит" + "dota_tooltip_modifier_modifier_card_77_Description" "Поглощение: до %damage_reduction_pct%%% урона за %mana_per_mitigated_damage% маны за 1 ед. поглощённого урона." + "dota_tooltip_modifier_modifier_card_78" "Пульс маны" + "dota_tooltip_modifier_modifier_card_78_Description" "Каст способности: стак +%max_mana_pct_per_stack%%% к макс. мане на %stack_duration_sec% сек." + "dota_tooltip_modifier_modifier_card_78_mana_stack" "Пульс маны" + "dota_tooltip_modifier_modifier_card_78_mana_stack_Description" "+%max_mana_pct_per_stack%%% к максимальной мане." + "dota_tooltip_modifier_modifier_card_79" "Эхо выбора" + "dota_tooltip_modifier_modifier_card_79_Description" "Ожидает следующий выбор: взятая карта будет выдана повторно (кроме пустой, мифических и Фростморна)." + "dota_tooltip_modifier_modifier_card_80" "Фростморн (незавершён)" + "dota_tooltip_modifier_modifier_card_80_Description" "Собрано осколков: %dMODIFIER_PROPERTY_TOOLTIP% / 3. Возьми лезвие, рукоять и душу из выбора карт." + "dota_tooltip_modifier_modifier_card_80_pact_complete" "Фростморн" + "dota_tooltip_modifier_modifier_card_80_pact_complete_Description" "Клинок собран. Проклятие даёт +%outgoing_damage_per_curse_stack_pct%%% исходящего урона за стак вместо входящего. Стаков: %dMODIFIER_PROPERTY_TOOLTIP%." + "dota_tooltip_modifier_modifier_card_81" "Лезвие Фростморна" + "dota_tooltip_modifier_modifier_card_81_Description" "Исходящий урон: +%outgoing_damage_pct%%%. Усиление заклинаний: +%spell_amp_pct%%%." + "dota_tooltip_modifier_modifier_card_82" "Рукоять Фростморна" + "dota_tooltip_modifier_modifier_card_82_Description" "Макс. здоровье: +%max_health_pct%%%. Макс. мана: +%max_mana_pct%%% (бонус маны зафиксирован при взятии)." + "dota_tooltip_modifier_modifier_card_83" "Душа Фростморна" + "dota_tooltip_modifier_modifier_card_83_Description" "Размер: +%model_scale_pct%%%. Вампиризм: +%physical_vampirism_pct%%% физ., +%magical_vampirism_pct%%% маг." + "dota_tooltip_modifier_modifier_card_84" "Маленькое буйство маны" + "dota_tooltip_modifier_modifier_card_84_Description" "Усиление заклинаний (зафиксировано при взятии): +%dMODIFIER_PROPERTY_TOOLTIP%%%." + "dota_tooltip_modifier_card_cursed" "Проклятие (счётчик)" + "dota_tooltip_modifier_card_cursed_Description" "Взятых проклятых карт: %dMODIFIER_PROPERTY_TOOLTIP%. За каждую: +5%% к получаемому урону." + + // Магазин + + "store_title" "Магазин" + "store_tab_items" "Предметы" + "store_tab_cards" "Карты" + "store_tab_arcade" "Аркада" + "store_tab_upgrades" "Эффекты" + "store_tab_chatwheel" "Колесо чата" + "store_tab_marketplace" "Торговая площадка" + "store_tab_promotions" "Акции" + "store_promotions_title" "Доступные акции" + "store_promotions_bundles_title" "Наборы и акции" + "store_promotions_bundles_hint" "Специальные предложения за рубли. Каждый набор — один раз на аккаунт." + "store_promotions_weekly_title" "Предложение этой недели" + "store_promocode_button" "ПРОМОКОД" + "store_promocode_title" "Промокод" + "store_promocode_desc" "Введи код и получи валюту магазина" + "store_promocode_placeholder" "Например: ZOMBIENEW" + "store_promocode_apply" "Применить" + "store_promocode_close" "Закрыть" + "store_deal_timer_prefix" "Обновление через" + "store_deal_timer_loading" "Загрузка таймера…" + "store_deals_daily_badge" "Акция дня" + "store_deals_daily_empty" "Акция дня загружается…" + "store_deals_weekly_badge" "Акция недели" + "store_deals_weekly_slot" "Неделя" + "store_deal_item_owned" "Уже в коллекции" + "store_deal_used" "Акция использована" + "store_deal_buy" "Купить со скидкой" + "store_bundle_owned" "Уже куплено" + "store_bundle_buy" "Купить" + "store_bundle_timer_urgency" "Успей купить!" + "store_bundle_timer_ends_in" "До конца акции" + "store_bundle_timer_prefix" "Акция закончится через" + "store_promo_sound_click" "Нажми — послушать" + "store_bundle_starter_zaika_500_title" "Стартовый бандл «Зайка»" + "store_bundle_starter_zaika_500_description" "Battle Pass Premium, звук «Зайка», 20 000 пыли, 300 донат-осколков и 250 000 зомби-осколков." + "store_bundle_starter_zaika_500_reward_battle_pass_premium" "Battle Pass — Premium" + "store_bundle_starter_zaika_500_reward_sound_zaika" "Звук чат-колеса «Зайка»" + "store_bundle_starter_zaika_500_reward_dust" "20 000 пыли" + "store_bundle_starter_zaika_500_reward_donate" "300 донат-осколков" + "store_bundle_starter_zaika_500_reward_free" "250 000 зомби-осколков" + "store_bundle_newbie_card_packs_399_title" "Набор карт для новичков" + "store_bundle_newbie_card_packs_399_description" "8 обычных и 2 премиум пака карт аркады — открой их во вкладке «Аркада»." + "store_bundle_newbie_card_packs_399_reward_arcade_pack_standard" "8 обычных паков карт" + "store_bundle_newbie_card_packs_399_reward_arcade_pack_premium" "2 премиум пака карт" + + "store_subscription_monthly_subscription_199_title" "Месячная подписка" + "store_subscription_monthly_subscription_199_description" "Каждый день при первом заходе: 30 донат-осколков и 5 000 зомби-осколков. Можно покупать снова — срок суммируется." + "store_subscription_monthly_subscription_199_reward_daily_donate" "30 донат-осколков в день" + "store_subscription_monthly_subscription_199_reward_daily_free" "5 000 зомби-осколков в день" + "store_subscription_monthly_subscription_199_reward_duration" "+30 дней за покупку" + "store_subscription_buy" "Оформить" + "store_subscription_extend" "Продлить" + "store_subscription_active" "Подписка активна" + "store_subscription_active_until" "Активна до" + "store_subscription_daily_claimed" "Сегодня награда уже получена" + "store_subscription_timer_prefix" "Действует до" + "store_subscription_daily_reward_toast" "Подписка: +{d} донат, +{s} зомби-осколков" + "store_subscription_daily_welcome_title" "С возвращением!" + "store_subscription_daily_welcome_kicker" "Месячная подписка" + "store_subscription_daily_welcome_body" "Ты получаешь ежедневную награду:" + "store_subscription_daily_reward_line_donate" "+{amount} донат-осколков" + "store_subscription_daily_reward_line_free" "+{amount} зомби-осколков" + "store_subscription_daily_ok" "Отлично!" + "store_exchange_title" "Обмен валюты" + "store_exchange_desc" "Обменяй донат осколки на зомби осколки по фиксированному курсу." + "store_exchange_rate_prefix" "Курс" + "store_exchange_apply" "Обменять" + "store_exchange_spend" "Потратишь" + "store_exchange_get" "получишь" + "store_chatwheel_title" "КОЛЕСО ЧАТА" + "store_marketplace_title" "ТОРГОВАЯ ПЛОЩАДКА" + "store_marketplace_hint" "Выставляй предметы арсенала за зомби-осколки и покупай предложения других игроков." + "store_marketplace_refresh" "Обновить" + "store_marketplace_your_inventory" "Твои предметы для листинга" + "store_marketplace_active_listings" "Активные лоты" + "store_marketplace_my_listings" "Мои лоты" + "store_marketplace_mine_active_slots_hint" "Активные лоты (8 слотов)" + "store_marketplace_mine_buy_slots_hint" "Дополнительные слоты за донат" + "store_marketplace_slot_buy_donate" "Купить за %PRICE% донат валюты" + "store_marketplace_seller" "Продавец" + "store_marketplace_listing_id" "Лот" + "store_marketplace_buy" "Выкупить" + "store_marketplace_cancel" "Снять" + "store_marketplace_list" "Выставить" + "store_marketplace_subtab_browse" "Торговая площадка" + "store_marketplace_subtab_mine" "Мои лоты" + "store_marketplace_subtab_sales_history" "История продаж" + "store_marketplace_history_col_item" "Предмет" + "store_marketplace_history_col_buyer" "Покупатель" + "store_marketplace_history_col_price" "Цена" + "store_marketplace_history_col_fee" "Комиссия" + "store_marketplace_history_col_received" "Вам" + "store_marketplace_history_col_date" "Дата" + "store_marketplace_history_empty" "Пока нет продаж. После покупки твоего лота запись появится здесь." + "store_marketplace_history_buyer_id" "Steam: %s" + "store_marketplace_listing_details" "Детали лота" + "store_marketplace_stats_filter" "Фильтр по статам" + "store_marketplace_slot_filter" "Фильтр по слоту" + "store_marketplace_item_stats" "Статы предмета" + "store_marketplace_listing_price" "Цена лота" + "store_marketplace_set_price" "Указать цену" + "store_marketplace_commission_note" "Комиссия площадки: 20% удерживается у продавца" + "store_marketplace_seller_prefix" "Продавец" + "store_marketplace_first_dropper_prefix" "Первым выбил" + "store_marketplace_confirm_wallet_title" "Боевые осколки" + "store_marketplace_confirm_balance_was" "Сейчас" + "store_marketplace_confirm_balance_after" "После сделки" + "store_marketplace_confirm_spend" "Списание" + "store_marketplace_confirm_insufficient" "Недостаточно боевых осколков" + "store_marketplace_take_from_auction" "Забрать с аукциона" + "store_marketplace_min_level_1_short" "Только с 1 уровня" + "store_marketplace_price_min_short" "Мин.: 5000" + "store_marketplace_no_listing_selected" "Выбери лот в сетке слева" + "store_marketplace_sort_label" "Сортировка:" + "store_marketplace_sort_price_asc" "Цена ↑" + "store_marketplace_sort_price_desc" "Цена ↓" + "store_marketplace_sort_time_desc" "Сначала новые" + "store_marketplace_empty_browse" "Сейчас нет лотов по выбранным фильтрам. Нажми «Обновить» или смени фильтр по статам." + "store_marketplace_empty_inventory" "В арсенале нет предметов для выставления. Открой арсенал или дождись синхронизации." + "store_marketplace_slots" "Слоты лотов" + "store_marketplace_buy_slot" "Купить слот" + "store_marketplace_slots_max" "Максимум слотов" + "store_marketplace_col_item" "Предмет" + "store_marketplace_col_score" "Сила" + "store_marketplace_col_price" "Цена" + "store_marketplace_col_time" "Время" + "store_marketplace_col_action" "Действие" + "store_marketplace_error_fetch" "Не удалось загрузить лоты." + "store_marketplace_error_invalid_data" "Некорректные данные запроса." + "store_marketplace_error_locked_item" "Закреплённые и любимые предметы нельзя выставлять." + "store_marketplace_error_min_price" "Минимальная цена лота: 5000." + "store_marketplace_error_slots_full" "Свободные слоты лотов закончились." + "store_marketplace_error_create" "Не удалось создать лот." + "store_marketplace_error_buy" "Не удалось купить лот." + "store_marketplace_error_buy_in_progress" "Подождите: предыдущая покупка на маркете ещё обрабатывается." + "store_marketplace_error_create_in_progress" "Подождите: предыдущее выставление лота ещё обрабатывается." + "store_marketplace_error_cancel" "Не удалось снять лот." + "store_marketplace_error_buy_slot" "Не удалось купить слот." + "store_marketplace_success_create" "Лот успешно создан." + "store_marketplace_success_buy" "Покупка завершена." + "store_marketplace_success_cancel" "Лот снят с продажи." + "store_marketplace_success_buy_slot" "Новый слот куплен." + "store_items_title" "ПРЕДМЕТЫ" + "store_cards_title" "КАРТЫ" + "store_arcade_title" "АРКАДА" + "store_arcade_subtitle" "Пак из 3 случайных карт. Нажми на карту, чтобы открыть её." + "store_item_arcade_pack_standard_name" "Стандартный пак" + "store_item_arcade_pack_standard_description" "3 случайные карты из каталога. Дубликаты заменяются пылью за апгрейд этой карты." + "store_item_arcade_pack_premium_name" "Премиум-пак" + "store_item_arcade_pack_premium_description" "3 случайные карты из каталога. Дубликаты заменяются пылью за апгрейд этой карты." + "store_arcade_free_pack_badge_prefix" "Бесплатно" + "store_arcade_reveal_title" "Открой карты" + "store_arcade_reveal_hint" "Нажми на каждую карту, чтобы увидеть награду" + "store_arcade_reveal_progress" "Открыто" + "store_arcade_reveal_done" "Все карты открыты!" + "store_arcade_reveal_close" "Закрыть" + "store_arcade_duplicate_dust" "Дубликат — пыль" + "store_arcade_duplicate_max" "Дубликат (лимит копий)" + "store_arcade_flip_error" "Не удалось открыть карту" + "store_arcade_purchase_failed" "Не удалось купить пак" + "store_arcade_pity_mythic" "Мифик" + "store_arcade_pity_epic" "Эпик" + "store_upgrades_title" "ЭФФЕКТЫ" + + "filter_purchased" "Приобретено" + "filter_not_purchased" "Не приобретено" + "filter_price_range" "Диапазон цен" + "filter_price_min" "Мин:" + "filter_price_max" "Макс:" + "search_and_filters" "Поиск и фильтры" + "filter_all" "Все" + "filter_donate_currency" "Донатная валюта" + "filter_free_currency" "Бесплатная валюта" + "filter_cheap" "Дешёвые" + "filter_expensive" "Дорогие" + "filter_common" "Обычные" + "filter_rare" "Редкие" + "filter_epic" "Эпические" + "filter_legendary" "Легендарные" + "filter_mythic" "Мифические" + "purcase_confirm" "Подтверждение покупки" + "confirm" "Вы точно хотите купить этот предмет?" + "accept_buying" "Купить" + "cancel_buying" "Отмена" + "select_currency" "Выберите валюту для оплаты:" + "store_buy" "КУПИТЬ" + "store_purchased" "КУПЛЕНО" + "store_card_copies_label" "Копии:" + "store_card_limit_reached" "ЛИМИТ" + "store_cancel" "ОТМЕНИТЬ" + "store_close" "ЗАКРЫТЬ" + "store_rarity_common" "ОБЫЧНО" + "store_rarity_rare" "РЕДКО" + "store_rarity_epic" "ЭПИЧЕСКИ" + "store_rarity_legendary" "ЛЕГЕНДАРНО" + "store_rarity_mythic" "МИФИЧЕСКИ" + "store_unique" "УНИКАЛЬНО" + "store_equip" "ОДЕТЬ" + "store_unequip" "СНЯТЬ" + "store_slot_effect" "СЛОТ: ЭФФЕКТ" + "store_slot_wings" "СЛОТ: КРЫЛЬЯ" + "store_slot_model" "СЛОТ: МОДЕЛЬ" + + + // Редактор колод + "deck_builder_open_button" "Колоды" + "deck_builder_title" "РЕДАКТОР КОЛОД" + "deck_builder_close" "×" + "deck_builder_dust_label" "Пыль" + "deck_builder_decks_section" "КОЛОДЫ" + "deck_builder_new_deck" "+ НОВАЯ КОЛОДА" + "deck_builder_default_name" "Новая колода" + "deck_builder_save" "СОХРАНИТЬ" + "deck_builder_use" "ИСПОЛЬЗОВАТЬ" + "deck_builder_delete" "УДАЛИТЬ" + "deck_builder_available_cards" "ДОСТУПНЫЕ КАРТЫ" + "deck_builder_card_hint" "Shift + ЛКМ — подробный просмотр карты" + "deck_builder_deck_hint" "Не забудьте активировать колоду! Двойным нажатием можно активировать колоду для игры" + "deck_builder_card_purchased" "Куплено" + "deck_builder_card_not_purchased" "Не куплено" + "deck_builder_card_default" "По умолчанию" + "deck_builder_search_placeholder" "ПОИСК" + "deck_builder_filter_all" "ВСЕ" + "deck_builder_filter_common" "ОБЫЧНЫЕ" + "deck_builder_filter_rare" "РЕДКИЕ" + "deck_builder_filter_epic" "ЭПИЧЕСКИЕ" + "deck_builder_filter_legendary" "ЛЕГЕНДАРНЫЕ" + "deck_builder_filter_mythic" "МИФИЧЕСКИЕ" + "deck_builder_import_export_open" "Импорт / экспорт" + "deck_builder_no_decks" "Нет колод. Создайте новую!" + "deck_builder_auto_deck" "Новая колода" + "deck_builder_new_deck_name" "Новая колода" + "deck_builder_new_deck_name_with_number" "Новая колода" + "deck_builder_deck_name" "Колода" + "deck_builder_selected" "АКТИВНА" + "deck_builder_close_fullscreen" "ЗАКРЫТЬ" + "deck_builder_import_export_title" "ИМПОРТ / ЭКСПОРТ КОЛОДЫ" + "deck_builder_export" "ЭКСПОРТ" + "deck_builder_import" "ИМПОРТ" + "deck_builder_card_level" "Уровень: {level}/{max}" + "deck_builder_card_level_plain" "Уровень: {level}" + "deck_builder_upgrade_for_cost" "Улучшить за {cost}" + "deck_builder_upgrade_max_level" "Макс. уровень" + "deck_builder_upgrade_unavailable" "Улучшение недоступно" + "deck_builder_card_shard_only" "Не для колоды · только в бою" + "deck_builder_deck_slots_tooltip" "Занимает {count} {slots_word} в колоде" + "deck_builder_slot_word_one" "слот" + "deck_builder_slot_word_few" "слота" + "deck_builder_slot_word_many" "слотов" + "deck_builder_deck_incomplete_warning" "Колода неполная: {used}/{required} слотов. Недостающие карты будут автоматически добавлены из дефолтной колоды при старте." + "deck_builder_deck_activate_incomplete" "Колода занимает {used}/{required} слотов. Недостающие карты будут автоматически добавлены из дефолтной колоды." + "deck_builder_status_loading" "Колоды ещё загружаются" + "deck_builder_status_select_deck" "Сначала выберите или создайте колоду" + "deck_builder_status_no_deck_slot" "Нет свободного слота для новой колоды" + "deck_builder_status_create_failed" "Не удалось создать новую колоду" + "deck_builder_card_limit_reached" "Лимит копий достигнут" + "deck_builder_card_copies_label" "Копии:" + "deck_builder_import_export_status_exported" "Строка колоды экспортирована" + "deck_builder_import_export_status_imported" "Колода успешно импортирована" + "deck_builder_import_export_copy_code" "Скопировать код" + "deck_builder_import_export_load_code" "Загрузить из кода" + "deck_builder_import_export_apply" "Применить к колоде" + "deck_builder_import_export_new_deck" "Новая колода" + "deck_builder_import_export_status_loaded" "Колода загружена из кода" + "deck_builder_import_export_status_applied" "Колода обновлена" + "deck_builder_import_error_non_deckable" "Эту карту нельзя добавить в колоду (только прокачка в инвентаре)." + "deck_builder_import_error_empty" "Введите строку колоды" + "deck_builder_import_error_format" "Неверный формат. Используй id,id,id" + "deck_builder_import_error_deck_size" "Слишком много слотов в колоде (максимум {max})" + "deck_builder_import_error_unknown_card" "В строке есть неизвестная карта" + "deck_builder_import_error_max_copies" "Превышен лимит копий карты" + "deck_builder_import_error_not_owned" "Недостаточно купленных копий карты" + "card_remaining_cards_title" "Оставшиеся карты" + "card_remaining_cards" "Оставшиеся количество карт:" + "inherent_card_tooltip" "Врождённые карты применяются автоматически в начале игры и не занимают слоты в колоде" + "inherent_card" "Врождённая" + + // card selection + "card_reroll_button" "Стоимость обновления:" + "card_reroll_free_second_opinion" "Бесплатно" + "card_hide_button" "Скрыть карты" + "card_show_button" "Показать карты" + + // Cooking Panel + "cooking_panel_search" "Поиск" + "cooking_panel_recipe_details" "Детали рецепта" + "cooking_panel_ready_dishes" "Готовые блюда" + "cooking_panel_no_recipes_found" "Рецепты не найдены" + "cooking_panel_cooking_time" "Готовится: {time}с" + "cooking_panel_select_recipe" "Выберите рецепт" + "cooking_panel_recipe_not_found" "Рецепт не найден" + "cooking_panel_ingredients" "Ингредиенты:" + "cooking_panel_cook" "Готовить" + "cooking_panel_not_enough_ingredients" "Недостаточно ингредиентов" + "campfire_already_occupied" "Костер уже занят другим игроком" + "cooking_panel_no_ready_dishes" "Нет готовых блюд" + "cooking_panel_charges" "Количество: {charges}" + "cooking_panel_dish_count" "Количество: x{count}" + "cooking_panel_cooking_duration" "Время готовки: {duration} секунд" + "cooking_intro_message" "Добро пожаловать в систему готовки!" + + // Чат-вилла звуки + "chat_wheel_donate_sound_i_am_sad" "Человек которого мы потеряли" + "chat_wheel_donate_sound_jump" "Прыгай дура!" + "chat_wheel_donate_sound_empty" "Пустой слот" + "chat_wheel_donate_sound_agent_gabena" "Агент Габена" + "chat_wheel_donate_sound_byd_dobr_idi" "Будь добр, иди.." + "chat_wheel_donate_sound_cat_shnapy" "Шни шна..." + "chat_wheel_donate_sound_dobro_pozhalovat_v_club" "Добро пожаловать в клуб" + "chat_wheel_donate_sound_eto_prosto_okhueno" "Это просто охуенно" + "chat_wheel_donate_sound_get_out" "GET OUT" + "chat_wheel_donate_sound_ia_vas_unichtozhu" "Я вас уничтожу" + "chat_wheel_donate_sound_kak_rulit" "Как рулить?" + "chat_wheel_donate_sound_kto_myaukaet" "Кто мяукает?" + "chat_wheel_donate_sound_Muhehehehe" "Мухехехе" + "chat_wheel_donate_sound_ne_tvoy_uroven_dorogoy" "Не твой уровень, дорогой" + "chat_wheel_donate_sound_ne_ponimaiu_karina_strimersha_slozhno_slozhno" "Ничего не понимаю (Карина, «сложно-сложно»)" + "chat_wheel_donate_sound_nikhuia_ne_ponial_no_ochen_interesno" "Нихуя не понял, но очень интересно" + "chat_wheel_donate_sound_oi_tak_nravitsa" "Ой, так нравится" + "chat_wheel_donate_sound_olyhi_bezdari_ogyzki" "ОООООгрызки" + "chat_wheel_donate_sound_ou_mai" "Oh my…" + "chat_wheel_donate_sound_po_syobam" "Уходим" + "chat_wheel_donate_sound_poshel_process" "Пошёл процесс" + "chat_wheel_donate_sound_posledniy_ponedelnik_zivesh" "Последний понедельник живёшь" + "chat_wheel_donate_sound_rot_etogo_kazino" "Рот этого казино" + "chat_wheel_donate_sound_s_kakoy_stati" "С какой стати" + "chat_wheel_donate_sound_sir_no_sir" "Сэр, нет сэр" + "chat_wheel_donate_sound_sir_yes_sir" "Сэр, да сэр" + "chat_wheel_donate_sound_stop_mne_ne_priyatno" "Стоп, мне не приятно" + "chat_wheel_donate_sound_stoyat_ya_yzhe_eto_sosal" "Стоять, я уже это сосал" + "chat_wheel_donate_sound_ura_pobeda" "Ура победа" + "chat_wheel_donate_sound_uvorot_ot_spelov" "Уворот от спеллов" + "chat_wheel_donate_sound_v_komp_igri_igral" "В комп игры играл" + "chat_wheel_donate_sound_vot_eto_nikhuia_sebe" "Вот это нихуя себе" + "chat_wheel_donate_sound_zachem_ya_suda_prishel" "Зачем я сюда пришёл" + "chat_wheel_donate_sound_zaika" "Зайка" + "chat_wheel_donate_sound_kitty_flex" "Китти флекс" + "chat_wheel_donate_sound_eblo_razraba" "Ебло разработчика" + "chat_wheel_donate_sound_kuda" "Куда" + "chat_wheel_donate_sound_bruh" "Bruh" + "chat_wheel_donate_sound_shizofreniya" "Шизофрения" + "chat_wheel_donate_sound_vot_eto_povorot" "Вот это поворот" + "chat_wheel_donate_sound_fbi_open_up" "FBI, открывай!" + "chat_wheel_donate_sound_nepravilno_poprobuy_esche_raz" "Неправильно, попробуй ещё раз" + "chat_wheel_donate_sound_dobro_pozhalovat_na_server_shizofreniya" "Добро пожаловать на сервер шизофрении" + "chat_wheel_donate_sound_nya" "Ня" + "chat_wheel_donate_sound_na_nas_napali" "На нас напали" + "chat_wheel_donate_sound_murlok" "Мурлок" + "chat_wheel_donate_sound_kak_zhit_to_a" "Как жить-то а" + + // Чат-вилла интерфейс + "chat_wheel_tab_settings" "Настройки" + "chat_wheel_tab_shop" "Покупка" + "chat_wheel_shop_title" "Доступные звуки для покупки" + "chat_wheel_no_sounds" "У вас нет доступных звуков для колеса чата" + "chat_wheel_sound_sad_description" "Грустный звук для колеса чата" + "dota_error_cooldown_chat_wheel" "Звук на перезарядке" + "chat_wheel_settings_hint" "Выберите слот на колесе, затем нажмите на звук" + + // Экран конца матча + "match_end_title" "Итог матча" + "match_end_title_win" "Победа!" + "match_end_title_loss" "Поражение" + "match_end_bp_caption" "Твой Battle Pass" + "match_end_bp_level_short" "Ур." + "match_end_bp_xp_short" "Опыт" + "match_end_col_hero" "Герой" + "match_end_col_player" "Игрок" + "match_end_col_kills" "Крипы" + "match_end_col_deaths" "Смерти" + "match_end_col_dmg_out" "Урон нанесён" + "match_end_col_dmg_in" "Урон получен" + "match_end_col_incoming_events" "Вход. урон (события)" + "match_end_col_currency" "Осколки" + "match_end_anim_caption" "Награда: опыт BP и осколки" + "match_end_anim_difficulty" "Сложность" + "match_end_anim_creeps" "Крипы" + "match_end_anim_quests" "Квесты" + "match_end_anim_deaths" "Смерти" + "match_end_anim_total" "Итого" + "match_end_close" "Закрыть" + + + } +} \ No newline at end of file diff --git a/resource/addon_schinese.txt b/resource/addon_schinese.txt new file mode 100644 index 0000000..db21f80 --- /dev/null +++ b/resource/addon_schinese.txt @@ -0,0 +1,5045 @@ +"lang" +{ + "Language" "Schinese" + "Tokens" + { +//为了我 +//要创建新密钥(如 $health),只需在此处添加条目即可 +"dota_ability_variable_health" "走向健康" //$health +"dota_ability_variable_mana" "到法力" //$mana +"dota_ability_variable_armor" "到盔甲" //$armor +"dota_ability_variable_damage" "造成损害" //$damage +"dota_ability_variable_str" "达到强度" //$str +"dota_ability_variable_int" "迈向智慧" //$int +"dota_ability_variable_agi" "灵活" //$agi +"dota_ability_variable_all" "到所有属性" //$all +"dota_ability_variable_primary_attribute" "到主属性" //$primary_attribute +"dota_ability_variable_attack" "攻击速度" //$attack +"dota_ability_variable_attack_pct" "达到基本攻击速度" //$attack_pct +"dota_ability_variable_hp_regen" "迈向健康恢复" //$hp_regen +"dota_ability_variable_lifesteal" "走向吸血鬼" //$lifesteal +"dota_ability_variable_mana_regen" "迈向法力恢复" //$mana_regen +"dota_ability_variable_mana_regen_aura" "将法力恢复到光环" //$mana_regen_aura +"dota_ability_variable_spell_amp" "造成伤害" //$spell_amp +"dota_ability_variable_debuff_amp" "到负面影响的持续时间" //$debuff_amp +"dota_ability_variable_move_speed" "达到移动速度" //$move_speed +"dota_ability_variable_evasion" "避免" //$evasion +"dota_ability_variable_spell_resist" "走向魔法抵抗" //$spell_resist +"dota_ability_variable_spell_lifesteal" "用咒语对抗吸血鬼" //$spell_lifesteal +"dota_ability_variable_spell_lifesteal_creep" "用咒语(来自怪物)走向吸血鬼" //$spell_lifesteal_creep +"dota_ability_variable_spell_lifesteal_hero" "用咒语(来自英雄)走向吸血鬼" //$spell_lifesteal_hero +"dota_ability_variable_selected_attrib" "到选定的属性" //$selected_attribute +"dota_ability_variable_attack_range" "攻击范围(在远程战斗中)" //$attack_range +"dota_ability_variable_attack_range_melee" "攻击范围(近战)" //$attack_range_melee +"dota_ability_variable_cast_range" "到咒语范围" //$cast_range +"dota_ability_variable_status_resist" "抵抗影响" //$status_resist +"dota_ability_variable_projectile_speed" "达到射弹速度" //$projectile_speed +"dota_ability_variable_manacost_reduction" "减少法力消耗" //$manacost_reduction +"dota_ability_variable_cooldown_reduction" "减少法术充能" //$cooldown_reduction +"dota_ability_variable_max_mana_percentage" "达到最大法力值" //$max_mana_percentage +"dota_ability_variable_slow_resistance" "抵抗减速" //$slow_resistance +"dota_ability_variable_aoe_bonus" "到法术半径" //$aoe_bonus +"dota_ability_variable_crit_multiplier" "到克里特岛乘数" //$crit_multiplier(自定义统计) +"dota_ability_variable_crit_chance" "克里特岛的机会" //$crit_chance +"dota_ability_variable_crit_mult" "致克里特人的伤害" //$crit_mult + + +"addon_game_name" "僵尸入侵2" +"cooldown_message" "充电:" + +"zinv_item_build_night_1" "第1夜" +"zinv_item_build_night_2" "第2夜" +"zinv_item_build_night_3" "第3夜" +"zinv_item_build_night_4" "第4夜" + +"ZOMIEINVASION_NEWS_TITLE" "僵尸入侵 2 新闻提要" + +"ZINV_NEWS_DEMO_TITLE" "演示:新闻的所有功能" +"ZINV_NEWS_DEMO_BODY" "示例卡:文本、图片、行和对象图标。" +"ZINV_NEWS_DEMO_DETAILS" "欢迎来到演示帖子!以下是所有布局选项。\n\n 带有换行符的常规文本块。您可以编写列表:\n - 点 1\n- 点 2\n\n按位置插入一张大图:\n [img=file://{images}/custom_game/loading_screen/loadscreen.png]\n\n 行:左侧为图片,右侧为文本:\n[imgrow=file://{images}/custom_game/loading_screen/loadscreen.png]\n 这是图像右侧长描述的示例。在这里您可以讨论功能、给出数字、给出使用示例\n 和任何其他信息。\n[/imgrow]\n\n尺寸项目的图标:\n[item=blink] — 更新 cd。\n [item=ultimate_scepter] — 重新设计的奖金。\n[item=aghanims_shard] — 新的升级。\n\n 如果文本中没有 [img=...],则从图像字段回退将起作用。感谢您的关注!" + +"ZINV_NEWS_START_TITLE" "欢迎来到新闻推送!" +"ZINV_NEWS_START_BODY" "我们推出了新闻提要。请在此处关注更新。" +"ZINV_NEWS_START_DETAILS" "欢迎来到新闻源!\n\n 现在所有重要的更改、修复和计划都将出现在这里。\n\n 未来计划:\n - UI 改进\n - 平衡编辑\n - 新的游戏棋子\n - 创建新字符\n\n\n 感谢您的参与!" + +//额外的 +"additional" "附加" +"additional_luck" "运气:" +"additional_vampirism" "物理。吸血鬼主义:" +"additional_magic_vampirism" "魔术师。吸血鬼主义:" +"additional_crit_mult" "克里特岛乘数:" + + +"difficulty_select_title" "选择难度:" +"difficulty_selected" "复杂性已选择" +"#easy" "简单" +"#normal" "常规" +"#hard" "复杂" +"#impossible" "不可能" + "easy" "简单" + "normal" "常规" + "hard" "复杂" + "impossible" "不可能" + "death_sentence" "死亡判决" + "contract_vote_slots_title" "投票中的裁决" + "contract_menu_title" "我的裁决" + "contract_menu_title_mine" "你的裁决:" + "contract_menu_empty" "暂无裁决" + "contract_stats_multiplier" "属性倍率" + "contract_reward_bonus" "奖励加成" + + "death_sentence_contracts_title" "死亡判决裁决" + "death_sentence_contracts_hint" " " + "death_sentence_contracts_inventory_section" "契约库存" + + "death_sentence_contracts_tooltip_kind" "裁决" + + "death_sentence_contracts_title" "死亡判决裁决" + "death_sentence_contracts_hint" " " + "death_sentence_contracts_inventory_section" "契约库存" + "death_sentence_contracts_tooltip_kind" "裁决" + "death_sentence_contracts_tooltip_reward" "奖励倍率:" + "death_sentence_contracts_tooltip_multiplier" "倍率:" + "death_sentence_contracts_tooltip_durability" "耐久:" + "death_sentence_complication_explode" "生成时有25%概率敌人将获得:死亡时在范围内爆炸并造成物理伤害。" + "death_sentence_complication_creep_stats" "小兵的最大生命值、攻击伤害和生命恢复 ×1.25。" + "death_sentence_complication_brutal_strikes" "敌人的基础攻击伤害提高2倍。" + "death_sentence_complication_zombie_virus" "生成时有25%概率敌人将获得:攻击时有概率施加中毒和减速。" + "death_sentence_complication_ghost_evasive" "生成时有25%概率敌人将获得:所有敌人拥有自由移动,闪避提高33%。" + "death_sentence_complication_phasing_march" "生成时有25%概率敌人将获得:可穿过其他单位,移动速度+100。" + "death_sentence_complication_zombie_armor_decress" "生成时有25%概率敌人将获得:攻击会短暂降低目标护甲。" + "death_sentence_complication_toxin" "生成时有25%概率敌人将获得:死亡后在范围内留下中立神经毒素。" + "death_sentence_complication_full_brutality" "生成时有25%概率敌人将获得:小兵强度突然提升至2倍。" + "death_sentence_complication_desperate_vampirism" "生成时有25%概率敌人将获得:生命低于一半时,攻击会回复生命。" + "death_sentence_complication_head_leap" "生成时有25%概率敌人将获得:敌人会周期性弧形跃向英雄曾站立的位置,对落点区域造成伤害。" + "death_sentence_complication_bone_armor" "生成时有25%概率敌人将获得:额外护甲。对远程英雄:450范围内没有英雄时,攻击者受到所造成伤害5倍的反击;否则有几率反射部分伤害。" + "death_sentence_complication_short_day" "白天阶段缩短一分钟。" + "death_sentence_complication_scarce_loot" "击杀生物时从全局掉落表获得物品的概率降低一半。" + "death_sentence_dismantle_ok" "已拆解裁决:+{d} 尘埃。" + "death_sentence_dismantle_fail" "无法拆解该裁决。" + "death_sentence_dismantle_batch_ok" "已拆解 {n} 份裁决:+{d} 尘埃。" + "death_sentence_dismantle_batch_skipped_pinned" "(已跳过 {n} 份 — 已固定。)" + "death_sentence_repair_ok" "裁决耐久已恢复至上限。" + "death_sentence_repair_fail" "无法恢复裁决耐久。" + "death_sentence_repair_fail_funds" "捐赠碎片不足。" + "death_sentence_repair_fail_full" "耐久已满。" + "death_sentence_repair_fail_state" "仅可在开局前的大厅中使用。" + "death_sentence_repair_fail_not_found" "未找到该裁决。" + "death_sentence_repair_fail_save" "保存失败。已退还捐赠碎片。" + "death_sentence_repair_confirm_body" "花费 {d} 枚捐赠碎片,将这份裁决的耐久恢复至上限吗?" + "death_sentence_repair_confirm_yes" "确认修理" + "death_sentence_repair_confirm_cancel" "取消" + "death_sentence_contracts_context_dismantle" " " + "death_sentence_detail_vote" "投票支持该裁决" + "death_sentence_detail_unvote" "撤回投票" + "death_sentence_detail_lobby_showcase_set" "置于大厅列表" + "death_sentence_detail_lobby_showcase_clear" "从大厅列表移除" + "death_sentence_detail_dismantle" "拆解" + "death_sentence_detail_repair" "修理" + "death_sentence_detail_actions_hint" " " + "death_sentence_detail_debuff_count" "波次难题栏位: %d1" + "death_sentence_contract_tile_hover_hint" " " + "death_sentence_contracts_grid_lore_hint" " " + "death_sentence_rarity_common" "普通" + "death_sentence_rarity_rare" "稀有" + "death_sentence_rarity_epic" "史诗" + "death_sentence_rarity_legendary" "传奇" + "death_sentence_rarity_mythic" "神话" + "ds_sentence_title_001" "裁决全面肃清" + "ds_sentence_title_002" "敕令全面肃清" + "ds_sentence_title_003" "判词全面肃清" + "ds_sentence_title_004" "密令全面肃清" + "ds_sentence_title_005" "谕旨全面肃清" + "ds_sentence_title_006" "告示全面肃清" + "ds_sentence_title_007" "通缉全面肃清" + "ds_sentence_title_008" "铁令全面肃清" + "ds_sentence_title_009" "终裁全面肃清" + "ds_sentence_title_010" "誓杀全面肃清" + "ds_sentence_title_011" "裁决无尽围城" + "ds_sentence_title_012" "敕令无尽围城" + "ds_sentence_title_013" "判词无尽围城" + "ds_sentence_title_014" "密令无尽围城" + "ds_sentence_title_015" "谕旨无尽围城" + "ds_sentence_title_016" "告示无尽围城" + "ds_sentence_title_017" "通缉无尽围城" + "ds_sentence_title_018" "铁令无尽围城" + "ds_sentence_title_019" "终裁无尽围城" + "ds_sentence_title_020" "誓杀无尽围城" + "ds_sentence_title_021" "裁决红区封锁" + "ds_sentence_title_022" "敕令红区封锁" + "ds_sentence_title_023" "判词红区封锁" + "ds_sentence_title_024" "密令红区封锁" + "ds_sentence_title_025" "谕旨红区封锁" + "ds_sentence_title_026" "告示红区封锁" + "ds_sentence_title_027" "通缉红区封锁" + "ds_sentence_title_028" "铁令红区封锁" + "ds_sentence_title_029" "终裁红区封锁" + "ds_sentence_title_030" "誓杀红区封锁" + "ds_sentence_title_031" "裁决黑幕隔离" + "ds_sentence_title_032" "敕令黑幕隔离" + "ds_sentence_title_033" "判词黑幕隔离" + "ds_sentence_title_034" "密令黑幕隔离" + "ds_sentence_title_035" "谕旨黑幕隔离" + "ds_sentence_title_036" "告示黑幕隔离" + "ds_sentence_title_037" "通缉黑幕隔离" + "ds_sentence_title_038" "铁令黑幕隔离" + "ds_sentence_title_039" "终裁黑幕隔离" + "ds_sentence_title_040" "誓杀黑幕隔离" + "ds_sentence_title_041" "裁决全境检疫" + "ds_sentence_title_042" "敕令全境检疫" + "ds_sentence_title_043" "判词全境检疫" + "ds_sentence_title_044" "密令全境检疫" + "ds_sentence_title_045" "谕旨全境检疫" + "ds_sentence_title_046" "告示全境检疫" + "ds_sentence_title_047" "通缉全境检疫" + "ds_sentence_title_048" "铁令全境检疫" + "ds_sentence_title_049" "终裁全境检疫" + "ds_sentence_title_050" "誓杀全境检疫" + "ds_sentence_title_051" "裁决绝对孤绝" + "ds_sentence_title_052" "敕令绝对孤绝" + "ds_sentence_title_053" "判词绝对孤绝" + "ds_sentence_title_054" "密令绝对孤绝" + "ds_sentence_title_055" "谕旨绝对孤绝" + "ds_sentence_title_056" "告示绝对孤绝" + "ds_sentence_title_057" "通缉绝对孤绝" + "ds_sentence_title_058" "铁令绝对孤绝" + "ds_sentence_title_059" "终裁绝对孤绝" + "ds_sentence_title_060" "誓杀绝对孤绝" + "ds_sentence_title_061" "裁决无情清洗" + "ds_sentence_title_062" "敕令无情清洗" + "ds_sentence_title_063" "判词无情清洗" + "ds_sentence_title_064" "密令无情清洗" + "ds_sentence_title_065" "谕旨无情清洗" + "ds_sentence_title_066" "告示无情清洗" + "ds_sentence_title_067" "通缉无情清洗" + "ds_sentence_title_068" "铁令无情清洗" + "ds_sentence_title_069" "终裁无情清洗" + "ds_sentence_title_070" "誓杀无情清洗" + "ds_sentence_title_071" "裁决前线抹除" + "ds_sentence_title_072" "敕令前线抹除" + "ds_sentence_title_073" "判词前线抹除" + "ds_sentence_title_074" "密令前线抹除" + "ds_sentence_title_075" "谕旨前线抹除" + "ds_sentence_title_076" "告示前线抹除" + "ds_sentence_title_077" "通缉前线抹除" + "ds_sentence_title_078" "铁令前线抹除" + "ds_sentence_title_079" "终裁前线抹除" + "ds_sentence_title_080" "誓杀前线抹除" + "ds_sentence_title_081" "裁决不留活口" + "ds_sentence_title_082" "敕令不留活口" + "ds_sentence_title_083" "判词不留活口" + "ds_sentence_title_084" "密令不留活口" + "ds_sentence_title_085" "谕旨不留活口" + "ds_sentence_title_086" "告示不留活口" + "ds_sentence_title_087" "通缉不留活口" + "ds_sentence_title_088" "铁令不留活口" + "ds_sentence_title_089" "终裁不留活口" + "ds_sentence_title_090" "誓杀不留活口" + "ds_sentence_title_091" "裁决焚桥绝路" + "ds_sentence_title_092" "敕令焚桥绝路" + "ds_sentence_title_093" "判词焚桥绝路" + "ds_sentence_title_094" "密令焚桥绝路" + "ds_sentence_title_095" "谕旨焚桥绝路" + "ds_sentence_title_096" "告示焚桥绝路" + "ds_sentence_title_097" "通缉焚桥绝路" + "ds_sentence_title_098" "铁令焚桥绝路" + "ds_sentence_title_099" "终裁焚桥绝路" + "ds_sentence_title_100" "誓杀焚桥绝路" + + "death_sentence_contracts_compact_hint" " " + "death_sentence_contracts_ds_row_tooltip" " " + "death_sentence_contracts_preview_empty_tooltip" " " + "death_sentence_contracts_preview_lobby_slot_empty" " " + "death_sentence_contracts_click_to_change" " " + + "death_sentence_contracts_open_btn" "§" + "death_sentence_contracts_open_tooltip" " " + "death_sentence_contracts_propose_btn" "提议裁决" + "death_sentence_contracts_valid_short" "计入:" + "death_sentence_contracts_total_short" "总计:" + "death_sentence_contracts_tooltip_votes_inline" "票数" + "death_sentence_contracts_votes_legend" "计入 · 总计" + "death_sentence_contracts_slot_not_owned_tooltip" " " + "ds_contract_iron" "钢铁誓约" + "ds_contract_iron_desc" "基础契约:除死亡判决外不对小兵追加修饰。比赛奖励总倍率:×3.5(普通档约 3–4)。" + "ds_contract_blood" "血之契约" + "ds_contract_blood_desc" "敌人出生时获得 +1 基础护甲。比赛奖励总倍率:×4.5(稀有档约 4–5)。" + "death_sentence_contracts_effect_iron_1" "除死亡判决外不对小兵施加额外改动。" + + "death_sentence_contracts_effect_iron_2" "纯粹死亡判决:敌方波次倍率 ×6。" + + "death_sentence_contracts_effect_blood_1" "敌人出生时额外获得 +1 基础护甲。" + + "death_sentence_contracts_effect_blood_2" "更难在前期清兵。" + + "ds_contract_shadow" "影纱" + "ds_contract_shadow_desc" "敌人出生时额外获得 +300 基础移动速度。比赛奖励总倍率:×5.5(史诗档约 5–6)。" + + "ds_contract_fury" "群落狂怒" + "ds_contract_fury_desc" "传奇裁决:比赛奖励总倍率 ×6.5(档次约 6–7)。随机波次难题数量随稀有度:普通 0、稀有 1、史诗 2、传奇 3、神话 4。" + + "death_sentence_contracts_effect_shadow_1" "敌人出生时额外获得 +300 基础移动速度。" + "death_sentence_contracts_effect_shadow_2" "没有控制在身时更难风筝大群小怪。" + + "death_sentence_contracts_effect_fury_1" "敌人攻击的基础最小/最大伤害乘以约 ×1.08。" + "death_sentence_contracts_effect_fury_2" "兵线堆叠时近战压力明显提高。" + + + + +"difficulty_description_easy" "<字体颜色='#00fe40d6'>♡简单难度: ──────────────────────────────────────────

非常适合熟悉。

─────────────────────────────────────────

功能:

<字体颜色='#00fe40d6'>-50% 健康,伤害,恢复所有敌人的健康。

+60/-60 秒 白天阶段和夜间阶段

─────────────────────────────────────────

为了获得更高的沉浸感,建议选择更高的难度。

─────────────────────────────────────────

" +"difficulty_description_normal" "<字体颜色='#02c4ff'>正常难度: ──────────────────────────────────────────

非常适合初学者。

─────────────────────────────────────────

功能:

敌人状态未更改。

─────────────────────────────────────────

建议玩这个难度。
─────────────────────────────────────────" +"difficulty_description_hard" "<字体颜色='#f70850d8'>高度复杂性: ──────────────────────────────────────────

非常适合已经接受过训练的玩家。

─────────────────────────────────────────

功能:

<字体颜色='#f70850d8'>+200% 健康,伤害,恢复所有敌人的健康。

-60/+60 秒 白天阶段和夜间阶段

─────────────────────────────────────────

仅当您知道什么时才运行此难度 做。

─────────────────────────────────────────

" +"difficulty_description_impossible" "哈哈哈哈哈哈哈哈
哈哈哈哈哈哈哈哈
哈哈哈哈哈哈哈哈
哈哈哈哈哈哈哈哈
哈哈哈哈哈哈哈哈
哈哈哈哈哈哈哈哈
哈哈哈哈哈哈哈哈
哈哈哈哈哈哈哈哈
哈哈哈哈哈哈哈哈 哈哈哈哈哈哈哈哈 哈哈哈哈哈哈哈哈 哈哈哈哈哈哈哈哈 哈哈哈哈哈哈哈哈 哈哈哈哈哈哈哈哈 哈哈哈哈哈哈哈哈 哈哈哈哈哈哈哈哈 哈哈哈哈哈哈哈哈 哈哈哈哈哈哈哈哈 哈哈哈哈哈哈哈哈 哈哈哈哈哈哈哈哈 哈哈哈哈哈哈哈哈 哈哈哈哈哈哈哈哈 哈哈哈哈哈哈哈哈 哈哈哈哈哈哈哈哈 哈哈哈哈哈哈哈哈 " +"difficulty_description_death_sentence" "死亡判决:

契约模式。选择你的契约并加入全局投票。契约会提高敌人属性倍率、附加队伍减益,并提供额外奖励加成。" + +//社交链接 +"social_discord" "Discord" +"social_boosty" "Boosty" + +"start_game" "开始游戏" +"cancel_game" "取消" +"ready_status_ready" "就绪" +"ready_status_not_ready" "尚未准备好" +"ready_button_ready" "尚未准备好" +"ready_button_not_ready" "就绪" +"setup_hints_title" "提示" +"setup_hint_fallback_empty" "该提示暂未填写。" +"setup_hint_1" "夜晚敌人更强、更激进。白天尽快收割目标并稳住节奏。" +"setup_hint_2" "尽量抱团:集火单个目标通常比分散伤害更有效。" +"setup_hint_3" "关注商店和夜晚后的卡牌。阵容联动通常比随机购买更重要。" +"setup_hint_4" "如果你经常阵亡,优先补生存、控制和站位,不要只堆伤害。" +"setup_hint_5" "多用信号和简短聊天轮盘信息,能显著提升团队配合。" +"setup_hint_6" "点击上方计时器可发起提前入夜投票,这样能减少时间带来的额外成长压力。" +"setup_hint_7" "每个英雄在单打和团队中都有价值。记得支援前排与输出位,一起变强。" +"setup_hint_8" "火焰与寒霜层数会互相抵消:当目标已有较多火焰层数时,新施加的寒霜会先抵消一部分火焰。" +"setup_hint_9" "寒霜:每层降低1%移速与攻速;30层后目标承受额外伤害,100层时首领会被定身/冻结。" +"setup_hint_10" "饥饿是食物增益:层数(最多100)会提高基础属性,并会随时间逐步衰减。" +"setup_hint_11" "在黑市花费水晶可获得实用卡牌、道具与关键强化。" +"setup_hint_12" "积极完成NPC目标与事件,可稳定获取资源和额外奖励。" +"setup_hint_13" "关注自定义属性,尤其是幸运值:它会放大许多百分比效果。" +"setup_hint_14" "如果难度太高,建议先在练习局熟悉地图与核心机制。" +"setup_hint_15" "关注项目动态:Discord 与 Telegram 的兑换码可提供额外货币。" +"setup_hint_16" "即使卡牌不多,也能通过站位、时机与团队配合打出效果。" +"setup_hint_17" "本模式的吸血机制与标准Dota不同,出装时请单独考虑。" +"setup_hint_18" "需要配装或战术建议时,可前往项目 Discord 社区交流。" +"setup_hint_19" "发现Bug请提交到 bug-reports,能帮助更快修复问题。" +"setup_hint_20" "部分英雄拥有“怒气”机制:可提高输出并影响道具表现。" +"setup_hint_21" "高难局更看重执行力:及时调整出装、保持节奏并坚持到最后。" +"setup_hint_22" "如果发现文本或翻译问题,可在 Discord 反馈以便快速修正。" +"setup_hint_23" "在 Impossible 难度下,波次敌人可能获得额外技能。" +"setup_hint_24" "请多包容新手并尽量不要中途退局,稳定阵容更容易取胜。" +"setup_hint_25" "高排名可获得额外奖励,记得查看游戏内排行榜。" +"ending_cutscene_ready_title" "最终过场动画准备" +"ending_cutscene_ready_names_prefix" "已准备:" +"ending_cutscene_ready_names_default" "已准备:-" +"ending_cutscene_ready_timer_prefix" "开始倒计时:" +"ending_cutscene_ready_timer_seconds" "秒" +"ending_cutscene_ready_waiting_first" "等待首位准备玩家..." +"ending_cutscene_ready_toggle_collapse" "收起面板" +"ending_cutscene_ready_toggle_expand" "展开面板" +"skip_night_vote_title" "跳过白天并开始夜晚?" +"skip_night_vote_yes" "同意" +"skip_night_vote_no" "拒绝" + +//迷你个人资料 +"profile_title" "玩家个人资料" +"profile_loading" "上传个人资料..." +"profile_error" "启动错误" +"profile_steam_id" "蒸汽ID:" +"profile_level" "等级" +"profile_stats_title" "统计数据" +"profile_games_played" "玩的游戏:" +"profile_winrate" "获胜百分比:" +"profile_wins" "获胜:" +"profile_losses" "失败:" +"profile_playtime" "游戏时间:" +"profile_first_game" "第一场比赛:" +"profile_first_game_unknown" "未知" +"profile_recent_games" "最近的游戏" +"profile_no_games" "还没有游戏。" +"profile_game_win" "胜利" +"profile_game_loss" "失败" +"profile_game_result" "结果" +"profile_game_date" "日期" +"profile_game_duration" "时间" +"profile_game_kd" "K/D" +"profile_game_hero" "英雄" +"profile_description" "玩家资料" +"mini_profile_tab_stats" "统计" +"mini_profile_tab_history" "历史" +"mini_profile_tab_rewards" "奖励" +"mini_profile_tab_achievements" "成就" +"mini_profile_rewards_title" "档案等级奖励" +"mini_profile_achievements_title" "英雄成就" +"mini_profile_achievements_search_placeholder" "搜索英雄..." +"mini_profile_filter_all" "全部" +"mini_profile_filter_completed" "已完成" +"mini_profile_filter_impossible" "不可能" +"mini_profile_filter_unplayed" "未游玩" +"mini_profile_stats_line" "场次: {games} 胜场: {wins} 胜率: {wr}%" +"mini_profile_rewards_granted_now" "本次发放: +{value} 碎片" +"mini_profile_rewards_auto_hint" "达到对应等级后会自动发放奖励" +"mini_profile_reward_level" "等级 {level}" +"mini_profile_reward_value" "+{value} 碎片" +"mini_profile_reward_status_granted_now" "本次发放" +"mini_profile_reward_status_claimed" "已领取" +"mini_profile_reward_status_locked" "未达成" +"hero_rank_tooltip_title" "英雄段位" +"hero_rank_tooltip_subtitle" "在更高难度获胜并获得碎片" +"hero_rank_tooltip_header_rank" "段位" +"hero_rank_tooltip_header_condition" "条件" +"hero_rank_tooltip_header_reward" "奖励" +"hero_rank_name_rank0" "无人" +"hero_rank_name_rank1" "新手" +"hero_rank_name_rank3" "爱好者" +"hero_rank_name_rank6" "老兵" +"hero_rank_name_rank8a" "职业" +"hero_rank_name_rank8b" "专家" +"hero_rank_name_rank8c" "不朽" +"hero_rank_name_master" "大师" +"hero_rank_tooltip_condition_rank0" "该英雄未通关" +"hero_rank_tooltip_condition_rank1" "在简单难度获胜" +"hero_rank_tooltip_condition_rank3" "在普通难度获胜" +"hero_rank_tooltip_condition_rank6" "在困难难度获胜" +"hero_rank_tooltip_condition_rank8a" "在不可能难度获胜" +"hero_rank_tooltip_condition_rank8b" "不可能: 10+ 胜" +"hero_rank_tooltip_condition_rank8c" "不可能: 100+ 胜" + +//战斗通行证 +"battle_pass_title" "战斗通行证" +"battle_pass_level" "级别:" +"battle_pass_xp" "XP" +"battle_pass_claim_all" "拿走一切" +"battle_pass_loading" "战斗通行证下载..." +"battle_pass_tab_rewards" "奖项" +"battle_pass_tab_quests" "任务" +"battle_pass_track_free" "免费" +"battle_pass_track_premium" "高级" +"battle_pass_premium_match_reward_bonus" "高级通行证:比赛结算奖励+50%加成已激活" +"battle_pass_buy_button" "购买战斗通行证" +"battle_pass_claim" "拿走" +"battle_pass_buy_dialog_title" "战斗通行证溢价" +"battle_pass_buy_dialog_desc" "获得独家高级曲目奖励!" +"battle_pass_buy_dialog_feature_1" "✦ 独特的化妆品" +"battle_pass_buy_dialog_feature_2" "✦ 每个级别的奖励加倍" +"battle_pass_buy_dialog_feature_3" "✦ 独家效果和卡牌" +"battle_pass_buy_dialog_feature_4" "✦ 获得的战斗通行证经验 +50%" +"battle_pass_buy_dialog_feature_5" "✦ 额外 +6 个每日任务" +"battle_pass_buy_dialog_price" "成本:" +"battle_pass_buy_dialog_confirm" "购买" +"battle_pass_buy_dialog_cancel" "取消" +"battle_pass_quests_empty" "任务即将完成……" +"battle_pass_quests_loading" "工作正在加载..." +"battle_pass_quests_daily" "日常任务" +"battle_pass_quests_completed" "来自" +"battle_pass_quests_completed_of" "已执行" +"battle_pass_quests_refresh" "通过以下方式更新:" +"battle_pass_quest_reward_xp" "BP XP" +"battle_pass_quest_completed" "完成" +"battle_pass_error_purchase" "购买错误" +"battle_pass_description" "战斗通行证" +"battle_pass_no_reward" "没有奖励" + +//战斗通行证任务({target} 自动从 quest。target 中替换) + +"battle_pass_quest_kill_zombies_100_name" "猎人" +"battle_pass_quest_kill_zombies_100_description" "杀死{target}生物。" +"battle_pass_quest_kill_zombies_300_name" "战士" +"battle_pass_quest_kill_zombies_300_description" "杀死{target}生物。" +"battle_pass_quest_kill_zombies_1000_name" "种族灭绝" +"battle_pass_quest_kill_zombies_1000_description" "杀死{target}生物。" +"battle_pass_quest_survive_10min_name" "幸存者" +"battle_pass_quest_survive_10min_description" "在游戏中花费{target}分钟。" +"battle_pass_quest_survive_20min_name" "老兵" +"battle_pass_quest_survive_20min_description" "在游戏中花费{target}分钟。" +"battle_pass_quest_buy_black_shop_3_name" "买家" +"battle_pass_quest_buy_black_shop_3_description" "在黑市上购买{target}一件商品" +"battle_pass_quest_buy_black_shop_5_name" "修复客户端" +"battle_pass_quest_buy_black_shop_5_description" "在黑市上购买{target}物品" +"battle_pass_quest_complete_quest_1_name" "助手" +"battle_pass_quest_complete_quest_1_description" "在NPC执行{target}任务" +"battle_pass_quest_complete_quest_3_name" "村庄英雄" +"battle_pass_quest_complete_quest_3_description" "使用 NPC 执行 {target} 作业" +"battle_pass_quest_earn_gold_5000_name" "金矿工人" +"battle_pass_quest_earn_gold_5000_description" "赚取{target}黄金" +"battle_pass_quest_earn_gold_15000_name" "银行家" +"battle_pass_quest_earn_gold_15000_description" "赚取{target}黄金" +"battle_pass_quest_hero_level_10_name" "英雄的身高" +"battle_pass_quest_hero_level_10_description" "达到{target}英雄级别" +"battle_pass_quest_hero_level_25_name" "主人" +"battle_pass_quest_hero_level_25_description" "达到{target}英雄级别" +"battle_pass_quest_survive_waves_5_name" "坚定" +"battle_pass_quest_survive_waves_5_description" "幸存{target}波" +"battle_pass_quest_survive_waves_10_name" "坚不可摧" +"battle_pass_quest_survive_waves_10_description" "幸存{target}波" +"battle_pass_quest_buy_black_shop_10_name" "真正的客户" +"battle_pass_quest_buy_black_shop_10_description" "在黑市上购买{target}物品" +"battle_pass_quest_buy_black_shop_common_name" "初学者收藏家" +"battle_pass_quest_buy_black_shop_common_description" "购买{target}常见优质商品" +"battle_pass_quest_buy_black_shop_rare_name" "稀有鉴赏家" +"battle_pass_quest_buy_black_shop_rare_description" "购买{target}稀有品质商品" +"battle_pass_quest_buy_black_shop_epic_name" "史诗猎人" +"battle_pass_quest_buy_black_shop_epic_description" "购买{target}一件史诗级品质商品" +"battle_pass_quest_buy_black_shop_legendary_name" "市场传奇" +"battle_pass_quest_buy_black_shop_legendary_description" "购买{target}一件传奇品质的物品" +"battle_pass_quest_complete_quest_5_name" "伟大的助手" +"battle_pass_quest_complete_quest_5_description" "使用 NPC 执行 {target} 作业" +"battle_pass_quest_complete_cooking_3_name" "厨师" +"battle_pass_quest_complete_cooking_3_description" "在火上烹饪{target}菜肴" +"battle_pass_quest_cook_grilled_meat_name" "厨师" +"battle_pass_quest_cook_grilled_meat_description" "制作炸肉" +"battle_pass_quest_tip_teammate_3_name" "慷慨的" +"battle_pass_quest_tip_teammate_3_description" "向盟友发送{target}类型" +"battle_pass_quest_tip_teammate_5_name" "宽宏大量" +"battle_pass_quest_tip_teammate_5_description" "向盟友发送{target}类型" +"battle_pass_quest_no_death_15min_name" "牢不可破" +"battle_pass_quest_no_death_15min_description" "在没有死亡的情况下存活{target}分钟。" +"battle_pass_quest_heal_ally_2000_name" "治疗师" +"battle_pass_quest_heal_ally_2000_description" "治愈{target} HP 上的盟友" +"battle_pass_quest_deal_damage_50000_name" "毁灭者" +"battle_pass_quest_deal_damage_50000_description" "交易{target}伤害" +"battle_pass_quest_collect_item_meat_20_name" "屠夫" +"battle_pass_quest_collect_item_meat_20_description" "收集{target}肉。" +"battle_pass_quest_collect_item_milk_20_name" "挤奶女工" +"battle_pass_quest_collect_item_milk_20_description" "收集{target}牛奶" +"battle_pass_quest_collect_item_wolf_claw_15_name" "狼猎人" +"battle_pass_quest_collect_item_wolf_claw_15_description" "收集{target}狼爪" +"battle_pass_quest_collect_item_ent_heart_10_name" "Ent 猎人" +"battle_pass_quest_collect_item_ent_heart_10_description" "收集{target} ent 心" +"battle_pass_quest_earn_gold_30000_name" "大亨" +"battle_pass_quest_earn_gold_30000_description" "赚取{target}黄金" +"battle_pass_quest_survive_30min_name" "生存传奇" +"battle_pass_quest_survive_30min_description" "在游戏中花费{target}分钟。" +"battle_pass_quest_survive_waves_15_name" "坚不可摧" +"battle_pass_quest_survive_waves_15_description" "幸存{target}波" +"battle_pass_quest_heal_ally_5000_name" "伟大的治疗师" +"battle_pass_quest_heal_ally_5000_description" "治愈{target} HP 上的盟友" +"battle_pass_quest_deal_damage_100000_name" "世界破坏者" +"battle_pass_quest_deal_damage_100000_description" "造成{target}伤害" +"battle_pass_quest_play_different_hero_3_name" "种类" +"battle_pass_quest_play_different_hero_3_description" "使用不同的英雄玩{target}" +"battle_pass_quest_play_different_hero_5_name" "万能大师" +"battle_pass_quest_play_different_hero_5_description" "使用不同的英雄玩{target}" + +//管理菜单 UI +"admin_menu_description" "管理菜单" +"管理_菜单_部分_创建" "创建" +"admin_menu_change_hero" "更改英雄" +"admin_menu_add_ally" "+ALLIED" +"admin_menu_add_enemy" "+ENEMY" +"admin_menu_add_dummy" "+目标" + +"admin_menu_section_selected_units" "选定的生物" +"admin_menu_level_plus_1" "+1 UR。" +"admin_menu_level_plus_30" "+30 UR。" +"admin_menu_spells" "已关闭。" +"admin_menu_hp" "健康" +"admin_menu_invulnerable" "非溃疡。" +"admin_menu_add_scepter" "+SCEPTER" +"admin_menu_add_shard" "+SHARD" +"admin_menu_add_card" "+MAP" +"admin_menu_side" "SIDE" +"admin_menu_reset" "重置" +"admin_menu_delete" "删除" + +"admin_menu_section_give" "功能" +"admin_menu_give_items" "问题项目" +"admin_menu_give_stats" "问题统计数据" +"admin_menu_spawn_zones" "SPAVNA 区域" + +"admin_menu_game_controls" "游戏元素:" +"admin_menu_pause" "暂停" +"admin_menu_apply" "应用" +"admin_menu_script_reload" "脚本重新加载" +"admin_menu_spawn_zones_debug" "+SPAVNA 区域" + +"admin_menu_luck" "LUCK" +"admin_menu_phys_vamp" "物理。吸血鬼主义" +"admin_menu_magic_vamp" "MAG。 吸血鬼主义" +"admin_menu_gold" "金色" +"admin_menu_crystals" "水晶" +"admin_menu_give" "问题" + +"admin_menu_spawn_shape" "区域形状" +"admin_menu_spawn_shape_circle" "圆圈" +"admin_menu_spawn_shape_square" "正方形" +"admin_menu_spawn_radius" "半径(圆圈)" +"admin_menu_spawn_size" "宽度/高度(正方形)" +"admin_menu_spawn_units_label" "单位(npc_name:count;npc_name2:count2)" +"admin_menu_spawn_create_here" "在英雄下方创建一个区域" +"admin_menu_spawn_delete_here" "删除英雄下方的区域" +"admin_menu_spawn_behavior" "区域行为" +"管理_菜单_生成_行为_中性" "中性" +"admin_menu_spawn_behavior_friendly" "友好" +"admin_menu_spawn_behavior_aggressive" "激进的" + +"admin_menu_items_tab_default" "常规" +"admin_menu_items_tab_blackshop" "BLACKSHOP" +"admin_menu_search_item" "搜索对象..." + +"admin_spawn_units_help_title" "可用单位" + + +"hero_selection_time_remaining" "罚球前时间" +"hero_selection_gold_lost" "失去的黄金" + +//比赛详情(比赛详情) +"match_details_title" "匹配详细信息" +"match_details_info" "匹配信息" +"match_details_stats" "统计数据" +"match_details_items" "项目" +"match_details_permanent_effects" "永久效果" +"match_details_result_win" "胜利" +"match_details_result_loss" "失败" +"match_details_duration" "持续时间" +"match_details_date" "日期" +"match_details_hero" "英雄" +"match_details_level" "英雄级别" +"match_details_kills" "谋杀" +"match_details_deaths" "死亡" +"match_details_gold" "赚取的黄金" +"match_details_no_items" "未找到项目" +"match_details_no_modifiers" "缺少永久效果" +"match_details_upgrades_section" "阿哈利姆神杖与魔晶" +"match_details_aghanim_scepter" "阿哈利姆神杖" +"match_details_aghanim_shard" "阿哈利姆魔晶" + +"leaderboard_title" "排行榜" +"leaderboard_loading" "正在加载排行榜..." +"leaderboard_col_player" "玩家" +"leaderboard_col_rank" "段位" +"leaderboard_col_rating" "评分" +"leaderboard_col_level" "等级" +"leaderboard_col_games" "场次" +"leaderboard_error_prefix" "错误:" +"leaderboard_request_failed" "请求失败" +"leaderboard_no_data" "暂无排行榜数据" +"leaderboard_invalid_format" "排行榜数据格式无效" +"leaderboard_process_error" "处理数据时出错" +"leaderboard_your_position" "你的排名:" +"leaderboard_rank_novice" "未定级" +"leaderboard_tooltip" "打开排行榜" + +"store_donation_title" "支持与捐赠碎片" +"store_donation_desc" "可在 Boosty 或项目 Discord 服务器充值捐赠碎片并支持项目。" + +"admin_menu_search_hero" "搜索英雄..." +"admin_menu_spawn_without_zone" "无区域生成" +"admin_menu_night_wave_title" "夜晚 / 波次" +"admin_menu_force_night" "强制夜晚" +"admin_menu_force_morning" "强制白天" +"admin_menu_day_night_timer_title" "昼夜计时器" +"admin_menu_set_timer" "设置" +"admin_menu_endless_day" "无尽白昼" +"admin_menu_items_tab_util" "实用物品" +"admin_menu_items_tab_quest" "任务物品" +"admin_menu_items_tab_admin" "管理物品" +"admin_menu_time_scale_title" "时间流速" + +"battle_pass_reward_battle_shards" "战斗碎片" +"battle_pass_reward_zombie_shards" "僵尸碎片" +"battle_pass_reward_chest" "宝箱" +"battle_pass_reward_card" "卡牌" +"battle_pass_reward_lightning_effect" "闪电特效" +"battle_pass_reward_dust" "尘" +"battle_pass_reward_dust_title" "尘" +"battle_pass_reward_arcade_pack_standard" "标准卡包" +"battle_pass_time_abbr_hours" "时" +"battle_pass_time_abbr_minutes" "分" +"battle_pass_quest_progress_min_suffix" "分" + +"mmr_rank_immortal" "冠绝一世" +"mmr_rank_divine" "超凡入圣" +"mmr_rank_ancient" "万古流芳" +"mmr_rank_legend" "传奇" +"mmr_rank_archon" "统御" +"mmr_rank_crusader" "朝圣" +"mmr_rank_guardian" "卫士" +"mmr_rank_herald" "先锋" +"mmr_rank_uncalibrated" "未定级" + +"profile_exp_abbr" "经验" +"mini_profile_error_processing" "处理资料时出错" + +"cooking_panel_tab_ingredients" "食材" +"cooking_panel_tab_dishes" "菜品" + +"deck_builder_no_deck_selected" "无卡组" +"deck_builder_slot_empty" "空" +"deck_builder_card_in_deck_label" "在卡组中" +"deck_builder_quality_common" "普通" +"deck_builder_quality_rare" "稀有" +"deck_builder_quality_epic" "史诗" +"deck_builder_quality_legendary" "传说" +"deck_builder_quality_mythic" "神话" + +// -----------------Arsenal-------------------- +"arsenal_open_button" "军械库" +"arsenal_title" "军械库" +"arsenal_heroes_section" "英雄" +"arsenal_catalog_title" "可用装备" +"arsenal_filter_slot_label" "部位" +"arsenal_filter_rarity_label" "稀有度" +"arsenal_filter_stat_label" "属性" +"arsenal_filter_stats_toggle_show" "按属性筛选…" +"arsenal_filter_stats_toggle_hide" "收起属性筛选" +"arsenal_equipped_stats_title" "装备加成" +"arsenal_equipped_stats_empty" "没有已装备物品" +"arsenal_incoming_reduction_linear_sum" "装备条目合计" +"arsenal_batch_selected_prefix" "已选择" +"arsenal_batch_selected_empty" "已选择:0" +"arsenal_batch_alt_hint" "按住 Alt + 左键可多选" +"arsenal_batch_select_all" "全选" +"arsenal_batch_favorite" "加入收藏" +"arsenal_catalog_empty" "通过掉落" +"arsenal_select_hero_hint" "请在左侧选择一名英雄" +"arsenal_no_heroes" "没有可用英雄" +"arsenal_clear" "全部卸下" +"arsenal_filter_all" "全部" +"arsenal_slot_weapon" "武器" +"arsenal_slot_armor" "护甲" +"arsenal_slot_helmet" "头盔" +"arsenal_slot_boots" "靴子" +"arsenal_slot_necklace" "项链" +"arsenal_slot_ring" "戒指" +"arsenal_locked_in_game" "军械库仅可在自定义游戏设置界面编辑(比赛开始前)" +"arsenal_item_not_owned" "你没有这件物品" +"arsenal_rarity_common" "普通" +"arsenal_rarity_rare" "稀有" +"arsenal_rarity_epic" "史诗" +"arsenal_rarity_legendary" "传奇" +"arsenal_rarity_mythic" "神话" +"arsenal_tooltip_owned" "拥有:{d:arsenal_owned_qty}" +"arsenal_tooltip_owned_unique" "已拥有唯一实例" +"arsenal_tooltip_not_owned" "未拥有" +"arsenal_tooltip_serial_prefix" "序列号" +"arsenal_tooltip_stats_title" "随机属性" +"arsenal_tooltip_stats_unavailable" "属性不可用" +"arsenal_tooltip_unknown_fifth" "第5条属性:???(5级解锁)" +"arsenal_detail_pin" "固定" +"arsenal_detail_unpin" "取消固定" +"arsenal_detail_disassemble" "分解" +"arsenal_detail_upgrade" "升级" +"arsenal_detail_upgrade_max" "已满级" +"arsenal_detail_pinned_mark" "已固定" +"arsenal_disassemble_pinned" "已固定的物品无法分解" +"arsenal_disassemble_equipped" "已装备物品无法分解,请先卸下。" +"arsenal_tooltip_equipped_header" "已装备于英雄:" +"arsenal_upgrade_not_enough_shards" "僵尸碎片不足,无法升级" +"arsenal_stat_bonus_damage" "攻击力" +"arsenal_stat_base_damage_pct" "基础攻击伤害(%)" +"arsenal_stat_outgoing_damage_pct" "输出伤害" +"arsenal_stat_attack_speed" "攻击速度" +"arsenal_stat_attack_speed_pct" "攻击速度(%)" +"arsenal_stat_bonus_armor" "护甲" +"arsenal_stat_max_health" "生命值" +"arsenal_stat_max_mana" "法力值" +"arsenal_stat_health_regen" "生命恢复" +"arsenal_stat_mana_regen" "法力恢复" +"arsenal_stat_move_speed" "移动速度" +"arsenal_stat_move_speed_pct" "移动速度(%)" +"arsenal_stat_magic_resist" "魔法抗性" +"arsenal_stat_spell_amp" "法术增强" +"arsenal_stat_all_stats" "全属性" +"arsenal_stat_all_stats_pct" "全属性(%)" +"arsenal_stat_bonus_strength" "力量" +"arsenal_stat_bonus_agility" "敏捷" +"arsenal_stat_bonus_intellect" "智力" +"arsenal_stat_strength_pct" "力量(%)" +"arsenal_stat_agility_pct" "敏捷(%)" +"arsenal_stat_intellect_pct" "智力(%)" +"arsenal_stat_luck" "幸运" +"arsenal_stat_incoming_damage_reduction_pct" "承受伤害减免" +"arsenal_stat_crit_mult" "暴击倍率" +"arsenal_stat_battle_level" "战斗等级" +"DOTA_Tooltip_Ability_item_arsenal_weapon_iron_blade" "铁制短匕" +"DOTA_Tooltip_Ability_item_arsenal_weapon_iron_blade_Description" "由铁锻造的简单刀刃。" +"DOTA_Tooltip_Ability_item_arsenal_weapon_storm_edge" "风暴之刃" +"DOTA_Tooltip_Ability_item_arsenal_weapon_storm_edge_Description" "刀刃伴随雷电嗡鸣。" +"DOTA_Tooltip_Ability_item_arsenal_weapon_sunfang_saber" "烈阳獠牙军刀" +"DOTA_Tooltip_Ability_item_arsenal_weapon_sunfang_saber_Description" "炽热军刀挥动时会留下灼烧轨迹。" +"DOTA_Tooltip_Ability_item_arsenal_armor_chain_mail" "锁子甲" +"DOTA_Tooltip_Ability_item_arsenal_armor_chain_mail_Description" "轻型护甲。" +"DOTA_Tooltip_Ability_item_arsenal_armor_dragon_plate" "龙鳞胸甲" +"DOTA_Tooltip_Ability_item_arsenal_armor_dragon_plate_Description" "以远古巨龙鳞片锻造而成。" +"DOTA_Tooltip_Ability_item_arsenal_armor_vanguard_shell" "先锋壁壳" +"DOTA_Tooltip_Ability_item_arsenal_armor_vanguard_shell_Description" "为正面冲锋打造的厚重护壳。" +"DOTA_Tooltip_Ability_item_arsenal_helmet_leather_cap" "皮帽" +"DOTA_Tooltip_Ability_item_arsenal_helmet_leather_cap_Description" "简单的头部防护。" +"DOTA_Tooltip_Ability_item_arsenal_helmet_arcane_circlet" "奥术冠环" +"DOTA_Tooltip_Ability_item_arsenal_helmet_arcane_circlet_Description" "强化与魔力的连接。" +"DOTA_Tooltip_Ability_item_arsenal_helmet_warcrown_mask" "战冠面甲" +"DOTA_Tooltip_Ability_item_arsenal_helmet_warcrown_mask_Description" "统帅之面甲,可在战场上震慑敌人。" +"DOTA_Tooltip_Ability_item_arsenal_boots_swift_boots" "轻便之靴" +"DOTA_Tooltip_Ability_item_arsenal_boots_swift_boots_Description" "适合长途跋涉的舒适靴子。" +"DOTA_Tooltip_Ability_item_arsenal_boots_phase_treads" "相位之靴" +"DOTA_Tooltip_Ability_item_arsenal_boots_phase_treads_Description" "可在空间中跨步而行的靴子。" +"DOTA_Tooltip_Ability_item_arsenal_boots_hermes_greaves" "赫尔墨斯胫甲" +"DOTA_Tooltip_Ability_item_arsenal_boots_hermes_greaves_Description" "带翼胫甲可赋予闪电般的突进速度。" +"DOTA_Tooltip_Ability_item_arsenal_necklace_amber_pendant" "琥珀坠饰" +"DOTA_Tooltip_Ability_item_arsenal_necklace_amber_pendant_Description" "温暖的琥珀能吸收法术。" +"DOTA_Tooltip_Ability_item_arsenal_necklace_soul_locket" "灵魂之锁" +"DOTA_Tooltip_Ability_item_arsenal_necklace_soul_locket_Description" "锁中封印着陨落法师的灵魂。" +"DOTA_Tooltip_Ability_item_arsenal_necklace_prism_choker" "棱晶项圈" +"DOTA_Tooltip_Ability_item_arsenal_necklace_prism_choker_Description" "项圈中的晶体可折射并强化魔法。" +"DOTA_Tooltip_Ability_item_arsenal_ring_copper_band" "铜环" +"DOTA_Tooltip_Ability_item_arsenal_ring_copper_band_Description" "一枚普通的铜色指环。" +"DOTA_Tooltip_Ability_item_arsenal_ring_void_signet" "虚空印记" +"DOTA_Tooltip_Ability_item_arsenal_ring_void_signet_Description" "充满虚空之力的戒指。" +"DOTA_Tooltip_Ability_item_arsenal_ring_titan_loop" "泰坦之环" +"DOTA_Tooltip_Ability_item_arsenal_ring_titan_loop_Description" "刻有泰坦印记的远古戒环。" + +"card_selection_card_word" "卡牌" +"card_selection_no_description" "暂无描述" +"card_selection_error_hero_dead" "英雄死亡时无法选择卡牌" + +"store_item_hero_drow_ranger_name" "卓尔游侠" +"store_item_hero_drow_ranger_description" "传奇射手,精准无比,造成巨额物理伤害。" +"store_item_hero_lina_name" "莉娜" +"store_item_hero_lina_description" "火焰法师,法术毁灭性强,造成大量魔法伤害。" +"store_item_hero_vengefulspirit_name" "复仇之魂" +"store_item_hero_vengefulspirit_description" "复仇辅助:伤害与吸血光环、破甲恐怖波动、移形换位,以及标记敌人的灵魂盛宴。" +"store_item_hero_phantom_assassin_name" "幻影刺客" +"store_item_hero_phantom_assassin_description" "致命刺客,对单体造成大量物理伤害。" +"store_item_hero_sven_name" "斯温" +"store_item_hero_sven_description" "强悍战士,可抵挡大军并造成大量物理伤害。" +"store_item_skin_effect_fire_name" "烈焰缠身" +"store_item_skin_effect_ice_name" "寒冰缚体" +"store_item_skin_effect_heaven_name" "神圣特效" +"store_item_skin_effect_fall_2021_emblem_name" "阿哈利姆之光" +"store_item_skin_effect_blue_gems_name" "蓝宝石光效" +"store_item_skin_effect_sponsor_name" "赞助特效" +"store_item_skin_effect_lotus_name" "莲花特效" +"store_item_skin_effect_bp_red_name" "战斗通行证:赤红" +"store_item_skin_effect_bp_garden_name" "战斗通行证:花园" +"store_item_skin_effect_bp_golden_name" "战斗通行证:金色" +"store_item_skin_effect_bp_diretide_emblem_name" "战斗通行证:夜魇暗潮徽章" +"store_item_skin_effect_bp_diretide_emblem_v1_name" "战斗通行证:夜魇暗潮徽章 I" +"store_item_skin_effect_bp_diretide_emblem_v3_name" "战斗通行证:夜魇暗潮徽章 III" +"store_item_chat_wheel_sound_jump_name" "跳吧,笨蛋" + +"store_item_hero_bloodhunter_name" "血猎者" +"store_item_hero_bloodhunter_description" "定制型血魔风格核心:血怒、幻象与斩杀。" +"store_item_hero_sand_king_name" "沙王" +"store_item_hero_sand_king_description" "沙漠之王:范围控制、清线与生存能力。" +"store_item_hero_nagash_name" "纳加什" +"store_item_hero_nagash_description" "定制力量英雄:黑暗友军、魔像与军团增益。" +"store_item_hero_yuki_onna_name" "雪女" +"store_item_hero_yuki_onna_description" "定制冰霜之灵:雪暴、仪式与召唤。" +"store_item_hero_sargatanas_name" "萨格塔纳斯" +"store_item_hero_sargatanas_description" "定制恶魔:地狱步、分裂、召唤与变身。" +"store_item_hero_elder_dragon_smaug_name" "古龙史矛革" +"store_item_hero_elder_dragon_smaug_description" "定制全属性巨龙:火焰、恐惧光环与炽怒。" +"store_item_hero_mirana_name" "米拉娜" +"store_item_hero_mirana_description" "月之猎手:神箭、星落,与露娜联动;伤害随法力成长。" +"store_item_hero_spectre_name" "幽鬼" +"store_item_hero_spectre_description" "战场之影:幽魂之刃、折射、Spectral Echo 之影,以及 Haunt 与 Reality。" +"store_item_hero_silencer_name" "沉默术士" +"store_item_hero_silencer_description" "智力核心控场:沉默、纯粹伤害法刃与全图压制。" +"store_item_hero_keeper_of_the_light_name" "光之守卫" +"store_item_hero_keeper_of_the_light_description" "强力辅助法师:治疗队友、致盲敌人并掌控团战节奏。" + +"hud_button_skip" "Vote for skip" + +//任务 +"quest_kill_pigs_title" "杀死猪" +"quest_kill_pigs_description" "Firestars 请求你帮助他为他的部落获取食物。" + +"quest_firestar_gourmet_title" "美食" +"quest_firestar_gourmet_description" "准备 5 份炸肉,让 Firestar 享用一顿真正的盛宴。" +"quest_firestar_leader_horn_title" "领头人的号角" +"quest_firestar_leader_horn_description" "夜晚要求带来狼人领袖的角。说一个非常重要的仪式。" +"quest_firestar_fishing_title" "鱼债" +"quest_firestar_fishing_description" "Firestars 承认他从 Kunka 那里拿走了一根钓鱼竿。抓 10 条鱼并带给他,这样他的胡子就不会被击中。" + +"firestar_quest_1_message_1" "Fires: Mrrryau!喵喵喵!" +"firestar_quest_1_message_2" "<字体颜色='#FF4500'>Fires: RRRR!喵喵-喵喵 10 喵!喵喵先生!" +"firestar_quest_1_message_3" "Fires: Moore-moore!啊啊啊!妙呀呀呀!" + +"firestar_quest_1_message_complete_1" "Fires:喵喵喵喵喵!" + +"firestar_quest_gourmet_message_1" "Fires: Mrrr... 闻起来像炸肉!你能再给我做几份吗?" +"firestar_quest_gourmet_message_complete_1" "Fires: 喵!多么丰盛的盛宴啊!独自吃这样的肉是一种罪过—接受奖励,做饭。" +"firestar_quest_leader_horn_message_complete_1" "Fires: 我有领袖的号角!伟大的。现在有一件更复杂的事情..." +"firestar_quest_fishing_message_1" "Fires: 好吧,我承认……我从昆卡那里拿了一根钓鱼竿。抱住她,在他注意到之前给我拿 10 条鱼!" +"firestar_quest_fishing_message_complete_1" "Fires: 喵!有鱼,但昆卡不知道。你救了我!" + +"quest_kill_sheep_title" "杀羊" +"quest_kill_sheep_description" "帮助你的祖父拿牛奶并带给他。" +"quest_oldmen_cheesemaker_title" "奶酪制造商" +"quest_oldmen_cheesemaker_description" "准备好并给爷爷带一块奶酪头,这样他就可以做他最喜欢的饺子了。" + +"lina_quest_ent_heart_title" "ent 的心" +"lina_quest_ent_heart_description" "Lina 要求把 ent 的心带给她,作为回报,她会给予一些东西。" +"lina_quest_ent_heart_message_complete_1" "Lina: 干得好!这颗心里的火焰依然活着。拿走这个核心,我不需要它。" + + +"dota_tooltip_ability_item_pollen_keeper" "花粉收集器" +"dota_tooltip_ability_item_pollen_keeper_Description" "

被动:花粉收获

攻击能量凝块,有 %chance%%% 的机会获得 1 个花粉电荷(最多:20)。" + +"lina_quest_bottle_title" "给莉娜的花粉" +"lina_quest_bottle_description" "在瓶子里收集20个花粉并返回莉娜。" +"lina_quest_bottle_message_1" "Lina: 这是瓶子。用火花的花粉填充它,带回 20 个电荷。" +"lina_quest_bottle_message_complete_1" "<字体颜色='#FF6A00'>Lina: 太棒了!花粉被收集起来,火焰变得更强了。谢谢你的帮助。" + +"largo_quest_kill_frogs_title" "青蛙狩猎" +"largo_quest_kill_frogs_description" "Largo 要求清理沼泽:消灭迷你青蛙、青蛙和青蛙大师。" +"largo_quest_kill_frogs_message_1" "Largo: 沼泽里又充满了生物。杀死20只青蛙然后回到我身边。" +"largo_quest_kill_frogs_message_complete_1" "Largo: 干得好!沼泽变得更加安静。保管好你的奖励。" + +"largo_quest_spider_legs_title" "蜘蛛脚" +"largo_quest_spider_legs_description" "Largo 要求携带 20 条蜘蛛腿作为狩猎战利品。" +"largo_quest_spider_legs_message_1" "Largo: 清理沼泽后,我们需要蜘蛛的战利品。带上20条蜘蛛腿。" +"largo_quest_spider_legs_message_complete_1" "Largo: 伟大的奖杯。这些爪子在工艺品和诱饵中很有用。" + +"maiden_quest_eggs_title" "CMka 的鸡蛋" +"maiden_quest_eggs_description" "水晶少女要求带 20 个鸡蛋。" +"maiden_quest_eggs_message_1" "Webcup: 我需要鸡蛋来进行寒冷仪式。带上 2 万美元,我会慷慨地感谢你。" +"maiden_quest_eggs_message_complete_1" "<字体颜色='#9FD3FF'>CMCA: 太棒了!正如所需要的那样。谢谢,保留奖励。" + +"医生_任务_青蛙_爪子_标题" "青蛙腿" +"doctor_quest_frog_paws_description" "医生要求你带 20 条青蛙腿来盛药水。" +"doctor_quest_frog_paws_message_1" "医生: 我需要新鲜的青蛙腿。带20块给我的药水。" +"doctor_quest_frog_paws_message_complete_1" "Doctor: 优秀材料。这足以容纳一整批药水。拿着奖励。" + +"doctor_quest_poison_title" "有毒提取物" +"医生_任务_毒药_描述" "医生需要蜘蛛毒液。给他带20份来完成配方。" +"doctor_quest_poison_message_1" "Doctor: 太好了,现在我需要纯蜘蛛毒液。带上20份。" +"doctor_quest_poison_message_complete_1" "<字体颜色='#7E5B3A'>Doctor:完美。适当浓度的毒药。我会根据它完成血清。" + +"friend_quest_pizza_prep_title" "披萨准备" +"friend_quest_pizza_prep_description" "古德要求准备披萨。" +"friend_quest_pizza_prep_message_1" "<字体颜色='#FFD700'>Gourde: Seeker!拿着披萨的制作食谱,然后我会给你我的酷酱汁。将会有世界上最美味的东西!你会舔你的手指!" + +"friend_quest_pizza_prep_message_complete_1" "Gourde: 使用我的酷酱汁。这将是世界上最美味的东西!" +"friend_quest_pizza_prep_message_complete_2" "Gourde: 感谢您与我分享,请保留我的礼物!我确信他会帮助你。" + +"oldmen_quest_1_message_1" "Did: 哦,你在说什么,帮助老孩子,要有感情!" +"oldmen_quest_1_message_2" "Did: 牛奶就是我,没有牦牛就没有猪油的博 - niyak!拿一些特罗查,嗯?" +"oldmen_quest_1_message_3" "Did: 我会为此给你一份这样的礼物——妈妈,别担心!" + +"oldmen_quest_1_message_complete_1" "Did: 哦,该死的,沉没!哦,牛奶——那些需要它的人!" +"oldmen_quest_1_message_complete_2" "Did: 现在我可以和父亲一起吃饺子了,就像我祖母一样,你太胆小了!" +"oldmen_quest_1_message_complete_3" "Did: 山羊,谢谢你的善意!多么迷人的面团啊,你一定会健康的!" + +"oldmen_quest_cheesemaker_message_1" "Did: 哦,sinku,牛奶—那就好了,或者不用糖浆饺子的帮助!" +"oldmen_quest_cheesemaker_message_2" "Did: Zrobi meni trohi siru,哈?如果你想带一瓶头,我就为你大惊小怪!" +"oldmen_quest_cheesemaker_message_complete_1" "Did: Otse so sir!从现在开始会有饺子—舔手指!我告诉你,棉花。" + +"quest_give_claw_title" "海盗需要爪子吗?" +"quest_give_claw_description" "一个海盗要求你为他弄狼爪。他没有解释原因,但承诺慷慨解囊。" + +"kunkka_quest_give_claw_message_1" "Kunkevich: 嘿,水手!我需要狼的爪子,而且要快!" +"kunkka_quest_give_claw_message_2" "Kunkevich: 不要问为什么—只要带上它。把它视为海盗仪式的一部分!" +"kunkka_quest_give_claw_message_3" "Kunkevich: 每爪你获得的黄金比一桶朗姆酒还要多!" + +"kunkka_quest_give_claw_message_complete_1" "<字体颜色='#a8d8ff'>Kunkevich:哇!你真的抓住他们了!" +"kunkka_quest_give_claw_message_complete_2" "Kunkevich: 我要用这些爪子给你缝一顶狼帽——像真正的狼王那样戴着!" +"kunkka_quest_give_claw_message_complete_3" "Kunkevich: 持有奖励,这是你应得的!" +"kunkka_quest_give_claw_message_complete_4" "Kunkevich: 如果你遇到麻烦—请给我打电话,我现在很高兴!" + +"quest_found_rom_title" "寻找朗姆酒" +"quest_found_rom_description" "帮助海盗在瓶子里找到他的宝藏。" + +"kunkka_quest_find_rom_message_1" "Kunkevich: 该死的,我的朗姆酒在哪里?!你们这些老鼠有谁从货舱里偷了它吗?" +"kunkka_quest_find_rom_message_2" "Kunkevich: 如果五分钟后我手里没有瓶子,我会在一分钟后开始挂它!" +"kunkka_quest_find_rom_message_3" "Kunkevich: 看,混蛋,这朗姆酒比你们悲惨的生活更有价值!" + +"kunkka_quest_find_rom_message_complete_1" "<字体颜色='#a8d8ff'>Kunkiewicz:哈哈哈!看看这个天才!" +"kunkka_quest_find_rom_message_complete_2" "Kunkevich: 我的朋友,你刚刚在天堂给自己买了一处住处。" +"kunkka_quest_find_rom_message_complete_3" "Kunkevich: 好吧,或者至少从这艘该死的船上地狱中解脱出来!" +"kunkka_quest_find_rom_message_complete_4" "Kunkevich: 拿走我的备用刀片,狗!现在让他侍奉不怕他沉重的人吧!" + +"quest_kunkka_skeletons_title" "墓地清洁工第一部分" +"quest_kunkka_skeletons_description" "海盗已经被骷髅引诱到墓地,他要求你除掉他们的几个物种。" + +"zone_skeletons_clean" "墓地清理" + +"quest_kunkka_clean_harbor_title" "清洁港口" +"quest_kunkka_clean_harbor_description" "在死水中放置一面旗帜,清除港口的骨垢。" + +"quest_kunkka_kill_lycan_title" "黑牙狩猎" +"quest_kunkka_kill_lycan_description" "追捕并杀死老板。他的嚎叫已经困扰这片土地太久了。" +"quest_kunkka_kill_satyr_demon_title" "萨特恶魔狩猎" +"quest_kunkka_kill_satyr_demon_description" "找到并击杀萨特恶魔,然后回到昆凯维奇领取奖励。" + +"kunkka_quest_2_message_1" "Kunkevich: 这样海怪就会把它们全部吃掉!该死的骷髅!它们在这里被培育得像老鼠在货舱里一样,所以它们是空的!" +"kunkka_quest_2_message_2" "Kunkiewicz: 嘿,勇敢的海狼!你不想得到一些黄金吗?!把老海盗从这骨肉祸害中拯救出来!" +"kunkka_quest_2_message_3" "Kunkevich: 把至少十几个行尸走肉砸成碎片,我以海魔的名义发誓,我会让你变得富有!" + +"kunkka_quest_kill_lycan_message_1" "Kunkevich: 你听到 Lycan 嚎叫了吗?这头野兽毁了我整个海景。你会把他放下—你会像真正的猎人一样得到奖励。" +"kunkka_quest_kill_lycan_message_complete_1" "Kunkevich: 多么大的战利品啊!这样的动物不会从虚弱的手上掉下来。保留这笔费用,你真的值得。" +"kunkka_quest_kill_satyr_demon_message_1" "Kunkevich: 附近有一只萨特恶魔在游荡,海岸都被它搅得不得安宁。找到它并送它下海。" +"kunkka_quest_kill_satyr_demon_message_complete_1" "Kunkevich: 干得漂亮!萨特恶魔已经倒下,大海终于能平静呼吸。拿上你的奖励。" + +"kunkka_quest_2_message_complete_1" "<字体颜色='#a8d8ff'>Kunkevich:哈哈哈!太棒了,水手!是的,你是一个真正的不死杀手!" +"kunkka_quest_2_message_complete_2" "Kunkevich: 荣耀归于海神!你终于可以深呼吸,不再有这些骨垢了!" + +"kunkka_quest_clean_harbor_message_1" "Kunkevich: 我的港口里有死肉的味道。这是给你的旗帜—呆在他们的巢穴之间并保持重点。" +"kunkka_quest_clean_harbor_message_complete_1" "Kunkevich: 这是工作,水手!水又干净了,你可以用朗姆酒冲洗一下。抓住你的头奖。" +"kunkka_quest_clean_harbor_message_fail_1" "Kunkevich: 旗帜被拆毁了,港口再次布满骨头。让我们采取不同的路线并重试。" +"quest_kunkka_find_anchor_title" "十字下的宝藏" +"quest_kunkka_find_anchor_description" "昆克维奇说他把宝藏埋在那该死的女巫附近。找到宝藏——赏金就归你了。" +"kunkka_quest_find_anchor_message_1" "昆克维奇: 拿着铲子!在地图上找十字标记——站上去再挖。勤快的话金币自己会冒出来。" +"kunkka_quest_find_anchor_message_complete_1" "昆克维奇: 哈!听见金币声了——没白挖。拿着赏金,海狼!" +"kunkka_quest_find_anchor_message_fail_1" "昆克维奇: 铲子在沙里,宝藏却没有……改天再来。" +"dota_hud_error_shovel_not_on_cross" "请靠近标记的宝藏点。" +"dota_hud_error_shovel_broken" "铲子已损坏——宝藏已全部挖完。" +"DOTA_Tooltip_ability_item_shovel" "可靠铲子" +"DOTA_Tooltip_ability_item_shovel_Description" "

主动:挖掘

站在十字上在脚下挖掘,可获得 %gold_min% 至 %gold_max% 金币。

被动:结实铲柄

增加生命值。" +"DOTA_Tooltip_ability_item_shovel_Lore" "昆克维奇在磁力宝藏狩猎中找到的魔法铲子。" +"DOTA_Tooltip_ability_item_shovel_bonus_health" "+$health" +"DOTA_Tooltip_ability_item_shameful_pipe" "耻辱烟斗" +"DOTA_Tooltip_ability_item_shameful_pipe_Description" "为远程英雄提供 %bonus_attack_range% 攻击距离。持有者免疫 creep 骨甲的伤害与反射。" +"DOTA_Tooltip_ability_item_shameful_pipe_Lore" "海盗宝藏里挖出的烟斗——近战无用,却能让箭飞得更远, creep 骨甲也伤不到持有者。" +"DOTA_Tooltip_ability_item_shameful_pipe_bonus_attack_range" "+$attack_range" +"DOTA_Tooltip_modifier_item_shameful_pipe" "耻辱烟斗" +"DOTA_Tooltip_modifier_item_shameful_pipe_Description" "增加攻击距离,免疫 creep 骨甲。" +"npc_kunkka_quest_anchor_marker" "锚标记" + +"clean_harbor_no_hero_near_flag" "旗帜上没有人 — 快点回到圆圈。" + +"DOTA_Tooltip_ability_item_clean_harbor" "港口清理标志" +"DOTA_Tooltip_ability_item_clean_harbor_Description" "放置在两个骷髅巢穴之间的一次性海盗旗。如果旗帜在英雄的保护下停留 60 秒,墓地中的骷髅就会停止生成,所有已经出现的死者都会散落在风中。" + +"DOTA_Tooltip_ability_item_fishing_rod" "钓鱼竿" +"DOTA_Tooltip_ability_item_fishing_rod_Description" "

活动:投掷钩子

将钩子沿指定方向投掷,最高可达 %hook_distance%。 当被敌人击中时,造成 %Damage% 物理伤害加上一部分攻击,在 %vision_duration% 秒上给出半径为 %vision_radius% 的概览,施加 %slow_movespeed%%% 的减速,并每 %tick% 秒对 %damage_per_tick% 进行周期性伤害。当钩子飞回来时,英雄却站着不动。当钩子向后滚动时,目标就会被你吸引。

如果捕获了一条鱼(npc_fish),则返回钩子后它会消失,奖励会飞向英雄。" +"DOTA_Tooltip_ability_item_fishing_rod_Lore" "用哪个和拖钓在岸上—只要它啄食。" +"DOTA_Tooltip_ability_item_fish" "鱼" +"DOTA_Tooltip_ability_item_fish_Description" "

Active:吃鱼

以 %heal% 健康对待目标并给予 %hunger_bonus% 饥饿赌注。" +"DOTA_Tooltip_ability_item_fish_Lore" "新鲜捕获。在巨浪过后特别有用。" +"dota_tooltip_ability_item_fish_heal" "+$health" +"dota_tooltip_ability_item_fish_hunger_bonus" "+$all" +"DOTA_Tooltip_modifier_bad_cast_debuff" "坏种姓" +"DOTA_Tooltip_modifier_bad_cast_debuff_Description" "你的定向能力朝着相反的方向飞行。" +"DOTA_Tooltip_modifier_blind_debuff" "炫目" +"DOTA_Tooltip_modifier_blind_debuff_Description" "屏幕受到干扰,无法导航。" +"DOTA_Tooltip_modifier_stats_multiplier" "属性倍率" +"DOTA_Tooltip_modifier_stats_multiplier_Description" "来自卡牌的主属性综合加成。当前倍率:%dMODIFIER_PROPERTY_TOOLTIP%%%" + +"quest_denny_quest_kill_sheeps_title" "丹尼和他的教义" + "quest_denny_quest_kill_sheeps_description" "丹尼教你,甚至付钱训练你,要求你杀羊来进一步发展你的技能。" + +"denny_quest_kill_sheeps_message_1" "<字体颜色='#FF0000'>Denny:嘿英雄!经过这些训练,我变得更加强大了!" +"denny_quest_kill_sheeps_message_2" "Denny: 你知道你需要更多的练习!你能杀死 20 只羊吗?" +"denny_quest_kill_sheeps_message_3" "Denny: 我想向所有人证明你不是一个弱者!我们一起就能处理好这件事!" + +"denny_quest_kill_sheeps_message_complete_1" "<字体颜色='#FF0000'>Denny:哇!我们做到了!我感觉我们正在成为真正的战士!" +"denny_quest_kill_sheeps_message_complete_2" "Denny: 现在您已经准备好迎接更严峻的挑战了!" + +"quest_denny_quest_kill_thieves_title" "路上的流氓" +"quest_denny_quest_kill_thieves_description" "丹尼相信,只有消灭所有强盗,道路才会安全。" + +"denny_quest_kill_thieves_message_1" "Denny: 路上有强盗在作案!杀死领导者和他的所有追随者!" +"denny_quest_kill_thieves_message_complete_1" "<字体颜色='#FF0000'>Denny: 太棒了!强盗被打败了—现在你可以平静地沿着路走!" +"denny_quest_kill_thieves_message_complete_2" "Denny: 我感谢你的勇气。你领取下一个奖励!" + + +"quest_denny_need_a_pet_title" "丹尼和他的小朋友" +"quest_denny_need_a_pet_description" "丹尼让你为他找一个特别的朋友。他说他想要一个快速、敏捷,或者强大、勇敢的人…… 奇怪的是,在僵尸末日期间,他如此执着于这一点。" +"denny_pet" "丹尼宠物" +"npc_pig_event" "涡轮猪" +"npc_wolf_event" "狼狗" +"npc_squirrel_event" "松鼠" + + +"dota_tooltip_modifier_pet_buff_npc_pig_event" "猪疯狂" +"dota_tooltip_modifier_pet_buff_npc_pig_event_Description" "所有传入伤害减少 10%%" + +"dota_tooltip_modifier_pet_buff_npc_wolf_event" "狼的力量" +"dota_tooltip_modifier_pet_buff_npc_wolf_event_Description" "所有输出伤害都会增加 15%,并且攻击造成 15% 伤害的可能性也达到 160%。" + +"dota_tooltip_modifier_pet_buff_npc_squirrel_event" "松鼠智慧" +"dota_tooltip_modifier_pet_buff_npc_squirrel_event_Description" "获得的经验增加了 35%" + +"dota_tooltip_ability_item_pet" "如何驯服任何人?" +"dota_tooltip_ability_item_pet_Description" "

Active:使用

如果使用得当,您可以获得一些您还不知道的结果,但书可能会坏掉。" +"dota_tooltip_ability_item_pet_Lore" "传奇动物训练师 Eldrik Zveregov 撰写的一本古老巨著。他们说他可以驯服任何生物--从小松鼠到凶猛的狼。他的秘密被写在了这本书里,但多年来,书页变得脆弱,一些驯化的食谱也丢失了。现在,一本书可以帮助你找到一个真正的朋友,但只能帮助那些能够正确使用它的人。" + +"denny_quest_find_pet_message_1" "<字体颜色='#FF0000'>Denny:嘿英雄!我有一个要求... 你能帮我找到一个特别的朋友吗?" +"denny_quest_find_pet_message_2" "Denny: 我梦想有一个人永远和我在一起。也许速度快、灵巧,就像那些跳到树上的松鼠……或者强壮勇敢,就像那些对着月亮嚎叫的狼……" +"denny_quest_find_pet_message_3" "Denny: 或者甚至是某个知道如何挖掘地面并找到宝藏的特殊人!你知道,我听说这样的生物非常聪明和忠诚!" + +"denny_quest_find_pet_message_complete_1" "<字体颜色='#FF0000'>Denny:哇哦!你发现...这是... 这是一只真正的宠物!我梦到这个已经很久了!" +"denny_quest_find_pet_message_complete_2" "Denny: 你知道,我一直想要一个朋友……一个无论天气如何都会和我在一起的朋友。也许像风一样快速敏捷,或者像真正的战士一样强大勇敢……" +"denny_quest_find_pet_message_complete_3" "Denny: 非常感谢!现在我有一个真正的同伴了!我会每天和他一起训练,成为最强的守卫!" + + +"quest_denny_training_title" "守护路径第 1 部分" +"quest_denny_training_description" "丹尼真的想成为一名警卫,并请你帮助他,为此他会去和羊战斗,并请你帮助他。" + +"Denny_quest_1_message_1" "Denny: 嗯...我...我想成为真正的警卫!但学校里的每个人都冒犯我,因为我很软弱……" +"Denny_quest_1_message_2" "Denny: 请教我如何战斗!我甚至不能照顾羊…… 我... 我会尽我所能付钱!" +"Denny_quest_1_message_3" "Denny: 你真的能帮忙吗?好极了!只是...别打得太重,好吗?" + +"Denny_quest_1_rofl_message_1" "<字体颜色='#FF0000'>Denny:嗨呀!你走吧!" +"Denny_quest_1_rofl_message_2" "Denny: 得到它!我很坚强!" +"Denny_quest_1_rofl_message_3" "<字体颜色='#FF0000'>Denny:哈!我就像一个真正的战士!" +"Denny_quest_1_rofl_message_4" "<字体颜色='#FF0000'>Denny:砰!砰!砰!" +"Denny_quest_1_rofl_message_5" "Denny: 我无敌了!好极了!" + + +"Denny_quest_1_message_complete_1" "Denny: 谢谢你,英雄!多亏了你,我变得更加强大了。来,把你的剑拿回来——它在我的训练中帮了我大忙。" +"Denny_quest_1_message_complete_2" "Denny: 但你知道…… 我还有一个要求。来吧,当你能帮助我变得更强大时!祝你好运,英雄!" +"Denny_quest_1_message_complete_3" "Denny: 我一定会成为一名真正的警卫,就像我梦想的那样!这一切都归功于你!" + +"Denny_quest_1_message_fail_1" "Denny: *抽泣* 我……我失败了…… 如果我有一把像昆基一样的剑,我就太虚弱了。" +"Denny_quest_1_message_fail_2" "Denny: 我想我真的不应该梦想成为一名警卫……" +"Denny_quest_1_message_fail_3" "Denny: 感谢您尝试帮助我…… 但显然这不是我的..." + + +"quest_collect_items_title" "收集物品" + "quest_collect_items_description" "收集三个铁分支进行放大" + +//默认功能 + +"text_no_alphatest_detected" "未通过 alpha 测试检测到 Fag,然后玩 go lessons do schmuck。" + +"music_on" "打开音乐" +"music_off" "关掉音乐。" +"dota_game_end_victory_title_radiant" "村庄得救了!" + "dota_game_end_victory_title_dire" "哦操,我倾斜了……(((" + +"black_shop_refreshed" "商店已更新!" +"black_shop_title" "黑市" +"免费" "免费" +"稀有度_常见" "常规" +"稀有度_稀有" "稀有" +"rarity_epic" "史诗" +"rarity_legendary" "传奇" +"稀有_天堂" "神圣" +"rarity_cursed" "该死的" +"time_now" "当前游戏的时间是:" +"秒" "秒" +"持续时间" "持续时间" +"day_time" "直到晚上" +"night_time" "直到白天" +"night_begin" "夜来了。" +"be_ready" "准备防御!" +"night_begin_1" "团队,快点!夜色已近!" +"NIGHT_BEGIN_2" "你好,你在哪儿!???!?!?!?夜已经到来!" + +"boss_spawned" "老板本人,你越来越接近了!" +"dota_tooltip_ability_ghost_evasive" "幻影逃避" +"dota_tooltip_ability_ghost_evasive_Description" "被动地允许您飞行和穿过单位。" +"dota_tooltip_ability_ghost_evasive_evasive" "$evasion" +"dota_tooltip_ability_wave_phasing_march" "相位行进" +"dota_tooltip_ability_wave_phasing_march_Description" "被动:可穿过其他单位,并获得 %bonus_movement_speed% 额外移动速度。" +"dota_tooltip_ability_wave_phasing_march_bonus_movement_speed" "+$move_speed" + +"1minfornight" "距离夜幕降临还有1分钟!" +"30秒通宵" "还有30秒就到夜幕降临了!" +"dota_tooltip_ability_item_test" "测试项目" +"nearby_item_distance" "单位" + +"no_quests_available" "目前没有可用的 kvets。稍后再检查。" +"任务" "求职板" +"quest_accept_button" "接受" +"quest_rewards_title" "奖励:" +"quest_reward_gold" "黄金" +"quest_reward_experience" "经验" +"quest_reward_crystals" "水晶" +"quest_reward_team_split" "全队平分" +"quest_state_available" "可用" +"quest_state_in_progress" "正在进行中" +"quest_state_completed" "已完成" +"quest_state_locked" "已锁定" +"quest_state_failed" "失败" + + + + + +//黑市 +"black_shop_refresh_description" "想刷新商店内容吗?" +"black_shop_refresh_button" "更新" +"black_shop_buy_card_description" "我这里也卖卡牌,但每次购买后价格都会上涨。" +"black_shop_buy_card_button" "购买卡牌" +"black_shop_not_enough_crystals" "水晶不够" +"black_shop_not_enough_gold" "Denyag 不够" +"black_shop_teleport_section_description" "选择一个目的地进行传送。" +"black_shop_teleport_menu_open" "把我传送到…" +"black_shop_teleport_title" "你要去哪里?" +"black_shop_teleport_cost_label" "传送费用:" +"black_shop_teleport_to_grove" "传送我到树林" +"black_shop_teleport_to_two_sisters" "传送我到双姐妹" +"black_shop_teleport_to_village_waterfall" "传送我到瀑布后的村庄" +"black_shop_teleport_to_ancient_ridge" "传送我到远古山脊" +"black_shop_back_button" "返回" +"black_shop_teleport_dead" "死亡时无法传送。" +"black_shop_card_purchased" "卡牌已购买" +"black_shop_exchange_section_description" "货币兑换:100 金币 = 1 水晶。" +"black_shop_exchange_open_button" "打开货币兑换" +"black_shop_exchange_title" "货币兑换" +"black_shop_exchange_hint" "选择方向,设置数量,然后确认兑换。" +"black_shop_exchange_summary_default" "1 水晶兑换 100 金币" +"black_shop_exchange_max_default" "上限: 1" +"black_shop_exchange_apply_button" "兑换" +"black_shop_exchange_not_enough" "资源不足" +"black_shop_exchange_max_prefix" "上限:" +"black_shop_exchange_summary_spend" "花费" +"black_shop_exchange_summary_get" "获得" +"black_shop_exchange_result_spent" "兑换:已花费" +"black_shop_exchange_result_received" "已获得" +"black_shop_gold_unit" "金币" +"black_shop_crystal_unit_short" "水晶" + +//默认_游戏玩法_修改器 + +"dota_tooltip_modifier_glyph_custom_passive" "祝福准备好了" +"dota_tooltip_modifier_glyph_custom_passive_Description" "随时防止死亡。" +"dota_tooltip_modifier_glyph_custom" "祝福" +"dota_tooltip_modifier_glyph_custom_Description" "英雄拥有来自天堂的保护。" +"dota_tooltip_modifier_glyph_custom_passive_cd" "祝福充电" +"dota_tooltip_modifier_glyph_custom_passive_cd_Description" "祝福去充电了。" + +"dota_tooltip_modifier_general_fired" "烧毁" +"dota_tooltip_modifier_general_fired_Description" "该生物着火了。" + +"dota_tooltip_modifier_general_froze" "冷却" +"dota_tooltip_modifier_general_froze_Description" "这个生物很冷。" + +"dota_tooltip_modifier_general_hunger" "饱和度" +"dota_tooltip_modifier_general_hunger_Description" "英雄获得了所有基本属性的奖励。" + +"dota_tooltip_modifier_item_pizza" "披萨" +"dota_tooltip_modifier_item_pizza_Description" "通过堆栈的数量增加力量、敏捷性和智力。" + + +"dota_tooltip_modifier_swamp_slow" "沼泽命运" +"dota_tooltip_modifier_swamp_slow_Description" "英雄陷入泥潭,因此被 %MODIFIER_PROPERTY_MOVESPEED_BONUS_CONSTANT% 的单位减慢速度并受到伤害。" + +//扎普雷特 + +"dota_hud_error_cheese_bad_target" "不是正确的目标。" +"wait_some_time_before_tree_growth" "我想我应该晚点再回来。" +"dota_hud_error_full_inventory" "目标有完整的库存" +"dota_hud_error_ability_not_ready" "英雄已经吃饱了,等他饿了再说。" +"dota_hud_error_havent_charges" "该物品没有费用。" +"dota_hud_error_rubick_spellsteal_self" "你不能从自己那里窃取能力。" +"dota_hud_error_rubick_spellsteal_no_ability" "目标没有正确的偷窃能力。" +"dota_hud_error_rubick_spellsteal_unstealable" "此能力不能被窃取。" +"dota_hud_error_not_in_zone" "英雄不在墓地的中心。" +"dota_hud_error_font_of_mercy_cd" "仁慈圣泉仍在充能。" +"dota_hud_error_font_of_mercy_no_crisis" "范围内没有生命值低于阈值的友方英雄。" + +//事件 +"event_gold_rush" "淘金热" +"event_midas_hands" "米达斯疯狂" +"event_gold_rush_description" "游戏动物群:

• 在英雄旁边,金袋数量达到 800 个单位

• 拿起它们,您将获得 100 到 1000 个黄金
" +"event_midas_hands_description" "Midas Blessing:

• 杀死敌人的概率为 15%,一袋金子会掉出来" + +//npc_dota_creature +"npc_pig" "猪" +"npc_sheep" "羊" +"npc_wolf" "狼" +"npc_boss_lycan" "黑牙" +"npc_ent" "小型古代 Ent" +"npc_skeleton_zombie_undead" "骷髅" +"npc_dead_skeleton_archer_undead" "骷髅弓箭手" +"npc_skeleton_zombie_half_undead" "骷髅清道夫" +"npc_dead_skeleton_undead" "骷髅剑客" +"npc_witch" "女巫" +"npc_chicken" "鸡" +"npc_thief_leader" "盗贼头目" +"npc_thief_archer" "弓箭手小偷" +"npc_thief_backer" "小偷" +"npc_black_dragon" "黑龙" +"npc_red_dragon" "红龙" +"npc_blue_dragon" "蓝龙" +"npc_blue_dragon_small" "小蓝龙" +"npc_red_dragon_small" "小红龙" +"npc_lycosidae_stalker" "小食母羊" +"npc_venomancer_brute" "大蛇" +"npc_ravenous_woodfang" "该死的咬伤" + +"npc_campfire" "篝火" +"npc_wisps" "能量凝块" + +"npc_frop_tadpole" "迷你青蛙" +"npc_small_frog_froglet" "青蛙" +"npc_mini_frog" "蝌蚪" +"npc_frogman_magi" "魔法青蛙" + +"npc_sakura_tree" "樱花树" +"npc_mound" "Hoop" + + +"npc_dummy_test" "sosal 用于什么?" + +"npc_attack_box" "盒子" + +//重命名英雄 +"npc_dota_hero_sargatanas" "萨尔加塔纳斯" +"npc_dota_hero_elder_dragon_smaug" "老龙史矛革" +"npc_dota_hero_yuki_onna" "Yuki-onna" +"npc_dota_hero_bloodhunter" "血猎者" +"npc_dota_hero_sand_king" "沙王" +"npc_dota_hero_nagash" "纳加什" +"npc_dota_hero_juggernaut" "主宰" + + +"npc_dota_melee_nagash_summon" "马尔杜克" + "npc_dota_ranged_nagash_summon" "阿波罗" + "npc_dota_mage_nagash_summon" "奥丁" + "npc_dota_shield_nagash_summon" "阿努比斯" +"npc_dota_nagash_soul_eater" "食尸鬼" + +//npc +"npc_market_blackshop" "非法店主" +"npc_homer" "村长" +"homer_defend_toast_kicker" "基地防守" +"homer_defend_toast_title" "{hom_name}正遭到攻击" +"homer_defend_toast_hint" "请返回出生点,击退进攻的敌人。" +"homer_defend_toast_fallback" "{hom_name}正遭到攻击!请守住基地。" +"homer_defend_toast_name_fallback" "村长" +"npc_quest_giver_kunkka" "Kunkka" +"npc_quest_giver_denny" "丹尼" +"npc_quest_giver_oldmen" "老人" +"npc_quest_giver_firestar" "焰星" +"npc_quest_giver_doctor" "草药师阿扎兹" +"npc_quest_giver_friend" "古德" +"npc_quest_giver_largo" "Largo Writer" +"npc_quest_giver_maiden" "友好的打" +"npc_quest_giver_lina" "有毒的莉娜" + + + + +"npc_kot_roflik_0" "MerseyK" +"npc_kot_roflik_1" "海狸" +"npc_kot_roflik_2" "KulStorybobik" +"npc_dota_rofl_kaban_pumba" "伦巴" +"npc_dota_golden_fish" "金裙子" + + +//召唤 + +"npc_dota_fire_summon" "火元素" + + + +//npc_wave_creeps +"npc_wave_zombie" "宗布-霍东" +"npc_wave_half_zombie" "Zonbu the carrion" +"npc_wave_toxin_zombie" "Zonbu-toxic" +"npc_wave_bearst_zombie" "Zonbu-Beast" +"npc_wave_ghost_ranged" "幽灵射手" +"npc_wave_ghoul" "Ghoul" +"npc_wave_skeleton_warrior" "战士骷髅" +"npc_wave_skeleton_assassin" "刺客骷髅" +"npc_wave_boss_death_prophet" "死亡之主先知" +"npc_wave_boss_lifestealer" "老板食命者" +"npc_wave_boss_skeleton" "老板骨架" +"npc_wave_boss_zombie" "老板僵尸" + +"npc_wave_dead_enchantress" "萎缩" + + +//蠕变_能力 +"dota_tooltip_ability_sheep_coil" "魔角" +"dota_tooltip_ability_sheep_coil_description" "释放一道闪电,造成伤害并减慢敌人在其恶意者 %radius% 单位半径内的速度。" +"dota_tooltip_ability_sheep_coil_damage" "损坏:" +"dota_tooltip_ability_sheep_coil_slow_duration" "持续时间:" +"dota_tooltip_ability_sheep_coil_movement_slow" "%SLOW:" +"dota_tooltip_ability_sheep_coil_lore" "长着角的混蛋,用他神奇的角把阴户给了他的恶意者。" +"dota_tooltip_modifier_sheep_coil_slow" "缓慢的魔法" +"dota_tooltip_modifier_sheep_coil_slow_Description" "你浑身都是魔法液体,这就是为什么你的速度会被 %dMODIFIER_PROPERTY_MOVESPEED_BONUS_PERCENTAGE%%%"减慢 + +"dota_tooltip_ability_frogmen_acid_jump" "酸跳跃" +"dota_tooltip_ability_frogmen_acid_jump_description" "青蛙跳到选定的点并摔倒在地,对半径内的敌人造成物理伤害并击晕他们。" +"dota_tooltip_ability_frogmen_acid_jump_radius" "半径:" +"dota_tooltip_ability_frogmen_acid_jump_stun_duration" "眩晕持续时间:" +"dota_tooltip_ability_frogmen_acid_jump_land_damage" "着陆损坏:" +"dota_tooltip_ability_frogmen_acid_jump_lore" "在变异沼泽中饲养的蟾蜍已经学会了用震耳欲聋的打击向敌人释放一波又一波的刺激性粘液。" + +"dota_tooltip_ability_pig_charge" "猪进" +"dota_tooltip_ability_pig_charge_description" "猪会冲向目标,如果它在短时间内撞击并推开震耳欲聋的物体,就会造成伤害。" +"dota_tooltip_ability_pig_charge_knockback_damage" "损坏:" +"dota_tooltip_ability_pig_charge_stun_duration" "STUN:" +"dota_tooltip_ability_pig_charge_lore" "一头不知道该怎么办的猪决定尝试一切能做的事情。" + +"dota_tooltip_ability_thief_arrow" "箭头" +"dota_tooltip_ability_thief_arrow_description" "小偷射出一支直线飞行的箭,对其路径上的第一个敌人造成物理伤害。" +"dota_tooltip_ability_thief_arrow_arrow_speed" "箭头速度:" +"dota_tooltip_ability_thief_arrow_arrow_width" "飞行宽度:" +"dota_tooltip_ability_thief_arrow_arrow_range" "范围:" +"dota_tooltip_ability_thief_arrow_lore" "那些在阴影中太懒的人很少有时间注意到打击来自哪里。" + +"dota_tooltip_ability_thief_charge" "暗影混蛋" +"dota_tooltip_ability_thief_charge_description" "小偷选择一个目标并以极快的速度冲向它,到达后击晕敌人并将其扔进一小块区域。" +"dota_tooltip_ability_thief_charge_movement_speed" "移动速度:" +"dota_tooltip_ability_thief_charge_stun_duration" "眩晕持续时间:" +"dota_tooltip_ability_thief_charge_bash_radius" "影响半径:" +"dota_tooltip_ability_thief_charge_lore" "对于真正的盗贼来说,猎物和刀刃之间的距离只是一个决定的问题。" + +"dota_tooltip_ability_agro_leader" "尖叫" +"dota_tooltip_ability_agro_leader_description" "发出愤怒的呼叫,导致半径%内的敌人在%持续时间%秒内攻击它。" +"dota_tooltip_ability_agro_leader_radius" "半径:" +"dota_tooltip_ability_agro_leader_duration" "持续时间:" +"dota_tooltip_ability_agro_leader_lore" "谁敢挑战盗贼领袖,就必然会成为关注的焦点。" + +//波浪_蠕变_能力 +"dota_tooltip_ability_zombie_virus" "僵尸病毒" +"dota_tooltip_ability_zombie_virus_description" "攻击有 %chance%%% 几率对敌人施加一层独立的毒素(同一目标最多 9 层)。每层每秒造成 %damage% 点伤害,降低 %slow_movespeed%%% 移动速度和 %armor% 点护甲。单层持续 %duration% 秒。施加该层的僵尸死亡时,该层消失。" +"dota_tooltip_ability_zombie_virus_damage" "每秒伤害:" +"dota_tooltip_ability_zombie_virus_slow_movespeed" "减速:" +"dota_tooltip_ability_zombie_virus_armor" "护甲降低:" +"dota_tooltip_ability_zombie_virus_duration" "单层持续时间:" +"dota_tooltip_ability_zombie_virus_chance" "触发几率:" +"dota_tooltip_ability_zombie_virus_lore" "死者体内发生变异的古老病毒使他们变成了行走的感染携带者。" + +"dota_tooltip_modifier_zombie_virus_debuff" "僵尸病毒" +"dota_tooltip_modifier_zombie_virus_debuff_description" "毒素层:持续伤害、减速与护甲降低;多层可叠加。移动速度 %dMODIFIER_PROPERTY_MOVESPEED_BONUS_PERCENTAGE%%%。" + +"dota_tooltip_ability_bone_armor" "骨甲" +"dota_tooltip_ability_bone_armor_description" "提供额外护甲。对远程英雄:若%hero_proximity_radius%范围内没有英雄,攻击者受到所造成伤害%isolated_damage_multiplier%倍的反击。否则有%damage_reflect_chance%%%几率反射%damage_reflect_pct%%%伤害。" +"dota_tooltip_ability_bone_armor_armor_bonus" "装甲:" +"dota_tooltip_ability_bone_armor_hero_proximity_radius" "英雄检测半径:" +"dota_tooltip_ability_bone_armor_isolated_damage_multiplier" "孤立伤害倍率:" +"dota_tooltip_ability_bone_armor_lore" "在冥界之火中调和的骨头比钢铁还要坚固,能够抵御攻击者的打击。" + +"dota_tooltip_ability_toxin" "毒素" +"dota_tooltip_ability_toxin_description" "死亡后,它会留下一个毒素池,使半径为 %radius% 单位的敌人的被动能力失效。重叠的毒素池会合并为一个:伤害叠加,每次合并半径增加 %merge_radius_bonus%,持续时间每次合并延长 %merge_duration_bonus% 秒。" +"dota_tooltip_ability_toxin_damage" "损害:" +"dota_tooltip_ability_toxin_radius" "半径:" +"dota_tooltip_ability_toxin_duration" "持续时间:" +"dota_tooltip_ability_toxin_merge_radius_bonus" "每次合并半径:" +"dota_tooltip_ability_toxin_merge_duration_bonus" "每次合并延长时间(秒):" +"dota_tooltip_ability_toxin_lore" "僵尸腐烂尸体释放的致命毒素会毒害该地区的所有生命,并扰乱魔法能力的运作。" + +"dota_tooltip_ability_skeleton_archer_fire_arrow" "火箭" +"dota_tooltip_ability_skeleton_archer_fire_arrow_description" "弓箭手骨架会发射箭,伤害并击晕其路径上的敌人。" +"dota_tooltip_ability_skeleton_archer_fire_arrow_bonus_damage" "损坏:" +"dota_tooltip_ability_skeleton_archer_fire_arrow_lore" "在冥界之火中调和的箭比钢铁更强大,能够抵挡攻击者的打击。" + +"dota_tooltip_ability_weaking_impetus" "减弱共振" +"dota_tooltip_ability_weaking_impetus_description" "在攻击中,它会对目标施加一种效果,使每个堆栈的传出伤害减少 %damage_reduction%%%。 效果加起来为 %max_stacks% 倍。" +"dota_tooltip_ability_weaking_impetus_damage_reduction" "每个堆栈的破坏伤害百分比:" +"dota_tooltip_ability_weaking_impetus_mana_hit" "命中时命中:" +"dota_tooltip_ability_weaking_impetus_debuff_duration" "持续时间:" +"dota_tooltip_ability_weaking_impetus_lore" "死去的森林仙女的魔法箭会吸走受害者的力量,使他们的攻击力随着每次攻击而减弱。" + +"dota_tooltip_ability_zombie_armor_decress" "装甲腐蚀攻击" +"dota_tooltip_ability_zombie_armor_decress_description" "每次攻击都会将目标的装甲减少 %armor_debuff% 至 %corruption_duration% 秒。" +"dota_tooltip_ability_zombie_armor_decress_armor_debuff" "重置装甲:" +"dota_tooltip_ability_zombie_armor_decress_corruption_duration" "持续时间:" + +"dota_tooltip_ability_zombie_rage" "僵尸愤怒" +"dota_tooltip_ability_zombie_rage_description" "以 %duration% 秒的速度进入狂怒状态,获得魔法抵抗和控制效果。" + +"dota_tooltip_ability_zombie_feast" "盛宴" +"dota_tooltip_ability_zombie_feast_description" "受到攻击时被动窃取目标的生命值。对抗蠕动,效率就会降低。" + +"dota_tooltip_ability_zombie_open_wounds" "血伤" +"dota_tooltip_ability_zombie_open_wounds_description" "将目标降低 %duration% 秒。 它可以让你从造成的损害中恢复过来。" +"dota_tooltip_ability_wave_desperate_vampirism" "濒死渴血" +"dota_tooltip_ability_wave_desperate_vampirism_description" "生命值低于最大值的 %hp_threshold_pct%%% 时,每次成功攻击会按那次命中造成伤害的 %vamp_pct%%% 回复生命。" +"dota_tooltip_ability_wave_desperate_vampirism_hp_threshold_pct" "触发阈值(%% 最大生命):" +"dota_tooltip_ability_wave_desperate_vampirism_vamp_pct" "回复(%% 命中伤害):" + +"dota_tooltip_ability_skeleton_blast" "地狱爆发" +"dota_tooltip_ability_skeleton_blast_description" "击晕目标并造成伤害:立即造成 %damage%,并定期造成 %blast_dot_damage%。" + +"dota_tooltip_ability_skeleton_king_mortal_strike" "致命一击" +"dota_tooltip_ability_skeleton_king_mortal_strike_description" "定期进行批判性打击,乘数为 %crit_mult%%。" + +"dota_tooltip_modifier_weaking_impetus_debuff" "动力减弱" +"dota_tooltip_modifier_weaking_impetus_debuff_description" "您的出站伤害减少%dMODIFIER_PROPERTY_DAMAGEOUTGOING_PERCENTAGE%%%,您的魔法增益减少%dMODIFIER_PROPERTY_SPELL_AMPLIFY_PERCENTAGE%%%。" + +//斯文 +"dota_tooltip_ability_ability_sven_storm_hammer_custom" "风暴锤" +"dota_tooltip_ability_ability_sven_storm_hammer_custom_Lore" "从父亲学校借来的叛军骑士的铁手套,可以击杀任何敌人的灵魂。" +"dota_tooltip_ability_ability_sven_storm_hammer_custom_radius" "半径:" +"dota_tooltip_ability_ability_sven_storm_hammer_custom_stun_duration" "眩晕持续时间:" +"dota_tooltip_ability_ability_sven_storm_hammer_custom_stun_duration_for_boss_mult" "对首领眩晕时间倍率:" +"dota_tooltip_ability_ability_sven_storm_hammer_custom_damage" "损坏:" +"dota_tooltip_ability_ability_sven_storm_hammer_custom_Description" "向敌方单位或地点投掷战锤:爆炸时对%radius%范围内敌人造成伤害并眩晕。开启自动施法时净化主目标、额外攻击一次,斯文会跟随飞行物移动。" + +"dota_tooltip_ability_ability_sven_great_cleave_custom" "大分裂" +"dota_tooltip_ability_ability_sven_great_cleave_custom_Description" "被动:攻击造成前方锥形分裂(%cleave_damage_pct%%%攻击伤害;宽度%cleave_starting_width%–%cleave_ending_width%,距离%cleave_radius%)。主动:消耗%health_cost_pct%%%当前生命,强化下一次攻击必定暴击,分裂强度为%cleave_damage_multiple_facet%%%。" +"dota_tooltip_ability_ability_sven_great_cleave_custom_Lore" "斯文的强力打击可以同时击中多个对手。" +"dota_tooltip_ability_ability_sven_great_cleave_custom_cleave_damage_pct" "% 分裂伤害:" +"dota_tooltip_ability_ability_sven_great_cleave_custom_cleave_radius" "距离:" +"dota_tooltip_ability_ability_sven_great_cleave_custom_health_cost_pct" "%当前生命(主动):" +"dota_tooltip_ability_ability_sven_great_cleave_custom_cleave_damage_multiple_facet" "%强化分裂倍率:" + +"dota_tooltip_ability_ability_sven_gods_strength_custom" "上帝的力量" +"dota_tooltip_ability_ability_sven_gods_strength_custom_Description" "斯文召唤古代诸神之力大幅提升伤害。天赋下每点已损失生命额外获得%gods_strength_damage_bonus_per_health%%%伤害。" +"dota_tooltip_ability_ability_sven_gods_strength_custom_Lore" "古代诸神赋予斯文在战斗中不可思议的力量。" +"dota_tooltip_ability_ability_sven_gods_strength_custom_gods_strength_damage_bonus" "伤害奖励百分比:" +"dota_tooltip_ability_ability_sven_gods_strength_custom_duration" "持续时间:" + +"dota_tooltip_ability_ability_sven_gods_strength_custom_Scepter_Description" "在能力持续时间内,斯文为每个装甲单元接收额外的 %gods_strength_bonus_strength_pct% 力单位。" +"dota_tooltip_ability_ability_sven_gods_strength_custom_Shard_Description" "上帝的力量持续期间,斯文获得 %gods_strength_shard_magic_resist%%% 魔法抗性。" +"dota_tooltip_ability_ability_sven_gods_strength_custom_gods_strength_shard_magic_resist" "碎片魔法抗性:" + +"dota_tooltip_modifier_sven_gods_strength_custom" "上帝的力量" +"dota_tooltip_modifier_sven_gods_strength_custom_Description" "损坏增加%dMODIFIER_PROPERTY_DAMAGEOUTGOING_PERCENTAGE%%%" + +"dota_tooltip_ability_ability_sven_warcry_custom" "战斗呐喊" +"dota_tooltip_ability_ability_sven_warcry_custom_Description" "斯文发出战斗口号,让他和附近的所有盟军英雄获得盔甲、移动速度和攻击的奖励。对于每件盔甲,强大的骑士都会受到额外伤害,金额为 %damage_bonus_per_armor%。" +"dota_tooltip_ability_ability_sven_warcry_custom_Lore" "斯文的战斗口号给盟友心中注入了勇气,给敌人带来了恐惧。" +"dota_tooltip_ability_ability_sven_warcry_custom_armor_bonus" "额外护甲:" +"dota_tooltip_ability_ability_sven_warcry_custom_movespeed_bonus" "%DOP。移动速度:" +"dota_tooltip_ability_ability_sven_warcry_custom_attackspeed_bonus" "%DOP。攻击速度:" +"dota_tooltip_ability_ability_sven_warcry_custom_duration" "持续时间:" +"dota_tooltip_ability_ability_sven_warcry_custom_radius" "半径:" + +"dota_tooltip_modifier_sven_warcry_custom_active" "沃克里" +"dota_tooltip_modifier_sven_warcry_custom_active_Description" "Warcry 分别将您的盔甲、移动速度和攻击速度提高 %dMODIFIER_PROPERTY_PHYSICAL_ARMOR_BONUS%%%、%dMODIFIER_PROPERTY_MOVESPEED_BONUS_PERCENTAGE%%% 和 %dMODIFIER_PROPERTY_ATTACKSPEED_PERCENTAGE%%%。" + +"dota_tooltip_modifier_sven_warcry_custom" "沃克里" +"dota_tooltip_modifier_sven_warcry_custom_Description" "装甲增加%dMODIFIER_PROPERTY_PHYSICAL_ARMOR_BONUS%,移动速度增加%dMODIFIER_PROPERTY_MOVESPEED_BONUS_PERCENTAGE%%%" + + +// -----------------复仇之魂-------------------- +"dota_tooltip_ability_vengefulspirit_magic_missile_custom" "魔法箭" +"dota_tooltip_ability_vengefulspirit_magic_missile_custom_Description" "向敌人发射魔法箭:眩晕、造成伤害并燃烧法力。命中后会弹射至施法距离内最近的另一名敌人。" +"dota_tooltip_ability_vengefulspirit_magic_missile_custom_Lore" "天怒族最基础的法术——魔法箭——是申德尔泽勒复仇的主要手段。" +"dota_tooltip_ability_vengefulspirit_magic_missile_custom_stun_duration" "眩晕持续时间:" +"dota_tooltip_ability_vengefulspirit_magic_missile_custom_damage" "伤害:" +"dota_tooltip_ability_vengefulspirit_magic_missile_custom_mana_burn" "法力燃烧:" + +"dota_tooltip_ability_vengefulspirit_wave_of_terror_custom" "恐怖波动" +"dota_tooltip_ability_vengefulspirit_wave_of_terror_custom_Description" "发出骇人尖叫:波动提供视野,造成伤害,并降低路径上敌人的护甲(固定值 + 基础护甲的 %armor_reduction_pct%%%)与攻击伤害。" +"dota_tooltip_ability_vengefulspirit_wave_of_terror_custom_Lore" "申德尔泽勒撕裂灵魂的嗓音预示着她的到来。" +"dota_tooltip_ability_vengefulspirit_wave_of_terror_custom_armor_reduction" "护甲降低:" +"dota_tooltip_ability_vengefulspirit_wave_of_terror_custom_armor_reduction_pct" "%基础护甲降低:" +"dota_tooltip_ability_vengefulspirit_wave_of_terror_custom_attack_reduction" "%攻击伤害降低:" +"dota_tooltip_ability_vengefulspirit_wave_of_terror_custom_damage" "伤害:" +"dota_tooltip_ability_vengefulspirit_wave_of_terror_custom_wave_width" "波动宽度:" +"dota_tooltip_ability_vengefulspirit_wave_of_terror_custom_vision_duration" "视野持续时间:" + +"dota_tooltip_modifier_vengefulspirit_wave_of_terror_custom" "恐怖波动" +"dota_tooltip_modifier_vengefulspirit_wave_of_terror_custom_Description" "护甲与攻击伤害降低。" + +"dota_tooltip_modifier_vengefulspirit_wave_of_terror_custom_buff" "恐怖波动" +"dota_tooltip_modifier_vengefulspirit_wave_of_terror_custom_buff_Description" "从敌人处偷取的护甲与攻击伤害。" + +"dota_tooltip_ability_ability_vengefulspirit_command_aura_custom" "命令光环" +"dota_tooltip_ability_ability_vengefulspirit_command_aura_custom_Description" "被动光环:附近友军获得额外基础攻击力、物理吸血与法术吸血。" +"dota_tooltip_ability_ability_vengefulspirit_command_aura_custom_Lore" "申德尔泽勒与并肩作战者分享怒火。" +"dota_tooltip_ability_ability_vengefulspirit_command_aura_custom_bonus_base_damage" "额外伤害:" +"dota_tooltip_ability_ability_vengefulspirit_command_aura_custom_physical_vampirism" "%物理吸血:" +"dota_tooltip_ability_ability_vengefulspirit_command_aura_custom_magical_vampirism" "%法术吸血:" +"dota_tooltip_ability_ability_vengefulspirit_command_aura_custom_aura_radius" "作用范围:" + +"dota_tooltip_ability_ability_vengefulspirit_spirit_debt_innate" "灵魂盛宴" +"dota_tooltip_ability_ability_vengefulspirit_spirit_debt_innate_Description" "技能为敌人施加灵魂债务标记。标记敌人死亡时,你恢复生命与法力;命令光环范围内的友军获得部分治疗。" +"dota_tooltip_ability_ability_vengefulspirit_spirit_debt_innate_Lore" "陨落之魂滋养活者的复仇。" +"dota_tooltip_ability_ability_vengefulspirit_spirit_debt_innate_mark_duration" "标记持续时间:" +"dota_tooltip_ability_ability_vengefulspirit_spirit_debt_innate_heal_on_kill_pct" "%目标最大生命值治疗:" +"dota_tooltip_ability_ability_vengefulspirit_spirit_debt_innate_mana_restore" "恢复法力:" +"dota_tooltip_ability_ability_vengefulspirit_spirit_debt_innate_ally_heal_share_pct" "%友军分享治疗:" + +"dota_tooltip_modifier_vengefulspirit_spirit_debt_mark" "灵魂债务" +"dota_tooltip_modifier_vengefulspirit_spirit_debt_mark_Description" "死亡时将滋养复仇之魂与附近友军。" + +"dota_tooltip_ability_vengefulspirit_nether_swap_custom" "移形换位" +"dota_tooltip_ability_vengefulspirit_nether_swap_custom_Description" "与目标交换生命百分比;保留最低生命值比例。对友方目标会转移所有带持续时间的增益效果。" +"dota_tooltip_ability_vengefulspirit_nether_swap_custom_Lore" "殉道不过是复仇的小小代价。" +"dota_tooltip_ability_vengefulspirit_nether_swap_custom_hit_point_minimum_pct" "%交换后最低生命值:" + +"dota_tooltip_ability_vengefulspirit_revenge" "复仇" +"dota_tooltip_ability_vengefulspirit_revenge_Description" "进入暗影界:无法被选中,每次攻击造成暴击伤害。" +"dota_tooltip_ability_vengefulspirit_revenge_Lore" "复仇从不留情。" +"dota_tooltip_ability_vengefulspirit_revenge_duration" "持续时间:" +"dota_tooltip_ability_vengefulspirit_revenge_crit_bonus" "%暴击伤害:" + +"DOTA_Tooltip_ability_special_bonus_unique_vengefulspirit_4_1" "+{s:bonus_heal_or_damage}% 目标最大生命值伤害或治疗(移形换位)" +"DOTA_Tooltip_ability_special_bonus_unique_vengefulspirit_4_2" "移形换位施加基础驱散" + +//斯文的才华 +"DOTA_Tooltip_ability_special_bonus_unique_sven_warcry_duration_base" "+{s:bonus_duration} 秒到战斗呐喊的持续时间。" +"DOTA_Tooltip_ability_special_bonus_unique_sven_storm_hammer_stun_duration" "+{s:bonus_stun_duration} 秒到风暴锤击晕" +"DOTA_Tooltip_ability_special_bonus_unique_sven_gods_strength_duration_base" "+{s:bonus_duration} 秒到上帝力量的持续时间。" +"DOTA_Tooltip_ability_special_bonus_unique_sven_warcry_damage_bonus_per_armor" "+{s:bonus_damage_bonus_per_armor} 对战斗呐喊中每个单位盔甲的伤害。" +"DOTA_Tooltip_ability_special_bonus_unique_sven_gods_strength_damage_bonus_base" "+{s:bonus_gods_strength_damage_bonus}% 来自上帝力量的伤害。" +"DOTA_Tooltip_ability_special_bonus_unique_sven_gods_strength_damage_per_health" "+{s:bonus_gods_strength_damage_bonus_per_health} 每点已损失生命值额外伤害(上帝之力期间)" +"DOTA_Tooltip_ability_special_bonus_unique_sven_great_cleave_damage_pct" "+{s:bonus_cleave_damage_pct}% 遭受大分裂损害" + + +// -----------------Sand King-------------------- +"dota_tooltip_ability_sandking_burrowstrike_custom" "Burrowstrike" +"dota_tooltip_ability_sandking_burrowstrike_custom_Description" "潜入地下并向目标点突进。沿途敌人受到魔法伤害并眩晕 %stun_duration% 秒;部分伤害随自身最大生命提高。" +"dota_tooltip_ability_sandking_burrowstrike_custom_Lore" "闪耀荒漠的沙砾记录着上古的传说。" +"dota_tooltip_ability_sandking_burrowstrike_custom_burrow_anim_time" "钻地前摇:" +"dota_tooltip_ability_sandking_burrowstrike_custom_burrow_width" "隧道宽度:" +"dota_tooltip_ability_sandking_burrowstrike_custom_burrow_speed" "地底突进速度:" +"dota_tooltip_ability_sandking_burrowstrike_custom_AbilityCastRange" "施法距离:" +"dota_tooltip_ability_sandking_burrowstrike_custom_stun_duration" "眩晕持续时间:" +"dota_tooltip_ability_sandking_burrowstrike_custom_burrow_bonus_damage_max_hp_pct" "%最大生命附加伤害:" + +"dota_tooltip_ability_sandking_sand_storm_custom" "Sand Storm" +"dota_tooltip_ability_sandking_sand_storm_custom_Description" "在身边唤起沙尘暴,持续 %duration% 秒并跟随移动。每 %damage_tick_rate% 秒对半径 %sand_storm_radius% 内的敌人造成魔法伤害并减速 %sand_storm_move_speed%%%。基础强度为每秒 %sand_storm_damage% 点伤害;额外伤害随最大生命与生命回复提升(见下方数值)。" +"dota_tooltip_ability_sandking_sand_storm_custom_Lore" "在闪耀荒漠,风暴不问姓名,只问谁还站在风里。" +"dota_tooltip_ability_sandking_sand_storm_custom_duration" "持续时间:" +"dota_tooltip_ability_sandking_sand_storm_custom_damage_tick_rate" "伤害间隔:" +"dota_tooltip_ability_sandking_sand_storm_custom_sand_storm_radius" "范围半径:" +"dota_tooltip_ability_sandking_sand_storm_custom_sand_storm_damage" "每秒基础伤害:" +"dota_tooltip_ability_sandking_sand_storm_custom_sand_storm_regen_damage_pct" "生命与回复加成系数:" +"dota_tooltip_ability_sandking_sand_storm_custom_sand_storm_move_speed" "%减速(移动):" + +"dota_tooltip_ability_sandking_scorpion_strike_custom" "Scorpion Strike" +"dota_tooltip_ability_sandking_scorpion_strike_custom_Description" "尾刺轰击地面,对半径 %radius% 内敌人造成物理伤害。内圈 %inner_radius% 伤害额外提高 %inner_radius_bonus_damage_pct%%%,并减速 %strike_slow%%%,持续 %debuff_duration% 秒。伤害包含固定加成、攻击力占比与最大生命占比。" +"dota_tooltip_ability_sandking_scorpion_strike_custom_Lore" "一记蛰刺落地,沙漠便记得谁才是主人。" +"dota_tooltip_ability_sandking_scorpion_strike_custom_Scepter_Description" "冷却大幅降低;每名被击中的敌人额外叠加 %scepter_stack% 次 Caustic Finale 剧毒。" +"dota_tooltip_ability_sandking_scorpion_strike_custom_radius" "半径:" +"dota_tooltip_ability_sandking_scorpion_strike_custom_inner_radius" "内圈半径:" +"dota_tooltip_ability_sandking_scorpion_strike_custom_inner_radius_bonus_damage_pct" "%内圈额外伤害:" +"dota_tooltip_ability_sandking_scorpion_strike_custom_attack_damage" "附加伤害:" +"dota_tooltip_ability_sandking_scorpion_strike_custom_attack_damage_pct_from_attack" "%攻击力折算:" +"dota_tooltip_ability_sandking_scorpion_strike_custom_scorpion_bonus_damage_max_hp_pct" "%最大生命附加伤害:" +"dota_tooltip_ability_sandking_scorpion_strike_custom_debuff_duration" "减速持续时间:" +"dota_tooltip_ability_sandking_scorpion_strike_custom_strike_slow" "%减速:" +"dota_tooltip_ability_sandking_scorpion_strike_custom_scepter_stack" "每名敌人剧毒叠层:" +"dota_tooltip_ability_sandking_scorpion_strike_custom_scepter_cd_pct" "%冷却缩减:" +"dota_tooltip_ability_sandking_scorpion_strike_custom_AbilityCooldown" "冷却时间:" + +"dota_tooltip_ability_sandking_caustic_finale_custom" "Caustic Finale" +"dota_tooltip_ability_sandking_caustic_finale_custom_Description" "普攻叠剧毒;叠满 %max_attacks% 次后在半径 %caustic_finale_radius% 引爆,造成魔法伤害并减速 %explosion_slow_pct%%%。爆炸伤害随等级成长。若中毒目标提前阵亡,爆炸更痛——部分伤害随目标最大生命与自身等级提高(见下方数值)。" +"dota_tooltip_ability_sandking_caustic_finale_custom_Lore" "商队曾淹没于更烈的剧毒里——滚烫沙地上的每一步,都可能是最后一步。" +"dota_tooltip_ability_sandking_caustic_finale_custom_caustic_finale_radius" "爆炸半径:" +"dota_tooltip_ability_sandking_caustic_finale_custom_explosion_damage_per_hero_level" "每级爆炸伤害:" +"dota_tooltip_ability_sandking_caustic_finale_custom_kill_explosion_max_hp_pct_base" "阵亡加成:目标最大生命基础(百分点):" +"dota_tooltip_ability_sandking_caustic_finale_custom_kill_explosion_max_hp_pct_per_level" "阵亡加成:每级额外(百分点):" +"dota_tooltip_ability_sandking_caustic_finale_custom_caustic_finale_duration" "剧毒持续时间:" +"dota_tooltip_ability_sandking_caustic_finale_custom_max_attacks" "引爆所需命中:" +"dota_tooltip_ability_sandking_caustic_finale_custom_explosion_slow_pct" "%爆炸减速:" + +"dota_tooltip_ability_sandking_epicenter_custom" "Epicenter" +"dota_tooltip_ability_sandking_epicenter_custom_Description" "短暂施法后,在 %pulse_phase_duration% 秒内从自身脚下释放 %epicenter_pulses% 轮向外扩散的沙震波(期间可自由移动)。每轮造成 %epicenter_damage% 点魔法伤害,减速移动 %epicenter_slow%%%、降低攻速 %epicenter_slow_as%,持续 %slow_duration% 秒。起始半径 %epicenter_radius_base%,每轮增加 %epicenter_radius_increment%。" +"dota_tooltip_ability_sandking_epicenter_custom_Lore" "大地颤抖,沙墙隆起——荒漠回应它的王。" +"dota_tooltip_ability_sandking_epicenter_custom_Scepter_Description" "脉冲期间以每秒 %scepter_rolls_per_second% 次推进时间刻度:每 %scepter_proc_every_n_time_checks% 次刻度,若当前波纹内有敌人,则在其脚下触发完整 Scorpion Strike。默认 1 次;每 %scepter_luck_per_extra_tail% 点幸运额外 +1 次(尽量打在不同敌人上,敌人不足会重复)。需已学习 Scorpion Strike。" +"dota_tooltip_ability_sandking_epicenter_custom_Shard_Description" "施法时间缩短 %shard_cast_reduction% 秒;每隔 %shard_break_count% 轮脉冲使半径 %shard_break_radius% 内的敌人眩晕 %shard_break_duration% 秒。" +"dota_tooltip_ability_sandking_epicenter_custom_epicenter_pulses" "脉冲次数:" +"dota_tooltip_ability_sandking_epicenter_custom_epicenter_damage" "单次脉冲伤害:" +"dota_tooltip_ability_sandking_epicenter_custom_epicenter_radius_base" "起始半径:" +"dota_tooltip_ability_sandking_epicenter_custom_epicenter_radius_increment" "半径增长:" +"dota_tooltip_ability_sandking_epicenter_custom_epicenter_slow" "%减速(移动):" +"dota_tooltip_ability_sandking_epicenter_custom_epicenter_slow_as" "攻速降低:" +"dota_tooltip_ability_sandking_epicenter_custom_slow_duration" "减速持续时间:" +"dota_tooltip_ability_sandking_epicenter_custom_pulse_phase_duration" "脉冲阶段持续时间:" +"dota_tooltip_ability_sandking_epicenter_custom_scepter_rolls_per_second" "每秒时间刻度:" +"dota_tooltip_ability_sandking_epicenter_custom_scepter_proc_every_n_time_checks" "每 N 次刻度触发尾刺:" +"dota_tooltip_ability_sandking_epicenter_custom_scepter_luck_per_extra_tail" "幸运间隔(+1 额外尾刺):" +"dota_tooltip_ability_sandking_epicenter_custom_shard_cast_reduction" "施法时间缩短:" +"dota_tooltip_ability_sandking_epicenter_custom_shard_break_duration" "眩晕持续时间:" +"dota_tooltip_ability_sandking_epicenter_custom_shard_break_count" "脉冲间隔触发眩晕:" +"dota_tooltip_ability_sandking_epicenter_custom_shard_break_radius" "眩晕半径:" + +"DOTA_Tooltip_ability_special_bonus_unique_sandking_burrow_range" "+{s:bonus_AbilityCastRange} Burrowstrike 施法距离" +"DOTA_Tooltip_ability_special_bonus_unique_sandking_burrow_stun" "+{s:bonus_stun_duration} 秒 Burrowstrike 眩晕" +"DOTA_Tooltip_ability_special_bonus_unique_sandking_storm_damage" "+{s:bonus_sand_storm_damage} Sand Storm 每秒魔法伤害" +"DOTA_Tooltip_ability_special_bonus_unique_sandking_scorpion_cd" "-{s:bonus_AbilityCooldown} 秒 Scorpion Strike 冷却" +"DOTA_Tooltip_ability_special_bonus_unique_sandking_epicenter_pulses" "+{s:bonus_epicenter_pulses} Epicenter 脉冲" +"DOTA_Tooltip_ability_special_bonus_unique_sandking_epicenter_damage" "+{s:bonus_epicenter_damage} Epicenter 单次伤害" +"DOTA_Tooltip_ability_special_bonus_unique_sandking_finale_radius" "+{s:bonus_caustic_finale_radius} Caustic Finale 爆炸半径" + + +// -------------------- +"dota_tooltip_ability_ability_juggernaut_blade_fury_custom" "刀锋之怒" +"dota_tooltip_ability_ability_juggernaut_blade_fury_custom_Description" "剑刃风暴:%radius%范围内造成自身伤害+魔法伤害并获得魔免。

可切换施法,向指定地点释放风暴。" +"dota_tooltip_ability_ability_juggernaut_blade_fury_custom_Lore" "Juggernaut 的刀刃技能使他能够制造致命的漩涡,扫除路径上的所有敌人。" +"dota_tooltip_ability_ability_juggernaut_blade_fury_custom_duration" "持续时间:" +"dota_tooltip_ability_ability_juggernaut_blade_fury_custom_radius" "半径:" +"dota_tooltip_ability_ability_juggernaut_blade_fury_custom_abilitycastrange" "涡流应用范围:" +"dota_tooltip_ability_ability_juggernaut_blade_fury_custom_damage_per_tick" "每次滴答的损坏:" +"dota_tooltip_ability_ability_juggernaut_blade_fury_custom_Shard_Description" "增加伤害和持续时间。新增被动效果:攻击有%proc_chance%%%几率(受幸运影响)触发1次剑刃风暴或延长现有风暴。" + + + +"dota_tooltip_modifier_juggernaut_blade_fury_custom" "刀锋之怒" +"dota_tooltip_modifier_juggernaut_blade_fury_custom_Description" "英雄围绕自己旋转,对半径内的敌人造成魔法伤害。魔法免疫处于激活状态,但攻击造成的损害会减少。" + +"dota_tooltip_ability_ability_juggernaut_healing_ward_custom" "治疗病房" +"dota_tooltip_ability_ability_juggernaut_healing_ward_custom_Description" "贾格诺呼吁建立一个跟随英雄的治疗病房,并以每秒最大生命值的 %heal_per_second%% 治疗半径为 %heal_radius% 单位内的所有盟友。Ward 存在 %ward_duration% 秒。" +"dota_tooltip_ability_ability_juggernaut_healing_ward_custom_Lore" "由大师级治疗师创建的古老病房拥有生命的力量,可以治愈最严重的伤口。" +"dota_tooltip_ability_ability_juggernaut_healing_ward_custom_ward_duration" "持续时间:" +"dota_tooltip_ability_ability_juggernaut_healing_ward_custom_heal_radius" "治疗半径:" +"dota_tooltip_ability_ability_juggernaut_healing_ward_custom_heal_per_second" "每秒处理百分比:" + +"dota_tooltip_modifier_juggernaut_healing_ward_custom" "治疗病房" +"dota_tooltip_modifier_juggernaut_healing_ward_custom_Description" "Vard 治愈半径内的所有盟友。" + +"dota_tooltip_ability_ability_juggernaut_blade_dance_custom" "刀舞" +"dota_tooltip_ability_ability_juggernaut_blade_dance_custom_Description" "被动暴击。

主动 — 星界斩:

冲向目标点(%min_travel_distance%–%max_travel_distance%),线宽%astral_slash_radius%,共%astral_slash_attack_count%段伤害(可暴击)。每段对敌人施加%astral_slash_debuff_duration%秒减甲%juggernaut_blade_dance_jugg_step_armor_reduce%%%。

alt:0.25秒后沿原路折返再造成一次伤害。" +"dota_tooltip_ability_ability_juggernaut_blade_dance_custom_Lore" "使用 Jagernaut 刀片的艺术使他能够发现敌人的弱点并切断路径上的一切。" +"dota_tooltip_ability_ability_juggernaut_blade_dance_custom_crit_chance" "%关键影响机会:" +"dota_tooltip_ability_ability_juggernaut_blade_dance_custom_crit_mult" "%严重伤害:" +"dota_tooltip_ability_ability_juggernaut_blade_dance_custom_lifesteal_percent" "%吸血鬼:" +"dota_tooltip_ability_ability_juggernaut_blade_dance_custom_AbilityCastRange" "中断范围:" +"dota_tooltip_ability_ability_juggernaut_blade_dance_custom_AbilityCooldown" "充电:" +"dota_tooltip_ability_ability_juggernaut_blade_dance_custom_AbilityManaCost" "法力消耗:" +"dota_tooltip_ability_ability_juggernaut_blade_dance_custom_min_travel_distance" "分钟。抢夺距离:" +"dota_tooltip_ability_ability_juggernaut_blade_dance_custom_max_travel_distance" "最大。抢夺距离:" +"dota_tooltip_ability_ability_juggernaut_blade_dance_custom_astral_slash_radius" "线宽:" + +"dota_tooltip_modifier_juggernaut_blade_dance_astral_slash_debuff" "星界斩" +"dota_tooltip_modifier_juggernaut_blade_dance_astral_slash_debuff_Description" "被星界斩击击中" + +"dota_tooltip_ability_ability_juggernaut_omnislash_custom" "Omnislash" +"dota_tooltip_ability_ability_juggernaut_omnislash_custom_Description" "Jagernaut 跳转到选定的目标并发起一系列攻击,造成 %damage%% 攻击伤害。通过在半径为 %bounce_radius% 单位内的敌人之间跳跃,该能力可以持续 %duration% 秒。在行动中,英雄是无敌的,无法被阻止。" +"dota_tooltip_ability_ability_juggernaut_omnislash_custom_Lore" "贾格纳特最伟大的技术,让他能够瞬间出拳,将敌人化为尘土。" +"dota_tooltip_ability_ability_juggernaut_omnislash_custom_duration" "持续时间:" +"dota_tooltip_ability_ability_juggernaut_omnislash_custom_damage" "%攻击伤害:" +"dota_tooltip_ability_ability_juggernaut_omnislash_custom_bounce_radius" "跳跃半径:" +"dota_tooltip_ability_ability_juggernaut_omnislash_custom_slash_interval_mult" "攻击率乘数:" +"dota_tooltip_ability_ability_juggernaut_omnislash_custom_Scepter_Description" "此外还赋予 Swift Slash 能力,可以快速攻击目标。" + +"dota_tooltip_ability_ability_juggernaut_miniomnislash_custom" "快速斜杠" +"dota_tooltip_ability_ability_juggernaut_miniomnislash_custom_duration" "持续时间:" +"dota_tooltip_ability_ability_juggernaut_miniomnislash_custom_damage" "%攻击伤害:" +"dota_tooltip_ability_ability_juggernaut_miniomnislash_custom_bounce_radius" "跳跃半径:" +"dota_tooltip_ability_ability_juggernaut_miniomnislash_custom_slash_interval_mult" "攻击率乘数:" +"dota_tooltip_ability_ability_juggernaut_miniomnislash_custom_Description" "贾格诺快速跳向目标,造成%伤害%攻击伤害。持续时间:%持续时间%秒。" +"dota_tooltip_ability_ability_juggernaut_miniomnislash_custom_Lore" "Omnislash 的简化版本,可让您快速击中目标。" + +"dota_tooltip_modifier_juggernaut_omnislash_custom" "Omnislash" +"dota_tooltip_modifier_juggernaut_omnislash_custom_Description" "英雄无敌,在敌人之间跳跃,发动一系列攻击。" + +"dota_tooltip_ability_ability_juggernaut_samurai_soul" "武士灵魂" +"dota_tooltip_ability_ability_juggernaut_samurai_soul_Description" "当英雄即将死去时,他会立即恢复最大程度的健康并获得一堆弱点。对于每个堆栈,英雄都会失去 %debuff_pct%%% 的基本特征(力量、灵巧、智力)。最多 4 杯,收到 4 杯后,英雄就会变成凡人。如果英雄没有受到 %cooldown% 秒的攻击,所有赌注都会重置。" +"dota_tooltip_ability_ability_juggernaut_samurai_soul_Lore" "武士 Jagernaut 的灵魂使他在最绝望的情况下能够生存,但代价是逐渐衰弱。" +"dota_tooltip_ability_ability_juggernaut_samurai_soul_cooldown" "堆栈重置时间:" +"dota_tooltip_ability_ability_juggernaut_samurai_soul_debuff_pct" "特征的百分比衰减:" + +"dota_tooltip_modifier_juggernaut_samurai_soul" "武士灵魂" +"dota_tooltip_modifier_juggernaut_samurai_soul_Description" "对于每个堆栈,都会丢失 25% 的基础统计数据。如果英雄已经有一段时间没有受到攻击,堆栈就会被重置。" + + + + + + + +//天才巨无霸 +"dota_tooltip_ability_special_bonus_unique_juggernaut_healing_ward_duration_custom" "+{s:bonus_ward_duration} 治愈病房持续时间的秒数。" +"dota_tooltip_ability_special_bonus_unique_juggernaut_blade_dance_lifesteal_custom" "+{s:bonus_lifesteal_percent}% 刀锋舞中的物理吸血鬼主义。" +"dota_tooltip_ability_special_bonus_unique_juggernaut_blade_fury_radius_custom" "+{s:bonus_radius} 单位到刀锋之怒半径。" + + + +//幻影刺客 +"dota_tooltip_ability_ability_phantom_assassin_stifling_dagger_custom" "刺匕首" +"dota_tooltip_ability_ability_phantom_assassin_stifling_dagger_custom_Description" "投掷匕首减速,造成%damage% + %attack_factor%%%攻击的物理伤害并附带攻击特效。天赋下可额外命中主目标附近最多2名敌人。" +"dota_tooltip_ability_ability_phantom_assassin_stifling_dagger_custom_Note0" "攻击效果以其通常的概率起作用。" +"dota_tooltip_ability_ability_phantom_assassin_stifling_dagger_custom_Note1" "该能力可以概述飞行过程中和击中目标后匕首的情况。" +"dota_tooltip_ability_ability_phantom_assassin_stifling_dagger_custom_Lore" "面纱姐妹学到的第一个技能往往预示着快速打击。" +"dota_tooltip_ability_ability_phantom_assassin_stifling_dagger_custom_slow_movespeed" "%移动速度减缓:" +"dota_tooltip_ability_ability_phantom_assassin_stifling_dagger_custom_duration" "持续时间:" +"dota_tooltip_ability_ability_phantom_assassin_stifling_dagger_custom_damage" "基础伤害:" +"dota_tooltip_ability_ability_phantom_assassin_stifling_dagger_custom_attack_factor" "%攻击伤害:" +"dota_tooltip_ability_ability_phantom_assassin_stifling_dagger_custom_abilitycastrange" "施法距离:" + + + +"dota_tooltip_modifier_ability_phantom_assassin_stifling_dagger_slow" "刺匕首" +"dota_tooltip_modifier_ability_phantom_assassin_stifling_dagger_slow_Description" "移动速度减慢%dMODIFIER_PROPERTY_MOVESPEED_BONUS_PERCENTAGE%%%" + +//幻影打击 +"dota_tooltip_ability_ability_phantom_assassin_phantom_strike_custom" "幻影打击" +"dota_tooltip_ability_ability_phantom_assassin_phantom_strike_custom_Description" "传送至目标并获得%bonus_duration%秒攻速。暴击天赋:无冷却,可对单位或地面施放,效果提供%crit_mult%%%暴击伤害。" +"dota_tooltip_ability_ability_phantom_assassin_phantom_strike_custom_Note0" "既可用于敌人,也可用于盟友。" +"dota_tooltip_ability_ability_phantom_assassin_phantom_strike_custom_Note1" "攻击速度奖励仅在攻击敌方生物时有效。" +"dota_tooltip_ability_ability_phantom_assassin_phantom_strike_custom_attack_speed_bonus" "攻击速度奖励:" +"dota_tooltip_ability_ability_phantom_assassin_phantom_strike_custom_bonus_duration" "持续时间:" +"dota_tooltip_ability_ability_phantom_assassin_phantom_strike_custom_Lore" "经过多年训练磨练的技术使莫特雷德能够在眨眼间消失并出现在目标旁边。" + +"dota_tooltip_modifier_phantom_assassin_phantom_strike_custom" "幻影打击" +"dota_tooltip_modifier_phantom_assassin_phantom_strike_custom_Description" "您的攻击速度增加了 %dMODIFIER_PROPERTY_ATTACKSPEED_BONUS_CONSTANT%" + +//模糊 +"dota_tooltip_ability_ability_phantom_assassin_blur_custom" "模糊" +"dota_tooltip_ability_ability_phantom_assassin_blur_custom_Description" "英雄进入隐身状态,并在 %duration% 秒内获得敏捷和移动速度加成。" +"dota_tooltip_ability_ability_phantom_assassin_blur_custom_Lore" "莫特雷德非常擅长隐藏自己的存在,即使是最敏锐的人也能隐身。" +"dota_tooltip_ability_ability_phantom_assassin_blur_custom_bonus_agility_pct" "%敏捷加成:" +"dota_tooltip_ability_ability_phantom_assassin_blur_custom_move_speed_pct" "%移动速度加成:" +"dota_tooltip_ability_ability_phantom_assassin_blur_custom_bonus_agility" "敏捷加成:" +"dota_tooltip_ability_ability_phantom_assassin_blur_custom_duration" "持续时间:" + +"dota_tooltip_modifier_phantom_assassin_blur_active_custom" "闪烁" +"dota_tooltip_modifier_phantom_assassin_blur_active_custom_Description" "您已变得不可见,并被 %dMODIFIER_PROPERTY_MOVESPEED_BONUS_PERCENTAGE%%% 加速" +"dota_tooltip_ability_ability_phantom_assassin_blur_custom_Shard_Description" "在施法点生成一个幻象。幻象持续与 Blur 相同时间,造成 %shard_illusion_damage_outgoing%%% 伤害,并承受 %shard_illusion_damage_incoming%%% 额外伤害。" + +//格雷斯杯 +"dota_tooltip_ability_ability_phantom_assassin_coup_de_grace_custom" "恩典之汤" +"dota_tooltip_ability_ability_phantom_assassin_coup_de_grace_custom_Description" "每次攻击都有几率造成暴击伤害。" +"dota_tooltip_ability_ability_phantom_assassin_coup_de_grace_custom_Lore" "当敌人不再能够抵挡莫特雷德的攻击时,莫特雷德经常会发出致命一击。" +"dota_tooltip_ability_ability_phantom_assassin_coup_de_grace_custom_crit_chance" "%暴击几率:" +"dota_tooltip_ability_ability_phantom_assassin_coup_de_grace_custom_crit_mult" "%暴击伤害:" +"dota_tooltip_ability_ability_phantom_assassin_coup_de_grace_custom_damage_mult" "%额外伤害:" +"dota_tooltip_ability_ability_phantom_assassin_coup_de_grace_custom_health_mult_decrease" "%生命损失:" +"dota_tooltip_ability_ability_phantom_assassin_coup_de_grace_custom_Scepter_Description" "该技能变为主动。持续 %duration% 秒期间,每次攻击都会暴击,并降低目标护甲,降低值为 %armor_reduction% + %armor_reduction_agility_pct%%% 的敏捷。冷却:%ability_cooldown% 秒。" +"dota_tooltip_ability_ability_phantom_assassin_coup_de_grace_custom_Note0" "能力期间对目标的第一次攻击会降低其装甲。" + +"dota_tooltip_modifier_phantom_assassin_coup_de_grace_custom_armor_reduction" "弱点" +"dota_tooltip_modifier_phantom_assassin_coup_de_grace_custom_armor_reduction_Description" "目标丢失了 %dMODIFIER_PROPERTY_PHYSICAL_ARMOR_BONUS% 的盔甲。" + +"dota_tooltip_modifier_phantom_assassin_coup_de_grace_custom_active" "恩典之汤" +"dota_tooltip_modifier_phantom_assassin_coup_de_grace_custom_active_Description" "每次攻击都至关重要,会降低目标的装甲。" + +//幻影狂欢 +"dota_tooltip_ability_ability_phantom_assassin_phantom_bash_custom" "幻影迷茫" +"dota_tooltip_ability_ability_phantom_assassin_phantom_bash_custom_Description" "幻影刺客发动一次打击,击晕目标 %bash_duration% 秒。冷却:%cooldown% 秒。" +"dota_tooltip_ability_ability_phantom_assassin_phantom_bash_custom_Note0" "每个英雄级别的充电时间减少 %bash_cd_level% 秒。" + +"dota_tooltip_modifier_cooldown" "幻影眩晕" +"dota_tooltip_modifier_cooldown_Description" "幻影眩晕正在充电。" + +//天才幻影刺客 +"dota_tooltip_ability_special_bonus_unique_assassin_blur_duration_base" "+{s:bonus_duration} 秒模糊。" +"dota_tooltip_ability_special_bonus_unique_assassin_blur_agility_base" "+{s:bonus_bonus_agility} 模糊效果的灵活性。" +"dota_tooltip_ability_special_bonus_unique_assassin_stifling_dagger_damage_base" "+{s:bonus_attack_factor}窒息匕首攻击造成的伤害百分比。" +"dota_tooltip_ability_special_bonus_unique_assassin_stifling_dagger_chance_again" "具有 {s:bonus_chance}% 机会的攻击将释放一把窒息匕首。" +"dota_tooltip_ability_special_bonus_unique_assassin_stifling_dagger_chance_again_Description" "★匕首使用伪随机来确定致命一击。每次打击的机会 + 2%,直到它起作用。" +"dota_tooltip_ability_special_bonus_unique_assassin_phantom_strike_lifesteal" "+{s:bonus_lifesteal_percent}% 幻影打击影响下的吸血鬼主义。" +"dota_tooltip_ability_special_bonus_unique_assassin_stifling_dagger_max_targets" "+{s:bonus_max_targets} 窒息匕首额外目标" +"dota_tooltip_ability_special_bonus_unique_assassin_phantom_strike_crit" "幻影打击:{s:bonus_crit_chance}% 暴击几率,{s:bonus_crit_mult}% 暴击倍数" + + + +// -----------------圣堂刺客-------------------- +"dota_tooltip_ability_ability_templar_assassin_refraction_custom" "Refraction" +"dota_tooltip_ability_ability_templar_assassin_refraction_custom_Description" "获得可抵挡 %instances% 次攻击的护盾,并在 %duration% 秒内使后续攻击额外造成 %bonus_damage% 点伤害。" +"dota_tooltip_ability_ability_templar_assassin_refraction_custom_instances" "层数:" +"dota_tooltip_ability_ability_templar_assassin_refraction_custom_bonus_damage" "额外伤害:" +"dota_tooltip_ability_ability_templar_assassin_refraction_custom_duration" "持续时间:" +"dota_tooltip_ability_ability_templar_assassin_refraction_custom_Shard_Description" "折光层数增加 %shard_instances_bonus% 层。" +"dota_tooltip_ability_ability_templar_assassin_refraction_custom_Lore" "一门隐秘技艺,让拉娜娅能够预判冲击来临的瞬间,并在刹那间还击。" + +"dota_tooltip_ability_ability_templar_assassin_psi_blades_custom" "Psi Blades" +"dota_tooltip_ability_ability_templar_assassin_psi_blades_custom_Description" "先天技能。攻击穿透目标,并对身后敌人造成 %attack_spill_pct%%% 伤害。额外攻击距离随英雄等级提升。" +"dota_tooltip_ability_ability_templar_assassin_psi_blades_custom_bonus_attack_range" "基础额外距离:" +"dota_tooltip_ability_ability_templar_assassin_psi_blades_custom_bonus_attack_range_per_hero_level" "每级额外距离:" +"dota_tooltip_ability_ability_templar_assassin_psi_blades_custom_attack_spill_range" "穿透距离:" +"dota_tooltip_ability_ability_templar_assassin_psi_blades_custom_attack_spill_pct" "%穿透伤害:" +"dota_tooltip_ability_ability_templar_assassin_psi_blades_custom_Lore" "灵能之刃穿透目标如同利刃划过暗影,将打击延伸到其身后的敌人。" + +"dota_tooltip_ability_ability_templar_assassin_meld_custom" "Meld" +"dota_tooltip_ability_ability_templar_assassin_meld_custom_Description" "两种模式。普通:隐身 %duration% 秒。替代施法:闪烁并向范围内最多 %blink_attack_targets% 名敌人发射攻击弹道(降低 %armor_reduction% 点护甲)。" +"dota_tooltip_ability_ability_templar_assassin_meld_custom_blink_attack_targets" "目标数:" +"dota_tooltip_ability_ability_templar_assassin_meld_custom_meld_damage_deal" "首击额外伤害:" +"dota_tooltip_ability_ability_templar_assassin_meld_custom_armor_reduction" "护甲降低:" +"dota_tooltip_ability_ability_templar_assassin_meld_custom_radius" "搜索半径:" +"dota_tooltip_ability_ability_templar_assassin_meld_custom_duration" "隐身持续时间:" +"dota_tooltip_ability_ability_templar_assassin_meld_custom_Shard_Description" "替代施法额外命中 %shard_bonus_targets% 个目标。" +"dota_tooltip_ability_ability_templar_assassin_meld_custom_Lore" "一息之间遁入虚空,敌人甚至不知道致命一击来自何方。" + +"dota_tooltip_ability_ability_templar_assassin_templar_secret_custom" "Templar Secret" +"dota_tooltip_ability_ability_templar_assassin_templar_secret_custom_Description" "被动:%temp_crit_chance%%% 几率造成 %temp_crit_mult%%% 伤害的自定义暴击。" +"dota_tooltip_ability_ability_templar_assassin_templar_secret_custom_temp_crit_chance" "%暴击几率:" +"dota_tooltip_ability_ability_templar_assassin_templar_secret_custom_temp_crit_mult" "%暴击伤害:" +"dota_tooltip_ability_ability_templar_assassin_templar_secret_custom_Lore" "唯有被选中者才能窥见帷幕后秘密:每一次精准出手都可能成为判决。" + +"dota_tooltip_ability_ability_templar_assassin_bedlam_custom" "Bedlam" +"dota_tooltip_ability_ability_templar_assassin_bedlam_custom_Description" "同伴环绕英雄并周期性发射弹道;命中后圣堂刺客对目标发动一次普通攻击。" +"dota_tooltip_ability_ability_templar_assassin_bedlam_custom_attack_interval" "攻击间隔:" +"dota_tooltip_ability_ability_templar_assassin_bedlam_custom_attack_radius" "攻击半径:" +"dota_tooltip_ability_ability_templar_assassin_bedlam_custom_attack_targets" "目标数:" +"dota_tooltip_ability_ability_templar_assassin_bedlam_custom_Scepter_Description" "目标数 +%scepter_bonus_targets%,攻击间隔缩短至基础的 %scepter_interval_mult_pct%%%。" +"dota_tooltip_ability_ability_templar_assassin_bedlam_custom_Lore" "由古老秘术凝成的同伴环绕身侧,以灵能弹幕撕裂敌阵。" + +"dota_tooltip_ability_ability_templar_assassin_refusion_trap_custom" "Refusion Trap" +"dota_tooltip_ability_ability_templar_assassin_refusion_trap_custom_Description" "放置陷阱。圣堂刺客攻击陷阱时,向附近敌人发射弹道。" +"dota_tooltip_ability_ability_templar_assassin_refusion_trap_custom_radius" "陷阱半径:" +"dota_tooltip_ability_ability_templar_assassin_refusion_trap_custom_count" "弹道数量:" +"dota_tooltip_ability_ability_templar_assassin_refusion_trap_custom_duration" "陷阱持续时间:" +"dota_tooltip_ability_ability_templar_assassin_refusion_trap_custom_Lore" "提前布下的陷阱静候主人的触碰,随后向周围敌人倾泻灵能怒火。" + +"dota_tooltip_ability_special_bonus_unique_templar_assassin_4" "+{s:bonus_bonus_damage} 折光攻击伤害" +"dota_tooltip_ability_special_bonus_unique_templar_assassin_8" "+{s:bonus_instances} 折光层数" +"dota_tooltip_ability_special_bonus_unique_templar_assassin_3" "+{s:bonus_radius} 隐匿搜索半径" +"dota_tooltip_ability_special_bonus_unique_templar_assassin_2" "Bedlam:+{s:bonus_attack_targets} 目标" +"dota_tooltip_ability_special_bonus_unique_templar_assassin_7" "+{s:bonus_attack_spill_pct}% 灵能之刃溅射伤害" + +//水晶少女 +"dota_tooltip_ability_ability_crystal_maiden_crystal_nova_custom" "水晶新星" +"dota_tooltip_ability_ability_crystal_maiden_crystal_nova_custom_Description" "在指定点引爆水晶能量,对半径%radius%内敌人造成魔法伤害并治疗盟友;伤害与治疗随智力提升;敌人叠加冰霜。过量治疗盟友时转为护盾(至多目标最大生命%max_shield_pct%%%,持续%shield_duration%秒)。" +"dota_tooltip_ability_ability_crystal_maiden_crystal_nova_custom_Lore" "莱利姐姐传授给她的古老冰晶魔法,既可以冻结敌人,又可以治愈盟友。" +"dota_tooltip_ability_ability_crystal_maiden_crystal_nova_custom_damage" "损坏:" +"dota_tooltip_ability_ability_crystal_maiden_crystal_nova_custom_heal" "治愈:" +"dota_tooltip_ability_ability_crystal_maiden_crystal_nova_custom_radius" "半径:" +"dota_tooltip_ability_ability_crystal_maiden_crystal_nova_custom_intellect_per_damage" "治疗/智力损害:" +"dota_tooltip_ability_ability_crystal_maiden_crystal_nova_custom_frost_stacks_per_level" "冻结堆栈:" +"dota_tooltip_ability_ability_crystal_maiden_crystal_nova_custom_slow_duration" "持续时间:" +"dota_tooltip_ability_ability_crystal_maiden_crystal_nova_custom_max_shield_pct" "护盾上限(占最大生命%):" +"dota_tooltip_ability_ability_crystal_maiden_crystal_nova_custom_shield_duration" "护盾持续时间:" + +"dota_tooltip_ability_ability_crystal_maiden_frostbite_custom" "冻伤" +"dota_tooltip_ability_ability_crystal_maiden_frostbite_custom_Description" "在%radius%范围内定身目标%duration%秒:敌人受周期性魔法伤害,盟友受治疗且所受伤害降低 %incoming_damage_pct%%% ;alt施法仅对敌人且伤害翻倍。生命低于%execute_threshold_pct%%%的敌人被处决,留下冰像可被击飞,在%explosion_radius%爆炸(每点施法者法力%damage_per_mana%伤害)。" +"dota_tooltip_ability_ability_crystal_maiden_frostbite_custom_Lore" "莱利的冰镣束缚着敌人,阻止他们移动,直到寒冷刺透他们的骨头。" +"dota_tooltip_ability_ability_crystal_maiden_frostbite_custom_damage" "每秒损坏次数:" +"dota_tooltip_ability_ability_crystal_maiden_frostbite_custom_heal_per_second" "一秒钟治愈:" +"dota_tooltip_ability_ability_crystal_maiden_frostbite_custom_incoming_damage_pct" "%友方所受伤害降低:" +"dota_tooltip_ability_ability_crystal_maiden_frostbite_custom_duration" "持续时间:" +"dota_tooltip_ability_ability_crystal_maiden_frostbite_custom_radius" "半径:" +"dota_tooltip_ability_ability_crystal_maiden_frostbite_custom_frost_stacks_per_level" "每秒冻结堆栈:" +"dota_tooltip_ability_ability_crystal_maiden_frostbite_custom_shard_Description" "强化了减伤效果。" +"dota_tooltip_ability_ability_crystal_maiden_frostbite_custom_execute_threshold_pct" "处决阈值(%):" +"dota_tooltip_ability_ability_crystal_maiden_frostbite_custom_explosion_radius" "冰像爆炸半径:" +"dota_tooltip_ability_ability_crystal_maiden_frostbite_custom_damage_per_mana" "爆炸每点法力伤害:" +"dota_tooltip_ability_ability_crystal_maiden_frostbite_custom_Note0" "替代种姓会使伤害加倍,并且仅适用于敌人。" + +"dota_tooltip_ability_ability_crystal_maiden_brilliance_aura_custom" "辉煌光环" +"dota_tooltip_ability_ability_crystal_maiden_brilliance_aura_custom_Description" "被动:%aura_radius%范围内盟友获得%mana_regen_pct%%%法力回复。主动:对%active_radius%内敌人造成魔法伤害并叠加冰霜;alt传送%teleport_range%距离。辉煌激活期间,每次施放技能生成雪花(最多%max_crystals%):造成%crystal_damage%伤害+敌人缺失法力%pct_25_mana_damage%%%,每次命中%crystal_frost_stacks%层冰霜,雪花存在%crystal_duration%秒。" +"dota_tooltip_ability_ability_crystal_maiden_brilliance_aura_custom_Lore" "莱利的魔法光环让盟友充满能量,使他们能够更频繁地运用自己的能力。" +"dota_tooltip_ability_ability_crystal_maiden_brilliance_aura_custom_mana_regen_pct" "%MANA再生:" +"dota_tooltip_ability_ability_crystal_maiden_brilliance_aura_custom_aura_radius" "天王星半径:" +"dota_tooltip_ability_ability_crystal_maiden_brilliance_aura_custom_active_radius" "主动效应半径:" +"dota_tooltip_ability_ability_crystal_maiden_brilliance_aura_custom_damage" "损坏:" +"dota_tooltip_ability_ability_crystal_maiden_brilliance_aura_custom_duration" "效果持续时间:" +"dota_tooltip_ability_ability_crystal_maiden_brilliance_aura_custom_frost_stacks_per_level" "冻结堆栈:" +"dota_tooltip_ability_ability_crystal_maiden_brilliance_aura_custom_intellect_per_damage" "智力损害:" +"dota_tooltip_ability_ability_crystal_maiden_brilliance_aura_custom_teleport_range" "传送范围:" +"dota_tooltip_ability_ability_crystal_maiden_brilliance_aura_custom_mana_cost_pct" "%法力成本:" +"dota_tooltip_ability_ability_crystal_maiden_brilliance_aura_custom_crystal_damage" "雪花伤害:" +"dota_tooltip_ability_ability_crystal_maiden_brilliance_aura_custom_pct_25_mana_damage" "%伤害(来自缺失法力):" +"dota_tooltip_ability_ability_crystal_maiden_brilliance_aura_custom_crystal_frost_stacks" "每次命中冰霜层数:" +"dota_tooltip_ability_ability_crystal_maiden_brilliance_aura_custom_max_crystals" "最大雪花数:" +"dota_tooltip_ability_ability_crystal_maiden_brilliance_aura_custom_crystal_duration" "雪花持续时间:" +"dota_tooltip_ability_ability_crystal_maiden_brilliance_aura_custom_Note0" "英雄失去法力后伤害会增加" +"dota_tooltip_ability_ability_crystal_maiden_brilliance_aura_custom_Note1" "在替代种姓中,英雄传送到指定点。" + +"dota_tooltip_ability_ability_crystal_maiden_freezing_field_custom" "冷冻场" +"dota_tooltip_ability_ability_crystal_maiden_freezing_field_custom_Description" "通道能力。在英雄周围半径为 %radius% 单位的范围内产生冰能爆炸,对敌人造成魔法伤害并施加冻结。伤害因英雄的智慧而增加。如果有权杖,就会定期铸造迷你版的水晶中篇小说。" +"dota_tooltip_ability_ability_crystal_maiden_freezing_field_custom_Lore" "最伟大的莱利咒语可以冻结整个战场,将敌人变成冰雕像。" +"dota_tooltip_ability_ability_crystal_maiden_freezing_field_custom_radius" "半径:" +"dota_tooltip_ability_ability_crystal_maiden_freezing_field_custom_explosion_radius" "爆炸半径:" +"dota_tooltip_ability_ability_crystal_maiden_freezing_field_custom_explosion_damage" "爆炸损坏:" +"dota_tooltip_ability_ability_crystal_maiden_freezing_field_custom_explosion_interval" "爆炸间隔:" +"dota_tooltip_ability_ability_crystal_maiden_freezing_field_custom_frost_stacks_per_explosion" "每次爆炸冻结堆栈:" +"dota_tooltip_ability_ability_crystal_maiden_freezing_field_custom_intellect_per_damage" "治疗/智力损害:" +"dota_tooltip_ability_ability_crystal_maiden_freezing_field_custom_slow_duration" "持续时间:" +"dota_tooltip_ability_ability_crystal_maiden_freezing_field_custom_Scepter_Description" "定期施放迷你版的水晶新星,当被击中时,会将冻伤挂在敌人或盟友身上,从而治愈盟友并对敌人造成伤害。驾驶时也可以使用该能力。" + + +//水晶少女修改器 +"dota_tooltip_modifier_crystal_maiden_frostbite_ally" "冻伤(ally)" +"dota_tooltip_modifier_crystal_maiden_frostbite_ally_Description" "被定身、持续治疗,且所受伤害降低。" + +"dota_tooltip_modifier_crystal_maiden_frostbite_enemy" "冻伤(敌人)" +"dota_tooltip_modifier_crystal_maiden_frostbite_enemy_Description" "固定下来并定期受到魔法伤害。还得到冷冻眼镜。" + +"dota_tooltip_modifier_crystal_maiden_brilliance_aura_buff" "被动光辉光环" +"dota_tooltip_modifier_crystal_maiden_brilliance_aura_buff_Description" "法力再生增加了%dMODIFIER_PROPERTY_MANA_REGEN_TOTAL_PERCENTAGE%%%。" + +"dota_tooltip_modifier_crystal_maiden_brilliance_crystals" "活跃的光环" +"dota_tooltip_modifier_crystal_maiden_brilliance_crystals_Description" "雪花旋转并攻击所有接近的敌人" + +"dota_tooltip_modifier_crystal_maiden_freezing_field_custom" "冻结字段" +"dota_tooltip_modifier_crystal_maiden_freezing_field_custom_Description" "在英雄周围产生冰能爆炸,伤害敌人并施加冻结。" + +//卓尔游侠 +"dota_tooltip_ability_ability_drow_ranger_frost_arrows_custom" "霜箭" +"dota_tooltip_ability_ability_drow_ranger_frost_arrows_custom_Description" "冰霜箭:额外伤害(攻击的%damage_pct%%%)与每次射击叠加寒冷。" +"dota_tooltip_ability_ability_drow_ranger_frost_arrows_custom_Scepter_Description" "%ricochet_chance%%%几率命中后弹射至500范围内另一敌人并叠加寒冷。" +"dota_tooltip_ability_ability_drow_ranger_frost_arrows_custom_damage_pct" "%额外伤害:" +"dota_tooltip_ability_ability_drow_ranger_frost_arrows_custom_frost_stacks_per_level" "寒冷层数:" +"dota_tooltip_ability_ability_drow_ranger_frost_arrows_custom_ricochet_chance" "%弹射几率(阿哈利姆):" +"dota_tooltip_ability_ability_drow_ranger_frost_arrows_custom_Lore" "冰箭 Traxex 由冰域最纯净的冰制成,可以冻结最温暖的心。" + +"dota_tooltip_ability_ability_drow_ranger_gust_custom" "Gust" +"dota_tooltip_ability_ability_drow_ranger_gust_custom_Description" "强风击退敌人、造成伤害并叠加寒冷。受伤害的敌人额外被冻结%frozen_son_duration%秒。" +"dota_tooltip_ability_ability_drow_ranger_gust_custom_gust_speed" "GUST SPEED:" +"dota_tooltip_ability_ability_drow_ranger_gust_custom_gust_width" "GUST WIDTH:" +"dota_tooltip_ability_ability_drow_ranger_gust_custom_gust_distance" "脉冲范围:" +"dota_tooltip_ability_ability_drow_ranger_gust_custom_knockback_distance" "排斥范围:" +"dota_tooltip_ability_ability_drow_ranger_gust_custom_knockback_duration" "排斥持续时间:" +"dota_tooltip_ability_ability_drow_ranger_gust_custom_damage" "损坏:" +"dota_tooltip_ability_ability_drow_ranger_gust_custom_frost_stacks_per_level" "寒冷层数:" +"dota_tooltip_ability_ability_drow_ranger_gust_custom_frozen_son_duration" "受伤后冻结时长:" +"dota_tooltip_ability_ability_drow_ranger_gust_custom_Lore" "冰河段的寒风教会了 Trucksex 控制元素,将它们变成对抗敌人的致命武器。" + +"dota_tooltip_ability_ability_drow_ranger_multishot_custom" "多点" +"dota_tooltip_ability_ability_drow_ranger_multishot_custom_Description" "引导1.75秒:多轮箭雨造成部分攻击伤害并叠加寒冷,箭矢穿透敌人。" +"dota_tooltip_ability_ability_drow_ranger_multishot_custom_arrow_count" "箭头数量:" +"dota_tooltip_ability_ability_drow_ranger_multishot_custom_arrow_damage_pct" "基础伤害的百分比份额:" +"dota_tooltip_ability_ability_drow_ranger_multishot_custom_arrow_width" "箭头宽度:" +"dota_tooltip_ability_ability_drow_ranger_multishot_custom_frost_stacks_per_level" "寒冷层数:" +"dota_tooltip_ability_ability_drow_ranger_multishot_custom_Lore" "在冰极限的恶劣条件下进行的多年训练使 Trucksex 能够以令人难以置信的速度和准确性射出许多箭。" + +"dota_tooltip_ability_ability_drow_ranger_marksmanship_custom" "射手天赋" +"dota_tooltip_ability_ability_drow_ranger_marksmanship_custom_Description" "当%disable_range%内无敌方英雄时获得%bonus_agility_pct%%%敏捷。每%hits%次成功攻击附加%bonus_damage%伤害;触发命中使目标短暂无视基础护甲。" +"dota_tooltip_ability_ability_drow_ranger_marksmanship_custom_bonus_damage" "触发额外伤害:" +"dota_tooltip_ability_ability_drow_ranger_marksmanship_custom_bonus_agility_pct" "%敏捷加成:" + +"dota_tooltip_ability_ability_drow_ranger_marksmanship_custom_disable_range" "失效半径:" +"dota_tooltip_ability_ability_drow_ranger_marksmanship_custom_hits" "触发间隔(攻击数):" +"dota_tooltip_ability_ability_drow_ranger_marksmanship_custom_Lore" "Traxex 无与伦比的射箭技巧使她成为世界上最危险的射手之一。" + + +//卓尔游侠天赋 +"dota_tooltip_ability_special_bonus_unique_drow_ranger_frost_arrow_damage_pct" "+{s:bonus_damage_pct}% 霜箭伤害" +"dota_tooltip_ability_special_bonus_unique_drow_ranger_arrow_damage_pct" "+{s:bonus_arrow_damage_pct}% 霜风暴害" +"dota_tooltip_ability_special_bonus_unique_drow_ranger_AbilityCooldown" "-{s:bonus_AbilityCooldown} 秒充电能力霜冻狂风。" +"dota_tooltip_ability_special_bonus_unique_drow_ranger_arrow_count_per_wave" "+{s:bonus_arrow_count_per_wave} 霜风暴中的箭头。" +"dota_tooltip_ability_special_bonus_unique_drow_ranger_gust_frozen_stack" "+{s:bonus_frost_stacks_per_level} 从阵风中冷却的眼镜。" + + +//莉娜 + +"dota_tooltip_ability_ability_lina_dragon_slave_custom" "Firelash" +"dota_tooltip_ability_ability_lina_dragon_slave_custom_Note0" "

Pasivnoe:Burn

当堆叠数为 0+ 时,主人开始从堆叠数 * 3 中每秒受到伤害。

当堆叠数为 70+ 时,效果主人会损失其盔甲和魔法师堆叠数的 25%。抵抗。

该效果不会获得冻伤,这就是 BURN 吸收冻伤效果的原因。" +"dota_tooltip_ability_ability_lina_dragon_slave_custom_Description" "莉娜释放火焰波伤害路径上的敌人,并附加相当于当前法力%mana_damage_from_current_pct%%%的额外伤害。攻击有%proc_chance%%%几率对当前目标自动施放龙破斩。" +"dota_tooltip_ability_ability_lina_dragon_slave_custom_Scepter_Description" "伤害增加莉娜智力的%damage_mult%%。" +"dota_tooltip_ability_ability_lina_dragon_slave_custom_proc_chance" "%攻击触发几率:" +"dota_tooltip_ability_ability_lina_dragon_slave_custom_dragon_slave_damage" "损坏:" +"dota_tooltip_ability_ability_lina_dragon_slave_custom_mana_damage_from_current_pct" "当前法力转额外伤害(%):" +"dota_tooltip_ability_ability_lina_dragon_slave_custom_fire_stacks_per_level" "燃烧:" +"dota_tooltip_ability_ability_lina_dragon_slave_custom_dragon_slave_distance" "范围:" +"dota_tooltip_ability_ability_lina_dragon_slave_custom_dragon_slave_width_initial" "初始宽度:" +"dota_tooltip_ability_ability_lina_dragon_slave_custom_dragon_slave_width_end" "最终宽度:" +"dota_tooltip_ability_ability_lina_dragon_slave_custom_Lore" "莉娜武器库中的第一个咒语,火风暴,会烧毁其路径上的一切。" + +"dota_tooltip_ability_ability_lina_light_strike_array_custom" "爆炸流" +"dota_tooltip_ability_ability_lina_light_strike_array_custom_Note0" "

Pasivnoe:Burn

当堆叠数为 0+ 时,主人开始因堆叠数 * 3 而每秒受到伤害。

当堆叠数为 70+ 时,效果主人会损失其盔甲和魔法师堆叠数的 25%。抵抗。

该效果不会产生冻伤,这就是 BURN 吸收冻伤效果的原因。" + +"dota_tooltip_ability_ability_lina_light_strike_array_custom_Description" "在区域召唤光击阵造成伤害与眩晕,并附加相当于当前法力%mana_damage_from_current_pct%%%的额外伤害。首次爆炸后,每高过1级的技能等级都会单独判定是否追加一次相同位置的爆炸(延迟与主施法相同)。" +"dota_tooltip_ability_ability_lina_light_strike_array_custom_shard_Description" "伤害随着莉娜的智力增加而增加" +"dota_tooltip_ability_ability_lina_light_strike_array_custom_light_strike_array_aoe" "半径:" +"dota_tooltip_ability_ability_lina_light_strike_array_custom_fire_stacks_per_level" "燃烧:" +"dota_tooltip_ability_ability_lina_light_strike_array_custom_light_strike_array_damage" "损坏:" +"dota_tooltip_ability_ability_lina_light_strike_array_custom_mana_damage_from_current_pct" "当前法力转额外伤害(%):" +"dota_tooltip_ability_ability_lina_light_strike_array_custom_light_strike_array_stun_duration" "令人惊叹的持续时间:" +"dota_tooltip_ability_ability_lina_light_strike_array_custom_Lore" "尽管莉娜喜欢烧死她的敌人,但她并不介意击晕他们。" + +"dota_tooltip_ability_ability_lina_flame_cloak_custom_Note0" "

Pasivnoe:Burn

当堆叠数为 0+ 时,主人开始从堆叠数 * 3 中每秒受到伤害。

当堆叠数为 70+ 时,效果主人会损失其盔甲和魔法师堆叠数的 25%。抵抗。

该效果不会获得冻伤,这就是 BURN 吸收冻伤效果的原因。" +"dota_tooltip_ability_ability_lina_flame_cloak_custom" "Firecloak" +"dota_tooltip_ability_ability_lina_flame_cloak_custom_Description" "烈焰斗篷:对范围内敌人造成伤害(每次伤害间隔附加当前法力%mana_damage_from_current_pct%%%)并叠加燃烧;被动层数提供移速与攻速,每层额外%spell_amplify%%%技能增强与%magical_resistance%%%魔抗。" +"dota_tooltip_ability_ability_lina_flame_cloak_custom_damage_per_second" "每秒损坏次数:" +"dota_tooltip_ability_ability_lina_flame_cloak_custom_mana_damage_from_current_pct" "每次伤害间隔附加当前法力(%):" +"dota_tooltip_ability_ability_lina_flame_cloak_custom_radius" "半径:" +"dota_tooltip_ability_ability_lina_flame_cloak_custom_fire_stacks_per_level" "燃烧:" +"dota_tooltip_ability_ability_lina_flame_cloak_custom_duration" "持续时间:" +"dota_tooltip_ability_ability_lina_flame_cloak_custom_attackspeed_bonus" "每堆攻击率百分比:" +"dota_tooltip_ability_ability_lina_flame_cloak_custom_movespeed_bonus" "每堆移动速度百分比:" +"dota_tooltip_ability_ability_lina_flame_cloak_custom_max_stacks" "最大堆栈:" +"dota_tooltip_ability_ability_lina_flame_cloak_custom_Lore" "莉娜用一件火焰斗篷包围自己,任何敢于靠近的人都会受到灼伤。" + +"dota_tooltip_ability_ability_lina_laguna_blade_custom" "泻湖之刃" +"dota_tooltip_ability_ability_lina_laguna_blade_custom_Description" "莉娜发射强大的雷击,对指定目标造成巨大伤害,并附加相当于当前法力%mana_damage_from_current_pct%%%的额外伤害。" +"dota_tooltip_ability_ability_lina_laguna_blade_custom_damage" "损坏:" +"dota_tooltip_ability_ability_lina_laguna_blade_custom_mana_damage_from_current_pct" "当前法力转额外伤害(%):" +"dota_tooltip_ability_ability_lina_laguna_blade_custom_Lore" "莉娜武器库中最强大的咒语——泻湖之刃,几乎可以焚烧任何敌人。" +"dota_tooltip_ability_ability_lina_laguna_blade_custom_Scepter_Description" "伤害变为纯粹并随智力提升。法力消耗按下方参数重算;施法后%duration%秒内,缺失法力转化为技能增强(每点缺失法力+1技能增强)。" + +"dota_tooltip_modifier_lina_flame_cloak_custom_passive_buff" "火焰斗篷" +"dota_tooltip_modifier_lina_flame_cloak_custom_passive_buff_Description" "英雄从火焰斗篷获得移动速度 %dMODIFIER_PROPERTY_MOVESPEED_BONUS_PERCENTAGE%%% 和 %dMODIFIER_PROPERTY_ATTACKSPEED_PERCENTAGE%%% 攻击速度的奖励。" + +"dota_tooltip_modifier_lina_laguna_blade_custom_buff" "拉古纳之刃" +"dota_tooltip_modifier_lina_laguna_blade_custom_buff_Description" "增加 %dMODIFIER_PROPERTY_SPELL_AMPLIFY_PERCENTAGE%%% 的法术增益。" + +//莉娜·塔兰特 +"dota_tooltip_ability_special_bonus_unique_lina_light_strike_array_five" "+{s:bonus_array_five} 热烈打击。" +"dota_tooltip_ability_special_bonus_unique_lina_laguna_blade_custom_cd" "-{s:bonus_AbilityCooldown} Lagoon Blade Recharge Sec" +"dota_tooltip_ability_special_bonus_unique_lina_flame_cloak_custom_damage_bonus" "+{s:bonus_damage_per_second} Firecloak 每秒的伤害。" +"dota_tooltip_ability_special_bonus_unique_lina_flame_cloak_custom_bonus_movespeed_attack_speed_pct" "+{s:bonus_movespeed_bonus}% 来自 Firecloak 的攻击速度和每个堆栈的移动。" + +"dota_tooltip_ability_ability_lina_flame_cloak_custom_spell_amplify" "每层技能增强(%):" +"dota_tooltip_ability_ability_lina_flame_cloak_custom_magical_resistance" "每层魔抗(%):" + +"dota_tooltip_ability_ability_lina_scorch_affinity_innate" "Scorch Affinity" +"dota_tooltip_ability_ability_lina_scorch_affinity_innate_Description" "半径%radius%内每个带有燃烧效果的单位使莉娜获得+%bonus_pct_base%%%额外输出伤害与等量所受伤害降低,且每名此类单位再额外获得莉娜每级%bonus_pct_per_hero_level%%%加成。" +"dota_tooltip_ability_ability_lina_scorch_affinity_innate_Lore" "周遭烈焰越盛,莉娜便越能从灼热中汲取力量,也更难被火焰所伤。" +"dota_tooltip_ability_ability_lina_scorch_affinity_innate_radius" "半径:" +"dota_tooltip_ability_ability_lina_scorch_affinity_innate_bonus_pct_base" "每名燃烧单位加成(%):" +"dota_tooltip_ability_ability_lina_scorch_affinity_innate_bonus_pct_per_hero_level" "每级英雄额外加成(%):" + +// keeper of the light +"DOTA_Tooltip_ability_keeper_of_the_light_illuminate_custom" "Illuminate" +"DOTA_Tooltip_ability_keeper_of_the_light_illuminate_custom_Description" "持续施法——蓄力后释放一道光波。路径上的敌人受到伤害,额外伤害等于释放时艾萨罗当前魔法值的%mana_damage_from_current_pct%%%,友军按伤害比例获得治疗。" +"DOTA_Tooltip_ability_keeper_of_the_light_illuminate_custom_Lore" "艾萨罗的光驱散黑暗,也为身旁的同伴带来希望。" +"DOTA_Tooltip_ability_keeper_of_the_light_illuminate_custom_heal_percent" "% 伤害转治疗:" +"DOTA_Tooltip_ability_keeper_of_the_light_illuminate_custom_max_damage" "最大伤害:" +"DOTA_Tooltip_ability_keeper_of_the_light_illuminate_custom_min_damage" "最小伤害:" +"DOTA_Tooltip_ability_keeper_of_the_light_illuminate_custom_radius" "宽度:" +"DOTA_Tooltip_ability_keeper_of_the_light_illuminate_custom_mana_damage_from_current_pct" "% 当前魔法值转为额外伤害:" + +"DOTA_Tooltip_ability_keeper_of_the_light_blinding_light_custom" "Blinding Light" +"DOTA_Tooltip_ability_keeper_of_the_light_blinding_light_custom_Description" "在目标区域制造闪光,击退并致盲敌人。被致盲期间攻击必定落空。" +"DOTA_Tooltip_ability_keeper_of_the_light_blinding_light_custom_Lore" "耀眼闪辉先灼烧敌人的意志,再让他们的攻击落空。" +"DOTA_Tooltip_ability_keeper_of_the_light_blinding_light_custom_miss_stack" "可闪避次数:" +"DOTA_Tooltip_ability_keeper_of_the_light_blinding_light_custom_miss_stack_boss" "对首领可闪避次数:" +"DOTA_Tooltip_ability_keeper_of_the_light_blinding_light_custom_duration_stack" "持续时间:" +"DOTA_Tooltip_ability_keeper_of_the_light_blinding_light_custom_radius" "半径:" +"DOTA_Tooltip_ability_keeper_of_the_light_blinding_light_custom_damage" "伤害:" + +"DOTA_Tooltip_ability_keeper_of_the_light_chakra_magic_custom" "Chakra Magic" +"DOTA_Tooltip_ability_keeper_of_the_light_chakra_magic_custom_Description" "为友军回复魔法值,减少其技能剩余冷却时间,并在短时间内降低其受到的物理与魔法伤害。" +"DOTA_Tooltip_ability_keeper_of_the_light_chakra_magic_custom_Lore" "艾萨罗分出一缕内在光辉,加速盟友的思绪与咒法流转。" +"DOTA_Tooltip_ability_keeper_of_the_light_chakra_magic_custom_mana_restore" "回复魔法:" +"DOTA_Tooltip_ability_keeper_of_the_light_chakra_magic_custom_cooldown_reduction" "冷却缩减:" +"DOTA_Tooltip_ability_keeper_of_the_light_chakra_magic_custom_incoming_damage_reduction_pct" "所受伤害降低:" +"DOTA_Tooltip_ability_keeper_of_the_light_chakra_magic_custom_damage_reduction_duration" "防护持续时间:" + +"DOTA_Tooltip_ability_keeper_of_the_light_recall_custom" "Recall" +"DOTA_Tooltip_ability_keeper_of_the_light_recall_custom_Description" "延迟后将目标传送到施法者身边。传送完成后双方获得移动速度加成。" +"DOTA_Tooltip_ability_keeper_of_the_light_recall_custom_Lore" "听见他召唤的人,终会被光带往最需要他们之处。" +"DOTA_Tooltip_ability_keeper_of_the_light_recall_custom_teleport_delay" "传送延迟:" +"DOTA_Tooltip_ability_keeper_of_the_light_recall_custom_ally_movespeed_pct" "% 移速加成:" + +"DOTA_Tooltip_ability_keeper_of_the_light_will_o_wisp_custom" "Will-O-Wisp" +"DOTA_Tooltip_ability_keeper_of_the_light_will_o_wisp_custom_Description" "在目标地点召唤灵火。每次激活都会对范围内敌人造成伤害,额外伤害等于艾萨罗当前魔法值的25%,并大幅减速。" +"DOTA_Tooltip_ability_keeper_of_the_light_will_o_wisp_custom_Lore" "古老灵火在战场上游曳,将敌人引向注定的毁灭。" +"DOTA_Tooltip_ability_keeper_of_the_light_will_o_wisp_custom_radius" "半径:" +"DOTA_Tooltip_ability_keeper_of_the_light_will_o_wisp_custom_slow_movespeed" "% 减速:" +"DOTA_Tooltip_ability_keeper_of_the_light_will_o_wisp_custom_hit_count" "激活次数:" +"DOTA_Tooltip_ability_keeper_of_the_light_will_o_wisp_custom_delay" "激活间隔:" +"DOTA_Tooltip_ability_keeper_of_the_light_will_o_wisp_custom_damage" "伤害:" + +"DOTA_Tooltip_ability_keeper_of_the_light_radiant_bind_custom" "Solar Bind" +"DOTA_Tooltip_ability_keeper_of_the_light_radiant_bind_custom_Description" "降低目标魔法抗性,并随其移动距离提高减速强度。对友军施放时效果反转,提供加速与魔抗。" +"DOTA_Tooltip_ability_keeper_of_the_light_radiant_bind_custom_Lore" "日光之缚对敌无情,对友却化作护佑。" +"DOTA_Tooltip_ability_keeper_of_the_light_radiant_bind_custom_duration" "持续时间:" +"DOTA_Tooltip_ability_keeper_of_the_light_radiant_bind_custom_slow_pct_per_distance" "% 每100距离减速:" +"DOTA_Tooltip_ability_keeper_of_the_light_radiant_bind_custom_magres_pct" "% 魔抗变化:" + +"DOTA_Tooltip_Ability_special_bonus_unique_keeper_of_the_light_4_1" "+{s:bonus_radius} 灵火半径" +"DOTA_Tooltip_Ability_special_bonus_unique_keeper_of_the_light_1_1" "+{s:bonus_heal_percent}% Illuminate 治疗" +"DOTA_Tooltip_Ability_special_bonus_unique_keeper_of_the_light_3_1" "Chakra Magic 额外恢复1点技能充能" +"DOTA_Tooltip_Ability_special_bonus_unique_keeper_of_the_light_4_2" "+{s:bonus_hit_count} 灵火激活次数" + +// silencer +"dota_tooltip_ability_silencer_curse_of_the_silent" "奥术诅咒" +"dota_tooltip_ability_silencer_curse_of_the_silent_Description" "诅咒范围内敌人,造成持续伤害并减速。该技能伤害会额外受到沉默术士智力加成。被诅咒目标每次施法都会延长诅咒持续时间。" +"dota_tooltip_ability_silencer_curse_of_the_silent_Lore" "沉默术士的诅咒同时蚕食言语与意志。" +"dota_tooltip_ability_silencer_curse_of_the_silent_damage" "每秒伤害:" +"dota_tooltip_ability_silencer_curse_of_the_silent_radius" "半径:" +"dota_tooltip_ability_silencer_curse_of_the_silent_duration" "持续时间:" +"dota_tooltip_ability_silencer_curse_of_the_silent_penalty_duration" "每次施法延长:" +"dota_tooltip_ability_silencer_curse_of_the_silent_movespeed" "% 减速:" + +"dota_tooltip_ability_glaives_of_wisdom" "智慧之刃" +"dota_tooltip_ability_glaives_of_wisdom_Description" "法球攻击,基于施法者智力造成额外纯粹伤害。" +"dota_tooltip_ability_glaives_of_wisdom_Lore" "每一柄飞刃,都是由理智写下的判决。" +"dota_tooltip_ability_glaives_of_wisdom_intellect_damage_pct" "% 智力转伤害:" + +"dota_tooltip_ability_razor_eye_of_the_storm_lua" "心灵风暴" +"dota_tooltip_ability_razor_eye_of_the_storm_lua_Description" "在施法者周围生成风暴,周期性打击附近敌人,造成物理伤害并降低护甲。风暴伤害会额外受到沉默术士智力加成。" +"dota_tooltip_ability_razor_eye_of_the_storm_lua_Scepter_Description" "阿哈利姆神杖会使风暴在每个间隔额外打击 %scepter_bonus_targets% 个目标。" +"dota_tooltip_ability_razor_eye_of_the_storm_lua_Lore" "无声风暴会一层层剥离护甲与勇气。" +"dota_tooltip_ability_razor_eye_of_the_storm_lua_radius" "半径:" +"dota_tooltip_ability_razor_eye_of_the_storm_lua_duration" "持续时间:" +"dota_tooltip_ability_razor_eye_of_the_storm_lua_strike_interval" "打击间隔:" +"dota_tooltip_ability_razor_eye_of_the_storm_lua_armor_reduction" "每次打击减甲:" +"dota_tooltip_ability_razor_eye_of_the_storm_lua_damage" "伤害:" + +"dota_tooltip_ability_int" "智力收集" +"dota_tooltip_ability_int_Description" "被动地随时间累积智力层数。" +"dota_tooltip_ability_int_grow_int" "每层智力:" +"dota_tooltip_ability_int_stack_interval" "层数间隔:" + +"dota_tooltip_ability_ability_last_word" "遗言" +"dota_tooltip_ability_ability_last_word_Description" "对目标施加延迟效果,触发后造成魔法伤害并使其沉默。" +"dota_tooltip_ability_ability_last_word_Shard_Description" "阿哈利姆魔晶会将 Last Word 的智力伤害系数提高 %shard_bonus_int%。" +"dota_tooltip_ability_ability_last_word_Lore" "最后一句话,属于让所有人闭嘴的那个人。" +"dota_tooltip_ability_ability_last_word_damage" "基础伤害:" +"dota_tooltip_ability_ability_last_word_debuff_duration" "触发延迟:" +"dota_tooltip_ability_ability_last_word_duration" "沉默持续时间:" +"dota_tooltip_ability_ability_last_word_int" "% 智力转伤害:" + +"dota_tooltip_ability_ability_global_silence" "全领域静默" +"dota_tooltip_ability_ability_global_silence_Description" "令地图上所有敌方单位陷入沉默,并提高其受到的伤害。" +"dota_tooltip_ability_ability_global_silence_Lore" "当世界寂静无声,只剩沉默术士的裁决。" +"dota_tooltip_ability_ability_global_silence_tooltip_duration" "持续时间:" +"dota_tooltip_ability_ability_global_silence_icnoming_enemy" "% 承受伤害提升:" + +"dota_tooltip_ability_special_bonus_unique_silencer_storm_interval" "-{s:bonus_strike_interval} 心灵风暴打击间隔" +"dota_tooltip_ability_special_bonus_unique_silencer_grow_int" "+{s:bonus_grow_int} 智力收集每层智力" +"dota_tooltip_ability_special_bonus_unique_silencer_storm_duration" "+{s:bonus_duration} 秒心灵风暴持续时间" +"dota_tooltip_ability_special_bonus_unique_silencer_intellect_damage_pct" "+{s:bonus_intellect_damage_pct}% 智慧之刃智力伤害" +"dota_tooltip_ability_special_bonus_unique_silencer_glaives_bounces" "+{s:bonus_bounce_count} 智慧之刃弹射次数" +"dota_tooltip_ability_special_bonus_unique_silencer" "+{s:bonus_damage} 奥术诅咒伤害" +"dota_tooltip_ability_special_bonus_unique_silencer_stack_interval" "-{s:bonus_stack_interval} 秒智力收集叠层间隔" + +// nevermore +"dota_tooltip_ability_nevermore_shadowraze1_custom" "毁灭阴影" +"dota_tooltip_ability_nevermore_shadowraze1_custom_Description" "影魔在前方区域释放毁灭能量,对范围内敌人造成伤害(英雄攻击力 + 当前魔法值)。每次命中都会叠加效果,强化后续毁灭阴影并额外减速目标。" +"dota_tooltip_ability_nevermore_shadowraze1_custom_shadowraze_range" "施法距离:" +"dota_tooltip_ability_nevermore_shadowraze1_custom_stack_bonus_damage" "每层额外伤害:" +"dota_tooltip_ability_nevermore_shadowraze1_custom_duration" "效果持续时间:" +"dota_tooltip_ability_nevermore_shadowraze1_custom_movement_speed_debuff" "% 每层减速:" +"dota_tooltip_ability_nevermore_shadowraze1_custom_Lore" "影之刻痕一旦落下,便不会只停留一次。" + +"dota_tooltip_ability_nevermore_shadowraze2_custom" "毁灭阴影" +"dota_tooltip_ability_nevermore_shadowraze2_custom_Description" "影魔在前方区域释放毁灭能量,对范围内敌人造成伤害(英雄攻击力 + 当前魔法值)。每次命中都会叠加效果,强化后续毁灭阴影并额外减速目标。" +"dota_tooltip_ability_nevermore_shadowraze2_custom_shadowraze_range" "施法距离:" +"dota_tooltip_ability_nevermore_shadowraze2_custom_stack_bonus_damage" "每层额外伤害:" +"dota_tooltip_ability_nevermore_shadowraze2_custom_duration" "效果持续时间:" +"dota_tooltip_ability_nevermore_shadowraze2_custom_movement_speed_debuff" "% 每层减速:" +"dota_tooltip_ability_nevermore_shadowraze2_custom_Lore" "第二道阴影落下时,逃跑只会显得更可笑。" + +"dota_tooltip_ability_nevermore_shadowraze3_custom" "毁灭阴影" +"dota_tooltip_ability_nevermore_shadowraze3_custom_Description" "影魔在前方区域释放毁灭能量,对范围内敌人造成伤害(英雄攻击力 + 当前魔法值)。每次命中都会叠加效果,强化后续毁灭阴影并额外减速目标。" +"dota_tooltip_ability_nevermore_shadowraze3_custom_shadowraze_range" "施法距离:" +"dota_tooltip_ability_nevermore_shadowraze3_custom_stack_bonus_damage" "每层额外伤害:" +"dota_tooltip_ability_nevermore_shadowraze3_custom_duration" "效果持续时间:" +"dota_tooltip_ability_nevermore_shadowraze3_custom_movement_speed_debuff" "% 每层减速:" +"dota_tooltip_ability_nevermore_shadowraze3_custom_Lore" "无论你站得多远,灵魂终会被他触及。" + +"dota_tooltip_ability_nevermore_necromastery_custom" "支配死灵" +"dota_tooltip_ability_nevermore_necromastery_custom_Description" "被动:影魔每拥有 1 层灵魂获得 %necromastery_damage_per_soul% 点额外攻击力,基础上限为 %necromastery_max_souls% 层(未计入灵魂溢出)。每击杀一名敌方单位获得 %souls_per_kill% 层灵魂,不超过当前上限。被动被破坏时,额外攻击力不生效。\n\n死亡时,影魔失去 %necromastery_soul_pct_release%%% 的当前灵魂(其余保留)。若已学会魂之挽歌且未被禁用,将自动施放。\n\n主动—不消耗魔法,冷却 %AbilityCooldown% 秒:当灵魂达到基础上限时,可施放灵魂溢出,持续 %active_duration% 秒,灵魂上限额外提高 %active_bonus_soul_cap% 点。" +"dota_tooltip_ability_nevermore_necromastery_custom_necromastery_max_souls" "最大灵魂数:" +"dota_tooltip_ability_nevermore_necromastery_custom_necromastery_damage_per_soul" "每个灵魂攻击力:" +"dota_tooltip_ability_nevermore_necromastery_custom_necromastery_soul_pct_release" "% 死亡时失去的灵魂:" +"dota_tooltip_ability_nevermore_necromastery_custom_souls_per_kill" "每次击杀获得灵魂:" +"dota_tooltip_ability_nevermore_necromastery_custom_active_duration" "溢出持续时间:" +"dota_tooltip_ability_nevermore_necromastery_custom_active_bonus_soul_cap" "额外灵魂上限:" +"dota_tooltip_ability_nevermore_necromastery_custom_Lore" "每一缕被夺取的灵魂,都会让他的饥渴更深一层。" + +"dota_tooltip_ability_nevermore_dark_lord_custom" "魔王降临" +"dota_tooltip_ability_nevermore_dark_lord_custom_Description" "影魔散发光环,按灵魂数量降低附近敌人的护甲。拥有魔晶后,光环内友军(不包括影魔自己)将获得额外护甲。" +"dota_tooltip_ability_nevermore_dark_lord_custom_Shard_Description" "还会为光环内友方英雄提供额外护甲,但不作用于影魔自身。" +"dota_tooltip_ability_nevermore_dark_lord_custom_armor_reduction_per_soul" "每个灵魂护甲降低:" +"dota_tooltip_ability_nevermore_dark_lord_custom_presence_radius" "范围:" +"dota_tooltip_ability_nevermore_dark_lord_custom_shard_ally_armor_bonus" "魔晶友军护甲加成:" +"dota_tooltip_ability_nevermore_dark_lord_custom_Lore" "仅仅靠近他,钢铁与意志都会被一同压垮。" + +"dota_tooltip_ability_nevermore_deadly_strike_custom" "致命一击" +"dota_tooltip_ability_nevermore_deadly_strike_custom_Description" "每次攻击命中后,影魔有概率强化下一次攻击为致命暴击。暴击伤害随当前灵魂数量提升。" +"dota_tooltip_ability_nevermore_deadly_strike_custom_deadly_strike_chance" "触发几率:" +"dota_tooltip_ability_nevermore_deadly_strike_custom_deadly_strike_crit_per_soul" "每个灵魂暴击伤害:" +"dota_tooltip_ability_nevermore_deadly_strike_custom_Lore" "他准备好的那一击,会在痛觉到来前先夺走生命。" + +"dota_tooltip_ability_nevermore_requiem_custom" "魂之挽歌" +"dota_tooltip_ability_nevermore_requiem_custom_Description" "影魔释放 %requiem_soul_pct_release%%% 已收集灵魂,形成能量波。每道波会造成伤害并施加恐惧与削弱。影魔死亡时该技能会自动触发。" +"dota_tooltip_ability_nevermore_requiem_custom_Scepter_Description" "每道命中敌人的灵魂波会返回影魔,并再次造成该波 %requiem_damage_pct_scepter%%% 的伤害。" +"dota_tooltip_ability_nevermore_requiem_custom_damage" "伤害:" +"dota_tooltip_ability_nevermore_requiem_custom_requiem_radius" "半径:" +"dota_tooltip_ability_nevermore_requiem_custom_requiem_reduction_ms" "% 移速降低:" +"dota_tooltip_ability_nevermore_requiem_custom_requiem_reduction_mres" "% 魔抗降低:" +"dota_tooltip_ability_nevermore_requiem_custom_requiem_slow_duration" "每道波持续时间:" +"dota_tooltip_ability_nevermore_requiem_custom_requiem_slow_duration_max" "最大持续时间:" +"dota_tooltip_ability_nevermore_requiem_custom_Lore" "当他的挽歌响起,战场上只剩亡者的回声。" + +"dota_tooltip_ability_special_bonus_unique_nevermore_custom_1" "+$attack_pct 致命一击触发几率" +"dota_tooltip_ability_special_bonus_unique_nevermore_custom_2" "+$attack_pct 每个灵魂致命一击暴击伤害" +"dota_tooltip_ability_special_bonus_unique_nevermore_custom_3" "+$all 灵魂挽歌最大灵魂数" +"dota_tooltip_ability_special_bonus_unique_nevermore_custom_4" "+$damage 每个灵魂攻击力" +"dota_tooltip_ability_special_bonus_unique_nevermore_custom_5" "+$armor 每灵魂护甲降低" +"dota_tooltip_ability_special_bonus_unique_nevermore_custom_6" "+$aoe_bonus 光环范围" +"dota_tooltip_ability_special_bonus_unique_nevermore_custom_7" "+$damage 魂之挽歌伤害" +"dota_tooltip_ability_special_bonus_unique_nevermore_custom_8" "+$damage 毁灭阴影每层额外伤害" + +// Troll Warlord +"dota_tooltip_ability_troll_warlord_switch_stance_custom" "狂战士之怒" +"dota_tooltip_ability_troll_warlord_switch_stance_custom_Description" "切换近战姿态:改变攻击距离、攻击间隔、护甲与移动速度。远程攻击有 %chance_ensnare%%% 几率射出束缚网(内置冷却 %cooldown_ensnare% 秒)。" +"dota_tooltip_ability_troll_warlord_switch_stance_custom_Lore" "怒火也是一种节奏,只是更吵。" +"dota_tooltip_ability_troll_warlord_switch_stance_custom_bonus_armor" "护甲加成:" +"dota_tooltip_ability_troll_warlord_switch_stance_custom_bonus_move_speed" "移速加成:" +"dota_tooltip_ability_troll_warlord_switch_stance_custom_base_attack_time" "攻击间隔:" +"dota_tooltip_ability_troll_warlord_switch_stance_custom_chance_ensnare" "% 束缚几率(远程):" +"dota_tooltip_ability_troll_warlord_switch_stance_custom_cooldown_ensnare" "束缚内置冷却:" +"dota_tooltip_ability_troll_warlord_switch_stance_custom_duration_ensnare" "禁锢持续时间:" +"dota_tooltip_ability_troll_warlord_switch_stance_custom_ensnare_speed" "飞网速度:" +"dota_tooltip_ability_troll_warlord_switch_stance_custom_split_radius" "分裂搜索范围:" +"dota_tooltip_ability_troll_warlord_switch_stance_custom_max_split_attack" "最大分裂攻击次数:" +"dota_tooltip_ability_troll_warlord_switch_stance_custom_Shard_Description" "远程攻击可打击主目标附近的多个敌人。" + +"dota_tooltip_ability_troll_warlord_active_axes_ranged" "旋风飞斧(远程)" +"dota_tooltip_ability_troll_warlord_active_axes_ranged_Description" "向选定方向掷出飞斧扇面,造成魔法伤害并附加基于施法者攻击力的额外伤害(%attack_damage_pct%%%),并减速敌人,持续 %axe_slow_duration% 秒。" +"dota_tooltip_ability_troll_warlord_active_axes_ranged_Lore" "斧子飞向手臂所指之处。" +"dota_tooltip_ability_troll_warlord_active_axes_ranged_axe_damage" "伤害:" +"dota_tooltip_ability_troll_warlord_active_axes_ranged_attack_damage_pct" "% 施法者攻击力转化额外伤害:" +"dota_tooltip_ability_troll_warlord_active_axes_ranged_axe_slow_duration" "减速持续时间:" +"dota_tooltip_ability_troll_warlord_active_axes_ranged_movement_speed" "% 减速:" +"dota_tooltip_ability_troll_warlord_active_axes_ranged_axe_width" "宽度:" +"dota_tooltip_ability_troll_warlord_active_axes_ranged_axe_range" "飞行距离:" + +"dota_tooltip_ability_troll_warlord_active_axes_melee" "旋风飞斧(近战)" +"dota_tooltip_ability_troll_warlord_active_axes_melee_Description" "飞斧环绕施法者旋转,造成魔法伤害并附加基于施法者攻击力的额外伤害(%attack_damage_pct%%%),并缴械敌人。" +"dota_tooltip_ability_troll_warlord_active_axes_melee_Lore" "近身圈是别人的危险区。" +"dota_tooltip_ability_troll_warlord_active_axes_melee_damage" "伤害:" +"dota_tooltip_ability_troll_warlord_active_axes_melee_attack_damage_pct" "% 施法者攻击力转化额外伤害:" +"dota_tooltip_ability_troll_warlord_active_axes_melee_debuff_duration" "缴械持续时间:" +"dota_tooltip_ability_troll_warlord_active_axes_melee_hit_radius" "命中半径:" +"dota_tooltip_ability_troll_warlord_active_axes_melee_max_range" "最大旋转半径:" + +"dota_tooltip_ability_troll_warlord_fervor_custom" "热血战魂" +"dota_tooltip_ability_troll_warlord_fervor_custom_Description" "每次攻击命中叠加攻击速度(最多 %max_stacks% 层),任意目标均可。持续有命中则层数保留;若 %stack_linger_duration% 秒内未命中则层数清零。" +"dota_tooltip_ability_troll_warlord_fervor_custom_Lore" "一个敌人,一种节拍。" +"dota_tooltip_ability_troll_warlord_fervor_custom_attack_speed" "每层攻击速度:" +"dota_tooltip_ability_troll_warlord_fervor_custom_max_stacks" "最大层数:" +"dota_tooltip_ability_troll_warlord_fervor_custom_stack_linger_duration" "无命中重置层数的时间:" +"dota_tooltip_ability_troll_warlord_fervor_custom_locked_attack_speed" "攻击速度阈值:" +"dota_tooltip_ability_troll_warlord_fervor_custom_pct_damage_per_attack_speed" "% 超出阈值每点攻速的伤害:" +"dota_tooltip_ability_troll_warlord_fervor_custom_Scepter_Description" "超过阈值的攻击速度提升攻击伤害。" + +"dota_tooltip_ability_troll_warlord_battle_trance_custom" "战斗专注" +"dota_tooltip_ability_troll_warlord_battle_trance_custom_Description" "巨魔战将进入战斗专注状态,持续 %trance_duration% 秒:提升攻击速度、移动速度,并获得 %lifesteal%%% 攻击吸血。" +"dota_tooltip_ability_troll_warlord_battle_trance_custom_Lore" "让敌人的血为下一步买单。" +"dota_tooltip_ability_troll_warlord_battle_trance_custom_trance_duration" "持续时间:" +"dota_tooltip_ability_troll_warlord_battle_trance_custom_lifesteal" "% 吸血:" +"dota_tooltip_ability_troll_warlord_battle_trance_custom_attack_speed" "攻击速度:" +"dota_tooltip_ability_troll_warlord_battle_trance_custom_movement_speed" "移速加成:" + +"dota_tooltip_ability_special_bonus_unique_troll_warlord_1_1" "+{s:bonus_bonus_move_speed} 狂战士之怒移速加成" +"dota_tooltip_ability_special_bonus_unique_troll_warlord_1_2" "+{s:bonus_base_attack_time} 秒 狂战士之怒攻击间隔修正" +"dota_tooltip_ability_special_bonus_unique_troll_warlord_2_1" "+{s:bonus_axe_slow_duration}% 旋风飞斧减速/缴械持续时间" +"dota_tooltip_ability_special_bonus_unique_troll_warlord_4_1" "-{s:bonus_AbilityCooldown} 秒 战斗专注冷却" +"dota_tooltip_ability_special_bonus_unique_troll_warlord_1_3" "+{s:bonus_bonus_armor} 狂战士之怒护甲" +"dota_tooltip_ability_special_bonus_unique_troll_warlord_3" "提升旋风飞斧(远程与近战)的魔法伤害。" +"dota_tooltip_ability_special_bonus_unique_troll_warlord_5" "+{s:bonus_attack_speed} 热血战魂每层攻击速度" +"dota_tooltip_ability_special_bonus_unique_troll_warlord_1_4" "+{s:bonus_chance_ensnare}% 远程束缚几率" +"dota_tooltip_ability_special_bonus_unique_troll_warlord_battle_trance_movespeed" "+{s:bonus_movement_speed} 战斗专注移速加成" + +// Ogre Magi +"dota_tooltip_ability_ability_ogre_magi_fireblast_custom" "火焰爆轰" +"dota_tooltip_ability_ability_ogre_magi_fireblast_custom_Description" "在目标地点引发火焰爆炸:%blast_radius% 范围内敌人受到 %fireblast_damage% 魔法伤害 + 食人魔最大生命值 %max_health_damage_pct%%% 并眩晕 %stun_duration% 秒。可触发多重施法。" +"dota_tooltip_ability_ability_ogre_magi_fireblast_custom_fireblast_damage" "伤害:" +"dota_tooltip_ability_ability_ogre_magi_fireblast_custom_max_health_damage_pct" "食人魔最大生命 %:" +"dota_tooltip_ability_ability_ogre_magi_fireblast_custom_stun_duration" "眩晕:" +"dota_tooltip_ability_ability_ogre_magi_fireblast_custom_blast_radius" "爆炸半径:" +"dota_tooltip_ability_ability_ogre_magi_fireblast_custom_Lore" "两把火合成一声轰,第三把火叫多重施法。" + +"dota_tooltip_ability_ability_ogre_magi_ignite_custom" "引燃" +"dota_tooltip_ability_ability_ogre_magi_ignite_custom_Description" "投掷燃烧黏液:点燃目标及 %ignite_radius% 范围内敌人 %duration% 秒,每秒造成 %burn_damage% 魔法伤害 + 食人魔最大生命值 %max_health_damage_pct%%% 并减速 %slow_movement_speed_pct%%%。可触发多重施法。" +"dota_tooltip_ability_ability_ogre_magi_ignite_custom_duration" "持续时间:" +"dota_tooltip_ability_ability_ogre_magi_ignite_custom_burn_damage" "每秒伤害:" +"dota_tooltip_ability_ability_ogre_magi_ignite_custom_max_health_damage_pct" "每秒食人魔最大生命 %:" +"dota_tooltip_ability_ability_ogre_magi_ignite_custom_ignite_radius" "溅射半径:" +"dota_tooltip_ability_ability_ogre_magi_ignite_custom_Lore" "鼻涕烧得比骄傲更久。" + +"dota_tooltip_ability_ability_ogre_magi_bloodlust_custom" "嗜血术" +"dota_tooltip_ability_ability_ogre_magi_bloodlust_custom_Description" "%duration% 秒内强化友方:+%bonus_attack_speed% 攻击速度、+%bonus_movement_speed%%% 移动速度;对自身额外 +%self_bonus% 攻击速度。自动施法会对最近未获得效果的友方施放。可触发多重施法。" +"dota_tooltip_ability_ability_ogre_magi_bloodlust_custom_bonus_attack_speed" "友方攻击速度:" +"dota_tooltip_ability_ability_ogre_magi_bloodlust_custom_self_bonus" "自身攻击速度:" +"dota_tooltip_ability_ability_ogre_magi_bloodlust_custom_bonus_movement_speed" "移动速度:" +"dota_tooltip_ability_ability_ogre_magi_bloodlust_custom_Lore" "食人魔一欢呼,大家都打得更快。" + +"dota_tooltip_ability_ability_ogre_magi_multicast_custom" "多重施法" +"dota_tooltip_ability_ability_ogre_magi_multicast_custom_Description" "被动:食人魔法术可能重复——%chance_2x%%% 双倍,2 级另加 %chance_3x%%% 三倍,3 级 %chance_4x%%% 四倍。受幸运影响。可被衰竭禁用。" +"dota_tooltip_ability_ability_ogre_magi_multicast_custom_chance_2x" "×2 几率:" +"dota_tooltip_ability_ability_ogre_magi_multicast_custom_chance_3x" "×3 几率:" +"dota_tooltip_ability_ability_ogre_magi_multicast_custom_chance_4x" "×4 几率:" +"dota_tooltip_ability_ability_ogre_magi_multicast_custom_Lore" "有时法术连发太多,连食人魔自己都吃惊。" +"dota_tooltip_ability_ability_ogre_magi_multicast_custom_Shard_Description" "主动:为 %shard_radius% 范围内友方施加火焰护盾,使其技能也可多重施法,持续 %shard_duration% 秒。" + +"dota_tooltip_ability_ability_ogre_magi_unrefined_fireblast_custom" "未精炼火焰爆轰" +"dota_tooltip_ability_ability_ogre_magi_unrefined_fireblast_custom_Description" "在目标地点引发火焰爆炸(同火焰爆轰):%blast_radius% 范围内 %blast_damage% 魔法伤害 + 食人魔最大生命值 %max_health_damage_pct%%% 并眩晕 %stun_duration% 秒。消耗当前 %mana_cost_pct%%% 法力。" +"dota_tooltip_ability_ability_ogre_magi_unrefined_fireblast_custom_blast_radius" "爆炸半径:" +"dota_tooltip_ability_ability_ogre_magi_unrefined_fireblast_custom_max_health_damage_pct" "食人魔最大生命 %:" +"dota_tooltip_ability_ability_ogre_magi_unrefined_fireblast_custom_blast_damage" "伤害:" +"dota_tooltip_ability_ability_ogre_magi_unrefined_fireblast_custom_mana_cost_pct" "法力消耗:" +"dota_tooltip_ability_ability_ogre_magi_unrefined_fireblast_custom_Lore" "法力越满,轰得越狠。" + +"dota_tooltip_ability_ability_ogre_magi_innate_custom" "愚者幸运" +"dota_tooltip_ability_ability_ogre_magi_innate_custom_Description" "先天:每级 +%luck_per_level% 幸运。可被衰竭禁用。" +"dota_tooltip_ability_ability_ogre_magi_innate_custom_luck_per_level" "每级幸运:" +"dota_tooltip_ability_ability_ogre_magi_innate_custom_Lore" "愚钝与幸运是近亲。" + +"dota_tooltip_modifier_modifier_ogre_magi_ignite_debuff" "引燃" +"dota_tooltip_modifier_modifier_ogre_magi_ignite_debuff_Description" "持续伤害与减速,层数提高伤害。" + +"dota_tooltip_modifier_modifier_ogre_magi_bloodlust_buff" "嗜血术" +"dota_tooltip_modifier_modifier_ogre_magi_bloodlust_buff_Description" "提升攻击与移动速度。" + +"dota_tooltip_modifier_modifier_ogre_magi_multicast_stack" "火焰护盾" +"dota_tooltip_modifier_modifier_ogre_magi_multicast_stack_Description" "接下来的技能施放可能触发多重施法。" + +"dota_tooltip_ability_special_bonus_unique_ogre_magi_fireblast_damage" "+{s:bonus_fireblast_damage} 火焰爆轰伤害" +"dota_tooltip_ability_special_bonus_unique_ogre_magi_fireblast_attack_proc" "{s:bonus_attack_proc_chance}% 几率(受幸运影响):攻击时在身前施放火焰爆轰" +"dota_tooltip_ability_special_bonus_unique_ogre_magi_ignite_damage" "+{s:bonus_burn_damage} 引燃每秒伤害" +"dota_tooltip_ability_special_bonus_unique_ogre_magi_bloodlust_as" "嗜血术 +{s:bonus_bonus_attack_speed} 攻击速度,+{s:bonus_physical_vampirism} 物理吸血" +"dota_tooltip_ability_special_bonus_unique_ogre_magi_ignite_stacks" "引燃层数提高燃烧伤害" +"dota_tooltip_ability_special_bonus_unique_ogre_magi_ignite_on_cast" "任意施法 20% 几率对最近敌人发射引燃" +"dota_tooltip_ability_special_bonus_unique_ogre_magi_ignite_proc_damage" "对引燃目标物理攻击伤害 +30%" +"dota_tooltip_ability_special_bonus_unique_ogre_magi_luck" "+{s:bonus_luck_per_level} 每级幸运(先天)" + +// -----------------Spectre-------------------- +"dota_tooltip_ability_ability_spectre_spectral_dagger_custom" "幽魂之刃" +"dota_tooltip_ability_ability_spectre_spectral_dagger_custom_Description" "向地点或敌人掷出匕首:%damage% 魔法伤害 + 幽鬼最大生命 %health_cost_pct%%% ,减速 %slow_pct%%% ,轨迹持续 %buff_persistence% 秒。幽鬼获得 +%bonus_movespeed%%% 移速。" +"dota_tooltip_ability_ability_spectre_spectral_dagger_custom_damage" "伤害:" +"dota_tooltip_ability_ability_spectre_spectral_dagger_custom_health_cost_pct" "最大生命百分比:" +"dota_tooltip_ability_ability_spectre_spectral_dagger_custom_slow_pct" "减速:" +"dota_tooltip_ability_ability_spectre_spectral_dagger_custom_bonus_movespeed" "移速(自身):" +"dota_tooltip_ability_ability_spectre_spectral_dagger_custom_buff_persistence" "轨迹持续:" +"dota_tooltip_ability_ability_spectre_spectral_dagger_custom_Lore" "影子切开血肉,也切开归途。" + +"dota_tooltip_ability_ability_spectre_spectral_echo_custom" "幽影回响" +"dota_tooltip_ability_ability_spectre_spectral_echo_custom_Description" "被动:攻击时 %proc_chance%%% 几率(受幸运影响)在目标附近随机位置生成 %shadows_per_proc% 道无敌黑色之影,攻击 %shadow_attacks% 次后消失;附近无敌人时跟随幽鬼直至在 %search_radius% 内发现目标。同时最多 %shadows_per_proc% 道。伤害 %illusion_outgoing_damage%%%。" +"dota_tooltip_ability_ability_spectre_spectral_echo_custom_shadow_attacks" "消失前攻击次数:" +"dota_tooltip_ability_ability_spectre_spectral_echo_custom_proc_chance" "触发几率:" +"dota_tooltip_ability_ability_spectre_spectral_echo_custom_shadows_per_proc" "每次触发之影数:" +"dota_tooltip_ability_ability_spectre_spectral_echo_custom_spawn_radius_near_target" "目标旁生成半径:" +"dota_tooltip_ability_special_bonus_unique_spectre_dagger_cooldown" "幽魂之刃冷却 -{s:bonus_AbilityCooldown} 秒" +"dota_tooltip_ability_special_bonus_unique_spectre_echo_proc_chance" "Spectral Echo 几率 +{s:bonus_proc_chance}%" +"dota_tooltip_ability_special_bonus_unique_spectre_spectral_echo_twin" "Spectral Echo 每次 +{s:bonus_shadows_per_proc} 道之影(共 2 道)" +"dota_tooltip_ability_special_bonus_unique_spectre_echo_double_strike" "Spectral Echo 之影多 +{s:bonus_shadow_attacks} 次攻击(共 2 次)" +"dota_tooltip_ability_special_bonus_unique_spectre_dispersion_pct" "折射 +{s:bonus_damage_reflection_pct}%" +"dota_tooltip_ability_special_bonus_unique_spectre_desolate_hp_pct" "荒芜最大生命伤害 +{s:bonus_health_damage_pct}%" +"dota_tooltip_ability_special_bonus_unique_spectre_echo_shadow_damage" "Spectral Echo 之影伤害 +{s:bonus_illusion_outgoing_damage}%" +"dota_tooltip_ability_special_bonus_unique_spectre_reality_all_haunts" "降临:每个鬼影重重幻象各放一道幽魂之刃,再与最近幻象换位" +"dota_tooltip_ability_ability_spectre_spectral_echo_custom_search_radius" "寻敌半径:" +"dota_tooltip_ability_ability_spectre_spectral_echo_custom_illusion_outgoing_damage" "幻象伤害:" +"dota_tooltip_ability_ability_spectre_spectral_echo_custom_illusion_attack_speed" "幻象攻速:" +"dota_tooltip_ability_ability_spectre_spectral_echo_custom_Lore" "每一击都会在影中回响——回响也会挥刃。" + +"dota_tooltip_ability_ability_spectre_desolate_custom" "荒芜" +"dota_tooltip_ability_ability_spectre_desolate_custom_Description" "先天:每次攻击造成额外纯粹伤害,数值为幽鬼最大生命的 %health_damage_pct%%%(1 级 1%%,每级 +0.1%%)。" +"dota_tooltip_ability_ability_spectre_desolate_custom_health_damage_pct" "最大生命转伤害:" +"dota_tooltip_ability_ability_spectre_desolate_custom_Lore" "影子以宿主体内的力量出击。" + +"dota_tooltip_ability_ability_spectre_dispersion_custom" "折射" +"dota_tooltip_ability_ability_spectre_dispersion_custom_Description" "被动:减少 %damage_reflection_pct%%% 所受伤害,并将部分伤害反射给 %max_radius% 内敌人(近距离更强,%min_radius% 内最大)。匕首轨迹目标反射更强。" +"dota_tooltip_ability_ability_spectre_dispersion_custom_damage_reflection_pct" "所受伤害减免:" +"dota_tooltip_ability_ability_spectre_dispersion_custom_min_radius" "最大反射距离:" +"dota_tooltip_ability_ability_spectre_dispersion_custom_max_radius" "反射范围:" +"dota_tooltip_ability_ability_spectre_dispersion_custom_Lore" "痛苦会分给所有敢靠近的人。" +"dota_tooltip_ability_ability_spectre_dispersion_custom_Shard_Description" "主动:%activation_duration% 秒内强化折射,反射 +%activation_bonus_pct%%%。冷却 %activation_cooldown% 秒,消耗 %activation_manacost% 魔法。" + +"dota_tooltip_ability_ability_spectre_haunt_custom" "鬼影重重" +"dota_tooltip_ability_ability_spectre_haunt_custom_Description" "创造 %illusion_count% 个幽鬼幻象,持续 %duration% 秒(%illusion_damage_outgoing%%% 伤害)。其攻击可触发 Spectral Echo。降临与离你最近的幻象换位并对最近敌人自动施放幽魂之刃。" +"dota_tooltip_ability_ability_spectre_haunt_custom_illusion_count" "幻象数量:" +"dota_tooltip_ability_ability_spectre_haunt_custom_duration" "持续时间:" +"dota_tooltip_ability_ability_spectre_haunt_custom_Lore" "影子走出身体,等待一击。" + +"dota_tooltip_ability_ability_spectre_reality_custom" "降临" +"dota_tooltip_ability_ability_spectre_reality_custom_Description" "与鬼影重重幻象换位,并对匕首施法范围内最近敌人施放幽魂之刃。" + +"dota_tooltip_ability_ability_spectre_shadow_step_custom" "暗影步" +"dota_tooltip_ability_ability_spectre_shadow_step_custom_Description" "阿哈利姆神杖:每次攻击 %duration_steal% 秒内从目标偷取 %health_steal% 最大生命,幽鬼每层获得 %health_bonus_self%(幻象效果降低 %illusion_decrease%%%)。" +"dota_tooltip_ability_ability_spectre_shadow_step_custom_health_steal" "每层偷取生命:" +"dota_tooltip_ability_ability_spectre_shadow_step_custom_health_bonus_self" "每层获得生命:" +"dota_tooltip_ability_ability_spectre_shadow_step_custom_duration_steal" "层持续:" +"dota_tooltip_ability_ability_spectre_shadow_step_custom_Lore" "每一击,都是他人生命的一小块。" + +"dota_tooltip_modifier_modifier_spectre_dagger_debuff_custom" "幽魂之刃" +"dota_tooltip_modifier_modifier_spectre_dagger_debuff_custom_Description" "减速与轨迹,强化荒芜与折射。" +"dota_tooltip_modifier_modifier_spectre_dagger_buff_custom" "幽魂之刃" +"dota_tooltip_modifier_modifier_spectre_dagger_buff_custom_Description" "移速提升。" +"dota_tooltip_modifier_modifier_spectre_dispersion_custom_boosted" "折射" +"dota_tooltip_modifier_modifier_spectre_dispersion_custom_boosted_Description" "伤害反射强化。" +"dota_tooltip_modifier_modifier_spectre_stack_buff" "暗影步" +"dota_tooltip_modifier_modifier_spectre_stack_buff_Description" "每层额外最大生命。" +"dota_tooltip_modifier_modifier_spectre_stack_debuff" "暗影步" +"dota_tooltip_modifier_modifier_spectre_stack_debuff_Description" "每层减少最大生命。" + +// Bristleback +"dota_tooltip_ability_bristleback_viscous_nasal_goo_custom" "粘稠鼻液" +"dota_tooltip_ability_bristleback_viscous_nasal_goo_custom_Description" "对敌人施放,在 %radius% 范围内溅射:降低 %base_armor_pct%%% 基础护甲 + 每层 %armor_per_stack_pct%%% ,并减速 %goo_duration% 秒。可叠加并刷新持续时间。" +"dota_tooltip_ability_bristleback_viscous_nasal_goo_custom_Lore" "雪地里得的感冒也能变成优势。" +"dota_tooltip_ability_bristleback_viscous_nasal_goo_custom_radius" "半径:" +"dota_tooltip_ability_bristleback_viscous_nasal_goo_custom_base_armor_pct" "%基础护甲降低:" +"dota_tooltip_ability_bristleback_viscous_nasal_goo_custom_armor_per_stack_pct" "%每层护甲降低:" +"dota_tooltip_ability_bristleback_viscous_nasal_goo_custom_base_move_slow" "%基础减速:" +"dota_tooltip_ability_bristleback_viscous_nasal_goo_custom_move_slow_per_stack" "%每层减速:" + +"dota_tooltip_ability_bristleback_quill_spray_custom" "针刺扫射" +"dota_tooltip_ability_bristleback_quill_spray_custom_Description" "向四周射出尖刺造成物理伤害,并对每个目标附加施法者 %attack_damage_bonus_pct%%% 攻击力的伤害。在 %quill_stack_duration% 秒内重复命中会叠加额外伤害。" +"dota_tooltip_ability_bristleback_quill_spray_custom_Lore" "保镖的体面很锋利,他的刺也一样。" +"dota_tooltip_ability_bristleback_quill_spray_custom_Note0" "该伤害不受伤害格挡影响。" +"dota_tooltip_ability_bristleback_quill_spray_custom_attack_damage_bonus_pct" "%附加攻击力比例:" +"dota_tooltip_ability_bristleback_quill_spray_custom_radius" "作用范围:" +"dota_tooltip_ability_bristleback_quill_spray_custom_quill_base_damage" "基础伤害:" +"dota_tooltip_ability_bristleback_quill_spray_custom_quill_stack_damage" "每层额外伤害:" +"dota_tooltip_ability_bristleback_quill_spray_custom_quill_stack_duration" "叠加窗口:" +"dota_tooltip_ability_bristleback_quill_spray_custom_max_damage" "单次最大伤害:" +"dota_tooltip_ability_bristleback_quill_spray_custom_scepter_description" "提升伤害与叠加持续时间。" + +"dota_tooltip_ability_bristleback_bristleback_custom" "刚毛后背" +"dota_tooltip_ability_bristleback_bristleback_custom_Description" "侧面与背后受到的伤害降低。从背后累计受到 %quill_release_threshold% 伤害后自动施放当前等级的针刺扫射。" +"dota_tooltip_ability_bristleback_bristleback_custom_Lore" "有时把背留给敌人反而更划算。" +"dota_tooltip_ability_bristleback_bristleback_custom_Note0" "背后:以背部中线为准 70°。" +"dota_tooltip_ability_bristleback_bristleback_custom_Note1" "侧面:以背部中线为准 110°。" +"dota_tooltip_ability_bristleback_bristleback_custom_side_damage_reduction" "%侧面减伤:" +"dota_tooltip_ability_bristleback_bristleback_custom_back_damage_reduction" "%背后减伤:" +"dota_tooltip_ability_bristleback_bristleback_custom_quill_release_threshold" "背后伤害阈值:" +"dota_tooltip_ability_bristleback_bristleback_custom_goo_radius" "鼻涕溅射范围:" +"dota_tooltip_ability_bristleback_bristleback_custom_scepter_description" "背后承受更高伤害时连续触发多次针刺扫射。" + +"dota_tooltip_ability_bristleback_hairball_custom" "毛团" +"dota_tooltip_ability_bristleback_hairball_custom_Description" "向目标区域吐出毛刺团,落地后对周围敌人施加多次鼻涕并触发多轮针刺扫射。" +"dota_tooltip_ability_bristleback_hairball_custom_Lore" "全身都是武器,包括不小心吞下去的部分。" +"dota_tooltip_ability_bristleback_hairball_custom_radius" "范围:" +"dota_tooltip_ability_bristleback_hairball_custom_quill_stacks" "针刺扫射次数:" +"dota_tooltip_ability_bristleback_hairball_custom_goo_stacks" "鼻涕施加次数:" + +"dota_tooltip_ability_bristleback_warpath" "战意" +"dota_tooltip_ability_bristleback_warpath_Description" "先天技能。受到伤害时获得一层(最大层数与持续时间随英雄等级提升)。每层提供 %damage_per_stack_base% + 英雄等级×%damage_per_stack_per_hero_level% 攻击力,以及 %move_speed_per_stack_base% + 英雄等级×%move_speed_per_stack_per_hero_level% 移动速度。" +"dota_tooltip_ability_bristleback_warpath_damage_per_stack_base" "每层基础攻击力:" +"dota_tooltip_ability_bristleback_warpath_damage_per_stack_per_hero_level" "每层攻击力 / 英雄等级:" +"dota_tooltip_ability_bristleback_warpath_move_speed_per_stack_base" "每层基础移速:" +"dota_tooltip_ability_bristleback_warpath_move_speed_per_stack_per_hero_level" "每层移速 / 英雄等级:" +"dota_tooltip_ability_bristleback_warpath_max_stacks_base" "基础最大层数:" +"dota_tooltip_ability_bristleback_warpath_max_stacks_per_hero_level" "最大层数 / 英雄等级:" +"dota_tooltip_ability_bristleback_warpath_stack_duration_base" "基础持续时间:" +"dota_tooltip_ability_bristleback_warpath_stack_duration_per_hero_level" "持续时间 / 英雄等级:" + +"dota_tooltip_ability_bristleback_rage_fortitude_custom" "Battle Fortitude" +"dota_tooltip_ability_bristleback_rage_fortitude_custom_Description" "当怒气高于 %rage_threshold% 时,获得负面效果免疫、额外护甲与魔法抗性。" +"dota_tooltip_ability_bristleback_rage_fortitude_custom_Lore" "里格瓦尔的血液沸腾时,尖刺与厚皮几乎刀枪不入。" +"dota_tooltip_ability_bristleback_rage_fortitude_custom_rage_threshold" "怒气阈值:" +"dota_tooltip_ability_bristleback_rage_fortitude_custom_bonus_armor" "+$armor" +"dota_tooltip_ability_bristleback_rage_fortitude_custom_bonus_magic_resist" "%+$spell_resist" +"dota_tooltip_modifier_bristleback_rage_fortitude_buff" "Battle Fortitude" +"dota_tooltip_modifier_bristleback_rage_fortitude_buff_Description" "免疫负面效果,并获得额外护甲与魔法抗性。" + +"dota_tooltip_ability_special_bonus_unique_bristleback_custom_3" "+{s:bonus_quill_base_damage} / +{s:bonus_max_damage} 针刺扫射基础/最大伤害" +"dota_tooltip_ability_special_bonus_unique_bristleback_custom_5" "+{s:bonus_quill_stack_damage} 针刺扫射每层伤害" +"dota_tooltip_ability_special_bonus_unique_bristleback_custom_6" "+{s:bonus_radius} 粘稠鼻液范围" +"dota_tooltip_ability_special_bonus_unique_bristleback_3" "+{s:bonus_damage_per_stack} 战意每层伤害" + +// -----------------Legion Commander-------------------- +"dota_tooltip_ability_ability_legion_commander_overwhelming_odds_custom" "压倒性优势" +"dota_tooltip_ability_ability_legion_commander_overwhelming_odds_custom_Description" "军团指挥官猛击地面,对范围内造成魔法伤害:命中的敌方英雄与小兵越多,总伤害越高。被命中的敌人被减速;每命中一名敌军,军团指挥官获得护甲,并在短时间内提升攻击速度与移动速度。" +"dota_tooltip_ability_ability_legion_commander_overwhelming_odds_custom_Lore" "敌军阵型越密,胜机越清晰。" +"dota_tooltip_ability_ability_legion_commander_overwhelming_odds_custom_radius" "作用范围:" +"dota_tooltip_ability_ability_legion_commander_overwhelming_odds_custom_damage_base" "基础伤害:" +"dota_tooltip_ability_ability_legion_commander_overwhelming_odds_custom_damage_per_enemy" "每名单位追加伤害:" +"dota_tooltip_ability_ability_legion_commander_overwhelming_odds_custom_armor_buff_duration" "护甲持续时间:" +"dota_tooltip_ability_ability_legion_commander_overwhelming_odds_custom_armor_per_enemy" "每名敌军护甲:" +"dota_tooltip_ability_ability_legion_commander_overwhelming_odds_custom_debuff_duration" "减速持续时间:" +"dota_tooltip_ability_ability_legion_commander_overwhelming_odds_custom_enemy_movespeed_slow" "%移动减慢:" +"dota_tooltip_ability_ability_legion_commander_overwhelming_odds_custom_self_buff_duration" "自身强化持续时间:" +"dota_tooltip_ability_ability_legion_commander_overwhelming_odds_custom_self_bonus_attack_speed" "自身额外攻速:" +"dota_tooltip_ability_ability_legion_commander_overwhelming_odds_custom_self_bonus_movespeed" "自身额外移速:" + +"dota_tooltip_ability_ability_legion_commander_press_the_attack_custom" "强攻" +"dota_tooltip_ability_ability_legion_commander_press_the_attack_custom_Description" "军团指挥官对友方英雄或自身施加强驱散,随后在 %duration% 秒内提高目标的生命恢复、攻击速度与移动速度。" +"dota_tooltip_ability_ability_legion_commander_press_the_attack_custom_Lore" "肩头一拍,阵列再起。" +"dota_tooltip_ability_ability_legion_commander_press_the_attack_custom_duration" "持续时间:" +"dota_tooltip_ability_ability_legion_commander_press_the_attack_custom_attack_speed_bonus" "额外攻速:" +"dota_tooltip_ability_ability_legion_commander_press_the_attack_custom_hp_regen" "生命恢复:" +"dota_tooltip_ability_ability_legion_commander_press_the_attack_custom_movespeed_bonus" "额外移速:" + +"dota_tooltip_ability_ability_legion_commander_moment_of_courage_custom" "勇气之霎" +"dota_tooltip_ability_ability_legion_commander_moment_of_courage_custom_Description" "被动:受到敌方普通攻击时,有 %trigger_chance_pct%%% 几率立即反击,按反击伤害的一部分回复生命,并在短时间内提升移动速度。触发检测间隔不少于 %proc_cooldown% 秒。" +"dota_tooltip_ability_ability_legion_commander_moment_of_courage_custom_Lore" "胆怯总会先败给剑锋——除非特斯丁先出手。" +"dota_tooltip_ability_ability_legion_commander_moment_of_courage_custom_trigger_chance_pct" "触发概率(%):" +"dota_tooltip_ability_ability_legion_commander_moment_of_courage_custom_proc_cooldown" "触发检测间隔:" +"dota_tooltip_ability_ability_legion_commander_moment_of_courage_custom_buff_duration" "加速持续时间:" +"dota_tooltip_ability_ability_legion_commander_moment_of_courage_custom_buff_bonus_movespeed" "触发后额外移速:" +"dota_tooltip_ability_ability_legion_commander_moment_of_courage_custom_counter_lifesteal_pct" "%反击伤害转治疗:" +"dota_tooltip_ability_ability_legion_commander_moment_of_courage_custom_attack_count" "瞬发攻击次数:" + +"dota_tooltip_ability_ability_legion_commander_duel_custom" "决斗" +"dota_tooltip_ability_ability_legion_commander_duel_custom_Description" "军团指挥官与一名敌人决斗 %duration% 秒:双方无法施法或使用主动物品,但获得额外攻速;对敌方英雄而言,这些限制持续时间会因状态抗性而更短。获胜将永久提升攻击伤害(对英雄更高、对小兵更低,总上限 %max_stack_damage%),并获得 %reward_random_stat% 点随机属性。若双方距离超过 %victory_range%,决斗无奖励结束。" +"dota_tooltip_ability_ability_legion_commander_duel_custom_Lore" "荣耀终结一切争辩。" +"dota_tooltip_ability_ability_legion_commander_duel_custom_duration" "持续时间:" +"dota_tooltip_ability_ability_legion_commander_duel_custom_victory_range" "决裂距离:" +"dota_tooltip_ability_ability_legion_commander_duel_custom_reward_damage" "战胜英雄时的额外攻击伤害:" +"dota_tooltip_ability_ability_legion_commander_duel_custom_reward_damage_creep" "战胜小兵时的额外攻击伤害:" +"dota_tooltip_ability_ability_legion_commander_duel_custom_reward_random_stat" "每次胜利随机属性:" +"dota_tooltip_ability_ability_legion_commander_duel_custom_max_stack_damage" "额外伤害总上限:" +"dota_tooltip_ability_ability_legion_commander_duel_custom_bonus_attack_speed" "额外攻速:" +"dota_tooltip_ability_ability_legion_commander_duel_custom_scepter_max_charges" "充能数:" +"dota_tooltip_ability_ability_legion_commander_duel_custom_scepter_cooldown_reduction" "冷却缩减:" +"dota_tooltip_ability_ability_legion_commander_duel_custom_scepter_magic_resist" "魔法抗性:" +"dota_tooltip_ability_ability_legion_commander_duel_custom_Scepter_Description" "将 Duel 的最大充能提升至 %scepter_max_charges%,冷却缩短 %scepter_cooldown_reduction% 秒,并在决斗持续期间为军团指挥官提供 %scepter_magic_resist%%% 魔法抗性。" +"dota_tooltip_ability_ability_legion_commander_duel_custom_Shard_Description" "决斗持续期间,军团指挥官每 %shard_overwhelming_interval% 秒会在脚下自动触发一次相当于当前等级「压倒性优势」的效果;不消耗魔法,也不进入该技能冷却(至少学习 1 级压倒性优势)。" +"dota_tooltip_ability_ability_legion_commander_duel_custom_shard_overwhelming_interval" "脉冲间隔:" +"dota_tooltip_ability_ability_legion_commander_duel_custom_Note0" "决斗获得的攻击伤害加成在本局内一直保留。" + +"dota_tooltip_ability_special_bonus_unique_legion_commander_odds_radius" "+70 压倒性优势范围" +"dota_tooltip_ability_special_bonus_unique_legion_commander_odds_damage" "+60 压倒性优势基础伤害" +"dota_tooltip_ability_special_bonus_unique_legion_commander_pta_duration" "+2 秒强攻持续时间" +"dota_tooltip_ability_special_bonus_unique_legion_commander_pta_regen" "+12 强攻生命恢复" +"dota_tooltip_ability_special_bonus_unique_legion_commander_moc_chance" "+10% 勇气之霎触发概率" +"dota_tooltip_ability_special_bonus_unique_legion_commander_moc_lifesteal" "+25% 反击治疗" +"dota_tooltip_ability_special_bonus_unique_legion_commander_duel_duration" "+2 秒决斗持续时间" +"dota_tooltip_ability_special_bonus_unique_legion_commander_duel_reward" "+10 决斗战胜英雄时的奖励伤害" + +// --------------------Queen of Pain-------------------- + +"DOTA_Tooltip_ability_queenofpain_shadow_strike_custom" "暗影突袭" +"DOTA_Tooltip_ability_queenofpain_shadow_strike_custom_Description" "向目标周围%radius%范围投出毒刃,造成魔法伤害并中毒%duration%秒。目标被减速,每%damage_interval%秒受到一次中毒伤害。首次命中与每次中毒伤害会额外附加相当于当前法力%mana_damage_from_current_pct%%%的魔法伤害。" +"DOTA_Tooltip_ability_queenofpain_shadow_strike_custom_Scepter_Description" "当该减益在敌人身上结束或被刷新时,会释放一次「痛苦尖叫」的效果。" +"DOTA_Tooltip_ability_queenofpain_shadow_strike_custom_Lore" "阿卡莎最爱用带毒的刀锋把受害者的痛苦慢慢拉长。" +"DOTA_Tooltip_ability_queenofpain_shadow_strike_custom_movement_slow" "移动减速(%):" +"DOTA_Tooltip_ability_queenofpain_shadow_strike_custom_strike_damage" "命中伤害:" +"DOTA_Tooltip_ability_queenofpain_shadow_strike_custom_duration_damage" "周期伤害:" +"DOTA_Tooltip_ability_queenofpain_shadow_strike_custom_duration" "持续时间:" +"DOTA_Tooltip_ability_queenofpain_shadow_strike_custom_damage_interval" "间隔:" +"DOTA_Tooltip_ability_queenofpain_shadow_strike_custom_radius" "作用范围:" +"DOTA_Tooltip_ability_queenofpain_shadow_strike_custom_mana_damage_from_current_pct" "当前法力转额外伤害(%):" + +"DOTA_Tooltip_ability_queenofpain_blink_custom" "闪烁" +"DOTA_Tooltip_ability_queenofpain_blink_custom_Description" "短距离位移;起点与落点各产生音爆,在%radius%范围内造成魔法伤害并沉默。每处伤害还附加相当于当前法力%mana_damage_from_current_pct%%%的额外部分。" +"DOTA_Tooltip_ability_queenofpain_blink_custom_Lore" "无声的女王用痛苦为自己加冕,让臣民无一幸免。" +"DOTA_Tooltip_ability_queenofpain_blink_custom_Note0" "可规避许多锁定英雄的指向性飞弹。" +"DOTA_Tooltip_ability_queenofpain_blink_custom_damage" "伤害:" +"DOTA_Tooltip_ability_queenofpain_blink_custom_duration" "沉默时间:" +"DOTA_Tooltip_ability_queenofpain_blink_custom_radius" "作用范围:" +"DOTA_Tooltip_ability_queenofpain_blink_custom_cast_range" "施法距离:" +"DOTA_Tooltip_ability_queenofpain_blink_custom_mana_damage_from_current_pct" "当前法力转额外伤害(%):" + +"DOTA_Tooltip_ability_queenofpain_scream_of_pain_custom" "痛苦尖叫" +"DOTA_Tooltip_ability_queenofpain_scream_of_pain_custom_Description" "发出刺穿性的尖啸,对%radius%范围内所有敌人造成魔法伤害,并再附加相当于当前法力%mana_damage_from_current_pct%%%的额外伤害。" +"DOTA_Tooltip_ability_queenofpain_scream_of_pain_custom_Shard_Description" "被击中的敌人(建筑、信使、肉山除外)在%shard_mark_duration%秒内易伤:受到的伤害提高%shard_incoming_damage_pct%%%。可驱散。" +"DOTA_Tooltip_ability_queenofpain_scream_of_pain_custom_Lore" "阿卡莎用甜腻的嗓音让对手迷失,再一并收割灵魂。" +"DOTA_Tooltip_ability_queenofpain_scream_of_pain_custom_Note0" "可伤害隐形单位。" +"DOTA_Tooltip_ability_queenofpain_scream_of_pain_custom_Note1" "该技能投射物无法被目标躲避。" +"DOTA_Tooltip_ability_queenofpain_scream_of_pain_custom_radius" "范围:" +"DOTA_Tooltip_ability_queenofpain_scream_of_pain_custom_damage" "伤害:" +"DOTA_Tooltip_ability_queenofpain_scream_of_pain_custom_shard_mark_duration" "易伤时间:" +"DOTA_Tooltip_ability_queenofpain_scream_of_pain_custom_shard_incoming_damage_pct" "额外承受伤害(%):" +"DOTA_Tooltip_ability_queenofpain_scream_of_pain_custom_mana_damage_from_current_pct" "当前法力转额外伤害(%):" + +"DOTA_Tooltip_Ability_special_bonus_unique_queen_of_pain_2_1" "+{s:bonus_damage} 闪烁伤害" +"DOTA_Tooltip_Ability_special_bonus_unique_queen_of_pain_8" "+{s:bonus_mitigation_pct_per_level}% 每层等级降低先天技能对自己的伤害" + +"DOTA_Tooltip_ability_queenofpain_agony_innate" "受虐狂喜" +"DOTA_Tooltip_ability_queenofpain_agony_innate_Description" "技能对敌人造成伤害时也会反噬自身:每层等级造成相当于实际伤害 %self_damage_pct_per_level%%% 的纯粹自伤。提供 +%spell_amp_bonus%%% 技能增强。每损失 1% 生命值,额外获得 +%spell_amp_per_missing_hp_pct%%% 技能增强。击杀任意单位后,恢复自身最大生命值的 5%。点出天赋后,每层等级再额外降低 %mitigation_pct_per_level%%% 的该自伤。" +"DOTA_Tooltip_ability_queenofpain_agony_innate_Lore" "阿卡莎沉醉于敌人的痛苦——但快感总有代价,而她心甘情愿。" +"DOTA_Tooltip_ability_queenofpain_agony_innate_self_damage_pct_per_level" "每层自伤比例(%):" +"DOTA_Tooltip_ability_queenofpain_agony_innate_spell_amp_bonus" "技能增强(%):" +"DOTA_Tooltip_ability_queenofpain_agony_innate_spell_amp_per_missing_hp_pct" "每损失 1% 生命值提供技能增强(%):" +"DOTA_Tooltip_ability_queenofpain_agony_innate_mitigation_pct_per_level" "天赋:每层降低自伤(%):" + +"DOTA_Tooltip_ability_skywrath_mage_arcane_bolt_custom" "奥术箭" +"DOTA_Tooltip_ability_skywrath_mage_arcane_bolt_custom_Description" "向目标发射缓慢飞行的奥术弹,造成 %bolt_damage% 点魔法伤害,并额外附加相当于智力 %int_multiplier% 的伤害。" +"DOTA_Tooltip_ability_skywrath_mage_arcane_bolt_custom_Shard_Description" "为天怒法师所有主动技能提供 +1 充能。" +"DOTA_Tooltip_ability_skywrath_mage_arcane_bolt_custom_Lore" "天怒王廷的学士坚信:完美的公式,远比蛮力更致命。" +"DOTA_Tooltip_ability_skywrath_mage_arcane_bolt_custom_bolt_damage" "基础伤害:" +"DOTA_Tooltip_ability_skywrath_mage_arcane_bolt_custom_int_multiplier" "智力系数:" +"DOTA_Tooltip_ability_skywrath_mage_arcane_bolt_custom_bolt_speed" "弹道速度:" +"DOTA_Tooltip_ability_skywrath_mage_arcane_bolt_custom_bolt_vision" "视野半径:" + +"DOTA_Tooltip_ability_skywrath_mage_concussive_shot_custom" "震荡光弹" +"DOTA_Tooltip_ability_skywrath_mage_concussive_shot_custom_Description" "向 %launch_radius% 范围内最近敌人发射追踪弹。命中后在 %slow_radius% 范围爆炸,造成 %damage% 点魔法伤害并减速 %slow_duration% 秒。" +"DOTA_Tooltip_ability_skywrath_mage_concussive_shot_custom_Shard_Description" "为天怒法师所有主动技能提供 +1 充能。" +"DOTA_Tooltip_ability_skywrath_mage_concussive_shot_custom_Lore" "当德拉贡努斯驾驭高天之风时,空气本身也会化作审判。" +"DOTA_Tooltip_ability_skywrath_mage_concussive_shot_custom_launch_radius" "搜敌半径:" +"DOTA_Tooltip_ability_skywrath_mage_concussive_shot_custom_slow_radius" "爆炸范围:" +"DOTA_Tooltip_ability_skywrath_mage_concussive_shot_custom_damage" "伤害:" +"DOTA_Tooltip_ability_skywrath_mage_concussive_shot_custom_slow_duration" "减速持续:" +"DOTA_Tooltip_ability_skywrath_mage_concussive_shot_custom_slow" "移速减缓(%):" +"DOTA_Tooltip_ability_skywrath_mage_concussive_shot_custom_int_multiplier" "智力系数:" + +"DOTA_Tooltip_ability_skywrath_mage_ancient_seal_custom" "上古封印" +"DOTA_Tooltip_ability_skywrath_mage_ancient_seal_custom_Description" "作用于目标周围 %radius% 范围内所有敌人,沉默 %seal_duration% 秒并降低 %resist_debuff%%% 魔法抗性。" +"DOTA_Tooltip_ability_skywrath_mage_ancient_seal_custom_Shard_Description" "为天怒法师所有主动技能提供 +1 充能。" +"DOTA_Tooltip_ability_skywrath_mage_ancient_seal_custom_Lore" "铭刻于古老符文的禁令,足以在法术成形前将其扼杀。" +"DOTA_Tooltip_ability_skywrath_mage_ancient_seal_custom_seal_duration" "持续时间:" +"DOTA_Tooltip_ability_skywrath_mage_ancient_seal_custom_resist_debuff" "魔抗降低(%):" +"DOTA_Tooltip_ability_skywrath_mage_ancient_seal_custom_radius" "范围:" +"DOTA_Tooltip_ability_skywrath_mage_ancient_seal_custom_int_multiplier" "智力系数:" + +"DOTA_Tooltip_ability_skywrath_mage_mystic_flare_custom" "神秘之耀" +"DOTA_Tooltip_ability_skywrath_mage_mystic_flare_custom_Description" "在目标点召唤持续 %duration% 秒的奥能领域,对范围内所有敌人造成魔法伤害。" +"DOTA_Tooltip_ability_skywrath_mage_mystic_flare_custom_Shard_Description" "为天怒法师所有主动技能提供 +1 充能。" +"DOTA_Tooltip_ability_skywrath_mage_mystic_flare_custom_Lore" "当天穹裂开,倾泻而下的奥术之光便是无情之刃。" +"DOTA_Tooltip_ability_skywrath_mage_mystic_flare_custom_radius" "范围:" +"DOTA_Tooltip_ability_skywrath_mage_mystic_flare_custom_duration" "持续时间:" +"DOTA_Tooltip_ability_skywrath_mage_mystic_flare_custom_damage" "总伤害:" +"DOTA_Tooltip_ability_skywrath_mage_mystic_flare_custom_int_multiplier" "智力系数:" +"DOTA_Tooltip_ability_skywrath_mage_mystic_flare_custom_damage_interval" "伤害间隔:" + +"DOTA_Tooltip_ability_skywrath_mage_innate_custom" "毁灭与复苏" +"DOTA_Tooltip_ability_skywrath_mage_innate_custom_Description" "每次施放任意技能后,天怒法师会在 %search_radius% 范围内向最多 %max_targets% 名敌人发射奥术箭。每支箭造成 Arcane Bolt %damage_pct%%% 的伤害。" +"DOTA_Tooltip_ability_skywrath_mage_innate_custom_Lore" "在德拉贡努斯眼中,毁灭与复苏不过是同一条天律的两面。" +"DOTA_Tooltip_ability_skywrath_mage_innate_custom_search_radius" "搜索范围:" +"DOTA_Tooltip_ability_skywrath_mage_innate_custom_max_targets" "目标数量:" +"DOTA_Tooltip_ability_skywrath_mage_innate_custom_damage_pct" "奥术箭伤害占比(%):" + +"DOTA_Tooltip_ability_skywrath_mage_staff_of_the_scion_custom" "司羽之杖" +"DOTA_Tooltip_ability_skywrath_mage_staff_of_the_scion_custom_Description" "持有阿哈利姆神杖时,每个技能独立拥有瞬间重置冷却的概率链:

50% -> 25% -> 12.5% -> 6.25%

每个技能单独计算。单个技能在连续触发 %max_successes% 次后重置,或在 %reset_duration% 秒后重置。" +"DOTA_Tooltip_ability_skywrath_mage_staff_of_the_scion_custom_Lore" "这件高天后裔的遗物,会回应完美施法,再次开启施咒之门。" +"DOTA_Tooltip_ability_skywrath_mage_staff_of_the_scion_custom_reset_duration" "重置时间:" +"DOTA_Tooltip_ability_skywrath_mage_staff_of_the_scion_custom_max_successes" "重置前触发次数:" + +"DOTA_Tooltip_Ability_special_bonus_unique_skywrath_6" "+{s:bonus_AbilityCastRange} 奥术箭施法距离" +"DOTA_Tooltip_Ability_special_bonus_unique_skywrath_concussive_shot_slow" "+{s:bonus_slow}% 震荡光弹减速" +"DOTA_Tooltip_Ability_special_bonus_unique_skywrath" "-{s:bonus_AbilityCooldown} 秒 上古封印冷却" +"DOTA_Tooltip_Ability_special_bonus_unique_skywrath_2" "+{s:bonus_int_multiplier} 奥术箭智力系数" +"DOTA_Tooltip_Ability_special_bonus_unique_skywrath_4" "+{s:bonus_launch_radius} 震荡光弹搜敌范围" +"DOTA_Tooltip_Ability_special_bonus_unique_skywrath_3" "{s:bonus_resist_debuff} 上古封印魔抗降低" +"DOTA_Tooltip_Ability_special_bonus_unique_skywrath_5" "+{s:bonus_damage} 神秘之耀伤害" +"dota_tooltip_modifier_modifier_skywrath_mage_staff_of_the_scion_arcane_tracker" "司羽之杖:奥术箭" +"dota_tooltip_modifier_modifier_skywrath_mage_staff_of_the_scion_arcane_tracker_Description" "奥术箭的瞬间刷新概率链已激活。层数显示重置前已成功触发次数。" +"dota_tooltip_modifier_modifier_skywrath_mage_staff_of_the_scion_concussive_tracker" "司羽之杖:震荡光弹" +"dota_tooltip_modifier_modifier_skywrath_mage_staff_of_the_scion_concussive_tracker_Description" "震荡光弹的瞬间刷新概率链已激活。层数显示重置前已成功触发次数。" +"dota_tooltip_modifier_modifier_skywrath_mage_staff_of_the_scion_seal_tracker" "司羽之杖:上古封印" +"dota_tooltip_modifier_modifier_skywrath_mage_staff_of_the_scion_seal_tracker_Description" "上古封印的瞬间刷新概率链已激活。层数显示重置前已成功触发次数。" +"dota_tooltip_modifier_modifier_skywrath_mage_staff_of_the_scion_flare_tracker" "司羽之杖:神秘之耀" +"dota_tooltip_modifier_modifier_skywrath_mage_staff_of_the_scion_flare_tracker_Description" "神秘之耀的瞬间刷新概率链已激活。层数显示重置前已成功触发次数。" + +"dota_tooltip_ability_ability_lina_laguna_blade_custom_mana_cost_facet" "阿哈利姆法力公式中的法力占比(%):" +"dota_tooltip_ability_ability_lina_laguna_blade_custom_mana_cost_facet_tooltip" "公式中的法力份额(%):" + + +// -------------------- + +"dota_tooltip_ability_ability_luna_lucent_beam_custom" "Lucent Beam" +"dota_tooltip_ability_ability_luna_lucent_beam_custom_Description" "对敌方单位发射月光束,造成%lucent_beam_damage%魔法伤害并眩晕%stun_duration%秒。主目标周围%lucent_beam_aoe_radius%范围内的敌人受到主伤害的%lucent_beam_aoe_damage_pct%%%伤害与眩晕(月蚀光束使用相同溅射)。" +"dota_tooltip_ability_ability_luna_lucent_beam_custom_lucent_beam_damage" "伤害:" +"dota_tooltip_ability_ability_luna_lucent_beam_custom_stun_duration" "眩晕:" +"dota_tooltip_ability_ability_luna_lucent_beam_custom_lucent_beam_aoe_radius" "主目标周围溅射半径:" +"dota_tooltip_ability_ability_luna_lucent_beam_custom_lucent_beam_aoe_damage_pct" "溅射伤害与眩晕占比(%):" +"dota_tooltip_ability_ability_luna_lucent_beam_custom_AbilityCastRange" "施法距离:" +"dota_tooltip_ability_ability_luna_lucent_beam_custom_Lore" "塞勒梅娜不会原谅那些阻碍她战士的人:一条射线—黑暗已经笼罩了敌人。" + +"dota_tooltip_ability_ability_luna_twin_glaives_custom" "双刃剑" +"dota_tooltip_ability_ability_luna_twin_glaives_custom_Description" "露娜掷出两道月刃沿弧线飞出,在目标处交汇后返回,路径上造成范围伤害;同一目标可被命中两次。每次命中造成%glaive_damage%外加部分攻击力。被击中的敌人%twin_facet_vuln_duration%秒内受到的物理与魔法伤害提高:每层易伤%twin_facet_pct%%%。" +"dota_tooltip_ability_ability_luna_twin_glaives_custom_glaive_damage" "基础月刃伤害:" +"dota_tooltip_ability_ability_luna_twin_glaives_custom_projectile_speed" "弹道速度:" +"dota_tooltip_ability_ability_luna_twin_glaives_custom_projectile_range" "最大距离:" +"dota_tooltip_ability_ability_luna_twin_glaives_custom_projectile_width" "伤害半径:" +"dota_tooltip_ability_ability_luna_twin_glaives_custom_axe_spread" "双刀间距:" +"dota_tooltip_ability_ability_luna_twin_glaives_custom_twin_facet_vuln_duration" "易伤持续时间:" +"dota_tooltip_ability_ability_luna_twin_glaives_custom_twin_facet_pct" "每层额外承受伤害(%):" +"dota_tooltip_ability_ability_luna_twin_glaives_custom_AbilityCastRange" "施法距离:" +"dota_tooltip_ability_ability_luna_twin_glaives_custom_Lore" "月盘的刀刃毫不留情:月亮照耀的地方,钢铁落下。" + +"dota_tooltip_ability_ability_luna_moon_glaive_custom" "月亮长剑" +"dota_tooltip_ability_ability_luna_moon_glaive_custom_Description" "攻击弹射月刃,在%bounce_range%范围内弹跳。首次弹跳造成%bounce_damage_pct%%%攻击伤害;后续每次为前一次的%bounce_falloff_pct%%%(低等级衰减更强)。天赋可消除衰减。若点出「光束连击」天赋,月光或月蚀光束命中敌人后,露娜会立即对该目标额外攻击两次。" +"dota_tooltip_ability_ability_luna_moon_glaive_custom_bounce_range" "弹跳半径:" +"dota_tooltip_ability_ability_luna_moon_glaive_custom_moon_glaive_bounce_count" "弹跳次数:" +"dota_tooltip_ability_ability_luna_moon_glaive_custom_bounce_falloff_pct" "弹跳伤害相对前一次(%):" +"dota_tooltip_ability_ability_luna_moon_glaive_custom_Lore" "狩猎不会以第一次打击结束:只要敌人站在附近,月球钢铁就会找到所有人。" + +"dota_tooltip_ability_ability_luna_lunar_blessing_custom" "月球祝福" +"dota_tooltip_ability_ability_luna_lunar_blessing_custom_Description" "与月蚀关联的固有力量。%blessing_radius%范围内友方攻击附加伤害:buff下每级英雄等级+%blessing_bonus_damage%伤害。友方米拉娜在 %moon_sisters_radius% 范围内时,露娜与米拉娜所受伤害降低 %moon_sisters_damage_reduction_pct%%%。" +"dota_tooltip_ability_ability_luna_lunar_blessing_custom_moon_sisters_radius" "与米拉娜联动半径:" +"dota_tooltip_ability_ability_luna_lunar_blessing_custom_moon_sisters_damage_reduction_pct" "所受伤害降低(姐妹):" +"dota_tooltip_ability_ability_luna_lunar_blessing_custom_blessing_radius" "半径:" +"dota_tooltip_ability_ability_luna_lunar_blessing_custom_blessing_bonus_damage" "每级伤害加成:" +"dota_tooltip_ability_ability_luna_lunar_blessing_custom_Shard_Description" "受月光祝福影响的友方额外获得技能增强:每英雄等级%blessing_shard_spell_amp_per_level%%%。" +"dota_tooltip_ability_ability_luna_lunar_blessing_custom_Lore" "月亮承载着塞勒梅娜的意志:月光也触及盟友的刀刃,而不仅仅是她自己的钢铁。" + +"dota_tooltip_ability_ability_luna_eclipse_custom" "日食" +"dota_tooltip_ability_ability_luna_eclipse_custom_Description" "月蚀持续%eclipse_duration%秒,作用于%eclipse_radius%范围;每%beam_interval%秒落下光束。无阿哈利姆时每个间隔随机命中区域内一名敌人;共%eclipse_beam_count%道打击。每道光束使用当前月光伤害与眩晕;若未学习月光则光束无伤害。" +"dota_tooltip_ability_ability_luna_eclipse_custom_eclipse_duration" "持续时间:" +"dota_tooltip_ability_ability_luna_eclipse_custom_beam_interval" "光束间隔:" +"dota_tooltip_ability_ability_luna_eclipse_custom_eclipse_beam_count" "光束次数:" +"dota_tooltip_ability_ability_luna_eclipse_custom_eclipse_radius" "半径:" +"dota_tooltip_ability_ability_luna_eclipse_custom_eclipse_facet_wave_count" "波次(阿哈利姆):" +"dota_tooltip_ability_ability_luna_eclipse_custom_Scepter_Description" "不再随机目标;月蚀改为%eclipse_facet_wave_count%轮波次,每轮对区域内每名敌人造成完整月光打击。" +"dota_tooltip_ability_ability_luna_eclipse_custom_Lore" "当月亮遮住太阳时,天空本身就成为塞勒梅娜的武器—,没有人会逃脱她的审判。" + +"dota_tooltip_modifier_modifier_luna_lunar_blessing_buff" "月球祝福" +"dota_tooltip_modifier_modifier_luna_lunar_blessing_buff_Description" "月光强化攻击伤害:加成随受祝福英雄等级与露娜等级提升。露娜持有阿哈利姆魔晶时,按你的等级额外获得技能增强。" +"dota_tooltip_modifier_modifier_luna_lunar_blessing_aura" "月球祝福" +"dota_tooltip_modifier_modifier_luna_lunar_blessing_aura_Description" "附近友方获得月光祝福效果。" + +"dota_tooltip_modifier_modifier_moon_sisters_innate" "月之姐妹" +"dota_tooltip_modifier_modifier_moon_sisters_innate_Description" "月之姐妹在旁:所受伤害降低。" + +"dota_tooltip_modifier_modifier_luna_eclipse_active" "日食" +"dota_tooltip_modifier_modifier_luna_eclipse_active_Description" "月光打击区域内敌人:魔法伤害与短眩晕(无阿哈利姆时每跳随机一人;有阿哈利姆时每波命中全部敌人)。" + +// --------------------米拉娜-------------------- + +"dota_tooltip_ability_ability_mirana_starstorm_custom" "Starstorm" +"dota_tooltip_ability_ability_mirana_starstorm_custom_Description" "在米拉娜周围召唤星暴:对施法点 %radius% 范围内敌人落下流星,出现后 %meteor_impact_delay% 秒造成 %damage% 点魔法伤害,外加相当于最大法力值 %mana_damage_pct%%% 的魔法伤害;%secondary_delay% 秒后最近敌人再落下一颗流星,%meteor_impact_delay% 秒后造成主伤害 %secondary_damage_pct%%% 的额外打击。" +"dota_tooltip_ability_ability_mirana_starstorm_custom_damage" "伤害:" +"dota_tooltip_ability_ability_mirana_starstorm_custom_mana_damage_pct" "法力伤害(%):" +"dota_tooltip_ability_ability_mirana_starstorm_custom_radius" "作用范围:" +"dota_tooltip_ability_ability_mirana_starstorm_custom_meteor_impact_delay" "流星下落时间:" +"dota_tooltip_ability_ability_mirana_starstorm_custom_secondary_delay" "第二颗流星延迟:" +"dota_tooltip_ability_ability_mirana_starstorm_custom_secondary_damage_pct" "额外打击伤害(%):" +"dota_tooltip_ability_ability_mirana_starstorm_custom_Lore" "天空回应月之猎手——敌人无处藏身。" + +"dota_tooltip_ability_ability_mirana_sacred_arrow_custom" "Sacred Arrow" +"dota_tooltip_ability_ability_mirana_sacred_arrow_custom_Description" "向指针方向射出神圣之箭。对路径上每个敌人造成 %arrow_damage% 点魔法伤害,外加相当于最大法力值 %mana_damage_pct%%% 的魔法伤害,仅在命中首领时停下。首领眩晕随飞行距离增加:每 100 距离 %stun_per_100_distance% 秒,最长 %max_stun_duration% 秒。" +"dota_tooltip_ability_ability_mirana_sacred_arrow_custom_arrow_damage" "伤害:" +"dota_tooltip_ability_ability_mirana_sacred_arrow_custom_mana_damage_pct" "法力伤害(%):" +"dota_tooltip_ability_ability_mirana_sacred_arrow_custom_arrow_range" "射程:" +"dota_tooltip_ability_ability_mirana_sacred_arrow_custom_arrow_speed" "速度:" +"dota_tooltip_ability_ability_mirana_sacred_arrow_custom_stun_per_100_distance" "每 100 距离眩晕:" +"dota_tooltip_ability_ability_mirana_sacred_arrow_custom_max_stun_duration" "最长眩晕:" +"dota_tooltip_ability_ability_mirana_sacred_arrow_custom_Shard_Description" "箭飞行期间,轨迹 %shard_starfall_radius% 范围内的每个敌人受到一次星暴:先 %shard_starfall_damage_pct_first%%% 再 %shard_starfall_damage_pct_second%%% 神圣之箭伤害(基础 + 法力)。" +"dota_tooltip_ability_ability_mirana_sacred_arrow_custom_shard_starfall_radius" "星暴半径:" +"dota_tooltip_ability_ability_mirana_sacred_arrow_custom_shard_starfall_damage_pct_first" "第一颗流星伤害(%):" +"dota_tooltip_ability_ability_mirana_sacred_arrow_custom_shard_starfall_damage_pct_second" "第二颗流星伤害(%):" +"dota_tooltip_ability_ability_mirana_sacred_arrow_custom_Lore" "箭飞得越远,塞勒梅涅的惩戒越重。" + +"dota_tooltip_ability_ability_mirana_leap_custom" "Leap" +"dota_tooltip_ability_ability_mirana_leap_custom_Description" "米拉娜朝面向方向跳跃,最远距离 %leap_distance%。每次跳跃消耗 %mana_cost_pct%%% 最大法力。空中期间受到的伤害降低 %leap_damage_reduction_pct%%% ,落地前无法再次跳跃。落地时 %leap_landing_radius% 范围内敌人受到相当于米拉娜攻击力 %leap_landing_damage_pct%%% 的伤害,并额外受到相当于最大法力值 %mana_damage_pct%%% 的魔法伤害。%max_charges% 层充能(4 + 每层英雄等级 +1),单层恢复 %AbilityChargeRestoreTime% 秒。立即获得 %leap_bonus_duration% 秒内 +%leap_speedbonus%%% 移动速度与 +%leap_speedbonus_as% 攻击速度。增益期间先天伤害 ×%innate_damage_multiplier%。" +"dota_tooltip_ability_ability_mirana_leap_custom_mana_cost_pct" "最大法力消耗(%):" +"dota_tooltip_ability_ability_mirana_leap_custom_leap_landing_radius" "落地伤害半径:" +"dota_tooltip_ability_ability_mirana_leap_custom_leap_landing_damage_pct" "落地伤害(攻击力 %):" +"dota_tooltip_ability_ability_mirana_leap_custom_mana_damage_pct" "落地法力伤害(%):" +"dota_tooltip_ability_ability_mirana_leap_custom_leap_damage_reduction_pct" "空中减伤(%):" +"dota_hud_error_mirana_leap_in_air" "落地前无法再次跳跃。" +"dota_tooltip_ability_ability_mirana_leap_custom_innate_damage_multiplier" "先天伤害倍数:" +"dota_tooltip_ability_ability_mirana_leap_custom_max_charges" "充能层数:" +"dota_tooltip_ability_ability_mirana_leap_custom_AbilityChargeRestoreTime" "充能恢复:" +"dota_tooltip_ability_ability_mirana_leap_custom_leap_distance" "跳跃距离:" +"dota_tooltip_ability_ability_mirana_leap_custom_leap_speed" "跳跃速度:" +"dota_tooltip_ability_ability_mirana_leap_custom_leap_height" "弧线高度:" +"dota_tooltip_ability_ability_mirana_leap_custom_leap_speedbonus" "移动速度:" +"dota_tooltip_ability_ability_mirana_leap_custom_leap_speedbonus_as" "攻击速度:" +"dota_tooltip_ability_ability_mirana_leap_custom_leap_bonus_duration" "增益持续时间:" +"dota_tooltip_ability_ability_mirana_leap_custom_Lore" "她不是逃跑——她在选择下一箭的角度。" + +"dota_tooltip_ability_ability_mirana_innate_custom" "Celestial Quiver" +"dota_tooltip_ability_ability_mirana_innate_custom_Description" "先天技能:远程攻击额外造成相当于 %mana_damage_pct%%% 最大法力值的魔法伤害。Leap 增益期间先天伤害提高 4 倍。友方露娜在 %moon_sisters_radius% 范围内时,米拉娜与露娜所受伤害降低 %moon_sisters_damage_reduction_pct%%%。可被衰竭禁用。" +"dota_tooltip_ability_ability_mirana_innate_custom_moon_sisters_radius" "与露娜联动半径:" +"dota_tooltip_ability_ability_mirana_innate_custom_moon_sisters_damage_reduction_pct" "所受伤害降低(姐妹):" +"dota_tooltip_ability_ability_mirana_innate_custom_mana_damage_pct" "法力伤害(%):" +"dota_tooltip_ability_ability_mirana_innate_custom_Lore" "塞勒梅涅的天界箭袋:每支箭都藏着一缕天火。" + +"dota_tooltip_ability_ability_mirana_moonlight_shadow_custom" "Moonlight Shadow" +"dota_tooltip_ability_ability_mirana_moonlight_shadow_custom_Description" "%duration% 秒内所有友方英雄不会死亡(生命值不会低于 1),移动速度 +%bonus_movement_speed%%% 。米拉娜额外获得 +%damage_bonus_base%%% 伤害,且效果每持续 1 秒再 +%damage_bonus_per_second%%% 。" +"dota_tooltip_ability_ability_mirana_moonlight_shadow_custom_duration" "持续时间:" +"dota_tooltip_ability_ability_mirana_moonlight_shadow_custom_bonus_movement_speed" "移动速度:" +"dota_tooltip_ability_ability_mirana_moonlight_shadow_custom_damage_bonus_base" "基础伤害加成(米拉娜):" +"dota_tooltip_ability_ability_mirana_moonlight_shadow_custom_damage_bonus_per_second" "每秒伤害加成(米拉娜):" +"dota_tooltip_ability_ability_mirana_moonlight_shadow_custom_scepter_ally_damage_share_pct" "友方伤害份额(%):" +"dota_tooltip_ability_ability_mirana_moonlight_shadow_custom_Scepter_Description" "月光之影下的友方英雄获得米拉娜成长中伤害加成的 %scepter_ally_damage_share_pct%%% 。" +"dota_tooltip_ability_ability_mirana_moonlight_shadow_custom_Lore" "月光庇护己方,直至狩猎结束。" + +"dota_tooltip_ability_special_bonus_unique_mirana_starstorm_damage" "+{s:bonus_damage} 星暴伤害" +"dota_tooltip_ability_special_bonus_unique_mirana_starstorm_cooldown" "星暴冷却 −{s:bonus_AbilityCooldown} 秒" +"dota_tooltip_ability_special_bonus_unique_mirana_starstorm_attack_proc" "{s:bonus_attack_proc_chance}% 几率(受幸运影响):攻击对目标敌人触发星落" +"dota_tooltip_ability_special_bonus_unique_mirana_sacred_arrow_side_arrows" "神圣之箭额外射出 2 支箭,与主箭呈 25° 夹角,分别位于左右两侧。" +"dota_tooltip_ability_special_bonus_unique_mirana_sacred_arrow_damage" "+{s:bonus_arrow_damage} 神圣之箭伤害" +"dota_tooltip_ability_special_bonus_unique_mirana_leap_charge_time" "跳跃充能恢复 −{s:bonus_AbilityChargeRestoreTime} 秒" +"dota_tooltip_ability_special_bonus_unique_mirana_leap_air_strike" "跳跃期间:对自身攻击距离内的每个敌人射出一发弹道(命中时完整攻击,每次跳跃每名敌人仅一次)" +"dota_tooltip_ability_special_bonus_unique_mirana_leap_landing_radius" "+{s:bonus_leap_landing_radius} 跳跃落地伤害范围" +"dota_tooltip_ability_special_bonus_unique_mirana_moonlight_duration" "月光之影持续时间 +{s:bonus_duration} 秒" +"dota_tooltip_ability_special_bonus_unique_mirana_innate_mana_damage" "+{s:bonus_mana_damage_pct}% 法力伤害(先天、星暴、神圣之箭)" + +"dota_tooltip_modifier_modifier_mirana_leap_custom_buff" "跳跃" +"dota_tooltip_modifier_modifier_mirana_leap_custom_buff_Description" "提升移动与攻击速度。先天伤害提高 4 倍。" + +"dota_tooltip_modifier_modifier_mirana_moonlight_shadow_custom" "月光之影" +"dota_tooltip_modifier_modifier_mirana_moonlight_shadow_custom_Description" "不会死亡。获得移动速度加成。伤害随时间提高(友方在神杖下获得米拉娜加成的一部分)。" + +"dota_tooltip_modifier_modifier_luna_twin_glaives_flight" "双刃剑" +"dota_tooltip_modifier_modifier_luna_twin_glaives_flight_Description" "月刃飞向目标或返回英雄。" + +"dota_tooltip_modifier_modifier_luna_moon_glaive_passive" "月亮长剑" +"dota_tooltip_modifier_modifier_luna_moon_glaive_passive_Description" "攻击可对目标附近敌人触发弹射。" + +"dota_tooltip_modifier_modifier_luna_twin_glaives_facet_vuln" "月刃易伤" +"dota_tooltip_modifier_modifier_luna_twin_glaives_facet_vuln_Description" "受到的物理与魔法伤害提高。" + +"dota_tooltip_ability_special_bonus_unique_luna_lucent_beam_damage" "+{s:bonus_lucent_beam_damage} 对 Lucent Beam 造成伤害" +"dota_tooltip_ability_special_bonus_unique_luna_orbit_radius" "+{s:bonus_cast_range} 到双刃剑的范围和飞行。" +"dota_tooltip_ability_special_bonus_unique_luna_glaive_extra_bounce" "+{s:bonus_moon_glaive_bounce_count} 弹跳月亮长剑" +"dota_tooltip_ability_special_bonus_unique_luna_blessing_aura" "+{s:bonus_blessing_radius} 到月亮祝福的半径" +"dota_tooltip_ability_special_bonus_unique_luna_lucent_beam_cooldown" "-{s:bonus_AbilityCooldown} 秒为 Lucent Beam 充电" +"dota_tooltip_ability_special_bonus_unique_luna_orbit_damage" "+{s:bonus_glaive_damage} 对双刃剑造成伤害" +"dota_tooltip_ability_special_bonus_unique_luna_eclipse_power" "+{s:bonus_eclipse_beam_count} 日食射线" +"dota_tooltip_ability_special_bonus_unique_luna_glaives_no_falloff" "月之刃:反弹伤害未减弱({s:bonus_bounce_falloff_pct}% 为之前的)" +"dota_tooltip_ability_special_bonus_unique_luna_moon_glaive_beam_attack" "月蚀或月光击中敌人后,露娜立即对该目标攻击两次" + +// -------------------- +"dota_tooltip_ability_ability_pudge_meat_hook_custom" "肉钩" +"dota_tooltip_ability_ability_pudge_meat_hook_custom_Description" "帕吉抛出肉钩:命中时造成 %hook_damage% 纯粹伤害 + 帕吉最大生命值 %max_health_damage_pct%%% 的伤害,并拉回敌人。" +"dota_tooltip_ability_ability_pudge_meat_hook_custom_hook_damage" "损坏:" +"dota_tooltip_ability_ability_pudge_meat_hook_custom_hook_distance" "挂钩范围:" +"dota_tooltip_ability_ability_pudge_meat_hook_custom_hook_speed" "钩索速度:" +"dota_tooltip_ability_ability_pudge_meat_hook_custom_hook_width" "钩索宽度:" +"dota_tooltip_ability_ability_pudge_meat_hook_custom_max_health_damage_pct" "命中时最大生命伤害(%):" + +"dota_tooltip_ability_ability_pudge_rot_custom" "腐烂" +"dota_tooltip_ability_ability_pudge_rot_custom_Description" "帕吉污染周围空气:每 %tick_interval% 秒对 %rot_radius% 内敌人造成魔法伤害(每秒 %rot_damage_per_sec% + 每秒增长 %rot_damage_increase_per_sec%,+ 帕吉最大生命 %max_health_damage_pct%%%/秒),减速并自身受伤。" +"dota_tooltip_ability_ability_pudge_rot_custom_rot_radius" "半径:" +"dota_tooltip_ability_ability_pudge_rot_custom_rot_damage_per_sec" "每秒损坏次数:" +"dota_tooltip_ability_ability_pudge_rot_custom_Scepter_Description" "使腐烂半径增加 %scepter_bonus_radius%,并使半径内的敌人生命值恢复减少 %scepter_enemy_hp_regen_reduction_pct%%%。" +"dota_tooltip_ability_ability_pudge_rot_custom_self_damage_pct_per_sec" "每秒最大自我伤害百分比。健康:" +"dota_tooltip_ability_ability_pudge_rot_custom_rot_slow_pct" "%SLOW:" +"dota_tooltip_ability_ability_pudge_rot_custom_scepter_enemy_hp_regen_reduction_pct" "敌人健康恢复率下降%:" +"dota_tooltip_ability_ability_pudge_rot_custom_rot_damage_increase_per_sec" "每秒伤害增长:" +"dota_tooltip_ability_ability_pudge_rot_custom_max_health_damage_pct" "每秒最大生命伤害(%):" + +"dota_tooltip_ability_ability_pudge_flesh_heap_custom" "肉堆" +"dota_tooltip_ability_ability_pudge_flesh_heap_custom_Description" "固有的被动能力。当敌人在 Dismember 的影响下死亡时,Pooj 会收到一个 Flesh Heap 堆栈。每一叠都赋予魔法力量和抵抗力。" +"dota_tooltip_ability_ability_pudge_flesh_heap_custom_stack_range" "堆栈设置半径:" +"dota_tooltip_ability_ability_pudge_flesh_heap_custom_strength_per_stack" "赌注的强度:" +"dota_tooltip_ability_ability_pudge_flesh_heap_custom_magic_resist_per_stack" "%ADD。魔术师。堆栈阻力:" + +"dota_tooltip_ability_ability_pudge_meat_shield_custom" "肉盾" +"dota_tooltip_ability_ability_pudge_meat_shield_custom_Description" "Pooja 肉不断阻挡来袭的伤害。" +"dota_tooltip_ability_ability_pudge_meat_shield_custom_block_damage" "损坏块:" +"dota_tooltip_ability_ability_pudge_meat_shield_custom_shard_block_per_strength" "每单位力的块数:" +"dota_tooltip_ability_ability_pudge_meat_shield_custom_Shard_Description" "肉盾还会额外阻挡每个 Pooja 力量单位的 %shard_block_per_strength% 伤害。" + +"dota_tooltip_ability_ability_pudge_dismember_custom" "肢解" +"dota_tooltip_ability_ability_pudge_dismember_custom_Description" "半径 %width% 内的敌人被拉向中心并受到周期性魔法伤害。每秒伤害:基础 %damage_per_second%,+ 力量 %strength_damage_pct%%%,+ 帕吉最大生命 %max_health_damage_pct%%%。%heal_from_damage_pct%%% 伤害转化为治疗;每 tick +%hunger_bonus% 饱食度。" +"dota_tooltip_ability_ability_pudge_dismember_custom_damage_per_second" "每秒基础伤害:" +"dota_tooltip_ability_ability_pudge_dismember_custom_strength_damage_pct" "每秒力量伤害(%):" +"dota_tooltip_ability_ability_pudge_dismember_custom_max_health_damage_pct" "每秒最大生命伤害(%):" +"dota_tooltip_ability_ability_pudge_dismember_custom_heal_from_damage_pct" "% 伤害治愈:" +"dota_tooltip_ability_ability_pudge_dismember_custom_pull_speed" "收缩速度:" +"dota_tooltip_ability_ability_pudge_dismember_custom_pulse_interval" "勾选间隔:" + +//帕奇人才 +"dota_tooltip_ability_special_bonus_unique_pudge_hook_range" "+{s:bonus_hook_distance} 到肉钩范围" +"dota_tooltip_ability_special_bonus_unique_pudge_hook_damage" "+{s:bonus_hook_damage} 肉钩伤害" +"dota_tooltip_ability_special_bonus_unique_pudge_rot_radius" "+{s:bonus_rot_radius} 到 Rot 半径" +"dota_tooltip_ability_special_bonus_unique_pudge_rot_damage" "+{s:bonus_rot_damage_per_sec} 每秒伤害 Rot" +"dota_tooltip_ability_special_bonus_unique_pudge_heap_range" "+{s:bonus_stack_range} 到 Flesh 堆半径" +"dota_tooltip_ability_special_bonus_unique_pudge_heap_strength" "+{s:bonus_block_damage} 到肉盾伤害方块" +"dota_tooltip_ability_special_bonus_unique_pudge_dismember_range" "+{s:bonus_cast_range} 到 Dismember 的范围" +"dota_tooltip_ability_special_bonus_unique_pudge_dismember_damage" "+{s:bonus_damage_per_second} 每秒伤害 肢解" + +//pudge 修饰符 +"dota_tooltip_modifier_pudge_meat_hook_bleed_custom" "钩子出血" +"dota_tooltip_modifier_pudge_meat_hook_bleed_custom_Description" "目标因 Pooja 的力量而流血并遭受周期性的物理伤害。" +"dota_tooltip_modifier_pudge_dismember_growth_custom" "臃肿的屠夫" +"dota_tooltip_modifier_pudge_dismember_growth_custom_Description" "在肢解期间,Pudge 的体型会根据他的最大健康状况而增加。" +"dota_tooltip_modifier_pudge_rot_slow_custom" "腐烂" +"dota_tooltip_modifier_pudge_rot_slow_custom_Description" "移动速度降低,使用 Aganim 后,Pudge 的健康恢复能力降低。" +"dota_tooltip_modifier_pudge_dismember_custom" "肢解" +"dota_tooltip_modifier_pudge_dismember_custom_Description" "目标无法移动并遭受周期性损坏。" +"dota_tooltip_modifier_pudge_rot_custom" "腐烂(光环)" +"dota_tooltip_modifier_pudge_rot_custom_Description" "帕奇在自己周围传播有毒光环,对敌人造成周期性伤害,但失去生命值。" +"dota_tooltip_modifier_pudge_meat_hook_pull" "肉钩" +"dota_tooltip_modifier_pudge_meat_hook_pull_Description" "目标被钩住并被强制移向 Pudge。" +"dota_tooltip_modifier_pudge_flesh_heap_custom" "肉堆" +"dota_tooltip_modifier_pudge_flesh_heap_custom_Description" "投注的持续效果:增加 Pooja 魔法的力量和抵抗力。" +"dota_tooltip_modifier_pudge_meat_shield_custom" "肉盾" +"dota_tooltip_modifier_pudge_meat_shield_custom_Description" "pudge 会阻挡部分来袭伤害,并通过自身的力量反映攻击者的伤害。" + +// -------------------- + +"DOTA_Tooltip_ability_ability_yuki_pohela" "Pohela" +"DOTA_Tooltip_ability_ability_yuki_pohela_Description" "Pohela 通过用冰壳覆盖盟友、增强对来袭伤害的抵抗力以及减慢攻击它的敌人的攻击速度来集中其冰力。" +"DOTA_Tooltip_ability_ability_yuki_pohela_AbilityCastRange" "半径:" +"DOTA_Tooltip_ability_ability_yuki_pohela_AbilityChannelTime" "应用程序持续时间:" +"DOTA_Tooltip_ability_ability_yuki_pohela_debuff_duration" "DEBUFF 持续时间:" +"DOTA_Tooltip_ability_ability_yuki_pohela_bonus_resistance" "% 伤害抵抗:" +"DOTA_Tooltip_ability_ability_yuki_pohela_reduce_atack_speed" "减少攻击速度:" + +"DOTA_Tooltip_modifier_ability_pohela_buff" "Pohela buff" +"DOTA_Tooltip_modifier_ability_pohela_buff_Description" "受到可靠保护的英雄^^" + + +"DOTA_Tooltip_ability_ability_yuki_frostshtorm" "Frostshtorm" +"DOTA_Tooltip_ability_ability_yuki_frostshtorm_Description" "英雄用他的冰亵渎了一个区域,进入该区域的人造成的伤害较少,而盟友受到的伤害则减少了一半。" +"DOTA_Tooltip_ability_ability_yuki_frostshtorm_AbilityCastRange" "应用半径:" +"DOTA_Tooltip_ability_ability_yuki_frostshtorm_radius" "半径" +"DOTA_Tooltip_ability_ability_yuki_frostshtorm_duration" "持续时间:" +"DOTA_Tooltip_ability_ability_yuki_frostshtorm_dmg_reduced" "减少对敌人的伤害:" +"DOTA_Tooltip_ability_ability_yuki_frostshtorm_dmg_increese" "增加对盟友的伤害:" + +"DOTA_Tooltip_ability_ability_yuki_revenge" "复仇" +"DOTA_Tooltip_ability_ability_yuki_revenge_Description" "雪女的aura冻结敌人:敌人每秒受到相当于她最大法力值 %mana_damage_pct%%% 加上 %base_damage% 的魔法伤害;盟友每秒恢复 %heal% 生命值。" +"DOTA_Tooltip_ability_ability_yuki_revenge_mana_damage_pct" "每秒伤害占最大法力值的百分比:" +"DOTA_Tooltip_ability_ability_yuki_revenge_radius" "半径" +"DOTA_Tooltip_ability_ability_yuki_revenge_heal" "每秒治疗次数:" +"DOTA_Tooltip_ability_ability_yuki_revenge_base_damage" "每秒基础伤害:" + +"DOTA_Tooltip_modifier_ability_revenge_buff" "复仇光环" +"DOTA_Tooltip_modifier_ability_revenge_buff_Description" "寒冷让你暖和起来。" + + + + + +"DOTA_Tooltip_ability_ability_yuki_ritual" "仪式" +"DOTA_Tooltip_ability_ability_yuki_ritual_Description" "英雄执行寒冷仪式,扩大所有盔甲并再生该地区的盟军英雄。减慢敌人的移动和攻击速度。" +"DOTA_Tooltip_ability_ability_yuki_ritual_AbilityCastRange" "应用范围:" +"DOTA_Tooltip_ability_ability_yuki_ritual_duration" "持续时间:" +"DOTA_Tooltip_ability_ability_yuki_ritual_bonus_armor" "%ADD。ARMOR:" +"DOTA_Tooltip_ability_ability_yuki_ritual_bonus_hp_regen" "%ADD。健康恢复:" +"DOTA_Tooltip_ability_ability_yuki_ritual_bonus_hp_regen_pct" "从 MAX 恢复健康百分比。健康:" + + "DOTA_Tooltip_modifier_ability_ritual_buff" "仪式增益" +"DOTA_Tooltip_modifier_ability_ritual_buff_Description" "受寒冷祝福。" + + +"DOTA_Tooltip_ability_ability_yuki_snowman" "雪人" +"DOTA_Tooltip_ability_ability_yuki_snowman_Description" "Yuki-onna 放了一个可爱的雪人,向盟友扔雪球。雪球在所有盟军英雄的半径范围内枯萎,并减慢敌方怪物的攻击速度。" +"DOTA_Tooltip_ability_ability_yuki_snowman_bonus_health" "健康:" +"DOTA_Tooltip_ability_ability_yuki_snowman_max_snowman" "MAX。SNOWMEN:" +"DOTA_Tooltip_ability_ability_yuki_snowmanl_slow_as" "攻击速度减慢:" +"DOTA_Tooltip_ability_ability_yuki_snowball" "雪球" +"DOTA_Tooltip_ability_ability_yuki_snowball_Description" "雪球><" +"DOTA_Tooltip_ability_ability_yuki_snowball_radius" "半径:" +"DOTA_Tooltip_ability_ability_yuki_snowball_heal" "治疗:" +"DOTA_Tooltip_ability_ability_yuki_snowball_slow_as" "攻击速度减慢:" + +"DOTA_Tooltip_ability_ability_yuki_challenge" "来自 yuki 的挑战" +"DOTA_Tooltip_ability_ability_yuki_challenge_Description" "雪女考验友方英雄:%duration% 秒内施加沉默、眩晕、缴械,并根据主属性造成周期性纯粹伤害。若英雄存活,每完成一次试炼永久获得 %bonus_main%%% 主属性。试炼中每次死亡使后续伤害降低 %dmg_reduce%%%,成功则提高 %dmg_incr%%%." +"DOTA_Tooltip_ability_ability_yuki_challenge_Scepter_Description" "将主属性增加另一个 %main_pct%" +"DOTA_Tooltip_ability_ability_yuki_challenge_Shard_Description" "减少每个属性的伤害" +"DOTA_Tooltip_ability_ability_yuki_challenge_duration" "持续时间:" +"DOTA_Tooltip_ability_ability_yuki_challenge_dmg_per_atr" "每个属性的损坏:" +"DOTA_Tooltip_ability_ability_yuki_challenge_interval" "损坏间隔:" +"DOTA_Tooltip_ability_ability_yuki_challenge_bonus_main" "每次试炼 %bonus_main%%% 主属性:" +"DOTA_Tooltip_ability_ability_yuki_challenge_dmg_incr" "损害增强:" +"DOTA_Tooltip_ability_ability_yuki_challenge_dmg_reduce" "收到减少:" +"DOTA_Tooltip_ability_ability_yuki_challenge_Note0" "目标身上试炼增益的提示中会显示已累计的主属性加成。" + + +"DOTA_Tooltip_modifier_ability_challenge_buff" "来自 yuki 的挑战" +"DOTA_Tooltip_modifier_ability_challenge_buff_Description" "你是一个游击队员。" +"dota_tooltip_modifier_challenge_buff" "雪女试炼" +"dota_tooltip_modifier_challenge_buff_Description" "主属性累计加成:+%dMODIFIER_PROPERTY_TOOLTIP%。" +"dota_tooltip_modifier_challenge_debuff" "雪女试炼" +"dota_tooltip_modifier_challenge_debuff_Description" "正在接受试炼:控制、无法治疗并受到周期性纯粹伤害。" + + +"DOTA_Tooltip_ability_special_bonus_unique_yuki_pohela_resistance" "+{s:bonus_bonus_resistance}% 来自 Pohela 能力的抵抗力。" +"DOTA_Tooltip_ability_special_bonus_unique_yuki_frostshtorm_duration" "+{s:bonus_duration} 秒到 FrostShtorm 持续时间。" +"DOTA_Tooltip_ability_special_bonus_unique_yuki_revenge_damage" "+{s:bonus_base_damage} 从复仇能力中获得基础伤害" +"DOTA_Tooltip_ability_special_bonus_unique_yuki_revenge_heal" "+{s:bonus_heal} 用于治疗复仇能力。" +"DOTA_Tooltip_ability_special_bonus_unique_yuki_snowman_max" "+{s:bonus_max_snowman} 雪人能力充能" + + +// ------------------- + + "DOTA_Tooltip_ability_ability_star_devour" "地狱吞噬" + "DOTA_Tooltip_ability_ability_star_devour_Description" "英雄可以吸收怪人并获得1 堆栈能力地狱吞噬" + "DOTA_Tooltip_ability_ability_star_devour_creep_level" "最大蠕变等级:" + "DOTA_Tooltip_ability_ability_star_devour_max_stack" "最大堆栈数:" + + + "DOTA_Tooltip_ability_ability_hellstep" "地狱之步" + "DOTA_Tooltip_ability_ability_hellstep_Description" "英雄喷出一股火焰,每秒都越来越猛烈地灼烧对手的身体,同时持续治疗范围内的友军,并且该能力还会增加对盟友和 Sargatanas 本人的伤害,伤害范围在该能力范围内。" + "DOTA_Tooltip_ability_ability_hellstep_min_damage" "MIN。DAMAGE:" + "DOTA_Tooltip_ability_ability_hellstep_max_damage" "最大伤害:" + "DOTA_Tooltip_ability_ability_hellstep_max_duration" "最大伤害秒数:" + "DOTA_Tooltip_ability_ability_hellstep_radius" "半径:" + "DOTA_Tooltip_ability_ability_hellstep_duration" "持续时间" + "DOTA_Tooltip_ability_ability_hellstep_stack_overhell" "调用堆栈" + "DOTA_Tooltip_ability_ability_hellstep_bonus_damage" "奖励伤害:" + "DOTA_Tooltip_ability_ability_hellstep_bonus_attack_speed" "奖励攻击速度:" + + "DOTA_Tooltip_ability_ability_firecleave_creep" "火劈" + "DOTA_Tooltip_ability_ability_firecleave_creep_fire_damage" "火灾损害:" + + "DOTA_Tooltip_ability_ability_firecleave" "火劈" + "DOTA_Tooltip_ability_ability_firecleave_Description" "英雄以无节制的力量攻击一个对手,攻击后面的对手并放火攻击原始目标。" + "DOTA_Tooltip_ability_ability_firecleave_great_cleave_damage" "%切割伤害:" + "DOTA_Tooltip_ability_ability_firecleave_fire_damage" "火灾损害:" + "DOTA_Tooltip_ability_ability_firecleave_duration" "火灾持续时间:" + + "DOTA_Tooltip_ability_ability_sunflame" "太阳火焰" + "DOTA_Tooltip_ability_ability_sunflame_Description" "英雄喷出恶魔之火,造成伤害并击败过热造成的减益效果,增加火焰造成的伤害" + "DOTA_Tooltip_ability_ability_sunflame_range" "半径:" + "DOTA_Tooltip_ability_ability_sunflame_stack_overhell" "过热堆栈:" + "DOTA_Tooltip_ability_ability_sunflame_damage" "损坏:" + "DOTA_Tooltip_ability_ability_sunflame_unduced" "减少伤害百分比:" + "DOTA_Tooltip_ability_ability_sunflame_damage_meta" "超强损坏:" + "DOTA_Tooltip_ability_ability_sunflame_unduced_meta" "减少伤害百分比:" + + "DOTA_Tooltip_ability_ability_hell_summon" "地狱召唤" + "DOTA_Tooltip_ability_ability_hell_summon_Description" "伤害取决于角色属性。每点力量提供生命值与伤害。玩家可以控制召唤的单位。" +"DOTA_Tooltip_ability_ability_hell_summon_str_dmg" "1 次强制损坏" +"DOTA_Tooltip_ability_ability_hell_summon_str_hp" "HP FOR 1 STRENGTH:" + + +"DOTA_Tooltip_ability_ability_metamorphosis" "旋转变形" +"DOTA_Tooltip_ability_ability_metamorphosis_Description" "恶魔会恢复其真实形态,增强其技能并减少伤害(伤害块的百分比取决于守护进程的强度,但必须限制在某个值内)。" +"DOTA_Tooltip_ability_ability_metamorphosis_duration" "持续时间:" +"DOTA_Tooltip_ability_ability_metamorphosis_bonus_resistance" "强度伤害奖励抵抗:" +"DOTA_Tooltip_ability_ability_metamorphosis_max_resistance" "最大伤害抗性:" +"DOTA_Tooltip_ability_ability_metamorphosis_radius" "半径:" +"DOTA_Tooltip_ability_ability_metamorphosis_armor_ag" "阿吉拉盔甲:" +"DOTA_Tooltip_ability_ability_metamorphosis_attack_speed_ag" "每点敏捷攻击速度:" + +"DOTA_Tooltip_Ability_special_bonus_unique_sargatanas_max_stack" "+{s:bonus_max_stack} 到吞噬能力的堆栈数。" + +"DOTA_Tooltip_Ability_special_bonus_unique_sargatanas_str_dmg" "+{s:bonus_str_dmg} 伤害称为地狱生物召唤能力。" + +"DOTA_Tooltip_Ability_special_bonus_unique_sargatanas_duration_1" "+{s:bonus_duration} 地狱步能力持续时间" + + + +// -------------------- + +"DOTA_Tooltip_ability_ability_rubick_telekinesis_custom" "心灵感应" +"DOTA_Tooltip_ability_ability_rubick_telekinesis_custom_Description" "Rubik 将目标举到空中。敌人着陆后会在半径范围内受到伤害和击晕。盟军可以在空中导航、接受治疗、获得攻击范围、攻击速度和魔法伤害的奖励。" +"DOTA_Tooltip_ability_ability_rubick_telekinesis_custom_Scepter_Description" "对自己和友军可无限维持。目标在空中的位置每2秒更新一次。若目标离开允许范围,效果会立刻结束。" +"DOTA_Tooltip_ability_ability_rubick_telekinesis_custom_AbilityCastRange" "范围:" +"DOTA_Tooltip_ability_ability_rubick_telekinesis_custom_land_damage" "着陆损坏:" +"DOTA_Tooltip_ability_ability_rubick_telekinesis_custom_land_stun_duration" "令人惊叹的持续时间:" +"DOTA_Tooltip_ability_ability_rubick_telekinesis_custom_land_stun_radius" "半径:" +"DOTA_Tooltip_ability_ability_rubick_telekinesis_custom_throw_distance" "投掷范围:" +"DOTA_Tooltip_ability_ability_rubick_telekinesis_custom_ally_heal_per_second" "每秒处理次数:" +"DOTA_Tooltip_ability_ability_rubick_telekinesis_custom_ally_attack_range_bonus" "添加。攻击范围:" +"DOTA_Tooltip_ability_ability_rubick_telekinesis_custom_ally_attack_speed_bonus" "ext。攻击速度:" +"DOTA_Tooltip_ability_ability_rubick_telekinesis_custom_ally_bonus_magic_damage" "ext。魔术师。损坏:" +"DOTA_Tooltip_ability_ability_rubick_telekinesis_custom_ally_bonus_magic_damage_per_int" "魔术师。智力损害:" +"DOTA_Tooltip_ability_ability_rubick_telekinesis_custom_Lore" "当世界似乎受到重力定律的约束时,只有真正的奥术大师才能将囚犯向上抬起并控制他的命运。 魔方最喜欢的技巧—扰乱通常的魔法流动,将混乱变成心灵感应控制的艺术。" +"dota_hud_error_rubick_telekinesis_boss" "无法对 Boss 施放。" +"dota_hud_error_rubick_telekinesis_homer" "无法对村长施放。" + +"DOTA_Tooltip_ability_ability_rubick_fade_bolt_custom" "褪色螺栓" +"DOTA_Tooltip_ability_ability_rubick_fade_bolt_custom_Description" "Rubik 创建一股神秘能量流,在敌人之间跳跃,造成 %damage% 魔法伤害,并通过 %attack_damage_reduction%%% 减少他们的攻击伤害。每次跳跃都会通过 %jump_damage_reduction_pct%%% 减少对螺栓的损坏。" +"DOTA_Tooltip_ability_ability_rubick_fade_bolt_custom_Shard_Description" "允许褪色螺栓在同一次连锁中再次命中已被击中的目标。" +"DOTA_Tooltip_ability_ability_rubick_fade_bolt_custom_AbilityCastRange" "范围:" +"DOTA_Tooltip_ability_ability_rubick_fade_bolt_custom_damage" "损坏:" +"DOTA_Tooltip_ability_ability_rubick_fade_bolt_custom_radius" "跳跃半径:" +"DOTA_Tooltip_ability_ability_rubick_fade_bolt_custom_attack_damage_reduction" "攻击伤害减少百分比:" +"DOTA_Tooltip_ability_ability_rubick_fade_bolt_custom_jump_damage_reduction_pct" "每次跳跃减少伤害百分比:" +"DOTA_Tooltip_ability_ability_rubick_fade_bolt_custom_duration" "消除持续时间:" +"DOTA_Tooltip_ability_ability_rubick_fade_bolt_custom_Lore" "Rubik 最喜欢的杀死杀手的咒语 — 简单但有效的环境。" + +"DOTA_Tooltip_ability_ability_rubick_arcane_supremacy" "奥术霸权" +"DOTA_Tooltip_ability_ability_rubick_arcane_supremacy_Description" "魔方或半径内的盟友对敌人造成的魔法伤害的一部分会分配给半径内的所有其他敌人。伤害被平均分配并根据能力的百分比施加。" +"DOTA_Tooltip_ability_ability_rubick_arcane_supremacy_aura_radius" "半径:" +"DOTA_Tooltip_ability_ability_rubick_arcane_supremacy_damage_pct" "% 伤害分布:" +"DOTA_Tooltip_ability_ability_rubick_arcane_supremacy_spell_amp" "%DOP 法术伤害:" +"DOTA_Tooltip_ability_ability_rubick_arcane_supremacy_spell_amp_pct_lvl" "每级 DOP 法术伤害百分比:" +"DOTA_Tooltip_ability_ability_rubick_arcane_supremacy_Lore" "了解魔法最私密的奥秘不仅使魔方能够强化他的咒语,而且还能扭曲他周围魔法伤害的本质。敌人最好远离—毕竟,即使他们奇迹般地抵抗,一些破坏力仍然会找到他们。" + +"DOTA_Tooltip_ability_ability_rubick_spellsteal_custom" "法术钢" +"DOTA_Tooltip_ability_ability_rubick_spellsteal_custom_Description" "Rubik 窃取了最后使用的盟友能力。被盗能力将取代当前被盗能力,并持续到下一次盗窃为止。" +"DOTA_Tooltip_ability_ability_rubick_spellsteal_custom_AbilityCastRange" "范围:" +"DOTA_Tooltip_ability_ability_rubick_spellsteal_custom_Lore" "真正的魔术师不会创造咒语—他会从那些不够小心的人那里借用咒语。鲁比克吸收了敌人的魔法,使其成为他自己的魔法。" + +"DOTA_Tooltip_ability_special_bonus_unique_rubick_telekinesis_stun" "+{s:bonus_land_stun_duration} 当心灵感应落地时,秒数变为击晕。" +"DOTA_Tooltip_ability_special_bonus_unique_rubick_telekinesis_land_radius" "+{s:bonus_land_stun_radius} 到心灵感应着陆半径" +"DOTA_Tooltip_ability_special_bonus_unique_rubick_fade_bolt_damage" "+{s:bonus_damage} 伤害褪色螺栓" +"DOTA_Tooltip_ability_special_bonus_unique_rubick_spellsteal_cooldown" "-{s:bonus_AbilityCooldown} 法术窃取充值秒" +"DOTA_Tooltip_ability_special_bonus_unique_rubick_arcane_supremacy_damage_pct" "+{s:bonus_damage_pct}% 奥术至尊伤害分布" +"DOTA_Tooltip_ability_special_bonus_unique_rubick_telekinesis_charges" "+{s:bonus_AbilityCharges} 心灵感应费用" +"DOTA_Tooltip_ability_special_bonus_unique_rubick_fade_bolt_convert" "褪色箭还能提供魔法增强,这取决于这种能力的伤害减少。" + +// -------------------- + +"DOTA_Tooltip_ability_ability_fire_punishment" "火灾惩罚" +"DOTA_Tooltip_ability_ability_fire_punishment_Description" "Smaug 将其目标设置为 %AbilityDuration% 秒,对其造成周期性伤害 %damage% + %pct_dmg%%%,同时将其魔法抗性降低 %debuff_magic%%%。" +"DOTA_Tooltip_ability_ability_fire_punishment_Lore" "燃烧了吗?" +"DOTA_Tooltip_ability_ability_fire_punishment_radius" "ARSON RADIUS:" + +"DOTA_Tooltip_ability_ability_jakiro_buff_1" "飞向月球" +"DOTA_Tooltip_ability_ability_jakiro_buff_1_Description" "

Pasivnoe:蜻蜓

Smaug 飞得更高,导致其机动性丧失 25%,但这会导致它穿过粉刺。" +"DOTA_Tooltip_ability_ability_jakiro_buff_1_Lore" "有翅膀的" + +"DOTA_Tooltip_ability_ability_incandescent_fury" "白炽狂怒" +"DOTA_Tooltip_ability_ability_incandescent_fury_Description" "史矛革喷出熔岩,以 %burn_interval% 秒的间隔对敌人造成 %damage% + %pct_dmg%%% 的伤害。" +"DOTA_Tooltip_ability_ability_incandescent_fury_Lore" "最糟糕的死亡是活活烧死,但我不需要担心——史矛革。" +"DOTA_Tooltip_ability_ability_incandescent_fury_Scepter_Description" "将充值减少至 %AbilityCooldown% 秒" +"DOTA_Tooltip_ability_ability_incandescent_fury_cast_range" "半径:" +"DOTA_Tooltip_ability_ability_incandescent_fury_duration" "持续时间:" + +"DOTA_Tooltip_ability_ability_dragon_fear_aura" "龙的恐惧" +"DOTA_Tooltip_ability_ability_dragon_fear_aura_Description" "攻击史矛革的敌人无法对抗自己的恐惧,这导致他们的攻击力减弱,而史矛革本人也会受到额外的魔法伤害。" +"DOTA_Tooltip_ability_ability_dragon_fear_aura_Lore" "史矛革的恐怖气息甚至让他们无法停止颤抖。" +"DOTA_Tooltip_ability_ability_dragon_fear_aura_outgoing" "%OUTGOING 伤害:" +"DOTA_Tooltip_ability_ability_dragon_fear_aura_incoming" "%INBOUND 伤害:" +"DOTA_Tooltip_ability_ability_dragon_fear_aura_duration" "持续时间:" + +"DOTA_Tooltip_ability_ability_dragon_gold_deal" "龙金交易" +"DOTA_Tooltip_ability_ability_dragon_gold_deal_Description" "史矛革非常珍惜它的东西,并从中获益更多,获得了额外的盔甲、伤害和健康。" +"DOTA_Tooltip_ability_ability_dragon_gold_deal_Lore" "史矛革——黄金让你变得更富有,但对我来说,黄金就是力量。" +"DOTA_Tooltip_ability_ability_dragon_gold_deal_gold" "堆栈的黄金数量:" +"DOTA_Tooltip_ability_ability_dragon_gold_deal_physical_armor" "额外护甲:" +"DOTA_Tooltip_ability_ability_dragon_gold_deal_spell_amp" "DOP。魔术师。损坏:" +"DOTA_Tooltip_ability_ability_dragon_gold_deal_health_bonus" "DOP。健康:" +"DOTA_Tooltip_ability_modifier_dragon_gold_deal_buff" "黄金疯狂" +"DOTA_Tooltip_ability_modifier_dragon_gold_deal_buff_Description" "由于英雄有犹太倾向,因此获得了奖励:伤害、生命值和盔甲。" + +"DOTA_Tooltip_ability_ability_dragon_reward" "龙奖励" +"DOTA_Tooltip_ability_ability_dragon_reward_Description" "史矛革利用死者的灵魂来达到自己的目的。" +"DOTA_Tooltip_ability_ability_dragon_reward_Lore" "灵魂收藏,这里有一个渔夫,一个猎人,甚至还有一个蟾蜍灵魂?好的。会的。" +"DOTA_Tooltip_ability_ability_dragon_reward_max" "最大淋浴:" +"DOTA_Tooltip_ability_ability_dragon_reward_bonus_attributes" "每堆附件奖励:" +"DOTA_Tooltip_ability_modifier_dragon_reward" "积累的灵魂" +"DOTA_Tooltip_ability_modifier_dragon_reward_Description" "英雄获得了奖励属性。" + +"DOTA_Tooltip_ability_ability_dragon_scales" "龙鳞" +"DOTA_Tooltip_ability_ability_dragon_scales_Description" "史矛革用鳞片包裹着他的身体,使他能够避免任何伤害并将其挡在敌人身上。" +"DOTA_Tooltip_ability_ability_dragon_scales_Lore" "你可以用这个等级锻造出坚不可摧的盔甲,但史矛革不打算分享这些等级。" +"DOTA_Tooltip_ability_ability_dragon_scales_reflect" "%REFLECTION:" +"DOTA_Tooltip_ability_ability_dragon_scales_duration" "持续时间:" +"DOTA_Tooltip_ability_ability_dragon_scales_max_radius" "半径:" +"DOTA_Tooltip_ability_modifier_dragon_scales" "龙鳞" +"DOTA_Tooltip_ability_modifier_dragon_scales_Description" "你的身体变得更强壮,并开始反映损伤。" + +"DOTA_Tooltip_ability_special_bonus_unique_smaug_dragon_scales_reflect" "+{s:bonus_reflect}% 到反射尺度反射" +"DOTA_Tooltip_ability_special_bonus_unique_smaug_dragon_scales_cd" "-{s:bonus_AbilityCooldown} 充电秒数反映" +"DOTA_Tooltip_ability_special_bonus_unique_smaug_dragon_gold_deal" "-{s:bonus_gold} 黄金获得龙金交易" +"DOTA_Tooltip_ability_special_bonus_unique_smaug_incandescent_fury" "+{s:bonus_damage} 对白炽狂怒能力的伤害。" +"DOTA_Tooltip_ability_special_bonus_unique_smaug_fire_punishment" "+100% 的射击惩罚能力" + +// -------------------- + +"DOTA_Tooltip_ability_dark_friends" "黑暗的朋友" +"DOTA_Tooltip_ability_dark_friends_Description" "针对每个能力等级调用黑暗盟友。每个召唤单位都会为半径为 %aura_radius% 的盟友提供光环。光环的效果会添加到每个召唤单位 +"DOTA_Tooltip_ability_dark_friends_Lore" "在古代地下墓穴的阴影下,阵亡战士的灵魂安息于此,长谷学会了从死者的世界中召唤忠实的仆人。这些黑暗的朋友曾经是伟大的战士,现在却在永恒的战斗中为他服务,带着过去时代的力量和智慧。" +"DOTA_Tooltip_ability_dark_friends_Note0" "这个生物得到了它主人的克里特岛修饰符。" +"DOTA_Tooltip_ability_dark_friends_aura_bonus_damage" "添加伤害" +"DOTA_Tooltip_ability_dark_friends_aura_bonus_attack_speed" "攻击速度添加" +"DOTA_Tooltip_ability_dark_friends_aura_bonus_movespeed_pct" "%移动速度" +"DOTA_Tooltip_ability_dark_friends_aura_bonus_armor" "添加装甲" + +"DOTA_Tooltip_modifier_dark_friends_create" "超越黑暗朋友" +"DOTA_Tooltip_modifier_dark_friends_create_Description" "英雄获得 %dMODIFIER_PROPERTY_PREATTACK_BONUS_DAMAGE% 伤害、%dMODIFIER_PROPERTY_ATTACKSPEED_BONUS_CONSTANT% 攻击速度、%dMODIFIER_PROPERTY_MOVESPEED_BONUS_PERCENTAGE% 移动速度和 %dMODIFIER_PROPERTY_PHYSICAL_ARMOR_BONUS% 盔甲。" + +"DOTA_Tooltip_ability_world_destroyer" "世界毁灭者" +"DOTA_Tooltip_ability_world_destroyer_Description" "纳加什召唤来自地狱深处的毁灭力量,在指定地点引发爆炸。爆炸抽离敌人的灵魂,使其为纳加什作战 %dominate_duration% 秒。噬魂者还会额外获得主人 50% 的生命值。" +"DOTA_Tooltip_ability_world_destroyer_Lore" "当世界崩溃时,Nagash 知道如何利用这些裂缝为自己谋利。" +"DOTA_Tooltip_ability_world_destroyer_Note0" "只能驯服低于其等级的生物。" +"DOTA_Tooltip_ability_world_destroyer_radius" "半径:" +"DOTA_Tooltip_ability_world_destroyer_health_soul_eater" "淋浴吸吮者健康:" +"DOTA_Tooltip_ability_world_destroyer_dominate_duration" "淋浴时长:" +"DOTA_Tooltip_ability_world_destroyer_bonus_damage" "爆炸伤害:" +"DOTA_Tooltip_ability_world_destroyer_bonus_attack_speed" "添加。攻击速度:" +"DOTA_Tooltip_ability_world_destroyer_bonus_armor" "添加。盔甲:" +"DOTA_Tooltip_ability_world_destroyer_Shard_Description" "使用 World Destroyer 或 Leader Call 时,纳加什在 %shard_invulnerable_duration% 秒内变为无敌。" + +"DOTA_Tooltip_ability_leader_call" "领导者呼叫" +"DOTA_Tooltip_ability_leader_call_Description" "纳加什捐献部分生命值,以召唤他压抑的灵魂中的爆发力。爆炸在 %explosion_radius% 范围内造成 %explosion_damage% 伤害,并为每个被炸毁或死亡的灵魂提供属性加成。当英雄死后,他会失去他积累的灵魂的%lost_souls_pct%%%。" +"DOTA_Tooltip_ability_leader_call_Lore" "领导者必须愿意牺牲一切来赢得胜利。" +"DOTA_Tooltip_ability_leader_call_explosion_radius" "爆炸半径:" +"DOTA_Tooltip_ability_leader_call_explosion_damage" "爆炸伤害:" +"DOTA_Tooltip_ability_leader_call_bonus_stats_per_soul" "灵魂表现奖励:" + +"DOTA_Tooltip_modifier_leader_token" "灵魂标记" +"DOTA_Tooltip_modifier_leader_token_Description" "没什么特别的,只是英雄在整个过程中设法吞噬的灵魂数量。" + +"DOTA_Tooltip_ability_fighting_up" "战斗起来" +"DOTA_Tooltip_ability_fighting_up_Description" "纳加什陷入战斗狂怒,将半径%内所有盟友的伤害、移动速度和攻击速度增加%持续时间%秒。" +"DOTA_Tooltip_ability_fighting_up_Lore" "战斗中没有怀疑的余地——只有愤怒和决心。" +"DOTA_Tooltip_ability_fighting_up_radius" "半径:" +"DOTA_Tooltip_ability_fighting_up_duration" "持续时间:" +"DOTA_Tooltip_ability_fighting_up_bonus_damage_pct" "%DOP。损坏:" +"DOTA_Tooltip_ability_fighting_up_bonus_movespeed_pct" "%DOP。移动速度:" +"DOTA_Tooltip_ability_fighting_up_bonus_attackspeed_pct" "%DOP。攻击速度:" + +"DOTA_Tooltip_ability_war_for_life" "为生命而战" +"DOTA_Tooltip_ability_war_for_life_Description" "纳迦什向死亡宣战:复活 %radius% 范围内所有阵亡友军,使其以 %health_res_pct%% 生命值复生,并在 %duration% 秒内获得 %inc_damage_res%% 伤害减免。" +"DOTA_Tooltip_ability_war_for_life_Lore" "生命是最伟大的战斗,Nagash 已经准备好战斗到最后。" +"DOTA_Tooltip_ability_war_for_life_radius" "半径:" +"DOTA_Tooltip_ability_war_for_life_health_res_pct" "复活者的健康状况:" +"DOTA_Tooltip_ability_war_for_life_inc_damage_res" "抗伤害百分比:" +"DOTA_Tooltip_ability_war_for_life_duration" "持续时间:" + +"DOTA_Tooltip_ability_dark_golem" "黑暗魔像" +"DOTA_Tooltip_ability_dark_golem_Description" "纳迦什化身强大的黑暗魔像,攻击附带 %cleave_damage%%% 劈砍伤害,作用距离为 %cleave_distance%。魔像会根据灵魂数获得伤害、攻速和移速加成,持续 %duration% 秒。" +"DOTA_Tooltip_ability_dark_golem_Lore" "来自地狱深处的最伟大的黑暗仆人——一个由堕落战士的灵魂锻造出来的傀儡。" +"DOTA_Tooltip_ability_dark_golem_cleave_damage" "%漂移伤害:" +"DOTA_Tooltip_ability_dark_golem_cleave_distance" "切割距离:" +"DOTA_Tooltip_ability_dark_golem_cleave_starting_width" "初始切割宽度:" +"DOTA_Tooltip_ability_dark_golem_cleave_ending_width" "最终切割宽度:" +"DOTA_Tooltip_ability_dark_golem_bonus_damage_pct_per_token" "灵魂伤害百分比:" +"DOTA_Tooltip_ability_dark_golem_bonus_attack_speed_pct_per_token" "每个灵魂的攻击速度百分比:" +"DOTA_Tooltip_ability_dark_golem_bonus_movespeed_pct_per_token" "每次淋浴的移动速度百分比:" +"DOTA_Tooltip_ability_dark_golem_duration" "持续时间:" + +"DOTA_Tooltip_modifier_dark_golem_buff" "黑暗魔像" +"DOTA_Tooltip_modifier_dark_golem_buff_Description" "你无法被阻止。" + +"DOTA_Tooltip_ability_special_bonus_unique_nagash_leader_call_damage" "+{s:bonus_unit_health_for_damage}% 招募的生物领导者呼叫的最大生命值造成的伤害。" +"DOTA_Tooltip_ability_special_bonus_unique_nagash_dark_friend_damage_pct" "+{s:bonus_outgoing_damage_pct}% 对黑暗朋友生物的伤害。" + + + + + +// -------------------- + +"DOTA_Tooltip_ability_ability_bloodrage" "血怒" +"DOTA_Tooltip_ability_ability_bloodrage_Description" "血腥猎人将受害者撕碎,吸收他的血液,并在 %stack_duration% 秒的 %stack% 馅饼中接受眼镜。每下一次赌注,英雄都会获得额外的伤害和攻击速度。" +"DOTA_Tooltip_ability_ability_bloodrage_physical_vampirism" "%ADD。VAMPIRISM:" +"DOTA_Tooltip_ability_ability_bloodrage_Lore" "他的爪子比任何刀片都锋利。" +"DOTA_Tooltip_ability_ability_bloodrage_bonus_damage" "添加伤害:" +"DOTA_Tooltip_ability_ability_bloodrage_bonus_attack_speed" "添加攻击速度:" +"DOTA_Tooltip_ability_ability_bloodrage_stack_damage" "每堆损坏:" +"DOTA_Tooltip_ability_ability_bloodrage_stack_attackspeed" "每堆攻击速度:" +"DOTA_Tooltip_ability_ability_bloodrage_duration" "持续时间:" +"DOTA_Tooltip_ability_ability_bloodrage_scepter_Description" "英雄收到%stack%堆栈。" + +"DOTA_Tooltip_ability_ability_bloodstained_memory" "血迹斑斑的记忆" +"DOTA_Tooltip_ability_ability_bloodstained_memory_Description" "力量存在于你的偏见之中,而血猎者知道如何释放这种力量。" +"DOTA_Tooltip_ability_ability_bloodstained_memory_healthbonus" "添加。每堆健康状况:" +"DOTA_Tooltip_ability_ability_bloodstained_memory_movespeed" "每堆栈的真实感知速度:" +"DOTA_Tooltip_ability_ability_bloodstained_memory_Lore" "没有什么比我的思想更强大——血液猎人。" + + + +"DOTA_Tooltip_ability_ability_illusion_of_blood" "血液的幻觉" +"DOTA_Tooltip_ability_ability_illusion_of_blood_Description" "血猎人通过捐献 15 杯血向死者寻求帮助,将他的复制品召唤到这个世界来保护自己。" +"DOTA_Tooltip_ability_ability_illusion_of_blood_Lore" "没有什么比我更好——利己主义的血猎者。" +"DOTA_Tooltip_ability_ability_illusion_of_blood_images_count" "幻觉数量:" +"DOTA_Tooltip_ability_ability_illusion_of_blood_blood" "<字体颜色='#980002'>血液成本:" +"DOTA_Tooltip_ability_ability_illusion_of_blood_illusion_duration" "幻觉寿命:" +"DOTA_Tooltip_ability_ability_illusion_of_blood_outgoing_damage" "%ILLUSION 伤害:" +"DOTA_Tooltip_ability_ability_illusion_of_blood_incoming_damage" "%DAMAGE BY ILLUSION:" +"DOTA_Tooltip_ability_ability_illusion_of_blood_magic_resistance" "%MAG。COMPR。ILLUSIONS:" + +"DOTA_Tooltip_ability_ability_sacrifice_revenge" "牺牲复仇" +"DOTA_Tooltip_ability_ability_sacrifice_revenge_Description" "血猎手释放真正的本性,在 %duration% 秒内进入狂怒状态。" +"DOTA_Tooltip_ability_ability_sacrifice_revenge_Lore" "你总是必须牺牲一些东西,Blood Hunter 从出生起就知道这一点。" +"DOTA_Tooltip_ability_ability_sacrifice_revenge_base_attack_time" "攻击基地间隔:" +"DOTA_Tooltip_ability_ability_sacrifice_revenge_bonus_armor" "ARMOR 乘数:" +"DOTA_Tooltip_ability_ability_sacrifice_revenge_shard_Description" "能力也会造成 %pure_damage%% 的净伤害" + +"DOTA_Tooltip_ability_ability_ring_of_corrosive" "腐蚀环" +"DOTA_Tooltip_ability_ability_ring_of_corrosive_Description" "血猎者以血兄弟会的名义进行血液仪式。 英雄以 %duration% 秒创建一个血圈,其中所有对手每 %tick_rate% 秒获得的净伤害等于其伤害的 %damage% + %damage_mult%%。" +"DOTA_Tooltip_ability_ability_ring_of_corrosive_Lore" "一个无害的圈子,你最好不要走进去。" +"DOTA_Tooltip_ability_ability_ring_of_corrosive_radius" "半径:" + + + +"DOTA_Tooltip_ability_special_bonus_unique_blood_hunter_bloodstained_health" "+{s:bonus_healthbonus} 奖励健康血迹记忆" +"DOTA_Tooltip_ability_special_bonus_unique_blood_hunter_duration_blood" "+{s:bonus_duration} 秒持续时间血腥" +"DOTA_Tooltip_ability_special_bonus_unique_blood_hunter_blood_of_illusions_count" "+{s:bonus_images_count} 造成的幻觉数量。" +"DOTA_Tooltip_ability_special_bonus_unique_blood_hunter_blood_of_illusions_incoming_damage" "-{s:bonus_incoming_damage}% 传入幻象伤害" +"DOTA_Tooltip_ability_special_bonus_unique_blood_hunter_pure_damage_bonus" "+{s:bonus_pure_damage}% 净伤害牺牲复仇" +"DOTA_Tooltip_ability_special_bonus_unique_blood_hunter_sacrifice_revenge_duration" "+{s:bonus_duration} 到牺牲复仇的持续时间" + + +"DOTA_Tooltip_modifier_bloodrage_buff" "血腥" +"DOTA_Tooltip_modifier_bloodrage_buff_Description" "你感觉自己像一只动物。" +"DOTA_Tooltip_modifier_sacrifice_revenge_buff" "疯狂" +"DOTA_Tooltip_modifier_sacrifice_revenge_buff_Description" "真实形式" +"DOTA_Tooltip_modifier_bloodstained_memory" "血迹斑斑的记忆" +"DOTA_Tooltip_modifier_bloodstained_memory_Description" "你的思维比光速还快。" + +// --------------------Sniper-------------------- + +"DOTA_Tooltip_ability_ability_sniper_shrapnel_custom" "榴霰弹" +"DOTA_Tooltip_ability_ability_sniper_shrapnel_custom_Description" "向地面发射:%damage_delay% 秒后在半径 %radius% 内形成 %duration% 秒的榴霰弹云。区域内敌人每 %tick_interval% 秒受到相当于狙击手攻击力 %attack_damage_pct%%% 的物理伤害,受到 %slow_movement_speed%%% 移动速度减缓,且所受伤害提高 %incoming_damage_pct%%% 。" +"DOTA_Tooltip_ability_ability_sniper_shrapnel_custom_Lore" "卡德尔喜欢与客人分享弹片。" +"DOTA_Tooltip_ability_ability_sniper_shrapnel_custom_radius" "半径:" +"DOTA_Tooltip_ability_ability_sniper_shrapnel_custom_damage_delay" "区域出现延迟:" +"DOTA_Tooltip_ability_ability_sniper_shrapnel_custom_duration" "区域持续时间:" +"DOTA_Tooltip_ability_ability_sniper_shrapnel_custom_attack_damage_pct" "每次伤害(攻击力 %):" +"DOTA_Tooltip_ability_ability_sniper_shrapnel_custom_tick_interval" "伤害间隔:" +"DOTA_Tooltip_ability_ability_sniper_shrapnel_custom_slow_movement_speed" "%移动减速:" +"DOTA_Tooltip_ability_ability_sniper_shrapnel_custom_incoming_damage_pct" "%所受伤害加深:" + +"DOTA_Tooltip_ability_ability_sniper_critical_focus_custom" "致命专注" +"DOTA_Tooltip_ability_ability_sniper_critical_focus_custom_Description" "持续 %duration% 秒:每次攻击都暴击并合并所有暴击来源;另向暴击池添加 %crit_chance%%% 几率与 %crit_mult%%% 倍率。" +"DOTA_Tooltip_ability_ability_sniper_critical_focus_custom_Lore" "片刻之间,只打十环。" +"DOTA_Tooltip_ability_ability_sniper_critical_focus_custom_duration" "持续时间:" +"DOTA_Tooltip_ability_ability_sniper_critical_focus_custom_crit_chance" "%额外暴击几率:" +"DOTA_Tooltip_ability_ability_sniper_critical_focus_custom_crit_mult" "%额外暴击倍率:" + +"DOTA_Tooltip_ability_ability_sniper_keen_eye_custom" "敏锐目光" +"DOTA_Tooltip_ability_ability_sniper_keen_eye_custom_Description" "被动:攻击距离 +%bonus_attack_range%。%proc_chance%%% 几率(受幸运影响)命中时额外造成 %headshot_bonus_damage% 物理伤害、击退目标,并在 %slow_duration% 秒内减速移动 %slow_movement_pct%%% 与攻击 %slow_attack_speed%。击退距离随目标距离变化,最大击退 %knockback_distance%。" +"DOTA_Tooltip_ability_ability_sniper_keen_eye_custom_Lore" "目不转睛。" +"DOTA_Tooltip_ability_ability_sniper_keen_eye_custom_bonus_attack_range" "额外攻击距离:" +"DOTA_Tooltip_ability_ability_sniper_keen_eye_custom_proc_chance" "%爆头几率:" +"DOTA_Tooltip_ability_ability_sniper_keen_eye_custom_headshot_bonus_damage" "爆头额外伤害:" +"DOTA_Tooltip_ability_ability_sniper_keen_eye_custom_slow_duration" "减速持续时间:" +"DOTA_Tooltip_ability_ability_sniper_keen_eye_custom_knockback_distance" "击退(最大):" +"DOTA_Tooltip_ability_ability_sniper_keen_eye_custom_slow_movement_pct" "%移动减速:" +"DOTA_Tooltip_ability_ability_sniper_keen_eye_custom_slow_attack_speed" "攻击减速:" + +"DOTA_Tooltip_ability_ability_sniper_assassinate_custom" "暗杀" +"DOTA_Tooltip_ability_ability_sniper_assassinate_custom_Description" "瞄准阶段:光标周围 %aoe_radius% 范围内的敌人被标记;随后对每个被标记敌人发射弹道(若区域内无人则对所选目标)。命中:眩晕、必暴、攻击、额外伤害 %attack_damage_bonus% 与持续 %armor_mark_duration% 秒的护甲标记。神杖:更长眩晕与更短冷却。" +"DOTA_Tooltip_ability_ability_sniper_assassinate_custom_scepter_Description" "更短冷却与更长眩晕。" +"DOTA_Tooltip_ability_ability_sniper_assassinate_custom_Lore" "一枪少一个问题。" +"DOTA_Tooltip_ability_ability_sniper_assassinate_custom_aoe_radius" "标记半径:" +"DOTA_Tooltip_ability_ability_sniper_assassinate_custom_debuff_duration" "标记持续时间:" +"DOTA_Tooltip_ability_ability_sniper_assassinate_custom_projectile_speed" "弹道速度:" +"DOTA_Tooltip_ability_ability_sniper_assassinate_custom_ministun_duration" "迷你眩晕:" +"DOTA_Tooltip_ability_ability_sniper_assassinate_custom_scepter_stun_duration" "神杖眩晕:" +"DOTA_Tooltip_ability_ability_sniper_assassinate_custom_scepter_cooldown" "神杖冷却:" +"DOTA_Tooltip_ability_ability_sniper_assassinate_custom_attack_damage_bonus" "额外伤害:" +"DOTA_Tooltip_ability_ability_sniper_assassinate_custom_armor_mark_duration" "护甲标记持续:" + +"DOTA_Tooltip_modifier_sniper_critical_focus_active" "致命专注" +"DOTA_Tooltip_modifier_sniper_critical_focus_active_Description" "每次攻击暴击并合并所有暴击来源。" + +"DOTA_Tooltip_modifier_sniper_assassinate_mark" "暗杀" +"DOTA_Tooltip_modifier_sniper_assassinate_mark_Description" "护甲约减半,用于穿透效果。" + +"dota_tooltip_ability_special_bonus_unique_sniper_shrapnel_radius" "+150 榴霰弹半径" +"dota_tooltip_ability_special_bonus_unique_sniper_keen_range" "+75 敏锐目光攻击距离" +"dota_tooltip_ability_special_bonus_unique_sniper_shrapnel_cooldown" "-3秒 榴霰弹冷却" +"dota_tooltip_ability_special_bonus_unique_sniper_critical_focus_duration" "+3秒 致命专注持续时间" +"dota_tooltip_ability_special_bonus_unique_sniper_critical_focus_crit_mult" "+40 致命专注暴击倍率" +"dota_tooltip_ability_special_bonus_unique_sniper_keen_proc" "+12% 敏锐目光爆头几率" +"dota_tooltip_ability_special_bonus_unique_sniper_assassinate_aoe" "+250 暗杀标记半径" +"dota_tooltip_ability_special_bonus_unique_sniper_assassinate_damage" "+50 暗杀额外伤害" + +// --------------------Hoodwink-------------------- + +"DOTA_Tooltip_ability_hoodwink_acorn_shot_custom" "橡果射击" +"DOTA_Tooltip_ability_hoodwink_acorn_shot_custom_Description" "霍德温克发射一枚特殊橡果并进行攻击,橡果会在附近敌人之间弹跳,造成减速,并造成基础伤害外加一部分攻击伤害。\n也可对地施放以生成一棵树,随后橡果会弹向附近敌人。" +"DOTA_Tooltip_ability_hoodwink_acorn_shot_custom_scepter_Description" "普通攻击时,霍德温克有几率发射一枚橡果,但弹跳次数较少。" +"DOTA_Tooltip_ability_hoodwink_acorn_shot_custom_Lore" "霍德温克并不挑剔,但橡木和铁木结出的橡果最适合当弹药——她总会多备一些。" +"DOTA_Tooltip_ability_hoodwink_acorn_shot_custom_bounce_range" "弹跳范围:" +"DOTA_Tooltip_ability_hoodwink_acorn_shot_custom_bounce_count" "弹跳次数:" +"DOTA_Tooltip_ability_hoodwink_acorn_shot_custom_debuff_duration" "减速持续时间:" +"DOTA_Tooltip_ability_hoodwink_acorn_shot_custom_slow" "%移动减速:" +"DOTA_Tooltip_ability_hoodwink_acorn_shot_custom_acorn_shot_damage" "基础伤害:" +"DOTA_Tooltip_ability_hoodwink_acorn_shot_custom_base_damage_pct" "%攻击伤害加成:" +"DOTA_Tooltip_ability_hoodwink_acorn_shot_custom_bounce_count_scepter" "攻击触发弹跳次数:" +"DOTA_Tooltip_ability_hoodwink_acorn_shot_custom_scepter_chance" "%发射橡果几率:" +"DOTA_Tooltip_ability_hoodwink_acorn_shot_custom_Note0" "橡果可以多次弹到同一目标。" +"DOTA_Tooltip_ability_hoodwink_acorn_shot_custom_Note1" "橡果不会弹到隐形单位。" +"DOTA_Tooltip_ability_hoodwink_acorn_shot_custom_Note2" "树木持续 %tree_duration% 秒。" + +"DOTA_Tooltip_ability_hoodwink_bushwhack_custom" "丛林伏击" +"DOTA_Tooltip_ability_hoodwink_bushwhack_custom_Description" "霍德温克投掷陷阱网。若目标区域内有树木,则其中敌人会被眩晕。目标会受到伤害、被拉向最近的树,并在眩晕期间失去视野。" +"DOTA_Tooltip_ability_hoodwink_bushwhack_custom_Lore" "霍德温克最喜欢在铁木密林里设伏,而迷雾林地里最谨慎的来客从不放松警惕。" +"DOTA_Tooltip_ability_hoodwink_bushwhack_custom_radius" "范围:" +"DOTA_Tooltip_ability_hoodwink_bushwhack_custom_duration" "眩晕持续时间:" +"DOTA_Tooltip_ability_hoodwink_bushwhack_custom_total_damage" "总伤害:" + +"DOTA_Tooltip_ability_hoodwink_scurry_custom" "疾行" +"DOTA_Tooltip_ability_hoodwink_scurry_custom_Description" "当霍德温克在 %radius% 范围内靠近树木时,能力会显著增强:额外攻击力、攻击速度,并使受到的伤害降低 %incoming_damage_reduction_pct%%% 。主动施放后会获得移速加成,并且即使附近没有树木也可保留这些增益。" +"DOTA_Tooltip_ability_hoodwink_scurry_custom_Lore" "在托莫坎林地里,没有霍德温克够不到的树枝和洞穴。" +"DOTA_Tooltip_ability_hoodwink_scurry_custom_bonus_damage" "额外攻击力:" +"DOTA_Tooltip_ability_hoodwink_scurry_custom_bonus_attack_speed" "额外攻速:" +"DOTA_Tooltip_ability_hoodwink_scurry_custom_movement_speed_pct" "%额外移速:" +"DOTA_Tooltip_ability_hoodwink_scurry_custom_duration" "持续时间:" +"DOTA_Tooltip_ability_hoodwink_scurry_custom_luck_evasion_multiplier" "%每1点幸运提供闪避:" +"DOTA_Tooltip_ability_hoodwink_scurry_custom_max_luck_evasion" "%最大闪避:" +"DOTA_Tooltip_ability_hoodwink_scurry_custom_incoming_damage_reduction_pct" "%所受伤害降低:" + +"DOTA_Tooltip_ability_hoodwink_lucky_innate_custom" "幸运本能" +"DOTA_Tooltip_ability_hoodwink_lucky_innate_custom_Description" "先天技能。霍德温克每提升1级英雄等级都会获得幸运。" +"DOTA_Tooltip_ability_hoodwink_lucky_innate_custom_luck_per_level" "每级幸运:" + +"DOTA_Tooltip_modifier_modifier_hoodwink_scurry_custom_buff" "疾行" +"DOTA_Tooltip_modifier_modifier_hoodwink_scurry_custom_buff_Description" "提供额外攻击速度与攻击力,并降低所受伤害。闪避取决于英雄当前幸运值,当前闪避数值会显示在该增益提示中。" + +"DOTA_Tooltip_ability_hoodwink_sharpshooter_custom" "神射手" +"DOTA_Tooltip_ability_hoodwink_sharpshooter_custom_Description" "霍德温克蓄力准备致命弩箭。箭矢会穿透所有敌人,造成减速并施加虚弱。蓄力 %max_charge_time% 秒后伤害与减益持续时间达到最大;若持续蓄力到 %misfire_time% 秒,会自动发射。\n后坐力会将霍德温克向后推离 %recoil_distance%。" +"DOTA_Tooltip_ability_hoodwink_sharpshooter_custom_Lore" "霍德温克偶然得到第一把连发弩,如今她把自己的宝贝保养得很好——当然,某些零件是从别人那“借”来的。" +"DOTA_Tooltip_ability_hoodwink_sharpshooter_custom_max_charge_time" "最大蓄力时间:" +"DOTA_Tooltip_ability_hoodwink_sharpshooter_custom_max_damage" "最大伤害:" +"DOTA_Tooltip_ability_hoodwink_sharpshooter_custom_max_slow_debuff_duration" "最大减益持续时间:" +"DOTA_Tooltip_ability_hoodwink_sharpshooter_custom_slow_move_pct" "%移动减速:" +"DOTA_Tooltip_ability_hoodwink_sharpshooter_custom_arrow_speed" "弹道速度:" + +"DOTA_Tooltip_ability_hoodwink_sharpshooter_release_custom" "结束神射手" +"DOTA_Tooltip_ability_hoodwink_sharpshooter_release_custom_Description" "立刻发射并恢复移动与攻击能力。" +"DOTA_Tooltip_ability_hoodwink_sharpshooter_release_custom_Lore" "开火!" + +"DOTA_Tooltip_ability_special_bonus_unique_hoodwink_scurry_charges" "+1 疾行充能" +"DOTA_Tooltip_ability_special_bonus_unique_hoodwink_bushwhack_damage" "+120 丛林伏击伤害" +"DOTA_Tooltip_ability_special_bonus_unique_hoodwink_1_1" "橡果射击可在树木间弹跳" +"DOTA_Tooltip_ability_special_bonus_unique_hoodwink_4_1" "橡果射击:神射手冷却减少1秒" +"DOTA_Tooltip_ability_special_bonus_unique_hoodwink_acorn_shot_bounces" "+2 橡果射击弹跳次数" +"DOTA_Tooltip_ability_special_bonus_unique_hoodwink_3_1" "+50% 疾行加成" +"DOTA_Tooltip_ability_special_bonus_unique_hoodwink_sharpshooter_damage" "+500 神射手伤害" +"DOTA_Tooltip_ability_special_bonus_unique_hoodwink_scurry_duration" "+1秒 疾行持续时间" + +//默认项目 +"dota_tooltip_ability_item_mega_treads" "强化皮靴" +"dota_tooltip_ability_item_mega_treads_bonus_stat" "+到主属性" +"dota_tooltip_ability_item_mega_treads_bonus_other_stat" "+到其他属性" +"dota_tooltip_ability_item_mega_treads_bonus_movement_speed" "+$move_speed" +"dota_tooltip_ability_item_mega_treads_Description" "

属性切换

为所选属性提供%bonus_stat%,为所有其他属性提供%bonus_other_stat%。\n\n

所选属性的奖励:

强度: +%bonus_magical_resistance%%% 为魔法抗性。

智能: +%spell_amplify%% 表示拼写伤害。

Agility: +%bonus_attackspeed% 表示攻击速度。" +"dota_tooltip_ability_item_mega_treads_Lore" "这些靴子是由一位传奇铁匠打造的,他希望超越普通的动力靴。据说他曾尝试过古老的魔法和稀有材料,直到他创造了这件强大的神器。" +"dota_tooltip_ability_item_mega_treads_Note0" "可以多次接收。" + +"dota_tooltip_ability_item_bloodstone_magical" "神奇的血石" +"dota_tooltip_ability_item_bloodstone_magical_bonus_health" "+$health" +"dota_tooltip_ability_item_bloodstone_magical_bonus_mana" "+$mana" +"dota_tooltip_ability_item_bloodstone_magical_bonus_health_regen" "+$hp_regen" +"dota_tooltip_ability_item_bloodstone_magical_spell_lifesteal" "%+$spell_lifesteal" +"dota_tooltip_ability_item_bloodstone_magical_Description" "

魔法石

激活后,还会在 %duration% 秒上额外提供 %spell_lifesteal_active%% 咒语吸血鬼,以及 %cooldown_reduction_active%%% 充值减少和 %casttime_reduction%%% 咒语应用时间减少。" +"dota_tooltip_ability_item_bloodstone_magical_Lore" "这块石头是在一座隐藏在地下的古庙里发现的。据说他拥有可以治愈和增强魔力的魔力。" + +"dota_tooltip_modifier_item_bloodstone_magical_vampirism" "血编织" +"dota_tooltip_modifier_item_bloodstone_magical_vampirism_Description" "英雄的法术应用时间减少了 %dMODIFIER_PROPERTY_CASTTIME_PERCENTAGE%%,技能重新加载减少了 %dMODIFIER_PROPERTY_COOLDOWN_PERCENTAGE%%。" + +"dota_tooltip_ability_item_bearstbow" "熊弓" +"dota_tooltip_ability_item_bearstbow_attack_speed" "+$attack" +"dota_tooltip_ability_item_bearstbow_attack_range" "+$attack_range" +"dota_tooltip_ability_item_bearstbow_damage" "+$damage" +"dota_tooltip_ability_item_bearstbow_strength" "+$str" +"dota_tooltip_ability_item_bearstbow_agility" "+$agi" +"dota_tooltip_ability_item_bearstbow_intellect" "+$int" +"dota_tooltip_ability_item_bearstbow_health" "+$health" +"dota_tooltip_ability_item_bearstbow_Description" "

Bearstbow

在其攻击半径内攻击目标的额外%attack_count%。" +"dota_tooltip_ability_item_bearstbow_Lore" "在地下一座古庙里发现的弓。据说他拥有可以击垮狮子的神奇力量。" + +"DOTA_Tooltip_ability_item_magical_quiver" "神奇的箭筒" +"DOTA_Tooltip_ability_item_magical_quiver_Description" "

Pasivnoe:魔法箭

远程攻击会造成额外的%proc_damage_magical%magic伤害。" +"DOTA_Tooltip_ability_item_magical_quiver_Lore" "一个装满浸透古老魔法的箭的箭袋。它的每一枪都携带着一种破坏能量粒子。" + +"DOTA_Tooltip_ability_item_demonic_bow" "恶魔弓" +"DOTA_Tooltip_ability_item_demonic_bow_attack_speed" "+$attack" +"DOTA_Tooltip_ability_item_demonic_bow_attack_range" "+$attack_range" +"DOTA_Tooltip_ability_item_demonic_bow_damage" "+$damage" +"DOTA_Tooltip_ability_item_demonic_bow_strength" "+$str" +"DOTA_Tooltip_ability_item_demonic_bow_agility" "+$agi" +"DOTA_Tooltip_ability_item_demonic_bow_intellect" "+$int" +"DOTA_Tooltip_ability_item_demonic_bow_health" "+$health" +"DOTA_Tooltip_ability_item_demonic_bow_Description" "

Pasivnoe:多发

远程攻击最多击中攻击半径内其他目标的%attack_count%。 每次攻击都会造成额外的 %proc_damage_magical% magic 伤害。英雄的攻击不会失手。" +"DOTA_Tooltip_ability_item_demonic_bow_Lore" "用堕落恶魔的角锻造的弓。他的弓弦被一只古老怪物的血管拉长,箭上带着诅咒,可以灼伤敌人的灵魂。" + +"dota_tooltip_ability_item_armlet_of_eternal_hunger" "永恒饥饿的臂章" +"dota_tooltip_ability_item_armlet_of_eternal_hunger_Description" "

Active:永恒饥饿

如果启用,英雄将变得沉默,伤害增加 +%bonus_damage_active%,力量增加 +%bonus_str_active%,攻击速度增加 %bonus_attack_speed_active%,移动速度增加 %movespeed_active%。
启用后,每秒损失 %health_per_sec% 的生命值。" +"dota_tooltip_ability_item_armlet_of_eternal_hunger_bonus_armor" "-to armor" +"dota_tooltip_ability_item_armlet_of_eternal_hunger_lifesteal" "%+$lifesteal" +"dota_tooltip_ability_item_armlet_of_eternal_hunger_bonus_damage" "+$damage" +"dota_tooltip_ability_item_armlet_of_eternal_hunger_bonus_attack_speed" "+$attack" +"dota_tooltip_ability_item_armlet_of_eternal_hunger_bonus_hp_regen" "+$hp_regen" +"dota_tooltip_ability_item_armlet_of_eternal_hunger_Lore" "永恒渴望的臂章是在古代吸血鬼被遗忘的熔炉中锻造的,血液和钢铁在这里融合在一起。它的承载者感受到对生命无法抑制的渴望,能够将致命的伤口转化为力量。但每下降一滴力量,你就必须付出生命的代价—只有最绝望的英雄才敢穿上这件该死的神器。" +"dota_tooltip_modifier_item_armlet_of_eternal_hunger_buff" "永恒饥饿的武器" +"dota_tooltip_modifier_item_armlet_of_eternal_hunger_buff_Description" "损坏增加%dMODIFIER_PROPERTY_PREATTACK_BONUS_DAMAGE%。 强度增加%dMODIFIER_PROPERTY_STATS_STRENGTH_BONUS%。 攻击速度增加%dMODIFIER_PROPERTY_ATTACKSPEED_BONUS_CONSTANT%,移动速度增加%dMODIFIER_PROPERTY_MOVESPEED_BONUS_CONSTANT%。 每秒失去 100 个生命值。" + +"dota_tooltip_ability_item_mini_bfury" "迷你愤怒" +"dota_tooltip_ability_item_mini_bfury_bonus_damage" "+$damage" +"dota_tooltip_ability_item_mini_bfury_Description" "

主动:狂怒树切割器

在选定点摧毁半径为 %tree% 的树木。\n\n

Pasivnoe:狂怒野兽

英雄攻击穿过受害者,对其周围半径为 %cleave_distance% 的敌人造成 %cleave_damage%%% physical 伤害(仅限近战)" +"dota_tooltip_ability_item_mini_bfury_Lore" "有一天,一位在激烈战斗中牺牲的伟大维京人将这把斧头留在了他的坟墓上,研究它的锋利程度,人们学会了制作其最古老武器的类似物。" + + +"dota_tooltip_ability_item_storm" "电击枪" +"dota_tooltip_ability_item_storm_bonus_damage" "+$damage" +"dota_tooltip_ability_item_storm_Description" "

Pasivnoe: Electro

当受到 %chance%% 的攻击时,用闪电攻击敌人:造成 %bolt_damage% magic 伤害并在 %stun% 秒处击晕。效果有效3次。充值:%ability_cooldown%秒。" +"dota_tooltip_ability_item_storm_Lore" "这把泰瑟枪曾经是用于猎杀古代怪物的实验武器的一部分。它的手柄上布满了闪电痕迹,里面隐藏着一颗可以积累和释放破坏能量的水晶。据说,每次发射该装置不仅会在敌人的身上留下灼伤,还会留下对大自然不屈不挠的力量的恐惧印记。" + +"dota_tooltip_modifier_storm_dps" "已电气化" +"dota_tooltip_modifier_storm_dps_Description" "获得 %bolt_damage% 魔法伤害,并在每次勾选时以 %stun% 秒被击晕。" + +"dota_tooltip_ability_item_balist_custom" "电子弩炮" +"dota_tooltip_ability_item_balist_custom_bonus_damage" "+$damage" +"dota_tooltip_ability_item_balist_custom_crit_multiplier" "+$crit_multiplier" +"dota_tooltip_ability_item_balist_custom_crit_chance" "%+$crit_chance" +"dota_tooltip_ability_item_balist_custom_crit_mult" "%+$crit_mult" +"dota_tooltip_ability_item_balist_custom_Description" "

被动:电

当受到 %chance%%% 概率的攻击时,它会用闪电击中主要目标 3 次。每次攻击还会移动到 %bolt_radius% 半径内的一个新目标(每个额外目标仅被击中一次)。对所有者造成伤害 + %bolt_damage% magic 伤害并在 %stun% 秒处击晕。充值:%ability_cooldown%秒。" +"dota_tooltip_ability_item_balist_custom_Lore" "一种改进的弩炮,结合了电击枪和暗晶的力量。它的发射不仅使敌人瘫痪,而且还造成毁灭性的打击。专为那些猎杀最危险生物的人而设计。" +"dota_tooltip_ability_item_balist_custom_Note0" "攻击修改器适用于雷击。" +"dota_tooltip_modifier_balist_custom_dps" "已电气化" +"dota_tooltip_modifier_balist_custom_dps_Description" "获得 %bolt_damage% 魔法伤害,并在每次勾选时以 %stun% 秒被击晕。" + +"dota_tooltip_ability_item_battle_fury_custom" "战斗狂怒" +"dota_tooltip_ability_item_battle_fury_custom_bonus_damage" "+$damage" +"dota_tooltip_ability_item_battle_fury_custom_Description" "

活动:狂怒树切割器

在选定点摧毁 %tree% 内的树木。\n\n

Pasivnoe:狂怒野兽

英雄攻击穿过受害者,对其周围 %cleave_distance% 半径范围内的敌人造成 %cleave_damage%%% 物理伤害(仅限近战)" +"dota_tooltip_ability_item_battle_fury_custom_Lore" "有一天,一位在激烈战斗中牺牲的伟大维京人将这把斧头留在坟墓上,研究它的锋利程度,人们学会了制作其最古老武器的类似物。" + +"dota_tooltip_ability_item_mega_fury" "超级愤怒" +"dota_tooltip_ability_item_mega_fury_bonus_damage" "+$damage" +"dota_tooltip_ability_item_mega_fury_attack_speed" "+$attack" +"dota_tooltip_ability_item_mega_fury_attack_range" "+$attack_range" +"dota_tooltip_ability_item_mega_fury_Description" "

主动:狂怒树切割器

在选定点摧毁半径为 %tree% 内的树木。\n\n

被动:狂怒野兽

英雄攻击穿过受害者,对其周围半径为 %cleave_distance% 内的敌人造成 %cleave_damage%%% physical 伤害(仅限近战)。英雄的攻击不会失手。" +"dota_tooltip_ability_item_mega_fury_Lore" "有一天,一位在激烈战斗中牺牲的伟大维京人将这把斧头留在坟墓上,研究它的锋利程度,人们学会了制作其最古老武器的类似物。" + +//吸血鬼面具 +"DOTA_Tooltip_ability_item_lifesteal_custom" "病态面具" +"DOTA_Tooltip_ability_item_lifesteal_custom_lifesteal" "%+$lifesteal" +"DOTA_Tooltip_ability_item_lifesteal_custom_Description" "

Pasive: Lifesteal

为所有者治愈其攻击造成的部分物理伤害。" +"DOTA_Tooltip_ability_item_lifesteal_custom_Lore" "一种吸收进入其视线的人能量的面具。" +"DOTA_Tooltip_modifier_modifier_item_lifesteal_custom" "Lifesteal" +"DOTA_Tooltip_modifier_modifier_item_lifesteal_custom_Description" "治愈主人因攻击而造成的部分身体伤害。" + +"DOTA_Tooltip_ability_item_voodoo_mask_custom" "巫毒面具" +"DOTA_Tooltip_ability_item_voodoo_mask_custom_lifesteal" "%+$spell_lifesteal_creep" +"DOTA_Tooltip_ability_item_voodoo_mask_custom_Description" "

Pasivnoe:咒语生命窃取

治愈主人造成的一部分魔法伤害。" +"DOTA_Tooltip_ability_item_voodoo_mask_custom_Lore" "磨练的面具,用于吸收巫师与敌人之间的魔法纽带。" +"DOTA_Tooltip_modifier_modifier_item_voodoo_mask_custom" "咒语生命窃取" +"DOTA_Tooltip_modifier_modifier_item_voodoo_mask_custom_Description" "治愈主人造成的魔法伤害的一部分。" + +"DOTA_Tooltip_ability_item_mask_of_madness_custom" "疯狂的面具" +"DOTA_Tooltip_ability_item_mask_of_madness_custom_lifesteal" "%+$lifesteal" +"DOTA_Tooltip_ability_item_mask_of_madness_custom_bonus_damage" "+$damage" +"DOTA_Tooltip_ability_item_mask_of_madness_custom_Description" "

Active: Berserk

给予所有者 %bonus_attackspeed_active% 攻击速度和 %move_speed_active% 移动速度,但将他的盔甲减少 %armor_reduction_active% 并禁止他使用能力。%duration% sec.\n\n

Pasive: Lifesteal

为主人治愈其攻击造成的部分身体伤害。" +"DOTA_Tooltip_ability_item_mask_of_madness_custom_Lore" "戴着这个面具,战士会陷入无节制的愤怒。" +"DOTA_Tooltip_modifier_modifier_item_mask_of_madness_custom" "生命窃取" +"DOTA_Tooltip_modifier_modifier_item_mask_of_madness_custom_Description" "治愈所有者其攻击造成的部分物理伤害。" +"DOTA_Tooltip_modifier_modifier_item_mask_of_madness_custom_buff" "狂暴" +"DOTA_Tooltip_modifier_modifier_item_mask_of_madness_custom_buff_Description" "增加攻击速度 %bonus_attackspeed_active% 和移动 %move_speed_active%,但减少护甲 %armor_reduction_active% 并不允许使用能力。持续时间:%持续时间%秒。" + +"DOTA_Tooltip_ability_item_vampire_claw" "吸血鬼爪" +"DOTA_Tooltip_ability_item_vampire_claw_vampirism" "+$lifesteal" + "DOTA_Tooltip_ability_item_vampire_claw_Description" "

活动:血河

存储的每个电荷立即恢复%heal_per_charge%的健康。每次蠕变攻击获得 %charges_for_attack_creep% 电荷。

最大电荷:%max_charges%" + "DOTA_Tooltip_ability_item_vampire_claw_bonus_damage" "+$damage" + "DOTA_Tooltip_ability_item_vampire_claw_bonus_str" "+$str" +"DOTA_Tooltip_ability_item_vampire_claw_Lore" "这只爪子曾经属于一个古老的吸血鬼,他对血液的渴望如此强烈,以至于他可以通过吸收敌人的生命力来治愈伤口。据说,每当爪子挖进肉里时,它的主人就会感受到力量的激增和对新战斗的不可抑制的渴望。但每次愈合时,刀片上都会出现新的血管,提醒:「真正的力量需要牺牲」。" + +"DOTA_Tooltip_ability_item_satanic_custom" "Satanic" +"DOTA_Tooltip_ability_item_satanic_custom_Description" "

主动:邪恶之怒

在 %unholy_duration% 秒内使攻击吸血提高 %unholy_lifesteal%%% ,消耗 %health_cost% 点生命值。\n\n

被动:吸血

攻击造成的物理伤害会按一定比例治疗佩戴者。" +"DOTA_Tooltip_ability_item_satanic_custom_Lore" "充满肉山之血的强大神器——这位恶魔在第一战中陨落。" +"DOTA_Tooltip_ability_item_satanic_custom_bonus_strength" "+$str" +"DOTA_Tooltip_ability_item_satanic_custom_bonus_damage" "+$damage" +"DOTA_Tooltip_ability_item_satanic_custom_lifesteal" "%+$lifesteal" +"DOTA_Tooltip_modifier_item_satanic_custom_unholy" "邪恶之怒" +"DOTA_Tooltip_modifier_item_satanic_custom_unholy_Description" "攻击吸血提高。" + +"dota_tooltip_ability_item_king_fly" "鳞翅目昆虫之吻" +"dota_tooltip_ability_item_king_fly_bonus_agility" "+$agi" +"dota_tooltip_ability_item_king_fly_bonus_evasion" "%+$evasion" +"dota_tooltip_ability_item_king_fly_bonus_attack_speed_pct" "%+$attack_pct" +"dota_tooltip_ability_item_king_fly_bonus_damage" "+$damage" +"dota_tooltip_ability_item_king_fly_Lore" "用无数蝴蝶的精华锻造的刀片。它们彩虹色的翅膀和翠绿色的花粉融合成了空灵美丽和致命精准的武器。" + +"DOTA_Tooltip_ability_item_zombie_slayer" "僵尸杀手" +"DOTA_Tooltip_ability_item_zombie_slayer_bonus_damage" "+$damage" +"DOTA_Tooltip_ability_item_zombie_slayer_bonus_agility" "+$agi" +"DOTA_Tooltip_ability_item_zombie_slayer_bonus_health" "+$health" +"DOTA_Tooltip_ability_item_zombie_slayer_bonus_mana_regen" "+$mana_regen" +"DOTA_Tooltip_ability_item_zombie_slayer_Description" "

Pasivnoe:腐蚀

攻击会使目标速度降低 %movespeed_reduction%%,减少装甲减少 %armor_reduction%,并降低健康恢复率降低 %health_reduction%%%。 持续时间:%debuff_duration% 秒。\n\n

Pasive:亡灵执行

如果目标 — 是亡灵,则其受到的伤害将增加 %incress_damage%%%。 持续时间:%debuff_duration%秒。" +"DOTA_Tooltip_ability_item_zombie_slayer_Lore" "这把刀曾经属于一个猎人,他独自一人清除了整个土地上的亡灵大军。它的钢上充满了堕落僵尸的恶意,手柄上装饰着其中最危险的僵尸的牙齿。据说,这种武器的每一次打击不仅会伤害肉体,还会灼伤敌人体内残留的黑魔法。那些穿着僵尸杀手的人将成为所有死而复生的生物的噩梦。" + +"DOTA_Tooltip_ability_item_demon_slayer" "恶魔杀手" +"DOTA_Tooltip_ability_item_demon_slayer_bonus_damage" "+$damage" +"DOTA_Tooltip_ability_item_demon_slayer_bonus_agility" "+$agi" +"DOTA_Tooltip_ability_item_demon_slayer_bonus_health" "+$health" +"DOTA_Tooltip_ability_item_demon_slayer_bonus_mana_regen" "+$mana_regen" +"DOTA_Tooltip_ability_item_demon_slayer_bonus_health_regen" "+$hp_regen" +"DOTA_Tooltip_ability_item_demon_slayer_bonus_magic_resistance" "%+$spell_resist" +"DOTA_Tooltip_ability_item_demon_slayer_Description" "

被动:腐蚀

攻击会使目标速度降低 %movespeed_reduction%%%,减少护甲 %armor_reduction%,并降低生命恢复 %health_reduction%%%。持续时间:%debuff_duration% 秒。\n\n

被动:亡灵处决

若目标为亡灵(波次小兵),受到的伤害增加 %incress_damage%%%。持续时间:%debuff_duration% 秒。\n\n

被动:法师克星

攻击施加持续 %duration% 秒的效果:目标失去 %spell_amp_debuff%%% 技能增强,每秒受到 %dps% 魔法伤害。\n\n

被动:恶魔契约

持有者受到的伤害减少 %incoming_damage_reduction%%%,造成的伤害增加 %outgoing_damage_bonus%%%。" +"DOTA_Tooltip_ability_item_demon_slayer_Lore" "不死猎刃与法师杀手斗篷的诅咒融合:吞噬法术,撕裂亡骸,将持有者的怒火化为毁灭洪流。" + +"DOTA_Tooltip_ability_item_desolator_custom" "黯灭" +"DOTA_Tooltip_ability_item_desolator_custom_bonus_damage" "+$damage" +"DOTA_Tooltip_ability_item_desolator_custom_bonus_health" "+$health" +"DOTA_Tooltip_ability_item_desolator_custom_Description" "

被动:腐蚀

攻击在 %corruption_duration% 秒内施加:护甲 %corruption_armor%,移动速度降低 %corruption_movespeed_slow%%% ,生命恢复降低 %corruption_heal_reduction%%%(机制与僵尸杀手一致)。\n\n

被动:猎杀充能

击杀仍受腐蚀影响的单位有 %chance_to_stack%%% 几率获得 +%bonus_damage_per_kill% 充能(上限 %max_damage%)。每层充能额外 +1 攻击力和 +1 生命值。远程使用黯灭弹道。" +"DOTA_Tooltip_ability_item_desolator_custom_Lore" "为撕裂「相信护甲能保命」而锻造。每个在腐蚀下倒下的敌人都会在刃上多留一道缺口——直到钢铁忘了如何冷却。" +"DOTA_Tooltip_modifier_modifier_item_desolator_custom_debuff" "腐蚀" +"DOTA_Tooltip_modifier_modifier_item_desolator_custom_debuff_Description" "护甲 %corruption_armor%;移动速度 %corruption_movespeed_slow%%% ;生命恢复降低 %corruption_heal_reduction%%%。" + +"DOTA_Tooltip_ability_item_desolator_custom_2" "黯灭 II" +"DOTA_Tooltip_ability_item_desolator_custom_2_bonus_damage" "+$damage" +"DOTA_Tooltip_ability_item_desolator_custom_2_bonus_health" "+$health" +"DOTA_Tooltip_ability_item_desolator_custom_2_Description" "

被动:加深腐蚀

命中施加/刷新 %corruption_duration% 秒:护甲可叠加(每层 −%corruption_armor_per_stack%,总计最多 %corruption_armor_cap%),移动速度降低 %corruption_movespeed_slow%%% ,生命恢复降低 %corruption_heal_reduction%%%(与僵尸杀手同类)。\n\n

被动:猎杀充能

击杀仍受此腐蚀的单位有 %chance_to_stack%%% 几率获得 +%bonus_damage_per_kill% 充能(上限 %max_damage%)。每层充能 +1 攻击力和 +1 生命。远程使用黯灭弹道。" +"DOTA_Tooltip_ability_item_desolator_custom_2_Lore" "同样的裂甲法则——只是钢咬得更深,直到层数撞上瓦解的上限。" +"DOTA_Tooltip_modifier_modifier_item_desolator_custom_2_debuff" "深度腐蚀" +"DOTA_Tooltip_modifier_modifier_item_desolator_custom_2_debuff_Description" "护甲降低为 min(层数×%corruption_armor_per_stack%, %corruption_armor_cap%);移动 %corruption_movespeed_slow%%% ;生命恢复 −%corruption_heal_reduction%%%。" + +"DOTA_Tooltip_ability_item_poor_shield" "穷鬼盾" +"DOTA_Tooltip_ability_item_poor_shield_bonus_physical_armor" "+$armor" +"DOTA_Tooltip_ability_item_poor_shield_Description" "

被动:伤害格挡

受到攻击伤害时有 %damage_block_chance%%% 的几率格挡 %damage_block% 点物理伤害。英雄的总触发几率会随幸运(Luck)提升。" +"DOTA_Tooltip_ability_item_poor_shield_Lore" "一块破木板和几条铁皮,凑合挡挡最开头的几刀。别指望它和先锋盾一样体面。" + +"DOTA_Tooltip_ability_item_warrior_shield" "战士之盾" +"DOTA_Tooltip_ability_item_warrior_shield_bonus_health" "+$health" +"DOTA_Tooltip_ability_item_warrior_shield_bonus_health_regen" "+$hp_regen" +"DOTA_Tooltip_ability_item_warrior_shield_Description" "

被动:伤害格挡

受到攻击伤害时有 %damage_block_chance%%% 的几率格挡 %damage_block% 点物理伤害。近战英雄额外格挡自身力量的 50%%。英雄的总触发几率会随幸运(Luck)提升。" +"DOTA_Tooltip_ability_item_warrior_shield_Lore" "加固的箍边与皮衬,挨一记狠的也能多喘口气,好让队友有空翻绷带。" + +"dota_tooltip_ability_item_crystalys" "水晶" +"dota_tooltip_ability_item_crystalys_bonus_damage" "+$damage" +"dota_tooltip_ability_item_crystalys_crit_chance" "%+$crit_chance" +"dota_tooltip_ability_item_crystalys_crit_mult" "%+$crit_mult" +"dota_tooltip_ability_item_crystalys_Lore" "铁匠大师为增强武器的杀伤力而创造的古代文物。它的锋利边缘能够将打击能量引导至敌人最脆弱的点。" + +"dota_tooltip_ability_item_magical_crit" "Lia Magic Bow" +"dota_tooltip_ability_item_magical_crit_Description" "被动:法术有几率造成暴击魔法伤害。包含神秘法杖、魔法石与巫毒面具的属性。" +"dota_tooltip_ability_item_magical_crit_bonus_intellect" "+$int" +"dota_tooltip_ability_item_magical_crit_bonus_strength" "+$str" +"dota_tooltip_ability_item_magical_crit_bonus_agility" "+$agi" +"dota_tooltip_ability_item_magical_crit_bonus_spell_amplify" "%+$spell_amp" +"dota_tooltip_ability_item_magical_crit_bonus_mana_regen" "+$mana_regen" +"dota_tooltip_ability_item_magical_crit_bonus_mana_pct" "%+$max_mana_percentage" +"dota_tooltip_ability_item_magical_crit_magical_vampirism" "%+$spell_lifesteal_hero" +"dota_tooltip_ability_item_magical_crit_spell_crit_chance" "%+$spell_crit_chance" +"dota_tooltip_ability_item_magical_crit_spell_crit_mult" "%+$spell_crit_mult" +"dota_tooltip_ability_item_magical_crit_Lore" "在法师塔中培育的水晶:它捕捉法力流,并在恰当时机将其灌入法术的薄弱处。" + +"dota_tooltip_ability_item_ethereal_blade_custom" "Ethereal Blade" +"dota_tooltip_ability_item_ethereal_blade_custom_Description" "

主动:灵刃冲击

对目标周围 %splash_radius% 范围内的敌人发射灵刃。敌人进入虚无状态 %duration% 秒,受到额外魔法伤害并被减速。伤害:%blast_damage_base% + 主属性 × %blast_stat_multiplier%。

可对友军施放:虚无 %duration_ally% 秒,并获得 %ally_bonus_movespeed%%% 移动速度与 %ally_bonus_attack_speed%%% 攻击速度。" +"dota_tooltip_ability_item_ethereal_blade_custom_bonus_strength" "+$str" +"dota_tooltip_ability_item_ethereal_blade_custom_bonus_agility" "+$agi" +"dota_tooltip_ability_item_ethereal_blade_custom_bonus_intellect" "+$int" +"dota_tooltip_ability_item_ethereal_blade_custom_status_resistance" "%+$status_resist" +"dota_tooltip_ability_item_ethereal_blade_custom_bonus_attack_speed" "+$attack" +"dota_tooltip_ability_item_ethereal_blade_custom_movement_speed_percent_bonus" "%+$move_speed" +"dota_tooltip_ability_item_ethereal_blade_custom_hp_regen_amp" "%+$hp_regen" +"dota_tooltip_ability_item_ethereal_blade_custom_mana_regen_multiplier" "%+$mana_regen" +"dota_tooltip_ability_item_ethereal_blade_custom_spell_amp" "%+$spell_amp" +"dota_tooltip_ability_item_ethereal_blade_custom_magic_damage_attack" "+$damage" +"dota_tooltip_ability_item_ethereal_blade_custom_splash_radius" "冲击半径:" +"dota_tooltip_ability_item_ethereal_blade_custom_blast_damage_base" "基础伤害:" +"dota_tooltip_ability_item_ethereal_blade_custom_blast_stat_multiplier" "主属性倍率:" +"dota_tooltip_ability_item_ethereal_blade_custom_ethereal_damage_bonus" "% 目标所受魔法伤害:" +"dota_tooltip_ability_item_ethereal_blade_custom_duration" "敌人持续时间:" +"dota_tooltip_ability_item_ethereal_blade_custom_duration_ally" "友军持续时间:" +"dota_tooltip_ability_item_ethereal_blade_custom_ally_bonus_movespeed" "% 友军移动速度:" +"dota_tooltip_ability_item_ethereal_blade_custom_ally_bonus_attack_speed" "% 友军攻击速度:" +"dota_tooltip_ability_item_ethereal_blade_custom_Lore" "由凝固以太锻成的利刃,以魔法切割血肉,并短暂将受害者拉出凡尘的击打。" + +"dota_tooltip_ability_item_dark_crystalys" "代达罗斯" +"dota_tooltip_ability_item_dark_crystalys_bonus_damage" "+$damage" +"dota_tooltip_ability_item_dark_crystalys_crit_chance" "%+$crit_chance" +"dota_tooltip_ability_item_dark_crystalys_crit_mult" "%+$crit_mult" +"dota_tooltip_ability_item_dark_crystalys_Lore" "这是 Crystalis 的改进版本,该神器保证在战斗中造成致命打击。" + +"dota_tooltip_ability_item_rapier_custom" "穿孔刀片" +"dota_tooltip_ability_item_rapier_custom_bonus_damage" "+$damage" +"dota_tooltip_ability_item_rapier_custom_Description" "

Pasivnoe:神圣刺击

攻击 %proc_damage%% 时,造成的伤害也将以 clean 形式处理。" +"dota_tooltip_ability_item_rapier_custom_Lore" "圣战士在与恶魔的战斗中倒下后,他的长矛被折成许多碎片。 其中一块碎片落入了一位才华横溢的铁匠手中,他受到金属神圣力量的启发,将其锻造成一把极其锋利的剑。" + +"dota_tooltip_ability_item_divine_rapier_custom" "神圣的剑" +"dota_tooltip_ability_item_divine_rapier_custom_bonus_damage" "+$damage" +"dota_tooltip_ability_item_divine_rapier_custom_Description" "

Pasivnoe:神圣刺击

攻击 %proc_damage%% 时,造成的伤害也将以 clean 形式处理。" +"dota_tooltip_ability_item_divine_rapier_custom_Lore" "一位圣战士在与恶魔的战斗中倒下后,他的长矛被折成许多碎片。其中一块碎片落入了一位才华横溢的铁匠手中,他受到金属神圣力量的启发,将其锻造成一把极其锋利的剑。" + +"dota_tooltip_ability_item_mjolnir_custom" "Mjollnir" +"dota_tooltip_ability_item_mjolnir_custom_Description" "

活动:链祝福

以%持续时间%秒用闪电链包围目标。这导致它承受 %outgoing_pct%% 的伤害,并且承受 %incoming_pct%% 的伤害较少。\n\n

Pasivnoe:连锁闪电

每次具有 %chain_chance%% 机会的攻击都可以产生连锁闪电攻击,%chain_strikes% 将在 %chain_radius% 半径内的随机敌人之间跳跃,造成攻击的 %chain_damage% + %chain_damage_self%% 的每个魔法伤害。连锁雷击是无法避免的。" +"dota_tooltip_ability_item_mjolnir_custom_Lore" "托尔由名叫布洛克和艾特里的矮人为他锻造的魔法锤。" +"dota_tooltip_ability_item_mjolnir_custom_bonus_damage" "+$damage" +"dota_tooltip_ability_item_mjolnir_custom_bonus_attack_speed" "+$attack" + +"DOTA_Tooltip_ability_item_magic_stone" "魔法石" +"DOTA_Tooltip_ability_item_magic_stone_Description" "

Pasivnoe:魔法光环

增强主人的法术并恢复法力。" +"DOTA_Tooltip_ability_item_magic_stone_Lore" "几条链上的一颗大宝石。古老的魔法能量在内部脉动。" +"DOTA_Tooltip_ability_item_magic_stone_bonus_strength" "+$str" +"DOTA_Tooltip_ability_item_magic_stone_bonus_spell_amplify" "%+$spell_amp" +"DOTA_Tooltip_ability_item_magic_stone_bonus_agility" "+$agi" +"DOTA_Tooltip_ability_item_magic_stone_bonus_intellect" "+$int" +"DOTA_Tooltip_ability_item_magic_stone_bonus_mana_regen" "+$mana_regen" +"DOTA_Tooltip_ability_item_magic_stone_bonus_mana_pct" "%+$max_mana_percentage" + +"DOTA_Tooltip_ability_item_magic_rapier" "神奇的匕首" +"DOTA_Tooltip_ability_item_magic_rapier_Description" "

Pasive: Divine Pierce

下一个指向性技能会对目标造成额外 魔法 伤害:%damage_per_cast% 加上所有者当前法力值的 %damage_per_cast_hand%%%(不是攻击力),减速 %slow_duration% 秒,并降低 %magical_resist_reduction%%% 魔法抗性。

效果可叠加。" +"DOTA_Tooltip_ability_item_magic_rapier_Lore" "用恒星金属碎片锻造并在古代龙的火焰中回火的传奇刀片。据说它的创造者大法师埃林多花了三个世纪的时间寻找这种武器的完美材料。当他终于找到流星时,它的核心已经充满了最纯粹的魔法能量。" +"DOTA_Tooltip_ability_item_magic_rapier_bonus_spell_amplify" "%+$spell_amp" +"DOTA_Tooltip_ability_item_magic_rapier_bonus_intellect" "+$int" +"DOTA_Tooltip_ability_item_magic_rapier_bonus_strength" "+$str" +"DOTA_Tooltip_ability_item_magic_rapier_bonus_agility" "+$agi" +"DOTA_Tooltip_ability_item_magic_rapier_bonus_mana_regen" "+$mana_regen" +"DOTA_Tooltip_ability_item_magic_rapier_bonus_mana_pct" "%+$max_mana_percentage" + +"DOTA_Tooltip_ability_item_magical_divine_rapier" "神奇的神圣之刃" +"DOTA_Tooltip_ability_item_magical_divine_rapier_Description" "

Pasive: Divine Pierce

以下定向能力将对目标造成额外的 %damage_per_cast% + %damage_per_cast_hand%%% 的所有者攻击 magic 伤害,减慢 %slow_duration% 秒并减少 %magical_resist_reduction%%% 的魔法抗性。

效果节拍。" +"DOTA_Tooltip_ability_item_magical_divine_rapier_Lore" "一把传奇的刀片,由恒星金属碎片锻造而成,并在古代龙的火焰中回火。据说它的创造者大法师埃林多花了三个世纪的时间寻找这种武器的完美材料。当他终于找到流星时,它的核心已经充满了最纯粹的魔法能量。" +"DOTA_Tooltip_ability_item_magical_divine_rapier_bonus_spell_amplify" "%+$spell_amp" +"DOTA_Tooltip_ability_item_magical_divine_rapier_bonus_intellect" "+$int" +"DOTA_Tooltip_ability_item_magical_divine_rapier_bonus_strength" "+$str" +"DOTA_Tooltip_ability_item_magical_divine_rapier_bonus_agility" "+$agi" +"DOTA_Tooltip_ability_item_magical_divine_rapier_bonus_mana_regen" "+$mana_regen" +"DOTA_Tooltip_ability_item_magical_divine_rapier_bonus_mana_pct" "%+$max_mana_percentage" + +"DOTA_Tooltip_ability_item_soul_devourer_staff" "灵魂吞噬者工作人员" +"DOTA_Tooltip_ability_item_soul_devourer_staff_Description" "

Pasivnoe:灵魂吞噬者

击杀后恢复最大法力值的 %kill_mana%%% ,每层堆叠增加最大法力 %bonus_mana% 。每 %stack_growth_interval% 秒,祝福堆叠数量增加 %stack_growth_percent%%%(向上取整)。" +"DOTA_Tooltip_ability_item_soul_devourer_staff_Lore" "死灵法师在坟墓山之战中失败后,这根杖被封印在光之神庙下的墓穴中。但随着僵尸末日的开始,古代海豹变得虚弱,神器重新获得自由,渴望吸收新的灵魂。" +"DOTA_Tooltip_ability_item_soul_devourer_staff_bonus_intellect" "+$int" +"DOTA_Tooltip_ability_item_soul_devourer_staff_bonus_manacost_reduction" "%+$manacost_reduction" +"DOTA_Tooltip_ability_item_soul_devourer_staff_bonus_mana_regen" "+$mana_regen" +"DOTA_Tooltip_ability_item_soul_devourer_staff_bonus_mana_special" "+$mana" +"DOTA_Tooltip_ability_item_soul_devourer_staff_bonus_cast_range" "+$cast_range" + + +"DOTA_Tooltip_modifier_soul_devourer_staff_buff" "灵魂吞噬者祝福" +"DOTA_Tooltip_modifier_soul_devourer_staff_buff_Description" "英雄获得了%dMODIFIER_PROPERTY_SPELL_AMPLIFY_PERCENTAGE%%的额外魔法强化。" + +"DOTA_Tooltip_ability_item_orb_of_fire" "火球" +"DOTA_Tooltip_ability_item_orb_of_fire_Note0" "

Pasivnoe: Burn

当堆叠数为 0+ 时,主人开始从堆叠数 * 3 中每秒受到伤害。

当堆叠数为 70+ 时,效果主人会损失其盔甲和魔法师堆叠数的 25%。抵抗。

这种效果不会影响冻伤,这就是为什么 BURN 会吸收冻伤的效果。" +"DOTA_Tooltip_ability_item_orb_of_fire_Description" "

Pasivnoe:火焰光环

半径为%radius%单位内的每一秒都会应用%damage%,并将对周围的每个人应用%fire_stack%燃烧效果。" +"DOTA_Tooltip_ability_item_orb_of_fire_Lore" "完全由火组成的球体。" + +"dota_tooltip_ability_item_ice_spine" "冰旋转" +"dota_tooltip_ability_item_ice_spine_Description" "

Pasivnoe:冰的脆弱性

所有佩戴者治疗法术都会恢复 %heal_amplify%% 的更多健康,同时佩戴该物品的人也会受到 %incoming_dmg_pct%%% 的更多伤害。" +"dota_tooltip_ability_item_ice_spine_Lore" "冰针是由冰姐妹们创造的,她们热爱受虐狂游戏玩家。" +"dota_tooltip_ability_item_ice_spine_spell_amplify" "%+$spell_amp" +"dota_tooltip_ability_item_ice_spine_movespeed_const" "+$move_speed" + +"DOTA_Tooltip_ability_item_fire_cape" "燃烧的裹尸布" +"DOTA_Tooltip_ability_item_fire_cape_Note0" "

Pasivnoe: Burn

当堆叠数为 0+ 时,主人开始从堆叠数 * 3 中每秒受到伤害。

当堆叠数为 70+ 时,效果主人会损失其盔甲和魔法师堆叠数的 25%。抵抗。

这种效果不会影响冻伤,这就是为什么 BURN 会吸收冻伤的效果。" +"DOTA_Tooltip_ability_item_fire_cape_Description" "

Pasivnoe:伟大的光环

半径为 %radius% 单位内的每一秒都会应用所有者最大法力的 %damage% + %mana_damage_pct%%%,将对周围的每个人应用 %fire_stack% 燃烧效果。此外,半径内的每个人都会分别减少 %bonus_magic_resistance%%%/%bonus_spell_amp%%% 的魔法抵抗力和魔法伤害。" +"DOTA_Tooltip_ability_item_fire_cape_Lore" "这件火斗篷是用火焰锻造的,即使在水下也不会熄灭。它的热量不仅灼伤敌人,还会抑制他们的魔法力量,消除阻力并减少法术伤害。据说,任何敢于接近这件文物主人的人都会感受到不屈不挠的火焰的全部力量,即使是最持久的咒语也能被焚烧。" +"DOTA_Tooltip_ability_item_fire_cape_bonus_damage" "+$damage" +"DOTA_Tooltip_ability_item_fire_cape_bonus_attack_speed" "+$attack" +"DOTA_Tooltip_ability_item_fire_cape_bonus_physical_armor" "+$armor" +"DOTA_Tooltip_ability_item_fire_cape_bonus_health_regen" "+$hp_regen" +"DOTA_Tooltip_ability_item_fire_cape_bonus_mana_regen" "+$mana_regen" +"DOTA_Tooltip_ability_item_fire_cape_bonus_magic_resistance" "%+$spell_resist" + +"DOTA_Tooltip_ability_item_radiance_custom" "Radiance" +"DOTA_Tooltip_ability_item_radiance_custom_Note0" "

被动:燃烧

当堆叠数为 0+ 时,目标每秒受到堆叠数 × 3 的伤害。

当堆叠数为 70+ 时,目标会根据堆叠数失去 25% 的护甲和魔法抗性。

燃烧不与冻伤叠加——燃烧会吞噬冻伤效果。" +"DOTA_Tooltip_ability_item_radiance_custom_Description" "

被动:燃烧光环

每 %tick_interval% 秒,在 %radius% 范围内对敌人造成 %damage% + 持有者最大生命值 %health_damage_pct%%% + 每秒每点生命恢复 %health_regen_damage% 的魔法伤害,并施加 %fire_stack% 层燃烧。" +"DOTA_Tooltip_ability_item_radiance_custom_Lore" "一把神圣之刃,缠绕着永不熄灭的火焰,即便在深渊最深处也不会熄灭。" +"DOTA_Tooltip_ability_item_radiance_custom_bonus_damage" "+$damage" +"DOTA_Tooltip_ability_item_radiance_custom_evasion" "%+$evasion" + +"DOTA_Tooltip_ability_item_blazing_radiance_custom" "Blazing guard" +"DOTA_Tooltip_ability_item_blazing_radiance_custom_Note0" "

被动:燃烧

当堆叠数为 0+ 时,目标每秒受到堆叠数 × 3 的伤害。

当堆叠数为 70+ 时,目标会根据堆叠数失去 25% 的护甲和魔法抗性。

燃烧不与冻伤叠加——燃烧会吞噬冻伤效果。" +"DOTA_Tooltip_ability_item_blazing_radiance_custom_Description" "

被动:炼狱光环

每 %tick_interval% 秒,在 %radius% 范围内造成 %damage% + 最大生命值 %health_damage_pct%%% + 每秒每点生命恢复 %health_regen_damage% + 最大法力 %mana_damage_pct%%% 的魔法伤害,并施加 %fire_stack% 层燃烧。范围内敌人失去 %aura_magic_resistance_reduction%%% 魔法抗性和 %aura_spell_amp_reduction%%% 技能增强。

额外使所有基础属性提高 %all_stats_pct%%% 。" +"DOTA_Tooltip_ability_item_blazing_radiance_custom_Lore" "由 Radiance 之火与 Blazing Shroud 之热缝成的斗篷:穿戴者周身火海翻腾,敌人的魔法在浓烟中黯淡。" +"DOTA_Tooltip_ability_item_blazing_radiance_custom_bonus_damage" "+$damage" +"DOTA_Tooltip_ability_item_blazing_radiance_custom_evasion" "%+$evasion" +"DOTA_Tooltip_ability_item_blazing_radiance_custom_bonus_attack_speed" "+$attack" +"DOTA_Tooltip_ability_item_blazing_radiance_custom_bonus_physical_armor" "+$armor" +"DOTA_Tooltip_ability_item_blazing_radiance_custom_bonus_health_regen" "+$hp_regen" +"DOTA_Tooltip_ability_item_blazing_radiance_custom_bonus_mana_regen" "+$mana_regen" +"DOTA_Tooltip_ability_item_blazing_radiance_custom_bonus_magic_resistance" "%+$spell_resist" +"DOTA_Tooltip_ability_item_blazing_radiance_custom_bonus_all_stats" "+$all" + +"DOTA_Tooltip_ability_item_ultimate_crown" "Ultimate Crown" +"DOTA_Tooltip_ability_item_ultimate_crown_bonus_all_stats" "+$all" +"DOTA_Tooltip_ability_item_ultimate_crown_Description" "额外使所有基础属性提高 %all_stats_pct%%% 。" +"DOTA_Tooltip_ability_item_ultimate_crown_Lore" "三颗神符凝成王冠:力量、敏捷与智力既以数值增长,也按天赋比例放大。" + +"DOTA_Tooltip_ability_item_skadi_custom" "Eye of Skadi" +"DOTA_Tooltip_ability_item_skadi_custom_bonus_all_stats" "+$all" +"DOTA_Tooltip_ability_item_skadi_custom_Description" "

被动:寒冰攻击

使持有者的攻击附带减速效果,持续 %debuff_duration% 秒。降低目标 %slow_attack_speed%%% 攻击速度;移动速度减速取决于攻击类型:对近战单位 %slow_movespeed_melee%%% ,对远程单位 %slow_movespeed_ranged%%% 。同时减少目标 %health_restoration_reduction%%% 生命恢复。

额外使所有基础属性提高 %all_stats_pct%%% 。" +"DOTA_Tooltip_ability_item_skadi_custom_Lore" "由远古巨龙之心锻成的冰珠:每一次打击都将猎物冻在寒霜中,直至生命从伤口流逝。" + + +"DOTA_Tooltip_ability_item_crimson_shivas_custom" "Crimson Shiva's Guard" +"DOTA_Tooltip_ability_item_crimson_shivas_custom_Note0" "

被动:冰冻光环

在 %aura_radius% 范围内使敌人攻击速度降低 %aura_as_reduction%,其受到的伤害增加 %aura_enemy_incoming_amp%%% ,友方受到的伤害减少 %aura_ally_incoming_reduction%%% 。" +"DOTA_Tooltip_ability_item_crimson_shivas_custom_Description" "

主动:极地壁垒

在 %blast_radius% 范围内释放寒霜冲击,造成 %blast_damage% 魔法伤害并减速敌人 %slow_duration% 秒。%guard_radius% 范围内的友方在 %guard_duration% 秒内获得强化防护:物理伤害格挡(基础 %damage_block_active% + 施法者力量 %block_strength_pct%%%)、额外护甲、攻击速度和移动速度;受到的伤害减少 %aura_ally_incoming_reduction%%% 。

被动:守护结界

提供护甲、全属性、生命、法力和回复。有 %damage_block_chance%%% 几率格挡 %damage_block% 点攻击物理伤害。近战英雄额外格挡自身力量的 %block_strength_pct%%% 。

额外使所有基础属性提高 %all_stats_pct%%% 。" +"DOTA_Tooltip_ability_item_crimson_shivas_custom_Lore" "寒冰与钢铁融为一体:敌人冻结,盟友坚守到最后。" +"DOTA_Tooltip_ability_item_crimson_shivas_custom_bonus_stats" "+$all" +"DOTA_Tooltip_ability_item_crimson_shivas_custom_bonus_all_stats" "+$all" +"DOTA_Tooltip_ability_item_crimson_shivas_custom_bonus_health" "+$health" +"DOTA_Tooltip_ability_item_crimson_shivas_custom_bonus_mana" "+$mana" +"DOTA_Tooltip_ability_item_crimson_shivas_custom_bonus_armor" "+$armor" +"DOTA_Tooltip_ability_item_crimson_shivas_custom_bonus_hp_regen" "+$hp_regen" +"DOTA_Tooltip_ability_item_crimson_shivas_custom_bonus_mana_regen" "+$mana_regen" + +"DOTA_Tooltip_ability_item_blademail_2" "刃甲 II" +"DOTA_Tooltip_ability_item_blademail_2_aura_armor" "+$armor" +"DOTA_Tooltip_ability_item_blademail_2_aura_attack_speed" "+$attack" +"DOTA_Tooltip_ability_item_blademail_2_Description" "

主动:刃甲爆发

持续 %active_duration% 秒:反弹来自敌人的 %active_reflect_pct%%% 受伤(结算后);此期间敌人造成的伤害会全额转化为生命恢复。特效与刃甲同类。\n\n

被动:伤害反弹

将受到的伤害(结算后)的 %reflect_pct%%% 反弹给攻击者。对生命流失、友方伤害与反弹伤害无效。\n\n

被动:荆棘光环

半径 %radius% 内:友方英雄与单位获得 +%aura_armor% 护甲与 +%aura_attack_speed% 攻击速度;敌人受到同等数值的减益。" +"DOTA_Tooltip_ability_item_blademail_2_Lore" "由折断的刀片编成的铠甲,以牙还牙。靠近携带者的人要么感到盟军钢刃的余温,要么感到敌阵荆棘的压迫。" +"DOTA_Tooltip_modifier_modifier_item_blademail_2_aura" "刃甲 II(光环)" +"DOTA_Tooltip_modifier_modifier_item_blademail_2_aura_Description" "携带者的友军:+%aura_armor% 护甲、+%aura_attack_speed% 攻击速度。敌军:−%aura_armor% 护甲与 −%aura_attack_speed% 攻击速度。" +"DOTA_Tooltip_modifier_modifier_item_blademail_2_active" "刃甲爆发" +"DOTA_Tooltip_modifier_modifier_item_blademail_2_active_Description" "反弹 %active_reflect_pct%%% 所受伤害,并将敌人造成的伤害全额转化为治疗。" + +"dota_tooltip_modifier_item_desolator_custom_debuff" "腐蚀" +"dota_tooltip_modifier_item_desolator_custom_debuff_Description" "护甲 %corruption_armor%;移动速度 %corruption_movespeed_slow%%% ;生命恢复降低 %corruption_heal_reduction%%%。" +"dota_tooltip_modifier_item_desolator_custom_2_debuff" "深度腐蚀" +"dota_tooltip_modifier_item_desolator_custom_2_debuff_Description" "护甲降低为 min(层数×%corruption_armor_per_stack%, %corruption_armor_cap%);移动 %corruption_movespeed_slow%%% ;生命恢复 −%corruption_heal_reduction%%%。" +"dota_tooltip_modifier_item_blademail_2_active" "刃甲爆发" +"dota_tooltip_modifier_item_blademail_2_active_Description" "反弹 %active_reflect_pct%%% 所受伤害,并将敌人造成的伤害全额转化为治疗。" +"dota_tooltip_modifier_item_blademail_2_aura" "刃甲 II(光环)" +"dota_tooltip_modifier_item_blademail_2_aura_Description" "携带者的友军:+%aura_armor% 护甲、+%aura_attack_speed% 攻击速度。敌军:−%aura_armor% 护甲与 −%aura_attack_speed% 攻击速度。" + +"dota_tooltip_ability_item_rom" "空烧瓶" +"dota_tooltip_ability_item_rom_Description" "Slurp 啜饮瓶有点空。" +"dota_tooltip_ability_item_rom_Lore" "有传言说,这种朗姆酒是由海盗王的灵质制成的。" + +"dota_tooltip_ability_item_ent_heart" "Ent 的心" +"dota_tooltip_ability_item_ent_heart_Description" "

任务项

用于通过任务。" +"dota_tooltip_ability_item_ent_heart_Lore" "从古老的 ent 中撕裂出来的心脏,仍然随着森林的活力而跳动。据说它的主人对疾病和伤口无懈可击,他的身体充满了大自然本身的力量。" + +"DOTA_Tooltip_ability_item_wolf_hat" "Wolf Hat" +"DOTA_Tooltip_ability_item_wolf_hat_bonus_strength" "+$str" +"DOTA_Tooltip_ability_item_wolf_hat_bonus_move_speed" "+$move_speed" +"DOTA_Tooltip_ability_item_wolf_hat_lifesteal_pct" "%+$lifesteal" +"DOTA_Tooltip_ability_item_wolf_hat_low_hp_damage_bonus" "%+$damage" +"DOTA_Tooltip_ability_item_wolf_hat_Description" "

主动:狼群咆哮

%howl_duration% 秒内获得 %howl_attack_speed% 攻击速度和 %howl_move_speed_pct%%% 移动速度。

被动:头狼之血

对生命值低于 %low_hp_threshold%%% 的敌人造成额外 %low_hp_damage_bonus%%% 伤害。" +"DOTA_Tooltip_ability_item_wolf_hat_Note0" "昆凯维奇交付狼爪任务的奖励。不可出售。" +"DOTA_Tooltip_ability_item_wolf_hat_Lore" "船长用你带回的爪子缝成了这顶帽子:其中回荡着狼群的嚎叫,以及对负伤猎物穷追不舍的渴望。水手们说,戴着它连海怪都不敢靠近。" + +"DOTA_Tooltip_modifier_item_wolf_hat_howl" "狼群咆哮" +"DOTA_Tooltip_modifier_item_wolf_hat_howl_Description" "攻击速度和移动速度提高。" + +"dota_tooltip_ability_item_wolf_claw" "狼爪" +"dota_tooltip_ability_item_wolf_claw_Description" "

任务项

用于通过任务。" +"dota_tooltip_ability_item_wolf_claw_Lore" "属于狼群传奇领袖的锋利爪子。他的权力转移给了新主人,让他充满了野蛮和对狩猎的渴望。" + +"dota_tooltip_ability_item_spider_legs_custom" "蜘蛛脚" +"dota_tooltip_ability_item_spider_legs_custom_Description" "

任务项

用于提交任务。" +"dota_tooltip_ability_item_spider_legs_custom_Lore" "蜘蛛脚上覆盖着有毒的绒毛。即使生物死了,它们仍然会颤抖,就好像它们试图抓住猎物一样。" + +"dota_tooltip_ability_item_poison" "蜘蛛毒液" +"dota_tooltip_ability_item_poison_Description" "

任务项

用于通过任务。" +"dota_tooltip_ability_item_poison_Lore" "密封瓶中的浓稠深色液体。炼金术士为此付出了慷慨的代价,因为这种毒药可以制成致命的混合物。" + +"dota_tooltip_ability_item_frog_paw" "青蛙腿" +"dota_tooltip_ability_item_frog_paw_Description" "

任务项

用于通过任务。" +"dota_tooltip_ability_item_frog_paw_Lore" "稀有沼泽青蛙的小脚。它们的粘稠汁液为药水增添了所需的浓度和特殊的余味。" + +"dota_tooltip_ability_item_lycan_horn" "黑牙角" +"dota_tooltip_ability_item_lycan_horn_Description" "

任务项

用于通过任务。" +"dota_tooltip_ability_item_lycan_horn_Lore" "覆盖着古老符文的角属于莱肯本人—狼之王。人们相信它的主人可以召唤满月的力量,在战斗中变得势不可挡。" + +"dota_tooltip_ability_item_kunkka_sword" "海盗军刀" +"dota_tooltip_ability_item_kunkka_sword_bonus_damage" "+$damage" +"dota_tooltip_ability_item_kunkka_sword_move_speed_bonus" "%-移动速度" +"dota_tooltip_ability_item_kunkka_sword_Description" "

激活:波浪切割

激活后,以下 %attack_count% 的所有者攻击将导致 %crit_mult%% 伤害。" +"dota_tooltip_ability_item_kunkka_sword_Note0" "不比你的主板重,AZAZ。" +"dota_tooltip_ability_item_kunkka_sword_Lore" "来自 Kunka 手中的剑 – 一种被传说覆盖的武器。无数的生物都被他打败了:海怪、嗜血的海盗,甚至还有深渊统治者利维坦本人。他们说,刀片太重了,一个简单、三叉戟的人无法将其从地上抬起,更不用说滑倒击中它了。但对于真正的海狼来说,它的沉重–不是负担,而是提醒:在它的手中–是海洋的愤怒,在深渊中缓和。" + +"dota_tooltip_modifier_item_kunkka_sword_active" "酒精中毒" +"dota_tooltip_modifier_item_kunkka_sword_active_Description" "下一次攻击将产生严重后果。" + +"dota_tooltip_ability_item_oldmen_amulet" "长老的护身符" +"dota_tooltip_ability_item_oldmen_amulet_Description" "

放松心态

恢复目标法力的%mana_restore%。" +"dota_tooltip_ability_item_oldmen_amulet_spell_amplify_percentage" "%+$spell_amp" +"dota_tooltip_ability_item_oldmen_amulet_manacost_debuff" "%+消耗法力值" + +"dota_tooltip_ability_item_oldmen_amulet_Lore" "一个古老的护身符,属于一位活了一千年的圣人。它包含了他一生中的所有智慧。" +"dota_tooltip_ability_item_oldmen_amulet_Note0" "正如祖父所说:这是一个迷人的护身符,里面塞满了猪油。" +//实用程序 + +"dota_tooltip_ability_item_meat" "肉" +"dota_tooltip_ability_item_meat_Description" "

Bon appetit

使用时,目标会吃食物并恢复%hunger_bonus% satness 和 %heal% health。" +"dota_tooltip_ability_item_meat_Lore" "根据当地猎人的古老食谱制作的一块多汁的肉。它的香气唤醒了即使是最挑剔的食客的狼性胃口。" +"dota_tooltip_ability_item_meat_Note0" "饱和度产生饱和效果,英雄每次饱腹感都会获得其基本属性的 1% 奖励。" + +"dota_tooltip_ability_item_bread" "面包" +"dota_tooltip_ability_item_bread_Description" "

Bon appetit

使用时,目标会吃一块温热的面包并恢复%hunger_bonus% saturation 和 %heal% health。" +"dota_tooltip_ability_item_bread_Lore" "僵尸末日中简单但珍贵的面包。闻起来好像某个地方还剩下正常的生活—没有死者、海浪和无尽的探索。" + +"dota_tooltip_ability_item_testo" "面团" +"dota_tooltip_ability_item_testo_Description" "

成分

生面团仅用作烹饪原料。它本身没有任何作用—但你可以用它做一些美味的东西。" +"dota_tooltip_ability_item_testo_Lore" "根据一位最懂得如何在饱食的情况下度过僵尸末日老人的秘方收集。" + +"dota_tooltip_ability_item_testo_pizza" "披萨准备" +"dota_tooltip_ability_item_testo_pizza_Description" "

配料

披萨的基础。这是一种准备:它不会满足你的饥饿感,但它可以制作最后的披萨。" +"dota_tooltip_ability_item_testo_pizza_Lore" "根据某个懂行的人的食谱收集的面团:合适的酱汁使这道菜成为传奇。" + +"dota_tooltip_ability_item_wooden_katana" "木制武士刀" +"dota_tooltip_ability_item_wooden_katana_Description" "

被动:精确方法

增加%damage_outgoing_percentage%%%造成的损害。" +"dota_tooltip_ability_item_wooden_katana_Lore" "老武士刀大师训练。轻木不会切割钢材,但会教会您产生精确而强烈的冲击力。" + +"dota_tooltip_ability_item_firecore" "繁荣核心" +"dota_tooltip_ability_item_firecore_Description" "

繁荣

充满纯火热能量的核心适合加速宇宙物质。" +"dota_tooltip_ability_item_firecore_Lore" "他们说这颗水晶—一条古龙的冰冻火焰。即使在最严寒中,他的温暖也不会消失,光明能够照亮任何黑暗时期的道路。" + + +"dota_tooltip_ability_item_mayonnaise" "蛋黄酱" +"dota_tooltip_ability_item_mayonnaise_Description" "

配料

制作披萨所需的清凉酱汁。" +"dota_tooltip_ability_item_mayonnaise_Lore" "浓稠、细腻、美味。看起来他在僵尸末日中也幸存了下来,并且变得更好了。" + +"dota_tooltip_ability_item_pizza" "披萨" +"dota_tooltip_ability_item_pizza_Description" "

Bon appetit

使用时,目标吃掉披萨并恢复%hunger_bonus% saturation。还加强了 %stack_count% 上的所有字符特征。" +"dota_tooltip_ability_item_pizza_Lore" "即使周围只有波浪和任务,你也可以吃披萨。令人难以置信的美味—在实践中得到了证明。" + +"dota_tooltip_ability_item_cheese" "腌制奶酪" +"dota_tooltip_ability_item_cheese_Description" "

成分

凝乳奶酪仅用作烹饪原料。它本身没有任何作用—但你可以用它做一些美味的东西。" +"dota_tooltip_ability_item_cheese_Lore" "这种奶酪曾经是幸存者节的最高奖项。他们说,无论谁尝试至少一次,都会获得抵御任何僵尸攻击的力量—或者至少在接下来的 24 小时内忘记饥饿。" + +"dota_tooltip_ability_item_grilled_meat" "炸肉" +"dota_tooltip_ability_item_grilled_meat_Description" "

Bon appetit(premium)

使用时,目标吃掉多汁的牛排并恢复%hunger_bonus% saturation 和 %heal% health。" +"dota_tooltip_ability_item_grilled_meat_Lore" "在僵尸浪潮之间的短暂休息期间,肉在火上烤至完美的脆皮。每件作品都让人想起那个时代,当时的主要敌人不是饥饿,而是完成最后一次烧烤的邻居。" +"dota_tooltip_ability_item_grilled_meat_Note0" "饱和度产生饱和效果,英雄每次饱腹感都会获得其基本属性的 1% 奖励。" + +"dota_tooltip_ability_item_milk" "牛奶" +"dota_tooltip_ability_item_milk_Description" "

Bon appetit

使用时,目标会吃食物并恢复%hunger_bonus% saturation 和 %mana% man。" +"dota_tooltip_ability_item_milk_Lore" "根据当地猎人的古老食谱制作的一块多汁的肉。它的香气唤醒了即使是最挑剔的食客的狼性胃口。" +"dota_tooltip_ability_item_milk_Note0" "饱和度产生饱和效果,英雄每次饱腹感都会获得其基本属性的 1% 奖励。" + +"dota_tooltip_ability_item_banana" "香蕉" +"dota_tooltip_ability_item_banana_Description" "

Bon appetit

使用时,目标吃掉成熟的香蕉并恢复%mana%mana和%hunger_bonus%saturation。" +"dota_tooltip_ability_item_banana_Lore" "一根在仓库、抢劫者的袭击和三波僵尸中幸存下来的香蕉。 他体内充满了钾和魔法,即使是最疲惫的施法者也能激发他施展另一个咒语。" +"dota_tooltip_ability_item_banana_Note0" "饱和度产生饱和效果,英雄每次饱腹感都会获得其基本属性的 1% 奖励。" + +"dota_tooltip_ability_item_sandwich" "三明治" +"dota_tooltip_ability_item_sandwich_Description" "

Bon appetit

使用时,目标吃掉一个巨大的三明治,获得 %hunger_bonus% satiety 和 %buff_duration% sec: +%bonus_damage% to damage, +%bonus_armor% to armor, 但移动速度会降低%move_speed_slow_pct%%%。" +"dota_tooltip_ability_item_sandwich_Lore" "三层肉、奶酪、酱汁和面包,厚如重量盔甲。做完这样的三明治后,你就变成了一个行走的坦克—但是,你的移动速度也一样快。" +"dota_tooltip_ability_item_sandwich_Note0" "饱和度产生饱和效果,英雄每次饱腹感都会获得其基本属性的 1% 奖励。" + +"dota_tooltip_modifier_item_sandwich_buff" "三明治" +"dota_tooltip_modifier_item_sandwich_buff_Description" "英雄的体型增加了 %dMODIFIER_PROPERTY_MODEL_SCALE%%%,获得了 %dMODIFIER_PROPERTY_PREATTACK_BONUS_DAMAGE% 的伤害加成和 %dMODIFIER_PROPERTY_PHYSICAL_ARMOR_BONUS% 的盔甲加成,但移动速度降低了 %dMODIFIER_PROPERTY_MOVESPEED_BONUS_PERCENTAGE%%%。" + +"dota_tooltip_ability_item_energy_drink" "能量饮料" +"dota_tooltip_ability_item_energy_drink_Description" "

静脉肾上腺素

使用时,目标会注入一升纯粹的疯狂,恢复%hunger_bonus% satiety、%heal%health 和%mana%man。在下一个 %buff_duration% 秒,英雄将 %buff_move_speed_pct%%% 接收到 移动速度,并将 %buff_attack_speed%% 接收到 攻击速度,像被 Gaben 亲自驱逐一样奔跑,并且挥动手臂的速度比他想象的要快。" +"dota_tooltip_ability_item_energy_drink_Lore" "由于«每平方像素嗡嗡声太大»,被正式禁止参加所有电子竞技锦标赛。" +"dota_tooltip_ability_item_energy_drink_Note0" "饱和度产生饱和效果,对于每次饱腹感,英雄都会获得其基本属性的 1% 奖励。" + +"dota_tooltip_modifier_energy_drink_buff" "能量电荷" +"dota_tooltip_modifier_energy_drink_buff_Description" "移动速度增加了 %dMODIFIER_PROPERTY_MOVESPEED_BONUS_PERCENTAGE%%%,攻击速度增加了 %dMODIFIER_PROPERTY_ATTACKSPEED_PERCENTAGE%%%。" + +"dota_tooltip_ability_item_cocktail" "水果鸡尾酒" +"dota_tooltip_ability_item_cocktail_Description" "

Active: Magic Kneading

使用时,目标会喝到可疑的鸡尾酒,获得 %hunger_bonus% satiety 和 %buff_duration% sec: %spell_lifesteal%%% spell vampirism, %spell_amp%%% spell boosts, 但能力的法力消耗增加了%manacost_increase%%%。" +"dota_tooltip_ability_item_cocktail_Lore" "调酒师发誓,这种鸡尾酒«绝对不会致命,即使致命—也很漂亮»。确实如此:喝第一口之后,你的魔法伤害就会增加,法力融化的速度比对发明它的人的尊重还要快。" +"dota_tooltip_ability_item_cocktail_Note0" "饱和度产生饱和效果,英雄每次饱腹感都会获得其基本属性的 1% 奖励。" + +"dota_tooltip_modifier_item_cocktail_buff" "水果鸡尾酒" +"dota_tooltip_modifier_item_cocktail_buff_Description" "英雄陶醉于一杯魔法鸡尾酒:获得 %dMODIFIER_PROPERTY_SPELL_AMPLIFY_PERCENTAGE%% 法术增益和魔法吸血鬼效果,但能力需要 %dMODIFIER_PROPERTY_MANACOST_PERCENTAGE_STACKING%%% 的更多法力值。" + +"dota_tooltip_ability_item_coffee" "咖啡" +"dota_tooltip_ability_item_coffee_Description" "

Active: Turbo Espresso

使用时,目标将浓缩咖啡倒入自身,获得 %hunger_bonus% saturation 和 %buff_duration% sec:减少 %cooldown_reduction%%% 的能力充电并减少 %casttime_reduction%%% 的咒语应用时间。" +"dota_tooltip_ability_item_coffee_Lore" "从油炸的谷物焊接到幸存者的尖叫声和僵尸的嚎叫中。喝完这样的杯子后,咒语本身似乎比你的思想更快地从你手中飞出。" +"dota_tooltip_ability_item_coffee_Note0" "饱和度产生饱和效果,英雄每次饱腹感都会获得其基本属性的 1% 奖励。" + +"dota_tooltip_ability_item_coffe_bean" "咖啡豆" +"dota_tooltip_ability_item_coffe_bean_Description" "

有效:咖啡因射击

立即恢复最大目标法力的%mana_pct%%%和%mana%mana。不占用浅黄色食物槽,非常适合放在任何零食上。" +"dota_tooltip_ability_item_coffe_bean_Lore" "在不止一次僵尸末日中幸存下来的油炸谷物。它们如此强大,甚至可以唤醒假装装饰最后三波浪潮的古老怪人。" + + + + + +"dota_tooltip_ability_item_bag_of_gold" "一袋金子" +"dota_tooltip_ability_item_bag_of_gold_Description" "

财富

筹集后,您将获得 20-50 金币。" + + + +"dota_tooltip_ability_item_rofl_for_kaban_pumba" "野猪帽工作人员" +"dota_tooltip_ability_item_rofl_for_kaban_pumba_Description" "

PUMBA????

需要一头野猪。" +"dota_tooltip_ability_item_rofl_for_kaban_pumba_Lore" "你可以打败他,而不必认为你是个剥皮者。" + +"dota_tooltip_ability_item_candy" "糖果:)" +"dota_tooltip_ability_item_candy_Description" "

猜猜你会得到什么

通过吃这种糖果,你会得到一种随机的效果,这些效果累积起来并协同工作。" +"dota_tooltip_ability_item_candy_Lore" "甜蜜的牙齿在这里!?))" +"dota_tooltip_ability_item_candy_Note0" "巧克力糖:英雄获得 15%% 移速加成,停下时会持续受到伤害。
焦糖糖:有 20%% 几率承受 100%% 额外伤害,也有 20%% 几率反击,造成自身伤害及所受伤害的一半。
过期糖果:生命不会低于 1,但有 50%% 几率眩晕,减速 50%%,全属性降低 50%%,视野减少 250。
魔法糖果:魔法伤害提高 15%%,冷却缩短 15%%,物理伤害降低 15%%。" + +"dota_tooltip_modifier_chocolate_candy" "巧克力糖果" +"dota_tooltip_modifier_chocolate_candy_Description" "你更快,但最好不要停下来。" + +"dota_tooltip_modifier_caramel_candy" "焦糖糖" +"dota_tooltip_modifier_caramel_candy_Description" "你可以承受更多伤害,或者好吧……反击操他的嘴……????" + +"dota_tooltip_modifier_mint_candy" "过时的糖果" +"dota_tooltip_modifier_mint_candy_Description" "它他妈就是 0)" + +"dota_tooltip_modifier_magic_candy" "魔法糖果" +"dota_tooltip_modifier_magic_candy_Description" "你的魔法师伤害增加,补给减少,物理伤害也减少。" + +"dota_tooltip_ability_item_easter_egg" "复活节彩蛋" +"dota_tooltip_ability_item_easter_egg_Description" "

金鱼

金鱼在你周围盘旋并扔掉金袋。" +"dota_tooltip_ability_item_easter_egg_Lore" "他们说黑鱼子酱很贵。" +"dota_tooltip_ability_item_easter_egg_Note0" "每袋10-50金" + +"dota_tooltip_ability_item_egg" "鸡蛋" +"dota_tooltip_ability_item_egg_Description" "

成分

鸡蛋。仅用作烹饪原料。它本身没有任何作用—但你可以用它做一些美味的东西。" +"dota_tooltip_ability_item_egg_Lore" "他们说黑鱼子酱很贵。" + +"dota_tooltip_modifier_orbiting_wisp" "金混蛋" +"dota_tooltip_modifier_orbiting_wisp_Description" "一条裙子在你周围盘旋,上面布满了马奈。~" + + + + +//黑店 +//常见的 +"dota_tooltip_ability_item_blackshop_common_injector" "<字体颜色='#A0A0A0'>注射器" +"dota_tooltip_ability_item_blackshop_common_injector_Description" "

最后手段

增加攻击和移动速度%attack_speed%。

每次后续使用增加另一个%attack_speed%。" +"dota_tooltip_ability_item_blackshop_common_injector_Lore" "秘密商店炼金术士开发的实验刺激器。据说它的创造者因经常使用自己的发明而消失了。" +"dota_tooltip_ability_item_blackshop_common_injector_Note0" "可以多次检索。" + +"dota_tooltip_ability_item_blackshop_common_bonus_stats_agi" "敏捷手册" +"dota_tooltip_ability_item_blackshop_common_bonus_stats_agi_Description" "

千步路径

灵活性提高%bonus_agility%。

每次后续使用都会增加另一个%bonus_agility%。" +"dota_tooltip_ability_item_blackshop_common_bonus_stats_agi_Lore" "一位传奇武术家撰写的古代手稿。每一页都充满了通过多年实践磨练的动作和技巧的智慧。" +"dota_tooltip_ability_item_blackshop_common_bonus_stats_agi_Note0" "可以多次接收。" + +"dota_tooltip_ability_item_blackshop_common_bonus_stats_str" "<字体颜色='#A0A0A0'>强度手册" +"dota_tooltip_ability_item_blackshop_common_bonus_stats_str_Description" "

泰坦的力量

强度增加%bonus_strength%。

每次后续使用都会增加另一个%bonus_strength%。" +"dota_tooltip_ability_item_blackshop_common_bonus_stats_str_Lore" "在一座古代泰坦神庙的废墟中发现的巨著。它的页面由金属制成,字母是由众神自己雕刻的。" +"dota_tooltip_ability_item_blackshop_common_bonus_stats_str_Note0" "可以多次检索。" + +"dota_tooltip_ability_item_blackshop_common_bonus_stats_int" "<字体颜色='#A0A0A0'>情报书" +"dota_tooltip_ability_item_blackshop_common_bonus_stats_int_Description" "

宇宙的秘密

增加 %bonus_intelligence% 的智力。

每次后续使用都会增加另一个 %bonus_intelligence% 的智力。" +"dota_tooltip_ability_item_blackshop_common_bonus_stats_int_Lore" "一本页面不断变化的神秘魔法书。他们说每个读者都能从中看到不同的内容,但他们都变得更加聪明。" +"dota_tooltip_ability_item_blackshop_common_bonus_stats_int_Note0" "可以多次检索。" + +"dota_tooltip_ability_item_blackshop_common_king_crown" "国王皇冠" +"dota_tooltip_ability_item_blackshop_common_king_crown_Description" "

皇家宏伟

增加健康百分比%bonus_health%,增加盔甲百分比%bonus_armor%。

对于后续每次使用,增加另一个百分比%bonus_health% 和 %bonus_armor%。" +"dota_tooltip_ability_item_blackshop_common_king_crown_Lore" "国王和国王佩戴的王冠。当国王看着她时,她的金冠和钻石在阳光下闪闪发光。" +"dota_tooltip_ability_item_blackshop_common_king_crown_Note0" "可以多次检索。" + +"dota_tooltip_ability_item_blackshop_common_manaflare" "<字体颜色='#A0A0A0'>Mana flame" +"dota_tooltip_ability_item_blackshop_common_manaflare_Description" "

能量流

法力增加%bonus_mana%,法力再生增加%bonus_mana_regen%。

再增加%bonus_mana%和%bonus_mana_regen%。" +"dota_tooltip_ability_item_blackshop_common_manaflare_Lore" "可用于创建法力幻象的法力飞行器。" +"dota_tooltip_ability_item_blackshop_common_manaflare_Note0" "可以多次检索。" + +"dota_tooltip_ability_item_blackshop_common_spell_mask" "<字体颜色='#A0A0A0'>拼写掩码" +"dota_tooltip_ability_item_blackshop_common_spell_mask_Description" "

秘密艺术

通过%bonus_spell_amp%%%增强法术,通过%bonus_mana_regen%增强法术再生。

对于每次后续使用,增加另一个%bonus_spell_amp%%%和%bonus_mana_regen%。" +"dota_tooltip_ability_item_blackshop_common_spell_mask_Lore" "在魔术师神庙废墟中发现的古代面具。据说她是由一位伟大的大法师创造的,以加强她的咒语,但这种力量的代价太高了。" +"dota_tooltip_ability_item_blackshop_common_spell_mask_Note0" "可以多次检索。" + +"dota_tooltip_ability_item_blackshop_common_boo_stuff" "伟大的 Boo 的员工" +"dota_tooltip_ability_item_blackshop_common_boo_stuff_Description" "

战士之手

伤害增加%bonus_stats%,攻击范围增加%bonus_attack_range%。

增加另一个%bonus_stats%和%bonus_attack_range%。" +"dota_tooltip_ability_item_blackshop_common_boo_stuff_Lore" "属于伟大战士 Boo 的传奇武器。据传说,他可以在令人难以置信的距离内击中敌人,他的打击震动了地面。" +"dota_tooltip_ability_item_blackshop_common_boo_stuff_Note0" "可以多次获得。

仅近战英雄的攻击范围会增加。" + +"dota_tooltip_ability_item_blackshop_common_stone_armor" "<字体颜色='#A0A0A0'>石头盔甲" +"dota_tooltip_ability_item_blackshop_common_stone_armor_Description" "

石皮

盔甲增加%bonus_armor%。

每次后续使用都会增加另一个%bonus_armor%。" +"dota_tooltip_ability_item_blackshop_common_stone_armor_Lore" "用流星石锻造的一件古代盔甲。即使是最锋利的刀片也无法在上面留下划痕。" +"dota_tooltip_ability_item_blackshop_common_stone_armor_Note0" "可以多次检索。" + +"dota_tooltip_ability_item_blackshop_common_vigor_tincture" "<字体颜色='#A0A0A0'>活力药剂" +"dota_tooltip_ability_item_blackshop_common_vigor_tincture_Description" "

回春

生命恢复提高 %bonus_hp_regen%。

每次后续使用再增加 %bonus_hp_regen%。" +"dota_tooltip_ability_item_blackshop_common_vigor_tincture_Lore" "黑市草药与精华的混合物。闻起来有铁锈和苦根的味道——但伤口愈合得更快。" +"dota_tooltip_ability_item_blackshop_common_vigor_tincture_Note0" "可以多次获得。" +"dota_tooltip_ability_item_blackshop_common_vigor_tincture_bonus_hp_regen" "+$hp_regen" + +"dota_tooltip_ability_item_blackshop_common_wind_dust" "<字体颜色='#A0A0A0'>花岗岩之靴" +"dota_tooltip_ability_item_blackshop_common_wind_dust_Description" "

轻步

移动速度提高 %bonus_movement_speed%。

每次后续使用再增加 %bonus_movement_speed%。" +"dota_tooltip_ability_item_blackshop_common_wind_dust_Lore" "从山口吹来的细小晶尘。撒开它——脚步会自己向前。" +"dota_tooltip_ability_item_blackshop_common_wind_dust_Note0" "可以多次获得。" +"dota_tooltip_ability_item_blackshop_common_wind_dust_bonus_movement_speed" "+$move_speed" + +"dota_tooltip_ability_item_blackshop_common_blue_tallow" "<字体颜色='#A0A0A0'>蓝蜡" +"dota_tooltip_ability_item_blackshop_common_blue_tallow_Description" "

法力流转

法力恢复提高 %bonus_mana_regen%。

每次后续使用再增加 %bonus_mana_regen%。" +"dota_tooltip_ability_item_blackshop_common_blue_tallow_Lore" "来自魔力溪流的凝固精华。在掌心融化,沿血脉带来清凉,思绪也随之清明。" +"dota_tooltip_ability_item_blackshop_common_blue_tallow_Note0" "可以多次获得。" +"dota_tooltip_ability_item_blackshop_common_blue_tallow_bonus_mana_regen" "+$mana_regen" + +//稀有的 +"dota_tooltip_ability_item_blackshop_rare_agility_cape" "<字体颜色='#4B69FF'>敏捷斗篷" +"dota_tooltip_ability_item_blackshop_rare_agility_cape_Description" "

轻微移动

灵活性提高%bonus_agility%。

每次后续使用都会增加另一个%bonus_agility%。" +"dota_tooltip_ability_item_blackshop_rare_agility_cape_Lore" "一种可以让穿着者比平时移动得更快的斗篷。它的创造者认为这件文物可能很危险,因此将其密封在一座古庙废墟中发现的一个箱子里。" +"dota_tooltip_ability_item_blackshop_rare_agility_cape_Note0" "可以多次检索。" + +"dota_tooltip_ability_item_blackshop_rare_critical_havoc" "关键灵魂毁灭者" +"dota_tooltip_ability_item_blackshop_rare_critical_havoc_Description" "

关键开发人员

给予%crit_chance%%%以%crit_mult%%%的力量进行关键打击的机会。" +"dota_tooltip_ability_item_blackshop_rare_critical_havoc_Lore" "在一颗垂死恒星的火焰中锻造的古老刀片。这种武器的每次打击本身都携带着一种宇宙能量粒子,能够撕裂现实的结构。" +"dota_tooltip_ability_item_blackshop_rare_critical_havoc_Note0" "只能检索一次。

重置将允许该项目重新出现。" + +"dota_tooltip_ability_item_blackshop_rare_damage_dagger" "毁灭之刃" +"dota_tooltip_ability_item_blackshop_rare_damage_dagger_Description" "

毁灭之刃

每次击中伤害增加%bonus_damage%,然后重新充电。

每次后续使用都会增加另一个%bonus_damage%。" +"dota_tooltip_ability_item_blackshop_rare_damage_dagger_Lore" "一把传奇之刃,不仅可以摧毁敌人,还可以摧毁现实本身。它的创造者认为这件文物可能很危险,因此将其密封在一座古庙废墟中发现的一个箱子里。" +"dota_tooltip_ability_item_blackshop_rare_damage_dagger_Note0" "可以多次检索。" + +"dota_tooltip_ability_item_blackshop_rare_egg_of_death" "死亡之蛋" +"dota_tooltip_ability_item_blackshop_rare_egg_of_death_Description" "

死亡之蛋

增加敏捷性、力量和智力%bonus_all%。

每次后续使用增加另一个%bonus_all%。" +"dota_tooltip_ability_item_blackshop_rare_egg_of_death_Lore" "可以用来创造死亡幻觉的鸡蛋。它的创造者认为这件文物可能很危险,因此将其密封在一座古庙废墟中发现的一个箱子里。" +"dota_tooltip_ability_item_blackshop_rare_egg_of_death_Note0" "可以多次检索。" + +"dota_tooltip_ability_item_blackshop_rare_granite_badge" "<字体颜色='#4B69FF'>花岗岩徽章" +"dota_tooltip_ability_item_blackshop_rare_granite_badge_Description" "

石心

最大生命值提高 %bonus_health%。

每次后续使用再增加 %bonus_health%。" +"dota_tooltip_ability_item_blackshop_rare_granite_badge_Lore" "嵌有符文采石场花岗岩碎片的金属牌。佩戴者仿佛被承重墙所包裹。" +"dota_tooltip_ability_item_blackshop_rare_granite_badge_Note0" "可以多次获得。" +"dota_tooltip_ability_item_blackshop_rare_granite_badge_bonus_health" "+$health" + +"dota_tooltip_ability_item_blackshop_rare_iron_resolve" "<字体颜色='#4B69FF'>钢铁意志" +"dota_tooltip_ability_item_blackshop_rare_iron_resolve_Description" "

不屈

状态抗性提高 %bonus_status_resist%%%。

每次后续使用再提高 %bonus_status_resist%%%。" +"dota_tooltip_ability_item_blackshop_rare_iron_resolve_Lore" "浸透血与火、经锻炉淬炼的骑士誓布。减益更难缠身。" +"dota_tooltip_ability_item_blackshop_rare_iron_resolve_Note0" "可以多次获得。" + +"dota_tooltip_ability_item_blackshop_rare_silver_eye" "<字体颜色='#4B69FF'>鹰眼" +"dota_tooltip_ability_item_blackshop_rare_silver_eye_Description" "

银色凝视

增加 %bonus_vision% 单位的视野,同时增加 %bonus_vision% 单位的攻击范围和法术范围。

对于每次后续使用,增加另一个 %bonus_vision%。" +"dota_tooltip_ability_item_blackshop_rare_silver_eye_Lore" "可以增强其主人视力的神器。它的创造者认为这件文物可能很危险,因此将其密封在一座古庙废墟中发现的一个箱子里。" +"dota_tooltip_ability_item_blackshop_rare_silver_eye_Note0" "只能检索一次。

重置将允许物品重新出现。

增加攻击范围对近战英雄不起作用。" + +//史诗 +"dota_tooltip_ability_item_blackshop_epic_power_of_grow" "<字体颜色='#8847FF'>魔法蘑菇" +"dota_tooltip_ability_item_blackshop_epic_power_of_grow_Description" "

增长力量

将角色大小增加%bonus_stats%%%,并且每增加一个百分比就会增加伤害。" +"dota_tooltip_ability_item_blackshop_epic_power_of_grow_Lore" "在古代德鲁伊的秘密花园里生长的神奇蘑菇。他们说这些蘑菇以地球的纯净活力为食,这就是它们达到令人难以置信的大小的原因。德鲁伊在成长仪式中使用它们,但当他们的学生开始滥用这些自然天赋来追求力量时,他们最终失去了对这种力量的控制。" +"dota_tooltip_ability_item_blackshop_epic_power_of_grow_Note0" "只能检索一次。

重置将允许该项目重新出现。" +"dota_tooltip_modifier_item_power_of_grow_scale" "神奇蘑菇" +"dota_tooltip_modifier_item_power_of_grow_scale_Description" "字符大小增加了 %dMODIFIER_PROPERTY_MODEL_SCALE%%%" + +"dota_tooltip_ability_item_blackshop_epic_critical_paladin_sword" "<字体颜色='#8847FF'>圣骑士刀片" +"dota_tooltip_ability_item_blackshop_epic_critical_paladin_sword_Description" "

圣骑士刀片

对%crit_multiplier%%%的临界冲击损伤" +"dota_tooltip_ability_item_blackshop_epic_critical_paladin_sword_Lore" "一把能够以巨大力量进行批判性打击的剑。它的创造者认为这件文物可能很危险,因此将其密封在一座古庙废墟中发现的一个箱子里。" +"dota_tooltip_ability_item_blackshop_epic_critical_paladin_sword_Note0" "可以检索多次。

项目只能在保证关键影响的情况下触发" + +"dota_tooltip_ability_item_blackshop_epic_trinity_seal" "<字体颜色='#8847FF'>三相之印" +"dota_tooltip_ability_item_blackshop_epic_trinity_seal_Description" "

三相纹章

力量、敏捷和智力各提高 %bonus_all%。

每次后续使用再为三项属性各增加 %bonus_all%。" +"dota_tooltip_ability_item_blackshop_epic_trinity_seal_Lore" "三环相扣的金属印信。铸于签订跨界契约的工坊——持有者从存在的每一面汲取一丝力量。" +"dota_tooltip_ability_item_blackshop_epic_trinity_seal_Note0" "可以多次获得。" +"dota_tooltip_ability_item_blackshop_epic_trinity_seal_bonus_all" "+$all" + +"dota_tooltip_ability_item_blackshop_epic_bulwark_plate" "<字体颜色='#8847FF'>壁垒板甲片" +"dota_tooltip_ability_item_blackshop_epic_bulwark_plate_Description" "

攻城难侵

增加 %bonus_armor_per% 护甲与 %bonus_mr_per%%% 魔抗。

每次再次使用都会再增加相同数值。" +"dota_tooltip_ability_item_blackshop_epic_bulwark_plate_Lore" "刻有坚韧符文的钢板,本用于攻城塔。穿在英雄身上便成了移动的堡垒。" +"dota_tooltip_ability_item_blackshop_epic_bulwark_plate_Note0" "可以多次获得。" +"dota_tooltip_modifier_item_bulwark_plate_display" "壁垒板甲片" +"dota_tooltip_modifier_item_bulwark_plate_display_Description" "堡垒庇护" + +//传奇 +"dota_tooltip_ability_item_blackshop_legendary_restock" "繁荣周期" +"dota_tooltip_ability_item_blackshop_legendary_restock_Description" "

现实不是限制

在黑色商店购买一件商品后,当前池中的另一件商品将代替购买的商品出现。" +"dota_tooltip_ability_item_blackshop_legendary_restock_Lore" "古代商人为维持无尽的货物流动而创造的神秘文物。据说他的魔法能够将这个世界上曾经存在过的任何物体具体化。" +"dota_tooltip_ability_item_blackshop_legendary_restock_Note0" "只能检索一次。

重置将不允许该项目重新出现。" + +"dota_tooltip_ability_item_blackshop_legendary_fire_summoner" "元素召唤法杖" +"dota_tooltip_ability_item_blackshop_legendary_fire_summoner_Description" "

元素的召唤杖

召唤火焰元素,它会攻击敌人。" +"dota_tooltip_ability_item_blackshop_legendary_fire_summoner_Lore" "一根古老的法杖,用最纯净的黑曜石在火山中心锻造而成,充满了原始火焰的能量。传说第一位元素论者利用他与火领主达成协议,获得了控制他火烈仆人的权力。" +"dota_tooltip_ability_item_blackshop_legendary_fire_summoner_Note0" "可以多次检索。

重置将允许项目重新出现。

获取其所有者的克里特岛修饰符。" + +"dota_tooltip_ability_item_blackshop_legendary_primordial_shard" "<字体颜色='#FFD700'>原初碎片" +"dota_tooltip_ability_item_blackshop_legendary_primordial_shard_Description" "

原初物质

力量、敏捷和智力各提高 %bonus_all%。

每次后续使用再为三项属性各增加 %bonus_all%。" +"dota_tooltip_ability_item_blackshop_legendary_primordial_shard_Lore" "元素尚未分离前的物质碎片。其中仍回响着没有法术与利刃、唯有浑然之力的世界的低语。" +"dota_tooltip_ability_item_blackshop_legendary_primordial_shard_Note0" "可以多次获得。" +"dota_tooltip_ability_item_blackshop_legendary_primordial_shard_bonus_all" "+$all" +"dota_tooltip_modifier_item_primordial_shard" "原初碎片" +"dota_tooltip_modifier_item_primordial_shard_Description" "提升力量、敏捷与智力" + +"dota_tooltip_ability_item_blackshop_legendary_twilight_mirror" "暮光之镜" +"dota_tooltip_ability_item_blackshop_legendary_twilight_mirror_Description" "

折映之力

永久获得 %spell_amp_pct%%% 技能增强,并使受到的所有伤害提高 %incoming_damage_pct%%%。" +"dota_tooltip_ability_item_blackshop_legendary_twilight_mirror_Lore" "一块凝固暮色磨成的圆盘:法术更易穿出,利刃也更容易刺穿你。" +"dota_tooltip_ability_item_blackshop_legendary_twilight_mirror_Note0" "每名英雄一次:若已有效果,物品不会被消耗。" +"dota_tooltip_modifier_item_twilight_mirror" "暮光之镜" +"dota_tooltip_modifier_item_twilight_mirror_Description" "技能增强与更易受伤" + +"dota_tooltip_ability_item_blackshop_legendary_astral_anchor" "星界锚点" +"dota_tooltip_ability_item_blackshop_legendary_astral_anchor_Description" "

远施缓行

永久增加 %cast_range_bonus% 施法距离,但降低 %move_speed_loss_pct%%% 移动速度。" +"dota_tooltip_ability_item_blackshop_legendary_astral_anchor_Lore" "在法术比脚步伸得更远之处生长的水晶——身体却跟不上意志。" +"dota_tooltip_ability_item_blackshop_legendary_astral_anchor_Note0" "每名英雄一次:若已有效果,物品不会被消耗。" +"dota_tooltip_modifier_item_astral_anchor" "星界锚点" +"dota_tooltip_modifier_item_astral_anchor_Description" "施法距离与减速" + +"dota_tooltip_ability_item_blackshop_legendary_fated_die" "宿命之骰" +"dota_tooltip_ability_item_blackshop_legendary_fated_die_Description" "

幸运

永久提高英雄幸运值 %luck_bonus%(影响掉落概率等模式内系统)。" +"dota_tooltip_ability_item_blackshop_legendary_fated_die_Lore" "陨铁铸成的骰子——坠落的方向由运势决定,而非重力。" +"dota_tooltip_ability_item_blackshop_legendary_fated_die_Note0" "可多次获得;幸运可叠加。" + +//诅咒 +"dota_tooltip_ability_item_blackshop_cursed_the_hand_of_gluttony" "食者的手" +"dota_tooltip_ability_item_blackshop_cursed_the_hand_of_gluttony_Description" "

食手

受到攻击时,英雄按所受伤害的 %bonus_stats%% 治疗。

每次再次获得该效果,额外提高 %bonus_stats%%%。

失控


攻击时,英雄有概率失控 3 秒。


失衡


英雄可能攻击友军,也可能被友军攻击。
" +"dota_tooltip_ability_item_blackshop_cursed_the_hand_of_gluttony_Lore" "一只可以随机选择一个盟友并攻击它的手。它的创造者认为这件文物可能很危险,因此将其密封在一座古庙废墟中发现的一个箱子里。" +"dota_tooltip_ability_item_blackshop_cursed_the_hand_of_gluttony_Note0" "可以多次检索。

重置将不允许项目重新出现。" + +"dota_tooltip_ability_item_blackshop_cursed_martyrs_brand" "殉道者烙印" +"dota_tooltip_ability_item_blackshop_cursed_martyrs_brand_Description" "

以血换击

每次使用叠加一层:+%bonus_damage_per% 攻击力,每层额外 +%incoming_damage_per_stack%%% 承受伤害。

层数可累加。" +"dota_tooltip_ability_item_blackshop_cursed_martyrs_brand_Lore" "烙在血肉上的铁印:你挥得越狠,回击也越痛。" +"dota_tooltip_ability_item_blackshop_cursed_martyrs_brand_Note0" "可多次获得;层数累加。" +"dota_tooltip_modifier_item_martyrs_brand" "殉道者烙印" +"dota_tooltip_modifier_item_martyrs_brand_Description" "每层提供攻击力与承受伤害" + +"dota_tooltip_ability_item_blackshop_cursed_widow_chain" "寡妇之链" +"dota_tooltip_ability_item_blackshop_cursed_widow_chain_Description" "

沉重一击

每层:+%bonus_outgoing_damage_pct%%% 输出伤害,−%attack_speed_loss_pct%%% 攻击速度。每次攻击会使自身定身 %root_duration% 秒。" +"dota_tooltip_ability_item_blackshop_cursed_widow_chain_Lore" "锁链将你拽向地面:一击见血,双腿却被链环缠住。" +"dota_tooltip_ability_item_blackshop_cursed_widow_chain_Note0" "可多次获得;层数累加。定身作用于攻击者自身。" +"dota_tooltip_modifier_item_widow_chain" "寡妇之链" +"dota_tooltip_modifier_item_widow_chain_Description" "伤害、慢攻速、攻击后自我定身" +"dota_tooltip_modifier_item_widow_chain_root" "链缚" +"dota_tooltip_modifier_item_widow_chain_root_Description" "定身" + +"dota_tooltip_ability_item_blackshop_cursed_glass_pact" "玻璃契约" +"dota_tooltip_ability_item_blackshop_cursed_glass_pact_Description" "

脆弱之力

永久使你造成的伤害提高 %outgoing_damage_pct%%%,受到的伤害提高 %incoming_damage_pct%%%。" +"dota_tooltip_ability_item_blackshop_cursed_glass_pact_Lore" "刻在透明刃上的契约——映出你的斩击,也映出他们的回击。" +"dota_tooltip_ability_item_blackshop_cursed_glass_pact_Note0" "每名英雄一次:若契约已生效,物品不会被消耗。" +"dota_tooltip_modifier_item_glass_pact" "玻璃契约" +"dota_tooltip_modifier_item_glass_pact_Description" "输出与承伤同时提高" + +//天上 +"dota_tooltip_ability_item_blackshop_heavenly_reset_to_zero" "从末尾开始" +"dota_tooltip_ability_item_blackshop_heavenly_reset_to_zero_Description" "

重生

重置整个物品池,允许您购买已从中删除的物品。" +"dota_tooltip_ability_item_blackshop_heavenly_reset_to_zero_Lore" "由寻找重新开始的方法的强大生物创造的古代文物。据说它的创造者利用时间本身的力量抹去了过去,从一张白纸开始。然而,这种力量是有代价的——一旦一件文物被使用,它的主人就永远失去了回到他以前的选择的机会。" +"dota_tooltip_ability_item_blackshop_heavenly_reset_to_zero_Note0" "只能检索一次。重置将不允许该项目重新出现。" + +"dota_tooltip_ability_item_blackshop_heavenly_font_of_mercy" "仁慈圣泉" +"dota_tooltip_ability_item_blackshop_heavenly_font_of_mercy_Description" "

群体治疗

使用时消耗。治疗 %radius% 范围内友方英雄 %heal_flat% 点生命,并额外按最大生命值的 %heal_max_hp_pct%%% 治疗,并驱散负面效果。此后只要英雄存活,祝福便保留在身上;当冷却结束且范围内有真实友方英雄(非幻象)生命值低于 %crisis_hp_pct%%% 时会自动再次触发。英雄死亡时,圣泉祝福及相关效果全部消失。

第一次冷却为 %hero_cooldown% 秒;之后每次触发,下一次冷却时间在上一次基础上减半(以基础 %hero_cooldown% 秒换算),最短不低于 12 秒。" +"dota_tooltip_ability_item_blackshop_heavenly_font_of_mercy_Lore" "只盛满怜悯的容器——浓烈到能洒向附近的每一位守护者。" +"dota_tooltip_ability_item_blackshop_heavenly_font_of_mercy_Note0" "每名英雄只能拥有一件圣泉效果,无法再次使用该物品。幻象不计入生命值判定。增益层数等于已触发的治疗次数。英雄死亡会失去圣泉祝福。" +"dota_tooltip_modifier_item_heavenly_font_of_mercy_display" "仁慈圣泉" +"dota_tooltip_modifier_item_heavenly_font_of_mercy_display_Description" "圣泉祝福。层数表示已触发群体治疗的次数。英雄死亡时移除。" +"dota_tooltip_modifier_item_heavenly_font_of_mercy_cooldown" "仁慈圣泉:冷却" +"dota_tooltip_modifier_item_heavenly_font_of_mercy_cooldown_Description" "距离下一次可在满足生命值条件时自动治疗前的剩余时间。英雄死亡时与祝福一并移除。" + +"dota_tooltip_ability_item_blackshop_heavenly_sanctuary_veil" "圣所帷幕" +"dota_tooltip_ability_item_blackshop_heavenly_sanctuary_veil_Description" "

帷幕

驱散负面效果。每次使用都会加入物品上固定的 +%status_resist%%% 状态抗性与 +%magic_resist%%% 魔法抗性;再次使用会再加一次相同常数(可累加)。效果不消失、不可驱散。" +"dota_tooltip_ability_item_blackshop_heavenly_sanctuary_veil_Lore" "以神殿寂静织成的布——披得越多,平静越久。" +"dota_tooltip_ability_item_blackshop_heavenly_sanctuary_veil_Note0" "使用后消耗。死亡后保留。" +"dota_tooltip_modifier_item_heavenly_sanctuary_veil_display" "圣所帷幕" +"dota_tooltip_modifier_item_heavenly_sanctuary_veil_display_Description" "帷幕祝福。层数为使用次数;状态抗性与魔法抗性按物品固定数值逐次累加。" +"dota_tooltip_modifier_item_heavenly_sanctuary_veil_mr" "帷幕:魔法抗性" +"dota_tooltip_modifier_item_heavenly_sanctuary_veil_mr_Description" "所有帷幕使用所累计的魔法抗性(每次等于物品的魔法抗性常数)。" + +"dota_tooltip_ability_item_blackshop_heavenly_dawn_chorus" "黎明合唱" +"dota_tooltip_ability_item_blackshop_heavenly_dawn_chorus_Description" "

群体庇护

在 %radius% 范围内强化友方英雄:+%status_resist%%% 状态抗性与 +%magic_resist%%% 魔法抗性。

每次再次使用都会为范围内每名友方再增加相同数值。效果不消失、不可驱散。" +"dota_tooltip_ability_item_blackshop_heavenly_dawn_chorus_Lore" "把第一缕光收成声响——只要群体还记得黎明,光就不散。" +"dota_tooltip_ability_item_blackshop_heavenly_dawn_chorus_Note0" "使用后消耗。对幻象无效。死亡后保留。" +"dota_tooltip_modifier_item_heavenly_dawn_chorus_display" "黎明合唱" +"dota_tooltip_modifier_item_heavenly_dawn_chorus_display_Description" "黎明回响" + +// -----------------斧王-------------------- +"dota_tooltip_ability_axe_berserkers_call_custom" "Berserker's Call" +"dota_tooltip_ability_axe_berserkers_call_custom_Description" "嘲讽半径 %radius% 内的敌人攻击斧王,并在 %duration% 秒内为其提供 %bonus_armor% 点护甲。" +"dota_tooltip_ability_axe_berserkers_call_custom_Lore" "斧王一吼,连恐惧也得听令。" +"dota_tooltip_ability_axe_berserkers_call_custom_Shard_Description" "拥有阿哈利姆魔晶时,狂战士之吼还会对敌人以及范围内的斧王与友方英雄施加战斗饥渴。" +"dota_tooltip_ability_axe_berserkers_call_custom_radius" "半径:" +"dota_tooltip_ability_axe_berserkers_call_custom_bonus_armor" "额外护甲:" +"dota_tooltip_ability_axe_berserkers_call_custom_duration" "持续时间:" + +"dota_tooltip_ability_axe_battle_hunger_custom" "Battle Hunger" +"dota_tooltip_ability_axe_battle_hunger_custom_Description" "对敌人:施加持续 %duration% 秒的饥渴,背向斧王移动时减速,并每秒造成 %damage_per_second% 点伤害,另加斧王护甲换算的额外伤害。对友军:每秒获得 5 层饥渴、治疗,并提高 %speed_bonus%%% 输出伤害。" +"dota_tooltip_ability_axe_battle_hunger_custom_Lore" "斧王的饥渴与食物无关,只与一场无人满意的厮杀有关。" +"dota_tooltip_ability_axe_battle_hunger_custom_slow" "%移动减速:" +"dota_tooltip_ability_axe_battle_hunger_custom_damage_per_second" "每秒伤害:" +"dota_tooltip_ability_axe_battle_hunger_custom_duration" "持续时间:" +"dota_tooltip_ability_axe_battle_hunger_custom_speed_bonus" "%输出伤害:" + +"dota_tooltip_ability_axe_counter_helix_custom" "Counter Helix" +"dota_tooltip_ability_axe_counter_helix_custom_Description" "受到伤害时,斧王有 %trigger_chance%%% 几率触发反击螺旋,对 %radius% 内敌人造成 %damage% 点纯粹伤害并发动攻击。" +"dota_tooltip_ability_axe_counter_helix_custom_Lore" "敌人靠得越近,越能明白这是个馊主意。" +"dota_tooltip_ability_axe_counter_helix_custom_trigger_chance" "%触发几率:" +"dota_tooltip_ability_axe_counter_helix_custom_radius" "半径:" +"dota_tooltip_ability_axe_counter_helix_custom_damage" "伤害:" + +"dota_tooltip_ability_axe_culling_blade_custom" "Culling Blade" +"dota_tooltip_ability_axe_culling_blade_custom_Description" "打击目标点周围区域内的敌人(半径 %cull_radius%)。对每个敌人造成相当于斧王当前攻击力的伤害。击杀时获得永久护甲:英雄 %armor_per_stack%,小兵 %armor_per_creep_kill%。成功击杀时加速 %speed_aoe% 范围内的友军。" +"dota_tooltip_ability_axe_culling_blade_custom_Lore" "斧王的判决很简短:挨上这一斧的人已经输了。" +"dota_tooltip_ability_axe_culling_blade_custom_Scepter_Description" "拥有阿哈利姆神杖时,淘汰之刃每成功击杀一名敌人,该技能剩余冷却时间额外减少 1 秒。" +"dota_tooltip_ability_axe_culling_blade_custom_Shard_Description" "拥有阿哈利姆魔晶时,斧王每击杀一个小兵,所有技能剩余冷却时间减少 1 秒。" +"dota_tooltip_ability_axe_culling_blade_custom_cull_radius" "半径:" +"dota_tooltip_ability_axe_culling_blade_custom_speed_bonus" "%移动速度:" +"dota_tooltip_ability_axe_culling_blade_custom_attack_speed_bonus" "攻击速度:" +"dota_tooltip_ability_axe_culling_blade_custom_speed_duration" "加速持续时间:" +"dota_tooltip_ability_axe_culling_blade_custom_speed_aoe" "加速范围:" +"dota_tooltip_ability_axe_culling_blade_custom_armor_per_stack" "击杀英雄护甲:" +"dota_tooltip_ability_axe_culling_blade_custom_armor_per_creep_kill" "击杀小兵护甲:" + +"dota_tooltip_ability_axe_one_man_army_custom" "One Man Army" +"dota_tooltip_ability_axe_one_man_army_custom_Description" "若 %radius% 内没有其他友方英雄,斧王根据护甲获得力量:每 1 点护甲提供 %armor_to_str% 点力量。" +"dota_tooltip_ability_axe_one_man_army_custom_Lore" "斧王不需要军队,斧王就是军队。" +"dota_tooltip_ability_axe_one_man_army_custom_radius" "判定半径:" +"dota_tooltip_ability_axe_one_man_army_custom_armor_to_str" "每点护甲力量:" + +"dota_tooltip_ability_special_bonus_unique_axe_2" "淘汰之刃半径 +150" +"dota_tooltip_ability_special_bonus_unique_axe_8" "一人之军每点护甲力量 +0.2" +"dota_tooltip_ability_special_bonus_unique_axe" "反击螺旋伤害 +200" +"dota_tooltip_ability_special_bonus_unique_axe_7" "狂战士之吼护甲 +10" +"dota_tooltip_ability_special_bonus_unique_axe_4" "战斗饥渴每秒伤害 +8" +"dota_tooltip_ability_special_bonus_unique_axe_5" "狂战士之吼半径 +85" +"dota_tooltip_ability_special_bonus_unique_axe_culling_blade_speed_duration" "淘汰之刃加速持续时间 +3 秒" + +//地图 +"card_1_name" "Bloodway" +"card_1_description" "+%vampirism_bonus%% 对吸血鬼。 最大生命值 −%max_health_penalty_pct%%%。
+%damage_multiplier%% 对每单位吸血鬼的所有物理伤害" +"card_1_vampirism_bonus" "吸血:" +"card_1_damage_multiplier" "每点吸血额外物理伤害:" +"card_1_max_health_penalty_pct" "最大生命降低:" + +"card_2_name" "<字体颜色='#FF6B6B'>光笔" +"card_2_description" "提供 %speed_bonus% 移动速度。" +"card_2_description_level_2" "等级 2:移动速度上限提升至 %speed_limit% 。" +"card_2_description_level_3" "等级 3:英雄可穿过单位;基础攻击间隔降低 %attacktime%%% 。" + +"card_3_name" "<字体颜色='#8a9597'>精神力量" +"card_3_description" "+%damage_pct%% 对所有物理和魔法伤害。" + +"card_4_name" "精神的狂暴" +"card_4_description" "将 %card_bonus% 的精神力量卡混入玩家池。" + +"card_5_name" "卡片疯狂" +"card_5_description" "对在本卡之前获得的每张牌,提供 %card_bonus_pct%% 的力量、敏捷与智力加成。" + +"card_6_name" "快乐的手" +"card_6_description" "获得时:额外进行一次 %card_show_count% 选卡。 永久将每次选卡的最少张数提高到 %min_card_choice% 张。" +"card_6_description_level_2" "等级 2: +%free_reroll_charges% 次免费重掷。" +"card_6_description_level_3" "等级 3:此额外选卡只会出现传说卡牌。" +"card_6_card_show_count" "额外选卡张数:" +"card_6_min_card_choice" "每次最少张数:" +"card_6_free_reroll_charges" "免费重掷:" + +"card_7_name" "Curse
日食
" +"card_7_description" "夜间:
+%daynight_bonus_pct%% 所有属性。
白天:
-%daynight_bonus_pct%% 所有属性。
" + +"card_8_name" "Curse
spray heart
" +"card_8_description" "每次击杀,英雄都会永远失去 %damage_bonus_health_decress% 的生命值,同时还会永久增加 %damage_bonus_health_decress% 的伤害。仅在拥有者生命值不低于 %stack_health_threshold% 时,击杀才会叠加层数。" + +"card_9_name" "Curse
alak midas
" +"card_9_description" "提供 +1 层共享贪婪。 击杀时获得额外金币:每张复制提供 +%kill_gold_bonus_pct%%% 的目标赏金。属性加成见「贪婪」 buff(按经济值计算)。
死亡时失去全部金币,并按卡牌复制数随机失去主物品栏物品。" + + +"card_10_name" "灰烬烙印" +"card_10_description" "攻击会施加持续 %debuff_duration% 秒的减益。
目标每有 1 层燃烧(modifier_general_fired),承受的伤害提高 %incoming_damage_per_fired_stack_pct%%% ,最多提高 %max_incoming_damage_bonus_pct%%% 。该加成仅对卡牌拥有者造成的伤害生效。" +"card_11_name" "雄心壮志" +"card_11_description" "按等级提升主属性。
若主属性不是全才:每级主属性 +%primary_attribute_bonus_per_level% 。若主属性是全才:每级全属性 +%universal_all_bonus_per_level% 。" +"card_12_name" "夜渊之眼" +"card_12_description" "每隔 %scan_interval% 秒,标记 %scan_radius% 范围内最强敌人,持续 %mark_duration% 秒
对被标记目标:造成 +%bonus_damage_pct%%% 额外伤害。击杀被标记目标时,回复最大生命值的 %on_kill_heal_pct%%% 并获得 +%on_kill_mana% 法力。对首领时,额外伤害降低 %boss_penalty_pct%%% 。" +"card_13_name" "燃渊之眼" +"card_13_description" "每次攻击施加 %fired_stacks_on_hit% 层燃烧
有 %explosion_chance_pct%%% 几率使目标在 %explosion_radius% 半径内爆炸。爆炸伤害:(燃烧层数 × %explosion_damage_per_stack%) + 攻击者伤害。" +"card_14_name" "炼狱弹射" +"card_14_description" "攻击有 %proc_chance_pct%%% 几率点燃目标,并在 %ricochet_radius% 半径内发射弹射投射物
主目标:基于攻击伤害造成 +%main_target_bonus_damage_pct%%% 魔法伤害,并施加 %fired_stacks% 层燃烧。每次弹射都会触发一次独立攻击,造成 %ricochet_damage_pct%%% 攻击伤害,并施加 %fired_stacks% 层燃烧。" +"card_15_name" "赤红镰刃" +"card_15_description" "提供固定 %base_crit_chance_pct%%% 的自定义暴击几率。
暴击伤害随幸运值成长:%crit_multiplier_pct%%% + 每 1 点 Luck 提升 %crit_multiplier_bonus_pct_per_luck%%% 。" +"card_16_name" "虚空碎片" +"card_16_description" "每击杀一个敌人获得 1 层(最多 %max_stacks% 层)。
每层提供 +%spell_amp_per_stack_pct%%% 技能增强。" +"card_17_name" "诅咒·血手" +"card_17_description" "攻击时,若生命值高于 %min_health_pct_to_activate%%% ,英雄会消耗最大生命值的 %health_cost_pct%%% 。
消耗的生命值转化为纯粹伤害;每层诅咒使伤害再乘以 (1 + 层数 × %curse_damage_pct_per_curse_stack%% / 100)。" +"card_18_name" "幸运眼镜" +"card_18_description" "被动提供 +%base_luck_bonus% 幸运值。
另外每拿取一张卡牌,额外获得 +%luck_per_card_taken% 幸运值。" +"card_19_name" "空衣和服" +"card_19_description" "攻击有 %ghost_step_chance_pct%%% 概率触发“幽步”,持续 %ghost_step_duration% 秒。
效果期间:+%ghost_step_move_speed_pct%%% 移动速度,+%ghost_step_evasion_pct%%% 闪避,并可穿过单位。" +"card_20_name" "黄金战靴" +"card_20_description" "每拥有 %gold_per_step% 金币,提供 +%move_speed_pct_per_step%%% 移动速度。
移除移动速度上限。
贪婪:每 250 经济值 1 层;每层 +1 全属性。" +"card_21_name" "烈日之刃" +"card_21_description" "每次攻击 npc_wave 类型单位时,都会造成额外纯粹伤害。
额外伤害:%wave_pure_damage_base% + 持有者攻击力。" +"card_22_name" "黎明金币" +"card_22_description" "每熬过一夜后的清晨获得金币。第 1 夜后 — %morning_gold_base%;之后每多一夜少 %morning_gold_decay_per_night%。" +"card_22_description_level_2" "2 级:选取时 +%pickup_gold_pct%%%(相对下次清晨金额)。" +"card_22_description_level_3" "3 级:入夜前再 +%pre_night_gold_pct%%%(同一金额)。" +"card_23_name" "黄金增长" +"card_23_description" "每 %gold_tick_interval_sec% 秒获得额外金币,数额为当前金币的 %gold_income_pct_per_minute%%% 。
若该间隔内获得的金币少于本次分红的 %min_earned_gold_pct_of_dividend_required%%% ,则不会发放分红。
贪婪:每 250 经济值 1 层;每层 +1 全属性。" +"card_24_name" "黄金之手" +"card_24_description" "每拥有 %gold_per_step% 金币,英雄获得 +%attack_damage_per_step%%% 攻击力和 +%spell_amp_per_step_pct%%% 技能增强。
贪婪:每 250 经济值 1 层;每层 +1 全属性。" +"card_25_name" "法力核心" +"card_25_description" "每有 %mana_per_step% 当前法力,英雄获得 +%spell_amp_per_step_pct%%% 技能增强和 +%mana_regen_per_step% 法力回复。" +"card_25_description_level_3" "3级:最大法力 +%max_mana_bonus_pct%%% 。" +"card_25_max_mana_bonus_pct" "最大法力:" +"card_26_name" "暗影誓约" +"card_26_description" "英雄生命值不会低于 1。
当生命值低于 %trigger_health_threshold% 时,英雄进入隐身并每秒回复最大生命值的 %heal_pct_per_second%%% 。效果结束后进入 %cooldown_seconds% 秒冷却。" +"card_27_name" "法力之刃" +"card_27_description" "英雄当前法力值的 %mana_to_damage_pct%%% 会转化为攻击伤害。" +"card_28_name" "血刃" +"card_28_description" "英雄当前生命值的 %health_to_damage_pct%%% 会转化为攻击伤害。" +"card_29_name" "血月" +"card_29_description" "夜晚提供 +%night_vampirism_pct%%% 物理吸血,并使受到的伤害降低 %night_incoming_damage_reduce_pct%%% 。" +"card_30_name" "诅咒·成长" +"card_30_description" "英雄每级:+%strength_per_level% 力量,但 -%agility_penalty_per_level% 敏捷与 -%intellect_penalty_per_level% 智力。
该效果会按已获得的诅咒数量进行倍增。" +"card_31_name" "诅咒·敏捷" +"card_31_description" "英雄每级:+%agility_per_level% 敏捷,但 -%strength_penalty_per_level% 力量与 -%intellect_penalty_per_level% 智力。
该效果会按已获得的诅咒数量进行倍增。" +"card_32_name" "沙漏" +"card_32_description" "提供 +%base_cooldown_reduction_pct%%% 技能冷却缩减。
英雄每次死亡后,该效果降低 %cooldown_loss_per_death_pct%%% 。" +"card_33_name" "法力狂潮" +"card_33_description" "拾取时仅触发一次:最大法力值提高当前最大法力值的 %mana_increase_pct%%% 。" +"card_33_description_level_3" "3级:额外获得「小型法力狂潮」。" +"card_34_name" "血之收割" +"card_34_description" "补刀击杀敌人时,恢复生命值:目标最大生命值的 %heal_from_enemy_max_hp_pct%%% + 你攻击伤害的 %heal_from_attack_damage_pct%%% 。" +"card_35_name" "剧毒伤口" +"card_35_description" "攻击会使敌人中毒。中毒每次造成目标当前生命值的 %damage_current_hp_pct%%% 伤害。
对 npc_boss 目标时,中毒伤害降为当前生命值的 %boss_damage_current_hp_pct%%% 。" +"card_36_name" "水晶税" +"card_36_description" "每消耗 %crystals_per_step% 个水晶,获得 +%damage_pct_per_step%%% 造成伤害。
上限:%max_bonus_pct%%% 。" +"card_37_name" "水晶袋" +"card_37_description" "拾取时立即获得 %base_crystals% 水晶,并按当前夜晚数每夜额外获得 %crystals_per_night% 水晶。" +"card_38_name" "水晶黎明" +"card_38_description" "每到新一天开始时,你当前的水晶会乘以 %multiplier%x。" +"card_39_name" "水晶兑换器" +"card_39_description" "拾取时仅触发一次:将你所有水晶按每枚水晶 %gold_per_crystal% 金币的汇率兑换为金币。" +"card_40_name" "金币兑换器" +"card_40_description" "拾取时仅触发一次:将你所有金币按每枚水晶需 %gold_per_crystal% 金币的汇率兑换为水晶(不足一枚水晶的金币会保留)。" +"card_41_name" "双刃储备" +"card_41_description" "获得时,将「法力之刃」与「血刃」洗入抽取池。" +"card_41_description_level_2" "2级:立即从两把利刃中选择其一。" +"card_41_description_level_3" "3级:额外 +1「法力之刃」与 +1「血刃」入池(共 4 张)。" +"card_41_pool_pairs" "利刃对数:" +"card_42_name" "求知若渴" +"card_42_description" "提高获得的经验值 %exp_bonus_pct%%% 。" +"card_43_name" "快手" +"card_43_description" "基础攻击间隔降低 %bat_reduction% 。" +"card_43_description_level_2" "等级 2:远程英雄弹道速度 +%projectile_speed_bonus% 。" +"card_43_description_level_3" "等级 3:攻击动画时间降低 %attack_anim_reduction_pct%%% 。" +"card_44_name" "苍蓝疾风" +"card_44_description" "+%bonus_move_speed%%% 移动速度,+%bonus_evasion%%% 闪避。
每点幸运:两者再 +%luck_bonus_pct_per_point%%%(闪避上限 %max_evasion_pct%%%)。" +"card_45_name" "能量饮料" +"card_45_description" "+%max_bonus_pct%%% 移动速度,每分钟降低 %decay_pct_per_minute%%% 。
每个新早晨恢复至 %max_bonus_pct%%% 。" +"card_46_name" "法力回流" +"card_46_description" "将你受到伤害的 %damage_to_mana_pct%%% 转化为魔法恢复。" +"card_47_name" "良好开局" +"card_47_description" "游戏开始时,英雄等级 +1。随后 3 分钟内,获得的经验提高 %exp_bonus_pct%%%。" +"card_47_exp_bonus_pct" "经验加成:" +"card_47_exp_boost_duration_sec" "加成持续时间:" +"card_48_name" "保险单" +"card_48_description" "每夜一次:受到伤害后若生命低于 %trigger_hp_pct%%% ,恢复 %shield_heal_max_hp_pct%%% 最大生命并获得 %incoming_damage_reduction_pct%%% 减伤,持续 %insurance_duration% 秒。" +"card_49_name" "第二意见" +"card_49_description" "获得时:额外进行一次卡牌选择。
每个新早晨:当日首次需花费水晶的重掷免费。" +"card_50_name" "传说甄选" +"card_50_description" "获得时:从三张随机传说卡牌中选择一张(全目录,不含固有传说)。" +"card_51_name" "迈达斯胸甲" +"card_51_description" "受到伤害时,获得相当于该伤害 %gold_from_damage_taken_pct%%% 的金币。 受到的伤害增加 %incoming_damage_base_pct%%%,且每累计受到 100 点伤害再增加 %incoming_damage_per_100_taken%%%。
每个早晨的金币上限:每张复制为 %morning_gold_cap%(黎明时重置)。
贪婪:每 250 经济值 1 层;每层 +1 全属性。" +"card_51_morning_gold_cap" "早晨金币上限:" +"card_52_name" "血契" +"card_52_description" "每有一张此卡,每个夜晚缩短 %night_duration_reduce_sec% 秒。
但敌人的全部属性每张提高 %enemy_stats_bonus_pct%%% 。" +"card_53_name" "灵魂回响" +"card_53_description" "若你已拥有精神力量,再次选到该卡会立刻额外触发一次卡牌选择。
额外选项数:%extra_card_choices%。" +"card_54_name" "诅咒·智力" +"card_54_description" "英雄每级:+%intellect_per_level% 智力,但 -%strength_penalty_per_level% 力量与 -%agility_penalty_per_level% 敏捷。
该效果会按已获得的诅咒数量进行倍增。" +"card_55_name" "霍默的活力" +"card_55_description" "使 npc_homer 的最大生命值提高 %max_health_bonus_pct%%% 。" +"card_56_name" "霍默之刺" +"card_56_description" "当 npc_homer 受到敌方小兵伤害时,将该伤害的 %creep_reflect_pct%%% 反弹给攻击者。" +"card_57_name" "命运之镜" +"card_57_description" "拾取时一次性触发:镜像你当前的卡池,并将其中所有卡牌复制一份。" +"card_58_name" "狂野生长" +"card_58_description" "游戏开始时额外获得 %extra_selections_on_start% 次选卡机会。黎明不再出现常规选卡。
开局每次额外选卡提供 %cards_per_selection% 张卡牌。" +"card_59_name" "经验之礼" +"card_59_description" "拾取时一次性获得 %xp_reward% 经验。
仅通过「愿望卡」获得;不能加入卡组。" +"card_59_xp_reward" "经验:" +"card_60_name" "黄金之礼" +"card_60_description" "拾取时一次性获得 %gold_reward% 金币。
仅通过「愿望卡」获得;不能加入卡组。" +"card_60_gold_reward" "金币:" +"card_61_name" "水晶之礼" +"card_61_description" "拾取时一次性获得 %crystals_reward% 水晶。
仅通过「愿望卡」获得;不能加入卡组。" +"card_61_crystals_reward" "水晶:" +"card_62_name" "许愿卡" +"card_62_description" "拾取时一次性开启 3 选 1:经验之礼、黄金之礼或水晶之礼。" +"card_63_name" "幸存者契约" +"card_63_description" "若你整晚未阵亡,则在黎明额外获得一次 %bonus_card_choices% 选卡。" +"card_63_bonus_card_choices" "额外选卡次数:" +"card_64_name" "狼性直觉" +"card_64_description" "攻击生命值低于 %hp_threshold_pct%%% 的目标时,下一击必定暴击。
使用你当前已有的暴击来源。" +"card_65_name" "血色刻印" +"card_65_description" "若目标生命值为 %target_hp_full_pct%%% ,该次攻击必定暴击并造成 %crit_multiplier_pct%%% 伤害。" +"card_66_name" "命运之刃" +"card_66_description" "施放技能后,你的下一次攻击会额外造成魔法伤害,数值等于你当前生命值的 %bonus_from_health_pct%%% 与当前魔法值的 %bonus_from_mana_pct%%% 之和。" +"card_67_name" "泰坦之心" +"card_67_description" "每点力量会使你的生命值提高 %hp_per_strength%,并使你的输出伤害提高 %outgoing_pct_per_strength%%%.
攻击有 %proc_chance_pct%%% 的概率触发额外伤害(基于最大生命值,%bonus_damage_from_max_hp_pct%%%),冷却 %proc_cooldown_sec% 秒。" +"card_68_name" "三重债契" +"card_68_description" "立刻为英雄叠加 %curse_stacks% 层诅咒,并将本卡再次洗入牌池,随后从 %cursed_offer_slots% 张随机卡牌中选择 1 张。
通常从牌池中的诅咒牌里抽取;若无可用诅咒牌,则不展示额外选择。
若已拥有「诅咒契约」:选项为牌池中任意三张随机卡牌(不限诅咒牌)。" +"card_69_name" "诅咒契约" +"card_69_description" "其余所有卡牌:其目录中的数值降低 %other_cards_effectiveness_pct%%%。
英雄每有一层诅咒:+%outgoing_damage_per_curse_stack_pct%%% 输出伤害。
不削弱本卡自身参数;不使用目录数字的特效不受影响。获得本卡后,之后每次从选卡领取卡牌都会叠加诅咒(此前已领取的不补算)。" +"card_70_name" "狂怒护符" +"card_70_description" "每点当前怒气使受到的伤害降低 %incoming_reduction_per_rage_pct%%% 。
仅对有怒气资源的英雄生效;无怒气则无减伤。" +"card_71_name" "重负诅咒" +"card_71_description" "每层诅咒:最大生命值 +%max_health_pct_per_curse_stack%%% 。
每秒对自身造成相当于最大生命值 %self_damage_max_hp_pct_per_sec%%% 的纯粹伤害。" +"card_72_name" "怒意疾速" +"card_72_description" "冷却时间缩短 %cooldown_reduction_pct%%% 。
仅对有怒气资源的英雄生效;其他英雄不获得加速。" +"card_73_name" "海军上将朗姆酒" +"card_73_description" "%deferred_damage_pct%%% 的承受伤害会被延迟,并在 %deferred_dot_duration_sec% 秒内以相等份额的纯粹伤害结算。
其余伤害仍立即结算;延迟部分仍可能致死。" +"card_74_name" "狙击镜" +"card_74_description" "暴击伤害倍率 +%crit_mult_bonus_pct%(在基础 100%% 的暴击倍率上叠加)。
半径 %aura_radius% 内的敌人失去相当于当前护甲 %enemy_armor_reduction_pct%%% 的护甲。
停留在光环内时会持续重算。" +"card_75_name" "法力冲击" +"card_75_description" "每 %blast_interval_sec% 秒消耗 %mana_cost_pct%%% 最大法力,%blast_delay_sec% 秒后对半径 %blast_radius% 内的敌人造成 %base_damage% + 最大法力 %damage_from_max_mana_pct%%%纯粹伤害
在 %search_radius% 搜索半径内选择能命中最多敌人的爆炸中心。" +"card_76_name" "法术共鸣" +"card_76_description" "施放技能后对半径 %radius% 内最多 %target_count% 名最近敌人各造成相当于当前法力 %mana_damage_pct%%%魔法伤害
按实际造成的伤害为自身恢复生命
物品与开关技能不触发,目标须可见。" +"card_77_name" "法力护盾" +"card_77_description" "最多 %damage_reduction_pct%%% 所受伤害可被吸收,每吸收 1 点伤害消耗 %mana_per_mitigated_damage% 法力
法力不足时按现有法力尽可能减免。" +"card_78_name" "法力脉冲" +"card_78_description" "每次施放技能获得持续 %stack_duration_sec% 秒 的层数:+%max_mana_pct_per_stack%%% 最大法力。
层数可叠加;物品与开关技能不触发。按施放时的最大法力计算。" +"card_79_name" "抉择回响" +"card_79_description" "下一次选卡会重复:选定卡牌后再获得一张相同的
仅触发一次。不会重复:空卡(404)、神话品质、霜之哀伤及其碎片。" + +"card_80_name" "霜之哀伤" +"card_80_description" "先天诅咒之刃。立即向牌库混入3块碎片(剑身、剑柄、剑魂)。在选卡中集齐三块即可重铸完整的霜之哀伤:诅咒不再增加受到的伤害,改为每层+%outgoing_damage_per_curse_stack_pct%%% 输出伤害
每块碎片获得时叠加1层诅咒。碎片无法在商店购买。无法被「命运之镜」与「抉择回响」复制。" + +"card_81_name" "碎片:剑刃" +"card_81_description" "霜之哀伤剑刃。+%outgoing_damage_pct%%% 输出伤害,+%spell_amp_pct%%% 技能增强。获得时叠加1层诅咒
与剑柄、剑魂一起集齐以铸成神剑。" + +"card_82_name" "碎片:剑柄" +"card_82_description" "霜之哀伤剑柄。+%max_health_pct%%% 最大生命,+%max_mana_pct%%% 最大法力(法力加成在获得时固定)。获得时叠加1层诅咒
与剑刃、剑魂一起集齐以铸成神剑。" + +"card_83_name" "碎片:剑魂" +"card_83_description" "霜之哀伤剑魂。+%model_scale_pct%%% 模型体型,+%physical_vampirism_pct%%% 物理与 +%magical_vampirism_pct%%% 技能吸血。获得时叠加1层诅咒
与剑刃、剑柄一起集齐以铸成神剑。" + +"card_84_name" "小型法力狂潮" +"card_84_description" "获得时仅触发一次:技能增强等于获得瞬间最大法力值的 %spell_amp_from_mana_pct%%% 。数值永久锁定。
仅通过「法力狂潮」(3级)获得;无法加入卡组。" +"card_84_spell_amp_from_mana_pct" "来自法力的技能增强:" + +"card_85_name" "钓鱼" +"card_85_description" "获得时开启包含 %bonus_card_choices% 张卡的额外选卡,将本卡重新混入卡组池,并向卡池加入%slag_pool_copies%份炉渣——无效果的空卡,只能从卡池抽到。
额外选卡的选项数量随卡牌等级提升。炉渣不能加入卡组,也无法升级。" +"card_85_bonus_card_choices" "额外选卡选项数:" +"card_85_slag_pool_copies" "混入炉渣:" + +"card_86_name" "炉渣" +"card_86_description" "无效果的空卡。仅通过「钓鱼」混入卡池。选择炉渣不会获得任何卡牌,但会消耗其在卡池中的权重。" + +"card_87_name" "炉渣之王" +"card_87_description" "卡池中每有一张未被选中的「炉渣」:力量、敏捷、智力 +%attr_pct_per_slag%%% 。" + +"card_88_name" "诱饵" +"card_88_description" "每当你获得「炉渣」时,立即获得 %free_rerolls_on_slag% 次免费重随。" + +"card_404_name" "空卡" +"card_404_description" "无效效果" + +//卡牌修饰符提示 +"dota_tooltip_modifier_modifier_card_1" "Bloodway" +"dota_tooltip_modifier_modifier_card_1_Description" "+%vampirism_bonus%% 对吸血鬼
+%damage_multiplier%% 对每单位吸血鬼的所有物理伤害" +"dota_tooltip_modifier_modifier_card_2" "光笔" +"dota_tooltip_modifier_modifier_card_2_Description" "移动速度: +%dMODIFIER_PROPERTY_MOVESPEED_BONUS_CONSTANT%。移速上限: %dMODIFIER_PROPERTY_MOVESPEED_LIMIT%。
基础攻击间隔: %dMODIFIER_PROPERTY_BASE_ATTACK_TIME_CONSTANT%。
3 级时可穿过单位。" +"dota_tooltip_modifier_modifier_card_63" "幸存者契约" +"dota_tooltip_modifier_modifier_card_63_Description" "被动:若你整晚未阵亡,则在黎明额外获得一次 %bonus_card_choices% 选卡。" +"dota_tooltip_modifier_modifier_card_64" "狼性直觉" +"dota_tooltip_modifier_modifier_card_64_Description" "攻击生命值低于 %hp_threshold_pct%%% 的目标时:下一击必定暴击。
使用你当前已有的暴击来源。" +"dota_tooltip_modifier_modifier_card_65" "血色刻印" +"dota_tooltip_modifier_modifier_card_65_Description" "若目标生命值为 %target_hp_full_pct%%% :下一击必定暴击,倍率为 %crit_multiplier_pct%%%." +"dota_tooltip_modifier_modifier_card_66" "命运之刃" +"dota_tooltip_modifier_modifier_card_66_Description" "层数:%dMODIFIER_PROPERTY_TOOLTIP%。
施放技能后,下一次攻击会消耗 1 层并造成额外魔法伤害:当前生命值的 %bonus_from_health_pct%%% + 当前魔法值的 %bonus_from_mana_pct%%%." +"dota_tooltip_modifier_modifier_card_69" "诅咒契约" +"dota_tooltip_modifier_modifier_card_69_Description" "每层诅咒: +%outgoing_damage_per_curse_stack_pct%%% 输出伤害。" +"dota_tooltip_modifier_modifier_card_70" "狂怒护符" +"dota_tooltip_modifier_modifier_card_70_Description" "怒气减伤:每点当前怒气降低 %incoming_reduction_per_rage_pct%%% 承受伤害(仅怒气英雄)。" +"dota_tooltip_modifier_modifier_card_71" "重负诅咒" +"dota_tooltip_modifier_modifier_card_71_Description" "每层诅咒最大生命 +%max_health_pct_per_curse_stack%%% 。
每秒:最大生命值的 %self_damage_max_hp_pct_per_sec%%% 纯粹伤害(自身)。" +"dota_tooltip_modifier_modifier_card_72" "怒意疾速" +"dota_tooltip_modifier_modifier_card_72_Description" "冷却缩短:%cooldown_reduction_pct%%%(仅怒气英雄)。" +"dota_tooltip_modifier_modifier_card_73" "海军上将朗姆酒" +"dota_tooltip_modifier_modifier_card_73_Description" "延迟承受:%deferred_damage_pct%%% 的伤害延后;该部分在 %deferred_dot_duration_sec% 秒内以纯粹伤害分段结算。" +"dota_tooltip_modifier_modifier_card_74" "狙击镜" +"dota_tooltip_modifier_modifier_card_74_Description" "暴击倍率:+%crit_mult_bonus_pct%。
光环半径 %aura_radius%;敌人按当前护甲失去 %enemy_armor_reduction_pct%%%。" +"dota_tooltip_modifier_modifier_card_75" "法力冲击" +"dota_tooltip_modifier_modifier_card_75_Description" "每 %blast_interval_sec% 秒:消耗 %mana_cost_pct%%% 法力,造成 %base_damage% + %damage_from_max_mana_pct%%% 最大法力的纯粹伤害,半径 %blast_radius%。" +"dota_tooltip_modifier_modifier_card_76" "法术共鸣" +"dota_tooltip_modifier_modifier_card_76_Description" "施放技能后:最多 %target_count% 名敌人,半径 %radius%,各造成当前法力 %mana_damage_pct%%% 魔法伤害。治疗量等于造成的伤害。" +"dota_tooltip_modifier_modifier_card_77" "法力护盾" +"dota_tooltip_modifier_modifier_card_77_Description" "吸收:最多 %damage_reduction_pct%%% 伤害,每吸收 1 点消耗 %mana_per_mitigated_damage% 法力。" +"dota_tooltip_modifier_modifier_card_78" "法力脉冲" +"dota_tooltip_modifier_modifier_card_78_Description" "施放技能:层数 +%max_mana_pct_per_stack%%% 最大法力,持续 %stack_duration_sec% 秒。" +"dota_tooltip_modifier_modifier_card_78_mana_stack" "法力脉冲" +"dota_tooltip_modifier_modifier_card_78_mana_stack_Description" "+%max_mana_pct_per_stack%%% 最大法力。" +"dota_tooltip_modifier_modifier_card_79" "抉择回响" +"dota_tooltip_modifier_modifier_card_79_Description" "等待下次选卡:所选卡牌将再获得一次(空卡、神话品质与霜之哀伤除外)。" +"dota_tooltip_modifier_modifier_card_80" "霜之哀伤(未铸成)" +"dota_tooltip_modifier_modifier_card_80_Description" "已收集碎片:%dMODIFIER_PROPERTY_TOOLTIP% / 3。从选卡中取得剑刃、剑柄与剑魂。" +"dota_tooltip_modifier_modifier_card_80_pact_complete" "霜之哀伤" +"dota_tooltip_modifier_modifier_card_80_pact_complete_Description" "神剑铸成。诅咒改为每层 +%outgoing_damage_per_curse_stack_pct%%% 输出伤害(不再增加受到的伤害)。当前层数:%dMODIFIER_PROPERTY_TOOLTIP%。" +"dota_tooltip_modifier_modifier_card_81" "霜之哀伤·剑刃" +"dota_tooltip_modifier_modifier_card_81_Description" "输出伤害: +%outgoing_damage_pct%%%。技能增强: +%spell_amp_pct%%%." +"dota_tooltip_modifier_modifier_card_82" "霜之哀伤·剑柄" +"dota_tooltip_modifier_modifier_card_82_Description" "最大生命: +%max_health_pct%%%。最大法力: +%max_mana_pct%%%(获得时固定加成)。" +"dota_tooltip_modifier_modifier_card_83" "霜之哀伤·剑魂" +"dota_tooltip_modifier_modifier_card_83_Description" "体型: +%model_scale_pct%%%。吸血: +%physical_vampirism_pct%%% 物理,+%magical_vampirism_pct%%% 技能。" +"dota_tooltip_modifier_modifier_card_84" "小型法力狂潮" +"dota_tooltip_modifier_modifier_card_84_Description" "技能增强(获得时锁定): +%dMODIFIER_PROPERTY_TOOLTIP%%% 。" +"dota_tooltip_modifier_modifier_card_3" "精神力量" +"dota_tooltip_modifier_modifier_card_3_Description" "+%damage_pct%% 对所有物理和魔法伤害。" +"dota_tooltip_modifier_modifier_card_4" "精神的狂暴" +"dota_tooltip_modifier_modifier_card_4_Description" "将 %card_bonus% 的精神力量卡混入玩家池。" +"dota_tooltip_modifier_modifier_card_5" "卡片疯狂" +"dota_tooltip_modifier_modifier_card_5_Description" "对在本卡之前获得的每张牌,提供 %card_bonus_pct%% 的力量、敏捷与智力加成。" +"dota_tooltip_modifier_modifier_card_6" "快乐的手" +"dota_tooltip_modifier_modifier_card_6_Description" "额外选卡。 每次选卡至少 %min_card_choice% 张。" +"dota_tooltip_modifier_modifier_card_7" "Curse 日食" +"dota_tooltip_modifier_modifier_card_7_Description" "夜间:
+%daynight_bonus_pct%% 所有属性。
白天:
-%daynight_bonus_pct%% 所有属性。
" +"dota_tooltip_modifier_modifier_card_8" "Curse spray heart" +"dota_tooltip_modifier_modifier_card_8_Description" "每次击杀,英雄都会永远失去 %damage_bonus_health_decress% 的生命值,同时还会永久增加 %damage_bonus_health_decress% 的伤害。仅在拥有者生命值不低于 %stack_health_threshold% 时,击杀才会叠加层数。
卡牌层数:%dMODIFIER_PROPERTY_TOOLTIP%" +"dota_tooltip_modifier_modifier_card_9" "Curse alak midas" +"dota_tooltip_modifier_modifier_card_9_Description" "+1 贪婪层数。 击杀额外金币:每张复制提供 +%kill_gold_bonus_pct%%% 的目标赏金。属性加成见「贪婪」 buff。
死亡时:失去全部金币,并按复制数随机失去物品。" +"dota_tooltip_modifier_modifier_card_10" "灰烬烙印" +"dota_tooltip_modifier_modifier_card_10_Description" "攻击会施加持续 %debuff_duration% 秒的减益。
目标每有 1 层燃烧(modifier_general_fired),承受的伤害提高 %incoming_damage_per_fired_stack_pct%%% ,最多提高 %max_incoming_damage_bonus_pct%%% 。该加成仅对卡牌拥有者造成的伤害生效。" +"dota_tooltip_modifier_modifier_card_11" "雄心壮志" +"dota_tooltip_modifier_modifier_card_11_Description" "按等级提升主属性。
若主属性不是全才:每级主属性 +%primary_attribute_bonus_per_level% 。若主属性是全才:每级全属性 +%universal_all_bonus_per_level% 。" +"dota_tooltip_modifier_modifier_card_12" "夜渊之眼" +"dota_tooltip_modifier_modifier_card_12_Description" "每隔 %scan_interval% 秒,标记 %scan_radius% 范围内最强敌人,持续 %mark_duration% 秒
对被标记目标:造成 +%bonus_damage_pct%%% 额外伤害。击杀被标记目标时,回复最大生命值的 %on_kill_heal_pct%%% 并获得 +%on_kill_mana% 法力。对首领时,额外伤害降低 %boss_penalty_pct%%% 。" +"dota_tooltip_modifier_modifier_card_13" "燃渊之眼" +"dota_tooltip_modifier_modifier_card_13_Description" "每次攻击施加 %fired_stacks_on_hit% 层燃烧
有 %explosion_chance_pct%%% 几率使目标在 %explosion_radius% 半径内爆炸。爆炸伤害:(燃烧层数 × %explosion_damage_per_stack%) + 攻击者伤害。" +"dota_tooltip_modifier_modifier_card_14" "炼狱弹射" +"dota_tooltip_modifier_modifier_card_14_Description" "攻击有 %proc_chance_pct%%% 几率点燃目标,并在 %ricochet_radius% 半径内发射弹射投射物
主目标:基于攻击伤害造成 +%main_target_bonus_damage_pct%%% 魔法伤害,并施加 %fired_stacks% 层燃烧。每次弹射都会触发一次独立攻击,造成 %ricochet_damage_pct%%% 攻击伤害,并施加 %fired_stacks% 层燃烧。" +"dota_tooltip_modifier_modifier_card_15" "赤红镰刃" +"dota_tooltip_modifier_modifier_card_15_Description" "提供固定 %base_crit_chance_pct%%% 的自定义暴击几率。
暴击伤害随幸运值成长:%crit_multiplier_pct%%% + 每 1 点 Luck 提升 %crit_multiplier_bonus_pct_per_luck%%% 。" +"dota_tooltip_modifier_modifier_card_16" "虚空碎片" +"dota_tooltip_modifier_modifier_card_16_Description" "每击杀一个敌人获得 1 层(最多 %max_stacks% 层)。
每层提供 +%spell_amp_per_stack_pct%%% 技能增强。
卡牌层数:%dMODIFIER_PROPERTY_TOOLTIP%" +"dota_tooltip_modifier_modifier_card_17" "诅咒·血手" +"dota_tooltip_modifier_modifier_card_17_Description" "攻击时,若生命值高于 %min_health_pct_to_activate%%% ,英雄会消耗最大生命值的 %health_cost_pct%%% 。
消耗的生命值转化为纯粹伤害;每层诅咒使伤害再乘以 (1 + 层数 × %curse_damage_pct_per_curse_stack%% / 100)。" +"dota_tooltip_modifier_modifier_card_18" "幸运眼镜" +"dota_tooltip_modifier_modifier_card_18_Description" "被动提供 +%base_luck_bonus% 幸运值。
另外每拿取一张卡牌,额外获得 +%luck_per_card_taken% 幸运值。" +"dota_tooltip_modifier_modifier_card_19" "空衣和服" +"dota_tooltip_modifier_modifier_card_19_Description" "攻击有 %ghost_step_chance_pct%%% 概率触发“幽步”,持续 %ghost_step_duration% 秒。
效果期间:+%ghost_step_move_speed_pct%%% 移动速度,+%ghost_step_evasion_pct%%% 闪避,并可穿过单位。" +"dota_tooltip_modifier_modifier_card_20" "黄金战靴" +"dota_tooltip_modifier_modifier_card_20_Description" "每拥有 %gold_per_step% 金币,提供 +%move_speed_pct_per_step%%% 移动速度。
移除移动速度上限。" +"dota_tooltip_modifier_modifier_card_21" "烈日之刃" +"dota_tooltip_modifier_modifier_card_21_Description" "每次攻击 npc_wave 类型单位时,都会造成额外纯粹伤害。
额外伤害:%wave_pure_damage_base% + 持有者攻击力。" +"dota_tooltip_modifier_modifier_card_22" "黎明金币" +"dota_tooltip_modifier_modifier_card_22_Description" "下次清晨:%dMODIFIER_PROPERTY_TOOLTIP% 金币。
每过一夜金额减少。" +"dota_tooltip_modifier_modifier_card_23" "黄金增长" +"dota_tooltip_modifier_modifier_card_23_Description" "每 %gold_tick_interval_sec% 秒获得额外金币,数额为当前金币的 %gold_income_pct_per_minute%%% 。" +"dota_tooltip_modifier_modifier_card_24" "黄金之手" +"dota_tooltip_modifier_modifier_card_24_Description" "每拥有 %gold_per_step% 金币,英雄获得 +%attack_damage_per_step%%% 攻击力和 +%spell_amp_per_step_pct%%% 技能增强。" +"dota_tooltip_modifier_modifier_card_25" "法力核心" +"dota_tooltip_modifier_modifier_card_25_Description" "每有 %mana_per_step% 当前法力,英雄获得 +%spell_amp_per_step_pct%%% 技能增强和 +%mana_regen_per_step% 法力回复。" +"dota_tooltip_modifier_modifier_card_26" "暗影誓约" +"dota_tooltip_modifier_modifier_card_26_Description" "英雄生命值不会低于 1。
当生命值低于 %trigger_health_threshold% 时,英雄进入隐身并每秒回复最大生命值的 %heal_pct_per_second%%% 。效果结束后进入 %cooldown_seconds% 秒冷却。" +"dota_tooltip_modifier_modifier_card_27" "法力之刃" +"dota_tooltip_modifier_modifier_card_27_Description" "英雄当前法力值的 %mana_to_damage_pct%%% 会转化为攻击伤害。" +"dota_tooltip_modifier_modifier_card_28" "血刃" +"dota_tooltip_modifier_modifier_card_28_Description" "英雄当前生命值的 %health_to_damage_pct%%% 会转化为攻击伤害。" +"dota_tooltip_modifier_modifier_card_29" "血月" +"dota_tooltip_modifier_modifier_card_29_Description" "夜晚提供 +%night_vampirism_pct%%% 物理吸血,并使受到的伤害降低 %night_incoming_damage_reduce_pct%%% 。" +"dota_tooltip_modifier_modifier_card_30" "诅咒·成长" +"dota_tooltip_modifier_modifier_card_30_Description" "英雄每级:+%strength_per_level% 力量,但 -%agility_penalty_per_level% 敏捷与 -%intellect_penalty_per_level% 智力。
该效果会按已获得的诅咒数量进行倍增。" +"dota_tooltip_modifier_modifier_card_31" "诅咒·敏捷" +"dota_tooltip_modifier_modifier_card_31_Description" "英雄每级:+%agility_per_level% 敏捷,但 -%strength_penalty_per_level% 力量与 -%intellect_penalty_per_level% 智力。
该效果会按已获得的诅咒数量进行倍增。" +"dota_tooltip_modifier_modifier_card_32" "沙漏" +"dota_tooltip_modifier_modifier_card_32_Description" "提供 +%base_cooldown_reduction_pct%%% 技能冷却缩减。
英雄每次死亡后,该效果降低 %cooldown_loss_per_death_pct%%% 。
获得卡牌后的死亡次数:%dMODIFIER_PROPERTY_TOOLTIP%" +"dota_tooltip_modifier_modifier_card_33" "法力狂潮" +"dota_tooltip_modifier_modifier_card_33_Description" "拾取时仅触发一次:最大法力值提高当前最大法力值的 %mana_increase_pct%%% 。
额外法力(层数):%dMODIFIER_PROPERTY_TOOLTIP%" +"dota_tooltip_modifier_modifier_card_34" "血之收割" +"dota_tooltip_modifier_modifier_card_34_Description" "补刀击杀敌人时,恢复生命值:目标最大生命值的 %heal_from_enemy_max_hp_pct%%% + 你攻击伤害的 %heal_from_attack_damage_pct%%% 。" +"dota_tooltip_modifier_modifier_card_35" "剧毒伤口" +"dota_tooltip_modifier_modifier_card_35_Description" "攻击会使敌人中毒。中毒每次造成目标当前生命值的 %damage_current_hp_pct%%% 伤害。
对 npc_boss 目标时,中毒伤害降为当前生命值的 %boss_damage_current_hp_pct%%% 。" +"dota_tooltip_modifier_modifier_card_36" "水晶税" +"dota_tooltip_modifier_modifier_card_36_Description" "每消耗 %crystals_per_step% 个水晶,获得 +%damage_pct_per_step%%% 造成伤害。
上限:%max_bonus_pct%%% 。
卡牌层数:%dMODIFIER_PROPERTY_TOOLTIP%" +"dota_tooltip_modifier_modifier_card_37" "水晶袋" +"dota_tooltip_modifier_modifier_card_37_Description" "拾取时立即获得 %base_crystals% 水晶,并按当前夜晚数每夜额外获得 %crystals_per_night% 水晶。" +"dota_tooltip_modifier_modifier_card_38" "水晶黎明" +"dota_tooltip_modifier_modifier_card_38_Description" "每到新一天开始时,你当前的水晶会乘以 %multiplier%x。" +"dota_tooltip_modifier_modifier_card_39" "水晶兑换器" +"dota_tooltip_modifier_modifier_card_39_Description" "拾取时仅触发一次:将你所有水晶按每枚水晶 %gold_per_crystal% 金币的汇率兑换为金币。" +"dota_tooltip_modifier_modifier_card_40" "金币兑换器" +"dota_tooltip_modifier_modifier_card_40_Description" "拾取时仅触发一次:将你所有金币按每枚水晶需 %gold_per_crystal% 金币的汇率兑换为水晶(不足一枚水晶的金币会保留)。" +"dota_tooltip_modifier_modifier_card_41" "双刃储备" +"dota_tooltip_modifier_modifier_card_41_Description" "将利刃洗入抽取池。2级:立即选择;3级:各多 1 张入池。" +"dota_tooltip_modifier_modifier_card_42" "求知若渴" +"dota_tooltip_modifier_modifier_card_42_Description" "提高获得的经验值 %exp_bonus_pct%%% 。" +"dota_tooltip_modifier_modifier_card_43" "快手" +"dota_tooltip_modifier_modifier_card_43_Description" "基础攻击间隔: %dMODIFIER_PROPERTY_BASE_ATTACK_TIME_CONSTANT%" +"dota_tooltip_modifier_modifier_card_44" "苍蓝疾风" +"dota_tooltip_modifier_modifier_card_44_Description" "+%bonus_move_speed%%% 移动速度,+%bonus_evasion%%% 闪避。
每点幸运:两者再 +%luck_bonus_pct_per_point%%%(闪避上限 %max_evasion_pct%%%)。" +"dota_tooltip_modifier_modifier_card_45" "能量饮料" +"dota_tooltip_modifier_modifier_card_45_Description" "+%max_bonus_pct%%% 移动速度,每分钟降低 %decay_pct_per_minute%%% 。
每个新早晨恢复至 %max_bonus_pct%%% 。
当前移速加成:%dMODIFIER_PROPERTY_TOOLTIP%%%" +"dota_tooltip_modifier_modifier_card_46" "法力回流" +"dota_tooltip_modifier_modifier_card_46_Description" "将你受到伤害的 %damage_to_mana_pct%%% 转化为魔法恢复。" +"dota_tooltip_modifier_modifier_card_47" "良好开局" +"dota_tooltip_modifier_modifier_card_47_Description" "经验获取加成: +%dMODIFIER_PROPERTY_TOOLTIP%%%。剩余时间见增益条。" +"dota_tooltip_modifier_modifier_card_48" "保险单" +"dota_tooltip_modifier_modifier_card_48_Description" "每夜一次:受到伤害后若生命低于 %trigger_hp_pct%%% ,恢复 %shield_heal_max_hp_pct%%% 最大生命并获得 %incoming_damage_reduction_pct%%% 减伤,持续 %insurance_duration% 秒。" +"dota_tooltip_modifier_modifier_card_49" "第二意见" +"dota_tooltip_modifier_modifier_card_49_Description" "获得时:额外进行一次卡牌选择。
每个新早晨:当日首次需花费水晶的重掷免费。" +"dota_tooltip_modifier_modifier_card_50" "传说甄选" +"dota_tooltip_modifier_modifier_card_50_Description" "获得时:从三张随机传说卡牌中选择一张(全目录,不含固有传说)。" +"dota_tooltip_modifier_modifier_card_10_debuff" "灰烬烙印(减益)" +"dota_tooltip_modifier_modifier_card_10_debuff_Description" "攻击会施加持续 %debuff_duration% 秒的减益。
目标每有 1 层燃烧(modifier_general_fired),承受的伤害提高 %incoming_damage_per_fired_stack_pct%%% ,最多提高 %max_incoming_damage_bonus_pct%%% 。该加成仅对卡牌拥有者造成的伤害生效。" +"dota_tooltip_modifier_modifier_card_12_marked" "夜渊之眼(标记)" +"dota_tooltip_modifier_modifier_card_12_marked_Description" "每隔 %scan_interval% 秒,标记 %scan_radius% 范围内最强敌人,持续 %mark_duration% 秒
对被标记目标:造成 +%bonus_damage_pct%%% 额外伤害。击杀被标记目标时,回复最大生命值的 %on_kill_heal_pct%%% 并获得 +%on_kill_mana% 法力。对首领时,额外伤害降低 %boss_penalty_pct%%% 。" +"dota_tooltip_modifier_modifier_card_19_ghost_step" "空衣和服(幽步)" +"dota_tooltip_modifier_modifier_card_19_ghost_step_Description" "攻击有 %ghost_step_chance_pct%%% 概率触发“幽步”,持续 %ghost_step_duration% 秒。
效果期间:+%ghost_step_move_speed_pct%%% 移动速度,+%ghost_step_evasion_pct%%% 闪避,并可穿过单位。" +"dota_tooltip_modifier_modifier_card_26_invisibility" "暗影誓约(隐身)" +"dota_tooltip_modifier_modifier_card_26_invisibility_Description" "英雄生命值不会低于 1。
当生命值低于 %trigger_health_threshold% 时,英雄进入隐身并每秒回复最大生命值的 %heal_pct_per_second%%% 。效果结束后进入 %cooldown_seconds% 秒冷却。" +"dota_tooltip_modifier_modifier_card_35_poison" "剧毒伤口(中毒)" +"dota_tooltip_modifier_modifier_card_35_poison_Description" "攻击会使敌人中毒。中毒每次造成目标当前生命值的 %damage_current_hp_pct%%% 伤害。
对 npc_boss 目标时,中毒伤害降为当前生命值的 %boss_damage_current_hp_pct%%% 。" +"dota_tooltip_modifier_modifier_card_48_insurance_buff" "保险单(护盾)" +"dota_tooltip_modifier_modifier_card_48_insurance_buff_Description" "每夜一次:受到伤害后若生命低于 %trigger_hp_pct%%% ,恢复 %shield_heal_max_hp_pct%%% 最大生命并获得 %incoming_damage_reduction_pct%%% 减伤,持续 %insurance_duration% 秒。" +"dota_tooltip_modifier_modifier_card_cursed" "诅咒(计数)" +"dota_tooltip_modifier_modifier_card_cursed_Description" "已选择的诅咒卡牌:%dMODIFIER_PROPERTY_TOOLTIP%。每层诅咒:受到的伤害 +5%%。" +"dota_tooltip_modifier_modifier_card_greed" "贪婪" +"dota_tooltip_modifier_modifier_card_greed_Description" "贪婪层数:图标数字为已拥有的贪婪卡牌数量。 全属性加成: %dMODIFIER_PROPERTY_TOOLTIP%(每 250 经济值 +1 力量、敏捷、智力,按层数叠加)。" +"dota_tooltip_modifier_modifier_card_51" "迈达斯胸甲" +"dota_tooltip_modifier_modifier_card_51_Description" "受到的伤害: +%dMODIFIER_PROPERTY_INCOMING_DAMAGE_PERCENTAGE%%%。
受伤获得金币(有早晨上限,黎明重置);每累计 100 点受伤,受到的伤害进一步增加。" +"dota_tooltip_modifier_modifier_card_52" "血契" +"dota_tooltip_modifier_modifier_card_52_Description" "全局卡牌效果:每张使夜晚缩短 30 秒。
副作用:敌人的全部属性每张提高 8%%。" +"dota_tooltip_modifier_modifier_card_53" "灵魂回响" +"dota_tooltip_modifier_modifier_card_53_Description" "若你已拥有精神力量:再次选到该卡会立刻额外触发一次 %extra_card_choices% 选卡。" +"dota_tooltip_modifier_modifier_card_54" "智力诅咒" +"dota_tooltip_modifier_modifier_card_54_Description" "英雄每级:+%intellect_per_level% 智力,但 -%strength_penalty_per_level% 力量与 -%agility_penalty_per_level% 敏捷。
该效果会按已获得的诅咒数量进行倍增。" +"dota_tooltip_modifier_modifier_card_55" "霍默的活力" +"dota_tooltip_modifier_modifier_card_55_Description" "被动:使 npc_homer 的最大生命值提高 %max_health_bonus_pct%%% 。" +"dota_tooltip_modifier_modifier_card_56" "霍默之刺" +"dota_tooltip_modifier_modifier_card_56_Description" "被动:当 npc_homer 受到敌方小兵伤害时,将该伤害的 %creep_reflect_pct%%% 反弹给攻击者。" +"dota_tooltip_modifier_modifier_card_57" "命运之镜" +"dota_tooltip_modifier_modifier_card_57_Description" "拾取时一次性触发:镜像你当前的卡池,并将其中所有卡牌复制一份。" +"dota_tooltip_modifier_modifier_card_58" "狂野生长" +"dota_tooltip_modifier_modifier_card_58_Description" "开局:额外 %extra_selections_on_start% 次选卡,每次 %cards_per_selection% 张。黎明跳过常规选卡。" + +//店铺 + +"store_title" "商店" +"store_tab_items" "物品" +"store_tab_cards" "地图" +"store_tab_arcade" "街机" +"store_tab_upgrades" "效果" +"store_tab_chatwheel" "聊天轮" +"store_tab_marketplace" "交易市场" +"store_tab_promotions" "促销" +"store_promotions_title" "可用促销" +"store_promotions_bundles_title" "礼包与特惠" +"store_promotions_bundles_hint" "卢布购买的特别优惠。每个礼包每个账号限购一次。" +"store_promotions_weekly_title" "本周特惠" +"store_promocode_button" "兑换码" +"store_promocode_title" "兑换码" +"store_promocode_desc" "输入兑换码即可获得商店货币" +"store_promocode_placeholder" "例如: ZOMBIENEW" +"store_promocode_apply" "兑换" +"store_promocode_close" "关闭" +"store_deal_timer_prefix" "刷新倒计时" +"store_deal_timer_loading" "正在加载计时…" +"store_deals_daily_badge" "今日特惠" +"store_deals_daily_empty" "正在加载今日特惠…" +"store_deals_weekly_badge" "本周特惠" +"store_deals_weekly_slot" "本周" +"store_deal_item_owned" "已在收藏中" +"store_deal_used" "促销已使用" +"store_deal_buy" "折扣购买" +"store_bundle_owned" "已购买" +"store_bundle_buy" "购买" +"store_bundle_timer_urgency" "抓紧购买!" +"store_bundle_timer_ends_in" "促销剩余" +"store_bundle_timer_prefix" "促销结束倒计时" +"store_promo_sound_click" "点击试听" +"store_bundle_starter_zaika_500_title" "新手礼包「Zaika」" +"store_bundle_starter_zaika_500_description" "Battle Pass 高级版、「Zaika」聊天轮音效、20,000 尘埃、300 捐赠碎片和 250,000 僵尸碎片。" +"store_bundle_starter_zaika_500_reward_battle_pass_premium" "Battle Pass — 高级版" +"store_bundle_starter_zaika_500_reward_sound_zaika" "「Zaika」聊天轮音效" +"store_bundle_starter_zaika_500_reward_dust" "20,000 尘埃" +"store_bundle_starter_zaika_500_reward_donate" "300 捐赠碎片" +"store_bundle_starter_zaika_500_reward_free" "250,000 僵尸碎片" +"store_bundle_newbie_card_packs_399_title" "新手卡牌礼包" +"store_bundle_newbie_card_packs_399_description" "8 个标准与 2 个高级街机卡牌包 — 在「街机」标签页打开。" +"store_bundle_newbie_card_packs_399_reward_arcade_pack_standard" "8 个标准卡包" +"store_bundle_newbie_card_packs_399_reward_arcade_pack_premium" "2 个高级卡包" + +"store_subscription_monthly_subscription_199_title" "月度订阅" +"store_subscription_monthly_subscription_199_description" "每日首次登录:30 捐赠碎片与 5,000 僵尸碎片。可重复购买叠加时长。" +"store_subscription_monthly_subscription_199_reward_daily_donate" "每日 30 捐赠碎片" +"store_subscription_monthly_subscription_199_reward_daily_free" "每日 5,000 僵尸碎片" +"store_subscription_monthly_subscription_199_reward_duration" "每次购买 +30 天" +"store_subscription_buy" "订阅" +"store_subscription_extend" "续订" +"store_subscription_active" "订阅已激活" +"store_subscription_active_until" "有效期至" +"store_subscription_daily_claimed" "今日奖励已领取" +"store_subscription_timer_prefix" "有效期至" +"store_subscription_daily_reward_toast" "订阅:+{d} 捐赠,+{s} 僵尸碎片" +"store_subscription_daily_welcome_title" "欢迎回来!" +"store_subscription_daily_welcome_kicker" "月度订阅" +"store_subscription_daily_welcome_body" "你获得了每日奖励:" +"store_subscription_daily_reward_line_donate" "+{amount} 捐赠碎片" +"store_subscription_daily_reward_line_free" "+{amount} 僵尸碎片" +"store_subscription_daily_ok" "太好了!" +"store_exchange_title" "货币兑换" +"store_exchange_desc" "按固定比例将捐赠碎片兑换为免费碎片。" +"store_exchange_rate_prefix" "汇率" +"store_exchange_apply" "兑换" +"store_exchange_spend" "消耗" +"store_exchange_get" "获得" +"store_chatwheel_title" "聊天轮" +"store_marketplace_title" "交易市场" +"store_marketplace_hint" "将军械库物品挂牌出售为僵尸碎片,并购买其他玩家的报价。" +"store_marketplace_refresh" "刷新" +"store_marketplace_your_inventory" "可上架物品" +"store_marketplace_active_listings" "活跃挂单" +"store_marketplace_my_listings" "我的挂单" +"store_marketplace_mine_active_slots_hint" "当前挂单(8 个槽位)" +"store_marketplace_mine_buy_slots_hint" "捐赠货币购买额外槽位" +"store_marketplace_slot_buy_donate" "花费 %PRICE% 捐赠货币购买" +"store_marketplace_seller" "卖家" +"store_marketplace_listing_id" "挂单" +"store_marketplace_buy" "购买价格" +"store_marketplace_cancel" "下架" +"store_marketplace_list" "上架" +"store_marketplace_subtab_browse" "交易市场" +"store_marketplace_subtab_mine" "我的挂单" +"store_marketplace_subtab_sales_history" "销售记录" +"store_marketplace_history_col_item" "物品" +"store_marketplace_history_col_buyer" "买家" +"store_marketplace_history_col_price" "价格" +"store_marketplace_history_col_fee" "手续费" +"store_marketplace_history_col_received" "实收" +"store_marketplace_history_col_date" "时间" +"store_marketplace_history_empty" "暂无销售记录。其他玩家购买你的挂单后会显示在这里。" +"store_marketplace_history_buyer_id" "Steam:%s" +"store_marketplace_listing_details" "挂单详情" +"store_marketplace_stats_filter" "词条筛选" +"store_marketplace_slot_filter" "部位筛选" +"store_marketplace_item_stats" "物品属性" +"store_marketplace_listing_price" "挂单价格" +"store_marketplace_set_price" "设置价格" +"store_marketplace_commission_note" "市场手续费:卖家将被扣除 20%。" +"store_marketplace_seller_prefix" "卖家" +"store_marketplace_first_dropper_prefix" "首次掉落者" +"store_marketplace_confirm_wallet_title" "战斗碎片" +"store_marketplace_confirm_balance_was" "当前" +"store_marketplace_confirm_balance_after" "成交后" +"store_marketplace_confirm_spend" "扣除" +"store_marketplace_confirm_insufficient" "战斗碎片不足" +"store_marketplace_take_from_auction" "从市场下架" +"store_marketplace_min_level_1_short" "仅限 1 级及以上" +"store_marketplace_price_min_short" "最低:5000" +"store_marketplace_no_listing_selected" "请先在左侧网格中选择挂单" +"store_marketplace_sort_label" "排序:" +"store_marketplace_sort_price_asc" "价格 ↑" +"store_marketplace_sort_price_desc" "价格 ↓" +"store_marketplace_sort_time_desc" "最新优先" +"store_marketplace_empty_browse" "当前筛选下没有挂单。请刷新或更改词条筛选。" +"store_marketplace_empty_inventory" "暂无可上架的军械库物品。请打开军械库或等待同步。" +"store_marketplace_slots" "挂单槽位" +"store_marketplace_buy_slot" "购买槽位" +"store_marketplace_slots_max" "槽位已满" +"store_marketplace_col_item" "物品" +"store_marketplace_col_score" "评分" +"store_marketplace_col_price" "价格" +"store_marketplace_col_time" "上架时间" +"store_marketplace_col_action" "操作" +"store_marketplace_error_fetch" "加载挂单失败。" +"store_marketplace_error_invalid_data" "请求数据无效。" +"store_marketplace_error_locked_item" "已固定或已收藏的物品无法上架。" +"store_marketplace_error_min_price" "最低上架价格为 5000。" +"store_marketplace_error_slots_full" "没有可用的挂单槽位。" +"store_marketplace_error_create" "创建挂单失败。" +"store_marketplace_error_buy" "购买挂单失败。" +"store_marketplace_error_buy_in_progress" "请稍候:上一笔市场购买仍在处理中。" +"store_marketplace_error_create_in_progress" "请稍候:上一笔上架请求仍在处理中。" +"store_marketplace_error_cancel" "下架失败。" +"store_marketplace_error_buy_slot" "购买挂单槽位失败。" +"store_marketplace_success_create" "挂单创建成功。" +"store_marketplace_success_buy" "购买成功。" +"store_marketplace_success_cancel" "挂单已下架。" +"store_marketplace_success_buy_slot" "挂单槽位购买成功。" +"store_items_title" "物品" +"store_cards_title" "卡片" +"store_arcade_title" "街机" +"store_arcade_subtitle" "内含3张随机卡牌,点击翻开查看。" +"store_item_arcade_pack_standard_name" "标准卡包" +"store_item_arcade_pack_standard_description" "从图鉴中随机获得3张卡牌。重复卡牌将按该卡升级所需粉尘退还。" +"store_item_arcade_pack_premium_name" "高级卡包" +"store_item_arcade_pack_premium_description" "从图鉴中随机获得3张卡牌。重复卡牌将按该卡升级所需粉尘退还。" +"store_arcade_free_pack_badge_prefix" "免费" +"store_arcade_reveal_title" "翻开卡牌" +"store_arcade_reveal_hint" "点击每张卡牌查看奖励" +"store_arcade_reveal_progress" "已翻开" +"store_arcade_reveal_done" "全部翻开!" +"store_arcade_reveal_close" "关闭" +"store_arcade_duplicate_dust" "重复 — 粉尘" +"store_arcade_duplicate_max" "重复(已达份数上限)" +"store_arcade_flip_error" "无法翻开卡牌" +"store_arcade_purchase_failed" "无法购买卡包" +"store_arcade_pity_mythic" "神话" +"store_arcade_pity_epic" "史诗" +"store_upgrades_title" "效果" + +"filter_purchased" "已购买" +"filter_not_purchased" "未购买" +"filter_price_range" "价格范围" +"filter_price_min" "最小值:" +"filter_price_max" "最大:" +"search_and_filters" "搜索和过滤器" +"filter_all" "一切" +"filter_donate_currency" "Donat 货币" +"filter_free_currency" "免费货币" +"filter_cheap" "便宜" +"filter_expensive" "亲爱的" +"filter_common" "常规" +"filter_rare" "稀有" +"filter_epic" "史诗" +"filter_legendary" "传奇" +"filter_mythic" "神话" +"purcase_confirm" "购买确认" +"确认" "你确定要买这个商品吗?" +"接受_购买" "购买" +"取消_购买" "取消" +"select_currency" "选择要支付的货币:" +"store_buy" "购买" +"store_purchased" "已购买" +"store_card_copies_label" "副本:" +"store_card_limit_reached" "上限" +"store_cancel" "取消" +"store_close" "关闭" +"store_rarity_common" "普通" +"store_rarity_rare" "稀有" +"store_rarity_epic" "EPIC" +"store_rarity_legendary" "传奇" +"store_rarity_mythic" "神话" +"store_unique" "唯一" +"store_equip" "连衣裙" +"store_unequip" "删除" +"store_slot_effect" "插槽:效果" +"store_slot_wings" "插槽:WINGS" +"store_slot_model" "插槽:模型" + + +//卡组编辑器 +"deck_builder_open_button" "卡组" +"deck_builder_title" "卡组编辑器" +"deck_builder_close" "×" +"deck_builder_dust_label" "尘" +"deck_builder_decks_section" "卡组" +"deck_builder_new_deck" "+ 新建卡组" +"deck_builder_default_name" "新卡组" +"deck_builder_save" "保存" +"deck_builder_use" "使用" +"deck_builder_delete" "删除" +"deck_builder_available_cards" "可用卡牌" +"deck_builder_card_hint" "Shift + 左键 — 查看卡牌详情" +"deck_builder_deck_hint" "别忘了激活卡组!双击可将卡组设为对战使用。" +"deck_builder_card_purchased" "已拥有" +"deck_builder_card_not_purchased" "未拥有" +"deck_builder_card_default" "默认" +"deck_builder_search_placeholder" "搜索" +"deck_builder_filter_all" "全部" +"deck_builder_filter_common" "普通" +"deck_builder_filter_rare" "稀有" +"deck_builder_filter_epic" "史诗" +"deck_builder_filter_legendary" "传说" +"deck_builder_filter_mythic" "神话" +"deck_builder_import_export_open" "导入 / 导出" +"deck_builder_no_decks" "还没有卡组,新建一个吧!" +"deck_builder_auto_deck" "新卡组" +"deck_builder_new_deck_name" "新卡组" +"deck_builder_new_deck_name_with_number" "新卡组" +"deck_builder_deck_name" "卡组" +"deck_builder_selected" "当前" +"deck_builder_close_fullscreen" "关闭" +"deck_builder_import_export_title" "卡组导入 / 导出" +"deck_builder_export" "导出" +"deck_builder_import" "导入" +"deck_builder_card_level" "等级:{level}/{max}" +"deck_builder_card_level_plain" "等级:{level}" +"deck_builder_upgrade_for_cost" "消耗 {cost} 升级" +"deck_builder_upgrade_max_level" "已满级" +"deck_builder_upgrade_unavailable" "无法升级" +"deck_builder_card_shard_only" "不可入组 · 仅战斗中" +"deck_builder_deck_slots_tooltip" "占用卡组 {count} 个{slots_word}" +"deck_builder_slot_word_one" "槽位" +"deck_builder_slot_word_few" "槽位" +"deck_builder_slot_word_many" "槽位" +"deck_builder_deck_incomplete_warning" "卡组未满:{used}/{required} 槽。开局时将用默认卡组自动补全。" +"deck_builder_deck_activate_incomplete" "卡组占用 {used}/{required} 槽。缺少的卡牌将从默认卡组自动补全。" +"deck_builder_status_loading" "卡组仍在加载" +"deck_builder_status_select_deck" "请先选择或创建卡组" +"deck_builder_status_no_deck_slot" "没有空余卡组栏位" +"deck_builder_status_create_failed" "无法创建新卡组" +"deck_builder_card_limit_reached" "已达副本上限" +"deck_builder_card_copies_label" "副本:" +"deck_builder_import_export_status_exported" "卡组字符串已导出" +"deck_builder_import_export_status_imported" "卡组导入成功" +"deck_builder_import_export_copy_code" "复制代码" +"deck_builder_import_export_load_code" "从代码加载" +"deck_builder_import_export_apply" "应用到卡组" +"deck_builder_import_export_new_deck" "新建卡组" +"deck_builder_import_export_status_loaded" "已从代码加载卡组" +"deck_builder_import_export_status_applied" "卡组已更新" +"deck_builder_import_error_non_deckable" "此卡不能加入卡组(仅可在库存中升级)。" +"deck_builder_import_error_empty" "请先输入卡组字符串" +"deck_builder_import_error_format" "格式错误,请使用 id,id,id" +"deck_builder_import_error_deck_size" "卡组槽位过多(最多 {max})" +"deck_builder_import_error_unknown_card" "字符串中包含未知卡牌" +"deck_builder_import_error_max_copies" "超出卡牌副本上限" +"deck_builder_import_error_not_owned" "拥有的副本数量不足" +"card_remaining_cards_title" "剩余卡片" +"card_remaining_cards" "剩余卡数:" +"inherent_card_tooltip" "固有卡牌在游戏开始时自动应用,不会占用牌组中的槽位。" +"inherent_card" "固有" + +//卡片选择 +"card_reroll_button" "更新成本:" +"card_reroll_free_second_opinion" "免费" +"card_hide_button" "隐藏卡片" +"card_show_button" "显示地图" + +//烹饪面板 +"cooking_panel_search" "搜索" +"cooking_panel_recipe_details" "食谱详情" +"cooking_panel_ready_dishes" "现成的饭菜" +"cooking_panel_no_recipes_found" "未找到食谱" +"cooking_panel_cooking_time" "准备时间:{time}s" +"cooking_panel_select_recipe" "选择食谱" +"cooking_panel_recipe_not_found" "未找到食谱" +"cooking_panel_ingredients" "成分:" +"烹饪_面板_烹饪" "烹饪" +"cooking_panel_not_enough_ingredients" "配料不够" +"campfire_already_occupied" "篝火已经忙着对付另一个玩家了" +"cooking_panel_no_ready_dishes" "没有现成的饭菜" +"烹饪_面板_费用" "数量:{费用}" +"cooking_panel_dish_count" "数量:x{count}" +"烹饪_面板_烹饪_持续时间" "烹饪时间:{持续时间}秒" +"cooking_intro_message" "欢迎来到烹饪系统!" + +//Chatvilla 的声音 +"chat_wheel_donate_sound_i_am_sad" "我们失去的人。" +"chat_wheel_donate_sound_jump" "跳傻瓜!" +"chat_wheel_donate_sound_empty" "空槽" +"聊天_轮_捐赠_声音_代理_gabena" "Gaben代理" +"chat_wheel_donate_sound_byd_dobr_idi" "请走吧……" +"聊天_轮子_捐赠_声音_猫_shnapy" "Schni Schna..." +"chat_wheel_donate_sound_dobro_pozhalovat_v_club" "欢迎来到俱乐部" +"chat_wheel_donate_sound_eto_prosto_okhueno" "简直绝了" +"聊天_轮_捐赠_声音_获取_输出" "输出" +"chat_wheel_donate_sound_ia_vas_unichtozhu" "我会消灭你们" +"chat_wheel_donate_sound_kak_rulit" "如何驾驶?" +"chat_wheel_donate_sound_kto_myaukaet" "谁在喵喵叫?" +"聊天_轮_捐赠_声音_Muhehehehe" "Muhehehe" +"chat_wheel_donate_sound_ne_tvoy_uroven_dorogoy" "亲爱的,这不是你的水平。" +"chat_wheel_donate_sound_ne_ponimaiu_karina_strimersha_slozhno_slozhno" "完全听不懂(主播:「好难好难」)" +"chat_wheel_donate_sound_nikhuia_ne_ponial_no_ochen_interesno" "啥也没懂,但很有意思" +"chat_wheel_donate_sound_oi_tak_nravitsa" "哎呀,好喜欢" +"聊天_轮_捐赠_声音_olyhi_bezdari_ogyzki" "OOOOOgni" +"chat_wheel_donate_sound_ou_mai" "天哪……" +"chat_wheel_donate_sound_po_syobam" "我们走吧。" +"chat_wheel_donate_sound_poshel_process" "该过程已经开始。" +"chat_wheel_donate_sound_posledniy_ponedelnik_zivesh" "上周一你还活着。" +"chat_wheel_donate_sound_rot_etogo_kazino" "这家赌场的入口" +"chat_wheel_donate_sound_s_kakoy_stati" "这到底是怎么回事" +"聊天_轮_捐赠_声音_sir_no_sir" "先生,不,先生" +"聊天_轮_捐赠_声音_sir_是_sir" "先生,是的,先生" +"chat_wheel_donate_sound_stop_mne_ne_priyatno" "停下来,我不高兴。" +"chat_wheel_donate_sound_stoyat_ya_yzhe_eto_sosal" "停下来,我已经吸了它" +"chat_wheel_donate_sound_ura_pobeda" "为胜利欢呼" +"聊天_轮_捐赠_声音_uvorot_ot_spelov" "咒语躲避" +"chat_wheel_donate_sound_v_komp_igri_igral" "电脑玩的游戏。" +"chat_wheel_donate_sound_vot_eto_nikhuia_sebe" "我靠,这也太离谱了" +"chat_wheel_donate_sound_zachem_ya_suda_prishel" "我为什么来这里?" +"chat_wheel_donate_sound_zaika" "兔子" +"chat_wheel_donate_sound_kitty_flex" "猫咪秀肌肉" +"chat_wheel_donate_sound_eblo_razraba" "开发者脸梗" +"chat_wheel_donate_sound_kuda" "去哪儿" +"chat_wheel_donate_sound_bruh" "不是吧" +"chat_wheel_donate_sound_shizofreniya" "精神分裂" +"chat_wheel_donate_sound_vot_eto_povorot" "这反转" +"chat_wheel_donate_sound_fbi_open_up" "FBI 开门!" +"chat_wheel_donate_sound_nepravilno_poprobuy_esche_raz" "不对,再试一次" +"chat_wheel_donate_sound_dobro_pozhalovat_na_server_shizofreniya" "欢迎来到精神分裂服务器" +"chat_wheel_donate_sound_nya" "喵" +"chat_wheel_donate_sound_na_nas_napali" "我们遭到袭击" +"chat_wheel_donate_sound_murlok" "鱼人" +"chat_wheel_donate_sound_kak_zhit_to_a" "这日子咋过啊" + +//聊天别墅界面 +"chat_wheel_tab_settings" "设置" +"chat_wheel_tab_shop" "购买" +"chat_wheel_shop_title" "可供购买的声音" +"chat_wheel_no_sounds" "聊天轮没有任何可用的声音" +"chat_wheel_sound_sad_description" "聊天轮的悲伤声音" +"dota_error_cooldown_chat_wheel" "充电时发出声音" +"chat_wheel_settings_hint" "选择轮子上的插槽,然后单击声音" + +// Match end screen +"match_end_title" "比赛结果" +"match_end_title_win" "胜利!" +"match_end_title_loss" "失败" +"match_end_bp_caption" "你的战斗通行证" +"match_end_bp_level_short" "等级" +"match_end_bp_xp_short" "经验" +"match_end_col_hero" "英雄" +"match_end_col_player" "玩家" +"match_end_col_kills" "补刀" +"match_end_col_deaths" "死亡" +"match_end_col_dmg_out" "造成伤害" +"match_end_col_dmg_in" "承受伤害" +"match_end_col_incoming_events" "承受(事件累计)" +"match_end_col_currency" "战斗碎片" +"match_end_anim_caption" "奖励:通行证经验与战斗碎片" +"match_end_anim_difficulty" "难度" +"match_end_anim_creeps" "小兵" +"match_end_anim_quests" "任务" +"match_end_anim_deaths" "死亡" +"match_end_anim_total" "总计" +"match_end_close" "关闭" + + +} +} \ No newline at end of file diff --git a/resource/flash3/custom_ui.txt b/resource/flash3/custom_ui.txt new file mode 100644 index 0000000..d2fde66 --- /dev/null +++ b/resource/flash3/custom_ui.txt @@ -0,0 +1,79 @@ +"CustomUI" +{ + // swf files should be published into the same folder as this file + + // Add a numbered block for each swf file to load when your addon starts + + + //"1" + //{ + // "File" "Example" + // "Depth" "50" + //} +} + +// ============================================= +// Depths of base Dota UI elements +// ============================================= +// hud_chat: 8, + +// error_msg: 10, + +// voicechat: 11, +// shop: 12, +// tutorial: 13, +// herodisplay: 14, +// actionpanel: 15, +// inventory: 16, +// channelbar: 17, + +// gameend: 19, +// chat_wheel: 20, +// survey: 21, +// quests: 22, +// questlog: 23, + +// ti_onstage_side: 30, + +// last_hit_challenge: 35, +// waitingforplayers: 36, +// highlight_reel: 37, +// stats_dropdown: 38, +// halloween: 39, +// killcam: 40, // and inspect +// scoreboard: 41, +// quickstats: 42, +// shared_units: 43, +// shared_content: 44, + +// holdout: 50, + +// spectator_items: 145, +// spectator_graph: 146, +// spectator_harvest: 147, +// spectator_player: 148, +// spectator_fantasy: 149, + +// heroselection: 250, +// spectate_heroselection: 251, +// shared_heroselectorandloadout : 252, + +// broadcaster: 364, + +// spectate: 365, +// coach: 366, + +// combat_log: 367, + +// guide_panel: 368, + +// loadgame: 380, + +// report_dialogue : 381, +// popups : 382, +// matchmaking_ready : 383, + +// ti_onstage_pods: 500, + +// overlay: 1000 +// ============================================= \ No newline at end of file diff --git a/resource/flash3/images/items/blackshop/agility_cape.png b/resource/flash3/images/items/blackshop/agility_cape.png new file mode 100644 index 0000000..eaf6ddf Binary files /dev/null and b/resource/flash3/images/items/blackshop/agility_cape.png differ diff --git a/resource/flash3/images/items/blackshop/astral_anchor.png b/resource/flash3/images/items/blackshop/astral_anchor.png new file mode 100644 index 0000000..8cd9572 Binary files /dev/null and b/resource/flash3/images/items/blackshop/astral_anchor.png differ diff --git a/resource/flash3/images/items/blackshop/blue_tallow.png b/resource/flash3/images/items/blackshop/blue_tallow.png new file mode 100644 index 0000000..0a0b5cf Binary files /dev/null and b/resource/flash3/images/items/blackshop/blue_tallow.png differ diff --git a/resource/flash3/images/items/blackshop/boo_stuff.png b/resource/flash3/images/items/blackshop/boo_stuff.png new file mode 100644 index 0000000..6bb01b9 Binary files /dev/null and b/resource/flash3/images/items/blackshop/boo_stuff.png differ diff --git a/resource/flash3/images/items/blackshop/book_agi.png b/resource/flash3/images/items/blackshop/book_agi.png new file mode 100644 index 0000000..eeda9dd Binary files /dev/null and b/resource/flash3/images/items/blackshop/book_agi.png differ diff --git a/resource/flash3/images/items/blackshop/book_int.png b/resource/flash3/images/items/blackshop/book_int.png new file mode 100644 index 0000000..b4e6fbe Binary files /dev/null and b/resource/flash3/images/items/blackshop/book_int.png differ diff --git a/resource/flash3/images/items/blackshop/book_str.png b/resource/flash3/images/items/blackshop/book_str.png new file mode 100644 index 0000000..ad27ff1 Binary files /dev/null and b/resource/flash3/images/items/blackshop/book_str.png differ diff --git a/resource/flash3/images/items/blackshop/critical_havoc.png b/resource/flash3/images/items/blackshop/critical_havoc.png new file mode 100644 index 0000000..2dc424a Binary files /dev/null and b/resource/flash3/images/items/blackshop/critical_havoc.png differ diff --git a/resource/flash3/images/items/blackshop/crown.png b/resource/flash3/images/items/blackshop/crown.png new file mode 100644 index 0000000..b483c08 Binary files /dev/null and b/resource/flash3/images/items/blackshop/crown.png differ diff --git a/resource/flash3/images/items/blackshop/damage_dagger.png b/resource/flash3/images/items/blackshop/damage_dagger.png new file mode 100644 index 0000000..940be10 Binary files /dev/null and b/resource/flash3/images/items/blackshop/damage_dagger.png differ diff --git a/resource/flash3/images/items/blackshop/dawn_chorus.png b/resource/flash3/images/items/blackshop/dawn_chorus.png new file mode 100644 index 0000000..50b32c2 Binary files /dev/null and b/resource/flash3/images/items/blackshop/dawn_chorus.png differ diff --git a/resource/flash3/images/items/blackshop/egg_of_death.png b/resource/flash3/images/items/blackshop/egg_of_death.png new file mode 100644 index 0000000..0ab1e04 Binary files /dev/null and b/resource/flash3/images/items/blackshop/egg_of_death.png differ diff --git a/resource/flash3/images/items/blackshop/fated_die.png b/resource/flash3/images/items/blackshop/fated_die.png new file mode 100644 index 0000000..d0e86d4 Binary files /dev/null and b/resource/flash3/images/items/blackshop/fated_die.png differ diff --git a/resource/flash3/images/items/blackshop/fire_summoner.png b/resource/flash3/images/items/blackshop/fire_summoner.png new file mode 100644 index 0000000..ef92f8a Binary files /dev/null and b/resource/flash3/images/items/blackshop/fire_summoner.png differ diff --git a/resource/flash3/images/items/blackshop/forceboots.png b/resource/flash3/images/items/blackshop/forceboots.png new file mode 100644 index 0000000..5d8cb7e Binary files /dev/null and b/resource/flash3/images/items/blackshop/forceboots.png differ diff --git a/resource/flash3/images/items/blackshop/glass_pact.png b/resource/flash3/images/items/blackshop/glass_pact.png new file mode 100644 index 0000000..8aa5762 Binary files /dev/null and b/resource/flash3/images/items/blackshop/glass_pact.png differ diff --git a/resource/flash3/images/items/blackshop/granite_boots.png b/resource/flash3/images/items/blackshop/granite_boots.png new file mode 100644 index 0000000..a89c0c7 Binary files /dev/null and b/resource/flash3/images/items/blackshop/granite_boots.png differ diff --git a/resource/flash3/images/items/blackshop/granite_stone.png b/resource/flash3/images/items/blackshop/granite_stone.png new file mode 100644 index 0000000..bf965d3 Binary files /dev/null and b/resource/flash3/images/items/blackshop/granite_stone.png differ diff --git a/resource/flash3/images/items/blackshop/holy_mercy.png b/resource/flash3/images/items/blackshop/holy_mercy.png new file mode 100644 index 0000000..ac00eb5 Binary files /dev/null and b/resource/flash3/images/items/blackshop/holy_mercy.png differ diff --git a/resource/flash3/images/items/blackshop/iron_plate.png b/resource/flash3/images/items/blackshop/iron_plate.png new file mode 100644 index 0000000..ab8310a Binary files /dev/null and b/resource/flash3/images/items/blackshop/iron_plate.png differ diff --git a/resource/flash3/images/items/blackshop/ironwood_tree.png b/resource/flash3/images/items/blackshop/ironwood_tree.png new file mode 100644 index 0000000..0282ea8 Binary files /dev/null and b/resource/flash3/images/items/blackshop/ironwood_tree.png differ diff --git a/resource/flash3/images/items/blackshop/magic_mushroom.png b/resource/flash3/images/items/blackshop/magic_mushroom.png new file mode 100644 index 0000000..38c8661 Binary files /dev/null and b/resource/flash3/images/items/blackshop/magic_mushroom.png differ diff --git a/resource/flash3/images/items/blackshop/manaflare.png b/resource/flash3/images/items/blackshop/manaflare.png new file mode 100644 index 0000000..b4f8a62 Binary files /dev/null and b/resource/flash3/images/items/blackshop/manaflare.png differ diff --git a/resource/flash3/images/items/blackshop/martyrs_brand.png b/resource/flash3/images/items/blackshop/martyrs_brand.png new file mode 100644 index 0000000..089fa91 Binary files /dev/null and b/resource/flash3/images/items/blackshop/martyrs_brand.png differ diff --git a/resource/flash3/images/items/blackshop/primordial_shard.png b/resource/flash3/images/items/blackshop/primordial_shard.png new file mode 100644 index 0000000..c0e7183 Binary files /dev/null and b/resource/flash3/images/items/blackshop/primordial_shard.png differ diff --git a/resource/flash3/images/items/blackshop/reset_to_zero.png b/resource/flash3/images/items/blackshop/reset_to_zero.png new file mode 100644 index 0000000..9eaaf70 Binary files /dev/null and b/resource/flash3/images/items/blackshop/reset_to_zero.png differ diff --git a/resource/flash3/images/items/blackshop/restock.png b/resource/flash3/images/items/blackshop/restock.png new file mode 100644 index 0000000..0027de9 Binary files /dev/null and b/resource/flash3/images/items/blackshop/restock.png differ diff --git a/resource/flash3/images/items/blackshop/sanctuary_veil.png b/resource/flash3/images/items/blackshop/sanctuary_veil.png new file mode 100644 index 0000000..3a31cea Binary files /dev/null and b/resource/flash3/images/items/blackshop/sanctuary_veil.png differ diff --git a/resource/flash3/images/items/blackshop/silver_eye.png b/resource/flash3/images/items/blackshop/silver_eye.png new file mode 100644 index 0000000..c0cc478 Binary files /dev/null and b/resource/flash3/images/items/blackshop/silver_eye.png differ diff --git a/resource/flash3/images/items/blackshop/soldier.png b/resource/flash3/images/items/blackshop/soldier.png new file mode 100644 index 0000000..0ecd08a Binary files /dev/null and b/resource/flash3/images/items/blackshop/soldier.png differ diff --git a/resource/flash3/images/items/blackshop/spell_mask.png b/resource/flash3/images/items/blackshop/spell_mask.png new file mode 100644 index 0000000..47ad4b3 Binary files /dev/null and b/resource/flash3/images/items/blackshop/spell_mask.png differ diff --git a/resource/flash3/images/items/blackshop/stone_armor.png b/resource/flash3/images/items/blackshop/stone_armor.png new file mode 100644 index 0000000..ed6c892 Binary files /dev/null and b/resource/flash3/images/items/blackshop/stone_armor.png differ diff --git a/resource/flash3/images/items/blackshop/the_hand_of_gluttony.png b/resource/flash3/images/items/blackshop/the_hand_of_gluttony.png new file mode 100644 index 0000000..9bdd84c Binary files /dev/null and b/resource/flash3/images/items/blackshop/the_hand_of_gluttony.png differ diff --git a/resource/flash3/images/items/blackshop/trinity_seal.png b/resource/flash3/images/items/blackshop/trinity_seal.png new file mode 100644 index 0000000..ef2192f Binary files /dev/null and b/resource/flash3/images/items/blackshop/trinity_seal.png differ diff --git a/resource/flash3/images/items/blackshop/twilight_mirror.png b/resource/flash3/images/items/blackshop/twilight_mirror.png new file mode 100644 index 0000000..7630216 Binary files /dev/null and b/resource/flash3/images/items/blackshop/twilight_mirror.png differ diff --git a/resource/flash3/images/items/blackshop/vigor_salve.png b/resource/flash3/images/items/blackshop/vigor_salve.png new file mode 100644 index 0000000..5c72d7c Binary files /dev/null and b/resource/flash3/images/items/blackshop/vigor_salve.png differ diff --git a/resource/flash3/images/items/blackshop/widow_chain.png b/resource/flash3/images/items/blackshop/widow_chain.png new file mode 100644 index 0000000..f441822 Binary files /dev/null and b/resource/flash3/images/items/blackshop/widow_chain.png differ diff --git a/resource/flash3/images/items/default_items/armlet_of_eternal_hunger/armlet2.png b/resource/flash3/images/items/default_items/armlet_of_eternal_hunger/armlet2.png new file mode 100644 index 0000000..09d9c24 Binary files /dev/null and b/resource/flash3/images/items/default_items/armlet_of_eternal_hunger/armlet2.png differ diff --git a/resource/flash3/images/items/default_items/armlet_of_eternal_hunger/armlet2_off.png b/resource/flash3/images/items/default_items/armlet_of_eternal_hunger/armlet2_off.png new file mode 100644 index 0000000..8574f93 Binary files /dev/null and b/resource/flash3/images/items/default_items/armlet_of_eternal_hunger/armlet2_off.png differ diff --git a/resource/flash3/images/items/default_items/bearstbow_1.png b/resource/flash3/images/items/default_items/bearstbow_1.png new file mode 100644 index 0000000..700cef0 Binary files /dev/null and b/resource/flash3/images/items/default_items/bearstbow_1.png differ diff --git a/resource/flash3/images/items/default_items/bearstbow_2.png b/resource/flash3/images/items/default_items/bearstbow_2.png new file mode 100644 index 0000000..0601bf9 Binary files /dev/null and b/resource/flash3/images/items/default_items/bearstbow_2.png differ diff --git a/resource/flash3/images/items/default_items/blademail_2.png b/resource/flash3/images/items/default_items/blademail_2.png new file mode 100644 index 0000000..8545032 Binary files /dev/null and b/resource/flash3/images/items/default_items/blademail_2.png differ diff --git a/resource/flash3/images/items/default_items/blazing_radiance.png b/resource/flash3/images/items/default_items/blazing_radiance.png new file mode 100644 index 0000000..6200f07 Binary files /dev/null and b/resource/flash3/images/items/default_items/blazing_radiance.png differ diff --git a/resource/flash3/images/items/default_items/bloodstone2.png b/resource/flash3/images/items/default_items/bloodstone2.png new file mode 100644 index 0000000..77b67e4 Binary files /dev/null and b/resource/flash3/images/items/default_items/bloodstone2.png differ diff --git a/resource/flash3/images/items/default_items/core_info.png b/resource/flash3/images/items/default_items/core_info.png new file mode 100644 index 0000000..ad2aa7c Binary files /dev/null and b/resource/flash3/images/items/default_items/core_info.png differ diff --git a/resource/flash3/images/items/default_items/demon_slayer.png b/resource/flash3/images/items/default_items/demon_slayer.png new file mode 100644 index 0000000..fb6ee3c Binary files /dev/null and b/resource/flash3/images/items/default_items/demon_slayer.png differ diff --git a/resource/flash3/images/items/default_items/dickpier.png b/resource/flash3/images/items/default_items/dickpier.png new file mode 100644 index 0000000..2667471 Binary files /dev/null and b/resource/flash3/images/items/default_items/dickpier.png differ diff --git a/resource/flash3/images/items/default_items/fire_cape.png b/resource/flash3/images/items/default_items/fire_cape.png new file mode 100644 index 0000000..fe83751 Binary files /dev/null and b/resource/flash3/images/items/default_items/fire_cape.png differ diff --git a/resource/flash3/images/items/default_items/golden_bag.png b/resource/flash3/images/items/default_items/golden_bag.png new file mode 100644 index 0000000..97a54e3 Binary files /dev/null and b/resource/flash3/images/items/default_items/golden_bag.png differ diff --git a/resource/flash3/images/items/default_items/graded_shiva.png b/resource/flash3/images/items/default_items/graded_shiva.png new file mode 100644 index 0000000..d3dd402 Binary files /dev/null and b/resource/flash3/images/items/default_items/graded_shiva.png differ diff --git a/resource/flash3/images/items/default_items/ice_spine.png b/resource/flash3/images/items/default_items/ice_spine.png new file mode 100644 index 0000000..9eaf856 Binary files /dev/null and b/resource/flash3/images/items/default_items/ice_spine.png differ diff --git a/resource/flash3/images/items/default_items/item_mega_treads_0.png b/resource/flash3/images/items/default_items/item_mega_treads_0.png new file mode 100644 index 0000000..4fdb171 Binary files /dev/null and b/resource/flash3/images/items/default_items/item_mega_treads_0.png differ diff --git a/resource/flash3/images/items/default_items/item_mega_treads_1.png b/resource/flash3/images/items/default_items/item_mega_treads_1.png new file mode 100644 index 0000000..50a8704 Binary files /dev/null and b/resource/flash3/images/items/default_items/item_mega_treads_1.png differ diff --git a/resource/flash3/images/items/default_items/item_mega_treads_2.png b/resource/flash3/images/items/default_items/item_mega_treads_2.png new file mode 100644 index 0000000..c23e44c Binary files /dev/null and b/resource/flash3/images/items/default_items/item_mega_treads_2.png differ diff --git a/resource/flash3/images/items/default_items/king_fly.png b/resource/flash3/images/items/default_items/king_fly.png new file mode 100644 index 0000000..d91a844 Binary files /dev/null and b/resource/flash3/images/items/default_items/king_fly.png differ diff --git a/resource/flash3/images/items/default_items/kunkka_sword.png b/resource/flash3/images/items/default_items/kunkka_sword.png new file mode 100644 index 0000000..fcba018 Binary files /dev/null and b/resource/flash3/images/items/default_items/kunkka_sword.png differ diff --git a/resource/flash3/images/items/default_items/lia_magic_bow.png b/resource/flash3/images/items/default_items/lia_magic_bow.png new file mode 100644 index 0000000..7b710ce Binary files /dev/null and b/resource/flash3/images/items/default_items/lia_magic_bow.png differ diff --git a/resource/flash3/images/items/default_items/magicpier.png b/resource/flash3/images/items/default_items/magicpier.png new file mode 100644 index 0000000..d6093d7 Binary files /dev/null and b/resource/flash3/images/items/default_items/magicpier.png differ diff --git a/resource/flash3/images/items/default_items/magicrapier.png b/resource/flash3/images/items/default_items/magicrapier.png new file mode 100644 index 0000000..4290ace Binary files /dev/null and b/resource/flash3/images/items/default_items/magicrapier.png differ diff --git a/resource/flash3/images/items/default_items/medkit.png b/resource/flash3/images/items/default_items/medkit.png new file mode 100644 index 0000000..0632a7d Binary files /dev/null and b/resource/flash3/images/items/default_items/medkit.png differ diff --git a/resource/flash3/images/items/default_items/mega_fury.png b/resource/flash3/images/items/default_items/mega_fury.png new file mode 100644 index 0000000..4ede895 Binary files /dev/null and b/resource/flash3/images/items/default_items/mega_fury.png differ diff --git a/resource/flash3/images/items/default_items/mini_bfury.png b/resource/flash3/images/items/default_items/mini_bfury.png new file mode 100644 index 0000000..e0ee750 Binary files /dev/null and b/resource/flash3/images/items/default_items/mini_bfury.png differ diff --git a/resource/flash3/images/items/default_items/oldmen_amulet.png b/resource/flash3/images/items/default_items/oldmen_amulet.png new file mode 100644 index 0000000..0156e9f Binary files /dev/null and b/resource/flash3/images/items/default_items/oldmen_amulet.png differ diff --git a/resource/flash3/images/items/default_items/orb_of_fire.png b/resource/flash3/images/items/default_items/orb_of_fire.png new file mode 100644 index 0000000..0f02fae Binary files /dev/null and b/resource/flash3/images/items/default_items/orb_of_fire.png differ diff --git a/resource/flash3/images/items/default_items/soul_devourer_staff.png b/resource/flash3/images/items/default_items/soul_devourer_staff.png new file mode 100644 index 0000000..cbfd13e Binary files /dev/null and b/resource/flash3/images/items/default_items/soul_devourer_staff.png differ diff --git a/resource/flash3/images/items/default_items/storm.png b/resource/flash3/images/items/default_items/storm.png new file mode 100644 index 0000000..eed2bbb Binary files /dev/null and b/resource/flash3/images/items/default_items/storm.png differ diff --git a/resource/flash3/images/items/default_items/ultimate_crown.png b/resource/flash3/images/items/default_items/ultimate_crown.png new file mode 100644 index 0000000..57e55fb Binary files /dev/null and b/resource/flash3/images/items/default_items/ultimate_crown.png differ diff --git a/resource/flash3/images/items/default_items/vampire_claw/vampire_claw_1.png b/resource/flash3/images/items/default_items/vampire_claw/vampire_claw_1.png new file mode 100644 index 0000000..96a83da Binary files /dev/null and b/resource/flash3/images/items/default_items/vampire_claw/vampire_claw_1.png differ diff --git a/resource/flash3/images/items/default_items/vampire_claw/vampire_claw_2.png b/resource/flash3/images/items/default_items/vampire_claw/vampire_claw_2.png new file mode 100644 index 0000000..bfc43e2 Binary files /dev/null and b/resource/flash3/images/items/default_items/vampire_claw/vampire_claw_2.png differ diff --git a/resource/flash3/images/items/default_items/vampire_claw/vampire_claw_3.png b/resource/flash3/images/items/default_items/vampire_claw/vampire_claw_3.png new file mode 100644 index 0000000..391c800 Binary files /dev/null and b/resource/flash3/images/items/default_items/vampire_claw/vampire_claw_3.png differ diff --git a/resource/flash3/images/items/default_items/vampire_claw/vampire_claw_4.png b/resource/flash3/images/items/default_items/vampire_claw/vampire_claw_4.png new file mode 100644 index 0000000..214d185 Binary files /dev/null and b/resource/flash3/images/items/default_items/vampire_claw/vampire_claw_4.png differ diff --git a/resource/flash3/images/items/default_items/vortex_axe_second.png b/resource/flash3/images/items/default_items/vortex_axe_second.png new file mode 100644 index 0000000..c51d060 Binary files /dev/null and b/resource/flash3/images/items/default_items/vortex_axe_second.png differ diff --git a/resource/flash3/images/items/default_items/warrior_shield.png b/resource/flash3/images/items/default_items/warrior_shield.png new file mode 100644 index 0000000..f3f9be6 Binary files /dev/null and b/resource/flash3/images/items/default_items/warrior_shield.png differ diff --git a/resource/flash3/images/items/default_items/wooden_katana.png b/resource/flash3/images/items/default_items/wooden_katana.png new file mode 100644 index 0000000..37c03af Binary files /dev/null and b/resource/flash3/images/items/default_items/wooden_katana.png differ diff --git a/resource/flash3/images/items/default_items/zombie_slayer.png b/resource/flash3/images/items/default_items/zombie_slayer.png new file mode 100644 index 0000000..fe2fde7 Binary files /dev/null and b/resource/flash3/images/items/default_items/zombie_slayer.png differ diff --git a/resource/flash3/images/items/quest_items/ent_heart.png b/resource/flash3/images/items/quest_items/ent_heart.png new file mode 100644 index 0000000..304d573 Binary files /dev/null and b/resource/flash3/images/items/quest_items/ent_heart.png differ diff --git a/resource/flash3/images/items/quest_items/fishing_rod.png b/resource/flash3/images/items/quest_items/fishing_rod.png new file mode 100644 index 0000000..52d56b3 Binary files /dev/null and b/resource/flash3/images/items/quest_items/fishing_rod.png differ diff --git a/resource/flash3/images/items/quest_items/frog_paw.png b/resource/flash3/images/items/quest_items/frog_paw.png new file mode 100644 index 0000000..c91ba9f Binary files /dev/null and b/resource/flash3/images/items/quest_items/frog_paw.png differ diff --git a/resource/flash3/images/items/quest_items/lycan_horn.png b/resource/flash3/images/items/quest_items/lycan_horn.png new file mode 100644 index 0000000..b0f3341 Binary files /dev/null and b/resource/flash3/images/items/quest_items/lycan_horn.png differ diff --git a/resource/flash3/images/items/quest_items/pet.png b/resource/flash3/images/items/quest_items/pet.png new file mode 100644 index 0000000..47bfb69 Binary files /dev/null and b/resource/flash3/images/items/quest_items/pet.png differ diff --git a/resource/flash3/images/items/quest_items/poison.png b/resource/flash3/images/items/quest_items/poison.png new file mode 100644 index 0000000..f5dce23 Binary files /dev/null and b/resource/flash3/images/items/quest_items/poison.png differ diff --git a/resource/flash3/images/items/quest_items/pollen_keeper_1.png b/resource/flash3/images/items/quest_items/pollen_keeper_1.png new file mode 100644 index 0000000..5a53f42 Binary files /dev/null and b/resource/flash3/images/items/quest_items/pollen_keeper_1.png differ diff --git a/resource/flash3/images/items/quest_items/pollen_keeper_2.png b/resource/flash3/images/items/quest_items/pollen_keeper_2.png new file mode 100644 index 0000000..63fed2d Binary files /dev/null and b/resource/flash3/images/items/quest_items/pollen_keeper_2.png differ diff --git a/resource/flash3/images/items/quest_items/pollen_keeper_3.png b/resource/flash3/images/items/quest_items/pollen_keeper_3.png new file mode 100644 index 0000000..402af90 Binary files /dev/null and b/resource/flash3/images/items/quest_items/pollen_keeper_3.png differ diff --git a/resource/flash3/images/items/quest_items/pollen_keeper_4.png b/resource/flash3/images/items/quest_items/pollen_keeper_4.png new file mode 100644 index 0000000..84e4074 Binary files /dev/null and b/resource/flash3/images/items/quest_items/pollen_keeper_4.png differ diff --git a/resource/flash3/images/items/quest_items/rom.png b/resource/flash3/images/items/quest_items/rom.png new file mode 100644 index 0000000..c2f2925 Binary files /dev/null and b/resource/flash3/images/items/quest_items/rom.png differ diff --git a/resource/flash3/images/items/quest_items/spider_legs.png b/resource/flash3/images/items/quest_items/spider_legs.png new file mode 100644 index 0000000..7950041 Binary files /dev/null and b/resource/flash3/images/items/quest_items/spider_legs.png differ diff --git a/resource/flash3/images/items/quest_items/wolf_claw.png b/resource/flash3/images/items/quest_items/wolf_claw.png new file mode 100644 index 0000000..fefb7ed Binary files /dev/null and b/resource/flash3/images/items/quest_items/wolf_claw.png differ diff --git a/resource/flash3/images/items/quest_items/wolf_hat.png b/resource/flash3/images/items/quest_items/wolf_hat.png new file mode 100644 index 0000000..398c51d Binary files /dev/null and b/resource/flash3/images/items/quest_items/wolf_hat.png differ diff --git a/resource/flash3/images/items/utils/banana.png b/resource/flash3/images/items/utils/banana.png new file mode 100644 index 0000000..04b088e Binary files /dev/null and b/resource/flash3/images/items/utils/banana.png differ diff --git a/resource/flash3/images/items/utils/bread.png b/resource/flash3/images/items/utils/bread.png new file mode 100644 index 0000000..bd92700 Binary files /dev/null and b/resource/flash3/images/items/utils/bread.png differ diff --git a/resource/flash3/images/items/utils/cake.png b/resource/flash3/images/items/utils/cake.png new file mode 100644 index 0000000..f978dfb Binary files /dev/null and b/resource/flash3/images/items/utils/cake.png differ diff --git a/resource/flash3/images/items/utils/candy.png b/resource/flash3/images/items/utils/candy.png new file mode 100644 index 0000000..aa00316 Binary files /dev/null and b/resource/flash3/images/items/utils/candy.png differ diff --git a/resource/flash3/images/items/utils/cheese.png b/resource/flash3/images/items/utils/cheese.png new file mode 100644 index 0000000..0ad61f0 Binary files /dev/null and b/resource/flash3/images/items/utils/cheese.png differ diff --git a/resource/flash3/images/items/utils/cocktail.png b/resource/flash3/images/items/utils/cocktail.png new file mode 100644 index 0000000..f91618b Binary files /dev/null and b/resource/flash3/images/items/utils/cocktail.png differ diff --git a/resource/flash3/images/items/utils/coffee.png b/resource/flash3/images/items/utils/coffee.png new file mode 100644 index 0000000..6815206 Binary files /dev/null and b/resource/flash3/images/items/utils/coffee.png differ diff --git a/resource/flash3/images/items/utils/coffee_bean.png b/resource/flash3/images/items/utils/coffee_bean.png new file mode 100644 index 0000000..c85c2d8 Binary files /dev/null and b/resource/flash3/images/items/utils/coffee_bean.png differ diff --git a/resource/flash3/images/items/utils/egg.png b/resource/flash3/images/items/utils/egg.png new file mode 100644 index 0000000..7e1f8ec Binary files /dev/null and b/resource/flash3/images/items/utils/egg.png differ diff --git a/resource/flash3/images/items/utils/energetic.png b/resource/flash3/images/items/utils/energetic.png new file mode 100644 index 0000000..cb180f5 Binary files /dev/null and b/resource/flash3/images/items/utils/energetic.png differ diff --git a/resource/flash3/images/items/utils/fish.png b/resource/flash3/images/items/utils/fish.png new file mode 100644 index 0000000..9937cf8 Binary files /dev/null and b/resource/flash3/images/items/utils/fish.png differ diff --git a/resource/flash3/images/items/utils/grilled_meat.png b/resource/flash3/images/items/utils/grilled_meat.png new file mode 100644 index 0000000..292f68f Binary files /dev/null and b/resource/flash3/images/items/utils/grilled_meat.png differ diff --git a/resource/flash3/images/items/utils/ham.png b/resource/flash3/images/items/utils/ham.png new file mode 100644 index 0000000..34e5622 Binary files /dev/null and b/resource/flash3/images/items/utils/ham.png differ diff --git a/resource/flash3/images/items/utils/mayonnaise.png b/resource/flash3/images/items/utils/mayonnaise.png new file mode 100644 index 0000000..7df00b0 Binary files /dev/null and b/resource/flash3/images/items/utils/mayonnaise.png differ diff --git a/resource/flash3/images/items/utils/meat.png b/resource/flash3/images/items/utils/meat.png new file mode 100644 index 0000000..343fd92 Binary files /dev/null and b/resource/flash3/images/items/utils/meat.png differ diff --git a/resource/flash3/images/items/utils/milk.png b/resource/flash3/images/items/utils/milk.png new file mode 100644 index 0000000..7e3e525 Binary files /dev/null and b/resource/flash3/images/items/utils/milk.png differ diff --git a/resource/flash3/images/items/utils/mushroom.png b/resource/flash3/images/items/utils/mushroom.png new file mode 100644 index 0000000..38c8661 Binary files /dev/null and b/resource/flash3/images/items/utils/mushroom.png differ diff --git a/resource/flash3/images/items/utils/pizza.png b/resource/flash3/images/items/utils/pizza.png new file mode 100644 index 0000000..bd4db6f Binary files /dev/null and b/resource/flash3/images/items/utils/pizza.png differ diff --git a/resource/flash3/images/items/utils/rofl_for_kaban_pumba.png b/resource/flash3/images/items/utils/rofl_for_kaban_pumba.png new file mode 100644 index 0000000..79c3f70 Binary files /dev/null and b/resource/flash3/images/items/utils/rofl_for_kaban_pumba.png differ diff --git a/resource/flash3/images/items/utils/sandwich.png b/resource/flash3/images/items/utils/sandwich.png new file mode 100644 index 0000000..613d273 Binary files /dev/null and b/resource/flash3/images/items/utils/sandwich.png differ diff --git a/resource/flash3/images/items/utils/testo.png b/resource/flash3/images/items/utils/testo.png new file mode 100644 index 0000000..5bee223 Binary files /dev/null and b/resource/flash3/images/items/utils/testo.png differ diff --git a/resource/flash3/images/items/utils/testo_pizza.png b/resource/flash3/images/items/utils/testo_pizza.png new file mode 100644 index 0000000..b969a9c Binary files /dev/null and b/resource/flash3/images/items/utils/testo_pizza.png differ diff --git a/resource/flash3/images/spellicons/blackshop/damage_dagger.png b/resource/flash3/images/spellicons/blackshop/damage_dagger.png new file mode 100644 index 0000000..940be10 Binary files /dev/null and b/resource/flash3/images/spellicons/blackshop/damage_dagger.png differ diff --git a/resource/flash3/images/spellicons/blackshop/magic_mushroom.png b/resource/flash3/images/spellicons/blackshop/magic_mushroom.png new file mode 100644 index 0000000..38c8661 Binary files /dev/null and b/resource/flash3/images/spellicons/blackshop/magic_mushroom.png differ diff --git a/resource/flash3/images/spellicons/blackshop/restock.png b/resource/flash3/images/spellicons/blackshop/restock.png new file mode 100644 index 0000000..0027de9 Binary files /dev/null and b/resource/flash3/images/spellicons/blackshop/restock.png differ diff --git a/resource/flash3/images/spellicons/cards/card_1.png b/resource/flash3/images/spellicons/cards/card_1.png new file mode 100644 index 0000000..9923951 Binary files /dev/null and b/resource/flash3/images/spellicons/cards/card_1.png differ diff --git a/resource/flash3/images/spellicons/cards/card_10.png b/resource/flash3/images/spellicons/cards/card_10.png new file mode 100644 index 0000000..bf5fa70 Binary files /dev/null and b/resource/flash3/images/spellicons/cards/card_10.png differ diff --git a/resource/flash3/images/spellicons/cards/card_11.png b/resource/flash3/images/spellicons/cards/card_11.png new file mode 100644 index 0000000..34c0bfe Binary files /dev/null and b/resource/flash3/images/spellicons/cards/card_11.png differ diff --git a/resource/flash3/images/spellicons/cards/card_12.png b/resource/flash3/images/spellicons/cards/card_12.png new file mode 100644 index 0000000..eb4aacb Binary files /dev/null and b/resource/flash3/images/spellicons/cards/card_12.png differ diff --git a/resource/flash3/images/spellicons/cards/card_13.png b/resource/flash3/images/spellicons/cards/card_13.png new file mode 100644 index 0000000..ada43af Binary files /dev/null and b/resource/flash3/images/spellicons/cards/card_13.png differ diff --git a/resource/flash3/images/spellicons/cards/card_14.png b/resource/flash3/images/spellicons/cards/card_14.png new file mode 100644 index 0000000..44ef688 Binary files /dev/null and b/resource/flash3/images/spellicons/cards/card_14.png differ diff --git a/resource/flash3/images/spellicons/cards/card_15.png b/resource/flash3/images/spellicons/cards/card_15.png new file mode 100644 index 0000000..be94d95 Binary files /dev/null and b/resource/flash3/images/spellicons/cards/card_15.png differ diff --git a/resource/flash3/images/spellicons/cards/card_16.png b/resource/flash3/images/spellicons/cards/card_16.png new file mode 100644 index 0000000..0516102 Binary files /dev/null and b/resource/flash3/images/spellicons/cards/card_16.png differ diff --git a/resource/flash3/images/spellicons/cards/card_17.png b/resource/flash3/images/spellicons/cards/card_17.png new file mode 100644 index 0000000..8c4a471 Binary files /dev/null and b/resource/flash3/images/spellicons/cards/card_17.png differ diff --git a/resource/flash3/images/spellicons/cards/card_18.png b/resource/flash3/images/spellicons/cards/card_18.png new file mode 100644 index 0000000..efdeeb5 Binary files /dev/null and b/resource/flash3/images/spellicons/cards/card_18.png differ diff --git a/resource/flash3/images/spellicons/cards/card_19.png b/resource/flash3/images/spellicons/cards/card_19.png new file mode 100644 index 0000000..4b5d007 Binary files /dev/null and b/resource/flash3/images/spellicons/cards/card_19.png differ diff --git a/resource/flash3/images/spellicons/cards/card_2.png b/resource/flash3/images/spellicons/cards/card_2.png new file mode 100644 index 0000000..9073fc5 Binary files /dev/null and b/resource/flash3/images/spellicons/cards/card_2.png differ diff --git a/resource/flash3/images/spellicons/cards/card_20.png b/resource/flash3/images/spellicons/cards/card_20.png new file mode 100644 index 0000000..225cb55 Binary files /dev/null and b/resource/flash3/images/spellicons/cards/card_20.png differ diff --git a/resource/flash3/images/spellicons/cards/card_21.png b/resource/flash3/images/spellicons/cards/card_21.png new file mode 100644 index 0000000..bb019ce Binary files /dev/null and b/resource/flash3/images/spellicons/cards/card_21.png differ diff --git a/resource/flash3/images/spellicons/cards/card_22.png b/resource/flash3/images/spellicons/cards/card_22.png new file mode 100644 index 0000000..ce75de3 Binary files /dev/null and b/resource/flash3/images/spellicons/cards/card_22.png differ diff --git a/resource/flash3/images/spellicons/cards/card_23.png b/resource/flash3/images/spellicons/cards/card_23.png new file mode 100644 index 0000000..ce71fa1 Binary files /dev/null and b/resource/flash3/images/spellicons/cards/card_23.png differ diff --git a/resource/flash3/images/spellicons/cards/card_24.png b/resource/flash3/images/spellicons/cards/card_24.png new file mode 100644 index 0000000..0c99131 Binary files /dev/null and b/resource/flash3/images/spellicons/cards/card_24.png differ diff --git a/resource/flash3/images/spellicons/cards/card_25.png b/resource/flash3/images/spellicons/cards/card_25.png new file mode 100644 index 0000000..89bb7b8 Binary files /dev/null and b/resource/flash3/images/spellicons/cards/card_25.png differ diff --git a/resource/flash3/images/spellicons/cards/card_26.png b/resource/flash3/images/spellicons/cards/card_26.png new file mode 100644 index 0000000..fdc406c Binary files /dev/null and b/resource/flash3/images/spellicons/cards/card_26.png differ diff --git a/resource/flash3/images/spellicons/cards/card_27.png b/resource/flash3/images/spellicons/cards/card_27.png new file mode 100644 index 0000000..17b062c Binary files /dev/null and b/resource/flash3/images/spellicons/cards/card_27.png differ diff --git a/resource/flash3/images/spellicons/cards/card_28.png b/resource/flash3/images/spellicons/cards/card_28.png new file mode 100644 index 0000000..2379001 Binary files /dev/null and b/resource/flash3/images/spellicons/cards/card_28.png differ diff --git a/resource/flash3/images/spellicons/cards/card_29.png b/resource/flash3/images/spellicons/cards/card_29.png new file mode 100644 index 0000000..79fdaf6 Binary files /dev/null and b/resource/flash3/images/spellicons/cards/card_29.png differ diff --git a/resource/flash3/images/spellicons/cards/card_3.png b/resource/flash3/images/spellicons/cards/card_3.png new file mode 100644 index 0000000..a7f2875 Binary files /dev/null and b/resource/flash3/images/spellicons/cards/card_3.png differ diff --git a/resource/flash3/images/spellicons/cards/card_30.png b/resource/flash3/images/spellicons/cards/card_30.png new file mode 100644 index 0000000..f077f7d Binary files /dev/null and b/resource/flash3/images/spellicons/cards/card_30.png differ diff --git a/resource/flash3/images/spellicons/cards/card_31.png b/resource/flash3/images/spellicons/cards/card_31.png new file mode 100644 index 0000000..72e6795 Binary files /dev/null and b/resource/flash3/images/spellicons/cards/card_31.png differ diff --git a/resource/flash3/images/spellicons/cards/card_32.png b/resource/flash3/images/spellicons/cards/card_32.png new file mode 100644 index 0000000..3b605fb Binary files /dev/null and b/resource/flash3/images/spellicons/cards/card_32.png differ diff --git a/resource/flash3/images/spellicons/cards/card_33.png b/resource/flash3/images/spellicons/cards/card_33.png new file mode 100644 index 0000000..27fbb19 Binary files /dev/null and b/resource/flash3/images/spellicons/cards/card_33.png differ diff --git a/resource/flash3/images/spellicons/cards/card_34.png b/resource/flash3/images/spellicons/cards/card_34.png new file mode 100644 index 0000000..9b311e6 Binary files /dev/null and b/resource/flash3/images/spellicons/cards/card_34.png differ diff --git a/resource/flash3/images/spellicons/cards/card_35.png b/resource/flash3/images/spellicons/cards/card_35.png new file mode 100644 index 0000000..92a6e2e Binary files /dev/null and b/resource/flash3/images/spellicons/cards/card_35.png differ diff --git a/resource/flash3/images/spellicons/cards/card_36.png b/resource/flash3/images/spellicons/cards/card_36.png new file mode 100644 index 0000000..7313827 Binary files /dev/null and b/resource/flash3/images/spellicons/cards/card_36.png differ diff --git a/resource/flash3/images/spellicons/cards/card_37.png b/resource/flash3/images/spellicons/cards/card_37.png new file mode 100644 index 0000000..69213f6 Binary files /dev/null and b/resource/flash3/images/spellicons/cards/card_37.png differ diff --git a/resource/flash3/images/spellicons/cards/card_38.png b/resource/flash3/images/spellicons/cards/card_38.png new file mode 100644 index 0000000..69d8769 Binary files /dev/null and b/resource/flash3/images/spellicons/cards/card_38.png differ diff --git a/resource/flash3/images/spellicons/cards/card_39.png b/resource/flash3/images/spellicons/cards/card_39.png new file mode 100644 index 0000000..9a5d3f7 Binary files /dev/null and b/resource/flash3/images/spellicons/cards/card_39.png differ diff --git a/resource/flash3/images/spellicons/cards/card_4.png b/resource/flash3/images/spellicons/cards/card_4.png new file mode 100644 index 0000000..c2a0e81 Binary files /dev/null and b/resource/flash3/images/spellicons/cards/card_4.png differ diff --git a/resource/flash3/images/spellicons/cards/card_40.png b/resource/flash3/images/spellicons/cards/card_40.png new file mode 100644 index 0000000..456315a Binary files /dev/null and b/resource/flash3/images/spellicons/cards/card_40.png differ diff --git a/resource/flash3/images/spellicons/cards/card_404.png b/resource/flash3/images/spellicons/cards/card_404.png new file mode 100644 index 0000000..a2807e0 Binary files /dev/null and b/resource/flash3/images/spellicons/cards/card_404.png differ diff --git a/resource/flash3/images/spellicons/cards/card_41.png b/resource/flash3/images/spellicons/cards/card_41.png new file mode 100644 index 0000000..7fd62a5 Binary files /dev/null and b/resource/flash3/images/spellicons/cards/card_41.png differ diff --git a/resource/flash3/images/spellicons/cards/card_42.png b/resource/flash3/images/spellicons/cards/card_42.png new file mode 100644 index 0000000..3904d84 Binary files /dev/null and b/resource/flash3/images/spellicons/cards/card_42.png differ diff --git a/resource/flash3/images/spellicons/cards/card_43.png b/resource/flash3/images/spellicons/cards/card_43.png new file mode 100644 index 0000000..893bcac Binary files /dev/null and b/resource/flash3/images/spellicons/cards/card_43.png differ diff --git a/resource/flash3/images/spellicons/cards/card_44.png b/resource/flash3/images/spellicons/cards/card_44.png new file mode 100644 index 0000000..6de1856 Binary files /dev/null and b/resource/flash3/images/spellicons/cards/card_44.png differ diff --git a/resource/flash3/images/spellicons/cards/card_45.png b/resource/flash3/images/spellicons/cards/card_45.png new file mode 100644 index 0000000..ca96ce0 Binary files /dev/null and b/resource/flash3/images/spellicons/cards/card_45.png differ diff --git a/resource/flash3/images/spellicons/cards/card_46.png b/resource/flash3/images/spellicons/cards/card_46.png new file mode 100644 index 0000000..bd3ea86 Binary files /dev/null and b/resource/flash3/images/spellicons/cards/card_46.png differ diff --git a/resource/flash3/images/spellicons/cards/card_47.png b/resource/flash3/images/spellicons/cards/card_47.png new file mode 100644 index 0000000..7024bca Binary files /dev/null and b/resource/flash3/images/spellicons/cards/card_47.png differ diff --git a/resource/flash3/images/spellicons/cards/card_48.png b/resource/flash3/images/spellicons/cards/card_48.png new file mode 100644 index 0000000..50f3354 Binary files /dev/null and b/resource/flash3/images/spellicons/cards/card_48.png differ diff --git a/resource/flash3/images/spellicons/cards/card_49.png b/resource/flash3/images/spellicons/cards/card_49.png new file mode 100644 index 0000000..9e836f9 Binary files /dev/null and b/resource/flash3/images/spellicons/cards/card_49.png differ diff --git a/resource/flash3/images/spellicons/cards/card_5.png b/resource/flash3/images/spellicons/cards/card_5.png new file mode 100644 index 0000000..abfd1b7 Binary files /dev/null and b/resource/flash3/images/spellicons/cards/card_5.png differ diff --git a/resource/flash3/images/spellicons/cards/card_50.png b/resource/flash3/images/spellicons/cards/card_50.png new file mode 100644 index 0000000..11c6478 Binary files /dev/null and b/resource/flash3/images/spellicons/cards/card_50.png differ diff --git a/resource/flash3/images/spellicons/cards/card_51.png b/resource/flash3/images/spellicons/cards/card_51.png new file mode 100644 index 0000000..96fb89a Binary files /dev/null and b/resource/flash3/images/spellicons/cards/card_51.png differ diff --git a/resource/flash3/images/spellicons/cards/card_52.png b/resource/flash3/images/spellicons/cards/card_52.png new file mode 100644 index 0000000..b344f52 Binary files /dev/null and b/resource/flash3/images/spellicons/cards/card_52.png differ diff --git a/resource/flash3/images/spellicons/cards/card_53.png b/resource/flash3/images/spellicons/cards/card_53.png new file mode 100644 index 0000000..12773d2 Binary files /dev/null and b/resource/flash3/images/spellicons/cards/card_53.png differ diff --git a/resource/flash3/images/spellicons/cards/card_54.png b/resource/flash3/images/spellicons/cards/card_54.png new file mode 100644 index 0000000..a9652a5 Binary files /dev/null and b/resource/flash3/images/spellicons/cards/card_54.png differ diff --git a/resource/flash3/images/spellicons/cards/card_55.png b/resource/flash3/images/spellicons/cards/card_55.png new file mode 100644 index 0000000..9559091 Binary files /dev/null and b/resource/flash3/images/spellicons/cards/card_55.png differ diff --git a/resource/flash3/images/spellicons/cards/card_56.png b/resource/flash3/images/spellicons/cards/card_56.png new file mode 100644 index 0000000..f842a19 Binary files /dev/null and b/resource/flash3/images/spellicons/cards/card_56.png differ diff --git a/resource/flash3/images/spellicons/cards/card_57.png b/resource/flash3/images/spellicons/cards/card_57.png new file mode 100644 index 0000000..80034d0 Binary files /dev/null and b/resource/flash3/images/spellicons/cards/card_57.png differ diff --git a/resource/flash3/images/spellicons/cards/card_58.png b/resource/flash3/images/spellicons/cards/card_58.png new file mode 100644 index 0000000..3faaa94 Binary files /dev/null and b/resource/flash3/images/spellicons/cards/card_58.png differ diff --git a/resource/flash3/images/spellicons/cards/card_59.png b/resource/flash3/images/spellicons/cards/card_59.png new file mode 100644 index 0000000..3904d84 Binary files /dev/null and b/resource/flash3/images/spellicons/cards/card_59.png differ diff --git a/resource/flash3/images/spellicons/cards/card_6.png b/resource/flash3/images/spellicons/cards/card_6.png new file mode 100644 index 0000000..7c0f8e5 Binary files /dev/null and b/resource/flash3/images/spellicons/cards/card_6.png differ diff --git a/resource/flash3/images/spellicons/cards/card_60.png b/resource/flash3/images/spellicons/cards/card_60.png new file mode 100644 index 0000000..ce75de3 Binary files /dev/null and b/resource/flash3/images/spellicons/cards/card_60.png differ diff --git a/resource/flash3/images/spellicons/cards/card_61.png b/resource/flash3/images/spellicons/cards/card_61.png new file mode 100644 index 0000000..69213f6 Binary files /dev/null and b/resource/flash3/images/spellicons/cards/card_61.png differ diff --git a/resource/flash3/images/spellicons/cards/card_62.png b/resource/flash3/images/spellicons/cards/card_62.png new file mode 100644 index 0000000..f8d6a25 Binary files /dev/null and b/resource/flash3/images/spellicons/cards/card_62.png differ diff --git a/resource/flash3/images/spellicons/cards/card_63.png b/resource/flash3/images/spellicons/cards/card_63.png new file mode 100644 index 0000000..51e4265 Binary files /dev/null and b/resource/flash3/images/spellicons/cards/card_63.png differ diff --git a/resource/flash3/images/spellicons/cards/card_64.png b/resource/flash3/images/spellicons/cards/card_64.png new file mode 100644 index 0000000..654b76d Binary files /dev/null and b/resource/flash3/images/spellicons/cards/card_64.png differ diff --git a/resource/flash3/images/spellicons/cards/card_65.png b/resource/flash3/images/spellicons/cards/card_65.png new file mode 100644 index 0000000..0bd42ed Binary files /dev/null and b/resource/flash3/images/spellicons/cards/card_65.png differ diff --git a/resource/flash3/images/spellicons/cards/card_66.png b/resource/flash3/images/spellicons/cards/card_66.png new file mode 100644 index 0000000..9b709c2 Binary files /dev/null and b/resource/flash3/images/spellicons/cards/card_66.png differ diff --git a/resource/flash3/images/spellicons/cards/card_67.png b/resource/flash3/images/spellicons/cards/card_67.png new file mode 100644 index 0000000..3bae598 Binary files /dev/null and b/resource/flash3/images/spellicons/cards/card_67.png differ diff --git a/resource/flash3/images/spellicons/cards/card_68.png b/resource/flash3/images/spellicons/cards/card_68.png new file mode 100644 index 0000000..6ce24d7 Binary files /dev/null and b/resource/flash3/images/spellicons/cards/card_68.png differ diff --git a/resource/flash3/images/spellicons/cards/card_69.png b/resource/flash3/images/spellicons/cards/card_69.png new file mode 100644 index 0000000..9d258ac Binary files /dev/null and b/resource/flash3/images/spellicons/cards/card_69.png differ diff --git a/resource/flash3/images/spellicons/cards/card_7.png b/resource/flash3/images/spellicons/cards/card_7.png new file mode 100644 index 0000000..23d5349 Binary files /dev/null and b/resource/flash3/images/spellicons/cards/card_7.png differ diff --git a/resource/flash3/images/spellicons/cards/card_70.png b/resource/flash3/images/spellicons/cards/card_70.png new file mode 100644 index 0000000..bf5c5bc Binary files /dev/null and b/resource/flash3/images/spellicons/cards/card_70.png differ diff --git a/resource/flash3/images/spellicons/cards/card_71.png b/resource/flash3/images/spellicons/cards/card_71.png new file mode 100644 index 0000000..13b1076 Binary files /dev/null and b/resource/flash3/images/spellicons/cards/card_71.png differ diff --git a/resource/flash3/images/spellicons/cards/card_72.png b/resource/flash3/images/spellicons/cards/card_72.png new file mode 100644 index 0000000..7df2e90 Binary files /dev/null and b/resource/flash3/images/spellicons/cards/card_72.png differ diff --git a/resource/flash3/images/spellicons/cards/card_73.png b/resource/flash3/images/spellicons/cards/card_73.png new file mode 100644 index 0000000..256830f Binary files /dev/null and b/resource/flash3/images/spellicons/cards/card_73.png differ diff --git a/resource/flash3/images/spellicons/cards/card_74.png b/resource/flash3/images/spellicons/cards/card_74.png new file mode 100644 index 0000000..12d06e9 Binary files /dev/null and b/resource/flash3/images/spellicons/cards/card_74.png differ diff --git a/resource/flash3/images/spellicons/cards/card_75.png b/resource/flash3/images/spellicons/cards/card_75.png new file mode 100644 index 0000000..ea29df7 Binary files /dev/null and b/resource/flash3/images/spellicons/cards/card_75.png differ diff --git a/resource/flash3/images/spellicons/cards/card_76.png b/resource/flash3/images/spellicons/cards/card_76.png new file mode 100644 index 0000000..d301fbf Binary files /dev/null and b/resource/flash3/images/spellicons/cards/card_76.png differ diff --git a/resource/flash3/images/spellicons/cards/card_77.png b/resource/flash3/images/spellicons/cards/card_77.png new file mode 100644 index 0000000..dce58de Binary files /dev/null and b/resource/flash3/images/spellicons/cards/card_77.png differ diff --git a/resource/flash3/images/spellicons/cards/card_78.png b/resource/flash3/images/spellicons/cards/card_78.png new file mode 100644 index 0000000..19bf685 Binary files /dev/null and b/resource/flash3/images/spellicons/cards/card_78.png differ diff --git a/resource/flash3/images/spellicons/cards/card_79.png b/resource/flash3/images/spellicons/cards/card_79.png new file mode 100644 index 0000000..73a5c0d Binary files /dev/null and b/resource/flash3/images/spellicons/cards/card_79.png differ diff --git a/resource/flash3/images/spellicons/cards/card_8.png b/resource/flash3/images/spellicons/cards/card_8.png new file mode 100644 index 0000000..c7529ae Binary files /dev/null and b/resource/flash3/images/spellicons/cards/card_8.png differ diff --git a/resource/flash3/images/spellicons/cards/card_80.png b/resource/flash3/images/spellicons/cards/card_80.png new file mode 100644 index 0000000..22db5c0 Binary files /dev/null and b/resource/flash3/images/spellicons/cards/card_80.png differ diff --git a/resource/flash3/images/spellicons/cards/card_81.png b/resource/flash3/images/spellicons/cards/card_81.png new file mode 100644 index 0000000..ce6d0a7 Binary files /dev/null and b/resource/flash3/images/spellicons/cards/card_81.png differ diff --git a/resource/flash3/images/spellicons/cards/card_82.png b/resource/flash3/images/spellicons/cards/card_82.png new file mode 100644 index 0000000..c370eb5 Binary files /dev/null and b/resource/flash3/images/spellicons/cards/card_82.png differ diff --git a/resource/flash3/images/spellicons/cards/card_83.png b/resource/flash3/images/spellicons/cards/card_83.png new file mode 100644 index 0000000..16dd77a Binary files /dev/null and b/resource/flash3/images/spellicons/cards/card_83.png differ diff --git a/resource/flash3/images/spellicons/cards/card_84.png b/resource/flash3/images/spellicons/cards/card_84.png new file mode 100644 index 0000000..6a5a295 Binary files /dev/null and b/resource/flash3/images/spellicons/cards/card_84.png differ diff --git a/resource/flash3/images/spellicons/cards/card_85.png b/resource/flash3/images/spellicons/cards/card_85.png new file mode 100644 index 0000000..85c4953 Binary files /dev/null and b/resource/flash3/images/spellicons/cards/card_85.png differ diff --git a/resource/flash3/images/spellicons/cards/card_86.png b/resource/flash3/images/spellicons/cards/card_86.png new file mode 100644 index 0000000..a74a1fc Binary files /dev/null and b/resource/flash3/images/spellicons/cards/card_86.png differ diff --git a/resource/flash3/images/spellicons/cards/card_9.png b/resource/flash3/images/spellicons/cards/card_9.png new file mode 100644 index 0000000..392b366 Binary files /dev/null and b/resource/flash3/images/spellicons/cards/card_9.png differ diff --git a/resource/flash3/images/spellicons/default_items/armlet_of_eternal_hunger/armlet2.png b/resource/flash3/images/spellicons/default_items/armlet_of_eternal_hunger/armlet2.png new file mode 100644 index 0000000..09d9c24 Binary files /dev/null and b/resource/flash3/images/spellicons/default_items/armlet_of_eternal_hunger/armlet2.png differ diff --git a/resource/flash3/images/spellicons/default_items/bloodstone2.png b/resource/flash3/images/spellicons/default_items/bloodstone2.png new file mode 100644 index 0000000..77b67e4 Binary files /dev/null and b/resource/flash3/images/spellicons/default_items/bloodstone2.png differ diff --git a/resource/flash3/images/spellicons/default_items/fire_cape.png b/resource/flash3/images/spellicons/default_items/fire_cape.png new file mode 100644 index 0000000..fe83751 Binary files /dev/null and b/resource/flash3/images/spellicons/default_items/fire_cape.png differ diff --git a/resource/flash3/images/spellicons/default_items/kunkka_sword.png b/resource/flash3/images/spellicons/default_items/kunkka_sword.png new file mode 100644 index 0000000..fcba018 Binary files /dev/null and b/resource/flash3/images/spellicons/default_items/kunkka_sword.png differ diff --git a/resource/flash3/images/spellicons/default_items/magicpier.png b/resource/flash3/images/spellicons/default_items/magicpier.png new file mode 100644 index 0000000..d6093d7 Binary files /dev/null and b/resource/flash3/images/spellicons/default_items/magicpier.png differ diff --git a/resource/flash3/images/spellicons/default_items/magicrapier.png b/resource/flash3/images/spellicons/default_items/magicrapier.png new file mode 100644 index 0000000..4290ace Binary files /dev/null and b/resource/flash3/images/spellicons/default_items/magicrapier.png differ diff --git a/resource/flash3/images/spellicons/default_items/orb_of_fire.png b/resource/flash3/images/spellicons/default_items/orb_of_fire.png new file mode 100644 index 0000000..0f02fae Binary files /dev/null and b/resource/flash3/images/spellicons/default_items/orb_of_fire.png differ diff --git a/resource/flash3/images/spellicons/default_items/soul_devourer_staff.png b/resource/flash3/images/spellicons/default_items/soul_devourer_staff.png new file mode 100644 index 0000000..cbfd13e Binary files /dev/null and b/resource/flash3/images/spellicons/default_items/soul_devourer_staff.png differ diff --git a/resource/flash3/images/spellicons/default_items/storm.png b/resource/flash3/images/spellicons/default_items/storm.png new file mode 100644 index 0000000..eed2bbb Binary files /dev/null and b/resource/flash3/images/spellicons/default_items/storm.png differ diff --git a/resource/flash3/images/spellicons/default_items/xp.png b/resource/flash3/images/spellicons/default_items/xp.png new file mode 100644 index 0000000..b283e27 Binary files /dev/null and b/resource/flash3/images/spellicons/default_items/xp.png differ diff --git a/resource/flash3/images/spellicons/new_heroes/crystal_maiden_brilliance_aura.png b/resource/flash3/images/spellicons/new_heroes/crystal_maiden_brilliance_aura.png new file mode 100644 index 0000000..f6f4937 Binary files /dev/null and b/resource/flash3/images/spellicons/new_heroes/crystal_maiden_brilliance_aura.png differ diff --git a/resource/flash3/images/spellicons/new_heroes/crystal_maiden_brilliance_aura_alt.png b/resource/flash3/images/spellicons/new_heroes/crystal_maiden_brilliance_aura_alt.png new file mode 100644 index 0000000..43d77ee Binary files /dev/null and b/resource/flash3/images/spellicons/new_heroes/crystal_maiden_brilliance_aura_alt.png differ diff --git a/resource/flash3/images/spellicons/new_heroes/crystal_maiden_frostbite.png b/resource/flash3/images/spellicons/new_heroes/crystal_maiden_frostbite.png new file mode 100644 index 0000000..a1afb80 Binary files /dev/null and b/resource/flash3/images/spellicons/new_heroes/crystal_maiden_frostbite.png differ diff --git a/resource/flash3/images/spellicons/new_heroes/crystal_maiden_frostbite_alt.png b/resource/flash3/images/spellicons/new_heroes/crystal_maiden_frostbite_alt.png new file mode 100644 index 0000000..8c002ea Binary files /dev/null and b/resource/flash3/images/spellicons/new_heroes/crystal_maiden_frostbite_alt.png differ diff --git a/resource/flash3/images/spellicons/new_heroes/juggernaut_blade_dance_custom_alt.png b/resource/flash3/images/spellicons/new_heroes/juggernaut_blade_dance_custom_alt.png new file mode 100644 index 0000000..bf0ed12 Binary files /dev/null and b/resource/flash3/images/spellicons/new_heroes/juggernaut_blade_dance_custom_alt.png differ diff --git a/resource/flash3/images/spellicons/new_heroes/juggernaut_death.png b/resource/flash3/images/spellicons/new_heroes/juggernaut_death.png new file mode 100644 index 0000000..a7b7f31 Binary files /dev/null and b/resource/flash3/images/spellicons/new_heroes/juggernaut_death.png differ diff --git a/resource/flash3/images/spellicons/new_heroes/juggernaut_miracle.png b/resource/flash3/images/spellicons/new_heroes/juggernaut_miracle.png new file mode 100644 index 0000000..09ae6a2 Binary files /dev/null and b/resource/flash3/images/spellicons/new_heroes/juggernaut_miracle.png differ diff --git a/resource/flash3/images/spellicons/new_heroes/mind_storm.png b/resource/flash3/images/spellicons/new_heroes/mind_storm.png new file mode 100644 index 0000000..14d45d9 Binary files /dev/null and b/resource/flash3/images/spellicons/new_heroes/mind_storm.png differ diff --git a/resource/flash3/images/spellicons/new_heroes/refusion_trap.png b/resource/flash3/images/spellicons/new_heroes/refusion_trap.png new file mode 100644 index 0000000..76a3276 Binary files /dev/null and b/resource/flash3/images/spellicons/new_heroes/refusion_trap.png differ diff --git a/resource/flash3/images/spellicons/new_heroes/sil_glaive.png b/resource/flash3/images/spellicons/new_heroes/sil_glaive.png new file mode 100644 index 0000000..da68aeb Binary files /dev/null and b/resource/flash3/images/spellicons/new_heroes/sil_glaive.png differ diff --git a/resource/flash3/images/spellicons/new_heroes/templar_secret.png b/resource/flash3/images/spellicons/new_heroes/templar_secret.png new file mode 100644 index 0000000..51a2e52 Binary files /dev/null and b/resource/flash3/images/spellicons/new_heroes/templar_secret.png differ diff --git a/resource/flash3/images/spellicons/old_heroes/bloodrage.png b/resource/flash3/images/spellicons/old_heroes/bloodrage.png new file mode 100644 index 0000000..ba3e838 Binary files /dev/null and b/resource/flash3/images/spellicons/old_heroes/bloodrage.png differ diff --git a/resource/flash3/images/spellicons/old_heroes/bloodstained_memory.png b/resource/flash3/images/spellicons/old_heroes/bloodstained_memory.png new file mode 100644 index 0000000..f7db3d6 Binary files /dev/null and b/resource/flash3/images/spellicons/old_heroes/bloodstained_memory.png differ diff --git a/resource/flash3/images/spellicons/old_heroes/burning_strange.png b/resource/flash3/images/spellicons/old_heroes/burning_strange.png new file mode 100644 index 0000000..7711e38 Binary files /dev/null and b/resource/flash3/images/spellicons/old_heroes/burning_strange.png differ diff --git a/resource/flash3/images/spellicons/old_heroes/devour_sargatanas.png b/resource/flash3/images/spellicons/old_heroes/devour_sargatanas.png new file mode 100644 index 0000000..3f3d6e7 Binary files /dev/null and b/resource/flash3/images/spellicons/old_heroes/devour_sargatanas.png differ diff --git a/resource/flash3/images/spellicons/old_heroes/dragon_blood.png b/resource/flash3/images/spellicons/old_heroes/dragon_blood.png new file mode 100644 index 0000000..8ce56ea Binary files /dev/null and b/resource/flash3/images/spellicons/old_heroes/dragon_blood.png differ diff --git a/resource/flash3/images/spellicons/old_heroes/dragon_breathe.png b/resource/flash3/images/spellicons/old_heroes/dragon_breathe.png new file mode 100644 index 0000000..554493c Binary files /dev/null and b/resource/flash3/images/spellicons/old_heroes/dragon_breathe.png differ diff --git a/resource/flash3/images/spellicons/old_heroes/dragon_fear_aura.png b/resource/flash3/images/spellicons/old_heroes/dragon_fear_aura.png new file mode 100644 index 0000000..849fa52 Binary files /dev/null and b/resource/flash3/images/spellicons/old_heroes/dragon_fear_aura.png differ diff --git a/resource/flash3/images/spellicons/old_heroes/dragon_gold_deal.png b/resource/flash3/images/spellicons/old_heroes/dragon_gold_deal.png new file mode 100644 index 0000000..8f4e17c Binary files /dev/null and b/resource/flash3/images/spellicons/old_heroes/dragon_gold_deal.png differ diff --git a/resource/flash3/images/spellicons/old_heroes/dragon_reward.png b/resource/flash3/images/spellicons/old_heroes/dragon_reward.png new file mode 100644 index 0000000..abaf28f Binary files /dev/null and b/resource/flash3/images/spellicons/old_heroes/dragon_reward.png differ diff --git a/resource/flash3/images/spellicons/old_heroes/dragon_scales.png b/resource/flash3/images/spellicons/old_heroes/dragon_scales.png new file mode 100644 index 0000000..765f4eb Binary files /dev/null and b/resource/flash3/images/spellicons/old_heroes/dragon_scales.png differ diff --git a/resource/flash3/images/spellicons/old_heroes/fire.png b/resource/flash3/images/spellicons/old_heroes/fire.png new file mode 100644 index 0000000..2a998f5 Binary files /dev/null and b/resource/flash3/images/spellicons/old_heroes/fire.png differ diff --git a/resource/flash3/images/spellicons/old_heroes/fire_punishment.png b/resource/flash3/images/spellicons/old_heroes/fire_punishment.png new file mode 100644 index 0000000..4811520 Binary files /dev/null and b/resource/flash3/images/spellicons/old_heroes/fire_punishment.png differ diff --git a/resource/flash3/images/spellicons/old_heroes/firecleave.png b/resource/flash3/images/spellicons/old_heroes/firecleave.png new file mode 100644 index 0000000..ea61ca4 Binary files /dev/null and b/resource/flash3/images/spellicons/old_heroes/firecleave.png differ diff --git a/resource/flash3/images/spellicons/old_heroes/hell_summon.png b/resource/flash3/images/spellicons/old_heroes/hell_summon.png new file mode 100644 index 0000000..8791741 Binary files /dev/null and b/resource/flash3/images/spellicons/old_heroes/hell_summon.png differ diff --git a/resource/flash3/images/spellicons/old_heroes/hellstep.png b/resource/flash3/images/spellicons/old_heroes/hellstep.png new file mode 100644 index 0000000..f03539f Binary files /dev/null and b/resource/flash3/images/spellicons/old_heroes/hellstep.png differ diff --git a/resource/flash3/images/spellicons/old_heroes/illusions_of_blood.png b/resource/flash3/images/spellicons/old_heroes/illusions_of_blood.png new file mode 100644 index 0000000..6e63b43 Binary files /dev/null and b/resource/flash3/images/spellicons/old_heroes/illusions_of_blood.png differ diff --git a/resource/flash3/images/spellicons/old_heroes/incandescent_fury.png b/resource/flash3/images/spellicons/old_heroes/incandescent_fury.png new file mode 100644 index 0000000..bb9be98 Binary files /dev/null and b/resource/flash3/images/spellicons/old_heroes/incandescent_fury.png differ diff --git a/resource/flash3/images/spellicons/old_heroes/nagash/dark_friend.png b/resource/flash3/images/spellicons/old_heroes/nagash/dark_friend.png new file mode 100644 index 0000000..0ce44e6 Binary files /dev/null and b/resource/flash3/images/spellicons/old_heroes/nagash/dark_friend.png differ diff --git a/resource/flash3/images/spellicons/old_heroes/nagash/dark_golem.png b/resource/flash3/images/spellicons/old_heroes/nagash/dark_golem.png new file mode 100644 index 0000000..cb3fa8a Binary files /dev/null and b/resource/flash3/images/spellicons/old_heroes/nagash/dark_golem.png differ diff --git a/resource/flash3/images/spellicons/old_heroes/nagash/leader_call.png b/resource/flash3/images/spellicons/old_heroes/nagash/leader_call.png new file mode 100644 index 0000000..e89546e Binary files /dev/null and b/resource/flash3/images/spellicons/old_heroes/nagash/leader_call.png differ diff --git a/resource/flash3/images/spellicons/old_heroes/nagash/soul_eater.png b/resource/flash3/images/spellicons/old_heroes/nagash/soul_eater.png new file mode 100644 index 0000000..5d32b28 Binary files /dev/null and b/resource/flash3/images/spellicons/old_heroes/nagash/soul_eater.png differ diff --git a/resource/flash3/images/spellicons/old_heroes/nagash/war_for_life.png b/resource/flash3/images/spellicons/old_heroes/nagash/war_for_life.png new file mode 100644 index 0000000..6d25694 Binary files /dev/null and b/resource/flash3/images/spellicons/old_heroes/nagash/war_for_life.png differ diff --git a/resource/flash3/images/spellicons/old_heroes/nagash/world_destroyer.png b/resource/flash3/images/spellicons/old_heroes/nagash/world_destroyer.png new file mode 100644 index 0000000..4457616 Binary files /dev/null and b/resource/flash3/images/spellicons/old_heroes/nagash/world_destroyer.png differ diff --git a/resource/flash3/images/spellicons/old_heroes/overhell.png b/resource/flash3/images/spellicons/old_heroes/overhell.png new file mode 100644 index 0000000..c24b49e Binary files /dev/null and b/resource/flash3/images/spellicons/old_heroes/overhell.png differ diff --git a/resource/flash3/images/spellicons/old_heroes/revolve_metamorphosis.png b/resource/flash3/images/spellicons/old_heroes/revolve_metamorphosis.png new file mode 100644 index 0000000..716e5a1 Binary files /dev/null and b/resource/flash3/images/spellicons/old_heroes/revolve_metamorphosis.png differ diff --git a/resource/flash3/images/spellicons/old_heroes/ring_of_corrosive.png b/resource/flash3/images/spellicons/old_heroes/ring_of_corrosive.png new file mode 100644 index 0000000..9f4ecca Binary files /dev/null and b/resource/flash3/images/spellicons/old_heroes/ring_of_corrosive.png differ diff --git a/resource/flash3/images/spellicons/old_heroes/sacrifice_revenge.png b/resource/flash3/images/spellicons/old_heroes/sacrifice_revenge.png new file mode 100644 index 0000000..8dc4422 Binary files /dev/null and b/resource/flash3/images/spellicons/old_heroes/sacrifice_revenge.png differ diff --git a/resource/flash3/images/spellicons/old_heroes/sunflame.png b/resource/flash3/images/spellicons/old_heroes/sunflame.png new file mode 100644 index 0000000..7e2decb Binary files /dev/null and b/resource/flash3/images/spellicons/old_heroes/sunflame.png differ diff --git a/resource/flash3/images/spellicons/old_heroes/yuki/challenge.png b/resource/flash3/images/spellicons/old_heroes/yuki/challenge.png new file mode 100644 index 0000000..e17ca43 Binary files /dev/null and b/resource/flash3/images/spellicons/old_heroes/yuki/challenge.png differ diff --git a/resource/flash3/images/spellicons/old_heroes/yuki/froststorm.png b/resource/flash3/images/spellicons/old_heroes/yuki/froststorm.png new file mode 100644 index 0000000..9ceef89 Binary files /dev/null and b/resource/flash3/images/spellicons/old_heroes/yuki/froststorm.png differ diff --git a/resource/flash3/images/spellicons/old_heroes/yuki/mana_berserk.png b/resource/flash3/images/spellicons/old_heroes/yuki/mana_berserk.png new file mode 100644 index 0000000..a728ec2 Binary files /dev/null and b/resource/flash3/images/spellicons/old_heroes/yuki/mana_berserk.png differ diff --git a/resource/flash3/images/spellicons/old_heroes/yuki/revenge.png b/resource/flash3/images/spellicons/old_heroes/yuki/revenge.png new file mode 100644 index 0000000..f5a7d67 Binary files /dev/null and b/resource/flash3/images/spellicons/old_heroes/yuki/revenge.png differ diff --git a/resource/flash3/images/spellicons/old_heroes/yuki/ritual.png b/resource/flash3/images/spellicons/old_heroes/yuki/ritual.png new file mode 100644 index 0000000..64fcedd Binary files /dev/null and b/resource/flash3/images/spellicons/old_heroes/yuki/ritual.png differ diff --git a/resource/flash3/images/spellicons/old_heroes/yuki/snowman.png b/resource/flash3/images/spellicons/old_heroes/yuki/snowman.png new file mode 100644 index 0000000..0176583 Binary files /dev/null and b/resource/flash3/images/spellicons/old_heroes/yuki/snowman.png differ diff --git a/resource/flash3/images/spellicons/util_items/candy.png b/resource/flash3/images/spellicons/util_items/candy.png new file mode 100644 index 0000000..aa00316 Binary files /dev/null and b/resource/flash3/images/spellicons/util_items/candy.png differ diff --git a/resource/flash3/images/spellicons/util_items/eggs.png b/resource/flash3/images/spellicons/util_items/eggs.png new file mode 100644 index 0000000..7e1f8ec Binary files /dev/null and b/resource/flash3/images/spellicons/util_items/eggs.png differ diff --git a/resource/overviews/invasion.txt b/resource/overviews/invasion.txt new file mode 100644 index 0000000..e1ec564 --- /dev/null +++ b/resource/overviews/invasion.txt @@ -0,0 +1,8 @@ +invasion +{ + material materials/overviews/invasion.vmat + pos_x -16384 + pos_y 16384 + scale 32.000 +} + diff --git a/resource/overviews/test.txt b/resource/overviews/test.txt new file mode 100644 index 0000000..2e384e7 --- /dev/null +++ b/resource/overviews/test.txt @@ -0,0 +1,8 @@ +test +{ + material materials/overviews/test.vmat + pos_x -8192 + pos_y 8192 + scale 16.000 +} + diff --git a/resource/overviews/zombie_invasion.txt b/resource/overviews/zombie_invasion.txt new file mode 100644 index 0000000..2a7f5fa --- /dev/null +++ b/resource/overviews/zombie_invasion.txt @@ -0,0 +1,8 @@ +zombie_invasion +{ + material materials/overviews/zombie_invasion.vmat + pos_x -16384 + pos_y 16384 + scale 32.000 +} + diff --git a/scripts/custom_net_tables.txt b/scripts/custom_net_tables.txt new file mode 100644 index 0000000..86391ea --- /dev/null +++ b/scripts/custom_net_tables.txt @@ -0,0 +1,19 @@ + +{ + custom_net_tables = + [ + "custom_stats", + "crystal_currency", + "cards", + "player_info", + "cooldown_info", + "sound_cooldowns", + "rubick_stolen_abilities", + "ability_alt_cast_states", + "arsenal_catalog", + "arsenal_loadouts", + "arsenal_inventory", + "arsenal_market", + "death_sentence_contracts", + ] +} \ No newline at end of file diff --git a/scripts/npc/creep_abilities/creep_abilities.kv b/scripts/npc/creep_abilities/creep_abilities.kv new file mode 100644 index 0000000..a262310 --- /dev/null +++ b/scripts/npc/creep_abilities/creep_abilities.kv @@ -0,0 +1,985 @@ +dotaa_abilities +{ + "witch_base" + { + "BaseClass" "ability_lua" + "ScriptFile" "abilities/creep/witch_base" + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_PASSIVE | DOTA_ABILITY_BEHAVIOR_HIDDEN" + "AbilityUnitTargetTeam" "DOTA_UNIT_TARGET_TEAM_ENEMY" + "AbilityUnitTargetType" "DOTA_UNIT_TARGET_HERO | DOTA_UNIT_TARGET_BASIC" + "AbilityUnitDamageType" "DAMAGE_TYPE_MAGICAL" + "SpellImmunityType" "SPELL_IMMUNITY_ENEMIES_NO" + "SpellDispellableType" "SPELL_DISPELLABLE_NO" + + "AbilityValues" + { + "amp_death_bonus" "15" + "attack_death_bonus" "25" + "health_death_bonus" "200" + "armor_death_bonus" "1" + } + } + + "kaban_rofl_ability" + { + "BaseClass" "ability_lua" + "ScriptFile" "abilities/creep/kaban_rofl_ability" + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_PASSIVE | DOTA_ABILITY_BEHAVIOR_HIDDEN" + "SpellDispellableType" "SPELL_DISPELLABLE_NO" + } + + "chicken_attack" + { + "BaseClass" "beastmaster_hawk_dive" + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_UNIT_TARGET" + "AbilityUnitTargetTeam" "DOTA_UNIT_TARGET_TEAM_ENEMY" + "AbilityUnitTargetType" "DOTA_UNIT_TARGET_HERO | DOTA_UNIT_TARGET_BASIC" + "AbilityUnitDamageType" "DAMAGE_TYPE_MAGICAL" + "SpellImmunityType" "SPELL_IMMUNITY_ENEMIES_NO" + "SpellDispellableType" "SPELL_DISPELLABLE_YES" + "MaxLevel" "1" + "FightRecapLevel" "1" + + + // Casting + //------------------------------------------------------------------------------------------------------------- + "AbilityCastRange" "350" + "AbilityCastPoint" "0.0" + "AbilityCastAnimation" "ACT_DOTA_CAST_ABILITY_5" + + + // Time + //------------------------------------------------------------------------------------------------------------- + "AbilityCooldown" "4" + + // Cost + //------------------------------------------------------------------------------------------------------------- + "AbilityManaCost" "0" + "AbilityValues" + { + "precast_warning_time" "0.35" + } + } + "pig_charge" + { + "BaseClass" "primal_beast_onslaught" // unique ID number for this ability. Do not change this once established or it will invalidate collected stats. + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_POINT | DOTA_ABILITY_BEHAVIOR_IGNORE_BACKSWING | DOTA_ABILITY_BEHAVIOR_ROOT_DISABLES" + "AbilityUnitTargetType" "DOTA_UNIT_TARGET_BASIC | DOTA_UNIT_TARGET_HERO" + "AbilityUnitDamageType" "DAMAGE_TYPE_PHYSICAL" + "AbilityUnitTargetTeam" "DOTA_UNIT_TARGET_TEAM_ENEMY" + "SpellImmunityType" "SPELL_IMMUNITY_ENEMIES_NO" + "SpellDispellableType" "SPELL_DISPELLABLE_YES" + "FightRecapLevel" "1" + "AbilityCastPoint" "0" + "AbilityCastAnimation" "ACT_DOTA_CAST_ABILITY_2" + "AbilityCastGestureSlot" "DEFAULT" + //"AbilityCastRange" "2000" + + "AbilityCooldown" "5" + + "AbilityManaCost" "0" + + "AbilityValues" + { + "charge_speed" "460" + "chargeup_time" "0.45" + "knockback_radius" "100" + "max_distance" "400" + "knockback_distance" "110" + "knockback_damage" "65" + "knockback_duration" "0.2" + "max_charge_time" "0.4" + "turn_rate" "70" + "base_power" "0.05" + "movement_turn_rate" "50" + "stun_duration" "0.2" + } + } + "sheep_coil" + { + "BaseClass" "ability_lua" + "ScriptFile" "abilities/creep/sheep_coil" + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_POINT | DOTA_ABILITY_BEHAVIOR_AOE" + "AbilityUnitTargetTeam" "DOTA_UNIT_TARGET_TEAM_ENEMY" + "AbilityType" "ABILITY_TYPE_BASIC" + "AbilityUnitDamageType" "DAMAGE_TYPE_MAGICAL" + "SpellImmunityType" "SPELL_IMMUNITY_ENEMIES_NO" + "SpellDispellableType" "SPELL_DISPELLABLE_YES" + "AbilityCastRange" "600" + "AbilityCastPoint" "0.3" + "AbilityCooldown" "2.5" + "AbilityManaCost" "25" + + "AbilityValues" + { + "radius" "125" + "damage" "15" + "projectile_speed" "1000" + "slow_duration" "2.5" + "movement_slow" "-5" + "precast_warning_time" "0.3" + } + } + "boss_nevermore_coil_wave" + { + "BaseClass" "ability_lua" + "ScriptFile" "abilities/creep/boss_nevermore_coil_wave" + "AbilityTextureName" "nevermore_shadowraze1" + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_POINT | DOTA_ABILITY_BEHAVIOR_AOE" + "AbilityUnitTargetTeam" "DOTA_UNIT_TARGET_TEAM_ENEMY" + "AbilityUnitTargetType" "DOTA_UNIT_TARGET_HERO | DOTA_UNIT_TARGET_BASIC" + "AbilityType" "ABILITY_TYPE_BASIC" + "AbilityUnitDamageType" "DAMAGE_TYPE_MAGICAL" + "SpellImmunityType" "SPELL_IMMUNITY_ENEMIES_NO" + "SpellDispellableType" "SPELL_DISPELLABLE_NO" + "AbilityCastRange" "10000" + "AbilityCastPoint" "0.4" + "AbilityCooldown" "0" + "AbilityManaCost" "0" + "AbilityValues" + { + "radius" "160" + "damage" "160" + "start_delay" "0.9" + "pulse_count" "4" + "step_distance" "340" + "projectile_speed" "1100" + "precast_warning_time" "0.9" + "debuff_duration" "5.0" + "coil_stack_bonus_damage" "20" + "coil_slow_per_stack" "3" + } + "AbilityCastAnimation" "ACT_DOTA_RAZE_1" + } + "boss_nevermore_coil_beam" + { + "BaseClass" "ability_lua" + "ScriptFile" "abilities/creep/boss_nevermore_coil_beam" + "AbilityTextureName" "nevermore_shadowraze3" + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_POINT | DOTA_ABILITY_BEHAVIOR_AOE" + "AbilityUnitTargetTeam" "DOTA_UNIT_TARGET_TEAM_ENEMY" + "AbilityUnitTargetType" "DOTA_UNIT_TARGET_HERO | DOTA_UNIT_TARGET_BASIC" + "AbilityType" "ABILITY_TYPE_BASIC" + "AbilityUnitDamageType" "DAMAGE_TYPE_MAGICAL" + "SpellImmunityType" "SPELL_IMMUNITY_ENEMIES_NO" + "SpellDispellableType" "SPELL_DISPELLABLE_NO" + "AbilityCastRange" "10000" + "AbilityCastPoint" "0.4" + "AbilityCooldown" "0" + "AbilityManaCost" "0" + "AbilityValues" + { + "radius" "160" + "start_delay" "0.9" + "precast_warning_time" "0.85" + "second_wave_delay" "1.0" + "lane_slot_count" "12" + "lane_slot_phase_bonus" "2" + "beam_start_dist" "140" + "beam_step" "195" + "knockback_distance" "900" + "knockback_duration" "0.42" + "coil_stack_bonus_damage" "20" + "coil_slow_per_stack" "3" + } + "AbilityCastAnimation" "ACT_DOTA_RAZE_1" + } + "boss_nevermore_hub_crossburst" + { + "BaseClass" "ability_lua" + "ScriptFile" "abilities/creep/boss_nevermore_hub_crossburst" + "AbilityTextureName" "nevermore_shadowraze2" + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_POINT | DOTA_ABILITY_BEHAVIOR_AOE" + "AbilityUnitTargetTeam" "DOTA_UNIT_TARGET_TEAM_ENEMY" + "AbilityUnitTargetType" "DOTA_UNIT_TARGET_HERO | DOTA_UNIT_TARGET_BASIC" + "AbilityType" "ABILITY_TYPE_BASIC" + "AbilityUnitDamageType" "DAMAGE_TYPE_MAGICAL" + "SpellImmunityType" "SPELL_IMMUNITY_ENEMIES_NO" + "SpellDispellableType" "SPELL_DISPELLABLE_NO" + "AbilityCastRange" "10000" + "AbilityCastPoint" "0.35" + "AbilityCooldown" "0" + "AbilityManaCost" "0" + "AbilityValues" + { + "radius" "165" + "spawn_pick_radius" "1500" + "ring_count" "7" + "ring_count_phase_bonus" "1" + "ring_step" "190" + "ring_start_dist" "90" + "ring_interval" "0.52" + "first_ring_delay" "0.55" + "precast_warning_time" "0.82" + "knockback_distance" "650" + "knockback_duration" "0.38" + "coil_stack_bonus_damage" "20" + "coil_slow_per_stack" "3" + } + "AbilityCastAnimation" "ACT_DOTA_RAZE_1" + } + "boss_nevermore_triple_coil_aoe" + { + "BaseClass" "ability_lua" + "ScriptFile" "abilities/creep/boss_nevermore_triple_coil_aoe" + "AbilityTextureName" "nevermore_shadowraze2" + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_POINT | DOTA_ABILITY_BEHAVIOR_AOE" + "AbilityUnitTargetTeam" "DOTA_UNIT_TARGET_TEAM_ENEMY" + "AbilityUnitTargetType" "DOTA_UNIT_TARGET_HERO | DOTA_UNIT_TARGET_BASIC" + "AbilityType" "ABILITY_TYPE_BASIC" + "AbilityUnitDamageType" "DAMAGE_TYPE_MAGICAL" + "SpellImmunityType" "SPELL_IMMUNITY_ENEMIES_NO" + "SpellDispellableType" "SPELL_DISPELLABLE_NO" + "AbilityCastRange" "10000" + "AbilityCastPoint" "0.4" + "AbilityCooldown" "0" + "AbilityManaCost" "0" + "AbilityValues" + { + "radius" "300" + "damage" "120" + "delay" "0.9" + "precast_warning_time" "0.9" + "side_offset" "170" + "forward_offset" "170" + "coil_radius" "120" + "debuff_duration" "5.0" + "coil_stack_bonus_damage" "20" + "coil_slow_per_stack" "3" + } + "AbilityCastAnimation" "ACT_DOTA_RAZE_2" + } + "boss_nevermore_requiem_barrage" + { + "BaseClass" "ability_lua" + "ScriptFile" "abilities/creep/boss_nevermore_requiem_barrage" + "AbilityTextureName" "nevermore_requiem" + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_POINT | DOTA_ABILITY_BEHAVIOR_CHANNELLED | DOTA_ABILITY_BEHAVIOR_AOE" + "AbilityUnitTargetTeam" "DOTA_UNIT_TARGET_TEAM_ENEMY" + "AbilityUnitTargetType" "DOTA_UNIT_TARGET_HERO | DOTA_UNIT_TARGET_BASIC" + "AbilityType" "ABILITY_TYPE_BASIC" + "AbilityUnitDamageType" "DAMAGE_TYPE_MAGICAL" + "SpellImmunityType" "SPELL_IMMUNITY_ENEMIES_NO" + "SpellDispellableType" "SPELL_DISPELLABLE_NO" + "AbilityCastRange" "12000" + "AbilityCastPoint" "0.3" + "AbilityCooldown" "0" + "AbilityManaCost" "0" + "AbilityValues" + { + "channel_time" "12.8" + "wave_interval" "0.88" + "wave_distance" "7200" + "wave_speed" "1120" + "wave_speed_min" "832" + "wave_speed_max" "1568" + "wave_preview_time" "0.5" + "wave_width_start" "112" + "wave_width_end" "112" + "damage" "272" + "permanent_magic_resist_reduce_per_hit" "24" + "radius" "400" + } + } + "boss_nevermore_time_walk" + { + "BaseClass" "ability_lua" + "ScriptFile" "abilities/creep/boss_nevermore_time_walk" + "AbilityTextureName" "faceless_void_time_walk" + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_POINT" + "AbilityUnitTargetTeam" "DOTA_UNIT_TARGET_TEAM_ENEMY" + "AbilityUnitTargetType" "DOTA_UNIT_TARGET_HERO | DOTA_UNIT_TARGET_BASIC" + "AbilityType" "ABILITY_TYPE_BASIC" + "AbilityUnitDamageType" "DAMAGE_TYPE_MAGICAL" + "SpellImmunityType" "SPELL_IMMUNITY_ENEMIES_NO" + "SpellDispellableType" "SPELL_DISPELLABLE_NO" + "AbilityCastRange" "9000" + "AbilityCastPoint" "0.25" + "AbilityCooldown" "0" + "AbilityManaCost" "0" + "AbilityValues" + { + "range" "9000" + "speed" "1500" + "radius" "220" + "damage" "180" + "coil_interval" "0.2" + "side_wave_interval" "0.35" + } + "AbilityCastAnimation" "ACT_DOTA_FLAIL" + } + "terrorblade_terror_wave" + { + // General + //------------------------------------------------------------------------------------------------------------- + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_NO_TARGET | DOTA_ABILITY_BEHAVIOR_IMMEDIATE" + "AbilityUnitTargetType" "DOTA_UNIT_TARGET_HERO | DOTA_UNIT_TARGET_BASIC" + "AbilityUnitDamageType" "DAMAGE_TYPE_MAGICAL" + "SpellDispellableType" "SPELL_DISPELLABLE_YES" + "MaxLevel" "1" + "FightRecapLevel" "1" + "IsGrantedByScepter" "0" + + // Casting + //------------------------------------------------------------------------------------------------------------- + "AbilityCastAnimation" "ACT_DOTA_CAST_ABILITY_3" + + // Time + //------------------------------------------------------------------------------------------------------------- + "AbilityCooldown" "0" + + // Cost + //------------------------------------------------------------------------------------------------------------- + "AbilityManaCost" "0" + + // Special + //------------------------------------------------------------------------------------------------------------- + "AbilityValues" + { + "fear_duration" "1.22" + "scepter_radius" + { + "value" "7000" + "affected_by_aoe_increase" "1" + } + "damage" "500" + "scepter_speed" "1000" + "scepter_spawn_delay" "0.6" + "scepter_meta_duration" "15" + } + } + "agro_leader" + { + "BaseClass" "ability_lua" + "ScriptFile" "abilities/creep/agro_leader" + "MaxLevel" "1" + "AbilityCooldown" "8" + "AbilityCastPoint" "0.8" + "AbilityTextureName" "axe_berserkers_call" + + "AbilityType" "DOTA_ABILITY_TYPE_BASIC" + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_NO_TARGET" + + "AbilityValues" + { + "radius" "700" + "duration" "3" + } + } + "thief_arrow" + { + "BaseClass" "ability_lua" + "ScriptFile" "abilities/creep/thief_arrow" + "AbilityTextureName" "mirana_arrow" + "MaxLevel" "1" + + // Ability General + //------------------------------------------------------------------------------------------------------------- + "AbilityType" "DOTA_ABILITY_TYPE_BASIC" + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_POINT" + "AbilityUnitDamageType" "DAMAGE_TYPE_PHYSICAL" + "SpellDispellableType" "SPELL_DISPELLABLE_YES_STRONG" + "SpellImmunityType" "SPELL_IMMUNITY_ENEMIES_NO" + + // Ability Casting + //------------------------------------------------------------------------------------------------------------- + "AbilityCastRange" "600" + "AbilityCastPoint" "0.8" + + // Ability Resource + //------------------------------------------------------------------------------------------------------------- + "AbilityCooldown" "1.5" + "AbilityManaCost" "0" + + // Damage + //------------------------------------------------------------------------------------------------------------- + "AbilityDamage" "100" + + // Special + //------------------------------------------------------------------------------------------------------------- + "AbilityValues" + { + "arrow_speed" "857.0" + "arrow_width" "115" + "arrow_range" "1100" + } + } + "thief_charge" + { + "BaseClass" "spirit_breaker_charge_of_darkness" + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_UNIT_TARGET" + "AbilityUnitTargetTeam" "DOTA_UNIT_TARGET_TEAM_ENEMY" + "AbilityUnitTargetType" "DOTA_UNIT_TARGET_HERO | DOTA_UNIT_TARGET_BASIC" + "SpellImmunityType" "SPELL_IMMUNITY_ENEMIES_NO" + "SpellDispellableType" "SPELL_DISPELLABLE_NO" + "FightRecapLevel" "1" + + "AbilitySound" "Hero_Spirit_Breaker.ChargeOfDarkness" + "HasShardUpgrade" "1" + + // Casting + //------------------------------------------------------------------------------------------------------------- + "AbilityCastPoint" "0.1" + "AbilityCastRange" "0" + + // Time + //------------------------------------------------------------------------------------------------------------- + + // Cost + //------------------------------------------------------------------------------------------------------------- + "AbilityManaCost" "0" + + // Stats + //------------------------------------------------------------------------------------------------------------- + "AbilityModifierSupportValue" ".30" // applies multiple modifiers + + // Special + //------------------------------------------------------------------------------------------------------------- + "AbilityValues" + { + "movement_speed" "1420" + + + "stun_duration" "1" + "bash_radius" "300" + + "vision_radius" "400 400 400 400" + + "vision_duration" "0.94 0.94 0.94 0.94" + "AbilityCooldown" "5" + + + "out_of_world_time" "0" + "guaranteed_bash_time" "0" + "reveal_charge" "0" + + "windup_time" "0.5" + "min_movespeed_bonus_pct" "25" + + "linger_time_min" "0" + + + "linger_time_max" "0" + + + "charge_for_max_linger" "0" + "precast_warning_time" "0.5" + + } + "AbilityCastAnimation" "ACT_DOTA_CAST_ABILITY_1" + } + "black_dragon_fireball" + { + // General + //------------------------------------------------------------------------------------------------------------- + "MaxLevel" "1" + "AbilityType" "ABILITY_TYPE_BASIC" + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_POINT | DOTA_ABILITY_BEHAVIOR_AOE" + "AbilityUnitDamageType" "DAMAGE_TYPE_MAGICAL" + "SpellImmunityType" "SPELL_IMMUNITY_ENEMIES_NO" + "FightRecapLevel" "1" + + // Casting + //------------------------------------------------------------------------------------------------------------- + "AbilityCastRange" "500" + "AbilityCastPoint" "0.3" + + // Time + //------------------------------------------------------------------------------------------------------------- + "AbilityCooldown" "5" + + // Cost + //------------------------------------------------------------------------------------------------------------- + "AbilityManaCost" "25" + + // Special + //------------------------------------------------------------------------------------------------------------- + "AbilityValues" + { + "radius" + { + "value" "300" + "affected_by_aoe_increase" "1" + } + "damage" "225" + "duration" "2.5" + "burn_interval" "0.5" + "precast_warning_time" "1" + } + "AbilityCastAnimation" "ACT_DOTA_CAST_ABILITY_1" + } + "black_dragon_dragonhide_aura" + { + // General + //------------------------------------------------------------------------------------------------------------- + "MaxLevel" "1" + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_PASSIVE" + "AbilityCastAnimation" "ACT_DOTA_CAST_ABILITY_3" + "IsBreakable" "1" + "AbilityValues" + { + "bonus_armor" + { + "value" "12" + } + "radius" + { + "value" "650" + "affected_by_aoe_increase" "1" + } + } + } + "spawnlord_master_stomp" + { + // General + //------------------------------------------------------------------------------------------------------------- + "MaxLevel" "1" + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_NO_TARGET" + "AbilityUnitDamageType" "DAMAGE_TYPE_PHYSICAL" + "SpellDispellableType" "SPELL_DISPELLABLE_YES" + + // Stats + //------------------------------------------------------------------------------------------------------------- + "AbilityCastPoint" "0.65" + "AbilityCooldown" "12.0" + "AbilityManaCost" "100" + "AbilityCastAnimation" "ACT_DOTA_CAST_ABILITY_1" + "AbilityValues" + { + "radius" "400" + "damage" + { + "value" "400" + } + "duration" + { + "value" "6" + } + "armor_reduction_pct" + { + "value" "50" + } + "precast_warning_time" "0" + } + } + "spawnlord_master_freeze" + { + // General + //------------------------------------------------------------------------------------------------------------- + "MaxLevel" "1" + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_UNIT_TARGET | DOTA_ABILITY_BEHAVIOR_AUTOCAST | DOTA_ABILITY_BEHAVIOR_ATTACK" + "AbilityUnitDamageType" "DAMAGE_TYPE_PHYSICAL" + "AbilityUnitTargetTeam" "DOTA_UNIT_TARGET_TEAM_ENEMY" + "AbilityUnitTargetType" "DOTA_UNIT_TARGET_HERO | DOTA_UNIT_TARGET_BASIC" + "AbilityUnitTargetFlags" "DOTA_UNIT_TARGET_FLAG_MAGIC_IMMUNE_ENEMIES" + "SpellImmunityType" "SPELL_IMMUNITY_ENEMIES_YES" + + // Time + //------------------------------------------------------------------------------------------------------------- + "AbilityCooldown" "15" + "AbilityCastRange" "650" + "AbilityCastAnimation" "ACT_DOTA_CAST_ABILITY_2" + "AbilityValues" + { + "duration" + { + "value" "1.5" + } + "damage" + { + "value" "100" + } + "tick_interval" + { + "value" "0.1" + } + "health_threshold_pct" + { + "value" "100" // 100 disables + } + } + } + "frogmen_acid_jump" + { + "BaseClass" "ability_lua" + "ScriptFile" "abilities/creep/frogmen_acid_jump" + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_POINT" + "AbilityType" "ABILITY_TYPE_BASIC" + "AbilityUnitTargetTeam" "DOTA_UNIT_TARGET_TEAM_ENEMY" + "AbilityUnitTargetType" "DOTA_UNIT_TARGET_HERO | DOTA_UNIT_TARGET_BASIC" + "AbilityUnitDamageType" "DAMAGE_TYPE_PHYSICAL" + "AbilityTextureName" "frogmen_arm_of_the_deep" + "AbilityCastRange" "600" + "AbilityCooldown" "6" + "AbilityManaCost" "0" + "AbilityCastPoint" "0.2" + "AbilityValues" + { + "radius" "225" + "stun_duration" "1.0" + "land_damage" "80" + "duration" "0.45" + "precast_warning_time" "0.15" + } + } + "mini_frog_catchy_lick" + { + "BaseClass" "largo_catchy_lick" + "AbilityType" "ABILITY_TYPE_BASIC" + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_UNIT_TARGET" + "AbilityUnitTargetTeam" "DOTA_UNIT_TARGET_TEAM_FRIENDLY | DOTA_UNIT_TARGET_TEAM_ENEMY" + "AbilityUnitTargetType" "DOTA_UNIT_TARGET_HERO | DOTA_UNIT_TARGET_CREEP" + "AbilityUnitDamageType" "DAMAGE_TYPE_MAGICAL" + "SpellImmunityType" "SPELL_IMMUNITY_ENEMIES_NO" + "AbilitySound" "Hero_Largo.CatchyLick.Target.Layer1" + + // Casting + //------------------------------------------------------------------------------------------------------------- + "AbilityCastAnimation" "ACT_DOTA_CAST_ABILITY_1" + "AbilityCastPoint" "0.2" + + // Time + //------------------------------------------------------------------------------------------------------------- + "AbilityCooldown" "3" + + // Cost + //------------------------------------------------------------------------------------------------------------- + "AbilityManaCost" "05" + + // Special + //------------------------------------------------------------------------------------------------------------- + "AbilityValues" + { + + "AbilityCastRange" "700" + "damage" "175" + "pull_distance" "25" + + + "pull_distance_ally" + { + "value" "375" + "special_bonus_unique_largo_4" "+50" + } + "pull_duration" "0.15" + "strong_dispel" + { + "value" "0" + } + "dispel_hp_regen" "4 7 10 13" + + "buff_duration" "10" + "AbilityCharges" "0" + "precast_warning_time" "0.15" + + } + } + "frog_magi_wave" + { + "BaseClass" "kunkka_tidal_wave" + // General + //------------------------------------------------------------------------------------------------------------- + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_POINT | DOTA_ABILITY_BEHAVIOR_SHOW_IN_GUIDES" + "AbilityUnitTargetTeam" "DOTA_UNIT_TARGET_TEAM_ENEMY" + "AbilityUnitTargetType" "DOTA_UNIT_TARGET_HERO | DOTA_UNIT_TARGET_BASIC" + "AbilityUnitDamageType" "DAMAGE_TYPE_MAGICAL" + "SpellImmunityType" "SPELL_IMMUNITY_ENEMIES_NO" + "SpellDispellableType" "SPELL_DISPELLABLE_YES" + "MaxLevel" "1" + "FightRecapLevel" "1" + "IsGrantedByShard" "0" + + // Casting + //------------------------------------------------------------------------------------------------------------- + "AbilityCastRange" "600" // if changing this, change wave_distance_tooltip + "AbilityCastPoint" "0.2" + "AbilityCastAnimation" "ACT_DOTA_CAST_ABILITY_4" + + + // Time + //------------------------------------------------------------------------------------------------------------- + "AbilityCooldown" "12" + + // Cost + //------------------------------------------------------------------------------------------------------------- + "AbilityManaCost" "0" + + // Special + //------------------------------------------------------------------------------------------------------------- + "AbilityValues" + { + "speed" "1000" + "radius" + { + "value" "250" + "affected_by_aoe_increase" "1" + } + "damage" "480" + "duration" "1.0" + "wave_distance_tooltip" "600" + "knockback_distance" "300" + } + } + "frog_magi_torrent" + { + "BaseClass" "kunkka_torrent" + // General + //------------------------------------------------------------------------------------------------------------- + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_AOE | DOTA_ABILITY_BEHAVIOR_POINT | DOTA_ABILITY_BEHAVIOR_IGNORE_BACKSWING" + "AbilityUnitDamageType" "DAMAGE_TYPE_MAGICAL" + "SpellImmunityType" "SPELL_IMMUNITY_ENEMIES_NO" + "SpellDispellableType" "SPELL_DISPELLABLE_YES" + "FightRecapLevel" "1" + "AbilitySound" "Ability.Torrent" + + // Casting + //------------------------------------------------------------------------------------------------------------- + "AbilityCastRange" "1300" + "AbilityCastPoint" "0.4" + + // Time + //------------------------------------------------------------------------------------------------------------- + + + + // Cost + //------------------------------------------------------------------------------------------------------------- + "AbilityManaCost" "0" + + // Stats + //------------------------------------------------------------------------------------------------------------- + "AbilityModifierSupportValue" "0.5" // applies 2 modifiers + + // Special + //------------------------------------------------------------------------------------------------------------- + "AbilityValues" + { + "radius" "200" + + "movespeed_bonus" "-70" + "slow_duration" "3" + "stun_duration" "0" + + "delay" "0" + "torrent_damage" "600" + + "percent_instant" "0" + "damage_tick_interval" "0.2" + "AbilityCooldown" "7" + "precast_warning_time" "0.25" + + } + "AbilityCastAnimation" "ACT_DOTA_CAST_ABILITY_1" + } + "venomancer_poison" + { + "BaseClass" "venomancer_poison_sting" + "AbilityType" "ABILITY_TYPE_BASIC" + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_PASSIVE" + "AbilityUnitDamageType" "DAMAGE_TYPE_MAGICAL" + "SpellImmunityType" "SPELL_IMMUNITY_ENEMIES_NO" + "SpellDispellableType" "SPELL_DISPELLABLE_YES" + "IsBreakable" "1" + "AbilityValues" + { + "duration" "3" + "damage" "140" + + "movement_speed" "-14" + + "hp_regen_reduction" "30" + + } + "AbilityCastAnimation" "ACT_DOTA_CAST_ABILITY_2" + } + "venomous_gale" + { + "BaseClass" "venomancer_venomous_gale" + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_AOE | DOTA_ABILITY_BEHAVIOR_POINT" + "AbilityUnitDamageType" "DAMAGE_TYPE_MAGICAL" + "SpellImmunityType" "SPELL_IMMUNITY_ENEMIES_NO" + "SpellDispellableType" "SPELL_DISPELLABLE_YES" + "FightRecapLevel" "1" + "AbilitySound" "Hero_Venomancer.VenomousGale" + "AbilityCastAnimation" "ACT_DOTA_CAST_ABILITY_1" + "AbilityCastGestureSlot" "DEFAULT" + "AbilityCastRange" "800" + "AbilityCastPoint" "0.0 0.0 0.0 0.0" + "AbilityManaCost" "100" + "AbilityValues" + { + "duration" "15" + "strike_damage" "500" + "tick_damage" "50" + "tick_interval" "1.0" + "movement_slow" "-50" + "radius" "125" + + "speed" "1200" + "create_wards" "0" + + "num_created_wards_tooltip" "0" + + "ward_hp_dmg_pct" "0" + + "AbilityCooldown" "10" + + "explosion_damage" "300" + "explosion_stun_duration" "1.6" + "precast_warning_time" "0.1" + + } + } + "spider_bite" + { + "BaseClass" "broodmother_incapacitating_bite" + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_PASSIVE" + "SpellDispellableType" "SPELL_DISPELLABLE_YES" + "SpellImmunityType" "SPELL_IMMUNITY_ENEMIES_NO" + "IsBreakable" "1" + "AbilityValues" + { + "miss_chance" "40" + "bonus_movespeed" "-15 -20 -25 -30" + "duration" "2.0 2.0 2.0 2.0" + "attack_damage" "0" + + } + "AbilityCastAnimation" "ACT_DOTA_CAST_ABILITY_3" + } + "spider_hunger" + { + "BaseClass" "broodmother_insatiable_hunger" + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_NO_TARGET" + "AbilityUnitDamageType" "DAMAGE_TYPE_PHYSICAL" + "SpellDispellableType" "SPELL_DISPELLABLE_NO" + "FightRecapLevel" "2" + "HasShardUpgrade" "1" + "AbilityCastPoint" "0.2 0.2 0.2" + "AbilityCastAnimation" "ACT_DOTA_CAST_ABILITY_4" + "AbilityCooldown" "10" + "AbilityManaCost" "0" + "AbilityValues" + { + "bonus_damage" "500" + "lifesteal_pct" "500" + "duration" "8" + "bat_bonus" "0" + "shard_damage_per_tick" "0" + "shard_damage_tick_interval" "0" + "aura_radius" "0" + } + } + "back_stun" + { + "BaseClass" "ability_lua" + "ScriptFile" "abilities/creep/demon_dragon_satyr/back_stun" + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_NO_TARGET" + "AbilityTextureName" "centaur_hoof_stomp" + "MaxLevel" "1" + + "AbilityCooldown" "5" + "AbilityManaCost" "0" + "AbilityCastPoint" "1.87" + + "AbilityValues" + { + "cast_range" "600" + "duration" "2" + "damage" "300" + } + + "precache" + { + "particle" "particles/generic_gameplay/generic_stunned.vpcf" + "soundfile" "soundevents/game_sounds_heroes/game_sounds_centaur.vsndevts" + } + "AbilityCastAnimation" "ACT_DOTA_CAST_ABILITY_6" + } + "face_stun" + { + "BaseClass" "ability_lua" + "ScriptFile" "abilities/creep/demon_dragon_satyr/face_stun" + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_POINT" + "AbilityTextureName" "centaur_hoof_stomp" + "MaxLevel" "1" + + "AbilityCooldown" "5" + "AbilityManaCost" "0" + "AbilityCastPoint" "1.30" + + "AbilityValues" + { + "cast_range" "300" + "duration" "2" + "damage" "300" + } + + "precache" + { + "particle" "particles/generic_gameplay/generic_stunned.vpcf" + "soundfile" "soundevents/game_sounds_heroes/game_sounds_centaur.vsndevts" + } + "AbilityCastAnimation" "ACT_DOTA_LEAP_STUN" + } + "fear" + { + "BaseClass" "ability_lua" + "ScriptFile" "abilities/creep/demon_dragon_satyr/fear" + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_NO_TARGET" + "AbilityTextureName" "night_stalker_void" + "MaxLevel" "1" + + "AbilityCooldown" "25" + "AbilityManaCost" "0" + "AbilityCastPoint" "0.9" + + "AbilityValues" + { + "cast_range" "1200" + "duration" "2.0" + } + + "precache" + { + "particle" "particles/generic_gameplay/generic_feared.vpcf" + "soundfile" "soundevents/game_sounds_heroes/game_sounds_nightstalker.vsndevts" + } + "AbilityCastAnimation" "ACT_DOTA_AREA_DENY" + } + "ability_animation" + { + "BaseClass" "ability_lua" + "ScriptFile" "abilities/creep/demon_dragon_satyr/animation" + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_PASSIVE" + "AbilityTextureName" "centaur_return" + "MaxLevel" "1" + } + "ability_grab" + { + "BaseClass" "ability_lua" + "ScriptFile" "abilities/creep/demon_dragon_satyr/grab" + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_POINT | DOTA_ABILITY_BEHAVIOR_CHANNELLED" + "AbilityTextureName" "centaur_khan_war_stomp" + "MaxLevel" "1" + + "AbilityCooldown" "12" + "AbilityManaCost" "0" + "AbilityCastPoint" "0.67" + "AbilityChannelTime" "5.0" + + "AbilityValues" + { + "cast_range" "150" + "damage" "900" + } + + "precache" + { + "particle" "particles/generic_gameplay/generic_stunned.vpcf" + } + "AbilityCastAnimation" "ACT_DOTA_NIAN_PIN_START" + } + + +} \ No newline at end of file diff --git a/scripts/npc/creep_abilities/wave_creep_abilities.kv b/scripts/npc/creep_abilities/wave_creep_abilities.kv new file mode 100644 index 0000000..138efd3 --- /dev/null +++ b/scripts/npc/creep_abilities/wave_creep_abilities.kv @@ -0,0 +1,472 @@ +dotaa_abilities +{ + "zombie_virus" + { + "BaseClass" "ability_lua" + "ScriptFile" "abilities/creep/zombie_virus.lua" + "AbilityTextureName" "life_stealer_open_wounds" + "MaxLevel" "1" + "DamageType" "DAMAGE_TYPE_PHYSICAL" + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_PASSIVE" + + "AbilityValues" + { + + "damage" "1" + + "tick_interval" "1" + + "duration" "3" + "chance" "75" + "slow_movespeed" "15" + "armor" "1" + } + } + "zombie_armor_decress" + { + "BaseClass" "ability_lua" + "ScriptFile" "abilities/creep/zombie_armor_decress.lua" + "AbilityTextureName" "item_assault" + "MaxLevel" "1" + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_PASSIVE" + + "AbilityValues" + { + "corruption_duration" "20" + "armor_debuff" "-3" + } + } + "ghost_evasive" + { + "BaseClass" "ability_lua" + "ScriptFile" "abilities/creep/ghost_evasive.lua" + "AbilityTextureName" "item_assault" + "MaxLevel" "1" + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_PASSIVE" + + "AbilityValues" + { + "evasive" "33" + } + } + "wave_phasing_march" + { + "BaseClass" "ability_lua" + "ScriptFile" "abilities/creep/wave_phasing_march.lua" + "AbilityTextureName" "item_phase_boots" + "MaxLevel" "1" + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_PASSIVE" + + "AbilityValues" + { + "bonus_movement_speed" "100" + } + } + "toxin" + { + "BaseClass" "ability_lua" + "ScriptFile" "abilities/creep/toxin.lua" + "AbilityTextureName" "viper_nethertoxin" + "MaxLevel" "1" + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_PASSIVE" + "AbilityUnitDamageType" "DAMAGE_TYPE_PHYSICAL" + + "AbilityValues" + { + "damage" "120" + "radius" "150" + "duration" "3" + "armor_magic_reduce_per_stack" "0.5" + "merge_radius_bonus" + { + "value" "45" + } + "merge_duration_bonus" + { + "value" "1" + } + } + } + "contract_head_leap" + { + "BaseClass" "ability_lua" + "ScriptFile" "abilities/creep/contract_head_leap.lua" + "AbilityTextureName" "monkey_king_tree_dance" + "MaxLevel" "1" + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_PASSIVE" + "AbilityUnitDamageType" "DAMAGE_TYPE_PHYSICAL" + + "AbilityValues" + { + "search_radius" "550" + "leap_interval" "2.4" + "leap_damage" "35" + "damage_from_attack_pct" "25" + "jump_duration" "0.45" + "height_above_hero" "140" + "damage_radius" "220" + "precast_warning_time" "0.85" + } + } + "bone_armor" + { + "BaseClass" "ability_lua" + "ScriptFile" "abilities/creep/bone_armor.lua" + "AbilityTextureName" "skeleton_king_mortal_strike" + "MaxLevel" "1" + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_PASSIVE" + + "AbilityValues" + { + "armor_bonus" "12" + "damage_reflect_chance" "50" + "damage_reflect_pct" "25" + "hero_proximity_radius" "350" + "isolated_damage_multiplier" "1" + } + } + "skeleton_archer_fire_arrow" + { + "BaseClass" "ability_lua" + "ScriptFile" "abilities/creep/skeleton_archer.lua" + "AbilityTextureName" "clinkz_searing_arrows" + "MaxLevel" "1" + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_PASSIVE" + "AbilityUnitDamageType" "DAMAGE_TYPE_MAGICAL" + + "AbilityValues" + { + "bonus_damage" "75" + } + } + + "weaking_impetus" + { + "BaseClass" "ability_lua" + "ScriptFile" "abilities/creep/weaking_impetus.lua" + "AbilityTextureName" "enchantress_impetus" + "MaxLevel" "1" + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_PASSIVE" + "AbilityUnitDamageType" "DAMAGE_TYPE_MAGICAL" + "AbilityManaCost" "40" + "AbilityValues" + { + "damage_reduction" "5" + "debuff_duration" "5" + "mana_hit" "40" + "max_stacks" "10" + } + } + "zombie_rage" + { + "BaseClass" "life_stealer_rage" + // General + //------------------------------------------------------------------------------------------------------------- + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_NO_TARGET | DOTA_ABILITY_BEHAVIOR_IMMEDIATE" + "SpellDispellableType" "SPELL_DISPELLABLE_NO" + "AbilitySound" "Hero_LifeStealer.Rage" + + // Casting + //------------------------------------------------------------------------------------------------------------- + "AbilityCastPoint" "0" + "AbilityCastRange" "0" + + // Time + //------------------------------------------------------------------------------------------------------------- + "AbilityCooldown" "20" + + // Cost + //------------------------------------------------------------------------------------------------------------- + "AbilityManaCost" "0" + + // Special + //------------------------------------------------------------------------------------------------------------- + "AbilityValues" + { + + + "duration" "3.0" + + "magic_resist" + { + "value" "80" + } + "status_resist" + { + "value" "50" + } + "debuff_immunity" + { + "value" "1" + } + + "bonus_armor" + { + "value" "20" + } + } + "AbilityCastAnimation" "ACT_DOTA_CAST_ABILITY_1" + } + "zombie_feast" + { + "BaseClass" "life_stealer_feast" + // General + //------------------------------------------------------------------------------------------------------------- + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_PASSIVE | DOTA_ABILITY_BEHAVIOR_NOT_LEARNABLE" + "SpellImmunityType" "SPELL_IMMUNITY_ENEMIES_YES" + "SpellDispellableType" "SPELL_DISPELLABLE_NO" + "AbilityUnitDamageType" "DAMAGE_TYPE_PHYSICAL" + "MaxLevel" "4" + "Innate" "1" + "DependentOnAbility" "life_stealer_infest" + "IsBreakable" "1" + + // Special + //------------------------------------------------------------------------------------------------------------- + "AbilityValues" + { + "hp_leech_percent" "2.5" + + "hp_damage_percent" "2.5" + + "bonus_hp_per_hero" "10 10 10 10" + + "bonus_hp_per_creep" "1" + "creep_deny_percent" "75" + "bonus_hp_total" + { + "dynamic_value" "true" + } + } + "AbilityCastAnimation" "ACT_DOTA_CAST_ABILITY_2" + } + "zombie_open_wounds" + { + "BaseClass" "life_stealer_open_wounds" + // General + //------------------------------------------------------------------------------------------------------------- + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_UNIT_TARGET" + "AbilityUnitTargetTeam" "DOTA_UNIT_TARGET_TEAM_ENEMY" + "AbilityUnitTargetType" "DOTA_UNIT_TARGET_HERO | DOTA_UNIT_TARGET_BASIC" + "SpellImmunityType" "SPELL_IMMUNITY_ENEMIES_NO" + "SpellDispellableType" "SPELL_DISPELLABLE_YES" + "FightRecapLevel" "1" + "AbilitySound" "Hero_LifeStealer.OpenWounds.Cast" + + "MaxLevel" "4" + + // Casting + //------------------------------------------------------------------------------------------------------------- + "AbilityCastPoint" "0.2" + "AbilityCastRange" "300 400 500 600" + "AbilityCastAnimation" "ACT_DOTA_CAST_ABILITY_3" + + // Time + //------------------------------------------------------------------------------------------------------------- + "AbilityCooldown" "30 25 20 15" + + // Cost + //------------------------------------------------------------------------------------------------------------- + "AbilityManaCost" "100" + + // Special + //------------------------------------------------------------------------------------------------------------- + "AbilityValues" + { + "duration" "7" + "slow_steps" + { + "value" "-50 -50 -40 -30 -20 -10 -10 -10" + "special_bonus_unique_lifestealer_4" "-15" + } + "heal_percent" + { + "value" "20 30 40 50" + "special_bonus_unique_lifestealer_6" "+25" + } + "max_health_as_damage_pct" "0" + "spread_radius" + { + "value" "700" + "affected_by_aoe_increase" "1" + } + "slow_step_pct_of_max" "70 80 90 100" + "slow_tooltip" + { + "value" "35 40 45 50" + "special_bonus_unique_lifestealer_4" "+15" + } + "reset_cooldown_on_kill" + { + "value" "0" + "special_bonus_facet_life_stealer_fleshfeast" "+1" + } + + } + } + "skeleton_blast" + { + "BaseClass" "skeleton_king_hellfire_blast" + // General + //------------------------------------------------------------------------------------------------------------- + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_UNIT_TARGET" + "AbilityUnitTargetTeam" "DOTA_UNIT_TARGET_TEAM_ENEMY" + "AbilityUnitTargetType" "DOTA_UNIT_TARGET_HERO | DOTA_UNIT_TARGET_BASIC" + "AbilityUnitDamageType" "DAMAGE_TYPE_MAGICAL" + "SpellImmunityType" "SPELL_IMMUNITY_ENEMIES_NO" + "SpellDispellableType" "SPELL_DISPELLABLE_YES_STRONG" + "FightRecapLevel" "1" + "AbilitySound" "Hero_SkeletonKing.Hellfire_Blast" + + // Casting + //------------------------------------------------------------------------------------------------------------- + "AbilityCastRange" "525" + "AbilityCastPoint" "0.35 0.35 0.35 0.35" + + // Time + //------------------------------------------------------------------------------------------------------------- + "AbilityCooldown" "14 12 10 8" + + // Damage. + //------------------------------------------------------------------------------------------------------------- + + // Cost + //------------------------------------------------------------------------------------------------------------- + "AbilityManaCost" "95 110 125 140" + + // Stats + //------------------------------------------------------------------------------------------------------------- + "AbilityModifierSupportValue" "0.5" // Applies two modifiers + + // Special + //------------------------------------------------------------------------------------------------------------- + + "AbilityValues" + { + "blast_speed" "1200" + "blast_stun_duration" + { + "value" "1.0 1.2 1.4 1.6" + "special_bonus_unique_wraith_king_11" "+1" + } + + "blast_dot_duration" + { + "value" "2.0" + "special_bonus_facet_skeleton_king_facet_bone_guard" + { + "value" "+0" + "special_bonus_unique_wraith_king_facet_1" "+2" + } + } + + "blast_slow" "-20" + + "damage" + { + "value" "80 100 120 140" + + } + "blast_dot_damage" + { + "value" "20 40 60 80" + } + } + + "AbilityCastAnimation" "ACT_DOTA_CAST_ABILITY_1" + } + "skeleton_king_mortal_strike" + { + // General + //------------------------------------------------------------------------------------------------------------- + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_PASSIVE" + "AbilityUnitTargetTeam" "DOTA_UNIT_TARGET_TEAM_ENEMY" + "AbilityUnitTargetType" "DOTA_UNIT_TARGET_HERO" + "AbilitySound" "Hero_SkeletonKing.CriticalStrike" + "HasShardUpgrade" "1" + "IsBreakable" "1" + + // Special + //------------------------------------------------------------------------------------------------------------- + "AbilityValues" + { + "crit_mult" "200" + "wraith_cd_mult" + { + "value" "1" + } + "wraith_crit_bonus" "0" + + "AbilityCooldown" "4.5" + + } + + "AbilityCastAnimation" "ACT_DOTA_CAST_ABILITY_3" + } + "suicide_boys" + { + "BaseClass" "ability_lua" + "ScriptFile" "abilities/zombie/Boss/suicide_boys.lua" + "AbilityTextureName" "techies_suicide" + "MaxLevel" "1" + + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_NO_TARGET | DOTA_ABILITY_BEHAVIOR_IMMEDIATE" + "AbilityUnitDamageType" "DAMAGE_TYPE_PURE" + "AbilityCooldown" "8" + "AbilityManaCost" "0" + "AbilityCastPoint" "0.0" + "AbilityCastAnimation" "ACT_DOTA_CAST_ABILITY_1" + + "AbilityValues" + { + "radius" "2000" + "toss_damage" "0" + } + } + + "wave_full_brutality" + { + "BaseClass" "ability_lua" + "ScriptFile" "abilities/creep/wave_full_brutality.lua" + "AbilityTextureName" "ogre_magi_bloodlust" + "MaxLevel" "1" + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_PASSIVE" + + "AbilityValues" + { + "bonus_pct" "100" + } + } + + "wave_desperate_vampirism" + { + "BaseClass" "ability_lua" + "ScriptFile" "abilities/creep/wave_desperate_vampirism.lua" + "AbilityTextureName" "life_stealer_feast" + "MaxLevel" "1" + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_PASSIVE" + + "AbilityValues" + { + "hp_threshold_pct" "50" + "vamp_pct" "100" + } + } + + "zombie_death_explosion" + { + "BaseClass" "ability_lua" + "ScriptFile" "abilities/creep/zombie_death_explosion.lua" + "AbilityTextureName" "techies_remote_mines" + "MaxLevel" "1" + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_PASSIVE" + "AbilityUnitDamageType" "DAMAGE_TYPE_PHYSICAL" + + "AbilityValues" + { + "radius" "200" + "explosion_damage" "220" + } + } +} + diff --git a/scripts/npc/event_units/event_units.kv b/scripts/npc/event_units/event_units.kv new file mode 100644 index 0000000..163965e --- /dev/null +++ b/scripts/npc/event_units/event_units.kv @@ -0,0 +1,106 @@ +"DOTAAbilities" +{ + "npc_squirrel_event" + { + "BaseClass" "npc_dota_creature" + "Model" "models/items/courier/hamster_courier/hamster_courier_lv1.vmdl" + "HealthBarOffset" "140" + "HasInventory" "0" + "ModelScale" "1.25" + "ArmorPhysical" "0" + "MagicalResistance" "0" + + "AttackCapabilities" "DOTA_UNIT_CAP_NO_ATTACK" + + "BoundsHullName" "DOTA_HULL_SIZE_HERO" + "RingRadius" "125" + + "MovementCapabilities" "DOTA_UNIT_CAP_MOVE_GROUND" + "MovementSpeed" "0" + + "StatusHealth" "1" + "StatusHealthRegen" "0" + + "VisionDaytimeRange" "0" + "VisionNighttimeRange" "0" + + "Ability1" "" + "Ability2" "invul_npc" + + "TeamName" "DOTA_TEAM_GOODGUYS" + "UnitRelationshipClass" "DOTA_NPC_UNIT_RELATIONSHIP_TYPE_SIEGE" + "Creature" + { + + } + } + "npc_pig_event" + { + "BaseClass" "npc_dota_creature" + "Model" "models/items/courier/d2l_steambear/d2l_steambear.vmdl" + "HealthBarOffset" "140" + "HasInventory" "0" + "ModelScale" "1.25" + "ArmorPhysical" "0" + "MagicalResistance" "0" + + "AttackCapabilities" "DOTA_UNIT_CAP_NO_ATTACK" + + "BoundsHullName" "DOTA_HULL_SIZE_HERO" + "RingRadius" "125" + + "MovementCapabilities" "DOTA_UNIT_CAP_MOVE_GROUND" + "MovementSpeed" "0" + + "StatusHealth" "1" + "StatusHealthRegen" "0" + + "VisionDaytimeRange" "0" + "VisionNighttimeRange" "0" + + "Ability1" "" + "Ability2" "invul_npc" + + "TeamName" "DOTA_TEAM_GOODGUYS" + "UnitRelationshipClass" "DOTA_NPC_UNIT_RELATIONSHIP_TYPE_SIEGE" + "Creature" + { + + } + } + "npc_wolf_event" + { + "BaseClass" "npc_dota_creature" + "Model" "models/pets/icewrack_wolf_alt/icewrack_wolf_alt.vmdl" + "HealthBarOffset" "140" + "HasInventory" "0" + "ModelScale" "1.25" + "ArmorPhysical" "0" + "MagicalResistance" "0" + + "AttackCapabilities" "DOTA_UNIT_CAP_NO_ATTACK" + + "BoundsHullName" "DOTA_HULL_SIZE_HERO" + "RingRadius" "125" + + "MovementCapabilities" "DOTA_UNIT_CAP_MOVE_GROUND" + "MovementSpeed" "0" + + "StatusHealth" "1" + "StatusHealthRegen" "0" + + "VisionDaytimeRange" "0" + "VisionNighttimeRange" "0" + + "Ability1" "" + "Ability2" "invul_npc" + + "TeamName" "DOTA_TEAM_GOODGUYS" + "UnitRelationshipClass" "DOTA_NPC_UNIT_RELATIONSHIP_TYPE_SIEGE" + "Creature" + { + + } + } + +} \ No newline at end of file diff --git a/scripts/npc/heroes/axe/axe.kv b/scripts/npc/heroes/axe/axe.kv new file mode 100644 index 0000000..cd404fa --- /dev/null +++ b/scripts/npc/heroes/axe/axe.kv @@ -0,0 +1,215 @@ +"DOTAAbilities" +{ + //================================================================================================================= + // Axe — кастомные ключи (*_custom), иконки ванильные (AbilityTextureName) + //================================================================================================================= + "axe_berserkers_call_custom" + { + "BaseClass" "ability_lua" + "ScriptFile" "abilities/heroes/axe/axe_berserkers_call_custom" + "AbilityTextureName" "axe_berserkers_call" + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_NO_TARGET | DOTA_ABILITY_BEHAVIOR_IGNORE_BACKSWING" + "SpellImmunityType" "SPELL_IMMUNITY_ENEMIES_YES" + "SpellDispellableType" "SPELL_DISPELLABLE_NO" + "FightRecapLevel" "1" + "AbilitySound" "Hero_Axe.Berserkers_Call" + + "HasShardUpgrade" "1" + + "AbilityCastPoint" "0.3" + "AbilityCooldown" "17 15 13 11" + "AbilityManaCost" "25" + + "AbilityValues" + { + "radius" + { + "value" "315" + "special_bonus_unique_axe_5" "+85" + } + "bonus_armor" + { + "value" "10 15 20 25" + "special_bonus_unique_axe_7" "+10" + } + "duration" "5" + "shard_hunger_duration" "12" + } + "AbilityCastAnimation" "ACT_DOTA_OVERRIDE_ABILITY_1" + } + + "axe_battle_hunger_custom" + { + "BaseClass" "ability_lua" + "ScriptFile" "abilities/heroes/axe/axe_battle_hunger_custom" + "AbilityTextureName" "axe_battle_hunger" + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_UNIT_TARGET" + "AbilityUnitTargetTeam" "DOTA_UNIT_TARGET_TEAM_ENEMY" + "AbilityUnitTargetType" "DOTA_UNIT_TARGET_HERO | DOTA_UNIT_TARGET_BASIC" + "AbilityUnitDamageType" "DAMAGE_TYPE_PHYSICAL" + "SpellImmunityType" "SPELL_IMMUNITY_ENEMIES_NO" + "SpellDispellableType" "SPELL_DISPELLABLE_YES" + "FightRecapLevel" "1" + "AbilitySound" "Hero_Axe.Battle_Hunger" + "MaxLevel" "4" + + "AbilityCastRange" "750" + "AbilityCastPoint" "0.3" + "AbilityCooldown" "24 18 12 6" + "AbilityManaCost" "35" + + "AbilityValues" + { + "slow" "12" + "damage_per_second" + { + "value" "16 32 56 72" + "special_bonus_unique_axe_4" "+28" + } + "armor_mult" "100" + "speed_bonus" "8 10 12 14" + "speed_per_enemy_pct" "12" + "duration" "12" + } + "AbilityCastAnimation" "ACT_DOTA_OVERRIDE_ABILITY_2" + } + + "axe_counter_helix_custom" + { + "BaseClass" "ability_lua" + "ScriptFile" "abilities/heroes/axe/axe_counter_helix_custom" + "AbilityTextureName" "axe_counter_helix" + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_PASSIVE" + "AbilityUnitDamageType" "DAMAGE_TYPE_PURE" + "SpellImmunityType" "SPELL_IMMUNITY_ENEMIES_YES" + "MaxLevel" "4" + + "AbilityValues" + { + "damage" + { + "value" "55 90 125 160" + "special_bonus_unique_axe" "+160" + } + "radius" "275" + "trigger_chance" "14 18 22 26" + "duration" "0.3" + } + } + + "axe_culling_blade_custom" + { + "BaseClass" "ability_lua" + "ScriptFile" "abilities/heroes/axe/axe_culling_blade_custom" + "AbilityTextureName" "axe_culling_blade" + "AbilityType" "DOTA_ABILITY_TYPE_ULTIMATE" + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_POINT | DOTA_ABILITY_BEHAVIOR_AOE | DOTA_ABILITY_BEHAVIOR_IGNORE_BACKSWING" + "AbilityUnitDamageType" "DAMAGE_TYPE_PURE" + "SpellImmunityType" "SPELL_IMMUNITY_ENEMIES_YES" + "SpellDispellableType" "SPELL_DISPELLABLE_NO" + "FightRecapLevel" "2" + "AbilitySound" "Hero_Axe.Culling_Blade" + "MaxLevel" "3" + "RequiredLevel" "6" + "LevelsBetweenUpgrades" "6" + + "HasScepterUpgrade" "1" + + "AbilityCastRange" "400" + "AbilityCastPoint" "0.4" + "AbilityManaCost" "50" + "AbilityCooldown" "0" + + "AbilityValues" + { + "cull_radius" + { + "value" "175" + "affected_by_aoe_increase" "1" + "special_bonus_unique_axe_2" "+150" + } + "speed_bonus" "20 25 30" + "attack_speed_bonus" "20 25 30" + "speed_duration" + { + "value" "6" + "special_bonus_unique_axe_culling_blade_speed_duration" "+3" + } + "speed_aoe" "900" + "scepter_speed_duration" "12" + "armor_per_stack" "1" + "armor_per_creep_kill" "0.1" + } + "AbilityCastAnimation" "ACT_DOTA_CAST_ABILITY_4" + } + + "axe_one_man_army_custom" + { + "BaseClass" "ability_lua" + "ScriptFile" "abilities/heroes/axe/axe_one_man_army_custom" + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_PASSIVE | DOTA_ABILITY_BEHAVIOR_NOT_LEARNABLE" + "MaxLevel" "1" + "Innate" "1" + "IsBreakable" "1" + + "AbilityValues" + { + "radius" "700" + "armor_to_str" + { + "value" "0.5" + "special_bonus_unique_axe_8" "+0.2" + } + "linger_duration" "3" + } + } + + "special_bonus_unique_axe_culling_blade_speed_duration" + { + "BaseClass" "special_bonus_undefined" + "AbilityType" "ABILITY_TYPE_ATTRIBUTES" + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_PASSIVE" + } + + "special_bonus_unique_axe_8" + { + "BaseClass" "special_bonus_undefined" + "AbilityType" "ABILITY_TYPE_ATTRIBUTES" + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_PASSIVE" + } + + "special_bonus_unique_axe" + { + "BaseClass" "special_bonus_undefined" + "AbilityType" "ABILITY_TYPE_ATTRIBUTES" + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_PASSIVE" + } + + "special_bonus_unique_axe_7" + { + "BaseClass" "special_bonus_undefined" + "AbilityType" "ABILITY_TYPE_ATTRIBUTES" + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_PASSIVE" + } + + "special_bonus_unique_axe_4" + { + "BaseClass" "special_bonus_undefined" + "AbilityType" "ABILITY_TYPE_ATTRIBUTES" + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_PASSIVE" + } + + "special_bonus_unique_axe_2" + { + "BaseClass" "special_bonus_undefined" + "AbilityType" "ABILITY_TYPE_ATTRIBUTES" + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_PASSIVE" + } + + "special_bonus_unique_axe_5" + { + "BaseClass" "special_bonus_undefined" + "AbilityType" "ABILITY_TYPE_ATTRIBUTES" + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_PASSIVE" + } +} diff --git a/scripts/npc/heroes/bloodhunter/bloodhunter.kv b/scripts/npc/heroes/bloodhunter/bloodhunter.kv new file mode 100644 index 0000000..7384049 --- /dev/null +++ b/scripts/npc/heroes/bloodhunter/bloodhunter.kv @@ -0,0 +1,275 @@ +"DOTAAbilities" +{ + + "ability_bloodrage" + { + "BaseClass" "ability_lua" + "ScriptFile" "abilities/heroes/bloodhunter/ability_bloodrage.lua" + "AbilityTextureName" "old_heroes/bloodrage" + "FightRecapLevel" "1" + "MaxLevel" "4" + "RequiredLevel" "1" + + "HasScepterUpgrade" "1" + + "AbilityUnitDamageType" "DAMAGE_TYPE_PHYSICAL" + "AbilityType" "DOTA_ABILITY_TYPE_BASIC" + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_NO_TARGET | DOTA_ABILITY_BEHAVIOR_IMMEDIATE" + "AbilityHealthCost" "20 40 60 120" + "precache" + { + "particle" "particles/units/heroes/hero_centaur/centaur_shard_buff_strength_counter_stack.vpcf" + } + + "AbilityValues" + { + "AbilityCooldown" "40" + + "duration" + { + "value" "12" + "special_bonus_unique_blood_hunter_duration_blood" "+4" + } + "stack_damage" "4 6 8 10" + "stack_attackspeed" "4 6 8 10" + "bonus_damage" "20 40 60 80" + "bonus_attack_speed" "20 30 40 50" + "stack" + { + "value" "2" + "special_bonus_scepter" "+3" + } + "physical_vampirism" "16" + "stack_duration" "20" + } + + } + "ability_bloodstained_memory" + { + "BaseClass" "ability_lua" + "ScriptFile" "abilities/heroes/bloodhunter/ability_bloodstained_memory.lua" + "AbilityTextureName" "old_heroes/bloodstained_memory" + "FightRecapLevel" "1" + "MaxLevel" "4" + "RequiredLevel" "1" + "AbilityType" "DOTA_ABILITY_TYPE_BASIC" + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_PASSIVE" + + "AbilityValues" + { + "healthbonus" + { + "value" "2 3 4 5" + "special_bonus_unique_blood_hunter_bloodstained_health" "+2" + } + "movespeed" "5" + + } + + } + + "ability_illusion_of_blood" + { + // General + //------------------------------------------------------------------------------------------------------------- + "BaseClass" "ability_lua" + "ScriptFile" "abilities/heroes/bloodhunter/ability_illusion_of_blood.lua" + "AbilityTextureName" "old_heroes/illusions_of_blood" + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_NO_TARGET" + "AbilityType" "DOTA_ABILITY_TYPE_BASIC" + "MaxLevel" "4" + "AbilityUnitTargetTeam" "DOTA_UNIT_TARGET_TEAM_FRIENDLY" + "AbilityUnitTargetType" "DOTA_UNIT_TARGET_HERO" + "AbilityUnitTargetFlags" "DOTA_UNIT_TARGET_FLAG_NOT_CREEP_HERO" + "AbilityCastRange" "1200" + "HasScepterUpgrade" "0" + "AbilitySound" "Hero_ChaosKnight.Phantasm" + + // Casting + //------------------------------------------------------------------------------------------------------------- + "AbilityCastPoint" "0.4 0.4 0.4" + "AbilityCastAnimation" "ACT_DOTA_CAST_ABILITY_4" + + // Time + //------------------------------------------------------------------------------------------------------------- + "AbilityHealthCost" "0 15 30 45" + + + // Special + //------------------------------------------------------------------------------------------------------------- + "AbilityValues" + { + + "images_count" + { + "value" "2" + "special_bonus_unique_blood_hunter_blood_of_illusions_count" "+2" + + } + + "illusion_duration" "30" + + "outgoing_damage" "400 605 800 1000" + + "incoming_damage" + { + "value" "120 140 160 180" + "special_bonus_unique_blood_hunter_blood_of_illusions_incoming_damage" "-25" + + + } + "invuln_duration" "0.2" + + "vision_radius" "400" + + "magic_resistance" "75" + "blood" "15 30 45 60" + } + } + "ability_sacrifice_revenge" + { + // General + //------------------------------------------------------------------------------------------------------------- + "BaseClass" "ability_lua" + "ScriptFile" "abilities/heroes/bloodhunter/ability_sacrifice_revenge.lua" + "AbilityTextureName" "old_heroes/sacrifice_revenge" + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_NO_TARGET | DOTA_ABILITY_BEHAVIOR_IMMEDIATE" + "AbilityType" "ABILITY_TYPE_ULTIMATE" + "SpellDispellableType" "SPELL_DISPELLABLE_NO" + "MaxLevel" "3" + "AbilitySound" "Hero_Alchemist.ChemicalRage.Cast" + "HasShardUpgrade" "1" + "AbilityCooldown" "40" + // Casting + //------------------------------------------------------------------------------------------------------------- + "AbilityCastPoint" "0.0" + "AbilityCastAnimation" "ACT_INVALID" + + // Time + //------------------------------------------------------------------------------------------------------------- + "AbilityCooldown" "0" + + // Cost + //------------------------------------------------------------------------------------------------------------- + "AbilityHealthCost" "150" + + // Special + //------------------------------------------------------------------------------------------------------------- + "AbilityValues" + { + + "duration" + { + "value" "25" + "special_bonus_unique_blood_hunter_sacrifice_revenge_duration" "+15" + + } + + "transformation_time" "0.2" + + "base_attack_time" "1.35 1.3 1.25" + + "bonus_armor" "-1 -1.5 -2" + + "pure_damage" + { + "value" "1 2 3" + "special_bonus_shard" "6" + "special_bonus_unique_blood_hunter_pure_damage_bonus" "+10" + } + + + } + } + "ability_ring_of_corrosive" + { + // General + //------------------------------------------------------------------------------------------------------------- + "BaseClass" "ability_lua" + "ScriptFile" "abilities/heroes/bloodhunter/ability_ring_of_corrosive.lua" + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_POINT | DOTA_ABILITY_BEHAVIOR_AOE" + "AbilityCastAnimation" "ACT_DOTA_CAST_ABILITY_2" + + "AbilityTextureName" "old_heroes/ring_of_corrosive" + "AbilityUnitDamageType" "DAMAGE_TYPE_PURE" + "MaxLevel" "3" + "RequiredLevel" "10" + "LevelsBetweenUpgrades" "10" + "AbilityCooldown" "210" + "AbilityHealthCost" "666" + + "AbilityValues" + { + "duration" "8 9 10" + + "damage" "666" + "damage_mult" "300" + "radius" "600" + + "tick_rate" "0.25" + } + + } + "special_bonus_unique_blood_hunter_bloodstained_health" + { + // General + //------------------------------------------------------------------------------------------------------------- + "BaseClass" "special_bonus_undefined" + "AbilityType" "ABILITY_TYPE_ATTRIBUTES" + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_PASSIVE" + + + } + "special_bonus_unique_blood_hunter_duration_blood" + { + // General + //------------------------------------------------------------------------------------------------------------- + "BaseClass" "special_bonus_undefined" + "AbilityType" "ABILITY_TYPE_ATTRIBUTES" + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_PASSIVE" + + + } + "special_bonus_unique_blood_hunter_blood_of_illusions_count" + { + // General + //------------------------------------------------------------------------------------------------------------- + "BaseClass" "special_bonus_undefined" + "AbilityType" "ABILITY_TYPE_ATTRIBUTES" + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_PASSIVE" + + + } + "special_bonus_unique_blood_hunter_blood_of_illusions_incoming_damage" + { + // General + //------------------------------------------------------------------------------------------------------------- + "BaseClass" "special_bonus_undefined" + "AbilityType" "ABILITY_TYPE_ATTRIBUTES" + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_PASSIVE" + + + } + "special_bonus_unique_blood_hunter_pure_damage_bonus" + { + // General + //------------------------------------------------------------------------------------------------------------- + "BaseClass" "special_bonus_undefined" + "AbilityType" "ABILITY_TYPE_ATTRIBUTES" + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_PASSIVE" + + + } + "special_bonus_unique_blood_hunter_sacrifice_revenge_duration" + { + // General + //------------------------------------------------------------------------------------------------------------- + "BaseClass" "special_bonus_undefined" + "AbilityType" "ABILITY_TYPE_ATTRIBUTES" + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_PASSIVE" + + + } +} + + \ No newline at end of file diff --git a/scripts/npc/heroes/bristleback/bristleback.kv b/scripts/npc/heroes/bristleback/bristleback.kv new file mode 100644 index 0000000..64b7df6 --- /dev/null +++ b/scripts/npc/heroes/bristleback/bristleback.kv @@ -0,0 +1,219 @@ +"DOTAAbilities" +{ + "bristleback_viscous_nasal_goo_custom" + { + "BaseClass" "ability_lua" + "ScriptFile" "abilities/heroes/bristleback/bristleback_viscous_nasal_goo_custom" + "AbilityTextureName" "bristleback_viscous_nasal_goo" + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_UNIT_TARGET | DOTA_ABILITY_BEHAVIOR_AOE | DOTA_ABILITY_BEHAVIOR_IGNORE_BACKSWING" + "AbilityUnitTargetTeam" "DOTA_UNIT_TARGET_TEAM_ENEMY" + "AbilityUnitTargetType" "DOTA_UNIT_TARGET_HERO | DOTA_UNIT_TARGET_BASIC" + "SpellImmunityType" "SPELL_IMMUNITY_ENEMIES_NO" + "SpellDispellableType" "SPELL_DISPELLABLE_YES" + "FightRecapLevel" "1" + "AbilitySound" "Hero_Bristleback.ViscousGoo.Cast" + "AbilityCastPoint" "0.3" + "AbilityCooldown" "0" + "AbilityManaCost" "10" + + "AbilityValues" + { + "goo_speed" "1000" + "goo_duration" "5.0" + "base_armor_pct" "8 10 12 15" + "armor_per_stack_pct" "1.5 2 2.5 3" + "base_move_slow" "10" + "move_slow_per_stack" "3 6 9 12" + "radius" + { + "value" "200" + "special_bonus_unique_bristleback_custom_6" "+200" + "affected_by_aoe_increase" "1" + } + "goo_duration_creep" "10.0" + "AbilityCastRange" "650" + } + "AbilityCastAnimation" "ACT_DOTA_CAST_ABILITY_1" + } + "bristleback_quill_spray_custom" + { + "BaseClass" "ability_lua" + "ScriptFile" "abilities/heroes/bristleback/bristleback_quill_spray_custom" + "AbilityTextureName" "bristleback_quill_spray" + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_NO_TARGET | DOTA_ABILITY_BEHAVIOR_IMMEDIATE | DOTA_ABILITY_BEHAVIOR_AUTOCAST" + "AbilityUnitDamageType" "DAMAGE_TYPE_PHYSICAL" + "SpellImmunityType" "SPELL_IMMUNITY_ENEMIES_YES" + "SpellDispellableType" "SPELL_DISPELLABLE_NO" + "FightRecapLevel" "1" + "AbilitySound" "Hero_Bristleback.QuillSpray" + "HasScepterUpgrade" "1" + + "AbilityCastAnimation" "ACT_INVALID" + "AbilityCastPoint" "0.0 0.0 0.0 0.0" + "AbilityCooldown" "2.5" + "AbilityManaCost" "10" + "AbilityValues" + { + "radius" + { + "value" "700" + "affected_by_aoe_increase" "1" + } + "quill_base_damage" + { + "value" "12 16 20 24" + "special_bonus_unique_bristleback_custom_3" "+16" + "special_bonus_scepter" "+27.5" + } + "quill_stack_damage" + { + "value" "30.0 35.0 40.0" + "special_bonus_unique_bristleback_custom_5" "+40.0" + "special_bonus_scepter" "+7.5" + } + "quill_stack_duration" + { + "value" "15.0" + "special_bonus_scepter" "+17.5" + } + "attack_damage_bonus_pct" "25" + "max_damage" + { + "value" "1250" + "special_bonus_unique_bristleback_custom_3" "+750" + "CalculateSpellDamageTooltip" "1" + } + "projectile_speed" "2400" + } + } + + "bristleback_bristleback_custom" + { + "BaseClass" "ability_lua" + "ScriptFile" "abilities/heroes/bristleback/bristleback_bristleback_custom" + "AbilityTextureName" "bristleback_bristleback" + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_PASSIVE" + "AbilitySound" "Hero_Bristleback.Bristleback" + "HasScepterUpgrade" "1" + + "AbilityCastPoint" "0" + "IsBreakable" "1" + "AbilityValues" + { + "side_damage_reduction" + { + "value" "8 12 16 20" + } + "back_damage_reduction" + { + "value" "14 18 22 26" + } + "side_angle" "110" + "back_angle" "70" + "quill_release_threshold" "275 250 225 200" + "quill_release_interval" "0.1" + "quill_release_threshold_scepter1" "400" + "quill_release_threshold_scepter2" "600" + "quill_release_threshold_scepter3" "800" + "quill_release_threshold_scepter4" "1000" + + } + "AbilityCastAnimation" "ACT_DOTA_SPAWN" + } + "bristleback_hairball_custom" + { + "BaseClass" "ability_lua" + "ScriptFile" "abilities/heroes/bristleback/bristleback_hairball_custom" + "AbilityTextureName" "bristleback_hairball" + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_POINT | DOTA_ABILITY_BEHAVIOR_AOE | DOTA_ABILITY_BEHAVIOR_HIDDEN | DOTA_ABILITY_BEHAVIOR_SHOW_IN_GUIDES" + "AbilityUnitTargetTeam" "DOTA_UNIT_TARGET_TEAM_ENEMY" + "AbilityUnitTargetType" "DOTA_UNIT_TARGET_HERO | DOTA_UNIT_TARGET_BASIC" + "SpellImmunityType" "SPELL_IMMUNITY_ALLIES_YES" + "SpellDispellableType" "SPELL_DISPELLABLE_YES" + "MaxLevel" "1" + "FightRecapLevel" "1" + "IsGrantedByShard" "1" + "AbilityCastRange" "750" + "AbilityCastPoint" "0.1" + "AbilityCastAnimation" "ACT_DOTA_CAST_ABILITY_4" + "AbilityCooldown" "13" + "AbilityManaCost" "60" + + "AbilityValues" + { + "projectile_speed" "1200" + "radius" + { + "value" "700" + "affected_by_aoe_increase" "1" + } + "quill_stacks" "3" + "goo_stacks" "2" + } + } + "bristleback_warpath" + { + "BaseClass" "ability_lua" + "ScriptFile" "abilities/heroes/bristleback/bristleback_warpath_custom" + "AbilityTextureName" "bristleback_warpath" + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_PASSIVE | DOTA_ABILITY_BEHAVIOR_HIDDEN | DOTA_ABILITY_BEHAVIOR_NOT_LEARNABLE" + "SpellDispellableType" "SPELL_DISPELLABLE_NO" + "AbilityCastAnimation" "ACT_DOTA_CAST_ABILITY_5" + "IsBreakable" "1" + "Innate" "1" + "MaxLevel" "1" + + "AbilityValues" + { + "damage_per_stack_base" "20" + "damage_per_stack_per_hero_level" "0.69" + "damage_per_stack" + { + "value" "0" + "special_bonus_unique_bristleback_3" "+25" + } + "move_speed_per_stack_base" "2" + "move_speed_per_stack_per_hero_level" "0.034" + "max_stacks_base" "8" + "max_stacks_per_hero_level" "0.14" + "stack_duration_base" "16" + "stack_duration_per_hero_level" "0.14" + } + } + "bristleback_rage_fortitude_custom" + { + "BaseClass" "ability_lua" + "ScriptFile" "abilities/heroes/bristleback/bristleback_rage_fortitude_custom" + "AbilityTextureName" "bristleback_warpath" + "AbilityType" "ABILITY_TYPE_ULTIMATE" + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_PASSIVE" + "SpellDispellableType" "SPELL_DISPELLABLE_NO" + "IsBreakable" "1" + "AbilityCastAnimation" "ACT_DOTA_CAST_ABILITY_6" + + "AbilityValues" + { + "rage_threshold" "50" + "bonus_armor" "15 30 45" + "bonus_magic_resist" "15 20 25" + } + } + "special_bonus_unique_bristleback_custom_3" + { + "AbilityType" "ABILITY_TYPE_ATTRIBUTES" + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_PASSIVE" + "BaseClass" "special_bonus_base" + } + "special_bonus_unique_bristleback_custom_5" + { + "AbilityType" "ABILITY_TYPE_ATTRIBUTES" + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_PASSIVE" + "BaseClass" "special_bonus_base" + } + "special_bonus_unique_bristleback_custom_6" + { + "AbilityType" "ABILITY_TYPE_ATTRIBUTES" + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_PASSIVE" + "BaseClass" "special_bonus_base" + } +} diff --git a/scripts/npc/heroes/crystal_maiden/crystal_maiden.kv b/scripts/npc/heroes/crystal_maiden/crystal_maiden.kv new file mode 100644 index 0000000..036b039 --- /dev/null +++ b/scripts/npc/heroes/crystal_maiden/crystal_maiden.kv @@ -0,0 +1,259 @@ +"DOTAAbilities" +{ + "ability_crystal_maiden_crystal_nova_custom" + { + "BaseClass" "ability_lua" + "ScriptFile" "abilities/heroes/crystal_maiden/ability_crystal_maiden_crystal_nova_custom" + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_POINT | DOTA_ABILITY_BEHAVIOR_AOE" + "AbilityUnitDamageType" "DAMAGE_TYPE_MAGICAL" + "AbilityTextureName" "crystal_maiden_crystal_nova" + "SpellImmunityType" "SPELL_IMMUNITY_ENEMIES_NO" + "SpellDispellableType" "SPELL_DISPELLABLE_YES" + "FightRecapLevel" "1" + "AbilitySound" "Hero_Crystal.CrystalNova" + "MaxLevel" "4" + + "AbilityCastRange" "700" + "AbilityCastPoint" "0.3" + "AbilityCooldown" "14 11 8 5" + "AbilityManaCost" "55 70 95 115" + + "AbilityValues" + { + "radius" + { + "value" "425" + "affected_by_aoe_increase" "1" + } + "damage" + { + "value" "125 175 225 275" + } + "heal" + { + "value" "100 150 200 250" + } + "intellect_per_damage" + { + "value" "0.5" + + } + + "slow_duration" "1" + "frost_stacks_per_level" "11 18 25 32" + "max_shield_pct" "70" + "shield_duration" "10" + + } + "AbilityCastAnimation" "ACT_DOTA_CAST_ABILITY_1" + } + "ability_crystal_maiden_frostbite_custom" + { + "BaseClass" "ability_lua" + "ScriptFile" "abilities/heroes/crystal_maiden/ability_crystal_maiden_frostbite_custom" + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_POINT | DOTA_ABILITY_BEHAVIOR_AOE | DOTA_ABILITY_BEHAVIOR_ALT_CASTABLE" + "AbilityUnitDamageType" "DAMAGE_TYPE_MAGICAL" + "AbilityTextureName" "crystal_maiden_frostbite" + "AbilityAlternativeTextureName" "crystal_maiden_frostbite" + "SpellImmunityType" "SPELL_IMMUNITY_ENEMIES_NO" + "SpellDispellableType" "SPELL_DISPELLABLE_YES" + "FightRecapLevel" "1" + "AbilitySound" "Hero_Crystal.Frostbite" + "MaxLevel" "4" + "HasShardUpgrade" "1" + + "AbilityCastRange" "500 600 700 800" + "AbilityCastPoint" "0.3" + "AbilityCooldown" "8 7 6 5" + "AbilityManaCost" "70 140 210 280" + + "AbilityValues" + { + "cast_range" + { + "value" "500 600 700 800" + } + "radius" + { + "value" "250 275 300 325" + "affected_by_aoe_increase" "1" + } + "duration" + { + "value" "3.0 3.5 4.0 4.5" + } + "damage" + { + "value" "50 100 150 200" + } + "heal_per_second" + { + "value" "50 75 100 125" + } + "frost_stacks_per_level" "5 7 9 11" + "execute_threshold_pct" "30" + "explosion_radius" "200" + "damage_per_mana" "2.5 5 7.5 10" + "incoming_damage_pct" + { + "value" "5 7 9 11" + "special_bonus_shard" "9" + } + } + "AbilityCastAnimation" "ACT_DOTA_CAST_ABILITY_2" + } + "ability_crystal_maiden_brilliance_aura_custom" + { + "BaseClass" "ability_lua" + "ScriptFile" "abilities/heroes/crystal_maiden/ability_crystal_maiden_brilliance_aura_custom" + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_POINT | DOTA_ABILITY_BEHAVIOR_IMMEDIATE | DOTA_ABILITY_BEHAVIOR_ALT_CASTABLE" + "AbilityTextureName" "crystal_maiden_brilliance_aura" + "SpellImmunityType" "SPELL_IMMUNITY_ALLIES_YES" + "FightRecapLevel" "1" + "AbilitySound" "Hero_Crystal.Brilliance" + "MaxLevel" "4" + + "AbilityCastPoint" "0.0" + "AbilityCooldown" "6" + + + "AbilityValues" + { + "mana_cost_pct" + { + "value" "15 20 25 30" + } + "aura_radius" + { + "value" "1200" + } + "active_radius" + { + "value" "450" + "affected_by_aoe_increase" "1" + } + "duration" + { + "value" "6.0" + } + "damage" + { + "value" "150 200 250 300" + } + "frost_stacks_per_level" + { + "value" "2 4 6 8" + } + "intellect_per_damage" + { + "value" "0.5" + } + "slow_duration" + { + "value" "1.0" + } + "mana_regen_pct" + { + "value" "0.5 1.0 1.5 2.0" + } + "teleport_range" + { + "value" "300" + } + "crystal_aspect_enabled" "1" + "crystal_explosion_radius" "200" + "crystal_damage" "75" + "crystal_frost_stacks" "2" + "crystal_duration" "15" + "max_crystals" "5" + "pct_25_mana_damage" "25" + } + "AbilityCastAnimation" "ACT_DOTA_CAST_ABILITY_3" + } + "ability_crystal_maiden_freezing_field_custom" + { + "BaseClass" "ability_lua" + "ScriptFile" "abilities/heroes/crystal_maiden/ability_crystal_maiden_freezing_field_custom" + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_NO_TARGET | DOTA_ABILITY_BEHAVIOR_CHANNELLED" + "AbilityType" "ABILITY_TYPE_ULTIMATE" + "AbilityUnitDamageType" "DAMAGE_TYPE_MAGICAL" + "AbilityTextureName" "crystal_maiden_freezing_field" + "SpellImmunityType" "SPELL_IMMUNITY_ENEMIES_NO" + "SpellDispellableType" "SPELL_DISPELLABLE_YES" + "FightRecapLevel" "1" + "AbilitySound" "Hero_Crystal.FreezingField" + "MaxLevel" "3" + "HasScepterUpgrade" "1" + "AbilityCastPoint" "0.0" + "AbilityChannelTime" "10.0" + "AbilityCooldown" "90 80 70" + "AbilityManaCost" "200 300 400" + + "AbilityValues" + { + "radius" + { + "value" "835" + "affected_by_aoe_increase" "1" + } + "damage_per_second" + { + "value" "105 170 250" + } + "explosion_interval" + { + "value" "0.1" + } + "explosion_radius" + { + "value" "400" + "affected_by_aoe_increase" "1" + } + "explosion_damage" + { + "value" "50 80 110" + } + "frost_stacks_per_explosion" + { + "value" "3 5 7" + } + "slow_duration" + { + "value" "1.0" + } + "intellect_per_damage" + { + "value" "0.5" + } + "mini_nova_pct" + { + "value" "50" + } + } + "AbilityCastAnimation" "ACT_DOTA_CAST_ABILITY_4" + } + //================================================================================================================= + // Crystal Maiden: Innate mana regen multiplier + //================================================================================================================= + "crystal_maiden_blueheart_floe" + { + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_PASSIVE | DOTA_ABILITY_BEHAVIOR_HIDDEN | DOTA_ABILITY_BEHAVIOR_SKIP_FOR_KEYBINDS | DOTA_ABILITY_BEHAVIOR_INNATE_UI" + "AbilityTextureName" "crystal_maiden_let_it_go" + + "MaxLevel" "4" + "Innate" "1" + "DependentOnAbility" "ability_crystal_maiden_freezing_field_custom" + "IsBreakable" "1" + + + "AbilityValues" + { + "mana_regen_multiplier" + { + "value" "25 50 75 100" + } + } + } + + +} diff --git a/scripts/npc/heroes/drow_ranger/drow_ranger.kv b/scripts/npc/heroes/drow_ranger/drow_ranger.kv new file mode 100644 index 0000000..eaf217b --- /dev/null +++ b/scripts/npc/heroes/drow_ranger/drow_ranger.kv @@ -0,0 +1,192 @@ +"DOTAAbilities" +{ + "ability_drow_ranger_frost_arrows_custom" + { + "BaseClass" "ability_lua" + "ScriptFile" "abilities/heroes/drow_ranger/ability_drow_ranger_frost_arrows_custom.lua" + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_PASSIVE" + "AbilityUnitDamageType" "DAMAGE_TYPE_PHYSICAL" + "AbilityTextureName" "drow_ranger_frost_arrows" + "HasScepterUpgrade" "1" + "MaxLevel" "4" + + + + "AbilityValues" + { + "damage_pct" + { + "value" "12 24 36 48" + "special_bonus_unique_drow_ranger_frost_arrow_damage_pct" "+12" + } + "frost_stacks_per_level" "1 2 3 4" + "AbilityCooldown" "0" + + "AbilityManaCost" "0" + + "crit_multiplier" "0" + "ricochet_chance" "4 8 12 16" + } + } + "ability_drow_ranger_gust_custom" + { + "BaseClass" "ability_lua" + "ScriptFile" "abilities/heroes/drow_ranger/ability_drow_ranger_gust_custom.lua" + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_POINT" + "AbilityUnitDamageType" "DAMAGE_TYPE_MAGICAL" + "AbilityTextureName" "drow_ranger_wave_of_silence" + "MaxLevel" "4" + + "AbilityCastRange" "900" + "AbilityCastPoint" "0.25" + "AbilityCooldown" "10 8 6 4" + "AbilityManaCost" "90 100 110 120" + + "AbilityValues" + { + "gust_speed" "1400" + "gust_width" "250" + "gust_distance" "900" + "knockback_distance" "90" + "knockback_duration" "0.2" + "damage" "180 240 300 360" + + "frost_stacks_per_level" + { + "value" "12 18 24 30" + "special_bonus_unique_drow_ranger_gust_frozen_stack" "+12" + } + "frozen_son_duration" "1.25" + } + } + "ability_drow_ranger_multishot_custom" + { + "BaseClass" "ability_lua" + "ScriptFile" "abilities/heroes/drow_ranger/ability_drow_ranger_multishot_custom.lua" + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_POINT | DOTA_ABILITY_BEHAVIOR_DIRECTIONAL | DOTA_ABILITY_BEHAVIOR_CHANNELLED" + "AbilityUnitDamageType" "DAMAGE_TYPE_PHYSICAL" + "AbilityTextureName" "drow_ranger_multishot" + "MaxLevel" "4" + + "AbilityCastRange" "1200" + "AbilityCastPoint" "0.0" + "AbilityChannelTime" "1.75" + "AbilityManaCost" "80 100 120 140" + + "AbilityValues" + { + "wave_count" "3" + "piercing_arrows" "1" + + "arrow_count_per_wave" + { + "value" "4" + "special_bonus_unique_drow_ranger_arrow_count_per_wave" "+1" + } + + + "arrow_damage_pct" + { + "value" "45 75 105 135" + "special_bonus_unique_drow_ranger_arrow_damage_pct" "+45" + } + "arrow_width" "90" + "arrow_speed" "1300" + "arrow_range_multiplier" "1.75" + "arrow_angle" "50" + "bypass_block" "1" + "AbilityCooldown" + { + "value" "26 22 18 14" + "special_bonus_unique_drow_ranger_AbilityCooldown" "-6" + } + "frost_stacks_per_level" "1 2 3 4" + } + "AbilityCastAnimation" "ACT_DOTA_CHANNEL_ABILITY_3" + } + "ability_drow_ranger_marksmanship_custom" + { + "BaseClass" "ability_lua" + "ScriptFile" "abilities/heroes/drow_ranger/ability_drow_ranger_marksmanship_custom.lua" + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_PASSIVE" + "AbilityUnitDamageType" "DAMAGE_TYPE_PHYSICAL" + "AbilityType" "ABILITY_TYPE_ULTIMATE" + "AbilityTextureName" "drow_ranger_marksmanship" + "MaxLevel" "3" + + "AbilityValues" + { + "bonus_agility_pct" "25 45 65" + "bonus_damage" "120 200 280" + "disable_range" "250" + "hits" "3" + "marksmanship_master" "1" + } + } + + "drow_ranger_trueshot" + { + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_PASSIVE | DOTA_ABILITY_BEHAVIOR_INNATE_UI | DOTA_ABILITY_BEHAVIOR_NOT_LEARNABLE | DOTA_ABILITY_BEHAVIOR_SKIP_FOR_KEYBINDS " + "Innate" "1" + "MaxLevel" "4" + "DependentOnAbility" "ability_drow_ranger_marksmanship_custom" + "IsBreakable" "1" + + "AbilityValues" + { + "radius" + { + "value" "1200" + "affected_by_aoe_increase" "1" + } + "trueshot_agi_bonus_base" "8 12 16 20" + "trueshot_agi_bonus_per_level" "2.5" + "trueshot_agi_bonus_allies_pct" "50" + "agi_bonus_pct_tooltip" + { + "dynamic_value" "true" + } + + } + } + + "special_bonus_unique_drow_ranger_AbilityCooldown" + { + "BaseClass" "special_bonus_undefined" + "AbilityType" "ABILITY_TYPE_ATTRIBUTES" + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_PASSIVE" + } + "special_bonus_unique_drow_ranger_arrow_count_per_wave" + { + "BaseClass" "special_bonus_undefined" + "AbilityType" "ABILITY_TYPE_ATTRIBUTES" + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_PASSIVE" + } + "special_bonus_unique_drow_ranger_arrow_damage_pct" + { + // General + //------------------------------------------------------------------------------------------------------------- + "BaseClass" "special_bonus_undefined" + "AbilityType" "ABILITY_TYPE_ATTRIBUTES" + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_PASSIVE" + + } + "special_bonus_unique_drow_ranger_gust_frozen_stack" + { + // General + //------------------------------------------------------------------------------------------------------------- + "BaseClass" "special_bonus_undefined" + "AbilityType" "ABILITY_TYPE_ATTRIBUTES" + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_PASSIVE" + + } + "special_bonus_unique_drow_ranger_frost_arrow_damage_pct" + { + // General + //------------------------------------------------------------------------------------------------------------- + "BaseClass" "special_bonus_undefined" + "AbilityType" "ABILITY_TYPE_ATTRIBUTES" + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_PASSIVE" + + } +} \ No newline at end of file diff --git a/scripts/npc/heroes/hoodwink/hoodwink.kv b/scripts/npc/heroes/hoodwink/hoodwink.kv new file mode 100644 index 0000000..d71d109 --- /dev/null +++ b/scripts/npc/heroes/hoodwink/hoodwink.kv @@ -0,0 +1,292 @@ +"DOTAAbilities" +{ + "hoodwink_acorn_shot_custom" + { + "BaseClass" "ability_lua" + "ScriptFile" "abilities/heroes/hoodwink/hoodwink_acorn_shot_custom" + "AbilityTextureName" "hoodwink_acorn_shot" + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_UNIT_TARGET | DOTA_ABILITY_BEHAVIOR_POINT | DOTA_ABILITY_BEHAVIOR_IGNORE_BACKSWING" + "AbilityUnitDamageType" "DAMAGE_TYPE_PHYSICAL" + "AbilityUnitTargetTeam" "DOTA_UNIT_TARGET_TEAM_ENEMY" + "AbilityUnitTargetType" "DOTA_UNIT_TARGET_HERO | DOTA_UNIT_TARGET_CREEP" + "SpellImmunityType" "SPELL_IMMUNITY_ENEMIES_NO" + "AbilityCooldown" "16 14 12 10" + "AbilityCastRange" "575" + "AbilityCastPoint" "0.2" + "AbilityManaCost" "85 90 95 100" + "HasScepterUpgrade" "1" + "AbilityCastAnimation" "ACT_DOTA_CAST_ABILITY_1" + "AbilityValues" + { + "acorn_shot_damage" + { + "value" "80 130 180 230" + "CalculateSpellDamageTooltip" "0" + } + "base_damage_pct" + { + "value" "100 110 120 130" + "CalculateSpellDamageTooltip" "0" + } + "bounce_count" + { + "value" "5 6 7 8" + "special_bonus_unique_hoodwink_acorn_shot_bounces" "+2" + } + "bounce_range" + { + "value" "525" + "affected_by_aoe_increase" "1" + } + "tree_duration" "20" + "tree_radius" "300" + "debuff_duration" "0.3" + "slow" "100" + "bounce_delay" "0.1" + "projectile_speed" "2200" + "can_bounce_off_of_trees" + { + "value" "0" + "special_bonus_unique_hoodwink_1_1" "+1" + } + "bounce_count_scepter" + { + "value" "0" + "special_bonus_scepter" "+3" + } + "scepter_chance" + { + "value" "0" + "special_bonus_scepter" "+15" + } + "reduce_cooldown_ultimate" + { + "value" "0" + "special_bonus_unique_hoodwink_4_1" "+1" + } + } + } + + "hoodwink_bushwhack_custom" + { + "BaseClass" "ability_lua" + "ScriptFile" "abilities/heroes/hoodwink/hoodwink_bushwhack_custom" + "HasShardUpgrade" "1" + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_POINT | DOTA_ABILITY_BEHAVIOR_AOE" + "AbilityUnitTargetType" "DOTA_UNIT_TARGET_HERO | DOTA_UNIT_TARGET_CREEP" + "AbilityUnitDamageType" "DAMAGE_TYPE_MAGICAL" + "SpellDispellableType" "SPELL_DISPELLABLE_YES_STRONG" + "AbilityTextureName" "hoodwink_bushwhack" + "AbilityCastRange" "1000" + "AbilityCastPoint" "0.2" + "AbilityCastAnimation" "ACT_DOTA_CAST_ABILITY_2" + "AbilityCooldown" "14" + "AbilityManaCost" "90 100 110 120" + "AbilityValues" + { + "radius" + { + "value" "265" + "affected_by_aoe_increase" "1" + } + "duration" "1.4 1.6 1.8 2.0" + "projectile_speed" "1300" + "interval" "0.3" + "damage_multiplier" "25" + "total_damage" + { + "value" "125 250 375 500" + "CalculateSpellDamageTooltip" "1" + "special_bonus_unique_hoodwink_bushwhack_damage" "+240" + } + } + } + + "hoodwink_scurry_custom" + { + "BaseClass" "ability_lua" + "ScriptFile" "abilities/heroes/hoodwink/hoodwink_scurry_custom" + "AbilityTextureName" "hoodwink_scurry" + "AbilityManaCost" "35" + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_NO_TARGET | DOTA_ABILITY_BEHAVIOR_IMMEDIATE" + "AbilityValues" + { + "movement_speed_pct" "20 25 30 35" + "duration" + { + "value" "2.0 2.5 3.0 3.5" + "special_bonus_unique_hoodwink_scurry_duration" "+1" + } + "radius" + { + "value" "225" + "affected_by_aoe_increase" "1" + } + "AbilityCharges" + { + "value" "1 2 3 4" + "special_bonus_unique_hoodwink_scurry_charges" "+1" + } + "AbilityChargeRestoreTime" + { + "value" "24 20 16 12" + } + "bonus_attack_speed" + { + "value" "15 28 46 64" + "special_bonus_unique_hoodwink_3_1" "+50%" + } + "bonus_damage" + { + "value" "20 45 65 80" + "special_bonus_unique_hoodwink_3_1" "+50%" + } + "luck_evasion_multiplier" "1" + "max_luck_evasion" "75" + "incoming_damage_reduction_pct" + { + "value" "4 5 6 7" + "special_bonus_unique_hoodwink_3_1" "+50%" + } + "current_aoe_bonus" + { + "dynamic_value" "true" + } + } + } + + "hoodwink_lucky_innate_custom" + { + "BaseClass" "ability_lua" + "ScriptFile" "abilities/heroes/hoodwink/hoodwink_lucky_innate_custom" + "AbilityTextureName" "hoodwink_scurry" + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_PASSIVE | DOTA_ABILITY_BEHAVIOR_HIDDEN | DOTA_ABILITY_BEHAVIOR_NOT_LEARNABLE" + "MaxLevel" "1" + "Innate" "1" + "IsBreakable" "0" + "AbilityValues" + { + "luck_per_level" "0.25" + } + } + + "hoodwink_sharpshooter_custom" + { + "BaseClass" "ability_lua" + "ScriptFile" "abilities/heroes/hoodwink/hoodwink_sharpshooter_custom" + "AbilityTextureName" "hoodwink_sharpshooter" + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_POINT | DOTA_ABILITY_BEHAVIOR_IGNORE_BACKSWING" + "AbilityUnitDamageType" "DAMAGE_TYPE_MAGICAL" + "SpellImmunityType" "SPELL_IMMUNITY_ENEMIES_NO" + "SpellDispellableType" "SPELL_DISPELLABLE_YES" + "AbilityType" "ABILITY_TYPE_ULTIMATE" + "AbilityCastRange" "3000" + "AbilityCastPoint" "0.0" + "AbilityCooldown" "25" + "AbilityManaCost" "100 150 200" + "AbilityCastAnimation" "ACT_DOTA_CHANNEL_ABILITY_6" + "AbilityValues" + { + "arrow_speed" + { + "value" "2200" + } + "arrow_width" + { + "value" "125" + "affected_by_aoe_increase" "1" + } + "arrow_range" "3000" + "arrow_vision" + { + "value" "350" + "affected_by_aoe_increase" "1" + } + "max_charge_time" "3.0" + "damage_multiplier" "200 300 400" + "max_damage" + { + "value" "600 950 1300" + "special_bonus_unique_hoodwink_sharpshooter_damage" "+900" + } + "recoil_distance" "350" + "recoil_height" "75" + "recoil_duration" "0.4" + "max_slow_debuff_duration" "5.0" + "misfire_time" "5.0" + "slow_move_pct" "30 40 50" + "turn_rate" "60" + "base_power" "0.2" + } + } + + "hoodwink_sharpshooter_release_custom" + { + "BaseClass" "ability_lua" + "ScriptFile" "abilities/heroes/hoodwink/hoodwink_sharpshooter_custom" + "AbilityTextureName" "hoodwink_sharpshooter_release" + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_IMMEDIATE | DOTA_ABILITY_BEHAVIOR_NO_TARGET | DOTA_ABILITY_BEHAVIOR_IGNORE_BACKSWING | DOTA_ABILITY_BEHAVIOR_NOT_LEARNABLE | DOTA_ABILITY_BEHAVIOR_HIDDEN | DOTA_ABILITY_BEHAVIOR_DONT_RESUME_ATTACK" + "AbilityType" "ABILITY_TYPE_ULTIMATE" + "AbilityCastPoint" "0.0" + "AbilityCastRange" "999999" + "AbilityCastAnimation" "ACT_DOTA_CAST_ABILITY_7" + "MaxLevel" "3" + "DependentOnAbility" "hoodwink_sharpshooter_custom" + } + + "special_bonus_unique_hoodwink_1_1" + { + "AbilityType" "ABILITY_TYPE_ATTRIBUTES" + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_PASSIVE" + "BaseClass" "special_bonus_base" + } + + "special_bonus_unique_hoodwink_3_1" + { + "AbilityType" "ABILITY_TYPE_ATTRIBUTES" + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_PASSIVE" + "BaseClass" "special_bonus_base" + } + + "special_bonus_unique_hoodwink_4_1" + { + "AbilityType" "ABILITY_TYPE_ATTRIBUTES" + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_PASSIVE" + "BaseClass" "special_bonus_base" + } + + "special_bonus_unique_hoodwink_scurry_charges" + { + "AbilityType" "ABILITY_TYPE_ATTRIBUTES" + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_PASSIVE" + "BaseClass" "special_bonus_base" + } + + "special_bonus_unique_hoodwink_bushwhack_damage" + { + "AbilityType" "ABILITY_TYPE_ATTRIBUTES" + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_PASSIVE" + "BaseClass" "special_bonus_base" + } + + "special_bonus_unique_hoodwink_acorn_shot_bounces" + { + "AbilityType" "ABILITY_TYPE_ATTRIBUTES" + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_PASSIVE" + "BaseClass" "special_bonus_base" + } + + "special_bonus_unique_hoodwink_sharpshooter_damage" + { + "AbilityType" "ABILITY_TYPE_ATTRIBUTES" + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_PASSIVE" + "BaseClass" "special_bonus_base" + } + + "special_bonus_unique_hoodwink_scurry_duration" + { + "AbilityType" "ABILITY_TYPE_ATTRIBUTES" + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_PASSIVE" + "BaseClass" "special_bonus_base" + } +} diff --git a/scripts/npc/heroes/juggernaut/juggernaut.kv b/scripts/npc/heroes/juggernaut/juggernaut.kv new file mode 100644 index 0000000..c202518 --- /dev/null +++ b/scripts/npc/heroes/juggernaut/juggernaut.kv @@ -0,0 +1,188 @@ +"DOTAAbilities" +{ + "ability_juggernaut_blade_fury_custom" + { + "BaseClass" "ability_lua" + "ScriptFile" "abilities/heroes/juggernaut/ability_juggernaut_blade_fury_custom.lua" + "AbilityTextureName" "juggernaut_blade_fury" + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_POINT | DOTA_ABILITY_BEHAVIOR_IMMEDIATE | DOTA_ABILITY_BEHAVIOR_ALT_CASTABLE" + "AbilityUnitDamageType" "DAMAGE_TYPE_MAGICAL" + "HasShardUpgrade" "1" + "AbilityCastPoint" "0.0" + "AbilityCooldown" "20" + "AbilityManaCost" "60" + + "AbilityValues" + { + "AbilityCastRange" "600" + "duration" + { + "value" "5" + "special_bonus_shard" "+2" + } + "radius" + { + "value" "250" + "special_bonus_unique_juggernaut_blade_fury_radius_custom" "+125" + } + "damage_per_tick" + { + "value" "50 70 90 110" + "special_bonus_shard" "+110" + } + "attack_damage" "25" + "proc_chance" "15" + "leap_sword" "1" + } + "AbilityCastAnimation" "ACT_DOTA_CAST_ABILITY_1" + } + "ability_juggernaut_healing_ward_custom" + { + "BaseClass" "ability_lua" + "ScriptFile" "abilities/heroes/juggernaut/ability_juggernaut_healing_ward_custom.lua" + "AbilityTextureName" "juggernaut_healing_ward" + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_POINT" + + "AbilityCastPoint" "0.3" + "AbilityCooldown" "40 35 30 25" + "AbilityManaCost" "30" + + "AbilityValues" + { + "ward_duration" + { + "value" "10.0" + "special_bonus_unique_juggernaut_healing_ward_duration_custom" "+15" + } + "heal_radius" "500" + "heal_per_second" "2 3 4 5" + } + "AbilityCastAnimation" "ACT_DOTA_CAST_ABILITY_2" + } + "ability_juggernaut_blade_dance_custom" + { + "BaseClass" "ability_lua" + "ScriptFile" "abilities/heroes/juggernaut/ability_juggernaut_blade_dance_custom.lua" + "AbilityTextureName" "new_heroes/juggernaut_blade_dance_custom_alt" + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_POINT | DOTA_ABILITY_BEHAVIOR_ALT_CASTABLE" + "AbilityCastRange" "800" + "AbilityCastPoint" "0.0" + "AbilityCooldown" "0" + "AbilityManaCost" "15" + + + + "AbilityValues" + { + "AbilityCharges" "1 2 3 4" + "AbilityChargeRestoreTime" "19 14 11 7" + "lifesteal_percent" + { + "value" "0" + "special_bonus_unique_juggernaut_blade_dance_lifesteal_custom" "20" + } + "crit_chance" "35" + "crit_mult" "145 175 205 225" + "juggernaut_blade_dance_jugg_step" "1" + "astral_slash_attack_count" "1 2 3 4" + "min_travel_distance" "200" + "max_travel_distance" "800" + "astral_slash_radius" "100" + "astral_slash_debuff_duration" "4.5" + "juggernaut_blade_dance_jugg_step_armor_reduce" "10" + } + + } + "ability_juggernaut_omnislash_custom" + { + "BaseClass" "ability_lua" + "ScriptFile" "abilities/heroes/juggernaut/ability_juggernaut_omnislash_custom.lua" + "AbilityTextureName" "juggernaut_omni_slash" + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_UNIT_TARGET" + "AbilityUnitTargetTeam" "DOTA_UNIT_TARGET_TEAM_ENEMY" + "AbilityUnitTargetType" "DOTA_UNIT_TARGET_HERO | DOTA_UNIT_TARGET_BASIC" + "AbilityType" "ABILITY_TYPE_ULTIMATE" + "MaxLevel" "3" + + "AbilityCastPoint" "0.3" + "AbilityCooldown" "80 60 40" + "AbilityManaCost" "25 50 75" + "AbilityCastRange" "600" + + "AbilityValues" + { + "duration" "1.5 2 2.5" + "slash_interval_mult" "1.4" + "bounce_radius" "425" + "damage" "200 250 300" + } + "AbilityCastAnimation" "ACT_DOTA_CAST_ABILITY_4" + } + "ability_juggernaut_miniomnislash_custom" + { + "BaseClass" "ability_lua" + "ScriptFile" "abilities/heroes/juggernaut/ability_juggernaut_omnislash_custom.lua" + "AbilityTextureName" "juggernaut_swift_slash" + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_UNIT_TARGET" + "AbilityUnitTargetTeam" "DOTA_UNIT_TARGET_TEAM_ENEMY" + "AbilityUnitTargetType" "DOTA_UNIT_TARGET_HERO | DOTA_UNIT_TARGET_BASIC" + "AbilityType" "ABILITY_TYPE_ULTIMATE" + "MaxLevel" "1" + "IsGrantedByScepter" "1" + "AbilityCastPoint" "0.3" + "AbilityCooldown" "40" + "AbilityManaCost" "25" + "AbilityCastRange" "600" + + "AbilityValues" + { + "duration" "1" + "slash_interval_mult" "2.8" + "bounce_radius" "425" + "damage" "300" + } + "AbilityCastAnimation" "ACT_DOTA_CAST_ABILITY_4" + } + + "ability_juggernaut_samurai_soul" + { + "BaseClass" "ability_lua" + "ScriptFile" "abilities/heroes/juggernaut/ability_juggernaut_samurai_soul.lua" + "AbilityTextureName" "new_heroes/juggernaut_miracle" + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_PASSIVE" + "MaxLevel" "1" + "Innate" "1" + "IsBreakable" "0" + "AbilityValues" + { + "cooldown" "15.0" + "max_restore_count" "2" + "debuff_pct" "50" + } + } + "special_bonus_unique_juggernaut_healing_ward_duration_custom" + { + "BaseClass" "special_bonus_undefined" + "AbilityType" "ABILITY_TYPE_ATTRIBUTES" + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_PASSIVE" + } + "special_bonus_unique_juggernaut_blade_dance_lifesteal_custom" + { + "BaseClass" "special_bonus_undefined" + "AbilityType" "ABILITY_TYPE_ATTRIBUTES" + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_PASSIVE" + } + "special_bonus_unique_juggernaut_blade_fury_radius_custom" + { + "BaseClass" "special_bonus_undefined" + "AbilityType" "ABILITY_TYPE_ATTRIBUTES" + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_PASSIVE" + } + "special_bonus_facet_juggernaut_blade_dance_astral_slash" + { + "BaseClass" "special_bonus_undefined" + "AbilityType" "ABILITY_TYPE_ATTRIBUTES" + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_PASSIVE" + } + +} diff --git a/scripts/npc/heroes/keeper_of_the_light/keeper_of_the_light.kv b/scripts/npc/heroes/keeper_of_the_light/keeper_of_the_light.kv new file mode 100644 index 0000000..b7a92f2 --- /dev/null +++ b/scripts/npc/heroes/keeper_of_the_light/keeper_of_the_light.kv @@ -0,0 +1,273 @@ +"DOTAAbilities" +{ + "keeper_of_the_light_illuminate_custom" + { + "BaseClass" "ability_lua" + "ScriptFile" "abilities/heroes/keeper_of_the_light/keeper_of_the_light_illuminate_custom" + "AbilityTextureName" "keeper_of_the_light_illuminate" + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_POINT | DOTA_ABILITY_BEHAVIOR_CHANNELLED" + "AbilityUnitDamageType" "DAMAGE_TYPE_MAGICAL" + "SpellImmunityType" "SPELL_IMMUNITY_ENEMIES_NO" + "FightRecapLevel" "1" + "AbilitySound" "Hero_KeeperOfTheLight.Illuminate.Discharge" + "LinkedAbility" "keeper_of_the_light_illuminate_end" + "AbilityCastRange" "1800" + "AbilityCastPoint" "0" + "AbilityChannelTime" "3" + "AbilityManaCost" "100 125 150 175" + "AbilityValues" + { + "max_damage" + { + "value" "200 315 430 550" + "special_bonus_unique_keeper_of_the_light" "+300" + "CalculateSpellDamageTooltip" "1" + } + "min_damage" + { + "value" "75 150 225 300" + "special_bonus_unique_keeper_of_the_light" "+150" + "CalculateSpellDamageTooltip" "1" + } + "heal_percent" + { + "value" "35" + "special_bonus_unique_keeper_of_the_light_1_1" "+20" + } + "mana_damage_from_current_pct" "50" + "radius" + { + "value" "400" + "affected_by_aoe_increase" "1" + } + "speed" "900" + "vision_radius" "800" + "AbilityCooldown" + { + "value" "13" + "special_bonus_unique_keeper_of_the_light_illuminate_cooldown" "-2" + } + } + "AbilityCastAnimation" "ACT_DOTA_CAST_ABILITY_1" + } + + "keeper_of_the_light_illuminate_end" + { + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_NO_TARGET | DOTA_ABILITY_BEHAVIOR_NOT_LEARNABLE | DOTA_ABILITY_BEHAVIOR_HIDDEN | DOTA_ABILITY_BEHAVIOR_DONT_RESUME_ATTACK" + "LinkedAbility" "keeper_of_the_light_illuminate_custom" + "AbilityCastPoint" "0.0 0.0 0.0 0.0" + "AbilityCastAnimation" "ACT_INVALID" + } + + "keeper_of_the_light_blinding_light_custom" + { + "BaseClass" "ability_lua" + "ScriptFile" "abilities/heroes/keeper_of_the_light/keeper_of_the_light_blinding_light_custom" + "AbilityTextureName" "keeper_of_the_light_blinding_light" + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_AOE | DOTA_ABILITY_BEHAVIOR_POINT" + "AbilityUnitDamageType" "DAMAGE_TYPE_MAGICAL" + "SpellImmunityType" "SPELL_IMMUNITY_ENEMIES_NO" + "SpellDispellableType" "SPELL_DISPELLABLE_YES" + "AbilityCastRange" "400 500 600 700" + "AbilityCastPoint" "0.3" + "AbilityCastAnimation" "ACT_DOTA_CAST_ABILITY_5" + "AbilityManaCost" "120 130 140 150" + "AbilityValues" + { + "duration_stack" "6" + "radius" + { + "value" "425 450 475 500" + "affected_by_aoe_increase" "1" + } + "knockback_duration" "0.6" + "knockback_distance" + { + "value" "400" + "affected_by_aoe_increase" "1" + } + "miss_stack" "2" + "miss_stack_boss" "1" + "miss_pct" "100" + "damage" + { + "value" "100 200 300 400" + "CalculateSpellDamageTooltip" "1" + } + "AbilityCooldown" + { + "value" "24 21 18 15" + "special_bonus_unique_keeper_of_the_light_5" "-5" + } + } + } + + "keeper_of_the_light_chakra_magic_custom" + { + "BaseClass" "ability_lua" + "ScriptFile" "abilities/heroes/keeper_of_the_light/keeper_of_the_light_chakra_magic_custom" + "AbilityTextureName" "keeper_of_the_light_chakra_magic" + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_UNIT_TARGET" + "AbilityUnitTargetTeam" "DOTA_UNIT_TARGET_TEAM_FRIENDLY" + "AbilityUnitTargetType" "DOTA_UNIT_TARGET_HERO | DOTA_UNIT_TARGET_BASIC" + "SpellImmunityType" "SPELL_IMMUNITY_ALLIES_YES" + "AbilityCastRange" "900" + "AbilityCastPoint" "0.3" + "AbilityManaCost" "0" + "SpellDispellableType" "SPELL_DISPELLABLE_YES" + "AbilityValues" + { + "mana_restore" "100 200 300 400" + "cooldown_reduction" "3 4 5 6" + "incoming_damage_reduction_pct" "12 16 20 24" + "damage_reduction_duration" "4 5 6 7" + "AbilityCooldown" + { + "value" "18 16 14 12" + "special_bonus_unique_keeper_of_the_light_7" "-2" + } + "strong_dispel" + { + "special_bonus_unique_keeper_of_the_light_14" "=1" + } + "refresh_charges" + { + "special_bonus_unique_keeper_of_the_light_3_1" "=1" + } + } + } + + "keeper_of_the_light_will_o_wisp_custom" + { + "BaseClass" "ability_lua" + "ScriptFile" "abilities/heroes/keeper_of_the_light/keeper_of_the_light_will_o_wisp_custom" + "AbilityTextureName" "keeper_of_the_light_will_o_wisp" + "AbilityType" "ABILITY_TYPE_ULTIMATE" + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_POINT | DOTA_ABILITY_BEHAVIOR_AOE" + "AbilityUnitTargetTeam" "DOTA_UNIT_TARGET_TEAM_ENEMY" + "AbilityUnitTargetType" "DOTA_UNIT_TARGET_HERO" + "AbilityUnitDamageType" "DAMAGE_TYPE_MAGICAL" + "SpellImmunityType" "SPELL_IMMUNITY_ENEMIES_NO" + "SpellDispellableType" "SPELL_DISPELLABLE_YES" + "AbilitySound" "Hero_KeeperOfTheLight.ManaLeak.Cast" + "AbilityCastPoint" "0.15" + "AbilityCharges" "3" + "AbilityCooldown" "0" + "AbilityChargeRestoreTime" "16 14 12" + "AbilityManaCost" "60 100 150" + "AbilityCastRange" "800" + "AbilityValues" + { + "radius" + { + "value" "350" + "special_bonus_unique_keeper_of_the_light_4_1" "+125" + "affected_by_aoe_increase" "1" + } + "active_duration" "1.2" + "active_tick" "0.2" + "delay" "1.2" + "damage" + { + "value" "200 400 600" + "CalculateSpellDamageTooltip" "1" + } + "slow_movespeed" "-60" + "hit_count" + { + "value" "4" + "special_bonus_unique_keeper_of_the_light_4_2" "+2" + } + } + "AbilityCastAnimation" "ACT_DOTA_CAST_ABILITY_2" + } + + "keeper_of_the_light_recall_custom" + { + "BaseClass" "ability_lua" + "ScriptFile" "abilities/heroes/keeper_of_the_light/keeper_of_the_light_recall_custom" + "AbilityTextureName" "keeper_of_the_light_recall" + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_UNIT_TARGET | DOTA_ABILITY_BEHAVIOR_DONT_RESUME_ATTACK | DOTA_ABILITY_BEHAVIOR_HIDDEN | DOTA_ABILITY_BEHAVIOR_NOT_LEARNABLE | DOTA_ABILITY_BEHAVIOR_AUTOCAST" + "AbilityUnitTargetTeam" "DOTA_UNIT_TARGET_TEAM_FRIENDLY" + "AbilityUnitTargetType" "DOTA_UNIT_TARGET_CUSTOM" + "AbilityUnitTargetFlags" "DOTA_UNIT_TARGET_FLAG_INVULNERABLE" + "SpellImmunityType" "SPELL_IMMUNITY_ALLIES_YES" + "SpellDispellableType" "SPELL_DISPELLABLE_NO" + "MaxLevel" "1" + "IsGrantedByShard" "1" + "AbilitySound" "Hero_KeeperOfTheLight.Recall.Cast" + "AbilityCastPoint" "0.3 0.3 0.3" + "AbilityCastAnimation" "ACT_DOTA_CAST_ABILITY_4" + "AbilityCastRange" "0" + "AbilityManaCost" "150" + "AbilityValues" + { + "teleport_delay" + { + "value" "4" + } + "movespeed_bonus_duration" "4" + "ally_movespeed_pct" + { + "value" "25" + } + "AbilityCooldown" + { + "value" "15" + } + } + } + + "keeper_of_the_light_radiant_bind_custom" + { + "BaseClass" "ability_lua" + "ScriptFile" "abilities/heroes/keeper_of_the_light/keeper_of_the_light_radiant_bind_custom" + "AbilityTextureName" "keeper_of_the_light_radiant_bind" + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_UNIT_TARGET | DOTA_ABILITY_BEHAVIOR_HIDDEN" + "AbilityUnitTargetTeam" "DOTA_UNIT_TARGET_TEAM_BOTH" + "AbilityUnitTargetType" "DOTA_UNIT_TARGET_HERO | DOTA_UNIT_TARGET_BASIC" + "SpellImmunityType" "SPELL_IMMUNITY_ENEMIES_NO" + "SpellDispellableType" "SPELL_DISPELLABLE_YES" + "AbilitySound" "Hero_KeeperOfTheLight.ManaLeak.Cast" + "MaxLevel" "1" + "IsGrantedByScepter" "1" + "AbilityCastPoint" "0.2" + "AbilityManaCost" "120" + "AbilityCastRange" "850" + "AbilityValues" + { + "duration" "5" + "slow_pct_per_distance" "8" + "distance_for_slow" "100" + "movespeed_limit" "700" + "magres_pct" "30" + "AbilityCooldown" "14" + } + "AbilityCastAnimation" "ACT_DOTA_CAST_ABILITY_2" + } + + "special_bonus_unique_keeper_of_the_light_4_1" + { + "AbilityType" "ABILITY_TYPE_ATTRIBUTES" + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_PASSIVE" + "BaseClass" "special_bonus_base" + } + "special_bonus_unique_keeper_of_the_light_1_1" + { + "AbilityType" "ABILITY_TYPE_ATTRIBUTES" + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_PASSIVE" + "BaseClass" "special_bonus_base" + } + "special_bonus_unique_keeper_of_the_light_3_1" + { + "AbilityType" "ABILITY_TYPE_ATTRIBUTES" + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_PASSIVE" + "BaseClass" "special_bonus_base" + } + "special_bonus_unique_keeper_of_the_light_4_2" + { + "AbilityType" "ABILITY_TYPE_ATTRIBUTES" + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_PASSIVE" + "BaseClass" "special_bonus_base" + } +} diff --git a/scripts/npc/heroes/legion_commander/legion_commander.kv b/scripts/npc/heroes/legion_commander/legion_commander.kv new file mode 100644 index 0000000..adfddd1 --- /dev/null +++ b/scripts/npc/heroes/legion_commander/legion_commander.kv @@ -0,0 +1,209 @@ +// Legion Commander — кастомный набор (логика в TS; стилистика ближе к dota1x6 / ваниле) +"DOTAAbilities" +{ + "ability_legion_commander_overwhelming_odds_custom" + { + "BaseClass" "ability_lua" + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_POINT | DOTA_ABILITY_BEHAVIOR_AOE | DOTA_ABILITY_BEHAVIOR_IGNORE_BACKSWING" + "AbilityUnitDamageType" "DAMAGE_TYPE_MAGICAL" + "SpellImmunityType" "SPELL_IMMUNITY_ENEMIES_NO" + "AbilityTextureName" "legion_commander_overwhelming_odds" + "ScriptFile" "abilities/heroes/legion_commander/ability_legion_commander_overwhelming_odds_custom.lua" + "MaxLevel" "4" + "AbilityCastRange" "625" + "AbilityCastPoint" "0.300000" + "AbilityManaCost" "30" + "AbilityCooldown" "13 12 11 10" + "AbilityCastAnimation" "ACT_DOTA_CAST_ABILITY_1" + + "AbilityValues" + { + "radius" + { + "value" "330" + "special_bonus_unique_legion_commander_odds_radius" "+70" + "affected_by_aoe_increase" "1" + } + "damage_base" + { + "value" "125 145 165 185" + "special_bonus_unique_legion_commander_odds_damage" "+60" + } + "damage_per_enemy" "18 22 26 30" + "armor_buff_duration" "7" + "armor_per_enemy" "2 3 4 5" + "debuff_duration" "5" + "enemy_movespeed_slow" "40 45 50 55" + "self_buff_duration" "5" + "self_bonus_attack_speed" "30 40 50 60" + "self_bonus_movespeed" "15 20 25 30" + } + } + + "ability_legion_commander_press_the_attack_custom" + { + "BaseClass" "ability_lua" + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_UNIT_TARGET" + "AbilityUnitTargetTeam" "DOTA_UNIT_TARGET_TEAM_FRIENDLY" + "AbilityUnitTargetType" "DOTA_UNIT_TARGET_HERO" + "AbilityTextureName" "legion_commander_press_the_attack" + "ScriptFile" "abilities/heroes/legion_commander/ability_legion_commander_press_the_attack_custom.lua" + "MaxLevel" "4" + "AbilityCastRange" "750" + "AbilityCastPoint" "0.225000" + "AbilityManaCost" "45" + "AbilityCooldown" "16 14 12 10" + "AbilityCastAnimation" "ACT_DOTA_CAST_ABILITY_2" + + "AbilityValues" + { + "duration" + { + "value" "4 5 6 7" + "special_bonus_unique_legion_commander_pta_duration" "+2" + } + "attack_speed_bonus" "65 95 125 155" + "movespeed_bonus" "20 35 45 55" + "hp_regen" + { + "value" "12 16 20 24" + "special_bonus_unique_legion_commander_pta_regen" "+12" + } + } + } + + "ability_legion_commander_moment_of_courage_custom" + { + "BaseClass" "ability_lua" + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_PASSIVE" + "AbilityTextureName" "legion_commander_moment_of_courage" + "ScriptFile" "abilities/heroes/legion_commander/ability_legion_commander_moment_of_courage_custom.lua" + "MaxLevel" "4" + + "AbilityValues" + { + "trigger_chance_pct" + { + "value" "25 30 35 40" + "special_bonus_unique_legion_commander_moc_chance" "+10" + } + "proc_cooldown" "0.35" + "buff_duration" "1.2" + "buff_bonus_movespeed" "40 55 65 85" + "counter_lifesteal_pct" + { + "value" "40 50 60 70" + "special_bonus_unique_legion_commander_moc_lifesteal" "+25" + } + "attack_count" "1" + } + } + + "ability_legion_commander_duel_custom" + { + "BaseClass" "ability_lua" + "AbilityType" "ABILITY_TYPE_ULTIMATE" + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_UNIT_TARGET" + "AbilityUnitTargetTeam" "DOTA_UNIT_TARGET_TEAM_ENEMY" + "AbilityUnitTargetType" "DOTA_UNIT_TARGET_HERO | DOTA_UNIT_TARGET_BASIC" + "AbilityUnitTargetFlags" "DOTA_UNIT_TARGET_FLAG_MAGIC_IMMUNE_ENEMIES" + "SpellImmunityType" "SPELL_IMMUNITY_ENEMIES_YES" + "AbilityTextureName" "legion_commander_duel" + "ScriptFile" "abilities/heroes/legion_commander/ability_legion_commander_duel_custom.lua" + "MaxLevel" "3" + "RequiredLevel" "6" + "LevelsBetweenUpgrades" "6" + "AbilityCastRange" "575" + "AbilityCastPoint" "0.300000" + "AbilityManaCost" "50" + "AbilityCastAnimation" "ACT_DOTA_CAST_ABILITY_4" + "HasScepterUpgrade" "1" + "HasShardUpgrade" "1" + + "AbilityValues" + { + "duration" + { + "value" "4 4.75 5.5" + "special_bonus_unique_legion_commander_duel_duration" "+2" + } + "victory_range" "1500" + "reward_damage" + { + "value" "10 18 26" + "special_bonus_unique_legion_commander_duel_reward" "+10" + } + "reward_damage_creep" "3 5 7" + "reward_random_stat" + { + "value" "1" + "special_bonus_scepter" "+4" + } + "max_stack_damage" "40000" + "bonus_attack_speed" "25 45 65" + "AbilityChargeRestoreTime" + { + "value" "75 70 65" + "special_bonus_scepter" "-10" + } + "AbilityCharges" + { + "value" "1" + "special_bonus_scepter" "2" + } + + "scepter_cooldown_reduction" "10" + "scepter_magic_resist" "25" + "shard_overwhelming_interval" "2" + } + } + + "special_bonus_unique_legion_commander_odds_radius" + { + "BaseClass" "special_bonus_undefined" + "AbilityType" "ABILITY_TYPE_ATTRIBUTES" + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_PASSIVE" + } + "special_bonus_unique_legion_commander_odds_damage" + { + "BaseClass" "special_bonus_undefined" + "AbilityType" "ABILITY_TYPE_ATTRIBUTES" + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_PASSIVE" + } + "special_bonus_unique_legion_commander_pta_duration" + { + "BaseClass" "special_bonus_undefined" + "AbilityType" "ABILITY_TYPE_ATTRIBUTES" + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_PASSIVE" + } + "special_bonus_unique_legion_commander_pta_regen" + { + "BaseClass" "special_bonus_undefined" + "AbilityType" "ABILITY_TYPE_ATTRIBUTES" + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_PASSIVE" + } + "special_bonus_unique_legion_commander_moc_chance" + { + "BaseClass" "special_bonus_undefined" + "AbilityType" "ABILITY_TYPE_ATTRIBUTES" + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_PASSIVE" + } + "special_bonus_unique_legion_commander_moc_lifesteal" + { + "BaseClass" "special_bonus_undefined" + "AbilityType" "ABILITY_TYPE_ATTRIBUTES" + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_PASSIVE" + } + "special_bonus_unique_legion_commander_duel_duration" + { + "BaseClass" "special_bonus_undefined" + "AbilityType" "ABILITY_TYPE_ATTRIBUTES" + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_PASSIVE" + } + "special_bonus_unique_legion_commander_duel_reward" + { + "BaseClass" "special_bonus_undefined" + "AbilityType" "ABILITY_TYPE_ATTRIBUTES" + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_PASSIVE" + } +} diff --git a/scripts/npc/heroes/lina/lina.kv b/scripts/npc/heroes/lina/lina.kv new file mode 100644 index 0000000..4afac6f --- /dev/null +++ b/scripts/npc/heroes/lina/lina.kv @@ -0,0 +1,229 @@ +"DOTAAbilities" +{ + "ability_lina_dragon_slave_custom" + { + "BaseClass" "ability_lua" + "ScriptFile" "abilities/heroes/lina/ability_lina_dragon_slave_custom" + "AbilityTextureName" "lina_dragon_slave" + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_UNIT_TARGET | DOTA_ABILITY_BEHAVIOR_POINT" + "AbilityUnitTargetTeam" "DOTA_UNIT_TARGET_TEAM_ENEMY" + "AbilityUnitTargetType" "DOTA_UNIT_TARGET_HERO | DOTA_UNIT_TARGET_BASIC" + "AbilityUnitDamageType" "DAMAGE_TYPE_MAGICAL" + "SpellImmunityType" "SPELL_IMMUNITY_ENEMIES_NO" + "FightRecapLevel" "1" + "AbilitySound" "Hero_Lina.DragonSlave" + + "AbilityCastRange" "1075" + "AbilityCastPoint" "0.35" + + "AbilityDuration" "0.6875 0.6875 0.6875 0.6875" + "AbilityCooldown" "10 9 8 7" + "AbilityManaCost" "100 110 120 130" + + "AbilityValues" + { + "dragon_slave_damage" + { + "value" "125 185 255 340" + "special_bonus_unique_lina_dragon_slave_damage_bonus" "+145" + } + "fire_stacks_per_level" "5 10 15 20" + "dragon_slave_speed" "1200" + "dragon_slave_width_initial" "275" + "dragon_slave_width_end" "200" + "dragon_slave_distance" "1075" + "proc_chance" "15 20 25 30" + "mana_damage_from_current_pct" "50" + } + "AbilityCastAnimation" "ACT_DOTA_CAST_ABILITY_1" + } + "ability_lina_light_strike_array_custom" + { + "BaseClass" "ability_lua" + "ScriptFile" "abilities/heroes/lina/ability_lina_light_strike_array_custom" + "AbilityTextureName" "lina_light_strike_array" + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_POINT | DOTA_ABILITY_BEHAVIOR_AOE" + "AbilityUnitDamageType" "DAMAGE_TYPE_MAGICAL" + "SpellImmunityType" "SPELL_IMMUNITY_ENEMIES_NO" + "SpellDispellableType" "SPELL_DISPELLABLE_YES_STRONG" + "FightRecapLevel" "1" + "AbilitySound" "Ability.LightStrikeArray" + "HasShardUpgrade" "1" + + "AbilityCastRange" "700" + "AbilityCastPoint" "0.45" + "AbilityCooldown" "13 11 9 7" + "AbilityManaCost" "100 115 130 145" + + + "AbilityValues" + { + "light_strike_array_aoe" + { + "value" "250" + "affected_by_aoe_increase" "1" + } + "light_strike_array_delay_time" "0.5" + "light_strike_array_stun_duration" + { + "value" "0.25 0.50 0.75 1" + "special_bonus_shard" "+0.25" + } + "light_strike_array_damage" + { + "value" "160 200 240 280" + "special_bonus_shard" "+280" + } + "fire_stacks_per_level" "5 10 15 20" + "mana_damage_from_current_pct" "50" + } + "AbilityCastAnimation" "ACT_DOTA_CAST_ABILITY_2" + } + "ability_lina_flame_cloak_custom" + { + "BaseClass" "ability_lua" + "ScriptFile" "abilities/heroes/lina/ability_lina_flame_cloak_custom" + "AbilityTextureName" "lina_flame_cloak" + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_NO_TARGET" + "SpellDispellableType" "SPELL_DISPELLABLE_NO" + "SpellImmunityType" "SPELL_IMMUNITY_ENEMIES_NO" + "FightRecapLevel" "1" + "AbilitySound" "Hero_Lina.FlameCloak.Cast" + + "AbilityCastPoint" "0.0" + "AbilityCooldown" "18 15 12 9" + "AbilityManaCost" "90 100 110 120" + + "AbilityValues" + { + "duration" + { + "value" "6.0 7.0 8.0 9.0" + } + "array_five" + { + + "special_bonus_unique_lina_light_strike_array_five" "4" + } + "fire_stacks_per_level" "1" + "damage_per_second" + { + "value" "10 20 30 40" + "special_bonus_unique_lina_flame_cloak_custom_damage_bonus" "+30" + } + "radius" "525" + "max_stacks" "7" + "visualzdelta" "100" + "attackspeed_bonus" + { + "value" "2 4 6 8" + "special_bonus_unique_lina_flame_cloak_custom_bonus_movespeed_attack_speed_pct" "+6" + } + "movespeed_bonus" + { + "value" "2 4 6 8" + "special_bonus_unique_lina_flame_cloak_custom_bonus_movespeed_attack_speed_pct" "+6" + } + + "spell_amplify" "5 10 15 20" + "magical_resistance" "5 10 15 20" + "mana_damage_from_current_pct" "50" + + } + "AbilityCastAnimation" "ACT_DOTA_CAST_ABILITY_3" + } + "ability_lina_laguna_blade_custom" + { + "BaseClass" "ability_lua" + "ScriptFile" "abilities/heroes/lina/ability_lina_laguna_blade_custom" + "AbilityTextureName" "lina_laguna_blade" + "AbilityType" "ABILITY_TYPE_ULTIMATE" + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_UNIT_TARGET" + "AbilityUnitTargetTeam" "DOTA_UNIT_TARGET_TEAM_ENEMY" + "AbilityUnitTargetType" "DOTA_UNIT_TARGET_HERO | DOTA_UNIT_TARGET_BASIC" + "SpellImmunityType" "SPELL_IMMUNITY_ENEMIES_NO" + "HasScepterUpgrade" "1" + "AbilityUnitDamageType" "DAMAGE_TYPE_MAGICAL" + "FightRecapLevel" "2" + "AbilitySound" "Ability.LagunaBladeImpact" + + + + "AbilityCastRange" "600" + "AbilityCastPoint" "0.3" + "AbilityCooldown" "70 60 50" + + + "AbilityValues" + { + "AbilityCooldown" + { + "value" "70 60 50" + "special_bonus_unique_lina_laguna_blade_custom_cd" "-25" + } + "damage" "1500 3000 4500" + "damage_delay" "0.25" + "mana_cost" "150 300 450" + "damage_mult" "300" + "duration" "20" + "spell_amplify" "10" + "mana_cost_facet" + { + "value" "0" + "special_bonus_scepter" "70" + } + "mana_cost_facet_tooltip" + { + "value" "0" + "special_bonus_scepter" "30" + } + "mana_damage_from_current_pct" "50" + } + "AbilityCastAnimation" "ACT_DOTA_CAST_ABILITY_4" + } + "special_bonus_unique_lina_light_strike_array_five" + { + "BaseClass" "special_bonus_undefined" + "AbilityType" "ABILITY_TYPE_ATTRIBUTES" + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_PASSIVE" + } + "special_bonus_unique_lina_laguna_blade_custom_cd" + { + "BaseClass" "special_bonus_undefined" + "AbilityType" "ABILITY_TYPE_ATTRIBUTES" + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_PASSIVE" + } + "special_bonus_unique_lina_flame_cloak_custom_damage_bonus" + { + "BaseClass" "special_bonus_undefined" + "AbilityType" "ABILITY_TYPE_ATTRIBUTES" + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_PASSIVE" + } + "special_bonus_unique_lina_flame_cloak_custom_bonus_movespeed_attack_speed_pct" + { + "BaseClass" "special_bonus_undefined" + "AbilityType" "ABILITY_TYPE_ATTRIBUTES" + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_PASSIVE" + } + + "ability_lina_scorch_affinity_innate" + { + "BaseClass" "ability_lua" + "ScriptFile" "abilities/heroes/lina/ability_lina_scorch_affinity_innate" + "AbilityTextureName" "lina_flame_cloak" + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_PASSIVE" + "MaxLevel" "1" + "Innate" "1" + "IsBreakable" "1" + "AbilityValues" + { + "radius" + { + "value" "900" + "affected_by_aoe_increase" "1" + } + "bonus_pct_base" "5" + "bonus_pct_per_hero_level" "1" + } + } +} \ No newline at end of file diff --git a/scripts/npc/heroes/luna/luna.kv b/scripts/npc/heroes/luna/luna.kv new file mode 100644 index 0000000..fcaa7c0 --- /dev/null +++ b/scripts/npc/heroes/luna/luna.kv @@ -0,0 +1,245 @@ +"DOTAAbilities" +{ + "ability_luna_lucent_beam_custom" + { + "BaseClass" "ability_lua" + "ScriptFile" "abilities/heroes/luna/ability_luna_lucent_beam_custom.lua" + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_UNIT_TARGET" + "AbilityUnitTargetTeam" "DOTA_UNIT_TARGET_TEAM_ENEMY" + "AbilityUnitTargetType" "DOTA_UNIT_TARGET_HERO | DOTA_UNIT_TARGET_BASIC" + "AbilityUnitDamageType" "DAMAGE_TYPE_MAGICAL" + "SpellImmunityType" "SPELL_IMMUNITY_ENEMIES_NO" + "AbilityTextureName" "luna_lucent_beam" + "MaxLevel" "4" + "AbilitySound" "Hero_Luna.LucentBeam" + "AbilityCastRange" "800" + "AbilityCastPoint" "0.4" + "AbilityManaCost" "90 100 110 120" + + + "AbilityValues" + { + "AbilityCooldown" + { + "value" "9 7 5 3" + "special_bonus_unique_luna_lucent_beam_cooldown" "-2.5" + } + "lucent_beam_damage" + { + "value" "125 190 255 370" + "special_bonus_unique_luna_lucent_beam_damage" "+230" + } + "stun_duration" "0.4" + "lucent_beam_aoe_radius" "320" + "lucent_beam_aoe_damage_pct" "50" + "mana_damage_pct" "50 100 150 200" + } + "AbilityCastAnimation" "ACT_DOTA_CAST_ABILITY_1" + } + + "ability_luna_twin_glaives_custom" + { + "BaseClass" "ability_lua" + "ScriptFile" "abilities/heroes/luna/ability_luna_twin_glaives_custom.lua" + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_POINT" + "AbilityUnitTargetTeam" "DOTA_UNIT_TARGET_TEAM_ENEMY" + "AbilityUnitTargetType" "DOTA_UNIT_TARGET_HERO | DOTA_UNIT_TARGET_BASIC" + "AbilityUnitDamageType" "DAMAGE_TYPE_PHYSICAL" + "SpellImmunityType" "SPELL_IMMUNITY_ENEMIES_NO" + "AbilityTextureName" "luna_lunar_orbit" + "MaxLevel" "4" + "AbilityManaCost" "25" + "AbilityCooldown" "6" + + "AbilityValues" + { + "cast_range" + { + "value" "1200" + "special_bonus_unique_luna_orbit_radius" "+200" + } + "glaive_damage" + { + "value" "140 180 220 280" + "special_bonus_unique_luna_orbit_damage" "+120" + } + "projectile_speed" "1200" + "AbilityCastRange" + { + "value" "1200" + "special_bonus_unique_luna_orbit_radius" "+200" + } + "projectile_range" + { + "value" "1300" + "special_bonus_unique_luna_orbit_radius" "+200" + } + "projectile_width" "75" + "axe_spread" "100" + "twin_facet_vuln_duration" "10" + "twin_facet_pct" "12 16 20 24" + } + "AbilityCastAnimation" "ACT_DOTA_CAST_ABILITY_1" + } + + "ability_luna_moon_glaive_custom" + { + "BaseClass" "ability_lua" + "ScriptFile" "abilities/heroes/luna/ability_luna_moon_glaive_custom.lua" + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_PASSIVE" + "AbilityUnitDamageType" "DAMAGE_TYPE_PHYSICAL" + "AbilityTextureName" "luna_moon_glaive" + "MaxLevel" "4" + + "AbilityValues" + { + "bounce_range" "500" + "moon_glaive_bounce_count" + { + "value" "4 5 6 7" + "special_bonus_unique_luna_glaive_extra_bounce" "+1" + } + "bounce_damage_pct" "50 60 70 80" + "bounce_falloff_pct" + { + "value" "60 70 80 90" + "special_bonus_unique_luna_glaives_no_falloff" "100" + } + "moon_facet_perform_attack" + { + "value" "0" + "special_bonus_unique_luna_moon_glaive_beam_attack" "1" + } + } + } + + "ability_luna_lunar_blessing_custom" + { + "BaseClass" "ability_lua" + "ScriptFile" "abilities/heroes/luna/ability_luna_lunar_blessing_custom.lua" + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_PASSIVE | DOTA_ABILITY_BEHAVIOR_HIDDEN" + "Innate" "1" + "AbilityTextureName" "luna_lunar_blessing" + "MaxLevel" "1" + "HasShardUpgrade" "1" + + "AbilityValues" + { + "blessing_radius" + { + "value" "800" + "affected_by_aoe_increase" "1" + "special_bonus_unique_luna_blessing_aura" "+350" + } + "blessing_bonus_damage" + { + "value" "2.5" + "hero_levelup" "+2.5" + "levelup_interval" "1" + } + "spell_amp_per_level" + { + "value" "0" + "special_bonus_shard" "+0.45" + } + "moon_sisters_radius" + { + "value" "1200" + "affected_by_aoe_increase" "1" + } + "moon_sisters_damage_reduction_pct" "25" + } + } + + "ability_luna_eclipse_custom" + { + "BaseClass" "ability_lua" + "ScriptFile" "abilities/heroes/luna/ability_luna_eclipse_custom.lua" + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_NO_TARGET" + "AbilityType" "ABILITY_TYPE_ULTIMATE" + "AbilityUnitDamageType" "DAMAGE_TYPE_MAGICAL" + "SpellImmunityType" "SPELL_IMMUNITY_ENEMIES_NO" + "AbilityTextureName" "luna_eclipse" + "MaxLevel" "3" + "HasScepterUpgrade" "1" + + "AbilityManaCost" "150 200 250" + "AbilityCooldown" "140 120 100" + "AbilityCastPoint" "0.4" + + "AbilityValues" + { + "eclipse_duration" "3.6" + "beam_interval" "0.45" + "eclipse_beam_count" + { + "value" "8" + "special_bonus_unique_luna_eclipse_power" "+4" + } + "eclipse_radius" "1200" + "eclipse_damage" "100 200 300" + "eclipse_mini_stun" "0.25" + "eclipse_facet_wave_count" + { + "value" "0" + "special_bonus_scepter" "8" + } + } + "AbilityCastAnimation" "ACT_DOTA_CAST_ABILITY_4" + } + + "special_bonus_unique_luna_lucent_beam_damage" + { + "BaseClass" "special_bonus_undefined" + "AbilityType" "ABILITY_TYPE_ATTRIBUTES" + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_PASSIVE" + } + "special_bonus_unique_luna_orbit_radius" + { + "BaseClass" "special_bonus_undefined" + "AbilityType" "ABILITY_TYPE_ATTRIBUTES" + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_PASSIVE" + } + "special_bonus_unique_luna_glaive_extra_bounce" + { + "BaseClass" "special_bonus_undefined" + "AbilityType" "ABILITY_TYPE_ATTRIBUTES" + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_PASSIVE" + } + "special_bonus_unique_luna_blessing_aura" + { + "BaseClass" "special_bonus_undefined" + "AbilityType" "ABILITY_TYPE_ATTRIBUTES" + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_PASSIVE" + } + "special_bonus_unique_luna_lucent_beam_cooldown" + { + "BaseClass" "special_bonus_undefined" + "AbilityType" "ABILITY_TYPE_ATTRIBUTES" + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_PASSIVE" + } + "special_bonus_unique_luna_orbit_damage" + { + "BaseClass" "special_bonus_undefined" + "AbilityType" "ABILITY_TYPE_ATTRIBUTES" + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_PASSIVE" + } + "special_bonus_unique_luna_eclipse_power" + { + "BaseClass" "special_bonus_undefined" + "AbilityType" "ABILITY_TYPE_ATTRIBUTES" + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_PASSIVE" + } + "special_bonus_unique_luna_glaives_no_falloff" + { + "BaseClass" "special_bonus_undefined" + "AbilityType" "ABILITY_TYPE_ATTRIBUTES" + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_PASSIVE" + } + "special_bonus_unique_luna_moon_glaive_beam_attack" + { + "BaseClass" "special_bonus_undefined" + "AbilityType" "ABILITY_TYPE_ATTRIBUTES" + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_PASSIVE" + } +} diff --git a/scripts/npc/heroes/mirana/mirana.kv b/scripts/npc/heroes/mirana/mirana.kv new file mode 100644 index 0000000..9420736 --- /dev/null +++ b/scripts/npc/heroes/mirana/mirana.kv @@ -0,0 +1,261 @@ +"DOTAAbilities" +{ + "ability_mirana_starstorm_custom" + { + "BaseClass" "ability_lua" + "ScriptFile" "abilities/heroes/mirana/ability_mirana_starstorm_custom.lua" + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_NO_TARGET | DOTA_ABILITY_BEHAVIOR_AOE" + "AbilityUnitDamageType" "DAMAGE_TYPE_MAGICAL" + "SpellImmunityType" "SPELL_IMMUNITY_ENEMIES_NO" + "AbilityTextureName" "mirana_starfall" + "MaxLevel" "4" + "AbilitySound" "Hero_Mirana.Starstorm" + "AbilityCastPoint" "0.4" + "AbilityManaCost" "90 100 110 120" + + "AbilityValues" + { + "AbilityCooldown" + { + "value" "12 11 10 9" + "special_bonus_unique_mirana_starstorm_cooldown" "-3" + } + "damage" + { + "value" "200 300 400 500" + "special_bonus_unique_mirana_starstorm_damage" "+180" + } + "radius" + { + "value" "650" + "affected_by_aoe_increase" "1" + } + "meteor_impact_delay" "0.57" + "secondary_delay" "0.8" + "secondary_damage_pct" "75" + "attack_proc_chance" + { + "value" "0" + "special_bonus_unique_mirana_starstorm_attack_proc" "20" + } + "mana_damage_pct" + { + "value" "10" + "hero_levelup" "+1" + "levelup_interval" "1" + "special_bonus_unique_mirana_innate_mana_damage" "+8" + } + } + "AbilityCastAnimation" "ACT_DOTA_CAST_ABILITY_1" + } + + "ability_mirana_sacred_arrow_custom" + { + "BaseClass" "ability_lua" + "ScriptFile" "abilities/heroes/mirana/ability_mirana_sacred_arrow_custom.lua" + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_POINT | DOTA_ABILITY_BEHAVIOR_DIRECTIONAL" + "AbilityUnitDamageType" "DAMAGE_TYPE_MAGICAL" + "SpellImmunityType" "SPELL_IMMUNITY_ENEMIES_NO" + "AbilityTextureName" "mirana_arrow" + "MaxLevel" "4" + "HasShardUpgrade" "1" + "AbilityCastPoint" "0.3" + "AbilityManaCost" "90 100 110 120" + + "AbilityValues" + { + "AbilityCooldown" "17 15 13 11" + "arrow_damage" + { + "value" "50 150 250 350" + "special_bonus_unique_mirana_sacred_arrow_damage" "+150" + } + "arrow_range" "3000" + "arrow_speed" "900" + "arrow_width" "115" + "stun_per_100_distance" "0.17" + "max_stun_duration" "3.8" + "mana_damage_pct" + { + "value" "10" + "hero_levelup" "+1" + "levelup_interval" "1" + "special_bonus_unique_mirana_innate_mana_damage" "+8" + } + "shard_starfall_radius" + { + "value" "250" + "affected_by_aoe_increase" "1" + } + "shard_starfall_damage_pct_first" "50" + "shard_starfall_damage_pct_second" "25" + "shard_meteor_impact_delay" "0.57" + "shard_second_meteor_delay" "0.35" + "talent_side_arrow_angle" "25" + } + "AbilityCastAnimation" "ACT_DOTA_CAST_ABILITY_2" + } + + "ability_mirana_leap_custom" + { + "BaseClass" "ability_lua" + "ScriptFile" "abilities/heroes/mirana/ability_mirana_leap_custom.lua" + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_NO_TARGET | DOTA_ABILITY_BEHAVIOR_ROOT_DISABLES | DOTA_ABILITY_BEHAVIOR_IGNORE_BACKSWING" + "AbilityTextureName" "mirana_leap" + "MaxLevel" "4" + "AbilityCastPoint" "0.0" + "AbilityManaCost" "0" + + "AbilityValues" + { + "mana_cost_pct" "5" + "leap_damage_reduction_pct" "75" + "leap_landing_radius" + { + "value" "175" + "affected_by_aoe_increase" "1" + "special_bonus_unique_mirana_leap_landing_radius" "+250" + } + "leap_landing_damage_pct" "25" + "mana_damage_pct" + { + "value" "10" + "hero_levelup" "+1" + "levelup_interval" "1" + "special_bonus_unique_mirana_innate_mana_damage" "+8" + } + "talent_air_strike_projectile_speed" "0" + "leap_distance" "575" + "leap_speed" "1300" + "leap_height" "150" + "leap_speedbonus" "8 16 24 32" + "leap_speedbonus_as" "60 80 100 120" + "leap_bonus_duration" "4" + "innate_damage_multiplier" "4" + } + "AbilityCastAnimation" "ACT_DOTA_CAST_ABILITY_3" + } + + "ability_mirana_innate_custom" + { + "BaseClass" "ability_lua" + "ScriptFile" "abilities/heroes/mirana/ability_mirana_innate_custom.lua" + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_PASSIVE | DOTA_ABILITY_BEHAVIOR_HIDDEN" + "AbilityType" "ABILITY_TYPE_BASIC" + "Innate" "1" + "IsBreakable" "1" + "AbilityTextureName" "mirana_celestial_quiver" + "MaxLevel" "1" + + "AbilityValues" + { + "mana_damage_pct" + { + "value" "10" + "hero_levelup" "+1" + "levelup_interval" "1" + "special_bonus_unique_mirana_innate_mana_damage" "+8" + } + "moon_sisters_radius" + { + "value" "1200" + "affected_by_aoe_increase" "1" + } + "moon_sisters_damage_reduction_pct" "25" + } + } + + "ability_mirana_moonlight_shadow_custom" + { + "BaseClass" "ability_lua" + "ScriptFile" "abilities/heroes/mirana/ability_mirana_moonlight_shadow_custom.lua" + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_NO_TARGET" + "AbilityType" "ABILITY_TYPE_ULTIMATE" + "AbilityTextureName" "mirana_invis" + "MaxLevel" "3" + "HasScepterUpgrade" "1" + "AbilityCastPoint" "0.3" + "AbilityManaCost" "75 75 75" + + "AbilityValues" + { + "AbilityCooldown" "140 120 100" + "duration" + { + "value" "18" + "special_bonus_unique_mirana_moonlight_duration" "+10" + } + "scepter_ally_damage_share_pct" "50" + "bonus_movement_speed" "9 12 15" + "damage_bonus_base" "15 20 25" + "damage_bonus_per_second" "2 3 4" + "radius" "25000" + } + "AbilityCastAnimation" "ACT_DOTA_CAST_ABILITY_4" + } + + "special_bonus_unique_mirana_starstorm_damage" + { + "BaseClass" "special_bonus_undefined" + "AbilityType" "ABILITY_TYPE_ATTRIBUTES" + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_PASSIVE" + } + "special_bonus_unique_mirana_starstorm_cooldown" + { + "BaseClass" "special_bonus_undefined" + "AbilityType" "ABILITY_TYPE_ATTRIBUTES" + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_PASSIVE" + } + "special_bonus_unique_mirana_starstorm_attack_proc" + { + "BaseClass" "special_bonus_undefined" + "AbilityType" "ABILITY_TYPE_ATTRIBUTES" + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_PASSIVE" + "AbilityValues" + { + "attack_proc_chance" "20" + } + } + "special_bonus_unique_mirana_sacred_arrow_side_arrows" + { + "BaseClass" "special_bonus_undefined" + "AbilityType" "ABILITY_TYPE_ATTRIBUTES" + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_PASSIVE" + } + "special_bonus_unique_mirana_sacred_arrow_damage" + { + "BaseClass" "special_bonus_undefined" + "AbilityType" "ABILITY_TYPE_ATTRIBUTES" + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_PASSIVE" + } + "special_bonus_unique_mirana_leap_charge_time" + { + "BaseClass" "special_bonus_undefined" + "AbilityType" "ABILITY_TYPE_ATTRIBUTES" + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_PASSIVE" + } + "special_bonus_unique_mirana_leap_air_strike" + { + "BaseClass" "special_bonus_undefined" + "AbilityType" "ABILITY_TYPE_ATTRIBUTES" + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_PASSIVE" + } + "special_bonus_unique_mirana_leap_landing_radius" + { + "BaseClass" "special_bonus_undefined" + "AbilityType" "ABILITY_TYPE_ATTRIBUTES" + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_PASSIVE" + } + "special_bonus_unique_mirana_moonlight_duration" + { + "BaseClass" "special_bonus_undefined" + "AbilityType" "ABILITY_TYPE_ATTRIBUTES" + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_PASSIVE" + } + "special_bonus_unique_mirana_innate_mana_damage" + { + "BaseClass" "special_bonus_undefined" + "AbilityType" "ABILITY_TYPE_ATTRIBUTES" + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_PASSIVE" + } +} diff --git a/scripts/npc/heroes/nagash/nagash.kv b/scripts/npc/heroes/nagash/nagash.kv new file mode 100644 index 0000000..1616526 --- /dev/null +++ b/scripts/npc/heroes/nagash/nagash.kv @@ -0,0 +1,164 @@ +"DOTAAbilities" +{ + "dark_friends" + { + "BaseClass" "ability_lua" + "ScriptFile" "abilities/heroes/nagash/dark_friends" + + "AbilityTextureName" "old_heroes/nagash/dark_friend" + + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_PASSIVE" + + + "MaxLevel" "4" + + "AbilityValues" + { + "aura_radius" "350" + "aura_bonus_damage" "25 50 75 100" + "aura_bonus_attack_speed" "20 30 40 50" + "aura_bonus_movespeed_pct" "2 4 6 8" + "aura_bonus_armor" "3 5 6 9" + "outgoing_damage_pct" + { + "value" "0" + "special_bonus_unique_nagash_dark_friend_damage_pct" "25" + } + + + } + } + "world_destroyer" + { + "BaseClass" "ability_lua" + "ScriptFile" "abilities/heroes/nagash/world_destroyer" + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_POINT | DOTA_ABILITY_BEHAVIOR_AOE" + "LinkedAbility" "leader_call" + "AbilityTextureName" "old_heroes/nagash/world_destroyer" + "AbilityCooldown" "30 28 26 24" + "AbilityManaCost" "125" + "AbilityCastRange" "600" + "AbilityCastPoint" "0.2" + "HasShardUpgrade" "1" + + "AbilityValues" + { + "radius" "500" + "health_soul_eater" "200 300 400 500" + "dominate_duration" "10 15 20 25" + "bonus_damage" "150 200 250 300" + "bonus_attack_speed" "50 80 100 120" + "bonus_armor" "4 6 8 10" + "shard_invulnerable_duration" "3" + } + } + "leader_call" + { + "BaseClass" "ability_lua" + "ScriptFile" "abilities/heroes/nagash/leader_call" + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_NO_TARGET" + "LinkedAbility" "world_destroyer" + "AbilityUnitDamageType" "DAMAGE_TYPE_PURE" + "AbilityTextureName" "old_heroes/nagash/leader_call" + "AbilityCooldown" "2.5" + "AbilityHealthCost" "125 150 175 200" + "AbilityCastRange" "600" + "AbilityCastPoint" "0.2" + "AbilityValues" + { + "explosion_radius" "175" + "explosion_damage" "40 60 80 100" + "unit_health_for_damage" + { + "value" "0" + "special_bonus_unique_nagash_leader_call_damage" "50" + } + + + "bonus_stats_per_soul" "0.1 0.2 0.3 0.4" + } + } + "fighting_up" + { + "BaseClass" "ability_lua" + "ScriptFile" "abilities/heroes/nagash/fighting_up" + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_NO_TARGET" + + "AbilityTextureName" "old_heroes/nagash/soul_eater" + "AbilityCooldown" "40" + "AbilityHealthCost" "275" + "AbilityCastRange" "600" + "AbilityCastPoint" "0" + "AbilityValues" + { + "radius" "900" + "duration" "16" + "bonus_damage_pct" "20 30 40 50" + "bonus_movespeed_pct" "12 16 20 24" + "bonus_attackspeed_pct" "15 20 25 30" + + } + } + "war_for_life" + { + "BaseClass" "ability_lua" + "ScriptFile" "abilities/heroes/nagash/war_for_life" + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_NO_TARGET" + "AbilityTextureName" "old_heroes/nagash/war_for_life" + "AbilityCooldown" "60" + "AbilityManaCost" "150" + "IsGrantedByScepter" "1" + "AbilityCastPoint" "0" + "MaxLevel" "1" + "AbilityValues" + { + + "lost_souls_pct" + { + "value" "30" + "special_bonus_shard" "-30" + } + "radius" "1500" + "health_res_pct" "20" + "inc_damage_res" "100" + "duration" "6" + } + } + "dark_golem" + { + "BaseClass" "ability_lua" + "ScriptFile" "abilities/heroes/nagash/dark_golem" + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_NO_TARGET" + "AbilityType" "ABILITY_TYPE_ULTIMATE" + "MaxLevel" "3" + "AbilityTextureName" "old_heroes/nagash/dark_golem" + "AbilityCooldown" "300" + "AbilityManaCost" "500 1000 2000" + "AbilityHealthCost" "500 1000 2000" + "AbilityCastPoint" "0" + "AbilityValues" + { + "cleave_damage" "300 400 500" + "cleave_distance" "400" + "cleave_starting_width" "150" + "cleave_ending_width" "360" + "bonus_damage_pct_per_token" "0.1 0.2 0.3" + "bonus_attack_speed_pct_per_token" "0.1 0.2 0.3" + "bonus_movespeed_pct_per_token" "0.1 0.2 0.3" + "duration" "42" + } + } + "special_bonus_unique_nagash_dark_friend_damage_pct" + { + "BaseClass" "special_bonus_undefined" + "AbilityType" "ABILITY_TYPE_ATTRIBUTES" + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_PASSIVE" + } + "special_bonus_unique_nagash_leader_call_damage" + { + "BaseClass" "special_bonus_undefined" + "AbilityType" "ABILITY_TYPE_ATTRIBUTES" + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_PASSIVE" + } + +} \ No newline at end of file diff --git a/scripts/npc/heroes/nevermore/nevermore.kv b/scripts/npc/heroes/nevermore/nevermore.kv new file mode 100644 index 0000000..bd02dbd --- /dev/null +++ b/scripts/npc/heroes/nevermore/nevermore.kv @@ -0,0 +1,279 @@ +"DOTAAbilities" +{ + "nevermore_shadowraze1_custom" + { + "BaseClass" "ability_lua" + "ScriptFile" "abilities/heroes/nevermore/nevermore_shadowraze_custom" + "AbilityTextureName" "nevermore_shadowraze1" + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_NO_TARGET | DOTA_ABILITY_BEHAVIOR_IGNORE_BACKSWING" + "AbilityUnitDamageType" "DAMAGE_TYPE_MAGICAL" + "SpellImmunityType" "SPELL_IMMUNITY_ENEMIES_NO" + "SpellDispellableType" "SPELL_DISPELLABLE_YES" + "FightRecapLevel" "1" + "LinkedAbility" "nevermore_shadowraze2_custom" + + "AbilityCastAnimation" "ACT_DOTA_RAZE_1" + "AbilityCastGestureSlot" "DEFAULT" + "AbilityCastPoint" "0.55" + "AbilityManaCost" "80" + "AbilityValues" + { + "shadowraze_damage" "90 180 270 360" + "shadowraze_radius" + { + "value" "250" + "affected_by_aoe_increase" "1" + } + "slow_duration" "1 1 1 1" + "slow_duration_for_boss" "0.5 0.5 0.5 0.5" + "shadowraze_range" "200" + "stack_bonus_damage" + { + "value" "10 15 20 25" + "special_bonus_unique_nevermore_custom_8" "+20" + } + "duration" "7" + "movement_speed_debuff" "6 8 10 12" + "AbilityCooldown" "9" + } + } + + "nevermore_shadowraze2_custom" + { + "BaseClass" "ability_lua" + "ScriptFile" "abilities/heroes/nevermore/nevermore_shadowraze_custom" + "AbilityTextureName" "nevermore_shadowraze2" + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_NO_TARGET | DOTA_ABILITY_BEHAVIOR_IGNORE_BACKSWING" + "OnLearnbar" "0" + "AbilityUnitDamageType" "DAMAGE_TYPE_MAGICAL" + "SpellDispellableType" "SPELL_DISPELLABLE_YES" + "SpellImmunityType" "SPELL_IMMUNITY_ENEMIES_NO" + "FightRecapLevel" "1" + "LinkedAbility" "nevermore_shadowraze3_custom" + + "AbilityCastAnimation" "ACT_DOTA_RAZE_2" + "AbilityCastGestureSlot" "DEFAULT" + "AbilityCastPoint" "0.55" + "AbilityManaCost" "80" + "AbilityValues" + { + "shadowraze_damage" "90 180 270 360" + "shadowraze_radius" + { + "value" "250" + "affected_by_aoe_increase" "1" + } + "slow_duration" "1 1 1 1" + "slow_duration_for_boss" "0.5 0.5 0.5 0.5" + "shadowraze_range" "450" + "stack_bonus_damage" + { + "value" "10 15 20 25" + "special_bonus_unique_nevermore_custom_8" "+20" + } + "duration" "7" + "movement_speed_debuff" "6 8 10 12" + "AbilityCooldown" "9" + } + } + + "nevermore_shadowraze3_custom" + { + "BaseClass" "ability_lua" + "ScriptFile" "abilities/heroes/nevermore/nevermore_shadowraze_custom" + "AbilityTextureName" "nevermore_shadowraze3" + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_NO_TARGET | DOTA_ABILITY_BEHAVIOR_IGNORE_BACKSWING" + "OnLearnbar" "0" + "AbilityUnitDamageType" "DAMAGE_TYPE_MAGICAL" + "SpellImmunityType" "SPELL_IMMUNITY_ENEMIES_NO" + "SpellDispellableType" "SPELL_DISPELLABLE_YES" + "FightRecapLevel" "1" + "LinkedAbility" "nevermore_shadowraze1_custom" + + "AbilityCastAnimation" "ACT_DOTA_RAZE_3" + "AbilityCastGestureSlot" "DEFAULT" + "AbilityCastPoint" "0.55" + "AbilityManaCost" "80" + "AbilityValues" + { + "shadowraze_damage" "90 180 270 360" + "shadowraze_radius" + { + "value" "250" + "affected_by_aoe_increase" "1" + } + "slow_duration" "1 1 1 1" + "slow_duration_for_boss" "0.5 0.5 0.5 0.5" + "shadowraze_range" "700" + "stack_bonus_damage" + { + "value" "10 15 20 25" + "special_bonus_unique_nevermore_custom_8" "+20" + } + "duration" "7" + "movement_speed_debuff" "6 8 10 12" + "AbilityCooldown" "9" + } + } + + "nevermore_necromastery_custom" + { + "BaseClass" "ability_lua" + "ScriptFile" "abilities/heroes/nevermore/nevermore_necromastery_custom" + "AbilityTextureName" "nevermore_necromastery" + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_NO_TARGET | DOTA_ABILITY_BEHAVIOR_IMMEDIATE" + "AbilityManaCost" "0" + "AbilityCooldown" "40 35 30 25" + "AbilityValues" + { + "necromastery_damage_per_soul" + { + "value" "2 3 4 5" + "special_bonus_unique_nevermore_custom_4" "+2" + } + "necromastery_max_souls" + { + "value" "20" + "special_bonus_unique_nevermore_custom_3" "+20" + } + "souls_per_kill" "1" + "souls_per_boss_kill" "3" + "necromastery_soul_pct_release" "30" + "active_duration" "10" + "active_bonus_soul_cap" "20 40 60 80" + } + } + + "nevermore_dark_lord_custom" + { + "BaseClass" "ability_lua" + "ScriptFile" "abilities/heroes/nevermore/nevermore_dark_lord_custom" + "AbilityTextureName" "nevermore_dark_lord" + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_PASSIVE | DOTA_ABILITY_BEHAVIOR_NOT_LEARNABLE" + "Innate" "1" + "HasShardUpgrade" "1" + "MaxLevel" "1" + "AbilityValues" + { + "armor_reduction_per_soul" + { + "value" "0.25" + "special_bonus_unique_nevermore_custom_5" "+0.25" + } + "presence_radius" + { + "value" "1200" + "special_bonus_unique_nevermore_custom_6" "+300" + } + "shard_ally_armor_bonus" "8" + } + } + + "nevermore_deadly_strike_custom" + { + "BaseClass" "ability_lua" + "ScriptFile" "abilities/heroes/nevermore/nevermore_deadly_strike_custom" + "AbilityTextureName" "nevermore_frenzy" + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_PASSIVE" + "MaxLevel" "4" + "AbilityValues" + { + "deadly_strike_chance" + { + "value" "20 25 30 35" + "special_bonus_unique_nevermore_custom_1" "+15" + } + "deadly_strike_base_crit" "100" + "deadly_strike_crit_per_soul" + { + "value" "1 1.5 2 2.5" + "special_bonus_unique_nevermore_custom_2" "+1" + } + } + } + + "nevermore_requiem_custom" + { + "BaseClass" "ability_lua" + "ScriptFile" "abilities/heroes/nevermore/nevermore_requiem_custom" + "AbilityTextureName" "nevermore_requiem" + "AbilityType" "ABILITY_TYPE_ULTIMATE" + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_NO_TARGET" + "AbilityUnitDamageType" "DAMAGE_TYPE_MAGICAL" + "SpellImmunityType" "SPELL_IMMUNITY_ENEMIES_NO" + "SpellDispellableType" "SPELL_DISPELLABLE_YES" + "AbilityCastPoint" "1.67 1.67 1.67" + "HasScepterUpgrade" "1" + "AbilityManaCost" "150 300 600" + "AbilityValues" + { + "damage" + { + "value" "100 220 340" + "special_bonus_unique_nevermore_custom_7" "+150" + } + "AbilityCooldown" "20" + "requiem_radius" "1000" + "requiem_reduction_ms" "-20 -25 -30" + "requiem_reduction_mres" "-5 -10 -15" + "requiem_slow_duration" "0.6" + "requiem_slow_duration_max" "2.15" + "requiem_line_width_start" "125" + "requiem_line_width_end" "300" + "requiem_line_speed" "700 700 700" + "requiem_damage_pct_scepter" "60" + "count_split_attack" "0" + "split_attack_duration" "0" + "requiem_soul_pct_release" "70" + } + } + + "special_bonus_unique_nevermore_custom_1" + { + "AbilityType" "ABILITY_TYPE_ATTRIBUTES" + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_PASSIVE" + "BaseClass" "special_bonus_base" + } + "special_bonus_unique_nevermore_custom_2" + { + "AbilityType" "ABILITY_TYPE_ATTRIBUTES" + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_PASSIVE" + "BaseClass" "special_bonus_base" + } + "special_bonus_unique_nevermore_custom_3" + { + "AbilityType" "ABILITY_TYPE_ATTRIBUTES" + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_PASSIVE" + "BaseClass" "special_bonus_base" + } + "special_bonus_unique_nevermore_custom_4" + { + "AbilityType" "ABILITY_TYPE_ATTRIBUTES" + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_PASSIVE" + "BaseClass" "special_bonus_base" + } + "special_bonus_unique_nevermore_custom_5" + { + "AbilityType" "ABILITY_TYPE_ATTRIBUTES" + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_PASSIVE" + "BaseClass" "special_bonus_base" + } + "special_bonus_unique_nevermore_custom_6" + { + "AbilityType" "ABILITY_TYPE_ATTRIBUTES" + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_PASSIVE" + "BaseClass" "special_bonus_base" + } + "special_bonus_unique_nevermore_custom_7" + { + "AbilityType" "ABILITY_TYPE_ATTRIBUTES" + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_PASSIVE" + "BaseClass" "special_bonus_base" + } + "special_bonus_unique_nevermore_custom_8" + { + "AbilityType" "ABILITY_TYPE_ATTRIBUTES" + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_PASSIVE" + "BaseClass" "special_bonus_base" + } +} diff --git a/scripts/npc/heroes/ogre_magi/ogre_magi.kv b/scripts/npc/heroes/ogre_magi/ogre_magi.kv new file mode 100644 index 0000000..0450e76 --- /dev/null +++ b/scripts/npc/heroes/ogre_magi/ogre_magi.kv @@ -0,0 +1,251 @@ +"DOTAAbilities" +{ + "ability_ogre_magi_fireblast_custom" + { + "BaseClass" "ability_lua" + "ScriptFile" "abilities/heroes/ogre_magi/ability_ogre_magi_fireblast_custom" + "AbilityTextureName" "ogre_magi_fireblast" + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_POINT | DOTA_ABILITY_BEHAVIOR_AOE | DOTA_ABILITY_BEHAVIOR_IGNORE_BACKSWING" + "AbilityUnitDamageType" "DAMAGE_TYPE_MAGICAL" + "SpellImmunityType" "SPELL_IMMUNITY_ENEMIES_NO" + "SpellDispellableType" "SPELL_DISPELLABLE_YES_STRONG" + "FightRecapLevel" "1" + "AbilitySound" "Hero_OgreMagi.Fireblast" + "MaxLevel" "4" + "AbilityCastRange" "800" + "AbilityCastPoint" "0.45" + "AbilityManaCost" "70 85 100 115" + + "AbilityValues" + { + "AbilityCooldown" "11 10 9 8" + "fireblast_damage" + { + "value" "70 130 190 250" + "special_bonus_unique_ogre_magi_fireblast_damage" "+220" + } + "stun_duration" "1.2" + "blast_radius" "200" + "max_health_damage_pct" "8 10 12 14" + "attack_proc_chance" + { + "value" "0" + "special_bonus_unique_ogre_magi_fireblast_attack_proc" "15" + } + "attack_proc_distance" "200" + } + "AbilityCastAnimation" "ACT_DOTA_CAST_ABILITY_1" + } + + "ability_ogre_magi_ignite_custom" + { + "BaseClass" "ability_lua" + "ScriptFile" "abilities/heroes/ogre_magi/ability_ogre_magi_ignite_custom" + "AbilityTextureName" "ogre_magi_ignite" + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_UNIT_TARGET | DOTA_ABILITY_BEHAVIOR_AOE" + "AbilityUnitTargetTeam" "DOTA_UNIT_TARGET_TEAM_ENEMY" + "AbilityUnitTargetType" "DOTA_UNIT_TARGET_HERO | DOTA_UNIT_TARGET_BASIC" + "AbilityUnitDamageType" "DAMAGE_TYPE_MAGICAL" + "SpellImmunityType" "SPELL_IMMUNITY_ENEMIES_NO" + "SpellDispellableType" "SPELL_DISPELLABLE_YES" + "FightRecapLevel" "1" + "AbilitySound" "Hero_OgreMagi.Ignite" + "MaxLevel" "4" + "AbilityCastRange" "700 800 900 1000" + "AbilityCastPoint" "0.35" + "AbilityManaCost" "80 90 100 110" + + "AbilityValues" + { + "AbilityCooldown" "17" + "duration" "5 6 7 8" + "burn_damage" + { + "value" "20 30 40 50" + "special_bonus_unique_ogre_magi_ignite_damage" "+12" + } + "slow_movement_speed_pct" "-25" + "projectile_speed" "1000" + "ignite_radius" "300" + "max_health_damage_pct" "2 3 4 5" + } + "AbilityCastAnimation" "ACT_DOTA_CAST_ABILITY_2" + } + + "ability_ogre_magi_bloodlust_custom" + { + "BaseClass" "ability_lua" + "ScriptFile" "abilities/heroes/ogre_magi/ability_ogre_magi_bloodlust_custom" + "AbilityTextureName" "ogre_magi_bloodlust" + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_UNIT_TARGET | DOTA_ABILITY_BEHAVIOR_AUTOCAST | DOTA_ABILITY_BEHAVIOR_DONT_RESUME_ATTACK" + "AbilityUnitTargetTeam" "DOTA_UNIT_TARGET_TEAM_FRIENDLY" + "AbilityUnitTargetType" "DOTA_UNIT_TARGET_HERO | DOTA_UNIT_TARGET_BASIC | DOTA_UNIT_TARGET_BUILDING" + "AbilityUnitTargetFlags" "DOTA_UNIT_TARGET_FLAG_INVULNERABLE" + "SpellImmunityType" "SPELL_IMMUNITY_ALLIES_YES" + "SpellDispellableType" "SPELL_DISPELLABLE_YES" + "FightRecapLevel" "1" + "AbilitySound" "Hero_OgreMagi.Bloodlust.Target" + "MaxLevel" "4" + "AbilityCastRange" "650" + "AbilityCastPoint" "0.45" + "AbilityManaCost" "40 50 60 70" + + "AbilityValues" + { + "AbilityCooldown" "20 18 16 14" + "bonus_movement_speed" "6 8 10 12" + "bonus_attack_speed" + { + "value" "35 50 65 80" + "special_bonus_unique_ogre_magi_bloodlust_as" "+35" + } + "self_bonus" + { + "value" "40 60 80 100" + "special_bonus_unique_ogre_magi_bloodlust_as" "+35" + } + "duration" "30" + "modelscale" "25" + "physical_vampirism" + { + "value" "0" + "special_bonus_unique_ogre_magi_bloodlust_as" "+30" + } + } + "AbilityCastAnimation" "ACT_DOTA_CAST_ABILITY_3" + } + + "ability_ogre_magi_multicast_custom" + { + "BaseClass" "ability_lua" + "ScriptFile" "abilities/heroes/ogre_magi/ability_ogre_magi_multicast_custom" + "AbilityTextureName" "ogre_magi_multicast" + "AbilityType" "ABILITY_TYPE_ULTIMATE" + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_PASSIVE" + "MaxLevel" "3" + "IsBreakable" "1" + "HasShardUpgrade" "1" + "AbilityCastAnimation" "ACT_DOTA_CAST_ABILITY_3" + "AbilityCastPoint" "0.5" + + "AbilityValues" + { + "chance_2x" "75" + "chance_3x" "0 30 30" + "chance_4x" "0 0 15" + "multicast_delay" "0.6" + "shard_cooldown" "45 40 35" + "shard_mana_cost" "100 125 150" + "shard_radius" "800" + "shard_duration" "5" + } + } + + "ability_ogre_magi_unrefined_fireblast_custom" + { + "BaseClass" "ability_lua" + "ScriptFile" "abilities/heroes/ogre_magi/ability_ogre_magi_unrefined_fireblast_custom" + "AbilityTextureName" "ogre_magi_unrefined_fireblast" + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_POINT | DOTA_ABILITY_BEHAVIOR_AOE | DOTA_ABILITY_BEHAVIOR_IGNORE_BACKSWING" + "AbilityUnitDamageType" "DAMAGE_TYPE_MAGICAL" + "SpellImmunityType" "SPELL_IMMUNITY_ENEMIES_NO" + "MaxLevel" "1" + "IsGrantedByScepter" "1" + "AbilityCastRange" "800" + "AbilityCastPoint" "0.45" + "AbilityManaCost" "0" + "AbilitySound" "Hero_OgreMagi.Fireblast" + + "AbilityValues" + { + "AbilityCooldown" "8" + "mana_cost_pct" "25" + "blast_damage" "300" + "stun_duration" "1.5" + "blast_radius" "200" + "max_health_damage_pct" "15" + } + } + + "ability_ogre_magi_innate_custom" + { + "BaseClass" "ability_lua" + "ScriptFile" "abilities/heroes/ogre_magi/ability_ogre_magi_innate_custom" + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_PASSIVE | DOTA_ABILITY_BEHAVIOR_HIDDEN" + "AbilityType" "ABILITY_TYPE_BASIC" + "Innate" "1" + "IsBreakable" "1" + "AbilityTextureName" "ogre_magi_dumb_luck" + "MaxLevel" "1" + + "AbilityValues" + { + "luck_per_level" + { + "value" "1" + "special_bonus_unique_ogre_magi_luck" "+1" + } + } + } + + "special_bonus_unique_ogre_magi_fireblast_damage" + { + "BaseClass" "special_bonus_undefined" + "AbilityType" "ABILITY_TYPE_ATTRIBUTES" + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_PASSIVE" + } + "special_bonus_unique_ogre_magi_ignite_damage" + { + "BaseClass" "special_bonus_undefined" + "AbilityType" "ABILITY_TYPE_ATTRIBUTES" + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_PASSIVE" + } + "special_bonus_unique_ogre_magi_bloodlust_as" + { + "BaseClass" "special_bonus_undefined" + "AbilityType" "ABILITY_TYPE_ATTRIBUTES" + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_PASSIVE" + } + "special_bonus_unique_ogre_magi_ignite_stacks" + { + "BaseClass" "special_bonus_undefined" + "AbilityType" "ABILITY_TYPE_ATTRIBUTES" + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_PASSIVE" + } + "special_bonus_unique_ogre_magi_ignite_on_cast" + { + "BaseClass" "special_bonus_undefined" + "AbilityType" "ABILITY_TYPE_ATTRIBUTES" + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_PASSIVE" + } + "special_bonus_unique_ogre_magi_ignite_proc_damage" + { + "BaseClass" "special_bonus_undefined" + "AbilityType" "ABILITY_TYPE_ATTRIBUTES" + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_PASSIVE" + } + "special_bonus_unique_ogre_magi_fireblast_attack_proc" + { + "BaseClass" "special_bonus_undefined" + "AbilityType" "ABILITY_TYPE_ATTRIBUTES" + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_PASSIVE" + } + "special_bonus_cast_range_100" + { + "BaseClass" "special_bonus_undefined" + "AbilityType" "ABILITY_TYPE_ATTRIBUTES" + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_PASSIVE" + } + "special_bonus_attack_damage_80" + { + "BaseClass" "special_bonus_undefined" + "AbilityType" "ABILITY_TYPE_ATTRIBUTES" + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_PASSIVE" + } + "special_bonus_unique_ogre_magi_luck" + { + "BaseClass" "special_bonus_undefined" + "AbilityType" "ABILITY_TYPE_ATTRIBUTES" + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_PASSIVE" + } +} diff --git a/scripts/npc/heroes/phantom_assassin/phantom_assassin.kv b/scripts/npc/heroes/phantom_assassin/phantom_assassin.kv new file mode 100644 index 0000000..df28232 --- /dev/null +++ b/scripts/npc/heroes/phantom_assassin/phantom_assassin.kv @@ -0,0 +1,196 @@ +"DOTAAbilities" +{ + "ability_phantom_assassin_stifling_dagger_custom" + { + "BaseClass" "ability_lua" + + "ScriptFile" "abilities\heroes\phantom_assassin\ability_phantom_assassin_stifling_dagger_custom.lua" + "AbilityTextureName" "phantom_assassin_stifling_dagger" + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_UNIT_TARGET | DOTA_ABILITY_BEHAVIOR_ROOT_DISABLES" + "AbilityUnitTargetTeam" "DOTA_UNIT_TARGET_TEAM_ENEMY" + "AbilityUnitTargetType" "DOTA_UNIT_TARGET_HERO | DOTA_UNIT_TARGET_BASIC" + "AbilityUnitDamageType" "DAMAGE_TYPE_PHYSICAL" + + "AbilityCastRange" "600 700 800 900" + "AbilityCastPoint" "0.15" + "AbilityCooldown" "0" + "AbilityManaCost" "40 35 30 25" + "AbilityCharges" "1 2 3 4" + "AbilityChargeRestoreTime" "8 7 6 5" + + "AbilityValues" + { + "attack_factor" + { + "value" "15 35 55 75" + "special_bonus_unique_assassin_stifling_dagger_damage_base" "+45" + } + "damage" "110 150 190 230" + "max_targets" + { + "value" "0" + "special_bonus_unique_assassin_stifling_dagger_max_targets" "2" + } + "chance" + { + "special_bonus_unique_assassin_stifling_dagger_chance_again" "10" + } + "movement_slow" "-50" + "slow_duration" "1.75 2.5 3.25 4.0" + "dagger_speed" "1200" + + } + } + "ability_phantom_assassin_phantom_strike_custom" + { + "BaseClass" "ability_lua" + "ScriptFile" "abilities/heroes/phantom_assassin/ability_phantom_assassin_phantom_strike_custom.lua" + "AbilityTextureName" "phantom_assassin_phantom_strike" + "AbilityUnitTargetTeam" "DOTA_UNIT_TARGET_TEAM_BOTH" + "AbilityUnitTargetType" "DOTA_UNIT_TARGET_HERO | DOTA_UNIT_TARGET_BASIC" + + "AbilityCastRange" "650 750 850 950" + "AbilityCastPoint" "0.25" + "AbilityManaCost" "25 40 45 50" + + "AbilityValues" + { + "cooldown" "11 9 7 5" + "attack_speed_bonus" "110 145 180 215" + "bonus_duration" "3.5 4 4.5 5" + "lifesteal_percent" + { + "value" "0" + "special_bonus_unique_assassin_phantom_strike_lifesteal" "+25" + } + "crit_chance" + { + "value" "0" + "special_bonus_unique_assassin_phantom_strike_crit" "100" + } + "crit_mult" + { + "value" "0" + "special_bonus_unique_assassin_phantom_strike_crit" "175" + } + } + } + "ability_phantom_assassin_blur_custom" + { + "BaseClass" "ability_lua" + "ScriptFile" "abilities/heroes/phantom_assassin/ability_phantom_assassin_blur_custom.lua" + "AbilityTextureName" "phantom_assassin_blur" + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_NO_TARGET" + "AbilityCooldown" "60" + "AbilityManaCost" "50" + "AbilityCastPoint" "0.25" + + "HasShardUpgrade" "1" + "AbilityValues" + { + "bonus_agility_pct" "15 30 45 60" + "duration" + { + "value" "8 10 12 14" + "special_bonus_unique_assassin_blur_duration_base" "+6" + } + "bonus_agility" + { + "value" "14 26 38 50" + "special_bonus_unique_assassin_blur_agility_base" "+25" + } + "move_speed_pct" "20" + "illusion_outgoing_damage" "115" + "illusion_incoming_damage" "300" + } + } + "ability_phantom_assassin_coup_de_grace_custom" + { + "BaseClass" "ability_lua" + "ScriptFile" "abilities/heroes/phantom_assassin/ability_phantom_assassin_coup_de_grace_custom.lua" + "AbilityTextureName" "phantom_assassin_coup_de_grace" + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_PASSIVE" + "AbilityType" "ABILITY_TYPE_ULTIMATE" + "HasScepterUpgrade" "1" + "MaxLevel" "3" + "AbilityValues" + { + "crit_chance" "15 20 30" + "crit_mult" "250 400 550" + "damage_mult" "0" + "armor_reduction" "2 4 6" + "armor_reduction_agility_pct" "6 8 10" + "ability_cooldown" "20" + "health_mult_decrease" "0" + "duration" "7" + } + } + "ability_phantom_assassin_phantom_bash_custom" + { + "BaseClass" "ability_lua" + "ScriptFile" "abilities/heroes/phantom_assassin/ability_phantom_assassin_phantom_bash_custom.lua" + "AbilityTextureName" "phantom_assassin_phantom_bash" + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_PASSIVE" + "MaxLevel" "1" + "Innate" "1" + "IsBreakable" "1" + "AbilityValues" + { + "cooldown" "16.0" + "bash_duration" "0.15" + "bash_cd_level" "0.10" + } + } + + "special_bonus_unique_assassin_blur_duration_base" + { + "BaseClass" "special_bonus_undefined" + "AbilityType" "ABILITY_TYPE_ATTRIBUTES" + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_PASSIVE" + } + "special_bonus_unique_assassin_blur_agility_base" + { + "BaseClass" "special_bonus_undefined" + "AbilityType" "ABILITY_TYPE_ATTRIBUTES" + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_PASSIVE" + } + "special_bonus_unique_assassin_stifling_dagger_chance_again" + { + // General + //------------------------------------------------------------------------------------------------------------- + "BaseClass" "special_bonus_undefined" + "AbilityType" "ABILITY_TYPE_ATTRIBUTES" + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_PASSIVE" + + } + "special_bonus_unique_assassin_stifling_dagger_damage_base" + { + // General + //------------------------------------------------------------------------------------------------------------- + "BaseClass" "special_bonus_undefined" + "AbilityType" "ABILITY_TYPE_ATTRIBUTES" + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_PASSIVE" + + } + "special_bonus_unique_assassin_phantom_strike_lifesteal" + { + // General + //------------------------------------------------------------------------------------------------------------- + "BaseClass" "special_bonus_undefined" + "AbilityType" "ABILITY_TYPE_ATTRIBUTES" + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_PASSIVE" + + } + "special_bonus_unique_assassin_stifling_dagger_max_targets" + { + "BaseClass" "special_bonus_undefined" + "AbilityType" "ABILITY_TYPE_ATTRIBUTES" + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_PASSIVE" + } + "special_bonus_unique_assassin_phantom_strike_crit" + { + "BaseClass" "special_bonus_undefined" + "AbilityType" "ABILITY_TYPE_ATTRIBUTES" + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_PASSIVE" + } +} \ No newline at end of file diff --git a/scripts/npc/heroes/pudge/pudge.kv b/scripts/npc/heroes/pudge/pudge.kv new file mode 100644 index 0000000..fa63425 --- /dev/null +++ b/scripts/npc/heroes/pudge/pudge.kv @@ -0,0 +1,205 @@ +"DOTAAbilities" +{ + "ability_pudge_meat_hook_custom" + { + "BaseClass" "ability_lua" + "ScriptFile" "abilities/heroes/pudge/ability_pudge_meat_hook_custom.lua" + "AbilityTextureName" "pudge_meat_hook" + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_POINT | DOTA_ABILITY_BEHAVIOR_DIRECTIONAL" + "AbilityUnitDamageType" "DAMAGE_TYPE_PURE" + "AbilityCastPoint" "0.3" + "AbilityCastRange" "900 1050 1200 1350" + "AbilityCooldown" "16 14 12 10" + "AbilityManaCost" "25" + + "AbilityValues" + { + "hook_distance" + { + "value" "900 1050 1200 1350" + "special_bonus_unique_pudge_hook_range" "+250" + } + "hook_speed" "1450" + "hook_width" "110" + "hook_damage" + { + "value" "160 200 240 280" + "special_bonus_unique_pudge_hook_damage" "+120" + } + "max_health_damage_pct" "50 100 150 200" + } + } + + "ability_pudge_rot_custom" + { + "BaseClass" "ability_lua" + "ScriptFile" "abilities/heroes/pudge/ability_pudge_rot_custom.lua" + "AbilityTextureName" "pudge_rot" + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_NO_TARGET | DOTA_ABILITY_BEHAVIOR_TOGGLE" + "AbilityUnitDamageType" "DAMAGE_TYPE_MAGICAL" + "AbilityManaCost" "0" + "AbilityCooldown" "0" + "HasScepterUpgrade" "1" + + "AbilityValues" + { + "tick_interval" "0.2" + "rot_radius" + { + "value" "260 280 300 320" + "special_bonus_unique_pudge_rot_radius" "+80" + } + "rot_damage_per_sec" + { + "value" "45 70 95 120" + "special_bonus_unique_pudge_rot_damage" "+35" + } + "rot_damage_increase_per_sec" "6 8 10 12" + "max_health_damage_pct" "0.6 0.9 1.2 1.5" + "self_damage_pct_per_sec" "3.5" + "rot_slow_pct" "22" + "scepter_bonus_radius" "60" + "scepter_enemy_hp_regen_reduction_pct" "45" + "shard_slow_bonus_pct" "10" + } + } + + "ability_pudge_flesh_heap_custom" + { + "BaseClass" "ability_lua" + "ScriptFile" "abilities/heroes/pudge/ability_pudge_flesh_heap_custom.lua" + "AbilityTextureName" "pudge_flesh_heap" + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_PASSIVE | DOTA_ABILITY_BEHAVIOR_HIDDEN" + "AbilityType" "ABILITY_TYPE_BASIC" + "IsBreakable" "1" + "Innate" "1" + + "AbilityValues" + { + "stack_range" + { + "value" "900" + "special_bonus_unique_pudge_heap_range" "+250" + } + "strength_per_stack" "0.10" + "magic_resist_per_stack" "0.10" + } + } + + "ability_pudge_meat_shield_custom" + { + "BaseClass" "ability_lua" + "ScriptFile" "abilities/heroes/pudge/ability_pudge_meat_shield_custom.lua" + "AbilityTextureName" "pudge_flesh_heap" + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_PASSIVE" + "AbilityType" "ABILITY_TYPE_BASIC" + "IsBreakable" "1" + "HasShardUpgrade" "1" + + "AbilityValues" + { + "block_damage" + { + "value" "12 16 20 24" + "special_bonus_unique_pudge_heap_strength" "+16" + } + "block_damage_vs_creeps" "12 18 24 30" + "shard_block_per_strength" "0.1" + } + } + + "ability_pudge_dismember_custom" + { + "BaseClass" "ability_lua" + "ScriptFile" "abilities/heroes/pudge/ability_pudge_dismember_custom.lua" + "AbilityTextureName" "pudge_dismember" + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_POINT | DOTA_ABILITY_BEHAVIOR_CHANNELLED | DOTA_ABILITY_BEHAVIOR_AOE" + "AbilityUnitDamageType" "DAMAGE_TYPE_MAGICAL" + "AbilityType" "ABILITY_TYPE_ULTIMATE" + "AbilityCastRange" "175 175 175" + "AbilityChannelTime" "2.4 2.8 3.2" + "AbilityCooldown" "0" + "AbilityManaCost" "25" + + + "AbilityValues" + { + "cast_range" + { + "value" "175 175 175" + "special_bonus_unique_pudge_dismember_range" "+125" + } + "channel_time" "2.4 2.8 3.2" + "width" "220" + "pull_speed" "850" + "pulse_interval" "0.25" + "damage_per_second" + { + "value" "130 190 250" + "special_bonus_unique_pudge_dismember_damage" "+70" + } + "strength_damage_pct" "50 100 150" + "max_health_damage_pct" "0.8 1.2 1.6" + "flesh_heap_stack_damage" "1.2" + "hunger_bonus" "5" + "heal_from_damage_pct" "80" + } + "AbilityCastAnimation" "ACT_DOTA_CHANNEL_ABILITY_4" + } + + "special_bonus_unique_pudge_hook_range" + { + "BaseClass" "special_bonus_undefined" + "AbilityType" "ABILITY_TYPE_ATTRIBUTES" + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_PASSIVE" + } + + "special_bonus_unique_pudge_hook_damage" + { + "BaseClass" "special_bonus_undefined" + "AbilityType" "ABILITY_TYPE_ATTRIBUTES" + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_PASSIVE" + } + + "special_bonus_unique_pudge_rot_radius" + { + "BaseClass" "special_bonus_undefined" + "AbilityType" "ABILITY_TYPE_ATTRIBUTES" + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_PASSIVE" + } + + "special_bonus_unique_pudge_rot_damage" + { + "BaseClass" "special_bonus_undefined" + "AbilityType" "ABILITY_TYPE_ATTRIBUTES" + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_PASSIVE" + } + + "special_bonus_unique_pudge_heap_range" + { + "BaseClass" "special_bonus_undefined" + "AbilityType" "ABILITY_TYPE_ATTRIBUTES" + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_PASSIVE" + } + + "special_bonus_unique_pudge_heap_strength" + { + "BaseClass" "special_bonus_undefined" + "AbilityType" "ABILITY_TYPE_ATTRIBUTES" + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_PASSIVE" + } + + "special_bonus_unique_pudge_dismember_range" + { + "BaseClass" "special_bonus_undefined" + "AbilityType" "ABILITY_TYPE_ATTRIBUTES" + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_PASSIVE" + } + + "special_bonus_unique_pudge_dismember_damage" + { + "BaseClass" "special_bonus_undefined" + "AbilityType" "ABILITY_TYPE_ATTRIBUTES" + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_PASSIVE" + } +} diff --git a/scripts/npc/heroes/queenofpain/queenofpain.kv b/scripts/npc/heroes/queenofpain/queenofpain.kv new file mode 100644 index 0000000..db17267 --- /dev/null +++ b/scripts/npc/heroes/queenofpain/queenofpain.kv @@ -0,0 +1,228 @@ +"DOTAAbilities" +{ + "queenofpain_shadow_strike_custom" + { + "BaseClass" "ability_lua" + "ScriptFile" "abilities/heroes/queenofpain/queenofpain_shadow_strike_custom" + "AbilityTextureName" "queenofpain_shadow_strike" + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_UNIT_TARGET | DOTA_ABILITY_BEHAVIOR_AOE" + "AbilityUnitTargetTeam" "DOTA_UNIT_TARGET_TEAM_ENEMY" + "AbilityUnitTargetType" "DOTA_UNIT_TARGET_HERO | DOTA_UNIT_TARGET_BASIC" + "AbilityUnitDamageType" "DAMAGE_TYPE_MAGICAL" + "SpellImmunityType" "SPELL_IMMUNITY_ENEMIES_NO" + "SpellDispellableType" "SPELL_DISPELLABLE_YES" + "HasScepterUpgrade" "1" + + "AbilitySound" "Hero_QueenOfPain.ShadowStrike" + + "AbilityCastRange" "450 500 550 600" + "AbilityCastPoint" "0.3" + + "AbilityCooldown" "13 10 7 4" + + "AbilityValues" + { + "mana_damage_from_current_pct" "50 100 150 200" + "strike_damage" + { + "value" "45 90 135 180" + "special_bonus_scepter" "+50" + } + "duration_damage" "40 90 150 220" + "movement_slow" "-15" + "projectile_speed" "900" + "attack_speed" + { + "special_bonus_unique_queen_of_pain_1" "+45" + } + "damage_interval" + { + "value" "3.0" + "special_bonus_unique_queen_of_pain_4" "-1" + } + "AbilityManaCost" + { + "value" "50 60 70 80" + "special_bonus_scepter" "+80%" + + } + + "radius" + { + "value" "200" + "affected_by_aoe_increase" "1" + } + "duration" "12" + } + "AbilityCastAnimation" "ACT_DOTA_CAST_ABILITY_1" + } + + "queenofpain_blink_custom" + { + "BaseClass" "ability_lua" + "ScriptFile" "abilities/heroes/queenofpain/queenofpain_blink_custom" + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_POINT | DOTA_ABILITY_BEHAVIOR_ROOT_DISABLES | DOTA_ABILITY_BEHAVIOR_AOE" + "AbilitySound" "Hero_QueenOfPain.Blink_in" + "AbilityTextureName" "queenofpain_blink" + "AbilityUnitDamageType" "DAMAGE_TYPE_MAGICAL" + "AbilityCastAnimation" "ACT_DOTA_CAST_ABILITY_2" + + "AbilityCastPoint" "0.33 0.33 0.33 0.33" + + "AbilityManaCost" "65" + + "AbilityValues" + { + "mana_damage_from_current_pct" "50 100 150 200" + "cast_range" "800 900 1000 1100" + + "damage" + { + "value" "40 80 120 160" + "special_bonus_unique_queen_of_pain_2_1" "+60" + } + "duration" "1 1.25 1.5 1.75" + + "radius" + { + "value" "300" + "affected_by_aoe_increase" "1" + } + "AbilityCooldown" + { + "value" "12.0 10.0 8.0 6.0" + "special_bonus_unique_queen_of_pain_6" "-2" + } + } + } + + "queenofpain_scream_of_pain_custom" + { + "BaseClass" "ability_lua" + "ScriptFile" "abilities/heroes/queenofpain/queenofpain_scream_of_pain_custom" + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_NO_TARGET" + "AbilityUnitDamageType" "DAMAGE_TYPE_MAGICAL" + "SpellImmunityType" "SPELL_IMMUNITY_ENEMIES_NO" + "AbilityTextureName" "queenofpain_scream_of_pain" + "AbilitySound" "Hero_QueenOfPain.ScreamOfPain" + "HasShardUpgrade" "1" + + "AbilityCastRange" "0" + "AbilityCastPoint" "0.0 0.0 0.0 0.0" + + "AbilityCooldown" "7.5 7 6.5 6" + + "AbilityManaCost" "120" + + "AbilityValues" + { + "mana_damage_from_current_pct" "50 100 150 200" + "shard_mark_duration" "4.0" + "shard_incoming_damage_pct" "6" + "radius" + { + "value" "600" + "affected_by_aoe_increase" "1" + } + "projectile_speed" "900" + "damage" + { + "value" "100 200 300 400" + "special_bonus_unique_queen_of_pain_2" "+150" + } + + } + "AbilityCastAnimation" "ACT_DOTA_CAST_ABILITY_3" + "AbilityCastGestureSlot" "DEFAULT" + } + + "queenofpain_sonic_wave" + { + "AbilityType" "ABILITY_TYPE_ULTIMATE" + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_DIRECTIONAL | DOTA_ABILITY_BEHAVIOR_POINT" + "SpellImmunityType" "SPELL_IMMUNITY_ENEMIES_YES" + "AbilityUnitDamageType" "DAMAGE_TYPE_PURE" + "FightRecapLevel" "2" + "AbilitySound" "Hero_QueenOfPain.SonicWave" + + "AbilityCastRange" "700" + "AbilityCastPoint" "0.452 0.452 0.452" + "AbilityCastAnimation" "ACT_DOTA_CAST_ABILITY_4" + + "AbilityManaCost" "250 400 550" + + "AbilityValues" + { + "starting_aoe" + { + "value" "100" + } + "distance" + { + "value" "900" + } + "final_aoe" + { + "value" "450" + "affected_by_aoe_increase" "1" + } + "speed" "900" + "damage" + { + "value" "1500 2000 3000" + "special_bonus_unique_queen_of_pain_7" "+500" + } + "tick_rate" "0.1" + "knockback_distance" + { + "value" "350" + "affected_by_aoe_increase" "1" + } + "knockback_duration" "1.4" + "AbilityCooldown" + { + "value" "70 65 60" + "special_bonus_unique_queen_of_pain_3" "-20" + } + } + } + + "queenofpain_agony_innate" + { + "BaseClass" "ability_lua" + "ScriptFile" "abilities/heroes/queenofpain/queenofpain_agony_innate" + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_PASSIVE | DOTA_ABILITY_BEHAVIOR_HIDDEN" + "AbilityType" "ABILITY_TYPE_BASIC" + "Innate" "1" + "MaxLevel" "1" + "IsBreakable" "1" + "AbilityTextureName" "queenofpain_scream_of_pain" + + "AbilityValues" + { + "aura_radius" "2500" + "self_damage_pct_per_level" "1.5" + "spell_amp_bonus" "12" + "spell_amp_per_missing_hp_pct" "0.1" + "mitigation_pct_per_level" + { + "value" "0" + "special_bonus_unique_queen_of_pain_8" "1" + } + } + } + + "special_bonus_unique_queen_of_pain_8" + { + "AbilityType" "ABILITY_TYPE_ATTRIBUTES" + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_PASSIVE" + "BaseClass" "special_bonus_base" + } + + "special_bonus_unique_queen_of_pain_2_1" + { + "AbilityType" "ABILITY_TYPE_ATTRIBUTES" + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_PASSIVE" + "BaseClass" "special_bonus_base" + } +} diff --git a/scripts/npc/heroes/rubick/rubick.kv b/scripts/npc/heroes/rubick/rubick.kv new file mode 100644 index 0000000..e884f50 --- /dev/null +++ b/scripts/npc/heroes/rubick/rubick.kv @@ -0,0 +1,177 @@ +"DOTAAbilities" +{ + "ability_rubick_telekinesis_custom" + { + "BaseClass" "ability_lua" + "ScriptFile" "abilities/heroes/rubick/ability_rubick_telekinesis_custom.lua" + "AbilityTextureName" "rubick_telekinesis" + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_UNIT_TARGET | DOTA_ABILITY_BEHAVIOR_ROOT_DISABLES" + "AbilityUnitTargetTeam" "DOTA_UNIT_TARGET_TEAM_BOTH" + "AbilityUnitTargetType" "DOTA_UNIT_TARGET_HERO | DOTA_UNIT_TARGET_BASIC" + "AbilityUnitDamageType" "DAMAGE_TYPE_MAGICAL" + "SpellImmunityType" "SPELL_IMMUNITY_ENEMIES_NO" + "HasScepterUpgrade" "1" + "FightRecapLevel" "1" + "AbilityCastRange" "625" + "AbilityCastPoint" "0.1" + + "AbilityManaCost" "110" + "AbilityValues" + { + "air_time" "2.5" + "lift_time" "0.25" + "drop_time" "0.07" + "lift_duration" "1.25 1.5 1.75 2.0" + "land_stun_duration" + { + "value" "1.0 1.25 1.5 1.75" + "special_bonus_unique_rubick_telekinesis_stun" "+1.25" + } + "land_stun_radius" + { + "value" "375" + "special_bonus_unique_rubick_telekinesis_land_radius" "+150" + } + "land_damage" "80 160 240 320" + "throw_distance" "375" + "lift_height" "200" + "ally_air_radius" "150" + "ally_air_max_radius" "250" + "ally_air_move_speed_mult" "1.5" + "ally_attack_speed_bonus" "110 170 230 290" + "ally_heal_per_second" "70 90 110 130" + "ally_attack_range_bonus" "100 150 200 250" + "ally_bonus_magic_damage" "180 240 300 360" + "ally_bonus_magic_damage_per_int" "0.5 0.75 1.0 1.25" + "caster_break_range_mult" "2" + "AbilityCharges" + { + "value" "1" + "special_bonus_unique_rubick_telekinesis_charges" "2" + } + "AbilityChargeRestoreTime" "12" + + + } + "AbilityCastAnimation" "ACT_DOTA_CAST_ABILITY_1" + } + "ability_rubick_fade_bolt_custom" + { + "BaseClass" "ability_lua" + "ScriptFile" "abilities/heroes/rubick/ability_rubick_fade_bolt_custom.lua" + "AbilityTextureName" "rubick_fade_bolt" + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_UNIT_TARGET | DOTA_ABILITY_BEHAVIOR_ROOT_DISABLES" + "AbilityUnitTargetTeam" "DOTA_UNIT_TARGET_TEAM_ENEMY" + "AbilityUnitTargetType" "DOTA_UNIT_TARGET_HERO | DOTA_UNIT_TARGET_BASIC" + "AbilityUnitDamageType" "DAMAGE_TYPE_MAGICAL" + "SpellImmunityType" "SPELL_IMMUNITY_ENEMIES_NO" + "SpellDispellableType" "SPELL_DISPELLABLE_YES" + "FightRecapLevel" "1" + "HasShardUpgrade" "1" + "AbilitySound" "Hero_Rubick.FadeBolt.Cast" + "AbilityCastRange" "800" + "AbilityCastPoint" "0.1" + "AbilityCooldown" "16 14 12 10" + "AbilityManaCost" "110 125 140 155" + "AbilityValues" + { + "damage" + { + "value" "150 250 350 450" + "special_bonus_unique_rubick_fade_bolt_damage" "+150" + } + "radius" "440" + "jump_damage_reduction_pct" "6" + "attack_damage_reduction" "5 15 25 35" + "duration" "10" + "jump_delay" "0.25" + "projectile_speed" "1200" + } + "AbilityCastAnimation" "ACT_DOTA_CAST_ABILITY_3" + } + "ability_rubick_arcane_supremacy" + { + "BaseClass" "ability_lua" + "ScriptFile" "abilities/heroes/rubick/ability_rubick_arcane_supremacy.lua" + "AbilityTextureName" "rubick_arcane_supremacy" + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_PASSIVE" + "AbilityValues" + { + "spell_amp" "15 25 35 45" + "spell_amp_pct_lvl" "0.5" + "aura_radius" "600" + "damage_pct" + { + "value" "15 20 25 30" + "special_bonus_unique_rubick_arcane_supremacy_damage_pct" "+15" + } + } + } + "ability_rubick_spellsteal_custom" + { + "BaseClass" "ability_lua" + "ScriptFile" "abilities/heroes/rubick/ability_rubick_spellsteal_custom.lua" + "AbilityTextureName" "rubick_spell_steal" + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_UNIT_TARGET | DOTA_ABILITY_BEHAVIOR_NOT_LEARNABLE" + "AbilityUnitTargetTeam" "DOTA_UNIT_TARGET_TEAM_FRIENDLY" + "AbilityUnitTargetType" "DOTA_UNIT_TARGET_HERO" + "SpellImmunityType" "SPELL_IMMUNITY_ENEMIES_NO" + "FightRecapLevel" "1" + "MaxLevel" "1" + "Innate" "1" + "AbilityCastRange" "900" + "AbilityCastPoint" "0.1" + "AbilityManaCost" "200" + "AbilityValues" + { + "AbilityCooldown" + { + "value" "20" + "special_bonus_unique_rubick_spellsteal_cooldown" "-10" + } + } + "AbilityCastAnimation" "ACT_DOTA_CAST_ABILITY_4" + } + "special_bonus_unique_rubick_telekinesis_stun" + { + "BaseClass" "special_bonus_undefined" + "AbilityType" "ABILITY_TYPE_ATTRIBUTES" + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_PASSIVE" + } + "special_bonus_unique_rubick_fade_bolt_damage" + { + "BaseClass" "special_bonus_undefined" + "AbilityType" "ABILITY_TYPE_ATTRIBUTES" + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_PASSIVE" + } + "special_bonus_unique_rubick_spellsteal_cooldown" + { + "BaseClass" "special_bonus_undefined" + "AbilityType" "ABILITY_TYPE_ATTRIBUTES" + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_PASSIVE" + } + "special_bonus_unique_rubick_arcane_supremacy_damage_pct" + { + "BaseClass" "special_bonus_undefined" + "AbilityType" "ABILITY_TYPE_ATTRIBUTES" + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_PASSIVE" + } + "special_bonus_unique_rubick_telekinesis_land_radius" + { + "BaseClass" "special_bonus_undefined" + "AbilityType" "ABILITY_TYPE_ATTRIBUTES" + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_PASSIVE" + } + "special_bonus_unique_rubick_fade_bolt_convert" + { + "BaseClass" "special_bonus_undefined" + "AbilityType" "ABILITY_TYPE_ATTRIBUTES" + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_PASSIVE" + } + "special_bonus_unique_rubick_telekinesis_charges" + { + "BaseClass" "special_bonus_undefined" + "AbilityType" "ABILITY_TYPE_ATTRIBUTES" + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_PASSIVE" + } +} \ No newline at end of file diff --git a/scripts/npc/heroes/sand_king/sand_king.kv b/scripts/npc/heroes/sand_king/sand_king.kv new file mode 100644 index 0000000..5e81378 --- /dev/null +++ b/scripts/npc/heroes/sand_king/sand_king.kv @@ -0,0 +1,267 @@ +"DOTAAbilities" +{ + + "sandking_burrowstrike_custom" + { + "BaseClass" "ability_lua" + "ScriptFile" "abilities/heroes/sand_king/sandking_burrowstrike_custom.lua" + "AbilityTextureName" "sandking_burrowstrike" + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_POINT | DOTA_ABILITY_BEHAVIOR_ROOT_DISABLES" + "AbilityUnitDamageType" "DAMAGE_TYPE_MAGICAL" + "SpellImmunityType" "SPELL_IMMUNITY_ENEMIES_NO" + "SpellDispellableType" "SPELL_DISPELLABLE_YES_STRONG" + "FightRecapLevel" "1" + "AbilitySound" "Ability.SandKing_BurrowStrike" + "MaxLevel" "4" + "AbilityCastPoint" "0.0" + "AbilityCooldown" "15 14 13 12" + "AbilityManaCost" "15" + "AbilityDamage" "80 150 220 290" + + "AbilityValues" + { + "burrow_anim_time" "0.52" + "burrow_width" + { + "value" "150" + "affected_by_aoe_increase" "1" + } + "burrow_speed" "2000" + "AbilityCastRange" + { + "value" "525 600 675 750" + "special_bonus_unique_sandking_burrow_range" "+75" + } + "stun_duration" + { + "value" "1.6 1.8 2.0 2.2" + "special_bonus_unique_sandking_burrow_stun" "+0.6" + } + "burrow_bonus_damage_max_hp_pct" "10 15 20 25" + } + } + + "sandking_sand_storm_custom" + { + "BaseClass" "ability_lua" + "ScriptFile" "abilities/heroes/sand_king/sandking_sand_storm_custom.lua" + "AbilityTextureName" "sandking_sand_storm" + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_NO_TARGET | DOTA_ABILITY_BEHAVIOR_DONT_RESUME_ATTACK | DOTA_ABILITY_BEHAVIOR_IMMEDIATE" + "AbilityUnitDamageType" "DAMAGE_TYPE_MAGICAL" + "SpellImmunityType" "SPELL_IMMUNITY_ENEMIES_NO" + "SpellDispellableType" "SPELL_DISPELLABLE_NO" + "FightRecapLevel" "1" + "AbilitySound" "Ability.SandKing_SandStorm.start" + "MaxLevel" "4" + "AbilityCastRange" "0" + "AbilityCooldown" "15 13 11 9" + "AbilityCastPoint" "0.0" + "AbilityManaCost" "85" + "AbilityCastAnimation" "ACT_DOTA_CAST_ABILITY_2" + + "AbilityValues" + { + "duration" "25" + "damage_tick_rate" "0.25" + "sand_storm_radius" + { + "value" "425 500 575 650" + "affected_by_aoe_increase" "1" + "special_bonus_unique_sandking_storm_radius" "+75" + } + "sand_storm_damage" + { + "value" "30 50 70 90" + "special_bonus_unique_sandking_storm_damage" "+40" + } + "sand_storm_regen_damage_pct" "4 5 6 7" + "sand_storm_move_speed" "60" + } + } + + "sandking_scorpion_strike_custom" + { + "BaseClass" "ability_lua" + "ScriptFile" "abilities/heroes/sand_king/sandking_scorpion_strike_custom.lua" + "AbilityTextureName" "sandking_scorpion_strike" + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_POINT | DOTA_ABILITY_BEHAVIOR_IGNORE_BACKSWING | DOTA_ABILITY_BEHAVIOR_AOE" + "AbilityUnitDamageType" "DAMAGE_TYPE_PHYSICAL" + "AbilityUnitTargetTeam" "DOTA_UNIT_TARGET_TEAM_ENEMY" + "AbilityUnitTargetType" "DOTA_UNIT_TARGET_HERO | DOTA_UNIT_TARGET_BASIC" + "AbilitySound" "Hero_Sandking.Stinger" + "AbilityCastAnimation" "ACT_DOTA_CAST_ABILITY_3" + "AbilityCastGestureSlot" "DEFAULT" + "MaxLevel" "4" + "AbilityCastRange" "200" + "FightRecapLevel" "1" + "AbilityCastPoint" "0.4" + "AbilityManaCost" "35 40 45 50" + "HasScepterUpgrade" "1" + "AbilityCooldown" "11 9 7 5" + + "AbilityValues" + { + "AbilityCooldown" + { + "value" "11 9 7 5" + "special_bonus_unique_sandking_scorpion_cd" "-2" + } + "radius" + { + "value" "225 255 285 315" + "affected_by_aoe_increase" "1" + } + "inner_radius" + { + "value" "125" + "affected_by_aoe_increase" "1" + } + "inner_radius_bonus_damage_pct" "40" + "attack_damage" "35 70 105 140" + "attack_damage_pct_from_attack" "75" + "debuff_duration" "4 5 6 7" + "strike_slow" "-10 -12 -14 -16" + "scepter_stack" "2" + "scepter_cd_pct" "80" + "scorpion_bonus_damage_max_hp_pct" "10 15 20 25" + } + } + + "sandking_caustic_finale_custom" + { + "BaseClass" "ability_lua" + "ScriptFile" "abilities/heroes/sand_king/sandking_caustic_finale_custom.lua" + "AbilityTextureName" "sandking_caustic_finale" + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_PASSIVE" + "AbilityUnitDamageType" "DAMAGE_TYPE_MAGICAL" + "SpellImmunityType" "SPELL_IMMUNITY_ENEMIES_NO" + "SpellDispellableType" "SPELL_DISPELLABLE_YES" + "AbilityUnitTargetTeam" "DOTA_UNIT_TARGET_TEAM_ENEMY" + "AbilityUnitTargetType" "DOTA_UNIT_TARGET_HERO | DOTA_UNIT_TARGET_BASIC" + "AbilitySound" "Ability.SandKing_CausticFinale" + "Innate" "1" + "MaxLevel" "1" + "IsBreakable" "1" + + "AbilityValues" + { + "caustic_finale_radius" + { + "value" "500" + "affected_by_aoe_increase" "1" + "special_bonus_unique_sandking_finale_radius" "+100" + } + "explosion_damage_per_hero_level" "30" + "kill_explosion_max_hp_pct_base" "5" + "kill_explosion_max_hp_pct_per_level" "0.1" + "caustic_finale_duration" "6" + "max_attacks" "3" + "explosion_slow_pct" "-20" + } + } + + "sandking_epicenter_custom" + { + "BaseClass" "ability_lua" + "ScriptFile" "abilities/heroes/sand_king/sandking_epicenter_custom.lua" + "AbilityTextureName" "sandking_epicenter" + "AbilityType" "ABILITY_TYPE_ULTIMATE" + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_NO_TARGET | DOTA_ABILITY_BEHAVIOR_IGNORE_BACKSWING" + "AbilityUnitDamageType" "DAMAGE_TYPE_MAGICAL" + "SpellImmunityType" "SPELL_IMMUNITY_ENEMIES_NO" + "SpellDispellableType" "SPELL_DISPELLABLE_NO" + "FightRecapLevel" "2" + "AbilitySound" "Ability.SandKing_Epicenter" + "HasShardUpgrade" "1" + "HasScepterUpgrade" "1" + "MaxLevel" "3" + "AbilityCooldown" "40" + "AbilityCastPoint" "2.5" + "AbilityCastAnimation" "ACT_DOTA_CAST_ABILITY_4" + "AbilityManaCost" "90" + + "AbilityValues" + { + "pulse_phase_duration" "6.0" + "epicenter_pulses" + { + "value" "12 16 20" + "special_bonus_unique_sandking_epicenter_pulses" "+4" + } + "epicenter_damage" + { + "value" "60 70 80" + "special_bonus_unique_sandking_epicenter_damage" "+35" + } + "epicenter_radius_base" + { + "value" "500" + "affected_by_aoe_increase" "1" + } + "epicenter_radius_increment" + { + "value" "13" + "affected_by_aoe_increase" "1" + } + "epicenter_slow" "-30 -40 -50" + "epicenter_slow_as" "-50 -55 -60" + "slow_duration" "3" + "scepter_rolls_per_second" "6" + "scepter_proc_every_n_time_checks" "2" + "scepter_luck_per_extra_tail" "10" + "shard_cast_reduction" "0.4" + "shard_break_duration" "3" + "shard_break_count" "4" + "shard_break_radius" "325" + } + } + + "special_bonus_unique_sandking_burrow_range" + { + "BaseClass" "special_bonus_undefined" + "AbilityType" "ABILITY_TYPE_ATTRIBUTES" + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_PASSIVE" + } + "special_bonus_unique_sandking_burrow_stun" + { + "BaseClass" "special_bonus_undefined" + "AbilityType" "ABILITY_TYPE_ATTRIBUTES" + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_PASSIVE" + } + "special_bonus_unique_sandking_storm_radius" + { + "BaseClass" "special_bonus_undefined" + "AbilityType" "ABILITY_TYPE_ATTRIBUTES" + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_PASSIVE" + } + "special_bonus_unique_sandking_storm_damage" + { + "BaseClass" "special_bonus_undefined" + "AbilityType" "ABILITY_TYPE_ATTRIBUTES" + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_PASSIVE" + } + "special_bonus_unique_sandking_scorpion_cd" + { + "BaseClass" "special_bonus_undefined" + "AbilityType" "ABILITY_TYPE_ATTRIBUTES" + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_PASSIVE" + } + "special_bonus_unique_sandking_finale_radius" + { + "BaseClass" "special_bonus_undefined" + "AbilityType" "ABILITY_TYPE_ATTRIBUTES" + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_PASSIVE" + } + "special_bonus_unique_sandking_epicenter_pulses" + { + "BaseClass" "special_bonus_undefined" + "AbilityType" "ABILITY_TYPE_ATTRIBUTES" + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_PASSIVE" + } + "special_bonus_unique_sandking_epicenter_damage" + { + "BaseClass" "special_bonus_undefined" + "AbilityType" "ABILITY_TYPE_ATTRIBUTES" + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_PASSIVE" + } +} diff --git a/scripts/npc/heroes/sargatanas/sargatanas.kv b/scripts/npc/heroes/sargatanas/sargatanas.kv new file mode 100644 index 0000000..77922cf --- /dev/null +++ b/scripts/npc/heroes/sargatanas/sargatanas.kv @@ -0,0 +1,268 @@ +"DOTAAbilities" +{ + "ability_star_devour" + { + "BaseClass" "ability_lua" + "ScriptFile" "abilities/heroes/sargatanas/ability_star_devour.lua" + "AbilityTextureName" "old_heroes/devour_sargatanas" + "FightRecapLevel" "1" + "MaxLevel" "4" + + "AbilityType" "DOTA_ABILITY_TYPE_BASIC" + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_UNIT_TARGET | DOTA_ABILITY_BEHAVIOR_DONT_RESUME_ATTACK" + "AbilityUnitTargetTeam" "DOTA_UNIT_TARGET_TEAM_CUSTOM" + "AbilityUnitTargetType" "DOTA_UNIT_TARGET_CUSTOM" + "AbilityUnitTargetFlags" "DOTA_UNIT_TARGET_FLAG_NOT_CREEP_HERO" + "SpellDispellableType" "SPELL_DISPELLABLE_NO" + "LinkedAbility" "ability_hell_summon" + + "AbilityCharges" "3" + + "AbilityCastRange" "300 300 300 300" + "AbilityCastPoint" "0.3 0.3 0.3 0.3" + + "AbilityCooldown" "2" + "AbilityManaCost" "40 50 60 70" + + "AbilityValues" + { + "bonus_gold" "0" + + "devour_time" "0" + + "regen" "1 5 9 13" + + "creep_level" "1 5 10 15 20" + + + "max_stack" + { + "value" "3 4 5 6" + "special_bonus_unique_sargatanas_max_stack" "+6" + } + + + "AbilityChargeRestoreTime" "24 18 14 8" + + } + } + "ability_hellstep" + { + "BaseClass" "ability_lua" + "ScriptFile" "abilities/heroes/sargatanas/ability_hellstep.lua" + "AbilityTextureName" "old_heroes/hellstep" + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_POINT | DOTA_ABILITY_BEHAVIOR_AOE" + "AbilityUnitDamageType" "DAMAGE_TYPE_MAGICAL" + "SpellImmunityType" "SPELL_IMMUNITY_ENEMIES_NO" + + "AbilityUnitTargetTeam" "DOTA_UNIT_TARGET_TEAM_FRIENDLY" + "AbilityUnitTargetType" "DOTA_UNIT_TARGET_HERO" + "MaxLevel" "4" + // Casting + //------------------------------------------------------------------------------------------------------------- + "AbilityCastPoint" "0.2" + "AbilityCastRange" "900" + + // Time + //------------------------------------------------------------------------------------------------------------- + "AbilityCooldown" "30 25 20 15" + + // Cost + //------------------------------------------------------------------------------------------------------------- + "AbilityManaCost" "100 120 140 180" + + + // Special + //------------------------------------------------------------------------------------------------------------- + "AbilityValues" + { + + "min_damage" "15 20 25 30" + "max_damage" "40 45 50 55" + + "max_duration" "6" + "radius" "400" + + "projectile_speed" "2000" + "stack_overhell" "1 2 3 4" + + "duration" + { + "value" "6 8 10 12" + "special_bonus_unique_sargatanas_duration_1" "+4" + } + + "bonus_damage" "80 140 180 220" + "bonus_attack_speed" "30 60 90 120" + + } + "AbilityCastAnimation" "ACT_DOTA_CAST_ABILITY_2" + } + "ability_firecleave" + { + "BaseClass" "ability_lua" + "ScriptFile" "abilities/heroes/sargatanas/ability_firecleave.lua" + "AbilityTextureName" "old_heroes/firecleave" + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_PASSIVE" + "SpellImmunityType" "SPELL_IMMUNITY_ENEMIES_YES" + "AbilityUnitDamageType" "DAMAGE_TYPE_PURE" + "MaxLevel" "7" + + // Special + //------------------------------------------------------------------------------------------------------------- + "AbilityValues" + { + + "cleave_starting_width" "150" + + "cleave_ending_width" "360" + + "cleave_distance" "650" + + "great_cleave_damage" "40 70 90 110" + + "fire_damage" "2 4 8 16" + + "duration" "8" + + } + "AbilityCastAnimation" "ACT_DOTA_CAST_ABILITY_2" + } + "ability_sunflame" + { + "BaseClass" "ability_lua" + "ScriptFile" "abilities/heroes/sargatanas/ability_sunflame.lua" + "AbilityTextureName" "old_heroes/sunflame" + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_POINT" + "SpellImmunityType" "SPELL_IMMUNITY_ENEMIES_YES" + "AbilityUnitTargetFlags" "DOTA_UNIT_TARGET_FLAG_MAGIC_IMMUNE_ENEMIES" + "AbilityUnitDamageType" "DAMAGE_TYPE_MAGICAL" + "SpellDispellableType" "SPELL_DISPELLABLE_YES" + "SpellImmunityType" "SPELL_IMMUNITY_ENEMIES_NO" + "RequiredLevel" "15" + "MaxLevel" "1" + + "AbilityCooldown" "14" + "AbilityManaCost" "500" + + + // Special + //------------------------------------------------------------------------------------------------------------- + "AbilityValues" + { + + "start_radius" "150 150 150 150" + + "end_radius" "250 250 250 250" + + "range" "800" + + "speed" "800" + + "stack_overhell" "110" + + "damage" "20000" + + "unduced" "34" + + "damage_meta" "50000" + + "unduced_meta" "67" + + } + "AbilityCastAnimation" "ACT_DOTA_CAST_ABILITY_2" + } + "ability_hell_summon" + { + "BaseClass" "ability_lua" + "ScriptFile" "abilities/heroes/sargatanas/ability_hell_summon.lua" + "AbilityTextureName" "old_heroes/hell_summon" + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_NO_TARGET" + "SpellImmunityType" "SPELL_IMMUNITY_ENEMIES_YES" + + "LinkedAbility" "ability_star_devour" + + "AbilityCooldown" "30 28 26 24" + + // Special + //------------------------------------------------------------------------------------------------------------- + "AbilityValues" + { + "str_dmg" + { + "value" "1 2 3 4 5" + "special_bonus_unique_sargatanas_str_dmg" "+0.75" + } + + "str_hp" "10" + + + } + } + + "ability_metamorphosis" + { + "BaseClass" "ability_lua" + "ScriptFile" "abilities/heroes/sargatanas/ability_metamorphosis.lua" + "AbilityTextureName" "old_heroes/revolve_metamorphosis" + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_NO_TARGET" + "SpellImmunityType" "SPELL_IMMUNITY_ENEMIES_YES" + "MaxLevel" "4" + "LevelsBetweenUpgrades" "5" + "RequiredLevel" "10" + + "AbilityCooldown" "40 36 32 28" + "AbilityManaCost" "40 50 60 70" + + // Special + //------------------------------------------------------------------------------------------------------------- + "AbilityValues" + { + + "duration" + { + "value" "20 24 28 32" + "special_sargatanas_meta" "+10" + } + "bonus_resistance" "1" + "max_resistance" + { + "value" "24 31 39 44" + "special_sargatanas_meta" "+6" + } + + "radius" "500" + + "armor_ag" "0.2 0.4 0.6 0.8" + "attack_speed_ag" "0.35" + + } + "AbilityCastAnimation" "ACT_DOTA_CAST_ABILITY_2" + } + + "special_bonus_unique_sargatanas_max_stack" + { + // General + //------------------------------------------------------------------------------------------------------------- + "BaseClass" "special_bonus_undefined" + "AbilityType" "ABILITY_TYPE_ATTRIBUTES" + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_PASSIVE" + } + "special_bonus_unique_sargatanas_duration_1" + { + // General + //------------------------------------------------------------------------------------------------------------- + "BaseClass" "special_bonus_undefined" + "AbilityType" "ABILITY_TYPE_ATTRIBUTES" + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_PASSIVE" + } + "special_bonus_unique_sargatanas_str_dmg" + { + // General + //------------------------------------------------------------------------------------------------------------- + "BaseClass" "special_bonus_undefined" + "AbilityType" "ABILITY_TYPE_ATTRIBUTES" + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_PASSIVE" + } + +} diff --git a/scripts/npc/heroes/sargatanas/summons.kv b/scripts/npc/heroes/sargatanas/summons.kv new file mode 100644 index 0000000..ddbc24f --- /dev/null +++ b/scripts/npc/heroes/sargatanas/summons.kv @@ -0,0 +1,398 @@ +"DOTAUnits" +{ + "npc_spirit_sargatanas_hell_summon" + { + // General + // + "BaseClass" "npc_dota_creature" // Class of entity of link to. + "Model" "models/heroes/brewmaster/brewmaster_firespirit.vmdl" // Model. + "SoundSet" "Creep_Good_Range" // Name of sound set. + "ModelScale" "0.75" + "Level" "1" + "IsSummoned" "1" + "SelectionGroup" "Spirits" + "UnitLabel" "Spirits" + + // Abilities + //---------------------------------------------------------------- + + "Ability1" "ability_firecleave_creep" // Ability 1. + "Ability2" "" // Ability 2. + "Ability3" "" // Ability 3. + "Ability4" "" // Ability 4. + "Ability5" "" // Ability 5. + "Ability6" "" // Ability 6 - Extra. + "Ability7" "" // Ability 7 - Extra. + "Ability8" "" // Ability 8 - Extra. + + // Armor + //---------------------------------------------------------------- + "ArmorPhysical" "2" // Physical protection. + "MagicalResistance" "40" // Magical protection. + + // Attack + //---------------------------------------------------------------- + "AttackCapabilities" "DOTA_UNIT_CAP_RANGED_ATTACK" + "AttackDamageMin" "2" // Damage range min. + "AttackDamageMax" "6" // Damage range max. + "AttackDamageType" "DAMAGE_TYPE_ArmorPhysical" + "AttackRate" "1.8" // Speed of attack. + "AttackAnimationPoint" "0.2" // Normalized time in animation cycle to attack. + "AttackAcquisitionRange" "800" // Range within a target can be acquired. + "AttackRange" "500" // Range within a target can be attacked. + "ProjectileModel" "particles/units/heroes/hero_lina/lina_base_attack.vpcf" // Particle system model for projectile. + "ProjectileSpeed" "900" // Speed of projectile. + + // Bounty + //---------------------------------------------------------------- + "BountyXP" "10" // Experience earn. + "BountyGoldMin" "18" // Gold earned min. + "BountyGoldMax" "24" // Gold earned max. + + // Bounds + //---------------------------------------------------------------- + "BoundsHullName" "DOTA_HULL_SIZE_SMALL" // Hull type used for navigation/locomotion. + "HealthBarOffset" "130" + + // Movement + //---------------------------------------------------------------- + "MovementCapabilities" "DOTA_UNIT_CAP_MOVE_GROUND" // Type of locomotion - ground, air + "MovementSpeed" "280" // Speed + "MovementTurnRate" "0.9" // Turning rate. + + // Status + //---------------------------------------------------------------- + "StatusHealth" "170" // Base health. + "StatusHealthRegen" "4" // Health regeneration rate. + "StatusMana" "0" // Base mana. + "StatusManaRegen" "0" // Mana regeneration rate. + + // Team + //---------------------------------------------------------------- + "TeamName" "DOTA_TEAM_GOODGUYS" // Team name. + "CombatClassAttack" "DOTA_COMBAT_CLASS_ATTACK_HERO" + "CombatClassDefend" "DOTA_COMBAT_CLASS_DEFEND_HERO" + "UnitRelationshipClass" "DOTA_NPC_UNIT_RELATIONSHIP_TYPE_DEFAULT" + + // Vision + //---------------------------------------------------------------- + "VisionDaytimeRange" "1200" // Range of vision during day light. + "VisionNighttimeRange" "800" // Range of vision at night time. + } + "npc_golem_sargatanas_hell_summon" + { + // General + // + "BaseClass" "npc_dota_creature" // Class of entity of link to. + "Model" "models/items/warlock/golem/ti_8_warlock_darkness_apostate_golem/ti_8_warlock_darkness_apostate_golem.vmdl" // Model. + "SoundSet" "Creep_Good_Range" // Name of sound set. + "ModelScale" "0.55" + "Level" "1" + "IsSummoned" "1" + "SelectionGroup" "Spirits" + "UnitLabel" "Spirits" + + // Abilities + //---------------------------------------------------------------- + + "Ability1" "ability_firecleave_creep" // Ability 1. + "Ability2" "" // Ability 2. + "Ability3" "" // Ability 3. + "Ability4" "" // Ability 4. + "Ability5" "" // Ability 5. + "Ability6" "" // Ability 6 - Extra. + "Ability7" "" // Ability 7 - Extra. + "Ability8" "" // Ability 8 - Extra. + + // Armor + //---------------------------------------------------------------- + "ArmorPhysical" "2" // Physical protection. + "MagicalResistance" "40" // Magical protection. + + // Attack + //---------------------------------------------------------------- + "AttackCapabilities" "DOTA_UNIT_CAP_RANGED_ATTACK" + "AttackDamageMin" "54" // Damage range min. + "AttackDamageMax" "57" // Damage range max. + "AttackDamageType" "DAMAGE_TYPE_ArmorPhysical" + "AttackRate" "1.5" // Speed of attack. + "AttackAnimationPoint" "0.2" // Normalized time in animation cycle to attack. + "AttackAcquisitionRange" "800" // Range within a target can be acquired. + "AttackRange" "500" // Range within a target can be attacked. + "ProjectileModel" "particles/units/heroes/hero_lina/lina_base_attack.vpcf" // Particle system model for projectile. + "ProjectileSpeed" "900" // Speed of projectile. + + // Bounty + //---------------------------------------------------------------- + "BountyXP" "10" // Experience earn. + "BountyGoldMin" "18" // Gold earned min. + "BountyGoldMax" "24" // Gold earned max. + + // Bounds + //---------------------------------------------------------------- + "BoundsHullName" "DOTA_HULL_SIZE_SMALL" // Hull type used for navigation/locomotion. + "HealthBarOffset" "130" + + // Movement + //---------------------------------------------------------------- + "MovementCapabilities" "DOTA_UNIT_CAP_MOVE_GROUND" // Type of locomotion - ground, air + "MovementSpeed" "280" // Speed + "MovementTurnRate" "0.9" // Turning rate. + + // Status + //---------------------------------------------------------------- + "StatusHealth" "260" // Base health. + "StatusHealthRegen" "4" // Health regeneration rate. + "StatusMana" "0" // Base mana. + "StatusManaRegen" "0" // Mana regeneration rate. + + // Team + //---------------------------------------------------------------- + "TeamName" "DOTA_TEAM_GOODGUYS" // Team name. + "CombatClassAttack" "DOTA_COMBAT_CLASS_ATTACK_HERO" + "CombatClassDefend" "DOTA_COMBAT_CLASS_DEFEND_HERO" + "UnitRelationshipClass" "DOTA_NPC_UNIT_RELATIONSHIP_TYPE_DEFAULT" + + // Vision + //---------------------------------------------------------------- + "VisionDaytimeRange" "1200" // Range of vision during day light. + "VisionNighttimeRange" "800" // Range of vision at night time. + } + "npc_scorpion_sargatanas_hell_summon" + { + // General + // + "BaseClass" "npc_dota_creature" // Class of entity of link to. + "Model" "models/items/broodmother/spiderling/ti9_cache_brood_mother_of_thousands_spiderling/ti9_cache_brood_mother_of_thousands_spiderling.vmdl" // Model. + "SoundSet" "Creep_Good_Range" // Name of sound set. + "ModelScale" "0.75" + "Level" "1" + "IsSummoned" "1" + "SelectionGroup" "Spirits" + "UnitLabel" "Spirits" + + // Abilities + //---------------------------------------------------------------- + + "Ability1" "ability_firecleave_creep" // Ability 1. + "Ability2" "" // Ability 2. + "Ability3" "" // Ability 3. + "Ability4" "" // Ability 4. + "Ability5" "" // Ability 5. + "Ability6" "" // Ability 6 - Extra. + "Ability7" "" // Ability 7 - Extra. + "Ability8" "" // Ability 8 - Extra. + + // Armor + //---------------------------------------------------------------- + "ArmorPhysical" "2" // Physical protection. + "MagicalResistance" "40" // Magical protection. + + // Attack + //---------------------------------------------------------------- + "AttackCapabilities" "DOTA_UNIT_CAP_RANGED_ATTACK" + "AttackDamageMin" "106" // Damage range min. + "AttackDamageMax" "117" // Damage range max. + "AttackDamageType" "DAMAGE_TYPE_ArmorPhysical" + "AttackRate" "1.5" // Speed of attack. + "AttackAnimationPoint" "0.2" // Normalized time in animation cycle to attack. + "AttackAcquisitionRange" "800" // Range within a target can be acquired. + "AttackRange" "500" // Range within a target can be attacked. + "ProjectileModel" "particles/units/heroes/hero_lina/lina_base_attack.vpcf" // Particle system model for projectile. + "ProjectileSpeed" "900" // Speed of projectile. + + // Bounty + //---------------------------------------------------------------- + "BountyXP" "10" // Experience earn. + "BountyGoldMin" "18" // Gold earned min. + "BountyGoldMax" "24" // Gold earned max. + + // Bounds + //---------------------------------------------------------------- + "BoundsHullName" "DOTA_HULL_SIZE_SMALL" // Hull type used for navigation/locomotion. + "HealthBarOffset" "130" + + // Movement + //---------------------------------------------------------------- + "MovementCapabilities" "DOTA_UNIT_CAP_MOVE_GROUND" // Type of locomotion - ground, air + "MovementSpeed" "280" // Speed + "MovementTurnRate" "0.9" // Turning rate. + + // Status + //---------------------------------------------------------------- + "StatusHealth" "1450" // Base health. + "StatusHealthRegen" "4" // Health regeneration rate. + "StatusMana" "0" // Base mana. + "StatusManaRegen" "0" // Mana regeneration rate. + + // Team + //---------------------------------------------------------------- + "TeamName" "DOTA_TEAM_GOODGUYS" // Team name. + "CombatClassAttack" "DOTA_COMBAT_CLASS_ATTACK_HERO" + "CombatClassDefend" "DOTA_COMBAT_CLASS_DEFEND_HERO" + "UnitRelationshipClass" "DOTA_NPC_UNIT_RELATIONSHIP_TYPE_DEFAULT" + + // Vision + //---------------------------------------------------------------- + "VisionDaytimeRange" "1200" // Range of vision during day light. + "VisionNighttimeRange" "800" // Range of vision at night time. + } + "npc_dragon_sargatanas_hell_summon" + { + // General + // + "BaseClass" "npc_dota_creature" // Class of entity of link to. + "Model" "models/creeps/neutral_creeps/n_creep_black_dragon/n_creep_black_dragon.vmdl" // Model. + "SoundSet" "Creep_Good_Range" // Name of sound set. + "ModelScale" "0.75" + "Level" "1" + "IsSummoned" "1" + "SelectionGroup" "Spirits" + "UnitLabel" "Spirits" + + // Abilities + //---------------------------------------------------------------- + + "Ability1" "ability_firecleave_creep" // Ability 1. + "Ability2" "" // Ability 2. + "Ability3" "" // Ability 3. + "Ability4" "" // Ability 4. + "Ability5" "" // Ability 5. + "Ability6" "" // Ability 6 - Extra. + "Ability7" "" // Ability 7 - Extra. + "Ability8" "" // Ability 8 - Extra. + + // Armor + //---------------------------------------------------------------- + "ArmorPhysical" "8" // Physical protection. + "MagicalResistance" "40" // Magical protection. + + // Attack + //---------------------------------------------------------------- + "AttackCapabilities" "DOTA_UNIT_CAP_RANGED_ATTACK" + "AttackDamageMin" "140" // Damage range min. + "AttackDamageMax" "160" // Damage range max. + "AttackDamageType" "DAMAGE_TYPE_ArmorPhysical" + "AttackRate" "1.5" // Speed of attack. + "AttackAnimationPoint" "0.2" // Normalized time in animation cycle to attack. + "AttackAcquisitionRange" "800" // Range within a target can be acquired. + "AttackRange" "500" // Range within a target can be attacked. + "ProjectileModel" "particles/units/heroes/hero_lina/lina_base_attack.vpcf" // Particle system model for projectile. + "ProjectileSpeed" "900" // Speed of projectile. + + // Bounty + //---------------------------------------------------------------- + "BountyXP" "10" // Experience earn. + "BountyGoldMin" "18" // Gold earned min. + "BountyGoldMax" "24" // Gold earned max. + + // Bounds + //---------------------------------------------------------------- + "BoundsHullName" "DOTA_HULL_SIZE_SMALL" // Hull type used for navigation/locomotion. + "HealthBarOffset" "130" + + // Movement + //---------------------------------------------------------------- + "MovementCapabilities" "DOTA_UNIT_CAP_MOVE_GROUND" // Type of locomotion - ground, air + "MovementSpeed" "280" // Speed + "MovementTurnRate" "0.9" // Turning rate. + + // Status + //---------------------------------------------------------------- + "StatusHealth" "670" // Base health. + "StatusHealthRegen" "4" // Health regeneration rate. + "StatusMana" "0" // Base mana. + "StatusManaRegen" "0" // Mana regeneration rate. + + // Team + //---------------------------------------------------------------- + "TeamName" "DOTA_TEAM_GOODGUYS" // Team name. + "CombatClassAttack" "DOTA_COMBAT_CLASS_ATTACK_HERO" + "CombatClassDefend" "DOTA_COMBAT_CLASS_DEFEND_HERO" + "UnitRelationshipClass" "DOTA_NPC_UNIT_RELATIONSHIP_TYPE_DEFAULT" + + // Vision + //---------------------------------------------------------------- + "VisionDaytimeRange" "1200" // Range of vision during day light. + "VisionNighttimeRange" "800" // Range of vision at night time. + } + "npc_kaban_sargatanas_hell_summon" + { + // General + // + "BaseClass" "npc_dota_creature" // Class of entity of link to. + "Model" "models/heroes/beastmaster/beastmaster_beast.vmdl" // Model. + "SoundSet" "Creep_Good_Range" // Name of sound set. + "ModelScale" "0.75" + "Level" "1" + "IsSummoned" "1" + "SelectionGroup" "Spirits" + "UnitLabel" "Spirits" + + // Abilities + //---------------------------------------------------------------- + + "Ability1" "ability_firecleave_creep" // Ability 1. + "Ability2" "ghoul_crazy_look" // Ability 2. + "Ability3" "" // Ability 3. + "Ability4" "" // Ability 4. + "Ability5" "" // Ability 5. + "Ability6" "" // Ability 6 - Extra. + "Ability7" "" // Ability 7 - Extra. + "Ability8" "" // Ability 8 - Extra. + + // Armor + //---------------------------------------------------------------- + "ArmorPhysical" "22" // Physical protection. + "MagicalResistance" "80" // Magical protection. + + // Attack + //---------------------------------------------------------------- + "AttackCapabilities" "DOTA_UNIT_CAP_RANGED_ATTACK" + "AttackDamageMin" "280" // Damage range min. + "AttackDamageMax" "292" // Damage range max. + "AttackDamageType" "DAMAGE_TYPE_ArmorPhysical" + "AttackRate" "1.2" // Speed of attack. + "AttackAnimationPoint" "0.2" // Normalized time in animation cycle to attack. + "AttackAcquisitionRange" "800" // Range within a target can be acquired. + "AttackRange" "700" // Range within a target can be attacked. + "ProjectileModel" "particles/units/heroes/hero_lina/lina_base_attack.vpcf" // Particle system model for projectile. + "ProjectileSpeed" "900" // Speed of projectile. + + // Bounty + //---------------------------------------------------------------- + "BountyXP" "10" // Experience earn. + "BountyGoldMin" "18" // Gold earned min. + "BountyGoldMax" "24" // Gold earned max. + + // Bounds + //---------------------------------------------------------------- + "BoundsHullName" "DOTA_HULL_SIZE_SMALL" // Hull type used for navigation/locomotion. + "HealthBarOffset" "130" + + // Movement + //---------------------------------------------------------------- + "MovementCapabilities" "DOTA_UNIT_CAP_MOVE_GROUND" // Type of locomotion - ground, air + "MovementSpeed" "550" // Speed + "MovementTurnRate" "0.25" // Turning rate. + + // Status + //---------------------------------------------------------------- + "StatusHealth" "1270" // Base health. + "StatusHealthRegen" "14" // Health regeneration rate. + "StatusMana" "0" // Base mana. + "StatusManaRegen" "0" // Mana regeneration rate. + + // Team + //---------------------------------------------------------------- + "TeamName" "DOTA_TEAM_GOODGUYS" // Team name. + "CombatClassAttack" "DOTA_COMBAT_CLASS_ATTACK_HERO" + "CombatClassDefend" "DOTA_COMBAT_CLASS_DEFEND_HERO" + "UnitRelationshipClass" "DOTA_NPC_UNIT_RELATIONSHIP_TYPE_DEFAULT" + + // Vision + //---------------------------------------------------------------- + "VisionDaytimeRange" "1200" // Range of vision during day light. + "VisionNighttimeRange" "800" // Range of vision at night time. + } +} \ No newline at end of file diff --git a/scripts/npc/heroes/silencer/silencer.kv b/scripts/npc/heroes/silencer/silencer.kv new file mode 100644 index 0000000..5ad0cc8 --- /dev/null +++ b/scripts/npc/heroes/silencer/silencer.kv @@ -0,0 +1,212 @@ +"DOTAAbilities" +{ + "silencer_curse_of_the_silent" + { + "BaseClass" "ability_lua" + "ScriptFile" "abilities/heroes/silencer/silencer_curse_of_the_silent" + "AbilityTextureName" "silencer_curse_of_the_silent" + "AbilityType" "DOTA_ABILITY_TYPE_BASIC" + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_POINT | DOTA_ABILITY_BEHAVIOR_AOE" + "SpellImmunityType" "SPELL_IMMUNITY_ENEMIES_NO" + "AbilityUnitDamageType" "DAMAGE_TYPE_PURE" + "SpellDispellableType" "SPELL_DISPELLABLE_YES" + "AbilitySound" "Hero_Silencer.Curse.Cast" + "AbilityCastRange" "1000" + "AbilityCastPoint" "0.3" + "AbilityCooldown" "22 20 18 16" + "AbilityManaCost" "130 135 140 145" + "AbilityValues" + { + "damage" + { + "value" "20 40 60 80" + "special_bonus_unique_silencer" "+120" + } + "radius" "350" + "duration" "6" + "penalty_duration" "2" + "movespeed" "-10" + } + } + + "glaives_of_wisdom" + { + "BaseClass" "ability_lua" + "ScriptFile" "abilities/heroes/silencer/glaives_of_wisdom" + "AbilityTextureName" "silencer_glaives_of_wisdom" + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_PASSIVE" + "AbilityUnitTargetTeam" "DOTA_UNIT_TARGET_TEAM_ENEMY" + "AbilityUnitTargetType" "DOTA_UNIT_TARGET_HERO | DOTA_UNIT_TARGET_BASIC" + "SpellImmunityType" "SPELL_IMMUNITY_ENEMIES_NO" + "AbilityUnitDamageType" "DAMAGE_TYPE_PURE" + "AbilitySound" "Hero_Silencer.GlaivesOfWisdom" + "AbilityManaCost" "0" + "AbilityCooldown" "0" + "AbilityCastRange" "0" + "AbilityValues" + { + "intellect_damage_pct" + { + "value" "100 150 200 250" + "special_bonus_unique_silencer_intellect_damage_pct" "+150" + } + } + } + + "razor_eye_of_the_storm_lua" + { + "BaseClass" "ability_lua" + "ScriptFile" "abilities/heroes/silencer/razor_eye_of_the_storm_lua" + "AbilityTextureName" "new_heroes/mind_storm" + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_NO_TARGET | DOTA_ABILITY_BEHAVIOR_IMMEDIATE" + "AbilityUnitDamageType" "DAMAGE_TYPE_PURE" + "SpellImmunityType" "SPELL_IMMUNITY_ENEMIES_YES" + "AbilityCastPoint" "0" + "AbilityCooldown" "30 28 26 24" + "AbilityManaCost" "60 100 140 180" + "HasScepterUpgrade" "1" + "AbilityValues" + { + "radius" "500" + "scepter_bonus_targets" "3" + "duration" + { + "value" "20" + "special_bonus_unique_silencer_storm_duration" "+6" + } + "strike_interval" + { + "value" "0.75" + "special_bonus_unique_silencer_storm_interval" "-0.5" + } + "armor_reduction" "1" + "damage" "75 100 125 150" + } + } + + "int" + { + "BaseClass" "ability_lua" + "ScriptFile" "abilities/heroes/silencer/int" + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_PASSIVE" + "AbilityTextureName" "new_heroes/sil_glaive" + "MaxLevel" "1" + "innate" "1" + "AbilityValues" + { + "grow_int" + { + "value" "1.4" + "special_bonus_unique_silencer_grow_int" "+0.6" + } + "stack_interval" + { + "value" "10" + "special_bonus_unique_silencer_stack_interval" "-5" + } + } + } + + "ability_last_word" + { + "BaseClass" "ability_lua" + "ScriptFile" "abilities/heroes/silencer/ability_last_word" + "AbilityTextureName" "silencer_last_word" + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_UNIT_TARGET" + "AbilityUnitTargetTeam" "DOTA_UNIT_TARGET_TEAM_ENEMY" + "AbilityUnitTargetType" "DOTA_UNIT_TARGET_HERO | DOTA_UNIT_TARGET_BASIC" + "AbilityUnitDamageType" "DAMAGE_TYPE_PURE" + "SpellImmunityType" "SPELL_IMMUNITY_ENEMIES_NO" + "MaxLevel" "3" + "RequiredLevel" "7" + "LevelsBetweenUpgrades" "7" + "AbilityCastPoint" "0.3" + "AbilityCastRange" "900" + "AbilityCooldown" "0" + "AbilityManaCost" "115" + "AbilityCharges" "3" + "AbilityChargeRestoreTime" "20" + "HasShardUpgrade" "1" + "AbilityValues" + { + "damage" "225 450 675" + "debuff_duration" "4" + "duration" "4" + "shard_bonus_int" "50" + "int" + { + "value" "150" + "special_bonus_shard" "+50" + } + } + "AbilityCastAnimation" "ACT_DOTA_CAST_ABILITY_3" + } + + "ability_global_silence" + { + "BaseClass" "ability_lua" + "ScriptFile" "abilities/heroes/silencer/ability_global_silence" + "AbilityTextureName" "silencer_global_silence" + "AbilityType" "ABILITY_TYPE_ULTIMATE" + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_NO_TARGET" + "SpellDispellableType" "SPELL_DISPELLABLE_YES" + "SpellImmunityType" "SPELL_IMMUNITY_ENEMIES_YES" + "AbilityCastPoint" "0.3" + "MaxLevel" "3" + "AbilityCooldown" "90" + "AbilityManaCost" "1200" + "AbilityValues" + { + "tooltip_duration" "6 7 8" + "icnoming_enemy" "20 40 60" + } + "AbilityCastAnimation" "ACT_DOTA_CAST_ABILITY_4" + } + + "special_bonus_unique_silencer_intellect_damage_pct" + { + "AbilityType" "ABILITY_TYPE_ATTRIBUTES" + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_PASSIVE" + "BaseClass" "special_bonus_base" + } + "special_bonus_unique_silencer_grow_int" + { + "AbilityType" "ABILITY_TYPE_ATTRIBUTES" + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_PASSIVE" + "BaseClass" "special_bonus_base" + } + "special_bonus_unique_silencer_stack_interval" + { + "AbilityType" "ABILITY_TYPE_ATTRIBUTES" + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_PASSIVE" + "BaseClass" "special_bonus_base" + } + "special_bonus_unique_silencer_storm_interval" + { + "AbilityType" "ABILITY_TYPE_ATTRIBUTES" + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_PASSIVE" + "BaseClass" "special_bonus_base" + } + "special_bonus_unique_silencer_storm_duration" + { + "AbilityType" "ABILITY_TYPE_ATTRIBUTES" + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_PASSIVE" + "BaseClass" "special_bonus_base" + } + "special_bonus_unique_silencer_glaives_bounces" + { + "AbilityType" "ABILITY_TYPE_ATTRIBUTES" + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_PASSIVE" + "BaseClass" "special_bonus_base" + "AbilityValues" + { + "bonus_bounce_count" "2" + } + } + "special_bonus_unique_silencer" + { + "AbilityType" "ABILITY_TYPE_ATTRIBUTES" + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_PASSIVE" + "BaseClass" "special_bonus_base" + } +} diff --git a/scripts/npc/heroes/skywrath_mage/skywrath_mage.kv b/scripts/npc/heroes/skywrath_mage/skywrath_mage.kv new file mode 100644 index 0000000..7b505ec --- /dev/null +++ b/scripts/npc/heroes/skywrath_mage/skywrath_mage.kv @@ -0,0 +1,259 @@ +"DOTAAbilities" +{ + "skywrath_mage_arcane_bolt_custom" + { + "BaseClass" "ability_lua" + "ScriptFile" "abilities/heroes/skywrath_mage/skywrath_mage_arcane_bolt_custom" + "AbilityTextureName" "skywrath_mage_arcane_bolt" + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_UNIT_TARGET | DOTA_ABILITY_BEHAVIOR_AOE" + "AbilityUnitTargetTeam" "DOTA_UNIT_TARGET_TEAM_ENEMY" + "AbilityUnitTargetType" "DOTA_UNIT_TARGET_HERO | DOTA_UNIT_TARGET_BASIC" + "AbilityUnitDamageType" "DAMAGE_TYPE_MAGICAL" + "SpellImmunityType" "SPELL_IMMUNITY_ENEMIES_NO" + "AbilitySound" "Hero_SkywrathMage.ArcaneBolt.Cast" + "AbilityCastRange" "875" + "AbilityCastPoint" "0.1 0.1 0.1 0.1" + "AbilityCooldown" "0" + "HasShardUpgrade" "1" + "AbilityManaCost" "70" + "AbilityValues" + { + "AbilityCharges" + { + "value" "1" + "special_bonus_shard" "+1" + } + "AbilityChargeRestoreTime" "2.0 1.5 1.0 0.5" + "AbilityCastRange" + { + "value" "875" + "special_bonus_unique_skywrath_6" "+125" + } + "bolt_damage" "125 175 225 275" + "int_multiplier" + { + "value" "1.5 2 2.5 3" + "special_bonus_unique_skywrath_2" "+1.5" + } + "bolt_speed" "500" + "bolt_vision" "325" + "vision_duration" "3.34" + } + } + + "skywrath_mage_concussive_shot_custom" + { + "BaseClass" "ability_lua" + "ScriptFile" "abilities/heroes/skywrath_mage/skywrath_mage_concussive_shot_custom" + "AbilityTextureName" "skywrath_mage_concussive_shot" + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_NO_TARGET" + "AbilityUnitDamageType" "DAMAGE_TYPE_MAGICAL" + "SpellImmunityType" "SPELL_IMMUNITY_ENEMIES_NO" + "AbilityCastPoint" "0.0 0.0 0.0 0.0" + "AbilityCastRange" "1600" + "AbilityCooldown" "0" + "HasShardUpgrade" "1" + "AbilityManaCost" "80 85 90 95" + "AbilityValues" + { + "AbilityCharges" + { + "value" "1" + "special_bonus_shard" "+1" + } + "AbilityChargeRestoreTime" "15 14 13 12" + "launch_radius" + { + "value" "1600" + "special_bonus_unique_skywrath_4" "+1000" + "affected_by_aoe_increase" "1" + } + "speed" "800 800 800 800" + "slow_radius" + { + "value" "250" + "affected_by_aoe_increase" "1" + } + "damage" "120 180 240 300" + "int_multiplier" "2 4 6 8" + "slow_duration" "4" + "slow" + { + "value" "40" + "special_bonus_unique_skywrath_concussive_shot_slow" "+20" + } + "creep_damage_pct" "60" + "shot_vision" "300" + "vision_duration" "3.34" + } + } + + "skywrath_mage_ancient_seal_custom" + { + "BaseClass" "ability_lua" + "ScriptFile" "abilities/heroes/skywrath_mage/skywrath_mage_ancient_seal_custom" + "AbilityTextureName" "skywrath_mage_ancient_seal" + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_UNIT_TARGET" + "AbilityUnitTargetTeam" "DOTA_UNIT_TARGET_TEAM_ENEMY" + "AbilityUnitTargetType" "DOTA_UNIT_TARGET_HERO | DOTA_UNIT_TARGET_BASIC" + "AbilityUnitDamageType" "DAMAGE_TYPE_MAGICAL" + "SpellImmunityType" "SPELL_IMMUNITY_ENEMIES_NO" + "SpellDispellableType" "SPELL_DISPELLABLE_YES" + "AbilityCastRange" "700 750 800 850" + "AbilityCastPoint" "0.1 0.1 0.1 0.1" + "AbilityCooldown" "0" + "HasShardUpgrade" "1" + "AbilityManaCost" "80 90 100 110" + "AbilityValues" + { + "AbilityCharges" + { + "value" "1" + "special_bonus_shard" "+1" + } + "AbilityChargeRestoreTime" + { + "value" "14" + "special_bonus_unique_skywrath" "-6" + } + "seal_duration" "3.0 4.0 5.0 6.0" + "radius" + { + "value" "300" + "affected_by_aoe_increase" "1" + } + "resist_debuff" + { + "value" "-20 -25 -30 -35" + "special_bonus_unique_skywrath_3" "-10" + } + "int_multiplier" "0.5" + "AbilityCooldown" "0" + + } + } + + "skywrath_mage_mystic_flare_custom" + { + "BaseClass" "ability_lua" + "ScriptFile" "abilities/heroes/skywrath_mage/skywrath_mage_mystic_flare_custom" + "AbilityTextureName" "skywrath_mage_mystic_flare" + "AbilityType" "ABILITY_TYPE_ULTIMATE" + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_POINT | DOTA_ABILITY_BEHAVIOR_AOE | DOTA_ABILITY_BEHAVIOR_IGNORE_BACKSWING" + "AbilityUnitDamageType" "DAMAGE_TYPE_MAGICAL" + "SpellImmunityType" "SPELL_IMMUNITY_ENEMIES_NO" + "AbilitySound" "Hero_SkywrathMage.MysticFlare.Cast" + "AbilityCastRange" "1200" + "AbilityCastPoint" "0.1" + "AbilityCooldown" "0" + "HasShardUpgrade" "1" + "AbilityManaCost" "150 300 450" + "AbilityValues" + { + "AbilityCharges" + { + "value" "1" + "special_bonus_shard" "+1" + } + "AbilityChargeRestoreTime" "60.0 40.0 20.0" + "radius" + { + "value" "170" + "affected_by_aoe_increase" "1" + } + "duration" "2.0" + "damage" + { + "value" "800 1200 1600" + "special_bonus_unique_skywrath_5" "+400" + } + "int_multiplier" "4.0 8 16 32" + "damage_interval" "0.1" + } + } + + "skywrath_mage_innate_custom" + { + "BaseClass" "ability_lua" + "ScriptFile" "abilities/heroes/skywrath_mage/skywrath_mage_innate_custom" + "AbilityTextureName" "skywrath_mage_arcane_bolt" + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_PASSIVE | DOTA_ABILITY_BEHAVIOR_HIDDEN" + "AbilityType" "ABILITY_TYPE_BASIC" + "Innate" "1" + "MaxLevel" "1" + "IsBreakable" "1" + "AbilityValues" + { + "search_radius" "700" + "max_targets" "3" + "damage_pct" "50" + "bolt_vision" "325" + "vision_duration" "0.34" + } + } + + "skywrath_mage_staff_of_the_scion_custom" + { + "BaseClass" "ability_lua" + "ScriptFile" "abilities/heroes/skywrath_mage/skywrath_mage_staff_of_the_scion_custom" + "AbilityTextureName" "skywrath_mage_staff_of_the_scion" + "AbilityType" "ABILITY_TYPE_BASIC" + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_PASSIVE" + "MaxLevel" "1" + "IsGrantedByScepter" "1" + "AbilityValues" + { + "reset_duration" "30" + "max_successes" "4" + } + } + + "special_bonus_unique_skywrath_2" + { + "AbilityType" "ABILITY_TYPE_ATTRIBUTES" + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_PASSIVE" + "BaseClass" "special_bonus_base" + } + + "special_bonus_unique_skywrath_6" + { + "AbilityType" "ABILITY_TYPE_ATTRIBUTES" + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_PASSIVE" + "BaseClass" "special_bonus_base" + } + + "special_bonus_unique_skywrath_concussive_shot_slow" + { + "AbilityType" "ABILITY_TYPE_ATTRIBUTES" + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_PASSIVE" + "BaseClass" "special_bonus_base" + } + + "special_bonus_unique_skywrath" + { + "AbilityType" "ABILITY_TYPE_ATTRIBUTES" + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_PASSIVE" + "BaseClass" "special_bonus_base" + } + + "special_bonus_unique_skywrath_4" + { + "AbilityType" "ABILITY_TYPE_ATTRIBUTES" + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_PASSIVE" + "BaseClass" "special_bonus_base" + } + + "special_bonus_unique_skywrath_3" + { + "AbilityType" "ABILITY_TYPE_ATTRIBUTES" + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_PASSIVE" + "BaseClass" "special_bonus_base" + } + + "special_bonus_unique_skywrath_5" + { + "AbilityType" "ABILITY_TYPE_ATTRIBUTES" + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_PASSIVE" + "BaseClass" "special_bonus_base" + } +} diff --git a/scripts/npc/heroes/smaug/smaug.kv b/scripts/npc/heroes/smaug/smaug.kv new file mode 100644 index 0000000..abf3f4c --- /dev/null +++ b/scripts/npc/heroes/smaug/smaug.kv @@ -0,0 +1,228 @@ +"DOTAAbilities" +{ + + "ability_fire_punishment" + { + "BaseClass" "ability_lua" + "ScriptFile" "abilities/heroes/smaug/ability_fire_punishment" + "AbilityTextureName" "old_heroes/fire_punishment" + "FightRecapLevel" "1" + "MaxLevel" "4" + + "AbilityType" "DOTA_ABILITY_TYPE_PASSIVE" + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_PASSIVE" + "AbilityUnitTargetTeam" "DOTA_UNIT_TARGET_TEAM_ENEMY" + "AbilityUnitTargetType" "DOTA_UNIT_TARGET_HERO | DOTA_UNIT_TARGET_BASIC | DOTA_UNIT_TARGET_BUILDING" + "AbilityUnitTargetFlags" "DOTA_UNIT_TARGET_FLAG_MAGIC_IMMUNE_ENEMIES | DOTA_UNIT_TARGET_FLAG_DEAD" + "AbilityUnitDamageType" "DAMAGE_TYPE_MAGICAL" + "SpellDispellableType" "SPELL_DISPELLABLE_YES" + "SpellImmunityType" "SPELL_IMMUNITY_ENEMIES_NO" + + "AbilityCastRange" "600" + "AbilityCastPoint" "0.0 0.0 0.0 0.0" + "AbilityDuration" "1.5" + "AbilityCooldown" "0" + + "AbilityValues" + { + + "debuff_magic" + { + "value" " -1 -2 -3 -4" + "special_bonus_unique_smaug_fire_punishment" "-2" + } + "radius" + { + "value" "80 100 120 140" + "special_bonus_unique_smaug_fire_punishment" "+30" + } + "pct_dmg" + { + "value" "22" + "special_bonus_unique_smaug_fire_punishment" "+66" + + } + "damage" + { + "value" "12 20 32 48" + "special_bonus_unique_smaug_fire_punishment" "+32" + } + } + } + "ability_incandescent_fury" + { + "BaseClass" "ability_lua" + "ScriptFile" "abilities/heroes/smaug/ability_incandescent_fury" + "AbilityTextureName" "old_heroes/incandescent_fury" + "FightRecapLevel" "2" + "RequiredLevel" "6" + "LevelsBetweenUpgrades" "6" + "MaxLevel" "3" + "HasScepterUpgrade" "1" + "AbilityType" "DOTA_ABILITY_TYPE_ULTIMATE" + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_POINT" + "AbilityUnitTargetTeam" "DOTA_UNIT_TARGET_TEAM_ENEMY" + "AbilityUnitTargetType" "DOTA_UNIT_TARGET_HERO | DOTA_UNIT_TARGET_BASIC" + "AbilityUnitDamageType" "DAMAGE_TYPE_MAGICAL" + "SpellDispellableType" "SPELL_DISPELLABLE_YES" + "SpellImmunityType" "SPELL_IMMUNITY_ENEMIES_NO" + + "AbilityCastRange" "1400" + "AbilityCastPoint" "0.55" + "AbilityManaCost" "220 330 440" + + "AbilityValues" + { + "AbilityCooldown" + { + "value" "180" + "special_bonus_scepter" "-90" + } + "pct_dmg" "100 200 300" + "damage" + { + "value" "322 644 988" + "special_bonus_unique_smaug_incandescent_fury" "+1012" + } + "cast_range" "1400" + "path_radius" "260" + "duration" "10" + "burn_interval" "0.25" + "linger_duration" "2" + } + } + "ability_dragon_fear_aura" + { + "BaseClass" "ability_lua" + "ScriptFile" "abilities/heroes/smaug/ability_dragon_fear_aura" + "AbilityTextureName" "old_heroes/dragon_fear_aura" + "FightRecapLevel" "1" + "MaxLevel" "4" + "AbilityType" "DOTA_ABILITY_TYPE_BASIC" + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_PASSIVE" + "AbilityUnitTargetTeam" "DOTA_UNIT_TARGET_TEAM_ENEMY" + "SpellDispellableType" "SPELL_DISPELLABLE_NO" + "AbilityValues" + { + "outgoing" "-14 -16 -20 -26" + "duration" "1" + "spell_amp" "16 20 24 28" + "incoming" "15 20 25 30" + } + } + "ability_dragon_gold_deal" + { + "BaseClass" "ability_lua" + "ScriptFile" "abilities/heroes/smaug/ability_dragon_gold_deal" + "AbilityTextureName" "old_heroes/dragon_gold_deal" + "FightRecapLevel" "1" + "RequiredLevel" "5" + "LevelsBetweenUpgrades" "5" + "MaxLevel" "3" + "AbilityType" "DOTA_ABILITY_TYPE_BASIC" + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_PASSIVE" + "AbilityUnitTargetTeam" "DOTA_UNIT_TARGET_TEAM_ENEMY" + "SpellDispellableType" "SPELL_DISPELLABLE_NO" + "AbilityValues" + { + "health_bonus" "0.1 0.2 0.3" + "spell_amp" "0.025 0.05 0.75" + "physical_armor" "0.01 0.02 0.03" + "gold" + { + "value" "100" + "special_bonus_unique_smaug_dragon_gold_deal" "-50" + } + } + } + "ability_dragon_reward" + { + "BaseClass" "ability_lua" + "ScriptFile" "abilities/heroes/smaug/ability_dragon_reward" + "AbilityTextureName" "old_heroes/dragon_reward" + "FightRecapLevel" "1" + "IsGrantedByShard" "1" + "HasShardUpgrade" "1" + "MaxLevel" "1" + "AbilityType" "DOTA_ABILITY_TYPE_BASIC" + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_PASSIVE" + "AbilityUnitTargetTeam" "DOTA_UNIT_TARGET_TEAM_ENEMY" + "SpellDispellableType" "SPELL_DISPELLABLE_NO" + "AbilityValues" + { + "max" "99" + "bonus_attributes" "1" + } + } + "ability_dragon_scales" + { + "BaseClass" "ability_lua" + "ScriptFile" "abilities/heroes/smaug/ability_dragon_scales" + "AbilityTextureName" "old_heroes/dragon_scales" + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_NO_TARGET" + "AbilityType" "DOTA_ABILITY_TYPE_BASIC" + "SpellImmunityType" "SPELL_IMMUNITY_ENEMIES_YES" + "MaxLevel" "4" + // Special + //------------------------------------------------------------------------------------------------------------- + "AbilityValues" + { + "AbilityCooldown" + { + "value" "30" + "special_bonus_unique_smaug_dragon_scales_cd" "-10" + } + + "reflect" + { + "value" "20 40 60 80" + "special_bonus_unique_smaug_dragon_scales_reflect" "+20" + } + "invul" "-1000" + "duration" "4 4.5 5 5.5" + "min_radius" "300" + "max_radius" "700" + } + "AbilityCastAnimation" "ACT_DOTA_CAST_ABILITY_3" + } + "special_bonus_unique_smaug_dragon_scales_cd" + { + // General + //------------------------------------------------------------------------------------------------------------- + "BaseClass" "special_bonus_undefined" + "AbilityType" "ABILITY_TYPE_ATTRIBUTES" + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_PASSIVE" + } + "special_bonus_unique_smaug_dragon_scales_reflect" + { + // General + //------------------------------------------------------------------------------------------------------------- + "BaseClass" "special_bonus_undefined" + "AbilityType" "ABILITY_TYPE_ATTRIBUTES" + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_PASSIVE" + } + "special_bonus_unique_smaug_dragon_gold_deal" + { + // General + //------------------------------------------------------------------------------------------------------------- + "BaseClass" "special_bonus_undefined" + "AbilityType" "ABILITY_TYPE_ATTRIBUTES" + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_PASSIVE" + } + "special_bonus_unique_smaug_incandescent_fury" + { + // General + //------------------------------------------------------------------------------------------------------------- + "BaseClass" "special_bonus_undefined" + "AbilityType" "ABILITY_TYPE_ATTRIBUTES" + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_PASSIVE" + } + "special_bonus_unique_smaug_fire_punishment" + { + // General + //------------------------------------------------------------------------------------------------------------- + "BaseClass" "special_bonus_undefined" + "AbilityType" "ABILITY_TYPE_ATTRIBUTES" + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_PASSIVE" + } +} \ No newline at end of file diff --git a/scripts/npc/heroes/sniper/sniper.kv b/scripts/npc/heroes/sniper/sniper.kv new file mode 100644 index 0000000..e87e2a8 --- /dev/null +++ b/scripts/npc/heroes/sniper/sniper.kv @@ -0,0 +1,188 @@ +"DOTAAbilities" +{ + "ability_sniper_shrapnel_custom" + { + "BaseClass" "ability_lua" + "ScriptFile" "abilities/heroes/sniper/ability_sniper_shrapnel_custom" + "AbilityTextureName" "sniper_shrapnel" + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_POINT | DOTA_ABILITY_BEHAVIOR_AOE" + "AbilityUnitDamageType" "DAMAGE_TYPE_PHYSICAL" + "SpellImmunityType" "SPELL_IMMUNITY_ENEMIES_YES" + "AbilityManaCost" "50" + "AbilityCastRange" "1800" + "AbilityCastPoint" "0.3" + "AbilityCastAnimation" "ACT_DOTA_CAST_ABILITY_1" + "AbilityValues" + { + "AbilityCooldown" + { + "value" "12 10 8 6" + "special_bonus_unique_sniper_shrapnel_cooldown" "-3" + } + "radius" + { + "value" "400" + "affected_by_aoe_increase" "1" + "special_bonus_unique_sniper_shrapnel_radius" "+150" + } + "damage_delay" "1.2" + "duration" "10" + "tick_interval" "1.0" + "attack_damage_pct" "25" + "slow_movement_speed" "-18" + "incoming_damage_pct" "25" + } + } + + "ability_sniper_critical_focus_custom" + { + "BaseClass" "ability_lua" + "ScriptFile" "abilities/heroes/sniper/ability_sniper_critical_focus_custom" + "AbilityTextureName" "sniper_headshot" + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_NO_TARGET | DOTA_ABILITY_BEHAVIOR_IMMEDIATE" + "AbilityCooldown" "30 26 22 18" + "AbilityManaCost" "50" + "AbilityCastAnimation" "ACT_DOTA_CAST_ABILITY_2" + "AbilityValues" + { + "duration" + { + "value" "1.5 2 2.5 3" + "special_bonus_unique_sniper_critical_focus_duration" "+1.5" + } + "crit_chance" "75" + "crit_mult" + { + "value" "125 145 165 185" + "special_bonus_unique_sniper_critical_focus_crit_mult" "+25" + } + } + } + + "ability_sniper_keen_eye_custom" + { + "BaseClass" "ability_lua" + "ScriptFile" "abilities/heroes/sniper/ability_sniper_keen_eye_custom" + "AbilityTextureName" "sniper_take_aim" + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_PASSIVE" + "AbilityValues" + { + "bonus_attack_range" + { + "value" "100 150 200 250" + "special_bonus_unique_sniper_keen_range" "+75" + } + "proc_chance" + { + "value" "15" + "special_bonus_unique_sniper_keen_proc" "+12" + } + "headshot_bonus_damage" "40 50 60 70" + "slow_duration" "1.5" + "knockback_distance" "120" + "slow_movement_pct" "-40" + "slow_attack_speed" "-40" + } + } + + "ability_sniper_assassinate_custom" + { + "BaseClass" "ability_lua" + "ScriptFile" "abilities/heroes/sniper/ability_sniper_assassinate_custom" + "AbilityTextureName" "sniper_assassinate" + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_UNIT_TARGET | DOTA_ABILITY_BEHAVIOR_AOE | DOTA_ABILITY_BEHAVIOR_IGNORE_BACKSWING" + "AbilityUnitTargetTeam" "DOTA_UNIT_TARGET_TEAM_ENEMY" + "AbilityType" "ABILITY_TYPE_ULTIMATE" + "AbilityUnitTargetType" "DOTA_UNIT_TARGET_HERO | DOTA_UNIT_TARGET_BASIC" + "AbilityUnitDamageType" "DAMAGE_TYPE_PHYSICAL" + "SpellImmunityType" "SPELL_IMMUNITY_ENEMIES_YES" + "HasScepterUpgrade" "1" + + "AbilityManaCost" "50 100 150" + "AbilityCastRange" "3000" + + "AbilityCastAnimation" "ACT_DOTA_CAST_ABILITY_4" + "AbilityValues" + { + "AbilityCastPoint" + { + + "value" "2" + "special_bonus_scepter" "-1.4" + } + "AbilityCooldown" + { + "value" "20 18 16" + "special_bonus_scepter" "-10" + } + "aoe_radius" + { + "value" "400" + "special_bonus_unique_sniper_assassinate_aoe" "+250" + } + "debuff_duration" "2.5" + "projectile_speed" "2500" + "ministun_duration" + { + "value" "0.1" + "special_bonus_scepter" "+1.9" + } + + "attack_damage_bonus" + { + "value" "20 40 60" + "special_bonus_unique_sniper_assassinate_damage" "+50" + } + "armor_mark_duration" "4" + } + } + + "special_bonus_unique_sniper_shrapnel_radius" + { + "BaseClass" "special_bonus_undefined" + "AbilityType" "ABILITY_TYPE_ATTRIBUTES" + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_PASSIVE" + } + "special_bonus_unique_sniper_keen_range" + { + "BaseClass" "special_bonus_undefined" + "AbilityType" "ABILITY_TYPE_ATTRIBUTES" + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_PASSIVE" + } + "special_bonus_unique_sniper_shrapnel_cooldown" + { + "BaseClass" "special_bonus_undefined" + "AbilityType" "ABILITY_TYPE_ATTRIBUTES" + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_PASSIVE" + } + "special_bonus_unique_sniper_critical_focus_duration" + { + "BaseClass" "special_bonus_undefined" + "AbilityType" "ABILITY_TYPE_ATTRIBUTES" + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_PASSIVE" + } + "special_bonus_unique_sniper_critical_focus_crit_mult" + { + "BaseClass" "special_bonus_undefined" + "AbilityType" "ABILITY_TYPE_ATTRIBUTES" + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_PASSIVE" + } + "special_bonus_unique_sniper_keen_proc" + { + "BaseClass" "special_bonus_undefined" + "AbilityType" "ABILITY_TYPE_ATTRIBUTES" + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_PASSIVE" + } + "special_bonus_unique_sniper_assassinate_aoe" + { + "BaseClass" "special_bonus_undefined" + "AbilityType" "ABILITY_TYPE_ATTRIBUTES" + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_PASSIVE" + } + "special_bonus_unique_sniper_assassinate_damage" + { + "BaseClass" "special_bonus_undefined" + "AbilityType" "ABILITY_TYPE_ATTRIBUTES" + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_PASSIVE" + } +} diff --git a/scripts/npc/heroes/spectre/spectre.kv b/scripts/npc/heroes/spectre/spectre.kv new file mode 100644 index 0000000..1b0e360 --- /dev/null +++ b/scripts/npc/heroes/spectre/spectre.kv @@ -0,0 +1,306 @@ +"DOTAAbilities" +{ + "ability_spectre_spectral_dagger_custom" + { + "BaseClass" "ability_lua" + "ScriptFile" "abilities/heroes/spectre/ability_spectre_spectral_dagger_custom" + "AbilityTextureName" "spectre_spectral_dagger" + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_POINT | DOTA_ABILITY_BEHAVIOR_UNIT_TARGET" + "AbilityUnitTargetTeam" "DOTA_UNIT_TARGET_TEAM_ENEMY" + "AbilityUnitTargetType" "DOTA_UNIT_TARGET_HERO | DOTA_UNIT_TARGET_BASIC" + "SpellDispellableType" "SPELL_DISPELLABLE_YES" + "AbilityUnitDamageType" "DAMAGE_TYPE_MAGICAL" + "FightRecapLevel" "1" + "AbilitySound" "Hero_Spectre.DaggerCast" + "MaxLevel" "4" + "AbilityCastRange" "1100" + "AbilityCastPoint" "0.3" + "AbilityManaCost" "35" + + "AbilityValues" + { + "AbilityCooldown" + { + "value" "22 20 18 16" + "special_bonus_unique_spectre_dagger_cooldown" "-4" + } + "damage" + { + "value" "50 100 150 200" + "special_bonus_unique_spectre_dagger_damage" "+80" + } + "bonus_movespeed" "10 17 21 25" + "slow_pct" "14 18 22 26" + "buff_persistence" "5.0" + "dagger_radius" + { + "value" "125" + "affected_by_aoe_increase" "1" + } + "vision_radius" + { + "value" "200" + "affected_by_aoe_increase" "1" + } + "speed" "800" + } + "AbilityCastAnimation" "ACT_DOTA_CAST_ABILITY_1" + } + + "ability_spectre_spectral_echo_custom" + { + "BaseClass" "ability_lua" + "ScriptFile" "abilities/heroes/spectre/ability_spectre_spectral_echo_custom" + "AbilityTextureName" "spectre_desolate" + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_PASSIVE" + "MaxLevel" "4" + "IsBreakable" "1" + + "AbilityValues" + { + "proc_chance" + { + "value" "15 20 25 30" + "special_bonus_unique_spectre_echo_proc_chance" "+12" + } + "shadows_per_proc" + { + "value" "1" + "special_bonus_unique_spectre_spectral_echo_twin" "+1" + } + "spawn_radius_near_target" "120" + "follow_distance" "140" + "search_radius" + { + "value" "350" + "affected_by_aoe_increase" "1" + } + "illusion_outgoing_damage" + { + "value" "50 55 60 65" + "special_bonus_unique_spectre_echo_shadow_damage" "+25" + } + "shadow_attacks" + { + "value" "1" + "special_bonus_unique_spectre_echo_double_strike" "+1" + } + "attack_damage_pct" "100" + "illusion_attack_speed" "50 75 100 125" + } + "AbilityCastAnimation" "ACT_DOTA_CAST_ABILITY_2" + } + + "ability_spectre_desolate_custom" + { + "BaseClass" "ability_lua" + "ScriptFile" "abilities/heroes/spectre/ability_spectre_desolate_custom" + "AbilityTextureName" "spectre_desolate" + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_PASSIVE | DOTA_ABILITY_BEHAVIOR_INNATE_UI | DOTA_ABILITY_BEHAVIOR_NOT_LEARNABLE | DOTA_ABILITY_BEHAVIOR_SKIP_FOR_KEYBINDS" + "AbilityType" "ABILITY_TYPE_BASIC" + "AbilityUnitDamageType" "DAMAGE_TYPE_PURE" + "SpellImmunityType" "SPELL_IMMUNITY_ENEMIES_YES" + "AbilitySound" "Hero_Spectre.Desolate" + "MaxLevel" "1" + "Innate" "1" + "IsBreakable" "1" + + "AbilityValues" + { + "health_damage_pct" + { + "value" "1" + "hero_levelup" "+0.1" + "special_bonus_unique_spectre_desolate_hp_pct" "+0.4" + } + + } + } + + "ability_spectre_dispersion_custom" + { + "BaseClass" "ability_lua" + "ScriptFile" "abilities/heroes/spectre/ability_spectre_dispersion_custom" + "AbilityTextureName" "spectre_dispersion" + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_PASSIVE" + "SpellImmunityType" "SPELL_IMMUNITY_ENEMIES_YES" + "HasShardUpgrade" "1" + "MaxLevel" "4" + "IsBreakable" "1" + + "AbilityValues" + { + "damage_reflection_pct" + { + "value" "8 12 16 20" + "special_bonus_unique_spectre_dispersion_pct" "+8" + } + "damage_reflection_pct_boosted" "150" + "min_radius" + { + "value" "300" + "affected_by_aoe_increase" "1" + } + "max_radius" + { + "value" "800" + "affected_by_aoe_increase" "1" + } + "activation_cooldown" + { + "value" "0" + "special_bonus_shard" "+16" + } + "activation_manacost" + { + "value" "0" + "special_bonus_shard" "+50" + } + "activation_bonus_pct" + { + "value" "0" + "special_bonus_shard" "+50" + } + "activation_duration" + { + "value" "0" + "special_bonus_shard" "+5" + } + } + "AbilityCastAnimation" "ACT_DOTA_CAST_ABILITY_4" + } + + "ability_spectre_haunt_custom" + { + "BaseClass" "ability_lua" + "ScriptFile" "abilities/heroes/spectre/ability_spectre_haunt_custom" + "AbilityTextureName" "spectre_haunt" + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_NO_TARGET" + "SpellImmunityType" "SPELL_IMMUNITY_ENEMIES_YES" + "AbilityType" "ABILITY_TYPE_ULTIMATE" + "AbilitySound" "Hero_Spectre.Haunt" + "MaxLevel" "3" + "AbilityCastPoint" "0.4" + "AbilityCastAnimation" "ACT_DOTA_CAST_ABILITY_4" + "AbilityCooldown" "80 70 60" + "AbilityManaCost" "25" + "LinkedAbility" "ability_spectre_reality_custom" + + "AbilityValues" + { + "illusion_count" "1 2 3" + "duration" + { + "value" "40" + "special_bonus_unique_spectre_haunt_duration" "+20" + } + "illusion_damage_outgoing" "0 10 20" + "illusion_damage_incoming" "0" + "spawn_radius" "140" + } + } + + "ability_spectre_reality_custom" + { + "BaseClass" "ability_lua" + "ScriptFile" "abilities/heroes/spectre/ability_spectre_haunt_custom" + "AbilityTextureName" "spectre_reality" + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_NOT_LEARNABLE | DOTA_ABILITY_BEHAVIOR_NO_TARGET" + "AbilityType" "ABILITY_TYPE_BASIC" + "MaxLevel" "1" + "AbilityCastAnimation" "ACT_DOTA_CAST_ABILITY_5" + "AbilityCastPoint" "0" + "AbilityManaCost" "0" + "LinkedAbility" "ability_spectre_haunt_custom" + + "AbilityValues" + { + "AbilityCooldown" + { + "value" "10" + "special_bonus_unique_spectre_reality_cooldown" "-6" + } + } + } + + "ability_spectre_shadow_step_custom" + { + "BaseClass" "ability_lua" + "ScriptFile" "abilities/heroes/spectre/ability_spectre_shadow_step_custom" + "AbilityTextureName" "spectre_shadow_step" + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_PASSIVE" + "AbilityType" "ABILITY_TYPE_ULTIMATE" + "MaxLevel" "1" + "IsGrantedByScepter" "1" + "IsBreakable" "1" + + "AbilityValues" + { + "duration_steal" "5" + "health_steal" "50" + "illusion_decrease" "50" + "health_bonus_self" "30" + } + } + + "special_bonus_unique_spectre_dagger_cooldown" + { + "BaseClass" "special_bonus_undefined" + "AbilityType" "ABILITY_TYPE_ATTRIBUTES" + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_PASSIVE" + } + "special_bonus_unique_spectre_echo_proc_chance" + { + "BaseClass" "special_bonus_undefined" + "AbilityType" "ABILITY_TYPE_ATTRIBUTES" + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_PASSIVE" + } + "special_bonus_unique_spectre_spectral_echo_twin" + { + "BaseClass" "special_bonus_undefined" + "AbilityType" "ABILITY_TYPE_ATTRIBUTES" + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_PASSIVE" + } + "special_bonus_unique_spectre_dagger_damage" + { + "BaseClass" "special_bonus_undefined" + "AbilityType" "ABILITY_TYPE_ATTRIBUTES" + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_PASSIVE" + } + "special_bonus_unique_spectre_dispersion_pct" + { + "BaseClass" "special_bonus_undefined" + "AbilityType" "ABILITY_TYPE_ATTRIBUTES" + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_PASSIVE" + } + "special_bonus_unique_spectre_haunt_duration" + { + "BaseClass" "special_bonus_undefined" + "AbilityType" "ABILITY_TYPE_ATTRIBUTES" + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_PASSIVE" + } + "special_bonus_unique_spectre_desolate_hp_pct" + { + "BaseClass" "special_bonus_undefined" + "AbilityType" "ABILITY_TYPE_ATTRIBUTES" + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_PASSIVE" + } + "special_bonus_unique_spectre_echo_shadow_damage" + { + "BaseClass" "special_bonus_undefined" + "AbilityType" "ABILITY_TYPE_ATTRIBUTES" + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_PASSIVE" + } + "special_bonus_unique_spectre_echo_double_strike" + { + "BaseClass" "special_bonus_undefined" + "AbilityType" "ABILITY_TYPE_ATTRIBUTES" + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_PASSIVE" + } + "special_bonus_unique_spectre_reality_all_haunts" + { + "BaseClass" "special_bonus_undefined" + "AbilityType" "ABILITY_TYPE_ATTRIBUTES" + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_PASSIVE" + } +} diff --git a/scripts/npc/heroes/sven/sven.kv b/scripts/npc/heroes/sven/sven.kv new file mode 100644 index 0000000..0fafb22 --- /dev/null +++ b/scripts/npc/heroes/sven/sven.kv @@ -0,0 +1,135 @@ +"DOTAAbilities" +{ + "ability_sven_storm_hammer_custom" + { + "BaseClass" "ability_lua" + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_UNIT_TARGET | DOTA_ABILITY_BEHAVIOR_AOE | DOTA_ABILITY_BEHAVIOR_IGNORE_BACKSWING" + "AbilityUnitTargetTeam" "DOTA_UNIT_TARGET_TEAM_ENEMY" + "AbilityUnitTargetType" "DOTA_UNIT_TARGET_HERO | DOTA_UNIT_TARGET_BASIC" + "AbilityUnitDamageType" "DAMAGE_TYPE_PHYSICAL" + "AbilityTextureName" "sven_storm_bolt" + "SpellImmunityType" "SPELL_IMMUNITY_ENEMIES_NO" + "ScriptFile" "abilities/heroes/sven/ability_sven_storm_hammer_custom.lua" + "MaxLevel" "4" + "AbilityCastRange" "600" + "AbilityCastPoint" "0.25" + "AbilityManaCost" "40" + "AbilityCastAnimation" "ACT_DOTA_CAST_ABILITY_1" + "AbilityCooldown" "18 16 14 12" + "AbilityValues" + { + "projectile_speed" "1000" + "radius" "275" + "stun_duration" + { + "value" "1.25 1.5 1.75 2.0" + "special_bonus_unique_sven_storm_hammer_stun_duration" "+1" + } + "damage" "225 275 325 375" + "sven_storm_hammer_purger" "1" + "stun_duration_for_boss_mult" "2" + } + } + "ability_sven_great_cleave_custom" + { + "BaseClass" "ability_lua" + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_PASSIVE" + "AbilityTextureName" "sven_great_cleave" + "ScriptFile" "abilities/heroes/sven/ability_sven_great_cleave_custom.lua" + + "MaxLevel" "4" + "AbilityCastPoint" "0" + "AbilityCooldown" "12 11 10 9" + "AbilityValues" + { + "cleave_damage_pct" + { + "value" "45 60 75 95" + "special_bonus_unique_sven_great_cleave_damage_pct" "+105" + } + "cleave_radius" "400" + "cleave_starting_width" "150" + "cleave_ending_width" "360" + "health_cost_pct" "5" + "cleave_damage_multiple_facet" "200 300 400 500" + } + } + "ability_sven_warcry_custom" + { + "BaseClass" "ability_lua" + "AbilityTextureName" "sven_warcry" + "ScriptFile" "abilities/heroes/sven/ability_sven_warcry_custom.lua" + "MaxLevel" "4" + "AbilityCastPoint" "0" + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_NO_TARGET | DOTA_ABILITY_BEHAVIOR_IMMEDIATE" + "AbilityCooldown" "16" + "AbilityManaCost" "24" + + "AbilityCastAnimation" "ACT_DOTA_OVERRIDE_ABILITY_3" + "AbilityValues" + { + "duration" + { + "value" "10 12 14 16" + "special_bonus_unique_sven_warcry_duration_base" "+5" + } + "radius" "700" + "armor_bonus" "8 12 16 20" + "movespeed_bonus" "20 30 40 50" + "attackspeed_bonus" "5 10 15 20" + "damage_bonus_per_armor" + { + "value" "2 4 6 8" + "special_bonus_unique_sven_warcry_damage_bonus_per_armor" "+4" + } + } + } + "ability_sven_gods_strength_custom" + { + "BaseClass" "ability_lua" + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_NO_TARGET | DOTA_ABILITY_BEHAVIOR_IGNORE_BACKSWING" + "AbilityType" "ABILITY_TYPE_ULTIMATE" + "AbilityTextureName" "sven_gods_strength" + "ScriptFile" "abilities/heroes/sven/ability_sven_gods_strength_custom.lua" + "MaxLevel" "3" + "AbilityCastPoint" "0.3" + "HasShardUpgrade" "1" + "HasScepterUpgrade" "1" + "AbilityCooldown" "110 100 90" + "AbilityManaCost" "75" + "AbilityCastAnimation" "ACT_DOTA_OVERRIDE_ABILITY_4" + "AbilityValues" + { + "gods_strength_damage_bonus" + { + "value" "100 150 200" + "special_bonus_unique_sven_gods_strength_damage_bonus_base" "+300" + } + "gods_strength_damage_bonus_per_health" + { + "value" "0" + "special_bonus_unique_sven_gods_strength_damage_per_health" "0.2 0.4 0.6 0.8" + } + "duration" + { + "value" "25" + "special_bonus_unique_sven_gods_strength_duration_base" "+15" + } + "gods_strength_bonus_strength_pct" + { + "special_bonus_scepter" "0.6 0.8 1.0" + + } + "gods_strength_shard_magic_resist" + { + "value" "80" + } + } + } + "special_bonus_unique_sven_gods_strength_damage_per_health" + { + "BaseClass" "special_bonus_undefined" + "AbilityType" "ABILITY_TYPE_ATTRIBUTES" + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_PASSIVE" + } +} \ No newline at end of file diff --git a/scripts/npc/heroes/templar_assassin/templar_assassin.kv b/scripts/npc/heroes/templar_assassin/templar_assassin.kv new file mode 100644 index 0000000..66da7dc --- /dev/null +++ b/scripts/npc/heroes/templar_assassin/templar_assassin.kv @@ -0,0 +1,229 @@ +"DOTAAbilities" +{ + "ability_templar_assassin_refraction_custom" + { + "BaseClass" "ability_lua" + "ScriptFile" "abilities/heroes/templar_assassin/ability_templar_assassin_refraction_custom.lua" + "AbilityTextureName" "templar_assassin_refraction" + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_NO_TARGET | DOTA_ABILITY_BEHAVIOR_IMMEDIATE" + "AbilityUnitDamageType" "DAMAGE_TYPE_PHYSICAL" + "SpellImmunityType" "SPELL_IMMUNITY_ENEMIES_YES" + "AbilityUnitTargetFlags" "DOTA_UNIT_TARGET_FLAG_MAGIC_IMMUNE_ENEMIES" + "SpellDispellableType" "SPELL_DISPELLABLE_NO" + "AbilityCastPoint" "0.0" + "AbilityCooldown" "20 18 16 14" + "AbilityManaCost" "90" + "MaxLevel" "4" + "HasShardUpgrade" "1" + + "AbilityValues" + { + "instances" + { + "value" "8 12 16 20" + "special_bonus_unique_templar_assassin_8" "+10" + } + "bonus_damage" + { + "value" "70 110 150 190" + "special_bonus_unique_templar_assassin_4" "+20" + } + "damage_threshold" "5" + "duration" "17" + "shard_instances_bonus" "2" + } + "AbilityCastAnimation" "ACT_DOTA_OVERRIDE_ABILITY_1" + } + + "ability_templar_assassin_psi_blades_custom" + { + "BaseClass" "ability_lua" + "ScriptFile" "abilities/heroes/templar_assassin/ability_templar_assassin_psi_blades_custom.lua" + "AbilityTextureName" "templar_assassin_psi_blades" + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_PASSIVE" + "AbilityUnitDamageType" "DAMAGE_TYPE_PURE" + "SpellImmunityType" "SPELL_IMMUNITY_ENEMIES_YES" + "MaxLevel" "1" + "Innate" "1" + "IsBreakable" "1" + + "AbilityValues" + { + "bonus_attack_range" "50" + "bonus_attack_range_per_hero_level" "5" + "attack_spill_range" "650" + "attack_spill_width" "90" + "attack_spill_pct" + { + "value" "40" + "special_bonus_unique_templar_assassin_7" "+40" + } + } + } + + "ability_templar_assassin_meld_custom" + { + "BaseClass" "ability_lua" + "ScriptFile" "abilities/heroes/templar_assassin/ability_templar_assassin_meld_custom.lua" + "AbilityTextureName" "templar_assassin_meld" + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_POINT | DOTA_ABILITY_BEHAVIOR_IGNORE_BACKSWING | DOTA_ABILITY_BEHAVIOR_ALT_CASTABLE | DOTA_ABILITY_BEHAVIOR_IMMEDIATE" + "AbilityUnitTargetTeam" "DOTA_UNIT_TARGET_TEAM_ENEMY" + "AbilityUnitTargetType" "DOTA_UNIT_TARGET_HERO | DOTA_UNIT_TARGET_BASIC" + "AbilityUnitTargetFlags" "DOTA_UNIT_TARGET_FLAG_MAGIC_IMMUNE_ENEMIES" + "AbilityUnitDamageType" "DAMAGE_TYPE_PHYSICAL" + "SpellImmunityType" "SPELL_IMMUNITY_ENEMIES_YES" + "MaxLevel" "4" + "AbilityManaCost" "80 60 40 05" + "HasShardUpgrade" "1" + + "AbilityValues" + { + "AbilityCharges" "1 2 3 4" + "AbilityChargeRestoreTime" "7" + "blink_range" "400" + "min_blink_range" "100" + "blink_attack_targets" "1 2 3 4" + "meld_damage_deal" "150 300 450 600" + "radius" + { + "value" "720" + "special_bonus_unique_templar_assassin_3" "+120" + } + "duration" "12 14 16 18" + "armor_reduction" "-12 -16 -20 -24" + "shard_bonus_targets" "1" + } + } + + "ability_templar_assassin_templar_secret_custom" + { + "BaseClass" "ability_lua" + "ScriptFile" "abilities/heroes/templar_assassin/ability_templar_assassin_templar_secret_custom.lua" + "AbilityTextureName" "new_heroes/templar_secret" + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_PASSIVE" + "MaxLevel" "4" + + "AbilityValues" + { + "temp_crit_chance" "100" + "temp_crit_mult" "140 160 180 200" + } + } + + "ability_templar_assassin_bedlam_custom" + { + "BaseClass" "ability_lua" + "ScriptFile" "abilities/heroes/templar_assassin/ability_templar_assassin_bedlam_custom.lua" + "AbilityTextureName" "dark_willow_bedlam" + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_PASSIVE" + "AbilityType" "ABILITY_TYPE_ULTIMATE" + "AbilityUnitDamageType" "DAMAGE_TYPE_PHYSICAL" + "MaxLevel" "3" + "HasScepterUpgrade" "1" + + "AbilityValues" + { + "attack_interval" "0.5 0.4 0.3" + "attack_radius" "600" + "attack_targets" + { + "value" "1" + "special_bonus_unique_templar_assassin_2" "+1" + } + "roaming_radius" "200" + "roaming_seconds_per_rotation" "1.8" + "scepter_bonus_targets" "2" + "scepter_interval_mult_pct" "80" + } + } + + "ability_templar_assassin_refusion_trap_custom" + { + "BaseClass" "ability_lua" + "ScriptFile" "abilities/heroes/templar_assassin/ability_templar_assassin_refusion_trap_custom.lua" + "AbilityTextureName" "new_heroes/refusion_trap" + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_POINT" + "SpellImmunityType" "SPELL_IMMUNITY_ENEMIES_YES" + "AbilityCooldown" "90" + "IsGrantedByScepter" "1" + "MaxLevel" "1" + + "AbilityValues" + { + "radius" "600" + "duration" "9" + "count" "2" + } + } + + "special_bonus_unique_templar_assassin_4" + { + "BaseClass" "special_bonus_undefined" + "AbilityType" "ABILITY_TYPE_ATTRIBUTES" + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_PASSIVE" + "AbilitySpecial" + { + "01" + { + "var_type" "FIELD_INTEGER" + "value" "20" + } + } + } + "special_bonus_unique_templar_assassin_8" + { + "BaseClass" "special_bonus_undefined" + "AbilityType" "ABILITY_TYPE_ATTRIBUTES" + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_PASSIVE" + "AbilitySpecial" + { + "01" + { + "var_type" "FIELD_INTEGER" + "value" "2" + } + } + } + "special_bonus_unique_templar_assassin_3" + { + "BaseClass" "special_bonus_undefined" + "AbilityType" "ABILITY_TYPE_ATTRIBUTES" + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_PASSIVE" + "AbilitySpecial" + { + "01" + { + "var_type" "FIELD_INTEGER" + "value" "120" + } + } + } + "special_bonus_unique_templar_assassin_2" + { + "BaseClass" "special_bonus_undefined" + "AbilityType" "ABILITY_TYPE_ATTRIBUTES" + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_PASSIVE" + "AbilitySpecial" + { + "01" + { + "var_type" "FIELD_INTEGER" + "value" "1" + } + } + } + "special_bonus_unique_templar_assassin_7" + { + "BaseClass" "special_bonus_undefined" + "AbilityType" "ABILITY_TYPE_ATTRIBUTES" + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_PASSIVE" + "AbilitySpecial" + { + "01" + { + "var_type" "FIELD_INTEGER" + "value" "20" + } + } + } +} diff --git a/scripts/npc/heroes/troll_warlord/troll_warlord.kv b/scripts/npc/heroes/troll_warlord/troll_warlord.kv new file mode 100644 index 0000000..bbe1822 --- /dev/null +++ b/scripts/npc/heroes/troll_warlord/troll_warlord.kv @@ -0,0 +1,310 @@ +"DOTAAbilities" +{ + "troll_warlord_switch_stance_custom" + { + "BaseClass" "ability_lua" + "ScriptFile" "abilities/heroes/troll_warlord/troll_warlord_switch_stance_custom" + "AbilityTextureName" "troll_warlord_switch_stance" + + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_NO_TARGET | DOTA_ABILITY_BEHAVIOR_TOGGLE | DOTA_ABILITY_BEHAVIOR_IMMEDIATE" + "SpellImmunityType" "SPELL_IMMUNITY_ENEMIES_YES" + "AbilitySound" "Hero_TrollWarlord.BerserkersRage.Toggle" + "HasShardUpgrade" "1" + "Innate" "1" + + "AbilityCastPoint" "0.2" + + "AbilityValues" + { + "bonus_armor" + { + "value" "15" + "special_bonus_unique_troll_warlord_1_3" "8" + } + "bonus_move_speed" + { + "value" "45" + "special_bonus_unique_troll_warlord_1_1" "25" + } + "base_attack_time" + { + "value" "1.4" + "special_bonus_unique_troll_warlord_1_2" "-0.4" + } + "chance_ensnare" + { + "value" "20" + "special_bonus_unique_troll_warlord_1_4" "+15" + } + "cooldown_ensnare" "1.7" + "duration_ensnare" "1.5" + "ensnare_speed" "700" + "split_radius" + { + "special_bonus_shard" "+200" + } + "max_split_attack" + { + "special_bonus_shard" "+2" + } + } + "AbilityCastAnimation" "ACT_DOTA_CAST_ABILITY_1" + } + + "troll_warlord_active_axes_ranged" + { + "BaseClass" "ability_lua" + "ScriptFile" "abilities/heroes/troll_warlord/troll_warlord_active_axes_ranged" + "AbilityTextureName" "troll_warlord_whirling_axes_ranged" + + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_UNIT_TARGET | DOTA_ABILITY_BEHAVIOR_POINT | DOTA_ABILITY_BEHAVIOR_IGNORE_BACKSWING" + "AbilityUnitTargetTeam" "DOTA_UNIT_TARGET_TEAM_ENEMY" + "AbilityUnitTargetType" "DOTA_UNIT_TARGET_HERO | DOTA_UNIT_TARGET_BASIC" + + "AbilityUnitDamageType" "DAMAGE_TYPE_MAGICAL" + "SpellImmunityType" "SPELL_IMMUNITY_ENEMIES_NO" + "SpellDispellableType" "SPELL_DISPELLABLE_YES" + + "AbilitySound" "Hero_TrollWarlord.WhirlingAxes.Ranged" + "LinkedAbility" "troll_warlord_active_axes_melee" + + "AbilityCastAnimation" "ACT_DOTA_WHIRLING_AXES_RANGED" + "AbilityCastGestureSlot" "DEFAULT" + + "AbilityCastPoint" "0.2 0.2 0.2 0.2" + + "AbilityManacost" "25" + "AbilityCooldown" "12" + + "AbilityValues" + { + "AbilityCastRange" + { + "value" "800" + } + "axe_width" + { + "value" "100" + "affected_by_aoe_increase" "1" + } + "axe_speed" + { + "value" "1500.0" + } + "axe_range" + { + "value" "950.0" + } + "axe_damage" + { + "value" "60 120 180 240" + "special_bonus_unique_troll_warlord_3" "+150" + } + "attack_damage_pct" "100 125 150 175" + "axe_slow_duration" + { + "value" "2.0 2.5 3.0 3.5" + "special_bonus_unique_troll_warlord_2_1" "+20%" + } + "movement_speed" "30 35 40 45" + "axe_spread" "25" + "axe_count" "5" + } + } + + "troll_warlord_active_axes_melee" + { + "BaseClass" "ability_lua" + "ScriptFile" "abilities/heroes/troll_warlord/troll_warlord_active_axes_melee" + "AbilityTextureName" "troll_warlord_whirling_axes_melee" + + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_NO_TARGET | DOTA_ABILITY_BEHAVIOR_IMMEDIATE" + "AbilityUnitDamageType" "DAMAGE_TYPE_MAGICAL" + + "SpellImmunityType" "SPELL_IMMUNITY_ENEMIES_NO" + "SpellDispellableType" "SPELL_DISPELLABLE_YES" + "FightRecapLevel" "1" + "AbilitySound" "Hero_TrollWarlord.WhirlingAxes.Melee" + "LinkedAbility" "troll_warlord_active_axes_ranged" + + "AbilityCastAnimation" "ACT_DOTA_CAST_ABILITY_3" + "AbilityCastGestureSlot" "DEFAULT" + "AbilityCastPoint" "0.0" + + "AbilityManacost" "25" + "AbilityCooldown" "12" + + "AbilityValues" + { + "damage" + { + "value" "75 150 225 300" + "special_bonus_unique_troll_warlord_3" "+100" + } + "attack_damage_pct" "100 150 200 250" + "hit_radius" + { + "value" "100" + "affected_by_aoe_increase" "1" + } + "max_range" + { + "value" "450.0" + "affected_by_aoe_increase" "1" + } + "axe_movement_speed" "1250" + "debuff_duration" + { + "value" "0.5" + "special_bonus_unique_troll_warlord_2_1" "+50%" + } + "whirl_duration" "3.0" + } + } + + "troll_warlord_fervor_custom" + { + "BaseClass" "ability_lua" + "ScriptFile" "abilities/heroes/troll_warlord/troll_warlord_fervor_custom" + "AbilityTextureName" "troll_warlord_fervor" + + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_PASSIVE" + "SpellDispellableType" "SPELL_DISPELLABLE_NO" + "HasScepterUpgrade" "1" + + "IsBreakable" "1" + + "AbilityValues" + { + "attack_speed" + { + "value" "15 20 25 30" + "special_bonus_unique_troll_warlord_5" "+8" + } + "max_stacks" "14" + + + "stack_linger_duration" "4.0" + "stack_linger_bar_extra" "0.75" + "locked_attack_speed" "700" + "pct_damage_per_attack_speed" + { + "special_bonus_scepter" "+1" + } + } + } + + "troll_warlord_battle_trance_custom" + { + "BaseClass" "ability_lua" + "ScriptFile" "abilities/heroes/troll_warlord/troll_warlord_battle_trance_custom" + "AbilityTextureName" "troll_warlord_battle_trance" + + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_NO_TARGET | DOTA_ABILITY_BEHAVIOR_IMMEDIATE" + "SpellDispellableType" "SPELL_DISPELLABLE_NO" + "FightRecapLevel" "2" + "AbilitySound" "Hero_TrollWarlord.BattleTrance.Cast" + "AbilityType" "ABILITY_TYPE_ULTIMATE" + "MaxLevel" "3" + + "AbilityCastAnimation" "ACT_DOTA_CAST_ABILITY_4" + "AbilityCastGestureSlot" "DEFAULT" + "AbilityCastPoint" "0.0 0.0 0.0" + + "AbilityValues" + { + "AbilityCastRange" + { + "value" "525" + } + "AbilityManaCost" + { + "value" "100" + } + "trance_duration" + { + "value" "6.5" + } + "AbilityCooldown" + { + "value" "90 80 70" + "special_bonus_unique_troll_warlord_4_1" "-20" + } + "lifesteal" "40 60 80" + "attack_speed" "140 170 200" + "movement_speed" + { + "value" "25 30 35" + "special_bonus_unique_troll_warlord_battle_trance_movespeed" "+10" + } + "range" + { + "value" "900" + "affected_by_aoe_increase" "1" + } + } + } + + "special_bonus_unique_troll_warlord_1_1" + { + "AbilityType" "ABILITY_TYPE_ATTRIBUTES" + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_PASSIVE" + "BaseClass" "special_bonus_base" + } + + "special_bonus_unique_troll_warlord_1_2" + { + "AbilityType" "ABILITY_TYPE_ATTRIBUTES" + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_PASSIVE" + "BaseClass" "special_bonus_base" + } + + "special_bonus_unique_troll_warlord_2_1" + { + "AbilityType" "ABILITY_TYPE_ATTRIBUTES" + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_PASSIVE" + "BaseClass" "special_bonus_base" + } + + "special_bonus_unique_troll_warlord_4_1" + { + "AbilityType" "ABILITY_TYPE_ATTRIBUTES" + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_PASSIVE" + "BaseClass" "special_bonus_base" + } + + "special_bonus_unique_troll_warlord_1_3" + { + "AbilityType" "ABILITY_TYPE_ATTRIBUTES" + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_PASSIVE" + "BaseClass" "special_bonus_base" + } + + "special_bonus_unique_troll_warlord_3" + { + "AbilityType" "ABILITY_TYPE_ATTRIBUTES" + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_PASSIVE" + "BaseClass" "special_bonus_base" + } + + "special_bonus_unique_troll_warlord_5" + { + "AbilityType" "ABILITY_TYPE_ATTRIBUTES" + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_PASSIVE" + "BaseClass" "special_bonus_base" + } + + "special_bonus_unique_troll_warlord_1_4" + { + "AbilityType" "ABILITY_TYPE_ATTRIBUTES" + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_PASSIVE" + "BaseClass" "special_bonus_base" + } + + "special_bonus_unique_troll_warlord_battle_trance_movespeed" + { + "AbilityType" "ABILITY_TYPE_ATTRIBUTES" + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_PASSIVE" + "BaseClass" "special_bonus_base" + } +} diff --git a/scripts/npc/heroes/vengefulspirit/vengefulspirit.kv b/scripts/npc/heroes/vengefulspirit/vengefulspirit.kv new file mode 100644 index 0000000..dc85c71 --- /dev/null +++ b/scripts/npc/heroes/vengefulspirit/vengefulspirit.kv @@ -0,0 +1,213 @@ +"DOTAAbilities" +{ + "vengefulspirit_magic_missile_custom" + { + "BaseClass" "ability_lua" + "ScriptFile" "abilities/heroes/vengefulspirit/vengefulspirit_magic_missile_custom.lua" + "AbilityTextureName" "vengefulspirit_magic_missile" + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_UNIT_TARGET" + "AbilityUnitTargetTeam" "DOTA_UNIT_TARGET_TEAM_ENEMY" + "AbilityUnitTargetType" "DOTA_UNIT_TARGET_HERO | DOTA_UNIT_TARGET_BASIC" + "AbilityUnitDamageType" "DAMAGE_TYPE_MAGICAL" + "SpellImmunityType" "SPELL_IMMUNITY_ENEMIES_NO" + "SpellDispellableType" "SPELL_DISPELLABLE_YES_STRONG" + "AbilitySound" "Hero_VengefulSpirit.MagicMissile" + "AbilityCastPoint" "0.3 0.3 0.3 0.3" + "AbilityCooldown" "12 11 10 9" + "AbilityManaCost" "90 95 100 105" + "AbilityValues" + { + "speed" "1350" + "stun_duration" + { + "value" "1.5 1.9 2.3 2.7" + "special_bonus_unique_vengeful_spirit_1" "+0.3" + } + "mana_burn" "60 120 180 240" + "damage" "150 300 450 600" + "AbilityCastRange" + { + "value" "650" + "special_bonus_unique_vengeful_spirit_missile_castrange" "+100" + } + } + "AbilityCastAnimation" "ACT_DOTA_CAST_ABILITY_1" + } + + "vengefulspirit_wave_of_terror_custom" + { + "BaseClass" "ability_lua" + "ScriptFile" "abilities/heroes/vengefulspirit/vengefulspirit_wave_of_terror_custom.lua" + "AbilityTextureName" "vengefulspirit_wave_of_terror" + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_POINT" + "AbilityUnitDamageType" "DAMAGE_TYPE_MAGICAL" + "SpellImmunityType" "SPELL_IMMUNITY_ENEMIES_NO" + "SpellDispellableType" "SPELL_DISPELLABLE_YES" + "AbilitySound" "Hero_VengefulSpirit.WaveOfTerror" + "AbilityCastRange" "1400" + "AbilityCastPoint" "0.3 0.3 0.3 0.3" + "AbilityCooldown" "16 14 12 10" + "AbilityDuration" "8" + "AbilityManaCost" "40" + "AbilityValues" + { + "damage" "100 150 200 250" + "wave_speed" "2000.0" + "wave_width" + { + "value" "325" + "affected_by_aoe_increase" "1" + } + "armor_reduction" + { + "value" "-4 -6 -8 -10" + "special_bonus_unique_vengeful_spirit_4" "+-4" + } + "armor_reduction_pct" "12 16 20 24" + "attack_reduction" "10 15 20 25" + "vision_aoe" + { + "value" "350" + "affected_by_aoe_increase" "1" + } + "vision_duration" "4" + "steal_pct" + { + "value" "0" + "special_bonus_unique_vengeful_spirit_wave_of_terror_steal" "+10" + } + } + "AbilityCastAnimation" "ACT_DOTA_CAST_ABILITY_2" + } + + "ability_vengefulspirit_command_aura_custom" + { + "BaseClass" "ability_lua" + "ScriptFile" "abilities/heroes/vengefulspirit/ability_vengefulspirit_command_aura_custom.lua" + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_PASSIVE | DOTA_ABILITY_BEHAVIOR_AURA" + "AbilityTextureName" "vengefulspirit_command_aura" + "AbilityUnitTargetTeam" "DOTA_UNIT_TARGET_TEAM_FRIENDLY" + "SpellImmunityType" "SPELL_IMMUNITY_ALLIES_YES" + "IsBreakable" "1" + "AbilityCastRange" "1200" + "AbilityCastAnimation" "ACT_DOTA_CAST_ABILITY_3" + "AbilityValues" + { + "bonus_base_damage" + { + "value" "10 18 26 34" + "special_bonus_unique_vengeful_spirit_2" "+22" + } + "physical_vampirism" "8 12 16 20" + "magical_vampirism" "4 6 8 10" + "aura_radius" + { + "value" "1200" + "affected_by_aoe_increase" "1" + } + } + } + + "ability_vengefulspirit_spirit_debt_innate" + { + "BaseClass" "ability_lua" + "ScriptFile" "abilities/heroes/vengefulspirit/ability_vengefulspirit_spirit_debt_innate.lua" + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_PASSIVE | DOTA_ABILITY_BEHAVIOR_HIDDEN" + "AbilityType" "ABILITY_TYPE_BASIC" + "Innate" "1" + "IsBreakable" "1" + "AbilityTextureName" "vengefulspirit_command_aura_alt1" + "MaxLevel" "1" + "AbilityValues" + { + "mark_duration" "8" + "heal_on_kill_pct" "4" + "mana_restore" "35" + "ally_heal_share_pct" "50" + "fallback_aura_radius" "1200" + } + } + + "vengefulspirit_revenge" + { + "BaseClass" "ability_lua" + "ScriptFile" "abilities/heroes/vengefulspirit/vengefulspirit_revenge.lua" + "AbilityTextureName" "vengefulspirit_command_aura_alt2" + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_NO_TARGET" + "AbilityUnitTargetTeam" "DOTA_UNIT_TARGET_TEAM_ENEMY" + "AbilityUnitTargetType" "DOTA_UNIT_TARGET_HERO | DOTA_UNIT_TARGET_BASIC" + "AbilityUnitDamageType" "DAMAGE_TYPE_MAGICAL" + "SpellImmunityType" "SPELL_IMMUNITY_ENEMIES_NO" + "SpellDispellableType" "SPELL_DISPELLABLE_YES_STRONG" + "IsGrantedByShard" "1" + "AbilitySound" "Hero_VengefulSpirit.MagicMissile" + "MaxLevel" "1" + "AbilityCastPoint" "0.3" + "AbilityCooldown" "14" + "AbilityManaCost" "90" + "AbilityValues" + { + "duration" "4.5" + "crit_bonus" "150" + } + "AbilityCastAnimation" "ACT_DOTA_CAST_ABILITY_1" + } + + "vengefulspirit_nether_swap_custom" + { + "BaseClass" "ability_lua" + "ScriptFile" "abilities/heroes/vengefulspirit/vengefulspirit_nether_swap_custom.lua" + "AbilityTextureName" "vengefulspirit_nether_swap" + "AbilityType" "ABILITY_TYPE_ULTIMATE" + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_UNIT_TARGET" + "AbilityUnitTargetTeam" "DOTA_UNIT_TARGET_TEAM_BOTH" + "AbilityUnitTargetType" "DOTA_UNIT_TARGET_HERO | DOTA_UNIT_TARGET_BASIC" + "AbilityUnitTargetFlags" "DOTA_UNIT_TARGET_FLAG_INVULNERABLE" + "AbilityUnitDamageType" "DAMAGE_TYPE_MAGICAL" + "SpellImmunityType" "SPELL_IMMUNITY_ENEMIES_YES" + "SpellDispellableType" "SPELL_DISPELLABLE_YES" + "AbilitySound" "Hero_VengefulSpirit.NetherSwap" + "AbilityCastRange" "850 975 1100" + "AbilityCastPoint" "0.4" + "AbilityCastAnimation" "ACT_DOTA_CAST_ABILITY_4" + "AbilityCooldown" "46 38 30" + "AbilityManaCost" "100 150 200" + "AbilityValues" + { + "hit_point_minimum_pct" + { + "value" "50 40 30" + "DamageTypeTooltip" "DAMAGE_TYPE_NONE" + } + "heal_or_damage" + { + "value" "0" + "special_bonus_unique_vengefulspirit_4_1" "+12" + } + "has_dispell" + { + "value" "0" + "special_bonus_unique_vengefulspirit_4_2" "+1" + } + "AbilityCooldown" + { + "value" "60 50 40" + "special_bonus_unique_vengeful_spirit_9" "-18" + } + } + } + + "special_bonus_unique_vengefulspirit_4_1" + { + "AbilityType" "ABILITY_TYPE_ATTRIBUTES" + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_PASSIVE" + "BaseClass" "special_bonus_base" + } + + "special_bonus_unique_vengefulspirit_4_2" + { + "AbilityType" "ABILITY_TYPE_ATTRIBUTES" + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_PASSIVE" + "BaseClass" "special_bonus_base" + } +} diff --git a/scripts/npc/heroes/yuki-onna/yuki-onna.kv b/scripts/npc/heroes/yuki-onna/yuki-onna.kv new file mode 100644 index 0000000..39b0a9b --- /dev/null +++ b/scripts/npc/heroes/yuki-onna/yuki-onna.kv @@ -0,0 +1,364 @@ +"DOTAAbilities" +{ + + "ability_yuki_pohela" + { + // General + //------------------------------------------------------------------------------------------------------------- + "BaseClass" "ability_lua" + "ScriptFile" "abilities/heroes/yuki-onna/ability_yuki_pohela.lua" + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_UNIT_TARGET | DOTA_ABILITY_BEHAVIOR_CHANNELLED" + "AbilityUnitTargetTeam" "DOTA_UNIT_TARGET_TEAM_FRIENDLY" + "AbilityUnitTargetType" "DOTA_UNIT_TARGET_HERO" + "AbilityTextureName" "ancient_apparition_cold_feet" + + "MaxLevel" "4" + + // Casting + //------------------------------------------------------------------------------------------------------------- + "AbilityCastRange" "600" + + // Time + //------------------------------------------------------------------------------------------------------------- + "AbilityCooldown" "40" + + // Cost + //------------------------------------------------------------------------------------------------------------- + "AbilityManaCost" "70 90 125 160" + + // Special + //------------------------------------------------------------------------------------------------------------- + "AbilityValues" + { + "AbilityChannelTime" + { + "value" "6" + } + "channel_duration" + { + "value" "6" + } + "bonus_resistance" + { + "value" "20 25 30 35" + "special_bonus_unique_yuki_pohela_resistance" "+15" + } + "reduce_atack_speed" "10 15 20 25" + "debuff_duration" "1 1.25 1.5 1.75" + + } + "AbilityCastAnimation" "ACT_DOTA_CAST_ABILITY_3" + } + + "ability_yuki_frostshtorm" + { + // General + //------------------------------------------------------------------------------------------------------------- + "BaseClass" "ability_lua" + "ScriptFile" "abilities/heroes/yuki-onna/ability_yuki_frostshtorm" + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_POINT | DOTA_ABILITY_BEHAVIOR_AOE" + "SpellImmunityType" "SPELL_IMMUNITY_ENEMIES_NO" + "SpellDispellableType" "SPELL_DISPELLABLE_YES" + "AbilityTextureName" "old_heroes/yuki/froststorm" + + + "AbilityCastRange" "675" + "AbilityCastPoint" "0.6" + + "MaxLevel" "4" + + // Time + //------------------------------------------------------------------------------------------------------------- + "AbilityCooldown" "35" + + // Cost + //------------------------------------------------------------------------------------------------------------- + "AbilityManaCost" "60 100 140 200" + + // Special + //------------------------------------------------------------------------------------------------------------- + "AbilityValues" + { + "radius" + { + "value" "300 325 350 375" + } + "duration" + { + "value" "8" + "special_bonus_unique_yuki_frostshtorm_duration" "+5" + + } + "dmg_increese" "20 40 60 80" + "dmg_reduced" "-20 -40 -60 -80" + + } + "AbilityCastAnimation" "ACT_DOTA_CAST_ABILITY_2" + } + + "ability_yuki_revenge" + { + "BaseClass" "ability_lua" + "ScriptFile" "abilities/heroes/yuki-onna/ability_yuki_revenge.lua" + "AbilityTextureName" "old_heroes/yuki/revenge" + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_PASSIVE" + "AbilityUnitDamageType" "DAMAGE_TYPE_MAGICAL" + "SpellImmunityType" "SPELL_IMMUNITY_ENEMIES_NO" + + "MaxLevel" "4" + + // Special + //------------------------------------------------------------------------------------------------------------- + "AbilityValues" + { + "mana_damage_pct" + { + "value" "10 20 30 40" + "CalculateSpellDamageTooltip" "0" + } + + "radius" + { + "value" "375" + } + + "int_think" "1" + + "heal" + { + "value" "15 30 45 60" + "special_bonus_unique_yuki_revenge_heal" "+145" + } + "base_damage" + { + "value" "45 65 85 105" + "special_bonus_unique_yuki_revenge_damage" "+105" + } + + + + } + "AbilityCastAnimation" "ACT_DOTA_CAST_ABILITY_3" + } + + "ability_yuki_ritual" + { + "BaseClass" "ability_lua" + "ScriptFile" "abilities/heroes/yuki-onna/ability_yuki_ritual.lua" + "AbilityTextureName" "old_heroes/yuki/ritual" + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_NO_TARGET" + "SpellImmunityType" "SPELL_IMMUNITY_ENEMIES_YES" + "AbilityUnitTargetTeam" "DOTA_UNIT_TARGET_TEAM_BOTH" + + "AbilityCastRange" "800" + "AbilityCastPoint" "2.5" + + "AbilityCooldown" "60" + "AbilityManaCost" "550" + + "RequiredLevel" "25" + "MaxLevel" "1" + + "AbilityValues" + { + "duration" "14" + + "bonus_armor" "200" + "bonus_hp_regen" + { + "value" "100" + + } + "bonus_hp_regen_pct" "2" + "reduce_ms" "100" + "reduce_as" "50" + } + } + + "ability_yuki_challenge" + { + // General + //------------------------------------------------------------------------------------------------------------- + "BaseClass" "ability_lua" + "ScriptFile" "abilities/heroes/yuki-onna/ability_yuki_challenge.lua" + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_UNIT_TARGET" + "AbilityUnitTargetTeam" "DOTA_UNIT_TARGET_TEAM_FRIENDLY" + "AbilityUnitTargetType" "DOTA_UNIT_TARGET_HERO" + "SpellImmunityType" "SPELL_IMMUNITY_ENEMIES_YES" + "AbilityTextureName" "old_heroes/yuki/challenge" + "HasScepterUpgrade" "1" + "HasShardUpgrade" "1" + "RequiredLevel" "10" + "MaxLevel" "3" + "LevelsBetweenUpgrades" "5" + + // Casting + //------------------------------------------------------------------------------------------------------------- + "AbilityCastRange" "400" + + // Time + //------------------------------------------------------------------------------------------------------------- + "AbilityCooldown" "90" + + // Cost + //------------------------------------------------------------------------------------------------------------- + "AbilityManaCost" "600 1200 1800" + + // Special + //------------------------------------------------------------------------------------------------------------- + "AbilityValues" + { + + "duration" "6" + "dmg_per_atr" + { + "value" "6" + "special_bonus_shard" "-2.5" + "CalculateSpellDamageTooltip" "0" + } + "interval" "0.2" + "bonus_main" "4 8 12" + "dmg_incr" "50" + "dmg_reduce" "30" + "main_pct" "8" + + } + "AbilityCastAnimation" "ACT_DOTA_CAST_ABILITY_3" + } + + "ability_yuki_snowman" + { + // General + //------------------------------------------------------------------------------------------------------------- + "BaseClass" "ability_lua" + "ScriptFile" "abilities/heroes/yuki-onna/ability_yuki_snowman.lua" + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_POINT" + "AbilityTextureName" "old_heroes/yuki/snowman" + + "AbilityCastRange" "400 600 800" + "AbilityCastPoint" "0.8" + + "MaxLevel" "3" + "LevelsBetweenUpgrades" "10" + "RequiredLevel" "6" + // Time + //------------------------------------------------------------------------------------------------------------- + "AbilityCooldown" "30" + + // Cost + //------------------------------------------------------------------------------------------------------------- + "AbilityManaCost" "200 400 600" + + // Special + //------------------------------------------------------------------------------------------------------------- + "AbilityValues" + { + "bonus_health" "1200 2400 3600" + "max_snowman" + { + "value" "3 4 5" + "special_bonus_unique_yuki_snowman_max" "+2" + + } + } + "AbilityCastAnimation" "ACT_DOTA_CAST_ABILITY_2" + } + + "ability_yuki_snowball" + { + // General + //------------------------------------------------------------------------------------------------------------- + "BaseClass" "ability_lua" + "ScriptFile" "abilities/heroes/yuki-onna/ability_yuki_snowball.lua" + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_UNIT_TARGET" + "AbilityUnitTargetTeam" "DOTA_UNIT_TARGET_TEAM_FRIENDLY" + "AbilityUnitTargetType" "DOTA_UNIT_TARGET_HERO | DOTA_UNIT_TARGET_BASIC" + "AbilityTextureName" "tusk_snowball" + + + // Casting + //------------------------------------------------------------------------------------------------------------- + "AbilityCastRange" "500 800 1100" + + // Time + //------------------------------------------------------------------------------------------------------------- + "AbilityCooldown" "0" + + // Cost + //------------------------------------------------------------------------------------------------------------- + "AbilityManaCost" "0" + + // Special + //------------------------------------------------------------------------------------------------------------- + "AbilityValues" + { + + "radius" "250" + "heal" "10 25 40" + "slow_as" "-10 -20 -30" + "duration" "2" + + } + "AbilityCastAnimation" "ACT_DOTA_CAST_ABILITY_3" + } + + + + "special_bonus_unique_yuki_revenge_heal" + { + // General + //------------------------------------------------------------------------------------------------------------- + "BaseClass" "special_bonus_undefined" + "AbilityType" "ABILITY_TYPE_ATTRIBUTES" + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_PASSIVE" + + + } + + + "special_bonus_unique_yuki_revenge_damage" + { + // General + //------------------------------------------------------------------------------------------------------------- + "BaseClass" "special_bonus_undefined" + "AbilityType" "ABILITY_TYPE_ATTRIBUTES" + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_PASSIVE" + + + } + + "special_bonus_unique_yuki_pohela_resistance" + { + // General + //------------------------------------------------------------------------------------------------------------- + "BaseClass" "special_bonus_undefined" + "AbilityType" "ABILITY_TYPE_ATTRIBUTES" + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_PASSIVE" + + + } + + "special_bonus_unique_yuki_frostshtorm_duration" + { + // General + //------------------------------------------------------------------------------------------------------------- + "BaseClass" "special_bonus_undefined" + "AbilityType" "ABILITY_TYPE_ATTRIBUTES" + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_PASSIVE" + + } + "special_bonus_unique_yuki_snowman_max" + { + // General + //------------------------------------------------------------------------------------------------------------- + "BaseClass" "special_bonus_undefined" + "AbilityType" "ABILITY_TYPE_ATTRIBUTES" + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_PASSIVE" + + } + + +} + + + \ No newline at end of file diff --git a/scripts/npc/herolist.txt b/scripts/npc/herolist.txt new file mode 100644 index 0000000..85948b5 --- /dev/null +++ b/scripts/npc/herolist.txt @@ -0,0 +1,147 @@ +// +// +// Hero currently on/off, will be # of instances (-1 = infinite) +// + +"CustomHeroList" +{ + + // usefull + + "npc_dota_hero_drow_ranger" "1" + "npc_dota_hero_phantom_assassin" "1" + "npc_dota_hero_lina" "1" + "npc_dota_hero_crystal_maiden" "1" + "npc_dota_hero_axe" "1" + "npc_dota_hero_sven" "1" + + "npc_dota_hero_bloodhunter" "1" + "npc_dota_hero_yuki_onna" "1" + "npc_dota_hero_nagash" "1" + "npc_dota_hero_sargatanas" "1" + "npc_dota_hero_elder_dragon_smaug" "1" + + "npc_dota_hero_pudge" "1" + "npc_dota_hero_medusa" "0" + "npc_dota_hero_rubick" "1" + "npc_dota_hero_luna" "1" + "npc_dota_hero_mirana" "1" + + "npc_dota_hero_queenofpain" "1" + "npc_dota_hero_troll_warlord" "1" + "npc_dota_hero_skywrath_mage" "1" + "npc_dota_hero_silencer" "1" + + "npc_dota_hero_terrorblade" "0" + "npc_dota_hero_jakiro" "0" + "npc_dota_hero_winter_wyvern" "0" + "npc_dota_hero_bloodseeker" "0" + "npc_dota_hero_abyssal_underlord" "0" + + + "npc_dota_hero_juggernaut" "1" + "npc_dota_hero_hoodwink" "1" + "npc_dota_hero_templar_assassin" "1" + "npc_dota_hero_nevermore" "1" + "npc_dota_hero_sniper" "1" + "npc_dota_hero_bristleback" "1" + "npc_dota_hero_ogre_magi" "1" + "npc_dota_hero_legion_commander" "1" + "npc_dota_hero_sand_king" "1" + + "npc_dota_hero_alchemist" "0" + "npc_dota_hero_tidehunter" "0" + "npc_dota_hero_wisp" "0" + + "npc_dota_hero_dazzle" "0" + "npc_dota_hero_clinkz" "0" + "npc_dota_hero_ursa" "0" + "npc_dota_hero_skeleton_king" "0" + "npc_dota_hero_dark_seer" "0" + "npc_dota_hero_enigma" "0" + "npc_dota_hero_muerta" "0" + "npc_dota_hero_treant" "0" + "npc_dota_hero_phantom_lancer" "0" + "npc_dota_hero_slark" "0" + + "npc_dota_hero_huskar" "0" + "npc_dota_hero_tusk" "0" + "npc_dota_hero_lion" "0" + "npc_dota_hero_shredder" "0" + "npc_dota_hero_antimage" "0" + "npc_dota_hero_oracle" "0" + "npc_dota_hero_marci" "0" + "npc_dota_hero_techies" "0" + "npc_dota_hero_gyrocopter" "0" + "npc_dota_hero_ember_spirit" "0" + "npc_dota_hero_lycan" "0" + "npc_dota_hero_dragon_knight" "0" + "npc_dota_hero_spectre" "1" + "npc_dota_hero_tiny" "0" + "npc_dota_hero_obsidian_destroyer" "0" + "npc_dota_hero_vengefulspirit" "1" + "npc_dota_hero_windrunner" "0" + "npc_dota_hero_viper" "0" + "npc_dota_hero_invoker" "0" + "npc_dota_hero_tinker" "0" + "npc_dota_hero_earthshaker" "0" + "npc_dota_hero_omniknight" "0" + "npc_dota_hero_keeper_of_the_light" "1" + "npc_dota_hero_snapfire" "0" + "npc_dota_hero_bounty_hunter" "0" + "npc_dota_hero_kunkka" "0" + "npc_dota_hero_centaur" "0" + "npc_dota_hero_necrolyte" "0" + "npc_dota_hero_enchantress" "0" + "npc_dota_hero_witch_doctor" "0" + "npc_dota_hero_techies" "0" + + "npc_dota_hero_abaddon" "0" + "npc_dota_hero_ancient_apparition" "0" + "npc_dota_hero_bane" "0" + "npc_dota_hero_batrider" "0" + "npc_dota_hero_beastmaster" "0" + "npc_dota_hero_brewmaster" "0" + "npc_dota_hero_broodmother" "0" + "npc_dota_hero_chaos_knight" "0" + "npc_dota_hero_chen" "0" + "npc_dota_hero_rattletrap" "0" + "npc_dota_hero_death_prophet" "0" + "npc_dota_hero_disruptor" "0" + "npc_dota_hero_doom_bringer" "0" + "npc_dota_hero_earth_spirit" "0" + "npc_dota_hero_elder_titan" "0" + "npc_dota_hero_faceless_void" "0" + "npc_dota_hero_monkey_king" "0" + "npc_dota_hero_pangolier" "0" + "npc_dota_hero_leshrac" "0" + "npc_dota_hero_lich" "0" + "npc_dota_hero_life_stealer" "0" + "npc_dota_hero_lone_druid" "0" + "npc_dota_hero_magnataur" "0" + "npc_dota_hero_meepo" "0" + "npc_dota_hero_mirana" "1" + "npc_dota_hero_morphling" "0" + "npc_dota_hero_naga_siren" "0" + "npc_dota_hero_furion" "0" + "npc_dota_hero_night_stalker" "0" + "npc_dota_hero_nyx_assassin" "0" + "npc_dota_hero_phoenix" "0" + "npc_dota_hero_puck" "0" + + "npc_dota_hero_pugna" "0" + "npc_dota_hero_razor" "0" + "npc_dota_hero_riki" "0" + "npc_dota_hero_slardar" "0" + "npc_dota_hero_spirit_breaker" "0" + "npc_dota_hero_storm_spirit" "0" + "npc_dota_hero_shadow_demon" "0" + "npc_dota_hero_shadow_shaman" "0" + "npc_dota_hero_undying" "0" + "npc_dota_hero_venomancer" "0" + "npc_dota_hero_visage" "0" + "npc_dota_hero_warlock" "0" + "npc_dota_hero_weaver" "0" + "npc_dota_hero_zeus" "0" + "npc_dota_hero_zuus" "0" +} \ No newline at end of file diff --git a/scripts/npc/items/blackshop/common.kv b/scripts/npc/items/blackshop/common.kv new file mode 100644 index 0000000..6f5bbf9 --- /dev/null +++ b/scripts/npc/items/blackshop/common.kv @@ -0,0 +1,249 @@ +"DOTAAbilities" +{ + "item_blackshop_common_injector" + { + "BaseClass" "item_lua" + "ScriptFile" "items/blackshop/common/item_blackshop_common_injector" + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_NO_TARGET" + "ItemCost" "0" + "AbilityTextureName" "blackshop/soldier" + "ItemKillable" "0" + "ItemSellable" "0" + "ItemShareability" "ITEM_FULLY_SHAREABLE_STACKING" + "ItemPurchasable" "0" + "Model" "models/props_gameplay/treasure_chest_gold.vmdl" + "Effect" "particles/units/heroes/hero_omniknight/omniknight_heavenly_grace_buff.vpcf" + "AbilityCooldown" "0" + "ItemDeclarations" "DECLARE_PURCHASES_TO_TEAMMATES | DECLARE_PURCHASES_IN_SPEECH | DECLARE_PURCHASES_TO_SPECTATORS" + "AbilityValues" + { + "attack_speed" "15" + "movement_speed" "15" + } + } + "item_blackshop_common_bonus_stats_agi" + { + "BaseClass" "item_lua" + "ScriptFile" "items/blackshop/common/item_blackshop_common_bonus_stats_agi" + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_NO_TARGET" + "ItemCost" "0" + "AbilityTextureName" "blackshop/book_agi" + "ItemKillable" "0" + "ItemSellable" "0" + "ItemShareability" "ITEM_FULLY_SHAREABLE_STACKING" + "ItemPurchasable" "0" + "Model" "models/gameplay/attrib_tome_agi.vmdl" + "Effect" "particles/units/heroes/hero_omniknight/omniknight_heavenly_grace_buff.vpcf" + "AbilityCooldown" "0" + "ItemDeclarations" "DECLARE_PURCHASES_TO_TEAMMATES | DECLARE_PURCHASES_IN_SPEECH | DECLARE_PURCHASES_TO_SPECTATORS" + "AbilityValues" + { + "bonus_agility" "12" + } + } + "item_blackshop_common_bonus_stats_str" + { + "BaseClass" "item_lua" + "ScriptFile" "items/blackshop/common/item_blackshop_common_bonus_stats_str" + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_NO_TARGET" + "ItemCost" "0" + "AbilityTextureName" "blackshop/book_str" + "ItemKillable" "0" + "ItemSellable" "0" + "ItemShareability" "ITEM_FULLY_SHAREABLE_STACKING" + "ItemPurchasable" "0" + "Model" "models/gameplay/attrib_tome_str.vmdl" + "Effect" "particles/units/heroes/hero_omniknight/omniknight_heavenly_grace_buff.vpcf" + "AbilityCooldown" "0" + "ItemDeclarations" "DECLARE_PURCHASES_TO_TEAMMATES | DECLARE_PURCHASES_IN_SPEECH | DECLARE_PURCHASES_TO_SPECTATORS" + "AbilityValues" + { + "bonus_strength" "12" + } + } + "item_blackshop_common_bonus_stats_int" + { + "BaseClass" "item_lua" + "ScriptFile" "items/blackshop/common/item_blackshop_common_bonus_stats_int" + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_NO_TARGET" + "ItemCost" "0" + "AbilityTextureName" "blackshop/book_int" + "ItemKillable" "0" + "ItemSellable" "0" + "ItemShareability" "ITEM_FULLY_SHAREABLE_STACKING" + "ItemPurchasable" "0" + "Model" "models/gameplay/attrib_tome_int.vmdl" + "Effect" "particles/units/heroes/hero_omniknight/omniknight_heavenly_grace_buff.vpcf" + "AbilityCooldown" "0" + "ItemDeclarations" "DECLARE_PURCHASES_TO_TEAMMATES | DECLARE_PURCHASES_IN_SPEECH | DECLARE_PURCHASES_TO_SPECTATORS" + "AbilityValues" + { + "bonus_intelligence" "12" + } + } + "item_blackshop_common_king_crown" + { + "BaseClass" "item_lua" + "ScriptFile" "items/blackshop/common/item_blackshop_common_king_crown" + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_NO_TARGET" + "ItemCost" "0" + "AbilityTextureName" "blackshop/crown" + "ItemKillable" "0" + "ItemSellable" "0" + "ItemShareability" "ITEM_FULLY_SHAREABLE_STACKING" + "ItemPurchasable" "0" + "Model" "models/props_gameplay/treasure_chest_gold.vmdl" + "Effect" "particles/units/heroes/hero_omniknight/omniknight_heavenly_grace_buff.vpcf" + "AbilityCooldown" "0" + "ItemDeclarations" "DECLARE_PURCHASES_TO_TEAMMATES | DECLARE_PURCHASES_IN_SPEECH | DECLARE_PURCHASES_TO_SPECTATORS" + "AbilityValues" + { + "bonus_health" "150" + "bonus_armor" "15" + } + } + "item_blackshop_common_manaflare" + { + "BaseClass" "item_lua" + "ScriptFile" "items/blackshop/common/item_blackshop_common_manaflare" + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_NO_TARGET" + "ItemCost" "0" + "AbilityTextureName" "blackshop/manaflare" + "ItemKillable" "0" + "ItemShareability" "ITEM_FULLY_SHAREABLE_STACKING" + "ItemPurchasable" "0" + "ItemSellable" "0" + "Model" "models/props_gameplay/treasure_chest_gold.vmdl" + "Effect" "particles/units/heroes/hero_omniknight/omniknight_heavenly_grace_buff.vpcf" + "AbilityCooldown" "0" + "ItemDeclarations" "DECLARE_PURCHASES_TO_TEAMMATES | DECLARE_PURCHASES_IN_SPEECH | DECLARE_PURCHASES_TO_SPECTATORS" + "AbilityValues" + { + "bonus_mana" "150" + "bonus_mana_regen" "1.5" + } + } + "item_blackshop_common_spell_mask" + { + "BaseClass" "item_lua" + "ScriptFile" "items/blackshop/common/item_blackshop_common_spell_mask" + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_NO_TARGET" + "ItemCost" "0" + "AbilityTextureName" "blackshop/spell_mask" + "ItemKillable" "0" + "ItemSellable" "0" + "ItemShareability" "ITEM_FULLY_SHAREABLE_STACKING" + "ItemPurchasable" "0" + "Model" "models/props_gameplay/treasure_chest_gold.vmdl" + "Effect" "particles/units/heroes/hero_omniknight/omniknight_heavenly_grace_buff.vpcf" + "AbilityCooldown" "0" + "ItemDeclarations" "DECLARE_PURCHASES_TO_TEAMMATES | DECLARE_PURCHASES_IN_SPEECH | DECLARE_PURCHASES_TO_SPECTATORS" + "AbilityValues" + { + "bonus_spell_amp" "3" + "bonus_mana_regen" "3" + } + } + "item_blackshop_common_boo_stuff" + { + "BaseClass" "item_lua" + "ScriptFile" "items/blackshop/common/item_blackshop_common_boo_stuff" + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_NO_TARGET" + "ItemCost" "0" + "AbilityTextureName" "blackshop/boo_stuff" + "ItemKillable" "0" + "ItemShareability" "ITEM_FULLY_SHAREABLE_STACKING" + "ItemPurchasable" "0" + "ItemSellable" "0" + "Model" "models/props_gameplay/treasure_chest_gold.vmdl" + "Effect" "particles/units/heroes/hero_omniknight/omniknight_heavenly_grace_buff.vpcf" + "AbilityCooldown" "0" + "ItemDeclarations" "DECLARE_PURCHASES_TO_TEAMMATES | DECLARE_PURCHASES_IN_SPEECH | DECLARE_PURCHASES_TO_SPECTATORS" + "AbilityValues" + { + "bonus_stats" "30" + "bonus_attack_range" "50" + } + } + "item_blackshop_common_stone_armor" + { + "BaseClass" "item_lua" + "ScriptFile" "items/blackshop/common/item_blackshop_common_stone_armor" + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_NO_TARGET" + "ItemCost" "0" + "AbilityTextureName" "blackshop/stone_armor" + "ItemKillable" "0" + "ItemSellable" "0" + "ItemShareability" "ITEM_FULLY_SHAREABLE_STACKING" + "ItemPurchasable" "0" + "Model" "models/props_gameplay/treasure_chest_gold.vmdl" + "Effect" "particles/units/heroes/hero_omniknight/omniknight_heavenly_grace_buff.vpcf" + "AbilityCooldown" "0" + "ItemDeclarations" "DECLARE_PURCHASES_TO_TEAMMATES | DECLARE_PURCHASES_IN_SPEECH | DECLARE_PURCHASES_TO_SPECTATORS" + "AbilityValues" + { + "bonus_armor" "6" + } + } + "item_blackshop_common_vigor_tincture" + { + "BaseClass" "item_lua" + "ScriptFile" "items/blackshop/common/item_blackshop_common_vigor_tincture" + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_NO_TARGET" + "ItemCost" "0" + "AbilityTextureName" "blackshop/vigor_salve" + "ItemKillable" "0" + "ItemSellable" "0" + "ItemShareability" "ITEM_FULLY_SHAREABLE_STACKING" + "ItemPurchasable" "0" + "Model" "models/props_gameplay/treasure_chest_gold.vmdl" + "Effect" "particles/units/heroes/hero_omniknight/omniknight_heavenly_grace_buff.vpcf" + "AbilityCooldown" "0" + "ItemDeclarations" "DECLARE_PURCHASES_TO_TEAMMATES | DECLARE_PURCHASES_IN_SPEECH | DECLARE_PURCHASES_TO_SPECTATORS" + "AbilityValues" + { + "bonus_hp_regen" "2" + } + } + "item_blackshop_common_wind_dust" + { + "BaseClass" "item_lua" + "ScriptFile" "items/blackshop/common/item_blackshop_common_wind_dust" + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_NO_TARGET" + "ItemCost" "0" + "AbilityTextureName" "blackshop/granite_boots" + "ItemKillable" "0" + "ItemSellable" "0" + "ItemShareability" "ITEM_FULLY_SHAREABLE_STACKING" + "ItemPurchasable" "0" + "Model" "models/props_gameplay/treasure_chest_gold.vmdl" + "Effect" "particles/units/heroes/hero_omniknight/omniknight_heavenly_grace_buff.vpcf" + "AbilityCooldown" "0" + "ItemDeclarations" "DECLARE_PURCHASES_TO_TEAMMATES | DECLARE_PURCHASES_IN_SPEECH | DECLARE_PURCHASES_TO_SPECTATORS" + "AbilityValues" + { + "bonus_movement_speed" "25" + } + } + "item_blackshop_common_blue_tallow" + { + "BaseClass" "item_lua" + "ScriptFile" "items/blackshop/common/item_blackshop_common_blue_tallow" + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_NO_TARGET" + "ItemCost" "0" + "AbilityTextureName" "blackshop/blue_tallow" + "ItemKillable" "0" + "ItemSellable" "0" + "ItemShareability" "ITEM_FULLY_SHAREABLE_STACKING" + "ItemPurchasable" "0" + "Model" "models/props_gameplay/treasure_chest_gold.vmdl" + "Effect" "particles/units/heroes/hero_omniknight/omniknight_heavenly_grace_buff.vpcf" + "AbilityCooldown" "0" + "ItemDeclarations" "DECLARE_PURCHASES_TO_TEAMMATES | DECLARE_PURCHASES_IN_SPEECH | DECLARE_PURCHASES_TO_SPECTATORS" + "AbilityValues" + { + "bonus_mana_regen" "2" + } + } + +} \ No newline at end of file diff --git a/scripts/npc/items/blackshop/cursed.kv b/scripts/npc/items/blackshop/cursed.kv new file mode 100644 index 0000000..aae5d5a --- /dev/null +++ b/scripts/npc/items/blackshop/cursed.kv @@ -0,0 +1,91 @@ +"DOTAAbilities" +{ + + "item_blackshop_cursed_the_hand_of_gluttony" + { + "BaseClass" "item_lua" + "ScriptFile" "items/blackshop/cursed/item_blackshop_cursed_the_hand_of_gluttony" + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_NO_TARGET" + "ItemCost" "0" + "AbilityTextureName" "blackshop/the_hand_of_gluttony" + "ItemKillable" "0" + "ItemShareability" "ITEM_FULLY_SHAREABLE_STACKING" + "ItemPurchasable" "0" + "ItemSellable" "0" + "Model" "models/props_gameplay/treasure_chest_gold.vmdl" + "Effect" "particles/econ/items/shadow_fiend/sf_desolation/sf_base_attack_desolation.vpcf" + "AbilityCooldown" "0" + "ItemDeclarations" "DECLARE_PURCHASES_TO_TEAMMATES | DECLARE_PURCHASES_IN_SPEECH | DECLARE_PURCHASES_TO_SPECTATORS" + "AbilityValues" + { + "bonus_stats" "66.6" + } + } + "item_blackshop_cursed_martyrs_brand" + { + "BaseClass" "item_lua" + "ScriptFile" "items/blackshop/cursed/item_blackshop_cursed_martyrs_brand" + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_NO_TARGET" + "ItemCost" "0" + "AbilityTextureName" "blackshop/martyrs_brand" + "ItemKillable" "0" + "ItemShareability" "ITEM_FULLY_SHAREABLE_STACKING" + "ItemStackable" "0" + "ItemPurchasable" "0" + "ItemSellable" "0" + "Model" "models/props_gameplay/treasure_chest_gold.vmdl" + "Effect" "particles/econ/items/shadow_fiend/sf_desolation/sf_base_attack_desolation.vpcf" + "AbilityCooldown" "0" + "ItemDeclarations" "DECLARE_PURCHASES_TO_TEAMMATES | DECLARE_PURCHASES_IN_SPEECH | DECLARE_PURCHASES_TO_SPECTATORS" + "AbilityValues" + { + "bonus_damage_per" "30" + "incoming_damage_per_stack" "6.66" + } + } + "item_blackshop_cursed_widow_chain" + { + "BaseClass" "item_lua" + "ScriptFile" "items/blackshop/cursed/item_blackshop_cursed_widow_chain" + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_NO_TARGET" + "ItemCost" "0" + "AbilityTextureName" "blackshop/widow_chain" + "ItemKillable" "0" + "ItemShareability" "ITEM_FULLY_SHAREABLE_STACKING" + "ItemStackable" "0" + "ItemPurchasable" "0" + "ItemSellable" "0" + "Model" "models/props_gameplay/treasure_chest_gold.vmdl" + "Effect" "particles/econ/items/shadow_fiend/sf_desolation/sf_base_attack_desolation.vpcf" + "AbilityCooldown" "0" + "ItemDeclarations" "DECLARE_PURCHASES_TO_TEAMMATES | DECLARE_PURCHASES_IN_SPEECH | DECLARE_PURCHASES_TO_SPECTATORS" + "AbilityValues" + { + "bonus_outgoing_damage_pct" "50" + "attack_speed_loss_pct" "50" + "root_duration" "1.5" + } + } + "item_blackshop_cursed_glass_pact" + { + "BaseClass" "item_lua" + "ScriptFile" "items/blackshop/cursed/item_blackshop_cursed_glass_pact" + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_NO_TARGET" + "ItemCost" "0" + "AbilityTextureName" "blackshop/glass_pact" + "ItemKillable" "0" + "ItemShareability" "ITEM_FULLY_SHAREABLE_STACKING" + "ItemStackable" "0" + "ItemSellable" "0" + "ItemPurchasable" "0" + "Model" "models/props_gameplay/treasure_chest_gold.vmdl" + "Effect" "particles/econ/items/shadow_fiend/sf_desolation/sf_base_attack_desolation.vpcf" + "AbilityCooldown" "0" + "ItemDeclarations" "DECLARE_PURCHASES_TO_TEAMMATES | DECLARE_PURCHASES_IN_SPEECH | DECLARE_PURCHASES_TO_SPECTATORS" + "AbilityValues" + { + "outgoing_damage_pct" "15" + "incoming_damage_pct" "15" + } + } +} \ No newline at end of file diff --git a/scripts/npc/items/blackshop/epic.kv b/scripts/npc/items/blackshop/epic.kv new file mode 100644 index 0000000..e888c19 --- /dev/null +++ b/scripts/npc/items/blackshop/epic.kv @@ -0,0 +1,90 @@ +"DOTAAbilities" +{ + + "item_blackshop_epic_power_of_grow" + { + "BaseClass" "item_lua" + "ScriptFile" "items/blackshop/epic/item_blackshop_epic_power_of_grow" + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_NO_TARGET" + "ItemCost" "0" + "AbilityTextureName" "blackshop/magic_mushroom" + "ItemKillable" "0" + "ItemShareability" "ITEM_FULLY_SHAREABLE_STACKING" + "ItemStackable" "0" + "ItemSellable" "0" + "ItemPurchasable" "0" + "Model" "models/props_gameplay/treasure_chest_gold.vmdl" + "Effect" "particles/econ/items/lanaya/lanaya_epit_trap/templar_assassin_epit_trap.vpcf" + "AbilityCooldown" "0" + "ItemDeclarations" "DECLARE_PURCHASES_TO_TEAMMATES | DECLARE_PURCHASES_IN_SPEECH | DECLARE_PURCHASES_TO_SPECTATORS" + "AbilityValues" + { + "bonus_stats" "30" + } + } + "item_blackshop_epic_critical_paladin_sword" + { + "BaseClass" "item_lua" + "ScriptFile" "items/blackshop/epic/item_blackshop_epic_critical_paladin_sword" + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_NO_TARGET" + "ItemCost" "0" + "AbilityTextureName" "item_paladin_sword" + "ItemKillable" "0" + "ItemShareability" "ITEM_FULLY_SHAREABLE_STACKING" + "ItemStackable" "0" + "ItemSellable" "0" + "ItemPurchasable" "0" + "Model" "models/props_gameplay/treasure_chest_gold.vmdl" + "Effect" "particles/econ/items/lanaya/lanaya_epit_trap/templar_assassin_epit_trap.vpcf" + "AbilityCooldown" "0" + "ItemDeclarations" "DECLARE_PURCHASES_TO_TEAMMATES | DECLARE_PURCHASES_IN_SPEECH | DECLARE_PURCHASES_TO_SPECTATORS" + "AbilityValues" + { + "crit_multiplier" "175" + } + } + "item_blackshop_epic_trinity_seal" + { + "BaseClass" "item_lua" + "ScriptFile" "items/blackshop/epic/item_blackshop_epic_trinity_seal" + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_NO_TARGET" + "ItemCost" "0" + "AbilityTextureName" "blackshop/trinity_seal" + "ItemKillable" "0" + "ItemShareability" "ITEM_FULLY_SHAREABLE_STACKING" + "ItemStackable" "0" + "ItemPurchasable" "0" + "ItemSellable" "0" + "Model" "models/props_gameplay/treasure_chest_gold.vmdl" + "Effect" "particles/econ/items/lanaya/lanaya_epit_trap/templar_assassin_epit_trap.vpcf" + "AbilityCooldown" "0" + "ItemDeclarations" "DECLARE_PURCHASES_TO_TEAMMATES | DECLARE_PURCHASES_IN_SPEECH | DECLARE_PURCHASES_TO_SPECTATORS" + "AbilityValues" + { + "bonus_all" "14" + } + } + "item_blackshop_epic_bulwark_plate" + { + "BaseClass" "item_lua" + "ScriptFile" "items/blackshop/epic/item_blackshop_epic_bulwark_plate" + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_NO_TARGET" + "ItemCost" "0" + "AbilityTextureName" "blackshop/iron_plate" + "ItemKillable" "0" + "ItemShareability" "ITEM_FULLY_SHAREABLE_STACKING" + "ItemStackable" "0" + "ItemPurchasable" "0" + "ItemSellable" "0" + "Model" "models/props_gameplay/treasure_chest_gold.vmdl" + "Effect" "particles/econ/items/lanaya/lanaya_epit_trap/templar_assassin_epit_trap.vpcf" + "AbilityCooldown" "0" + "ItemDeclarations" "DECLARE_PURCHASES_TO_TEAMMATES | DECLARE_PURCHASES_IN_SPEECH | DECLARE_PURCHASES_TO_SPECTATORS" + "AbilityValues" + { + "bonus_armor_per" "9" + "bonus_mr_per" "4" + } + } + +} \ No newline at end of file diff --git a/scripts/npc/items/blackshop/heavenly.kv b/scripts/npc/items/blackshop/heavenly.kv new file mode 100644 index 0000000..299c020 --- /dev/null +++ b/scripts/npc/items/blackshop/heavenly.kv @@ -0,0 +1,94 @@ +"DOTAAbilities" +{ + "item_blackshop_heavenly_reset_to_zero" + { + "BaseClass" "item_lua" + "ScriptFile" "items/blackshop/heavenly/item_blackshop_heavenly_reset_to_zero" + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_NO_TARGET" + "ItemCost" "0" + "AbilityTextureName" "blackshop/reset_to_zero" + "ItemKillable" "0" + "ItemShareability" "ITEM_FULLY_SHAREABLE_STACKING" + "ItemStackable" "0" + "ItemPurchasable" "0" + "ItemSellable" "0" + "Model" "models/props_gameplay/treasure_chest_gold.vmdl" + "Effect" "particles/econ/items/templar_assassin/ta_2022_immortal/ta_2022_immortal_trap_crimson.vpcf" + "AbilityCooldown" "0" + "ItemDeclarations" "DECLARE_PURCHASES_TO_TEAMMATES | DECLARE_PURCHASES_IN_SPEECH | DECLARE_PURCHASES_TO_SPECTATORS" + "AbilityValues" + { + + } + } + "item_blackshop_heavenly_font_of_mercy" + { + "BaseClass" "item_lua" + "ScriptFile" "items/blackshop/heavenly/item_blackshop_heavenly_font_of_mercy" + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_NO_TARGET | DOTA_ABILITY_BEHAVIOR_AOE" + "ItemCost" "0" + "AbilityTextureName" "blackshop/holy_mercy" + "ItemKillable" "0" + "ItemShareability" "ITEM_FULLY_SHAREABLE_STACKING" + "ItemStackable" "0" + "ItemPurchasable" "0" + "ItemSellable" "0" + "Model" "models/props_gameplay/treasure_chest_gold.vmdl" + "Effect" "particles/econ/items/templar_assassin/ta_2022_immortal/ta_2022_immortal_trap_crimson.vpcf" + "AbilityCooldown" "0" + "ItemDeclarations" "DECLARE_PURCHASES_TO_TEAMMATES | DECLARE_PURCHASES_IN_SPEECH | DECLARE_PURCHASES_TO_SPECTATORS" + "AbilityValues" + { + "heal_flat" "450" + "heal_max_hp_pct" "18" + "radius" "900" + "hero_cooldown" "75" + "crisis_hp_pct" "30" + } + } + "item_blackshop_heavenly_sanctuary_veil" + { + "BaseClass" "item_lua" + "ScriptFile" "items/blackshop/heavenly/item_blackshop_heavenly_sanctuary_veil" + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_NO_TARGET" + "ItemCost" "0" + "AbilityTextureName" "blackshop/sanctuary_veil" + "ItemKillable" "0" + "ItemShareability" "ITEM_FULLY_SHAREABLE_STACKING" + "ItemStackable" "0" + "ItemSellable" "0" + "ItemPurchasable" "0" + "Model" "models/props_gameplay/treasure_chest_gold.vmdl" + "Effect" "particles/econ/items/templar_assassin/ta_2022_immortal/ta_2022_immortal_trap_crimson.vpcf" + "AbilityCooldown" "0" + "ItemDeclarations" "DECLARE_PURCHASES_TO_TEAMMATES | DECLARE_PURCHASES_IN_SPEECH | DECLARE_PURCHASES_TO_SPECTATORS" + "AbilityValues" + { + "status_resist" "28" + "magic_resist" "22" + } + } + "item_blackshop_heavenly_dawn_chorus" + { + "BaseClass" "item_lua" + "ScriptFile" "items/blackshop/heavenly/item_blackshop_heavenly_dawn_chorus" + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_NO_TARGET | DOTA_ABILITY_BEHAVIOR_AOE" + "ItemCost" "0" + "AbilityTextureName" "blackshop/dawn_chorus" + "ItemKillable" "0" + "ItemShareability" "ITEM_FULLY_SHAREABLE_STACKING" + "ItemStackable" "0" + "ItemSellable" "0" + "ItemPurchasable" "0" + "Model" "models/props_gameplay/treasure_chest_gold.vmdl" + "Effect" "particles/econ/items/templar_assassin/ta_2022_immortal/ta_2022_immortal_trap_crimson.vpcf" + "AbilityCooldown" "0" + "ItemDeclarations" "DECLARE_PURCHASES_TO_TEAMMATES | DECLARE_PURCHASES_IN_SPEECH | DECLARE_PURCHASES_TO_SPECTATORS" + "AbilityValues" + { + "radius" "650" + "status_resist" "14" + "magic_resist" "12" + } + } +} \ No newline at end of file diff --git a/scripts/npc/items/blackshop/legendary.kv b/scripts/npc/items/blackshop/legendary.kv new file mode 100644 index 0000000..44cdf8a --- /dev/null +++ b/scripts/npc/items/blackshop/legendary.kv @@ -0,0 +1,131 @@ +"DOTAAbilities" +{ + "item_blackshop_legendary_restock" + { + "BaseClass" "item_lua" + "ScriptFile" "items/blackshop/legendary/item_blackshop_legendary_restock" + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_NO_TARGET" + "ItemCost" "0" + "AbilityTextureName" "blackshop/restock" + "ItemKillable" "0" + "ItemSellable" "0" + "ItemShareability" "ITEM_FULLY_SHAREABLE_STACKING" + "ItemPurchasable" "0" + "Model" "models/props_gameplay/treasure_chest_gold.vmdl" + "Effect" "particles/econ/items/templar_assassin/ta_2022_immortal/ta_2022_immortal_trap_gold.vpcf" + "AbilityCooldown" "0" + "ItemDeclarations" "DECLARE_PURCHASES_TO_TEAMMATES | DECLARE_PURCHASES_IN_SPEECH | DECLARE_PURCHASES_TO_SPECTATORS" + "AbilityValues" + { + + } + } + "item_blackshop_legendary_fire_summoner" + { + "BaseClass" "item_lua" + "ScriptFile" "items/blackshop/legendary/item_blackshop_legendary_fire_summoner" + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_NO_TARGET" + "ItemCost" "0" + "AbilityTextureName" "blackshop/fire_summoner" + "ItemKillable" "0" + "ItemShareability" "ITEM_FULLY_SHAREABLE_STACKING" + "ItemPermanent" "0" + "ItemSellable" "0" + "ItemPurchasable" "0" + "Model" "models/props_gameplay/treasure_chest_gold.vmdl" + "Effect" "particles/econ/items/templar_assassin/ta_2022_immortal/ta_2022_immortal_trap_gold.vpcf" + "AbilityCooldown" "0" + "ItemDeclarations" "DECLARE_PURCHASES_TO_TEAMMATES | DECLARE_PURCHASES_IN_SPEECH | DECLARE_PURCHASES_TO_SPECTATORS" + + "AbilitySpecial" + { + + } + } + "item_blackshop_legendary_primordial_shard" + { + "BaseClass" "item_lua" + "ScriptFile" "items/blackshop/legendary/item_blackshop_legendary_primordial_shard" + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_NO_TARGET" + "ItemCost" "0" + "AbilityTextureName" "blackshop/primordial_shard" + "ItemKillable" "0" + "ItemShareability" "ITEM_FULLY_SHAREABLE_STACKING" + "ItemStackable" "0" + "ItemPurchasable" "0" + "ItemSellable" "0" + "Model" "models/props_gameplay/treasure_chest_gold.vmdl" + "Effect" "particles/econ/items/templar_assassin/ta_2022_immortal/ta_2022_immortal_trap_gold.vpcf" + "AbilityCooldown" "0" + "ItemDeclarations" "DECLARE_PURCHASES_TO_TEAMMATES | DECLARE_PURCHASES_IN_SPEECH | DECLARE_PURCHASES_TO_SPECTATORS" + "AbilityValues" + { + "bonus_all" "22" + } + } + "item_blackshop_legendary_twilight_mirror" + { + "BaseClass" "item_lua" + "ScriptFile" "items/blackshop/legendary/item_blackshop_legendary_twilight_mirror" + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_NO_TARGET" + "ItemCost" "0" + "AbilityTextureName" "blackshop/twilight_mirror" + "ItemKillable" "0" + "ItemSellable" "0" + "ItemShareability" "ITEM_FULLY_SHAREABLE_STACKING" + "ItemStackable" "0" + "ItemPurchasable" "0" + "Model" "models/props_gameplay/treasure_chest_gold.vmdl" + "Effect" "particles/econ/items/templar_assassin/ta_2022_immortal/ta_2022_immortal_trap_gold.vpcf" + "AbilityCooldown" "0" + "ItemDeclarations" "DECLARE_PURCHASES_TO_TEAMMATES | DECLARE_PURCHASES_IN_SPEECH | DECLARE_PURCHASES_TO_SPECTATORS" + "AbilityValues" + { + "spell_amp_pct" "32" + "incoming_damage_pct" "14" + } + } + "item_blackshop_legendary_astral_anchor" + { + "BaseClass" "item_lua" + "ScriptFile" "items/blackshop/legendary/item_blackshop_legendary_astral_anchor" + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_NO_TARGET" + "ItemCost" "0" + "AbilityTextureName" "blackshop/astral_anchor" + "ItemKillable" "0" + "ItemShareability" "ITEM_FULLY_SHAREABLE_STACKING" + "ItemStackable" "0" + "ItemPurchasable" "0" + "ItemSellable" "0" + "Model" "models/props_gameplay/treasure_chest_gold.vmdl" + "Effect" "particles/econ/items/templar_assassin/ta_2022_immortal/ta_2022_immortal_trap_gold.vpcf" + "AbilityCooldown" "0" + "ItemDeclarations" "DECLARE_PURCHASES_TO_TEAMMATES | DECLARE_PURCHASES_IN_SPEECH | DECLARE_PURCHASES_TO_SPECTATORS" + "AbilityValues" + { + "cast_range_bonus" "150" + "move_speed_loss_pct" "10" + } + } + "item_blackshop_legendary_fated_die" + { + "BaseClass" "item_lua" + "ScriptFile" "items/blackshop/legendary/item_blackshop_legendary_fated_die" + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_NO_TARGET" + "ItemCost" "0" + "AbilityTextureName" "blackshop/fated_die" + "ItemKillable" "0" + "ItemShareability" "ITEM_FULLY_SHAREABLE_STACKING" + "ItemStackable" "0" + "ItemPurchasable" "0" + "ItemSellable" "0" + "Model" "models/props_gameplay/treasure_chest_gold.vmdl" + "Effect" "particles/econ/items/templar_assassin/ta_2022_immortal/ta_2022_immortal_trap_gold.vpcf" + "AbilityCooldown" "0" + "ItemDeclarations" "DECLARE_PURCHASES_TO_TEAMMATES | DECLARE_PURCHASES_IN_SPEECH | DECLARE_PURCHASES_TO_SPECTATORS" + "AbilityValues" + { + "luck_bonus" "38" + } + } +} \ No newline at end of file diff --git a/scripts/npc/items/blackshop/rare.kv b/scripts/npc/items/blackshop/rare.kv new file mode 100644 index 0000000..0bfb772 --- /dev/null +++ b/scripts/npc/items/blackshop/rare.kv @@ -0,0 +1,151 @@ +"DOTAAbilities" +{ + "item_blackshop_rare_egg_of_death" + { + "BaseClass" "item_lua" + "ScriptFile" "items/blackshop/rare/item_blackshop_rare_egg_of_death" + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_NO_TARGET" + "ItemCost" "0" + "AbilityTextureName" "blackshop/egg_of_death" + "ItemKillable" "0" + "ItemShareability" "ITEM_FULLY_SHAREABLE_STACKING" + "ItemStackable" "0" + "ItemSellable" "0" + "ItemPurchasable" "0" + "Model" "models/props_gameplay/treasure_chest_gold.vmdl" + "Effect" "particles/units/heroes/hero_ancient_apparition/ancient_apparition_base_attack.vpcf" + "AbilityCooldown" "0" + "ItemDeclarations" "DECLARE_PURCHASES_TO_TEAMMATES | DECLARE_PURCHASES_IN_SPEECH | DECLARE_PURCHASES_TO_SPECTATORS" + "AbilityValues" + { + "bonus_all" "10" + } + } + "item_blackshop_rare_agility_cape" + { + "BaseClass" "item_lua" + "ScriptFile" "items/blackshop/rare/item_blackshop_rare_agility_cape" + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_NO_TARGET" + "ItemCost" "0" + "AbilityTextureName" "blackshop/agility_cape" + "ItemKillable" "0" + "ItemShareability" "ITEM_FULLY_SHAREABLE_STACKING" + "ItemStackable" "0" + "ItemSellable" "0" + "ItemPurchasable" "0" + "Model" "models/props_gameplay/treasure_chest_gold.vmdl" + "Effect" "particles/units/heroes/hero_ancient_apparition/ancient_apparition_base_attack.vpcf" + "AbilityCooldown" "0" + "ItemDeclarations" "DECLARE_PURCHASES_TO_TEAMMATES | DECLARE_PURCHASES_IN_SPEECH | DECLARE_PURCHASES_TO_SPECTATORS" + "AbilityValues" + { + "bonus_agility" "30" + } + } + "item_blackshop_rare_damage_dagger" + { + "BaseClass" "item_lua" + "ScriptFile" "items/blackshop/rare/item_blackshop_rare_damage_dagger" + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_NO_TARGET" + "ItemCost" "0" + "AbilityTextureName" "blackshop/damage_dagger" + "ItemKillable" "0" + "ItemShareability" "ITEM_FULLY_SHAREABLE_STACKING" + "ItemStackable" "0" + "ItemSellable" "0" + "ItemPurchasable" "0" + "Model" "models/props_gameplay/treasure_chest_gold.vmdl" + "Effect" "particles/units/heroes/hero_ancient_apparition/ancient_apparition_base_attack.vpcf" + "AbilityCooldown" "0" + "ItemDeclarations" "DECLARE_PURCHASES_TO_TEAMMATES | DECLARE_PURCHASES_IN_SPEECH | DECLARE_PURCHASES_TO_SPECTATORS" + "AbilityValues" + { + "bonus_damage" "75" + } + } + "item_blackshop_rare_silver_eye" + { + "BaseClass" "item_lua" + "ScriptFile" "items/blackshop/rare/item_blackshop_rare_silver_eye" + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_NO_TARGET" + "ItemCost" "0" + "AbilityTextureName" "blackshop/silver_eye" + "ItemKillable" "0" + "ItemShareability" "ITEM_FULLY_SHAREABLE_STACKING" + "ItemStackable" "0" + "ItemSellable" "0" + "ItemPurchasable" "0" + "Model" "models/props_gameplay/treasure_chest_gold.vmdl" + "Effect" "particles/units/heroes/hero_ancient_apparition/ancient_apparition_base_attack.vpcf" + "AbilityCooldown" "0" + "ItemDeclarations" "DECLARE_PURCHASES_TO_TEAMMATES | DECLARE_PURCHASES_IN_SPEECH | DECLARE_PURCHASES_TO_SPECTATORS" + "AbilityValues" + { + "bonus_vision" "100" + } + } + "item_blackshop_rare_critical_havoc" + { + "BaseClass" "item_lua" + "ScriptFile" "items/blackshop/rare/item_blackshop_rare_critical_havoc" + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_NO_TARGET" + "ItemCost" "0" + "AbilityTextureName" "blackshop/critical_havoc" + "ItemKillable" "0" + "ItemShareability" "ITEM_FULLY_SHAREABLE_STACKING" + "ItemStackable" "0" + "ItemSellable" "0" + "ItemPurchasable" "0" + "Model" "models/props_gameplay/treasure_chest_gold.vmdl" + "Effect" "particles/units/heroes/hero_ancient_apparition/ancient_apparition_base_attack.vpcf" + "AbilityCooldown" "0" + "ItemDeclarations" "DECLARE_PURCHASES_TO_TEAMMATES | DECLARE_PURCHASES_IN_SPEECH | DECLARE_PURCHASES_TO_SPECTATORS" + "AbilityValues" + { + "crit_chance" "15" + "crit_mult" "130" + } + } + "item_blackshop_rare_granite_badge" + { + "BaseClass" "item_lua" + "ScriptFile" "items/blackshop/rare/item_blackshop_rare_granite_badge" + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_NO_TARGET" + "ItemCost" "0" + "AbilityTextureName" "blackshop/granite_stone" + "ItemKillable" "0" + "ItemShareability" "ITEM_FULLY_SHAREABLE_STACKING" + "ItemStackable" "0" + "ItemSellable" "0" + "ItemPurchasable" "0" + "Model" "models/props_gameplay/treasure_chest_gold.vmdl" + "Effect" "particles/units/heroes/hero_ancient_apparition/ancient_apparition_base_attack.vpcf" + "AbilityCooldown" "0" + "ItemDeclarations" "DECLARE_PURCHASES_TO_TEAMMATES | DECLARE_PURCHASES_IN_SPEECH | DECLARE_PURCHASES_TO_SPECTATORS" + "AbilityValues" + { + "bonus_health" "100" + } + } + "item_blackshop_rare_iron_resolve" + { + "BaseClass" "item_lua" + "ScriptFile" "items/blackshop/rare/item_blackshop_rare_iron_resolve" + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_NO_TARGET" + "ItemCost" "0" + "AbilityTextureName" "blackshop/ironwood_tree" + "ItemKillable" "0" + "ItemShareability" "ITEM_FULLY_SHAREABLE_STACKING" + "ItemStackable" "0" + "ItemSellable" "0" + "ItemPurchasable" "0" + "Model" "models/props_gameplay/treasure_chest_gold.vmdl" + "Effect" "particles/units/heroes/hero_ancient_apparition/ancient_apparition_base_attack.vpcf" + "AbilityCooldown" "0" + "ItemDeclarations" "DECLARE_PURCHASES_TO_TEAMMATES | DECLARE_PURCHASES_IN_SPEECH | DECLARE_PURCHASES_TO_SPECTATORS" + "AbilityValues" + { + "bonus_status_resist" "10" + } + } +} \ No newline at end of file diff --git a/scripts/npc/items/custom_items.kv b/scripts/npc/items/custom_items.kv new file mode 100644 index 0000000..17d80d4 --- /dev/null +++ b/scripts/npc/items/custom_items.kv @@ -0,0 +1,2518 @@ +"DOTAAbilities" +{ + "item_test" + { + "BaseClass" "item_lua" + "ScriptFile" "items/default_items/item_test" + "AbilityTextureName" "default_items/medkit" + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_UNIT_TARGET" + "AbilityUnitTargetTeam" "DOTA_UNIT_TARGET_TEAM_FRIENDLY | DOTA_UNIT_TARGET_TEAM_ENEMY" + "AbilityUnitTargetType" "DOTA_UNIT_TARGET_HERO | DOTA_UNIT_TARGET_BASIC" + "AbilityUnitTargetFlags" "DOTA_UNIT_TARGET_FLAG_NONE" + "AbilityChannelTime" "0" + + "ItemInitialCharges" "1" + "ItemStackable" "1" + + "AbilityCastAnimation" "ACT_DOTA_GENERIC_CHANNEL_1" + "Model" "models/props_gameplay/neutral_box.vmdl" + "Effect" "particles/heavenly_item_effect.vpcf" + + "AbilityCooldown" "0.0" + "ItemCost" "0" + "ItemPurchasable" "0" + "ItemQuality" "" + "ItemSellable" "0" + "ItemShareability" "ITEM_FULLY_SHAREABLE_STACKING" + "ItemPermanent" "0" + "ItemKillable" "0" + + } + "item_blink" + { + "ID" "1" + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_POINT | DOTA_ABILITY_BEHAVIOR_DIRECTIONAL | DOTA_ABILITY_BEHAVIOR_ROOT_DISABLES | DOTA_ABILITY_BEHAVIOR_OVERSHOOT" + "AbilityCastRange" "99999" + "AbilityOvershootCastRange" "960" + "AbilityCastPoint" "0.0" + "AbilityCooldown" "0.0" + "AbilityManaCost" "0" + "AbilitySharedCooldown" "blink" + "ItemCost" "2250" + "ItemShopTags" "teleport" + "ItemQuality" "component" + "ItemAliases" "blink dagger" + //"SideShop" "1" + "ItemDeclarations" "DECLARE_PURCHASES_TO_TEAMMATES | DECLARE_PURCHASES_IN_SPEECH | DECLARE_PURCHASES_TO_SPECTATORS" + "ShouldBeSuggested" "1" + "ItemPurchasable" "0" + "AbilityValues" + { + + "blink_range" "999999" + + + "blink_damage_cooldown" "0.0" + + "blink_range_clamp" "960" + + } + } + "item_bag_of_gold" + { + "BaseClass" "item_lua" + "ScriptFile" "items/default_items/item_bag_of_gold" + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_NO_TARGET | DOTA_ABILITY_BEHAVIOR_IMMEDIATE | DOTA_ABILITY_BEHAVIOR_DONT_RESUME_ATTACK" + "AbilityUnitTargetTeam" "DOTA_UNIT_TARGET_TEAM_FRIENDLY" + "AbilityUnitTargetType" "DOTA_UNIT_TARGET_HERO" + "Model" "models/props_gameplay/gold_bag.vmdl" + "PingOverrideText" "DOTA_Chat_Tombstone_Pinged" + + "AbilityTextureName" "default_items/golden_bag" + "ItemKillable" "0" + "AbilityCastRange" "100" + "AbilityCastPoint" "0.0" + "ItemCost" "0" + "ItemPurchasable" "0" + "ItemShopTags" "consumable" + "ItemQuality" "consumable" + "ItemStackable" "1" + "ItemShareability" "ITEM_FULLY_SHAREABLE" + "ItemPermanent" "0" + "ItemInitialCharges" "1" + "ItemKillable" "0" + "ItemCastOnPickup" "1" + "Effect" "particles/econ/courier/courier_dragon_2024_gold/courier_dragon_2024_gold_ambient_sparkles2.vpcf" + "AbilityValues" + { + + } + + } + "item_recipe_mega_treads" + { + // General + //------------------------------------------------------------------------------------------------------------- + "ID" "2186" + "BaseClass" "item_datadriven" + "Model" "models/props_gameplay/recipe.vmdl" + "AbilityTextureName" "default_items/core_info" + + // Item Info + //------------------------------------------------------------------------------------------------------------- + "ItemCost" "0" + "ItemShopTags" "" + + // Recipe + //------------------------------------------------------------------------------------------------------------- + "ItemRecipe" "1" + "ItemResult" "item_mega_treads" + "ItemRequirements" + { + "01" "item_power_treads;item_vambrace" + } + } + + "item_mega_treads" + { + // General + //------------------------------------------------------------------------------------------------------------- + "BaseClass" "item_lua" + "ScriptFile" "items/default_items/item_mega_treads" + "AbilityTextureName" "default_items/item_mega_treads_0" + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_IMMEDIATE | DOTA_ABILITY_BEHAVIOR_NO_TARGET" + + // Item Info + //------------------------------------------------------------------------------------------------------------- + "ItemCost" "1250" + "ItemShopTags" "attack_speed;move_speed;int;agi;str;damage" + "ItemQuality" "magic" + "ItemAliases" "power treads; mega; mega treads" + "ItemDeclarations" "DECLARE_PURCHASES_TO_SPECTATORS" + + // Special + //------------------------------------------------------------------------------------------------------------- + "AbilityValues" + { + "bonus_movement_speed" "65" + "bonus_stat" "20" + "bonus_other_stat" "9" + "bonus_attackspeed" "40" + "bonus_magical_resistance" "15" + "spell_amplify" "15" + } + } + "item_recipe_storm" + { + + "BaseClass" "item_datadriven" + "Model" "models/props_gameplay/recipe.vmdl" + + // Item Info + //------------------------------------------------------------------------------------------------------------- + "ItemCost" "300" + "ItemShopTags" "" + "AbilityTextureName" "default_items/core_info" + + // Recipe + //------------------------------------------------------------------------------------------------------------- + "ItemRecipe" "1" + "ItemResult" "item_storm" + "ItemRequirements" + { + "01" "item_javelin;item_mithril_hammer" + } + } + "item_storm" + { + // General + //------------------------------------------------------------------------------------------------------------- + "BaseClass" "item_lua" + "ScriptFile" "items/default_items/item_storm" + "AbilityTextureName" "default_items/storm" + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_IMMEDIATE | DOTA_ABILITY_BEHAVIOR_PASSIVE" + + // Item Info + //------------------------------------------------------------------------------------------------------------- + "ItemCost" "1800" + "ItemShopTags" "attack;damage" + "ItemQuality" "magic" + "ItemAliases" "storm; electro" + "ItemDeclarations" "DECLARE_PURCHASES_TO_SPECTATORS" + "AbilityCooldown" "3" + + // Special + //------------------------------------------------------------------------------------------------------------- + "AbilityValues" + { + + "bolt_damage" "75" + + "bonus_damage" "30" + + "ability_cooldown" "5" + + "chance" "25" + + "stun" "0.15" + + + } + } + "item_recipe_balist_custom" + { + + "BaseClass" "item_datadriven" + "Model" "models/props_gameplay/recipe.vmdl" + + // Item Info + //------------------------------------------------------------------------------------------------------------- + "ItemCost" "1500" + "ItemShopTags" "" + "AbilityTextureName" "default_items/core_info" + + // Recipe + //------------------------------------------------------------------------------------------------------------- + "ItemRecipe" "1" + "ItemResult" "item_balist_custom" + "ItemRequirements" + { + "01" "item_storm;item_dark_crystalys" + } + } + "item_balist_custom" + { + // General + //------------------------------------------------------------------------------------------------------------- + "BaseClass" "item_lua" + "ScriptFile" "items/default_items/item_balist_custom" + "AbilityTextureName" "item_ballista" + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_IMMEDIATE | DOTA_ABILITY_BEHAVIOR_PASSIVE" + + // Item Info + //------------------------------------------------------------------------------------------------------------- + + "ItemShopTags" "attack;damage" + "ItemQuality" "magic" + "ItemAliases" "storm; electro" + "ItemDeclarations" "DECLARE_PURCHASES_TO_SPECTATORS" + "AbilityCooldown" "3" + + // Special + //------------------------------------------------------------------------------------------------------------- + "AbilityValues" + { + + "bolt_damage" "75" + "crit_multiplier" "25" + "bonus_damage" "155" + "crit_chance" "30" + "crit_mult" "245" + "ability_cooldown" "5" + + "chance" "25" + + "stun" "0.15" + "bolt_radius" "400" + + + } + } + + + "item_recipe_bloodstone_magical" + { + + "BaseClass" "item_datadriven" + "Model" "models/props_gameplay/recipe.vmdl" + + // Item Info + //------------------------------------------------------------------------------------------------------------- + "ItemCost" "550" + "ItemShopTags" "" + "AbilityTextureName" "default_items/core_info" + + // Recipe + //------------------------------------------------------------------------------------------------------------- + "ItemRecipe" "1" + "ItemResult" "item_bloodstone_magical" + "ItemRequirements" + { + "01" "item_voodoo_mask_custom;item_soul_booster;item_soul_ring" + } + } + + "item_lifesteal_custom" + { + "BaseClass" "item_lua" + "ScriptFile" "items/default_items/item_lifesteal_custom" + "AbilityTextureName" "item_lifesteal" + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_PASSIVE" + + "ItemCost" "350" + "ItemShopTags" "unique;lifesteal" + "ItemQuality" "component" + "ItemAliases" "mask;lifesteal; morbit; morbid" + + "AbilityValues" + { + "lifesteal" "12" + } + } + "item_voodoo_mask_custom" + { + "BaseClass" "item_lua" + "ScriptFile" "items/default_items/item_voodoo_mask_custom" + "AbilityTextureName" "item_voodoo_mask" + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_PASSIVE" + + "ItemCost" "350" + "ItemShopTags" "unique;lifesteal" + "ItemQuality" "component" + "ItemAliases" "voodoo mask;voom; mask" + + "AbilityValues" + { + "lifesteal" "18" + } + } + + "item_bloodstone_magical" + { + // General + //------------------------------------------------------------------------------------------------------------- + "BaseClass" "item_lua" + "ScriptFile" "items/default_items/item_bloodstone_magical" + "AbilityTextureName" "default_items/bloodstone2" + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_IMMEDIATE | DOTA_ABILITY_BEHAVIOR_NO_TARGET" + "AbilityCooldown" "45" + // Item Info + //------------------------------------------------------------------------------------------------------------- + "ItemCost" "3350" + "ItemShopTags" "health;mana;health_regen;mana_regen;spell_amp" + "ItemQuality" "magic" + "ItemAliases" "power treads; mega; mega treads" + "ItemDeclarations" "DECLARE_PURCHASES_TO_SPECTATORS" + + // Special + //------------------------------------------------------------------------------------------------------------- + "AbilityValues" + { + "bonus_health" "800" + "bonus_mana" "800" + "bonus_health_regen" "7" + "bonus_mana_regen" "7" + "spell_lifesteal" "18" + "duration" "8" + "spell_lifesteal_active" "50" + "cooldown_reduction_active" "30" + "casttime_reduction" "25" + } + } + "item_recipe_bearstbow" + { + // General + //------------------------------------------------------------------------------------------------------------- + "BaseClass" "item_datadriven" + "Model" "models/props_gameplay/recipe.vmdl" + "AbilityTextureName" "default_items/core_info" + + // Item Info + //------------------------------------------------------------------------------------------------------------- + "ItemCost" "0" + "ItemShopTags" "" + + // Recipe + //------------------------------------------------------------------------------------------------------------- + "ItemRecipe" "1" + "ItemResult" "item_bearstbow" + "ItemRequirements" + { + "01" "item_specialists_array;item_moon_shard" + } + } + "item_bearstbow" + { + // General + //------------------------------------------------------------------------------------------------------------- + "BaseClass" "item_lua" + "ScriptFile" "items/default_items/item_bearstbow" + "AbilityTextureName" "default_items/bearstbow_1" + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_IMMEDIATE | DOTA_ABILITY_BEHAVIOR_PASSIVE" + + // Item Info + //------------------------------------------------------------------------------------------------------------- + "ItemCost" "4050" + "ItemShopTags" "split_attack" + "ItemQuality" "rare" + "ItemAliases" "bearstbow" + "ItemDeclarations" "DECLARE_PURCHASES_TO_SPECTATORS" + + // Special + //------------------------------------------------------------------------------------------------------------- + "AbilityValues" + { + + "attack_count" "1" + "attack_speed" "50" + "attack_range" "150" + //"damage" "65" + "strength" "0" + "agility" "15" + "intellect" "0" + "health" "200" + + } + } + "item_recipe_magical_quiver" + { + // General + //------------------------------------------------------------------------------------------------------------- + "BaseClass" "item_datadriven" + "Model" "models/props_gameplay/recipe.vmdl" + "AbilityTextureName" "default_items/core_info" + + // Item Info + //------------------------------------------------------------------------------------------------------------- + "ItemCost" "0" + "ItemShopTags" "" + + // Recipe + //------------------------------------------------------------------------------------------------------------- + "ItemRecipe" "1" + "ItemResult" "item_magical_quiver" + "ItemRequirements" + { + "01" "item_javelin;item_javelin;item_javelin" + } + } + "item_magical_quiver" + { + // General + //------------------------------------------------------------------------------------------------------------- + "BaseClass" "item_lua" + "ScriptFile" "items/default_items/item_magical_quiver" + "AbilityTextureName" "item_enchanted_quiver" + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_IMMEDIATE | DOTA_ABILITY_BEHAVIOR_PASSIVE" + + // Item Info + //------------------------------------------------------------------------------------------------------------- + "ItemCost" "1200" + "ItemShopTags" "damage" + "ItemQuality" "rare" + "ItemAliases" "bearstbow;bow" + "ItemDeclarations" "DECLARE_PURCHASES_TO_SPECTATORS" + + // Special + //------------------------------------------------------------------------------------------------------------- + "AbilityValues" + { + "proc_damage_magical" "135" + } + } + "item_recipe_demonic_bow" + { + // General + //------------------------------------------------------------------------------------------------------------- + "BaseClass" "item_datadriven" + "Model" "models/props_gameplay/recipe.vmdl" + "AbilityTextureName" "default_items/core_info" + + // Item Info + //------------------------------------------------------------------------------------------------------------- + "ItemCost" "0" + "ItemShopTags" "" + + // Recipe + //------------------------------------------------------------------------------------------------------------- + "ItemRecipe" "1" + "ItemResult" "item_demonic_bow" + "ItemRequirements" + { + "01" "item_bearstbow;item_magical_quiver;item_monkey_king_bar" + } + } + "item_demonic_bow" + { + // General + //------------------------------------------------------------------------------------------------------------- + "BaseClass" "item_lua" + "ScriptFile" "items/default_items/item_demonic_bow" + "AbilityTextureName" "default_items/bearstbow_2" + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_IMMEDIATE | DOTA_ABILITY_BEHAVIOR_PASSIVE" + + // Item Info + //------------------------------------------------------------------------------------------------------------- + + "ItemShopTags" "damage" + "ItemQuality" "rare" + "ItemAliases" "bearstbow" + "ItemDeclarations" "DECLARE_PURCHASES_TO_SPECTATORS" + + // Special + //------------------------------------------------------------------------------------------------------------- + "AbilityValues" + { + + "attack_count" "2" + "attack_speed" "60" + "attack_range" "150" + //"damage" "65" + "proc_damage_magical" "200" + "strength" "0" + "agility" "18" + "intellect" "0" + "health" "200" + + } + } + + "item_recipe_crystalys" + { + // General + //------------------------------------------------------------------------------------------------------------- + "BaseClass" "item_datadriven" + "Model" "models/props_gameplay/recipe.vmdl" + "AbilityTextureName" "default_items/core_info" + + // Item Info + //------------------------------------------------------------------------------------------------------------- + "ItemCost" "0" + "ItemShopTags" "" + + // Recipe + //------------------------------------------------------------------------------------------------------------- + "ItemRecipe" "1" + "ItemResult" "item_crystalys" + "ItemRequirements" + { + "01" "item_demon_edge;item_broadsword;item_blades_of_attack" + } + } + "item_crystalys" + { + // General + //------------------------------------------------------------------------------------------------------------- + "BaseClass" "item_lua" + "ScriptFile" "items/default_items/item_crystalys" + "AbilityTextureName" "item_lesser_crit" + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_IMMEDIATE | DOTA_ABILITY_BEHAVIOR_PASSIVE" + + // Item Info + //------------------------------------------------------------------------------------------------------------- + "ItemCost" "2400" + "ItemShopTags" "damage" + "ItemQuality" "rare" + "ItemAliases" "crit" + "ItemDeclarations" "DECLARE_PURCHASES_TO_SPECTATORS" + + // Special + //------------------------------------------------------------------------------------------------------------- + "AbilityValues" + { + "bonus_damage" "65" + "crit_chance" "30" + "crit_mult" "160" + + } + } + "item_recipe_magical_crit" + { + "BaseClass" "item_datadriven" + "Model" "models/props_gameplay/recipe.vmdl" + "AbilityTextureName" "default_items/core_info" + + "ItemCost" "0" + "ItemShopTags" "" + + "ItemRecipe" "1" + "ItemResult" "item_magical_crit" + "ItemRequirements" + { + "01" "item_mystic_staff;item_magic_stone;item_voodoo_mask_custom" + } + } + "item_magical_crit" + { + "BaseClass" "item_lua" + "ScriptFile" "items/default_items/item_magical_crit" + "AbilityTextureName" "default_items/lia_magic_bow" + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_IMMEDIATE | DOTA_ABILITY_BEHAVIOR_PASSIVE" + + "ItemCost" "3350" + "ItemShopTags" "int;spell_damage;hard_to_tag" + "ItemQuality" "rare" + "ItemAliases" "spell crit;magical crit" + "ItemDeclarations" "DECLARE_PURCHASES_TO_SPECTATORS" + + "AbilityValues" + { + "bonus_intellect" "40" + "bonus_strength" "6" + "bonus_agility" "6" + "bonus_spell_amplify" "9" + "bonus_mana_regen" "2.5" + "bonus_mana_pct" "11" + "magical_vampirism" "18" + "spell_crit_chance" "25" + "spell_crit_mult" "175" + } + } + + "item_recipe_ethereal_blade_custom" + { + "BaseClass" "item_datadriven" + "Model" "models/props_gameplay/recipe.vmdl" + "AbilityTextureName" "default_items/core_info" + + "ItemCost" "0" + "ItemShopTags" "" + + "ItemRecipe" "1" + "ItemResult" "item_ethereal_blade_custom" + "ItemRequirements" + { + "01" "item_ghost;item_trident" + } + } + "item_ethereal_blade_custom" + { + "BaseClass" "item_lua" + "ScriptFile" "items/default_items/item_ethereal_blade_custom" + "AbilityTextureName" "item_ethereal_blade" + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_UNIT_TARGET | DOTA_ABILITY_BEHAVIOR_DONT_RESUME_ATTACK" + "AbilityUnitTargetTeam" "DOTA_UNIT_TARGET_TEAM_BOTH" + "AbilityUnitTargetType" "DOTA_UNIT_TARGET_HERO | DOTA_UNIT_TARGET_BASIC" + "AbilityUnitTargetFlags" "DOTA_UNIT_TARGET_FLAG_NOT_MAGIC_IMMUNE_ALLIES" + "AbilityCastRange" "800" + "AbilityCastPoint" "0.0" + "AbilityCooldown" "22.0" + "AbilityManaCost" "100" + "AbilitySharedCooldown" "ethereal" + "FightRecapLevel" "1" + "SpellDispellableType" "SPELL_DISPELLABLE_YES" + + "ItemCost" "4650" + "ItemShopTags" "int;agi;str;hard_to_tag" + "ItemQuality" "artifact" + "ItemAliases" "eblade;ethereal blade" + "ItemDeclarations" "DECLARE_PURCHASES_TO_SPECTATORS" + "SuggestLategame" "1" + + "AbilityValues" + { + "bonus_strength" "30" + "bonus_agility" "30" + "bonus_intellect" "30" + "status_resistance" "30" + "bonus_attack_speed" "30" + "movement_speed_percent_bonus" "16" + "hp_regen_amp" "30" + "mana_regen_multiplier" "30" + "spell_amp" "30" + "magic_damage_attack" "30" + "abilitycastrange" "800" + "splash_radius" "300" + "projectile_speed" "1275" + "blast_damage_base" "50" + "blast_stat_multiplier" "1.5" + "ethereal_damage_bonus" "25" + "blast_movement_slow" "-80" + "turn_rate_reduction" "-50" + "duration" "4.0" + "duration_ally" "4.0" + "ally_bonus_movespeed" "50" + "ally_bonus_attack_speed" "50" + } + } + "item_recipe_rapier_custom" + { + // General + //------------------------------------------------------------------------------------------------------------- + "BaseClass" "item_datadriven" + "Model" "models/props_gameplay/recipe.vmdl" + "AbilityTextureName" "default_items/core_info" + + // Item Info + //------------------------------------------------------------------------------------------------------------- + "ItemCost" "450" + "ItemShopTags" "" + + // Recipe + //------------------------------------------------------------------------------------------------------------- + "ItemRecipe" "1" + "ItemResult" "item_rapier_custom" + "ItemRequirements" + { + "01" "item_relic" + } + } + "item_rapier_custom" + { + // General + //------------------------------------------------------------------------------------------------------------- + "BaseClass" "item_lua" + "ScriptFile" "items/default_items/item_rapier_custom" + "AbilityTextureName" "default_items/dickpier" + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_IMMEDIATE | DOTA_ABILITY_BEHAVIOR_PASSIVE" + "AbilityUnitDamageType" "DAMAGE_TYPE_PURE" + + // Item Info + //------------------------------------------------------------------------------------------------------------- + + "ItemShopTags" "damage" + "ItemQuality" "rare" + "ItemAliases" "pier" + "ItemDeclarations" "DECLARE_PURCHASES_TO_SPECTATORS" + + // Special + //------------------------------------------------------------------------------------------------------------- + "AbilityValues" + { + "bonus_damage" "110" + "proc_damage" "12" + } + } + "item_recipe_divine_rapier_custom" + { + // General + //------------------------------------------------------------------------------------------------------------- + "BaseClass" "item_datadriven" + "Model" "models/props_gameplay/recipe.vmdl" + "AbilityTextureName" "default_items/core_info" + + // Item Info + //------------------------------------------------------------------------------------------------------------- + "ItemCost" "650" + "ItemShopTags" "" + + // Recipe + //------------------------------------------------------------------------------------------------------------- + "ItemRecipe" "1" + "ItemResult" "item_divine_rapier_custom" + "ItemRequirements" + { + "01" "item_demon_edge;item_rapier_custom" + } + } + "item_divine_rapier_custom" + { + // General + //------------------------------------------------------------------------------------------------------------- + "BaseClass" "item_lua" + "ScriptFile" "items/default_items/item_divine_rapier_custom" + "AbilityTextureName" "item_rapier" + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_IMMEDIATE | DOTA_ABILITY_BEHAVIOR_PASSIVE" + "AbilityUnitDamageType" "DAMAGE_TYPE_PURE" + + // Item Info + //------------------------------------------------------------------------------------------------------------- + + "ItemShopTags" "damage" + "ItemQuality" "rare" + "ItemAliases" "pier" + "ItemDeclarations" "DECLARE_PURCHASES_TO_SPECTATORS" + + // Special + //------------------------------------------------------------------------------------------------------------- + "AbilityValues" + { + "bonus_damage" "350" + "proc_damage" "20" + } + } + "item_recipe_magic_rapier" + { + // General + //------------------------------------------------------------------------------------------------------------- + "BaseClass" "item_datadriven" + "Model" "models/props_gameplay/recipe.vmdl" + "AbilityTextureName" "default_items/core_info" + + // Item Info + //------------------------------------------------------------------------------------------------------------- + "ItemCost" "400" + "ItemShopTags" "" + + // Recipe + //------------------------------------------------------------------------------------------------------------- + "ItemRecipe" "1" + "ItemResult" "item_magic_rapier" + "ItemRequirements" + { + "01" "item_magic_stone;item_angels_demise" + } + } + "item_magic_rapier" + { + // General + //------------------------------------------------------------------------------------------------------------- + "BaseClass" "item_lua" + "ScriptFile" "items/default_items/item_magic_rapier" + "AbilityTextureName" "default_items/magicpier" + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_IMMEDIATE | DOTA_ABILITY_BEHAVIOR_PASSIVE" + "AbilityUnitDamageType" "DAMAGE_TYPE_MAGICAL" + + // Item Info + //------------------------------------------------------------------------------------------------------------- + + "ItemShopTags" "damage" + "ItemQuality" "rare" + "ItemAliases" "pier" + "ItemDeclarations" "DECLARE_PURCHASES_TO_SPECTATORS" + + // Special + //------------------------------------------------------------------------------------------------------------- + "AbilityValues" + { + "bonus_spell_amplify" "20" + "bonus_intellect" "15" + "bonus_strength" "6" + "bonus_agility" "6" + "bonus_mana_regen" "2.5" + "bonus_mana_pct" "11" + "magical_resist_reduction" "-1" + "slow_duration" "4.5" + "slow_pct" "30" + "damage_per_cast" "150" + "damage_per_cast_hand" "55" + } + } + "item_recipe_magic_stone" + { + // General + //------------------------------------------------------------------------------------------------------------- + "BaseClass" "item_datadriven" + "Model" "models/props_gameplay/recipe.vmdl" + "AbilityTextureName" "default_items/core_info" + + // Item Info + //------------------------------------------------------------------------------------------------------------- + "ItemCost" "250" + "ItemShopTags" "" + + // Recipe + //------------------------------------------------------------------------------------------------------------- + "ItemRecipe" "1" + "ItemResult" "item_magic_stone" + "ItemRequirements" + { + "01" "item_null_talisman;item_null_talisman;item_null_talisman" + } + } + "item_magic_stone" + { + // General + //------------------------------------------------------------------------------------------------------------- + "BaseClass" "item_lua" + "ScriptFile" "items/default_items/item_magic_stone" + "AbilityTextureName" "item_seer_stone" + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_IMMEDIATE | DOTA_ABILITY_BEHAVIOR_PASSIVE" + + + // Item Info + //------------------------------------------------------------------------------------------------------------- + + "ItemShopTags" "damage" + "ItemQuality" "rare" + "ItemAliases" "pier" + "ItemDeclarations" "DECLARE_PURCHASES_TO_SPECTATORS" + + // Special + //------------------------------------------------------------------------------------------------------------- + "AbilityValues" + { + "bonus_spell_amplify" "9" + "bonus_intellect" "15" + "bonus_strength" "6" + "bonus_agility" "6" + "bonus_mana_regen" "2.5" + "bonus_mana_pct" "11" + } + } + "item_recipe_magical_divine_rapier" + { + // General + //------------------------------------------------------------------------------------------------------------- + "BaseClass" "item_datadriven" + "Model" "models/props_gameplay/recipe.vmdl" + "AbilityTextureName" "default_items/core_info" + + // Item Info + //------------------------------------------------------------------------------------------------------------- + "ItemCost" "600" + "ItemShopTags" "" + + // Recipe + //------------------------------------------------------------------------------------------------------------- + "ItemRecipe" "1" + "ItemResult" "item_magical_divine_rapier" + "ItemRequirements" + { + "01" "item_mystic_staff;item_magic_rapier" + } + } + "item_magical_divine_rapier" + { + // General + //------------------------------------------------------------------------------------------------------------- + "BaseClass" "item_lua" + "ScriptFile" "items/default_items/item_magical_divine_rapier" + "AbilityTextureName" "default_items/magicrapier" + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_IMMEDIATE | DOTA_ABILITY_BEHAVIOR_PASSIVE" + "AbilityUnitDamageType" "DAMAGE_TYPE_MAGICAL" + + // Item Info + //------------------------------------------------------------------------------------------------------------- + + "ItemShopTags" "damage" + "ItemQuality" "rare" + "ItemAliases" "pier" + "ItemDeclarations" "DECLARE_PURCHASES_TO_SPECTATORS" + + // Special + //------------------------------------------------------------------------------------------------------------- + "AbilityValues" + { + "bonus_spell_amplify" "35" + "bonus_intellect" "55" + "bonus_strength" "12" + "bonus_agility" "12" + "bonus_mana_regen" "4.5" + "bonus_mana_pct" "15" + "magical_resist_reduction" "-2.5" + "slow_duration" "4.5" + "slow_pct" "30" + "damage_per_cast" "200" + "damage_per_cast_hand" "95" + } + } + "item_recipe_soul_devourer_staff" + { + // General + //------------------------------------------------------------------------------------------------------------- + "BaseClass" "item_datadriven" + "Model" "models/props_gameplay/recipe.vmdl" + "AbilityTextureName" "default_items/core_info" + + // Item Info + //------------------------------------------------------------------------------------------------------------- + "ItemCost" "725" + "ItemShopTags" "" + + // Recipe + //------------------------------------------------------------------------------------------------------------- + "ItemRecipe" "1" + "ItemResult" "item_soul_devourer_staff" + "ItemRequirements" + { + "01" "item_staff_of_wizardry;item_robe;item_aether_lens" + } + } + "item_soul_devourer_staff" + { + // General + //------------------------------------------------------------------------------------------------------------- + "BaseClass" "item_lua" + "ScriptFile" "items/default_items/item_soul_devourer_staff" + "AbilityTextureName" "default_items/soul_devourer_staff" + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_IMMEDIATE | DOTA_ABILITY_BEHAVIOR_PASSIVE" + + + // Item Info + //------------------------------------------------------------------------------------------------------------- + + "ItemShopTags" "damage" + "ItemQuality" "rare" + "ItemAliases" "pier" + "ItemDeclarations" "DECLARE_PURCHASES_TO_SPECTATORS" + + // Special + //------------------------------------------------------------------------------------------------------------- + "AbilityValues" + { + "kill_mana" "0.25" + "bonus_mana" "0.5" + "bonus_intellect" "15" + "bonus_mana_special" "300" + "bonus_manacost_reduction" "12" + "bonus_mana_regen" "4.5" + "bonus_cast_range" "225" + "stack_growth_interval" "60" + "stack_growth_percent" "10" + "stack_growth_duration" "62" + } + } + "item_recipe_mini_bfury" + { + // General + //------------------------------------------------------------------------------------------------------------- + "BaseClass" "item_datadriven" + "Model" "models/props_gameplay/recipe.vmdl" + "AbilityTextureName" "default_items/core_info" + + // Item Info + //------------------------------------------------------------------------------------------------------------- + "ItemCost" "0" + "ItemShopTags" "" + + // Recipe + //------------------------------------------------------------------------------------------------------------- + "ItemRecipe" "1" + "ItemResult" "item_mini_bfury" + "ItemRequirements" + { + "01" "item_quelling_blade;item_claymore" + } + } + "item_mini_bfury" + { + // General + //------------------------------------------------------------------------------------------------------------- + "BaseClass" "item_lua" + "ScriptFile" "items/default_items/item_mini_bfury" + "AbilityTextureName" "default_items/mini_bfury" + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_AOE | DOTA_ABILITY_BEHAVIOR_POINT" + "AbilityCastRange" "450" + "AbilityCooldown" "7" + + // Item Info + //------------------------------------------------------------------------------------------------------------- + + "ItemShopTags" "damage" + "ItemQuality" "rare" + "ItemAliases" "pier" + "ItemDeclarations" "DECLARE_PURCHASES_TO_SPECTATORS" + + // Special + //------------------------------------------------------------------------------------------------------------- + "AbilityValues" + { + "tree" "175" + + "cleave_damage" "60" + "bonus_damage" "44" + + "cleave_starting_width" "150" + "cleave_ending_width" "360" + "cleave_distance" "650" + } + } + "item_recipe_battle_fury_custom" + { + // General + //------------------------------------------------------------------------------------------------------------- + "BaseClass" "item_datadriven" + "Model" "models/props_gameplay/recipe.vmdl" + "AbilityTextureName" "default_items/core_info" + + // Item Info + //------------------------------------------------------------------------------------------------------------- + "ItemCost" "0" + "ItemShopTags" "" + + // Recipe + //------------------------------------------------------------------------------------------------------------- + "ItemRecipe" "1" + "ItemResult" "item_battle_fury_custom" + "ItemRequirements" + { + "01" "item_mini_bfury;item_claymore;item_broadsword;item_demon_edge" + } + } + "item_battle_fury_custom" + { + // General + //------------------------------------------------------------------------------------------------------------- + "BaseClass" "item_lua" + "ScriptFile" "items/default_items/item_battle_fury_custom" + "AbilityTextureName" "bfury" + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_AOE | DOTA_ABILITY_BEHAVIOR_POINT" + "AbilityCastRange" "450" + "AbilityCooldown" "7" + + // Item Info + //------------------------------------------------------------------------------------------------------------- + + "ItemShopTags" "damage" + "ItemQuality" "rare" + "ItemAliases" "pier" + "ItemDeclarations" "DECLARE_PURCHASES_TO_SPECTATORS" + + // Special + //------------------------------------------------------------------------------------------------------------- + "AbilityValues" + { + "tree" "250" + + "cleave_damage" "120" + "bonus_damage" "110" + + "cleave_starting_width" "150" + "cleave_ending_width" "360" + "cleave_distance" "650" + } + } + "item_recipe_mega_fury" + { + // General + //------------------------------------------------------------------------------------------------------------- + "BaseClass" "item_datadriven" + "Model" "models/props_gameplay/recipe.vmdl" + "AbilityTextureName" "default_items/core_info" + + // Item Info + //------------------------------------------------------------------------------------------------------------- + "ItemCost" "0" + "ItemShopTags" "" + + // Recipe + //------------------------------------------------------------------------------------------------------------- + "ItemRecipe" "1" + "ItemResult" "item_mega_fury" + "ItemRequirements" + { + "01" "item_battle_fury_custom;item_monkey_king_bar" + } + } + "item_mega_fury" + { + // General + //------------------------------------------------------------------------------------------------------------- + "BaseClass" "item_lua" + "ScriptFile" "items/default_items/item_mega_fury" + "AbilityTextureName" "default_items/mega_fury" + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_AOE | DOTA_ABILITY_BEHAVIOR_POINT" + "AbilityCastRange" "450" + "AbilityCooldown" "7" + + // Item Info + //------------------------------------------------------------------------------------------------------------- + + "ItemShopTags" "damage" + "ItemQuality" "rare" + "ItemAliases" "pier" + "ItemDeclarations" "DECLARE_PURCHASES_TO_SPECTATORS" + + // Special + //------------------------------------------------------------------------------------------------------------- + "AbilityValues" + { + "tree" "250" + + "cleave_damage" "180" + "bonus_damage" "180" + "attack_speed" "45" + "attack_range" "100" + + "cleave_starting_width" "150" + "cleave_ending_width" "360" + "cleave_distance" "650" + } + } + + "item_recipe_mjolnir_custom" + { + // General + //------------------------------------------------------------------------------------------------------------- + "BaseClass" "item_datadriven" + "AbilityTextureName" "default_items/core_info" + "Model" "models/props_gameplay/recipe.vmdl" + + // Item Info + //------------------------------------------------------------------------------------------------------------- + "ItemCost" "900" + "ItemShopTags" "" + + // Recipe + //------------------------------------------------------------------------------------------------------------- + "ItemRecipe" "1" + "ItemResult" "item_mjolnir_custom" + "ItemRequirements" + { + "01" "item_maelstrom;item_magical_quiver;item_moon_shard" + } + } + + "item_mjolnir_custom" + { + // General + //------------------------------------------------------------------------------------------------------------- + + "BaseClass" "item_lua" + "ScriptFile" "items/default_items/item_mjolnir_custom" + "AbilityTextureName" "item_mjollnir" + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_UNIT_TARGET" + "AbilityUnitDamageType" "DAMAGE_TYPE_MAGICAL" + "AbilityUnitTargetType" "DOTA_UNIT_TARGET_HERO | DOTA_UNIT_TARGET_CREEP" + "AbilityUnitTargetTeam" "DOTA_UNIT_TARGET_TEAM_FRIENDLY" + + // Item Info + //------------------------------------------------------------------------------------------------------------- + "ItemCost" "2800" + "ItemShopTags" "damage;attack_speed;unique" + "ItemQuality" "artifact" + "ItemAliases" "maelstrom" + "ItemDeclarations" "DECLARE_PURCHASES_TO_TEAMMATES | DECLARE_PURCHASES_IN_SPEECH | DECLARE_PURCHASES_TO_SPECTATORS" + "AbilityCastRange" "1250" + "AbilityCooldown" "70" + + // Special + //------------------------------------------------------------------------------------------------------------- + "AbilityValues" + { + "duration" "26" + "incoming_pct" "15" + "outgoing_pct" "15" + + "bonus_damage" "65" + "chain_chance" "25" + "chain_damage" "325" + "chain_strikes" "5" + "chain_radius" "600" + "chain_delay" "0.25" + "chain_cooldown" "0.2" + "chain_damage_self" "145" + "bonus_attack_speed" "165" + } + } + + "item_recipe_dark_crystalys" + { + // General + //------------------------------------------------------------------------------------------------------------- + "BaseClass" "item_datadriven" + "Model" "models/props_gameplay/recipe.vmdl" + "AbilityTextureName" "default_items/core_info" + // Item Info + //------------------------------------------------------------------------------------------------------------- + "ItemCost" "500" + "ItemShopTags" "" + + // Recipe + //------------------------------------------------------------------------------------------------------------- + "ItemRecipe" "1" + "ItemResult" "item_dark_crystalys" + "ItemRequirements" + { + "01" "item_crystalys;item_relic" + } + } + "item_dark_crystalys" + { + // General + //------------------------------------------------------------------------------------------------------------- + "BaseClass" "item_lua" + "ScriptFile" "items/default_items/item_dark_crystalys" + "AbilityTextureName" "item_greater_crit" + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_IMMEDIATE | DOTA_ABILITY_BEHAVIOR_PASSIVE" + + // Item Info + //------------------------------------------------------------------------------------------------------------- + "ItemCost" "4575" + "ItemShopTags" "damage" + "ItemQuality" "rare" + "ItemAliases" "crit" + "ItemDeclarations" "DECLARE_PURCHASES_TO_SPECTATORS" + + // Special + //------------------------------------------------------------------------------------------------------------- + "AbilityValues" + { + "bonus_damage" "125" + "crit_chance" "30" + "crit_mult" "225" + + } + } + "item_wolf_hat" + { + "BaseClass" "item_lua" + "ScriptFile" "items/quest_items/item_wolf_hat" + "AbilityTextureName" "quest_items/wolf_hat" + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_IMMEDIATE | DOTA_ABILITY_BEHAVIOR_NO_TARGET" + + "ItemCost" "0" + "ItemQuality" "rare" + "ItemPurchasable" "0" + "ItemSellable" "0" + "ItemShareability" "ITEM_NOT_SHAREABLE" + "ItemKillable" "1" + "ItemDroppable" "1" + "AbilityCooldown" "28" + "AbilityManaCost" "0" + + "AbilityValues" + { + "bonus_strength" "10" + "bonus_move_speed" "20" + "lifesteal_pct" "15" + "low_hp_threshold" "50" + "low_hp_damage_bonus" "15" + "howl_duration" "8" + "howl_attack_speed" "45" + "howl_move_speed_pct" "18" + } + } + + "item_kunkka_sword" + { + // General + //------------------------------------------------------------------------------------------------------------- + "BaseClass" "item_lua" + "ScriptFile" "items/quest_items/kunkka_sword" + "AbilityTextureName" "default_items/kunkka_sword" + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_IMMEDIATE | DOTA_ABILITY_BEHAVIOR_NO_TARGET" + + // Item Info + //------------------------------------------------------------------------------------------------------------- + "ItemCost" "0" + "ItemShopTags" "damage" + "ItemQuality" "rare" + "ItemAliases" "crit" + "ItemDeclarations" "DECLARE_PURCHASES_TO_SPECTATORS" + "ItemPurchasable" "0" + "AbilityCooldown" "18.0" + "AbilityHealthCost" "65" + "ItemShareability" "ITEM_FULLY_SHAREABLE_STACKING" + "ItemSellable" "0" + // Special + //------------------------------------------------------------------------------------------------------------- + "AbilityValues" + { + "attack_count" "6" + "duration" "10" + "bonus_damage" "32" + "crit_chance" "100" + "crit_mult" "190" + "move_speed_bonus" "15" + + + } + } + "item_oldmen_amulet" + { + // General + //------------------------------------------------------------------------------------------------------------- + "BaseClass" "item_lua" + "ScriptFile" "items/quest_items/oldmen_amulet" + "AbilityTextureName" "default_items/oldmen_amulet" + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_IMMEDIATE | DOTA_ABILITY_BEHAVIOR_UNIT_TARGET" + "AbilityUnitTargetTeam" "DOTA_UNIT_TARGET_TEAM_FRIENDLY" + "AbilityUnitTargetType" "DOTA_UNIT_TARGET_HERO | DOTA_UNIT_TARGET_BASIC" + + // Item Info + //------------------------------------------------------------------------------------------------------------- + "ItemCost" "666" + "ItemShopTags" "mana;spell" + "ItemQuality" "rare" + "ItemAliases" "amulet" + "ItemDeclarations" "DECLARE_PURCHASES_TO_SPECTATORS" + "ItemPurchasable" "0" + "AbilityCooldown" "25.0" + "ItemShareability" "ITEM_FULLY_SHAREABLE_STACKING" + "ItemSellable" "0" + + // Special + //------------------------------------------------------------------------------------------------------------- + "AbilityValues" + { + "spell_amplify_percentage" "15" + "manacost_debuff" "20" + "mana_restore" "150" + } + } + "item_wooden_katana" + { + // General + //------------------------------------------------------------------------------------------------------------- + "BaseClass" "item_lua" + "ScriptFile" "items/quest_items/wooden_katana" + "AbilityTextureName" "default_items/wooden_katana" + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_PASSIVE" + "AbilityUnitTargetTeam" "DOTA_UNIT_TARGET_TEAM_FRIENDLY" + "AbilityUnitTargetType" "DOTA_UNIT_TARGET_HERO | DOTA_UNIT_TARGET_BASIC" + + // Item Info + //------------------------------------------------------------------------------------------------------------- + "ItemCost" "666" + + "ItemQuality" "rare" + "ItemAliases" "amulet" + "ItemDeclarations" "DECLARE_PURCHASES_TO_SPECTATORS" + "ItemPurchasable" "0" + + "ItemShareability" "ITEM_FULLY_SHAREABLE_STACKING" + "ItemSellable" "0" + + // Special + //------------------------------------------------------------------------------------------------------------- + "AbilityValues" + { + "damage_outgoing_percentage" "200" + } + } + "item_pet" + { + // General + //------------------------------------------------------------------------------------------------------------- + "BaseClass" "item_lua" + "ScriptFile" "items/quest_items/item_pet" + "AbilityTextureName" "quest_items/pet" + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_UNIT_TARGET" + "AbilityUnitTargetTeam" "DOTA_UNIT_TARGET_TEAM_ENEMY" + "AbilityUnitTargetType" "DOTA_UNIT_TARGET_HERO | DOTA_UNIT_TARGET_TREE | DOTA_UNIT_TARGET_BASIC" + + // Item Info + //------------------------------------------------------------------------------------------------------------- + "ItemCost" "0" + "ItemShopTags" "mana;spell" + "ItemQuality" "rare" + "ItemAliases" "amulet" + "ItemDeclarations" "DECLARE_PURCHASES_TO_SPECTATORS" + "ItemPurchasable" "0" + "AbilityCooldown" "0" + "AbilityCastRange" "400" + "AbilityCastPoint" "1" + "ItemShareability" "ITEM_FULLY_SHAREABLE_STACKING" + "ItemSellable" "0" + + // Special + //------------------------------------------------------------------------------------------------------------- + "AbilityValues" + { + } + } + "item_recipe_mask_of_madness_custom" + { + "BaseClass" "item_datadriven" + "Model" "models/props_gameplay/recipe.vmdl" + "AbilityTextureName" "default_items/core_info" + + // Item Info + //------------------------------------------------------------------------------------------------------------- + "ItemCost" "0" + "ItemShopTags" "" + + // Recipe + //------------------------------------------------------------------------------------------------------------- + "ItemRecipe" "1" + "ItemResult" "item_mask_of_madness_custom" + "ItemRequirements" + { + "01" "item_lifesteal_custom;item_broadsword" + } + } + "item_mask_of_madness_custom" + { + // General + //------------------------------------------------------------------------------------------------------------- + "BaseClass" "item_lua" + "ScriptFile" "items/default_items/item_mask_of_madness_custom" + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_IMMEDIATE | DOTA_ABILITY_BEHAVIOR_NO_TARGET | DOTA_ABILITY_BEHAVIOR_DONT_RESUME_ATTACK" + "AbilityTextureName" "item_mask_of_madness" + "ItemShopTags" "damage;attack_speed" + "ItemQuality" "epic" + "ItemAliases" "mask;mom;mask of madness;mad" + // Stats + //------------------------------------------------------------------------------------------------------------- + "AbilityCooldown" "14" + "AbilityManaCost" "0" + + // Item Info + //------------------------------------------------------------------------------------------------------------- + "ItemCost" "975" + + "AbilityValues" + { + "bonus_damage" "20" + "bonus_attackspeed_active" "100" + "armor_reduction_active" "-10" + "move_speed_active" "20" + "lifesteal" "14" + "duration" "6" + } + + } + "item_recipe_armlet_of_eternal_hunger" + { + // General + //------------------------------------------------------------------------------------------------------------- + "BaseClass" "item_datadriven" + "Model" "models/props_gameplay/recipe.vmdl" + "AbilityTextureName" "default_items/core_info" + // Item Info + //------------------------------------------------------------------------------------------------------------- + "ItemCost" "425" + "ItemShopTags" "" + + // Recipe + //------------------------------------------------------------------------------------------------------------- + "ItemRecipe" "1" + "ItemResult" "item_armlet_of_eternal_hunger" + "ItemRequirements" + { + "01" "item_mask_of_madness_custom;item_armlet" + } + } + "item_armlet_of_eternal_hunger" + { + "BaseClass" "item_lua" + "ScriptFile" "items/default_items/item_armlet_of_eternal_hunger" + "AbilityTextureName" "default_items/armlet_of_eternal_hunger/armlet2" + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_TOGGLE" + "ItemCost" "3250" + "ItemShopTags" "damage;attack_speed;armor;regen;str" + "ItemQuality" "epic" + "ItemAliases" "armlet; eternal hunger" + "ItemDeclarations" "DECLARE_PURCHASES_TO_SPECTATORS" + "AbilityCooldown" "3.0" + "AbilityManaCost" "0" + "AbilityValues" + { + "bonus_damage" "35" + "bonus_attack_speed" "25" + "bonus_armor" "-4" + "bonus_hp_regen" "5" + "lifesteal" "16" + + "health_per_sec" "100" + + "bonus_damage_active" "95" + "bonus_attack_speed_active" "145" + "movespeed_active" "25" + "bonus_str_active" "25" + "lifesteal_active" "16" + } + } + "item_recipe_vampire_claw" + { + "AbilityTextureName" "default_items/core_info" + // General + //------------------------------------------------------------------------------------------------------------- + "BaseClass" "item_datadriven" + "Model" "models/props_gameplay/recipe.vmdl" + + + // Item Info + //------------------------------------------------------------------------------------------------------------- + "ItemCost" "0" + "ItemShopTags" "" + + // Recipe + //------------------------------------------------------------------------------------------------------------- + "ItemRecipe" "1" + "ItemResult" "item_vampire_claw" + "ItemRequirements" + { + + "01" "item_belt_of_strength;item_magic_wand;item_blades_of_attack" + } + } + + "item_vampire_claw" + { + // General + //------------------------------------------------------------------------------------------------------------- + "BaseClass" "item_lua" + "ScriptFile" "items/default_items/item_vampire_claw" + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_IMMEDIATE | DOTA_ABILITY_BEHAVIOR_NO_TARGET | DOTA_ABILITY_BEHAVIOR_DONT_RESUME_ATTACK" + "AbilityTextureName" "default_items/vampire_claw/vampire_claw_1" + + // Stats + //------------------------------------------------------------------------------------------------------------- + "AbilityCooldown" "14" + + // Item Info + //------------------------------------------------------------------------------------------------------------- + "ItemCost" "720" + "ItemStackable" "0" + + "ItemKillable" "1" + "ItemSellable" "1" + "ItemDroppable" "1" + "ItemPurchasable" "1" + "ItemInitialCharges" "0" + "ItemDisplayCharges" "1" + "ItemRequiresCharges" "1" + + "ItemQuality" "common" + + "AbilityValues" + { + "vampirism" "5" + "bonus_damage" "20" + "bonus_str" "12" + "charges_for_attack_creep" "1" + "max_charges" "30" + "heal_per_charge" "15" + } + + } + "item_recipe_satanic_custom" + { + "BaseClass" "item_datadriven" + "Model" "models/props_gameplay/recipe.vmdl" + "AbilityTextureName" "default_items/core_info" + "ItemCost" "0" + "ItemShopTags" "" + "ItemRecipe" "1" + "ItemResult" "item_satanic_custom" + "ItemRequirements" + { + "01" "item_vampire_claw;item_claymore;item_reaver" + } + } + "item_satanic_custom" + { + "BaseClass" "item_lua" + "ScriptFile" "items/default_items/item_satanic_custom" + "AbilityTextureName" "item_satanic" + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_IMMEDIATE | DOTA_ABILITY_BEHAVIOR_NO_TARGET" + "FightRecapLevel" "2" + "SpellDispellableType" "SPELL_DISPELLABLE_YES" + + "AbilityCooldown" "44" + "AbilityManaCost" "0" + + "ItemCost" "5050" + "ItemShopTags" "damage;str;unique;hard_to_tag;dps" + "ItemQuality" "artifact" + "ItemAliases" "satanic;unholy" + "ItemDeclarations" "DECLARE_PURCHASES_TO_TEAMMATES | DECLARE_PURCHASES_IN_SPEECH | DECLARE_PURCHASES_TO_SPECTATORS" + "SuggestLategame" "1" + "ItemPurchasable" "1" + + "AbilityValues" + { + "bonus_strength" "40" + "bonus_damage" "40" + "lifesteal" "25" + "unholy_lifesteal" "175" + "unholy_duration" "6" + "health_cost" "175" + } + } + "item_recipe_zombie_slayer" + { + // General + //------------------------------------------------------------------------------------------------------------- + "BaseClass" "item_datadriven" + "Model" "models/props_gameplay/recipe.vmdl" + "AbilityTextureName" "default_items/core_info" + // Item Info + //------------------------------------------------------------------------------------------------------------- + "ItemCost" "0" + "ItemShopTags" "" + + // Recipe + //------------------------------------------------------------------------------------------------------------- + "ItemRecipe" "1" + "ItemResult" "item_zombie_slayer" + "ItemRequirements" + { + "01" "item_falcon_blade;item_demon_edge;item_orb_of_corrosion" + } + } + "item_zombie_slayer" + { + "BaseClass" "item_lua" + "ScriptFile" "items/default_items/item_zombie_slayer" + "AbilityTextureName" "default_items/zombie_slayer" + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_PASSIVE" + "ItemCost" "2500" + "ItemShopTags" "damage;agi;debuff" + "ItemQuality" "rare" + "ItemAliases" "zombie slayer" + "ItemDeclarations" "DECLARE_PURCHASES_TO_SPECTATORS" + + "AbilityValues" + { + "bonus_damage" "60" + "bonus_agility" "10" + "bonus_health" "200" + "bonus_mana_regen" "3.0" + + "armor_reduction" "-9" + "health_reduction" "-55" + "movespeed_reduction" "-20" + "debuff_duration" "4" + "incress_damage" "35" + } + } + "item_recipe_demon_slayer" + { + // General + //------------------------------------------------------------------------------------------------------------- + "BaseClass" "item_datadriven" + "Model" "models/props_gameplay/recipe.vmdl" + "AbilityTextureName" "default_items/core_info" + // Item Info + //------------------------------------------------------------------------------------------------------------- + "ItemCost" "0" + "ItemShopTags" "" + + // Recipe + //------------------------------------------------------------------------------------------------------------- + "ItemRecipe" "1" + "ItemResult" "item_demon_slayer" + "ItemRequirements" + { + "01" "item_zombie_slayer;item_mage_slayer;item_wooden_katana" + } + } + "item_demon_slayer" + { + "BaseClass" "item_lua" + "ScriptFile" "items/default_items/item_demon_slayer" + "AbilityTextureName" "default_items/demon_slayer" + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_PASSIVE | DOTA_ABILITY_BEHAVIOR_DONT_PROC_OTHER_ABILITIES" + "AbilityUnitTargetTeam" "DOTA_UNIT_TARGET_TEAM_ENEMY" + "AbilityUnitTargetType" "DOTA_UNIT_TARGET_HERO | DOTA_UNIT_TARGET_BASIC" + "SpellDispellableType" "SPELL_DISPELLABLE_YES" + "AbilityUnitDamageType" "DAMAGE_TYPE_PHYSICAL" + + "ItemCost" "5075" + "ItemShopTags" "damage;agi;debuff;hard_to_tag;dps;magic_resist" + "ItemQuality" "rare" + "SuggestLategame" "1" + "ItemAliases" "demon slayer" + "ItemDeclarations" "DECLARE_PURCHASES_TO_SPECTATORS" + + "AbilityValues" + { + "bonus_damage" "75" + "bonus_agility" "5" + "bonus_health" "200" + "bonus_mana_regen" "5.5" + "bonus_health_regen" "5.5" + "bonus_magic_resistance" "10" + + "armor_reduction" "-9" + "health_reduction" "-55" + "movespeed_reduction" "-20" + "debuff_duration" "4" + "incress_damage" "35" + + "incoming_damage_reduction" "20" + "outgoing_damage_bonus" "200" + + "spell_amp_debuff" "10" + "duration" "3" + "dps" "40" + } + } + "item_recipe_skadi_custom" + { + // General + //------------------------------------------------------------------------------------------------------------- + "BaseClass" "item_datadriven" + "Model" "models/props_gameplay/recipe.vmdl" + "AbilityTextureName" "default_items/core_info" + // Item Info + //------------------------------------------------------------------------------------------------------------- + "ItemCost" "0" + "ItemShopTags" "" + + // Recipe + //------------------------------------------------------------------------------------------------------------- + "ItemRecipe" "1" + "ItemResult" "item_skadi_custom" + "ItemRequirements" + { + "01" "item_ultimate_crown;item_orb_of_corrosion" + } + } + "item_skadi_custom" + { + "BaseClass" "item_lua" + "ScriptFile" "items/default_items/item_skadi_custom" + "AbilityTextureName" "item_skadi" + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_PASSIVE" + "ItemCost" "8850" + "ItemShopTags" "agi;str;int;all;stats;debuff" + "ItemQuality" "rare" + "ItemAliases" "eye of skadi" + "ItemDeclarations" "DECLARE_PURCHASES_TO_SPECTATORS" + + "AbilityValues" + { + "all_stats_pct" "3" + "bonus_all_stats" "45" + "slow_movespeed_melee" "25" + "slow_movespeed_ranged" "50" + "slow_attack_speed" "20" + "health_restoration_reduction" "50" + "heal_reduction" "50" + "debuff_duration" "3" + } + } + "item_recipe_desolator_custom" + { + // General + //------------------------------------------------------------------------------------------------------------- + "BaseClass" "item_datadriven" + "Model" "models/props_gameplay/recipe.vmdl" + "AbilityTextureName" "default_items/core_info" + // Item Info + //------------------------------------------------------------------------------------------------------------- + "ItemCost" "0" + "ItemShopTags" "" + + // Recipe + //------------------------------------------------------------------------------------------------------------- + "ItemRecipe" "1" + "ItemResult" "item_desolator_custom" + "ItemRequirements" + { + "01" "item_mithril_hammer;item_mithril_hammer;item_orb_of_corrosion" + } + } + "item_desolator_custom" + { + "BaseClass" "item_lua" + "ScriptFile" "items/default_items/item_desolator_custom" + "AbilityTextureName" "item_desolator" + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_PASSIVE" + + "ItemCost" "2250" + "ItemShopTags" "damage;hard_to_tag" + "ItemQuality" "epic" + "ItemAliases" "desolator; deso" + "ItemDeclarations" "DECLARE_PURCHASES_TO_SPECTATORS" + "ItemPurchasable" "1" + "ItemStackable" "0" + "ItemInitialCharges" "0" + "ItemDisplayCharges" "1" + + "AbilityValues" + { + "bonus_damage" "40" + "bonus_health" "220" + "corruption_duration" "7.0" + "corruption_armor" "-6" + "corruption_heal_reduction" "-25" + "corruption_movespeed_slow" "-20" + "chance_to_stack" "35" + "max_damage" "90" + "bonus_damage_per_kill" "1" + } + } + "item_recipe_desolator_custom_2" + { + "BaseClass" "item_datadriven" + "Model" "models/props_gameplay/recipe.vmdl" + "AbilityTextureName" "default_items/core_info" + "ItemCost" "0" + "ItemShopTags" "" + "ItemRecipe" "1" + "ItemResult" "item_desolator_custom_2" + "ItemRequirements" + { + "01" "item_desolator_custom;item_desolator_custom" + } + } + "item_desolator_custom_2" + { + "BaseClass" "item_lua" + "ScriptFile" "items/default_items/item_desolator_custom" + "AbilityTextureName" "item_desolator_2" + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_PASSIVE" + + "ItemCost" "4500" + "ItemShopTags" "damage;hard_to_tag" + "ItemQuality" "epic" + "ItemAliases" "desolator 2; desolator ii" + "ItemDeclarations" "DECLARE_PURCHASES_TO_SPECTATORS" + "ItemPurchasable" "1" + "ItemStackable" "0" + "ItemInitialCharges" "0" + "ItemDisplayCharges" "1" + + "AbilityValues" + { + "bonus_damage" "80" + "bonus_health" "440" + "corruption_duration" "7.0" + "corruption_armor_per_stack" "6" + "corruption_armor_cap" "20" + "corruption_heal_reduction" "-35" + "corruption_movespeed_slow" "-20" + "chance_to_stack" "40" + "max_damage" "125" + "bonus_damage_per_kill" "2" + } + } + "item_poor_shield" + { + "BaseClass" "item_lua" + "ScriptFile" "items/default_items/item_poor_shield" + "AbilityTextureName" "item_stout_shield" + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_PASSIVE" + "ItemCost" "200" + "ItemShopTags" "armor;block;cheap" + "ItemQuality" "component" + "ItemAliases" "poor shield;pms;бедный щит" + "ItemDeclarations" "DECLARE_PURCHASES_TO_SPECTATORS" + "ItemPurchasable" "1" + "AbilityValues" + { + "damage_block_chance" "50" + "damage_block" "25" + } + } + "item_recipe_warrior_shield" + { + "BaseClass" "item_datadriven" + // General + //------------------------------------------------------------------------------------------------------------- + "Model" "models/props_gameplay/recipe.vmdl" + "AbilityTextureName" "default_items/core_info" + + // Item Info + //------------------------------------------------------------------------------------------------------------- + "ItemCost" "0" + "ItemShopTags" "" + + // Recipe + //------------------------------------------------------------------------------------------------------------- + "ItemRecipe" "1" + "ItemResult" "item_warrior_shield" + "ItemRequirements" + { + "01" "item_poor_shield;item_vitality_booster;item_ring_of_health" + } + } + "item_warrior_shield" + { + "BaseClass" "item_lua" + "ScriptFile" "items/default_items/item_warrior_shield" + "AbilityTextureName" "default_items/warrior_shield" + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_PASSIVE" + "ItemCost" "1000" + "ItemShopTags" "armor;block;cheap" + "ItemQuality" "component" + "ItemAliases" "poor shield;pms;бедный щит" + "ItemDeclarations" "DECLARE_PURCHASES_TO_SPECTATORS" + "ItemPurchasable" "1" + "AbilityValues" + { + "damage_block_chance" "60" + "bonus_health_regen" "6" + "bonus_health" "150" + "damage_block" "50" + } + } + //================================================================================================================= + // Recipe: Vanguard + //================================================================================================================= + "item_recipe_vanguard" + { + // General + //------------------------------------------------------------------------------------------------------------- + "Model" "models/props_gameplay/recipe.vmdl" + "AbilityTextureName" "default_items/core_info" + + // Item Info + //------------------------------------------------------------------------------------------------------------- + "ItemCost" "250" + "ItemShopTags" "" + + // Recipe + //------------------------------------------------------------------------------------------------------------- + "ItemRecipe" "1" + "ItemResult" "item_vanguard" + "ItemRequirements" + { + "01" "item_warrior_shield;" + } + } + + //================================================================================================================= + // Vanguard + //================================================================================================================= + "item_vanguard" + { + // General + //------------------------------------------------------------------------------------------------------------- + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_PASSIVE" + + // Item Info + //------------------------------------------------------------------------------------------------------------- + "ItemCost" "1250" + "ItemShopTags" "regen_health;block;health_pool;tank" + "ItemQuality" "epic" + "ItemDeclarations" "DECLARE_PURCHASES_TO_TEAMMATES | DECLARE_PURCHASES_IN_SPEECH | DECLARE_PURCHASES_TO_SPECTATORS" + "SuggestLategame" "1" + "ItemDisassembleRule" "DOTA_ITEM_DISASSEMBLE_ALWAYS" + + // Special + //------------------------------------------------------------------------------------------------------------- + "AbilityValues" + { + "bonus_health" "250" + "bonus_health_regen" "12" + "block_damage_melee" "120" + "block_damage_ranged" "60" + "block_chance" "60" + } + } + //================================================================================================================= + // Recipe: Crimson Guard + //================================================================================================================= + "item_recipe_crimson_guard" + { + // General + //------------------------------------------------------------------------------------------------------------- + "Model" "models/props_gameplay/recipe.vmdl" + "AbilityTextureName" "default_items/core_info" + + // Item Info + //------------------------------------------------------------------------------------------------------------- + "ItemCost" "1050" + "ItemShopTags" "" + + + + // Recipe + //------------------------------------------------------------------------------------------------------------- + "ItemRecipe" "1" + "ItemResult" "item_crimson_guard" + "ItemRequirements" + { + "01" "item_vanguard;item_helm_of_iron_will" + } + } + + //================================================================================================================= + // Crimson Guard + //================================================================================================================= + "item_crimson_guard" + { + // General + //------------------------------------------------------------------------------------------------------------- + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_IMMEDIATE | DOTA_ABILITY_BEHAVIOR_NO_TARGET" + "SpellDispellableType" "SPELL_DISPELLABLE_NO" + + // Stats + //------------------------------------------------------------------------------------------------------------- + "AbilityCooldown" "25.0" + "AbilityCastRange" "1200" + "AbilityManaCost" "120" + + + // Item Info + //------------------------------------------------------------------------------------------------------------- + "ItemCost" "2750" + "ItemShopTags" "armor;boost_armor;regen_health;block;health_pool;teamfight;tank" + "ItemQuality" "epic" + "ItemAlertable" "1" + "ItemDeclarations" "DECLARE_PURCHASES_TO_TEAMMATES | DECLARE_PURCHASES_IN_SPEECH | DECLARE_PURCHASES_TO_SPECTATORS" + "SuggestLategame" "1" + + // Special + //------------------------------------------------------------------------------------------------------------- + "AbilityValues" + { + "bonus_health" "550" + "bonus_health_regen" "20" + "bonus_armor" "18" + "block_damage_melee" "165" + "block_damage_ranged" "80" + "block_chance" "66" + "duration" "12" + "bonus_aoe_radius" + { + "value" "1200" + "affected_by_aoe_increase" "1" + } + "block_damage_active" "300" + "block_chance_active" "100" + "block_penalty_ranged" "25" + "max_hp_pct" "2.0" + } + } + + "item_orb_of_fire" + { + // General + //------------------------------------------------------------------------------------------------------------- + "BaseClass" "item_lua" + "ScriptFile" "items/default_items/item_orb_of_fire" + "AbilityTextureName" "default_items/orb_of_fire" + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_PASSIVE" + "AbilityUnitDamageType" "DAMAGE_TYPE_MAGICAL" + // Item Info + //------------------------------------------------------------------------------------------------------------- + "ItemCost" "300" + "ItemShopTags" "intelligence;spell_amp;damage" + "ItemQuality" "component" + "ItemAliases" "orb of fire; fire orb" + "ItemDeclarations" "DECLARE_PURCHASES_TO_SPECTATORS" + "ItemPurchasable" "1" + + // Special + //------------------------------------------------------------------------------------------------------------- + "AbilityValues" + { + "radius" "275" + "damage" "15" + "fire_stack" "2" + } + } + "item_recipe_fire_cape" + { + // General + //------------------------------------------------------------------------------------------------------------- + "BaseClass" "item_datadriven" + "Model" "models/props_gameplay/recipe.vmdl" + "AbilityTextureName" "default_items/core_info" + // Item Info + //------------------------------------------------------------------------------------------------------------- + "ItemCost" "350" + "ItemShopTags" "" + + // Recipe + //------------------------------------------------------------------------------------------------------------- + "ItemRecipe" "1" + "ItemResult" "item_fire_cape" + "ItemRequirements" + { + "01" "item_mage_slayer;item_platemail;item_orb_of_fire" + } + } + "item_fire_cape" + { + // General + //------------------------------------------------------------------------------------------------------------- + "BaseClass" "item_lua" + "ScriptFile" "items/default_items/item_fire_cape" + "AbilityTextureName" "default_items/fire_cape" + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_PASSIVE" + "AbilityUnitDamageType" "DAMAGE_TYPE_MAGICAL" + + // Item Info + //------------------------------------------------------------------------------------------------------------- + "ItemShopTags" "armor;defense;damage" + "ItemQuality" "rare" + "ItemAliases" "cape; fire cape" + "ItemDeclarations" "DECLARE_PURCHASES_TO_SPECTATORS" + "ItemPurchasable" "1" + + // Special + //------------------------------------------------------------------------------------------------------------- + "AbilityValues" + { + "radius" "325" + "damage" "45" + + "bonus_magic_resistance" "10" + "bonus_spell_amp" "15" + "mana_damage_pct" "10" + "bonus_damage" "35" + "bonus_attack_speed" "40" + "bonus_physical_armor" "10" + "bonus_health_regen" "6" + "bonus_mana_regen" "3" + + "fire_stack" "2" + } + } + "item_recipe_radiance_custom" + { + "BaseClass" "item_datadriven" + "Model" "models/props_gameplay/recipe.vmdl" + "AbilityTextureName" "default_items/core_info" + "ItemCost" "0" + "ItemShopTags" "" + "ItemRecipe" "1" + "ItemResult" "item_radiance_custom" + "ItemRequirements" + { + "01" "item_relic;item_orb_of_fire;item_talisman_of_evasion" + } + } + + "item_radiance_custom" + { + "BaseClass" "item_lua" + "ScriptFile" "items/default_items/item_radiance_custom" + "AbilityTextureName" "item_radiance" + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_PASSIVE" + "AbilityUnitDamageType" "DAMAGE_TYPE_MAGICAL" + + "ItemCost" "4700" + "ItemShopTags" "damage;farming;dps" + "ItemQuality" "epic" + "ItemAliases" "radiance" + "ItemDeclarations" "DECLARE_PURCHASES_TO_TEAMMATES | DECLARE_PURCHASES_IN_SPEECH | DECLARE_PURCHASES_TO_SPECTATORS" + "SuggestLategame" "1" + "ItemDisassembleRule" "DOTA_ITEM_DISASSEMBLE_ALWAYS" + "ItemPurchasable" "1" + + "AbilityValues" + { + "bonus_damage" "70" + "evasion" "12" + "radius" + { + "value" "650" + "affected_by_aoe_increase" "1" + } + "damage" "60" + "health_damage_pct" "1.5" + "health_regen_damage" "0.1" + "tick_interval" "1" + "fire_stack" "3" + } + } + + "item_recipe_blazing_radiance_custom" + { + "BaseClass" "item_datadriven" + "Model" "models/props_gameplay/recipe.vmdl" + "AbilityTextureName" "default_items/core_info" + "ItemCost" "0" + "ItemShopTags" "" + "ItemRecipe" "1" + "ItemResult" "item_blazing_radiance_custom" + "ItemRequirements" + { + "01" "item_fire_cape;item_radiance_custom;item_ultimate_crown" + } + } + + "item_blazing_radiance_custom" + { + "BaseClass" "item_lua" + "ScriptFile" "items/default_items/item_blazing_radiance_custom" + "AbilityTextureName" "default_items/blazing_radiance" + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_PASSIVE" + "AbilityUnitDamageType" "DAMAGE_TYPE_MAGICAL" + + "ItemCost" "5200" + "ItemShopTags" "damage;armor;farming;dps" + "ItemQuality" "epic" + "ItemAliases" "blazing radiance; infernal shroud" + "ItemDeclarations" "DECLARE_PURCHASES_TO_TEAMMATES | DECLARE_PURCHASES_IN_SPEECH | DECLARE_PURCHASES_TO_SPECTATORS" + "SuggestLategame" "1" + "ItemDisassembleRule" "DOTA_ITEM_DISASSEMBLE_ALWAYS" + "ItemPurchasable" "1" + + "AbilityValues" + { + "bonus_damage" "85" + "evasion" "12" + "bonus_attack_speed" "40" + "bonus_physical_armor" "10" + "bonus_health_regen" "6" + "bonus_mana_regen" "3" + "bonus_magic_resistance" "10" + "aura_magic_resistance_reduction" "10" + "aura_spell_amp_reduction" "15" + "radius" + { + "value" "650" + "affected_by_aoe_increase" "1" + } + "damage" "50" + "health_damage_pct" "1.5" + "health_regen_damage" "0.1" + "mana_damage_pct" "8" + "tick_interval" "1" + "fire_stack" "4" + "bonus_all_stats" "45" + "all_stats_pct" "3" + } + } + + "item_recipe_ultimate_crown" + { + "BaseClass" "item_datadriven" + "Model" "models/props_gameplay/recipe.vmdl" + "AbilityTextureName" "default_items/core_info" + "ItemCost" "0" + "ItemShopTags" "" + "ItemRecipe" "1" + "ItemResult" "item_ultimate_crown" + "ItemRequirements" + { + "01" "item_ultimate_orb;item_ultimate_orb;item_ultimate_orb" + } + } + + "item_ultimate_crown" + { + "BaseClass" "item_lua" + "ScriptFile" "items/default_items/item_ultimate_crown" + "AbilityTextureName" "default_items/ultimate_crown" + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_PASSIVE" + + "ItemCost" "8400" + "ItemShopTags" "agi;int;str" + "ItemQuality" "secret_shop" + "ItemAliases" "ultimate crown; crown" + "ItemDeclarations" "DECLARE_PURCHASES_TO_SPECTATORS" + "ItemPurchasable" "1" + "ItemDisassembleRule" "DOTA_ITEM_DISASSEMBLE_ALWAYS" + + "AbilityValues" + { + "bonus_all_stats" "45" + "all_stats_pct" "3" + } + } + + "item_recipe_blademail_2" + { + "BaseClass" "item_datadriven" + // General + //------------------------------------------------------------------------------------------------------------- + "Model" "models/props_gameplay/recipe.vmdl" + "AbilityTextureName" "default_items/core_info" + + // Item Info + //------------------------------------------------------------------------------------------------------------- + "ItemCost" "0" + "ItemShopTags" "" + + // Recipe + //------------------------------------------------------------------------------------------------------------- + "ItemRecipe" "1" + "ItemResult" "item_blademail_2" + "ItemRequirements" + { + "01" "item_assault;item_blade_mail;item_vampire_claw" + } + } + "item_blademail_2" + { + "BaseClass" "item_lua" + "ScriptFile" "items/default_items/item_blademail_2" + "AbilityTextureName" "default_items/blademail_2" + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_NO_TARGET | DOTA_ABILITY_BEHAVIOR_IMMEDIATE | DOTA_ABILITY_BEHAVIOR_DONT_RESUME_ATTACK" + "AbilityCooldown" "22" + "AbilityManaCost" "25" + + "ItemCost" "7275" + "ItemShopTags" "armor;block;damage_return" + "ItemQuality" "epic" + "ItemAliases" "blademail 2; woven blades; отражение" + "ItemDeclarations" "DECLARE_PURCHASES_TO_SPECTATORS" + "ItemPurchasable" "1" + + "AbilityValues" + { + "reflect_pct" "20" + "active_reflect_pct" "100" + "active_duration" "5.5" + "radius" "400" + "bonus_armor" "15" + "attack_speed" "40" + "aura_armor" "30" + "aura_attack_speed" "25" + } + } + //================================================================================================================= + // Recipe: Assault Cuirass + //================================================================================================================= + "item_recipe_assault" + { + // General + //------------------------------------------------------------------------------------------------------------- + "Model" "models/props_gameplay/recipe.vmdl" + "AbilityTextureName" "default_items/core_info" + + // Item Info + //------------------------------------------------------------------------------------------------------------- + "ItemCost" "1300" + "ItemShopTags" "" + + // Recipe + //------------------------------------------------------------------------------------------------------------- + "ItemRecipe" "1" + "ItemResult" "item_assault" + "ItemRequirements" + { + "01" "item_buckler;item_hyperstone;item_platemail" + } + } + + //================================================================================================================= + // Assault Cuirass + //================================================================================================================= + "item_assault" + { + // General + //------------------------------------------------------------------------------------------------------------- + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_PASSIVE" + "AbilityCastRange" "1200" + "SpellImmunityType" "SPELL_IMMUNITY_ENEMIES_YES" + + // Item Info + //------------------------------------------------------------------------------------------------------------- + "ItemCost" "4125" + "ItemShopTags" "attack_speed;armor;hard_to_tag;teamfight;tank" + "ItemQuality" "epic" + "ItemDeclarations" "DECLARE_PURCHASES_TO_TEAMMATES | DECLARE_PURCHASES_IN_SPEECH | DECLARE_PURCHASES_TO_SPECTATORS" + "SuggestLategame" "1" + + // Special + //------------------------------------------------------------------------------------------------------------- + "AbilityValues" + { + "bonus_attack_speed" "30" + "bonus_armor" "20" + "aura_radius" + { + "value" "1200" + "affected_by_aoe_increase" "1" + } + "aura_attack_speed" "30" + "aura_positive_armor" "10" + "aura_negative_armor" "-10" + } + } + + "item_recipe_crimson_shivas_custom" + { + "BaseClass" "item_datadriven" + "Model" "models/props_gameplay/recipe.vmdl" + "AbilityTextureName" "default_items/core_info" + "ItemCost" "1050" + "ItemShopTags" "" + "ItemRecipe" "1" + "ItemResult" "item_crimson_shivas_custom" + "ItemRequirements" + { + "01" "item_crimson_guard;item_shivas_guard;item_ultimate_crown" + } + } + + "item_crimson_shivas_custom" + { + "BaseClass" "item_lua" + "ScriptFile" "items/default_items/item_crimson_shivas_custom" + "AbilityTextureName" "default_items/graded_shiva" + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_IMMEDIATE | DOTA_ABILITY_BEHAVIOR_NO_TARGET | DOTA_ABILITY_BEHAVIOR_AOE" + "AbilityUnitDamageType" "DAMAGE_TYPE_MAGICAL" + "AbilityCooldown" "25" + "AbilityManaCost" "120" + "AbilityCastRange" "1200" + + "ItemCost" "5800" + "ItemShopTags" "armor;regen_health;regen_mana;hard_to_tag;teamfight;tank;int;str;agi" + "ItemQuality" "epic" + "ItemAlertable" "1" + "ItemDeclarations" "DECLARE_PURCHASES_TO_TEAMMATES | DECLARE_PURCHASES_IN_SPEECH | DECLARE_PURCHASES_TO_SPECTATORS" + "SuggestLategame" "1" + "ItemPurchasable" "1" + + "AbilityValues" + { + "bonus_stats" "60" + "bonus_health" "500" + "bonus_mana" "200" + "bonus_armor" "40" + "bonus_hp_regen" "12" + "bonus_mana_regen" "4" + "damage_block" "140" + "damage_block_chance" "60" + + "aura_radius" + { + "value" "900" + "affected_by_aoe_increase" "1" + } + "aura_as_reduction" "40" + "aura_enemy_incoming_amp" "20" + "aura_ally_incoming_reduction" "20" + + "blast_damage" "175" + "blast_radius" + { + "value" "900" + "affected_by_aoe_increase" "1" + } + "blast_speed" "350" + "slow_duration" "4" + "slow_move_pct" "-40" + "active_as_reduction" "-45" + + "guard_radius" + { + "value" "1200" + "affected_by_aoe_increase" "1" + } + "guard_duration" "12" + "bonus_armor_active" "15" + "bonus_attack_speed_active" "20" + "bonus_movespeed_active" "12" + "damage_block_active" "200" + "block_strength_pct" "50" + + "all_stats_pct" "3" + } + } + + "item_ice_spine" + { + "BaseClass" "item_lua" + "ScriptFile" "items/default_items/item_ice_spine" + "AbilityTextureName" "default_items/ice_spine" + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_PASSIVE" + "ItemCost" "500" + "ItemShopTags" "spell;amp" + "ItemQuality" "magic" + "ItemAliases" "magic; amplify" + "ItemDeclarations" "DECLARE_PURCHASES_TO_SPECTATORS" + "AbilityValues" + { + "spell_amplify" "7" + "heal_amplify" "15" + "movespeed_const" "20" + "incoming_dmg_pct" "12" + } + } +} + \ No newline at end of file diff --git a/scripts/npc/items/quest_items.kv b/scripts/npc/items/quest_items.kv new file mode 100644 index 0000000..a866e18 --- /dev/null +++ b/scripts/npc/items/quest_items.kv @@ -0,0 +1,449 @@ +"DOTAAbilities" +{ + "item_rom" + { + "BaseClass" "item_datadriven" + "AbilityTextureName" "quest_items/rom" + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_PASSIVE" + "AbilityCastAnimation" "ACT_DOTA_GENERIC_CHANNEL_1" + "Model" "models/props_gameplay/neutral_box.vmdl" + "ItemIsNeutralDrop" "0" + + "ItemCost" "0" + "ItemDroppable" "1" + "ItemPurchasable" "0" + "ItemSellable" "0" + "ItemKillable" "0" + "ItemInitialCharges" "0" + "ItemStackable" "0" + "ItemPermanent" "0" + "SideShop" "0" + + "ItemShareability" "ITEM_FULLY_SHAREABLE_STACKING" + + "AbilityValues" + { + + } + + } + "item_ent_heart" + { + "BaseClass" "item_datadriven" + "AbilityTextureName" "quest_items/ent_heart" + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_PASSIVE" + "Model" "models/props_gameplay/neutral_box.vmdl" + "ItemIsNeutralDrop" "0" + + "ItemCost" "40" + "ItemDroppable" "1" + "ItemPurchasable" "0" + "ItemSellable" "1" + "ItemKillable" "1" + "ItemInitialCharges" "1" + "ItemStackable" "1" + "ItemPermanent" "0" + "SideShop" "0" + + "ItemShareability" "ITEM_FULLY_SHAREABLE_STACKING" + + "AbilityValues" + { + + } + + } + "item_wolf_claw" + { + "BaseClass" "item_datadriven" + "AbilityTextureName" "quest_items/wolf_claw" + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_PASSIVE" + "Model" "models/props_gameplay/neutral_box.vmdl" + "ItemIsNeutralDrop" "0" + + "ItemCost" "50" + "ItemDroppable" "1" + "ItemPurchasable" "0" + "ItemSellable" "1" + "ItemKillable" "1" + "ItemInitialCharges" "1" + "ItemStackable" "1" + "ItemPermanent" "0" + "SideShop" "0" + + "ItemShareability" "ITEM_FULLY_SHAREABLE_STACKING" + + "AbilityValues" + { + + } + + } + + "item_testo" + { + "BaseClass" "item_datadriven" + "AbilityTextureName" "utils/testo" + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_PASSIVE" + "Model" "models/props_gameplay/neutral_box.vmdl" + "ItemIsNeutralDrop" "0" + + "ItemCost" "120" + "ItemDroppable" "1" + "ItemPurchasable" "0" + "ItemSellable" "1" + "ItemKillable" "1" + "ItemInitialCharges" "1" + "ItemStackable" "1" + "ItemPermanent" "0" + "SideShop" "0" + + "ItemShareability" "ITEM_FULLY_SHAREABLE_STACKING" + + "AbilityValues" + { + + } + + } + "item_egg" + { + "BaseClass" "item_datadriven" + "AbilityTextureName" "utils/egg" + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_PASSIVE" + "Model" "models/props_gameplay/moon_shard/moon_shard_003.vmdl" + "ModelScale" "10" + "ItemIsNeutralDrop" "0" + + "ItemCost" "120" + "ItemDroppable" "1" + "ItemPurchasable" "0" + "ItemSellable" "1" + "ItemKillable" "1" + "ItemInitialCharges" "1" + "ItemStackable" "1" + "ItemPermanent" "0" + "SideShop" "0" + + "ItemShareability" "ITEM_FULLY_SHAREABLE_STACKING" + + "AbilityValues" + { + + } + + } + "item_testo_pizza" + { + "BaseClass" "item_datadriven" + "AbilityTextureName" "utils/testo_pizza" + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_PASSIVE" + "Model" "models/props_gameplay/neutral_box.vmdl" + + "ItemIsNeutralDrop" "0" + + "ItemCost" "120" + "ItemDroppable" "1" + "ItemPurchasable" "0" + "ItemSellable" "0" + "ItemKillable" "1" + "ItemInitialCharges" "1" + "ItemStackable" "1" + "ItemPermanent" "0" + "SideShop" "0" + + "ItemShareability" "ITEM_FULLY_SHAREABLE_STACKING" + + "AbilityValues" + { + + } + + } + "item_spider_legs_custom" + { + "BaseClass" "item_datadriven" + "AbilityTextureName" "quest_items/spider_legs" + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_PASSIVE" + "Model" "models/props_gameplay/neutral_box.vmdl" + + "ItemIsNeutralDrop" "0" + + "ItemCost" "120" + "ItemDroppable" "1" + "ItemPurchasable" "0" + "ItemSellable" "0" + "ItemKillable" "1" + "ItemInitialCharges" "1" + "ItemStackable" "1" + "ItemPermanent" "0" + "SideShop" "0" + + "ItemShareability" "ITEM_FULLY_SHAREABLE_STACKING" + + "AbilityValues" + { + + } + + } + "item_poison" + { + "BaseClass" "item_datadriven" + "AbilityTextureName" "quest_items/poison" + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_PASSIVE" + "Model" "models/props_gameplay/neutral_box.vmdl" + + "ItemIsNeutralDrop" "0" + + "ItemCost" "120" + "ItemDroppable" "1" + "ItemPurchasable" "0" + "ItemSellable" "0" + "ItemKillable" "1" + "ItemInitialCharges" "1" + "ItemStackable" "1" + "ItemPermanent" "0" + "SideShop" "0" + + "ItemShareability" "ITEM_FULLY_SHAREABLE_STACKING" + + "AbilityValues" + { + + } + + } + "item_frog_paw" + { + "BaseClass" "item_datadriven" + "AbilityTextureName" "quest_items/frog_paw" + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_PASSIVE" + "Model" "models/props_gameplay/neutral_box.vmdl" + + "ItemIsNeutralDrop" "0" + + "ItemCost" "120" + "ItemDroppable" "1" + "ItemPurchasable" "0" + "ItemSellable" "0" + "ItemKillable" "1" + "ItemInitialCharges" "1" + "ItemStackable" "1" + "ItemPermanent" "0" + "SideShop" "0" + + "ItemShareability" "ITEM_FULLY_SHAREABLE_STACKING" + + "AbilityValues" + { + + } + + } + "item_pollen_keeper" + { + "BaseClass" "item_lua" + "AbilityTextureName" "quest_items/pollen_keeper_1" + "ScriptFile" "items/quest_items/item_pollen_keeper" + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_PASSIVE" + "Model" "models/props_gameplay/neutral_box.vmdl" + + "ItemIsNeutralDrop" "0" + + "ItemCost" "120" + "ItemDroppable" "1" + "ItemPurchasable" "0" + "ItemSellable" "0" + "ItemKillable" "1" + "ItemInitialCharges" "1" + "ItemStackable" "1" + "ItemPermanent" "0" + "SideShop" "0" + + "ItemShareability" "ITEM_FULLY_SHAREABLE_STACKING" + + "AbilityValues" + { + "chance" "15" + } + + } + + "item_mayonnaise" + { + "BaseClass" "item_datadriven" + "AbilityTextureName" "utils/mayonnaise" + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_PASSIVE" + "Model" "models/props_gameplay/neutral_box.vmdl" + + "ItemIsNeutralDrop" "0" + + "ItemCost" "1200" + "ItemDroppable" "1" + "ItemPurchasable" "0" + "ItemSellable" "0" + "ItemKillable" "1" + "ItemInitialCharges" "1" + "ItemStackable" "1" + "ItemPermanent" "0" + "SideShop" "0" + + "ItemShareability" "ITEM_FULLY_SHAREABLE_STACKING" + + "AbilityValues" + { + + } + + } + + "item_lycan_horn" + { + "BaseClass" "item_datadriven" + "AbilityTextureName" "quest_items/lycan_horn" + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_PASSIVE" + "Model" "models/props_gameplay/neutral_box.vmdl" + "ItemIsNeutralDrop" "0" + + "ItemCost" "0" + "ItemDroppable" "1" + "ItemPurchasable" "0" + "ItemSellable" "0" + "ItemKillable" "1" + "ItemInitialCharges" "0" + "ItemStackable" "0" + "ItemPermanent" "0" + "SideShop" "0" + + "ItemShareability" "ITEM_FULLY_SHAREABLE_STACKING" + + "AbilityValues" + { + + } + + } + + "item_clean_harbor" + { + "BaseClass" "item_lua" + "ScriptFile" "items/default_items/item_clean_harbor" + "AbilityTextureName" "item_roshans_banner" + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_NO_TARGET | DOTA_ABILITY_BEHAVIOR_IMMEDIATE" + "Model" "models/props_gameplay/neutral_box.vmdl" + "ItemIsNeutralDrop" "0" + + "ItemCost" "0" + "ItemDroppable" "1" + "ItemPurchasable" "0" + "ItemSellable" "0" + "ItemKillable" "1" + "ItemInitialCharges" "1" + "ItemStackable" "0" + "ItemPermanent" "0" + "SideShop" "0" + + "ItemShareability" "ITEM_FULLY_SHAREABLE_STACKING" + + "AbilityValues" + { + } + } + + "item_shameful_pipe" + { + "BaseClass" "item_lua" + "ScriptFile" "items/quest_items/item_shameful_pipe" + "AbilityTextureName" "blackshop/astral_anchor" + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_PASSIVE" + "Model" "models/props_gameplay/neutral_box.vmdl" + "ItemCost" "0" + "ItemPurchasable" "0" + "ItemSellable" "0" + "ItemKillable" "0" + "ItemDroppable" "1" + "ItemShareability" "ITEM_FULLY_SHAREABLE_STACKING" + "ItemPermanent" "1" + "AbilityValues" + { + "bonus_attack_range" "450" + } + } + + "item_shovel" + { + "BaseClass" "item_lua" + "ScriptFile" "items/quest_items/item_shovel" + "AbilityTextureName" "item_trusty_shovel" + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_NO_TARGET | DOTA_ABILITY_BEHAVIOR_CHANNELLED" + "AbilityChannelTime" "1" + "AbilityCastAnimation" "ACT_DOTA_GENERIC_CHANNEL_1" + "Model" "models/props_gameplay/neutral_box.vmdl" + "AbilityCooldown" "12" + "ItemCost" "0" + "ItemPurchasable" "0" + "ItemSellable" "0" + "ItemKillable" "0" + "ItemShareability" "ITEM_FULLY_SHAREABLE_STACKING" + "ItemPermanent" "0" + "ItemIsNeutralDrop" "1" + "ItemInitialCharges" "0" + "ItemDisplayCharges" "1" + "AbilityValues" + { + "gold_min" "145" + "gold_max" "325" + "bonus_health" "675" + "max_digs" "5" + } + } + + "item_fishing_rod" + { + "BaseClass" "item_lua" + "ScriptFile" "items/default_items/item_fishing_rod" + "AbilityTextureName" "quest_items/fishing_rod" + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_POINT | DOTA_ABILITY_BEHAVIOR_DIRECTIONAL | DOTA_ABILITY_BEHAVIOR_ROOT_DISABLES | DOTA_ABILITY_BEHAVIOR_OVERSHOOT" + "AbilityUnitDamageType" "DAMAGE_TYPE_PHYSICAL" + "Model" "models/props_gameplay/neutral_box.vmdl" + "ItemIsNeutralDrop" "0" + + "AbilityCastRange" "600" + "AbilityCastPoint" "0.3" + "AbilityCooldown" "2.5" + "AbilityManaCost" "25" + + "ItemCost" "0" + "ItemDroppable" "1" + "ItemPurchasable" "0" + "ItemSellable" "0" + "ItemKillable" "0" + "ItemInitialCharges" "0" + "ItemStackable" "0" + "ItemPermanent" "0" + "SideShop" "0" + + "ItemShareability" "ITEM_FULLY_SHAREABLE_STACKING" + + "AbilityValues" + { + "hook_distance" "952" + "hook_speed" "1600" + "hook_width" "100" + "Damage" "75" + "duration" "3" + "slow_movespeed" "-20" + "damage_per_tick" "15" + "tick" "0.5" + "vision_radius" "500" + "vision_duration" "4" + + "fish_catch_item" "item_fish" + "fish_loot_launch_height" "100" + "fish_loot_launch_distance" "150" + "fish_loot_launch_duration" "0.5" + } + } + +} \ No newline at end of file diff --git a/scripts/npc/items/removed_items.kv b/scripts/npc/items/removed_items.kv new file mode 100644 index 0000000..f73da01 --- /dev/null +++ b/scripts/npc/items/removed_items.kv @@ -0,0 +1,101 @@ +"DOTAAbilities" +{ + "item_radiance" "REMOVE" + "item_lifesteal" "REMOVE" + "item_satanic" "REMOVE" + "item_recipe_satanic" "REMOVE" + "item_mask_of_madness" "REMOVE" + "item_voodoo_mask" "REMOVE" + "item_cyclone" "REMOVE" + "item_basher" "REMOVE" + "item_glimmer_cape" "REMOVE" + "item_travel_boots_2" "REMOVE" + "item_dagon" "REMOVE" + "item_dagon_2" "REMOVE" + "item_dagon_3" "REMOVE" + "item_dagon_4" "REMOVE" + "item_dagon_5" "REMOVE" + "item_revenants_brooch" "REMOVE" + "item_helm_of_the_overlord" "REMOVE" + "item_silver_edge" "REMOVE" + "item_invis_sword" "REMOVE" + "item_shadow_amulet" "REMOVE" + "item_greater_crit" "REMOVE" + "item_lesser_crit" "REMOVE" + "item_dragon_scale" "REMOVE" + "item_pupils_gift" "REMOVE" + "item_grove_bow" "REMOVE" + "item_bullwhip" "REMOVE" + "item_skadi" "REMOVE" + + "item_eye_of_the_vizier" "REMOVE" + "item_vampire_fangs" "REMOVE" + "item_light_collector" "REMOVE" + "item_vambrace" "REMOVE" + "item_paladin_sword" "REMOVE" + "item_vindicators_axe" "REMOVE" + "item_craggy_coat" "REMOVE" + "item_enchanted_quiver" "REMOVE" + "item_elven_tunic" "REMOVE" + "item_doubloon" "REMOVE" + "item_timeless_relic" "REMOVE" + "item_ascetic_cap" "REMOVE" + "item_avianas_feather" "REMOVE" + "item_spy_gadget" "REMOVE" + "item_trickster_cloak" "REMOVE" + "item_ancient_guardian" "REMOVE" + "item_havoc_hammer" "REMOVE" + "item_force_boots" "REMOVE" + "item_seer_stone" "REMOVE" + "item_mirror_shield" "REMOVE" + "item_apex" "REMOVE" + "item_force_field" "REMOVE" + "item_giants_ring" "REMOVE" + "item_unwavering_condition" "REMOVE" + "item_book_of_shadows" "REMOVE" + //"item_keen_optic" "REMOVE" + "item_arcane_ring" "REMOVE" + //"item_possessed_mask" "REMOVE" + //"item_mysterious_hat" "REMOVE" + "item_safety_bubble" "REMOVE" + "item_lance_of_pursuit" "REMOVE" + //"item_ocean_heart" "REMOVE" + "item_broom_handle" "REMOVE" + //"item_chipped_vest" "REMOVE" + "item_royal_jelly" "REMOVE" + "item_faded_broach" "REMOVE" + "item_ironwood_tree" "REMOVE" + //"item_smoke_of_deceit" "REMOVE" + "item_faerie_fire" "REMOVE" + "item_enchanted_mango" "REMOVE" + "item_helm_of_the_dominator" "REMOVE" + "item_helm_of_the_dominator_2" "REMOVE" + "item_dust" "REMOVE" + "item_travel_boots" "REMOVE" + "item_vladmir" "REMOVE" + "item_solar_crest" "REMOVE" + "item_bfury" "REMOVE" + "item_guardian_greaves" "REMOVE" + "item_recipe_abyssal_blade" "REMOVE" + "item_abyssal_blade" "REMOVE" + "item_recipe_mjollnir" "REMOVE" + "item_mjollnir" "REMOVE" + "item_wind_waker" "REMOVE" + "item_hand_of_midas" "REMOVE" + "item_rapier" "REMOVE" + "item_urn_of_shadows" "REMOVE" + "item_arcane_blink" "REMOVE" + "item_swift_blink" "REMOVE" + "item_overwhelming_blink" "REMOVE" + "item_voodoo_mask" "REMOVE" + "item_bloodstone" "REMOVE" + "item_ethereal_blade" "REMOVE" + "item_recipe_ethereal_blade" "REMOVE" + + "item_desolator" "REMOVE" + "item_orb_of_corruption" "REMOVE" + "item_recipe_orb_of_corruption" "REMOVE" + "item_orb_of_corruption_2" "REMOVE" + "item_recipe_orb_of_corruption_2" "REMOVE" + "item_blood_grenade" "REMOVE" +} \ No newline at end of file diff --git a/scripts/npc/items/util_items.kv b/scripts/npc/items/util_items.kv new file mode 100644 index 0000000..96c2805 --- /dev/null +++ b/scripts/npc/items/util_items.kv @@ -0,0 +1,514 @@ +"DOTAAbilities" +{ + "item_meat" + { + "BaseClass" "item_lua" + "ScriptFile" "items/util_items/item_meat.lua" + "AbilityCastAnimation" "ACT_DOTA_ATTACK" + "Model" "models/gameplay/meat/meat.vmdl" + "AbilityTextureName" "utils/meat" + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_UNIT_TARGET | DOTA_ABILITY_BEHAVIOR_IMMEDIATE" + "AbilityUnitTargetTeam" "DOTA_UNIT_TARGET_TEAM_FRIENDLY | DOTA_UNIT_TARGET_TEAM_ENEMY" + "AbilityUnitTargetType" "DOTA_UNIT_TARGET_HERO" + "SpellImmunityType" "SPELL_IMMUNITY_ENEMIES_YES" + "AbilityCastRange" "200" + "ItemShareability" "ITEM_FULLY_SHAREABLE_STACKING" + "ItemQuality" "artifact" + "ItemCost" "0" + "ItemDroppable" "1" + "ItemPurchasable" "0" + "ItemSellable" "1" + "ItemKillable" "1" + "ItemInitialCharges" "1" + "ItemStackable" "1" + "ItemPermanent" "0" + "SideShop" "0" + + "AbilityValues" + { + "heal" "150" + "hunger_bonus" "5" + } + } + "item_fish" + { + "BaseClass" "item_lua" + "ScriptFile" "items/util_items/item_fish.lua" + "AbilityCastAnimation" "ACT_DOTA_ATTACK" + "Model" "models/props_gameplay/neutral_box.vmdl" + "AbilityTextureName" "utils/fish" + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_UNIT_TARGET | DOTA_ABILITY_BEHAVIOR_IMMEDIATE" + "AbilityUnitTargetTeam" "DOTA_UNIT_TARGET_TEAM_FRIENDLY | DOTA_UNIT_TARGET_TEAM_ENEMY" + "AbilityUnitTargetType" "DOTA_UNIT_TARGET_HERO" + "SpellImmunityType" "SPELL_IMMUNITY_ENEMIES_YES" + "AbilityCastRange" "200" + "ItemShareability" "ITEM_FULLY_SHAREABLE_STACKING" + "ItemQuality" "artifact" + "ItemCost" "0" + "ItemDroppable" "1" + "ItemPurchasable" "0" + "ItemSellable" "1" + "ItemKillable" "1" + "ItemInitialCharges" "1" + "ItemStackable" "1" + "ItemPermanent" "0" + "SideShop" "0" + + "AbilityValues" + { + "heal" "150" + "hunger_bonus" "5" + } + } + "item_pizza" + { + "BaseClass" "item_lua" + "ScriptFile" "items/util_items/item_pizza.lua" + "AbilityCastAnimation" "ACT_DOTA_ATTACK" + "Model" "models/props_gameplay/neutral_box.vmdl" + "AbilityTextureName" "utils/pizza" + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_UNIT_TARGET | DOTA_ABILITY_BEHAVIOR_IMMEDIATE" + "AbilityUnitTargetTeam" "DOTA_UNIT_TARGET_TEAM_FRIENDLY | DOTA_UNIT_TARGET_TEAM_ENEMY" + "AbilityUnitTargetType" "DOTA_UNIT_TARGET_HERO" + "SpellImmunityType" "SPELL_IMMUNITY_ENEMIES_YES" + "AbilityCastRange" "200" + "ItemShareability" "ITEM_FULLY_SHAREABLE_STACKING" + "ItemQuality" "artifact" + "ItemCost" "0" + "ItemDroppable" "1" + "ItemPurchasable" "0" + "ItemSellable" "0" + "ItemKillable" "1" + "ItemInitialCharges" "1" + "ItemStackable" "1" + "ItemPermanent" "0" + "SideShop" "0" + "AbilityValues" + { + "stack_count" "15" + "hunger_bonus" "100" + } + } + "item_firecore" + { + "BaseClass" "item_lua" + "ScriptFile" "items/util_items/item_firecore.lua" + "AbilityCastAnimation" "ACT_DOTA_ATTACK" + "Model" "models/props_gameplay/neutral_box.vmdl" + "AbilityTextureName" "item_philosophers_stone" + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_UNIT_TARGET | DOTA_ABILITY_BEHAVIOR_IMMEDIATE" + "AbilityUnitTargetTeam" "DOTA_UNIT_TARGET_TEAM_FRIENDLY | DOTA_UNIT_TARGET_TEAM_ENEMY" + "AbilityUnitTargetType" "DOTA_UNIT_TARGET_HERO" + "SpellImmunityType" "SPELL_IMMUNITY_ENEMIES_YES" + "AbilityCastRange" "200" + "ItemShareability" "ITEM_FULLY_SHAREABLE_STACKING" + "ItemQuality" "artifact" + "ItemCost" "0" + "ItemDroppable" "1" + "ItemPurchasable" "0" + "ItemSellable" "0" + "ItemKillable" "0" + "ItemInitialCharges" "0" + "ItemStackable" "0" + "ItemPermanent" "0" + "SideShop" "0" + } + "item_grilled_meat" + { + "BaseClass" "item_lua" + "ScriptFile" "items/util_items/item_grilled_meat.lua" + "AbilityCastAnimation" "ACT_DOTA_ATTACK" + "Model" "models/props_gameplay/neutral_box.vmdl" + "AbilityTextureName" "utils/grilled_meat" + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_UNIT_TARGET | DOTA_ABILITY_BEHAVIOR_IMMEDIATE" + "AbilityUnitTargetTeam" "DOTA_UNIT_TARGET_TEAM_FRIENDLY | DOTA_UNIT_TARGET_TEAM_ENEMY" + "AbilityUnitTargetType" "DOTA_UNIT_TARGET_HERO" + "SpellImmunityType" "SPELL_IMMUNITY_ENEMIES_YES" + "AbilityCastRange" "200" + "ItemShareability" "ITEM_FULLY_SHAREABLE_STACKING" + "ItemQuality" "artifact" + "ItemCost" "0" + "ItemDroppable" "1" + "ItemPurchasable" "0" + "ItemSellable" "1" + "ItemKillable" "1" + "ItemInitialCharges" "1" + "ItemStackable" "1" + "ItemPermanent" "0" + "SideShop" "0" + + "AbilityValues" + { + "heal" "350" + "hunger_bonus" "15" + } + } + + "item_bread" + { + "BaseClass" "item_lua" + "ScriptFile" "items/util_items/item_bread.lua" + "AbilityCastAnimation" "ACT_DOTA_ATTACK" + "Model" "models/props_gameplay/neutral_box.vmdl" + "AbilityTextureName" "utils/bread" + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_UNIT_TARGET | DOTA_ABILITY_BEHAVIOR_IMMEDIATE" + "AbilityUnitTargetTeam" "DOTA_UNIT_TARGET_TEAM_FRIENDLY | DOTA_UNIT_TARGET_TEAM_ENEMY" + "AbilityUnitTargetType" "DOTA_UNIT_TARGET_HERO" + "SpellImmunityType" "SPELL_IMMUNITY_ENEMIES_YES" + "AbilityCastRange" "200" + "ItemShareability" "ITEM_FULLY_SHAREABLE_STACKING" + "ItemQuality" "artifact" + "ItemCost" "0" + "ItemDroppable" "1" + "ItemPurchasable" "0" + "ItemSellable" "1" + "ItemKillable" "1" + "ItemInitialCharges" "1" + "ItemStackable" "1" + "ItemPermanent" "0" + "SideShop" "0" + + "AbilityValues" + { + "heal" "100" + "hunger_bonus" "40" + } + } + + "item_sandwich" + { + "BaseClass" "item_lua" + "ScriptFile" "items/util_items/item_sandwich.lua" + "AbilityCastAnimation" "ACT_DOTA_ATTACK" + "Model" "models/props_gameplay/neutral_box.vmdl" + "AbilityTextureName" "utils/sandwich" + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_UNIT_TARGET | DOTA_ABILITY_BEHAVIOR_IMMEDIATE" + "AbilityUnitTargetTeam" "DOTA_UNIT_TARGET_TEAM_FRIENDLY | DOTA_UNIT_TARGET_TEAM_ENEMY" + "AbilityUnitTargetType" "DOTA_UNIT_TARGET_HERO" + "SpellImmunityType" "SPELL_IMMUNITY_ENEMIES_YES" + "AbilityCastRange" "200" + "ItemShareability" "ITEM_FULLY_SHAREABLE_STACKING" + "ItemQuality" "artifact" + "ItemCost" "0" + "ItemDroppable" "1" + "ItemPurchasable" "0" + "ItemSellable" "1" + "ItemKillable" "1" + "ItemInitialCharges" "1" + "ItemStackable" "1" + "ItemPermanent" "0" + "SideShop" "0" + + "AbilityValues" + { + "hunger_bonus" "70" + "buff_duration" "180" + "bonus_damage" "150" + "bonus_armor" "16" + "move_speed_slow_pct" "30" + "model_scale" "40" + } + } + + "item_cheese" + { + "BaseClass" "item_lua" + "ScriptFile" "items/util_items/item_milk.lua" + "AbilityCastAnimation" "ACT_DOTA_ATTACK" + "Model" "models/props_gameplay/neutral_box.vmdl" + "AbilityTextureName" "utils/cheese" + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_PASSIVE" + "AbilityUnitTargetTeam" "DOTA_UNIT_TARGET_TEAM_FRIENDLY | DOTA_UNIT_TARGET_TEAM_ENEMY" + "AbilityUnitTargetType" "DOTA_UNIT_TARGET_HERO" + "SpellImmunityType" "SPELL_IMMUNITY_ENEMIES_YES" + "AbilityCastRange" "200" + "ItemShareability" "ITEM_FULLY_SHAREABLE_STACKING" + "ItemQuality" "artifact" + "ItemCost" "0" + "ItemDroppable" "1" + "ItemPurchasable" "0" + "ItemSellable" "1" + "ItemKillable" "1" + "ItemInitialCharges" "1" + "ItemStackable" "1" + "ItemPermanent" "0" + "SideShop" "0" + + "AbilityValues" + { + "mana" "75" + "hunger_bonus" "5" + } + } + "item_milk" + { + "BaseClass" "item_lua" + "ScriptFile" "items/util_items/item_milk.lua" + "AbilityCastAnimation" "ACT_DOTA_ATTACK" + "Model" "models/props_gameplay/bottle_mango001.vmdl" + "AbilityTextureName" "utils/milk" + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_UNIT_TARGET | DOTA_ABILITY_BEHAVIOR_IMMEDIATE" + "AbilityUnitTargetTeam" "DOTA_UNIT_TARGET_TEAM_FRIENDLY | DOTA_UNIT_TARGET_TEAM_ENEMY" + "AbilityUnitTargetType" "DOTA_UNIT_TARGET_HERO" + "SpellImmunityType" "SPELL_IMMUNITY_ENEMIES_YES" + "AbilityCastRange" "200" + "ItemShareability" "ITEM_FULLY_SHAREABLE_STACKING" + "ItemQuality" "artifact" + "ItemCost" "0" + "ItemDroppable" "1" + "ItemPurchasable" "0" + "ItemSellable" "1" + "ItemKillable" "1" + "ItemInitialCharges" "1" + "ItemStackable" "1" + "ItemPermanent" "0" + "SideShop" "0" + + "AbilityValues" + { + "mana" "75" + "hunger_bonus" "5" + } + } + "item_banana" + { + "BaseClass" "item_lua" + "ScriptFile" "items/util_items/item_banana.lua" + "AbilityCastAnimation" "ACT_DOTA_ATTACK" + "Model" "models/props_gameplay/banana_prop_closed.vmdl" + "AbilityTextureName" "utils/banana" + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_UNIT_TARGET | DOTA_ABILITY_BEHAVIOR_IMMEDIATE" + "AbilityUnitTargetTeam" "DOTA_UNIT_TARGET_TEAM_FRIENDLY | DOTA_UNIT_TARGET_TEAM_ENEMY" + "AbilityUnitTargetType" "DOTA_UNIT_TARGET_HERO" + "SpellImmunityType" "SPELL_IMMUNITY_ENEMIES_YES" + "AbilityCastRange" "200" + "ItemShareability" "ITEM_FULLY_SHAREABLE_STACKING" + "ItemQuality" "artifact" + "ItemCost" "0" + "ItemDroppable" "1" + "ItemPurchasable" "0" + "ItemSellable" "1" + "ItemKillable" "1" + "ItemInitialCharges" "1" + "ItemStackable" "1" + "ItemPermanent" "0" + "SideShop" "0" + + "AbilityValues" + { + "mana" "125" + "hunger_bonus" "9" + } + } + "item_rofl_for_kaban_pumba" + { + "BaseClass" "item_lua" + "ScriptFile" "items/util_items/item_rofl_for_kaban_pumba.lua" + "AbilityCastAnimation" "ACT_DOTA_ATTACK" + "Model" "models/props_gameplay/neutral_box.vmdl" + "Effect" "particles/heavenly_item_effect.vpcf" + "AbilityTextureName" "utils/rofl_for_kaban_pumba" + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_POINT | DOTA_ABILITY_BEHAVIOR_IMMEDIATE" + + "AbilityCastRange" "200" + "ItemQuality" "artifact" + "ItemCost" "0" + "AbilityCooldown" "60.0" + "ItemPurchasable" "0" + + "ItemSellable" "1" + "ItemKillable" "1" + "AbilityValues" + { + + } + } + "item_easter_egg" + { + "BaseClass" "item_lua" + "ScriptFile" "items/util_items/item_easter_egg.lua" + "AbilityCastAnimation" "ACT_DOTA_ATTACK" + "Model" "models/props_gameplay/moon_shard/moon_shard_meteor.vmdl" + "AbilityTextureName" "utils/eggs" + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_UNIT_TARGET | DOTA_ABILITY_BEHAVIOR_IMMEDIATE" + "AbilityUnitTargetTeam" "DOTA_UNIT_TARGET_TEAM_FRIENDLY | DOTA_UNIT_TARGET_TEAM_ENEMY" + "AbilityUnitTargetType" "DOTA_UNIT_TARGET_HERO" + "SpellImmunityType" "SPELL_IMMUNITY_ENEMIES_YES" + "AbilityCastRange" "200" + "ItemShareability" "ITEM_FULLY_SHAREABLE_STACKING" + "ItemQuality" "artifact" + "ItemCost" "0" + "ItemDroppable" "1" + "ItemPurchasable" "0" + "ItemSellable" "1" + "ItemKillable" "1" + "ItemInitialCharges" "1" + "ItemStackable" "1" + "SideShop" "0" + + "AbilityValues" + { + "hunger_bonus" "100" + } + } + "item_candy" + { + "BaseClass" "item_lua" + "ScriptFile" "items/util_items/item_candys.lua" + "AbilityCastAnimation" "ACT_DOTA_ATTACK" + "Model" "models/props_gameplay/moon_shard/moon_shard_001.vmdl" + "AbilityTextureName" "utils/candy" + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_UNIT_TARGET | DOTA_ABILITY_BEHAVIOR_IMMEDIATE" + "AbilityUnitTargetTeam" "DOTA_UNIT_TARGET_TEAM_FRIENDLY | DOTA_UNIT_TARGET_TEAM_ENEMY" + "AbilityUnitTargetType" "DOTA_UNIT_TARGET_HERO" + "SpellImmunityType" "SPELL_IMMUNITY_ENEMIES_YES" + "AbilityCastRange" "200" + "ItemShareability" "ITEM_FULLY_SHAREABLE_STACKING" + "ItemQuality" "artifact" + "ItemCost" "0" + "ItemDroppable" "1" + "ItemPurchasable" "0" + "ItemSellable" "1" + "ItemKillable" "1" + "ItemInitialCharges" "1" + "ItemStackable" "1" + "SideShop" "0" + + "AbilityValues" + { + "hunger_bonus" "15" + } + } + "item_energy_drink" + { + "BaseClass" "item_lua" + "ScriptFile" "items/util_items/item_energy_drink.lua" + "AbilityCastAnimation" "ACT_DOTA_ATTACK" + "Model" "models/props_gameplay/neutral_box.vmdl" + "AbilityTextureName" "utils/energetic" + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_UNIT_TARGET | DOTA_ABILITY_BEHAVIOR_IMMEDIATE" + "AbilityUnitTargetTeam" "DOTA_UNIT_TARGET_TEAM_FRIENDLY | DOTA_UNIT_TARGET_TEAM_ENEMY" + "AbilityUnitTargetType" "DOTA_UNIT_TARGET_HERO" + "SpellImmunityType" "SPELL_IMMUNITY_ENEMIES_YES" + "AbilityCastRange" "200" + "ItemShareability" "ITEM_FULLY_SHAREABLE_STACKING" + "ItemQuality" "artifact" + "ItemCost" "0" + "ItemDroppable" "1" + "ItemPurchasable" "0" + "ItemSellable" "1" + "ItemKillable" "1" + "ItemInitialCharges" "1" + "ItemStackable" "1" + "ItemPermanent" "0" + "SideShop" "0" + + "AbilityValues" + { + "heal" "100" + "mana" "150" + "hunger_bonus" "35" + "buff_duration" "45" + "buff_move_speed_pct" "12" + "buff_attack_speed" "30" + } + } + + "item_cocktail" + { + "BaseClass" "item_lua" + "ScriptFile" "items/util_items/item_cocktail.lua" + "AbilityCastAnimation" "ACT_DOTA_ATTACK" + "Model" "models/props_gameplay/neutral_box.vmdl" + "AbilityTextureName" "utils/cocktail" + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_UNIT_TARGET | DOTA_ABILITY_BEHAVIOR_IMMEDIATE" + "AbilityUnitTargetTeam" "DOTA_UNIT_TARGET_TEAM_FRIENDLY | DOTA_UNIT_TARGET_TEAM_ENEMY" + "AbilityUnitTargetType" "DOTA_UNIT_TARGET_HERO" + "SpellImmunityType" "SPELL_IMMUNITY_ENEMIES_YES" + "AbilityCastRange" "200" + "ItemShareability" "ITEM_FULLY_SHAREABLE_STACKING" + "ItemQuality" "artifact" + "ItemCost" "0" + "ItemDroppable" "1" + "ItemPurchasable" "0" + "ItemSellable" "1" + "ItemKillable" "1" + "ItemInitialCharges" "1" + "ItemStackable" "1" + "ItemPermanent" "0" + "SideShop" "0" + + "AbilityValues" + { + "hunger_bonus" "40" + "buff_duration" "120" + "spell_lifesteal" "15" + "spell_amp" "15" + "manacost_increase" "20" + } + } + + "item_coffee" + { + "BaseClass" "item_lua" + "ScriptFile" "items/util_items/item_coffee.lua" + "AbilityCastAnimation" "ACT_DOTA_ATTACK" + "Model" "models/props_gameplay/neutral_box.vmdl" + "AbilityTextureName" "utils/coffee" + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_UNIT_TARGET | DOTA_ABILITY_BEHAVIOR_IMMEDIATE" + "AbilityUnitTargetTeam" "DOTA_UNIT_TARGET_TEAM_FRIENDLY | DOTA_UNIT_TARGET_TEAM_ENEMY" + "AbilityUnitTargetType" "DOTA_UNIT_TARGET_HERO" + "SpellImmunityType" "SPELL_IMMUNITY_ENEMIES_YES" + "AbilityCastRange" "200" + "ItemShareability" "ITEM_FULLY_SHAREABLE_STACKING" + "ItemQuality" "artifact" + "ItemCost" "100" + "ItemDroppable" "1" + "ItemPurchasable" "0" + "ItemSellable" "1" + "ItemKillable" "1" + "ItemInitialCharges" "1" + "ItemStackable" "1" + "ItemPermanent" "0" + "SideShop" "0" + + "AbilityValues" + { + "hunger_bonus" "15" + "buff_duration" "60" + "cooldown_reduction" "15" + "casttime_reduction" "20" + } + } + + "item_coffe_bean" + { + "BaseClass" "item_lua" + "ScriptFile" "items/util_items/item_coffe_bean.lua" + "AbilityCastAnimation" "ACT_DOTA_ATTACK" + "Model" "models/props_gameplay/neutral_box.vmdl" + "AbilityTextureName" "utils/coffee_bean" + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_UNIT_TARGET | DOTA_ABILITY_BEHAVIOR_IMMEDIATE" + "AbilityUnitTargetTeam" "DOTA_UNIT_TARGET_TEAM_FRIENDLY | DOTA_UNIT_TARGET_TEAM_ENEMY" + "AbilityUnitTargetType" "DOTA_UNIT_TARGET_HERO" + "SpellImmunityType" "SPELL_IMMUNITY_ENEMIES_YES" + "AbilityCastRange" "200" + "ItemShareability" "ITEM_FULLY_SHAREABLE_STACKING" + "ItemQuality" "artifact" + "ItemCost" "50" + "ItemDroppable" "1" + "ItemPurchasable" "1" + "ItemSellable" "1" + "ItemKillable" "1" + "ItemInitialCharges" "1" + "ItemStackable" "1" + "ItemPermanent" "0" + "SideShop" "0" + "AbilityValues" + { + "mana_pct" "5" + "mana" "150" + } + } + +} \ No newline at end of file diff --git a/scripts/npc/neutral_items.txt b/scripts/npc/neutral_items.txt new file mode 100644 index 0000000..871690d --- /dev/null +++ b/scripts/npc/neutral_items.txt @@ -0,0 +1,295 @@ +"neutral_items" +{ + "madstone_limits" + { + "madstone_no_limit_time" "00:00" + } + + "neutral_tiers" + { + "1" + { + "start_time" "0:00" + "trinket_options" "4" + "enhancement_options" "4" + "craft_cost" "5" + "xp_bonus" "0" + + "items" + { + "item_occult_bracelet" "1" + "item_kobold_cup" "1" + "item_chipped_vest" "1" + "item_polliwog_charm" "1" + "item_dormant_curio" "1" + "item_duelist_gloves" "1" + "item_weighted_dice" "1" + "item_ash_legion_shield" "1" + "item_dagger_of_ristul" "1" + "item_stonefeather_satchel" "1" + "item_possessed_mask" "1" + "item_foragers_kit" "1" + } + + "enhancements" + { + "global" + { + "item_enhancement_quickened" "1" + "item_enhancement_vital" "1" + } + + "strength" + { + "item_enhancement_brawny" "1" + "item_enhancement_tough" "1" + } + + "agility" + { + "item_enhancement_alert" "1" + "item_enhancement_brawny" "1" + } + + "intelligence" + { + "item_enhancement_mystical" "1" + "item_enhancement_tough" "1" + } + + "universal" + { + "item_enhancement_alert" "1" + "item_enhancement_mystical" "1" + } + } + } + "2" + { + "start_time" "00:00" + "trinket_options" "5" + "enhancement_options" "5" + "craft_cost" "6" + "xp_bonus" "0" + + "items" + { + "item_essence_ring" "1" + "item_mana_draught" "1" + "item_poor_mans_shield" "1" + "item_searing_signet" "1" + "item_pogo_stick" "1" + "item_defiant_shell" "1" + "item_crippling_crossbow" "1" + "item_medallion_of_courage" "1" + "item_seeds_of_serenity" "1" + } + + "enhancements" + { + + "global" + { + "item_enhancement_quickened" "2" + } + + "strength" + { + "item_enhancement_brawny" "2" + "item_enhancement_tough" "2" + "item_enhancement_crude" "1" + } + + "agility" + { + "item_enhancement_alert" "2" + "item_enhancement_brawny" "2" + "item_enhancement_nimble" "1" + } + + "intelligence" + { + "item_enhancement_mystical" "2" + "item_enhancement_tough" "2" + "item_enhancement_keen_eyed" "1" + } + + "universal" + { + "item_enhancement_alert" "2" + "item_enhancement_mystical" "2" + "item_enhancement_titanic" "1" + } + } + } + "3" + { + "start_time" "00:00" + "trinket_options" "5" + "enhancement_options" "5" + "craft_cost" "7" + "xp_bonus" "0" + + "items" + { + "item_serrated_shiv" "1" + "item_gunpowder_gauntlets" "1" + "item_jidi_pollen_bag" "1" + "item_psychic_headband" "1" + "item_unrelenting_eye" "1" + "item_cloak_of_flames" "1" + "item_spellslinger" "1" + "item_stormcrafter" "1" + "item_partisans_brand" "1" + } + + "enhancements" + { + "global" + { + "item_enhancement_quickened" "3" + } + + "strength" + { + "item_enhancement_brawny" "3" + "item_enhancement_tough" "3" + "item_enhancement_crude" "2" + } + + "agility" + { + "item_enhancement_alert" "3" + "item_enhancement_brawny" "3" + "item_enhancement_nimble" "2" + } + + "intelligence" + { + "item_enhancement_mystical" "3" + "item_enhancement_tough" "3" + "item_enhancement_keen_eyed" "2" + } + + "universal" + { + "item_enhancement_alert" "3" + "item_enhancement_mystical" "3" + "item_enhancement_titanic" "2" + } + } + } + "4" + { + "start_time" "00:00" + "trinket_options" "5" + "enhancement_options" "5" + "craft_cost" "8" + "xp_bonus" "0" + + "items" + { + "item_giant_maul" "1" + "item_rattlecage" "1" + "item_idol_of_screeauk" "1" + "item_flayers_bota" "1" + "item_metamorphic_mandible" "1" + "item_dandelion_amulet" "1" + "item_enchanters_bauble" "1" + "item_prophets_pendulum" "1" + "item_conjurers_catalyst" "1" + } + + "enhancements" + { + "global" + { + "item_enhancement_quickened" "4" + "item_enhancement_timeless" "1" + } + + "strength" + { + "item_enhancement_brawny" "4" + "item_enhancement_tough" "4" + "item_enhancement_crude" "3" + } + + "agility" + { + "item_enhancement_alert" "4" + "item_enhancement_brawny" "4" + "item_enhancement_nimble" "3" + } + + "intelligence" + { + "item_enhancement_mystical" "4" + "item_enhancement_tough" "4" + "item_enhancement_keen_eyed" "3" + } + + "universal" + { + "item_enhancement_alert" "4" + "item_enhancement_mystical" "4" + "item_enhancement_titanic" "3" + } + } + } + "5" + { + "start_time" "00:00" + "trinket_options" "5" + "enhancement_options" "5" + "craft_cost" "9" + "recraft_cost" "3" + "xp_bonus" "0" + + "items" + { + "item_desolator_2" "1" + "item_fallen_sky" "1" + "item_demonicon" "1" + "item_minotaur_horn" "1" + "item_spider_legs" "1" + "item_riftshadow_prism" "1" + "item_dezun_bloodrite" "1" + "item_divine_regalia" "1" + "item_harmonizer" "1" + "item_heavy_blade" "1" + } + + "enhancements" + { + "global" + { + "item_enhancement_timeless" "2" + "item_enhancement_evolved" "1" + "item_enhancement_fleetfooted" "1" + "item_enhancement_vampiric" "1" + } + + "strength" + { + "item_enhancement_hulking" "1" + } + + "agility" + { + "item_enhancement_audacious" "1" + } + + "intelligence" + { + "item_enhancement_feverish" "1" + } + + "universal" + { + "item_enhancement_manic" "1" + } + } + } + } +} \ No newline at end of file diff --git a/scripts/npc/npc_abilities_custom.txt b/scripts/npc/npc_abilities_custom.txt new file mode 100644 index 0000000..40a429d --- /dev/null +++ b/scripts/npc/npc_abilities_custom.txt @@ -0,0 +1,134 @@ +#base "creep_abilities/creep_abilities.kv" +#base "creep_abilities/wave_creep_abilities.kv" +#base "heroes/phantom_assassin/phantom_assassin.kv" +#base "heroes/drow_ranger/drow_ranger.kv" +#base "heroes/lina/lina.kv" +#base "heroes/sven/sven.kv" +#base "heroes/crystal_maiden/crystal_maiden.kv" +#base "heroes/juggernaut/juggernaut.kv" +#base "heroes/keeper_of_the_light/keeper_of_the_light.kv" +#base "heroes/rubick/rubick.kv" +#base "heroes/sargatanas/sargatanas.kv" +#base "heroes/sargatanas/summons.kv" + +#base "heroes/nagash/nagash.kv" + +#base "heroes/smaug/smaug.kv" +#base "heroes/bloodhunter/bloodhunter.kv" +#base "heroes/yuki-onna/yuki-onna.kv" +#base "heroes/luna/luna.kv" +#base "heroes/mirana/mirana.kv" +#base "heroes/pudge/pudge.kv" + +#base "heroes/axe/axe.kv" +#base "heroes/templar_assassin/templar_assassin.kv" +#base "heroes/hoodwink/hoodwink.kv" +#base "heroes/sniper/sniper.kv" +#base "heroes/nevermore/nevermore.kv" +#base "heroes/silencer/silencer.kv" +#base "heroes/bristleback/bristleback.kv" +#base "heroes/queenofpain/queenofpain.kv" +#base "heroes/troll_warlord/troll_warlord.kv" +#base "heroes/skywrath_mage/skywrath_mage.kv" +#base "heroes/legion_commander/legion_commander.kv" +#base "heroes/sand_king/sand_king.kv" +#base "heroes/ogre_magi/ogre_magi.kv" +#base "heroes/spectre/spectre.kv" +#base "heroes/vengefulspirit/vengefulspirit.kv" + + +"DOTAAbilities" +{ + "invul_npc" + { + "BaseClass" "ability_lua" + "ScriptFile" "abilities/invul_npc.lua" + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_PASSIVE | DOTA_ABILITY_BEHAVIOR_HIDDEN" + } + "no_healthbar" + { + "BaseClass" "ability_lua" + "ScriptFile" "abilities/no_healthbar.lua" + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_PASSIVE | DOTA_ABILITY_BEHAVIOR_HIDDEN" + } + + "fish_basic" + { + "BaseClass" "ability_lua" + "ScriptFile" "abilities/fish_basic.lua" + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_PASSIVE | DOTA_ABILITY_BEHAVIOR_HIDDEN" + } + "spider_snake_passive" + { + "BaseClass" "ability_lua" + "ScriptFile" "abilities/spider_snake_passive.lua" + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_PASSIVE | DOTA_ABILITY_BEHAVIOR_HIDDEN" + } + dps_tracker + { + "BaseClass" "ability_lua" + "ScriptFile" "abilities/modifiers/dps_tracker.lua" + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_PASSIVE | DOTA_ABILITY_BEHAVIOR_HIDDEN" + } + "invul_box" + { + "BaseClass" "ability_lua" + "ScriptFile" "abilities/invul_box.lua" + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_PASSIVE | DOTA_ABILITY_BEHAVIOR_HIDDEN" + } + "ability_capture_point" + { + "BaseClass" "ability_lua" + "ScriptFile" "abilities/ability_capture_point.lua" + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_PASSIVE | DOTA_ABILITY_BEHAVIOR_HIDDEN" + "AbilityValues" + { + "activation_radius" "400" + "spawn_distance" "120" + } + } + "ability_stacking_crit" + { + "BaseClass" "ability_lua" + "ScriptFile" "abilities/modifiers/ability_stacking_crit.lua" + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_PASSIVE | DOTA_ABILITY_BEHAVIOR_HIDDEN" + } + "ability_stacking_spell_crit" + { + "BaseClass" "ability_lua" + "ScriptFile" "abilities/modifiers/ability_stacking_spell_crit.lua" + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_PASSIVE | DOTA_ABILITY_BEHAVIOR_HIDDEN" + } + "ability_stats_multiplier" + { + "BaseClass" "ability_lua" + "ScriptFile" "modifiers/modifier_stats_multiplier.lua" + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_PASSIVE | DOTA_ABILITY_BEHAVIOR_HIDDEN" + } + "ability_glyph_custom" + { + "BaseClass" "ability_lua" + "ScriptFile" "abilities/modifiers/ability_glyph_custom.lua" + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_PASSIVE | DOTA_ABILITY_BEHAVIOR_HIDDEN" + } + "ability_unit_less_laggy" + { + "BaseClass" "ability_lua" + "ScriptFile" "abilities/modifiers/ability_unit_less_laggy.lua" + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_PASSIVE | DOTA_ABILITY_BEHAVIOR_HIDDEN | DOTA_ABILITY_BEHAVIOR_NOT_LEARNABLE" + "Innate" "1" + } + "ability_effects" + { + "BaseClass" "ability_lua" + "ScriptFile" "abilities/modifiers/ability_effects.lua" + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_PASSIVE | DOTA_ABILITY_BEHAVIOR_HIDDEN " + + } + "ability_modifier_source" + { + "BaseClass" "ability_lua" + "ScriptFile" "abilities/modifiers/ability_modifier_source.lua" + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_PASSIVE | DOTA_ABILITY_BEHAVIOR_HIDDEN | DOTA_ABILITY_BEHAVIOR_NOT_LEARNABLE" + } +} diff --git a/scripts/npc/npc_heroes_custom.txt b/scripts/npc/npc_heroes_custom.txt new file mode 100644 index 0000000..89781a5 --- /dev/null +++ b/scripts/npc/npc_heroes_custom.txt @@ -0,0 +1,1138 @@ +"DOTAHeroes" +{ + "npc_dota_hero_phantom_assassin" + { + "Ability1" "ability_phantom_assassin_stifling_dagger_custom" + "Ability2" "ability_phantom_assassin_phantom_strike_custom" + "Ability3" "ability_phantom_assassin_blur_custom" + "Ability4" "generic_hidden" + "Ability5" "generic_hidden" + "Ability6" "ability_phantom_assassin_coup_de_grace_custom" + "Ability7" "ability_phantom_assassin_phantom_bash_custom" + "Ability8" "generic_hidden" + "Ability9" "generic_hidden" + "Ability10" "special_bonus_movement_speed_25" + "Ability11" "special_bonus_corruption_5" + "Ability12" "special_bonus_unique_assassin_stifling_dagger_max_targets" + "Ability13" "special_bonus_unique_assassin_blur_duration_base" + "Ability14" "special_bonus_unique_assassin_phantom_strike_crit" + "Ability15" "special_bonus_unique_assassin_stifling_dagger_damage_base" + "Ability16" "special_bonus_hp_800" + "Ability17" "special_bonus_unique_assassin_stifling_dagger_chance_again" + "ArmorPhysical" "4" + "AttackDamageMin" "71" + "AttackDamageMax" "80" + "AttackRate" "1.7" + "BaseAttackSpeed" "110" + "AttackRange" "150" + "AttributePrimary" "DOTA_ATTRIBUTE_AGILITY" + "AttributeBaseStrength" "5" + "AttributeStrengthGain" "1.20000" + "AttributeBaseAgility" "21" + "AttributeAgilityGain" "1.70000" + "AttributeBaseIntelligence" "15" + "AttributeIntelligenceGain" "1.400000" + "StatusHealth" "400" // Base health. + "StatusHealthRegen" "0.25" + "MovementSpeed" "265" + "Facets" "" + + } + "npc_dota_hero_juggernaut" + { + "Ability1" "ability_juggernaut_blade_fury_custom" + "Ability2" "ability_juggernaut_healing_ward_custom" + "Ability3" "ability_juggernaut_blade_dance_custom" + "Ability4" "ability_juggernaut_miniomnislash_custom" + "Ability5" "generic_hidden" + "Ability6" "ability_juggernaut_omnislash_custom" + "Ability7" "ability_juggernaut_samurai_soul" + "Ability8" "generic_hidden" + "Ability9" "generic_hidden" + "Ability10" "special_bonus_movement_speed_25" + "Ability11" "special_bonus_hp_125" + "Ability12" "special_bonus_attack_speed_60" + "Ability13" "special_bonus_unique_juggernaut_blade_fury_radius_custom" + "Ability14" "special_bonus_magic_resistance_15" + "Ability15" "special_bonus_unique_juggernaut_healing_ward_duration_custom" + "Ability16" "special_bonus_strength_35" + "Ability17" "special_bonus_unique_juggernaut_blade_dance_lifesteal_custom" + "ArmorPhysical" "4" + "AttackDamageMin" "51" + "AttackDamageMax" "60" + "AttackRate" "1.4" + "BaseAttackSpeed" "110" + "AttackRange" "150" + "AttributePrimary" "DOTA_ATTRIBUTE_AGILITY" + "AttributeBaseStrength" "20" + "AttributeStrengthGain" "2.20000" + "AttributeBaseAgility" "32" + "AttributeAgilityGain" "2.870000" + "StatusManaRegen" "0" + "AttributeBaseIntelligence" "15" + "AttributeIntelligenceGain" "1.400000" + "StatusHealth" "400" // Base health. + "StatusHealthRegen" "1.5" + "MovementSpeed" "300" + "StatusMana" "0" + "Facets" "" + + } + "npc_dota_hero_drow_ranger" + { + "Ability1" "ability_drow_ranger_frost_arrows_custom" + "Ability2" "ability_drow_ranger_gust_custom" + "Ability3" "ability_drow_ranger_multishot_custom" + "Ability4" "drow_ranger_glacier" + "Ability5" "drow_ranger_trueshot" + "Ability6" "ability_drow_ranger_marksmanship_custom" + "Ability10" "special_bonus_movement_speed_25" + "Ability11" "special_bonus_attack_range_75" + "Ability12" "special_bonus_unique_drow_ranger_frost_arrow_damage_pct" + "Ability13" "special_bonus_unique_drow_ranger_arrow_damage_pct" + "Ability14" "special_bonus_unique_drow_ranger_AbilityCooldown" + "Ability15" "special_bonus_unique_drow_ranger_arrow_count_per_wave" + "Ability16" "special_bonus_unique_drow_ranger_gust_frozen_stack" + "Ability17" "special_bonus_agility_80" + + "Facets" "" + + "ArmorPhysical" "0" + "AttackCapabilities" "DOTA_UNIT_CAP_RANGED_ATTACK" + "AttackDamageMin" "29" + "AttackDamageMax" "36" + "AttackRate" "1.700000" + "AttackRange" "625" + "AttributePrimary" "DOTA_ATTRIBUTE_AGILITY" + "AttributeBaseStrength" "16" + "AttributeStrengthGain" "1.900000" + "AttributeBaseAgility" "22" + "AttributeAgilityGain" "2.900000" + "AttributeBaseIntelligence" "15" + "AttributeIntelligenceGain" "1.400000" + "MovementSpeed" "310" + } + "npc_dota_hero_mirana" + { + "Ability1" "ability_mirana_starstorm_custom" + "Ability2" "ability_mirana_sacred_arrow_custom" + "Ability3" "ability_mirana_leap_custom" + "Ability4" "generic_hidden" + "Ability5" "generic_hidden" + "Ability6" "ability_mirana_moonlight_shadow_custom" + "Ability7" "ability_mirana_innate_custom" + "Ability8" "generic_hidden" + "Ability10" "special_bonus_unique_mirana_starstorm_damage" + "Ability11" "special_bonus_unique_mirana_starstorm_attack_proc" + "Ability12" "special_bonus_unique_mirana_sacred_arrow_side_arrows" + "Ability13" "special_bonus_unique_mirana_sacred_arrow_damage" + "Ability14" "special_bonus_unique_mirana_leap_air_strike" + "Ability15" "special_bonus_unique_mirana_leap_landing_radius" + "Ability16" "special_bonus_unique_mirana_moonlight_duration" + "Ability17" "special_bonus_unique_mirana_innate_mana_damage" + + "Facets" "" + + "ArmorPhysical" "0" + "AttackCapabilities" "DOTA_UNIT_CAP_RANGED_ATTACK" + "AttackDamageMin" "27" + "AttackDamageMax" "33" + "AttackRate" "1.700000" + "AttackRange" "630" + "AttributePrimary" "DOTA_ATTRIBUTE_AGILITY" + "AttributeBaseStrength" "16" + "AttributeStrengthGain" "1.900000" + "AttributeBaseAgility" "22" + "AttributeAgilityGain" "2.200000" + "AttributeBaseIntelligence" "18" + "AttributeIntelligenceGain" "1.400000" + "MovementSpeed" "285" + } + "npc_dota_hero_luna" + { + "Ability1" "ability_luna_lucent_beam_custom" + "Ability2" "ability_luna_twin_glaives_custom" + "Ability3" "ability_luna_moon_glaive_custom" + "Ability4" "generic_hidden" + "Ability5" "ability_luna_lunar_blessing_custom" + "Ability6" "ability_luna_eclipse_custom" + "Ability10" "special_bonus_unique_luna_lucent_beam_damage" + "Ability11" "special_bonus_unique_luna_orbit_radius" + "Ability12" "special_bonus_unique_luna_glaive_extra_bounce" + "Ability13" "special_bonus_unique_luna_blessing_aura" + "Ability14" "special_bonus_unique_luna_lucent_beam_cooldown" + "Ability15" "special_bonus_unique_luna_orbit_damage" + "Ability16" "special_bonus_unique_luna_eclipse_power" + "Ability17" "special_bonus_unique_luna_moon_glaive_beam_attack" + + "Facets" "" + + "ArmorPhysical" "2" + "AttackCapabilities" "DOTA_UNIT_CAP_RANGED_ATTACK" + "AttackDamageMin" "34" + "AttackDamageMax" "40" + "AttackRate" "1.700000" + "AttackRange" "330" + "AttributePrimary" "DOTA_ATTRIBUTE_AGILITY" + "AttributeBaseStrength" "20" + "AttributeStrengthGain" "2.200000" + "AttributeBaseAgility" "26" + "AttributeAgilityGain" "3.400000" + "AttributeBaseIntelligence" "18" + "AttributeIntelligenceGain" "1.900000" + "MovementSpeed" "325" + } + "npc_dota_hero_pudge" + { + "Ability1" "ability_pudge_meat_hook_custom" + "Ability2" "ability_pudge_rot_custom" + "Ability3" "ability_pudge_meat_shield_custom" + "Ability4" "generic_hidden" + "Ability5" "generic_hidden" + "Ability6" "ability_pudge_dismember_custom" + "Ability7" "ability_pudge_flesh_heap_custom" + "Ability10" "special_bonus_unique_pudge_hook_range" + "Ability11" "special_bonus_unique_pudge_rot_radius" + "Ability12" "special_bonus_unique_pudge_heap_range" + "Ability13" "special_bonus_unique_pudge_hook_damage" + "Ability14" "special_bonus_unique_pudge_rot_damage" + "Ability15" "special_bonus_unique_pudge_heap_strength" + "Ability16" "special_bonus_unique_pudge_dismember_range" + "Ability17" "special_bonus_unique_pudge_dismember_damage" + "ArmorPhysical" "0" + "AttackCapabilities" "DOTA_UNIT_CAP_MELEE_ATTACK" + "AttackDamageMin" "28" + "AttackDamageMax" "34" + "AttackRate" "1.700000" + "AttackRange" "175" + "AttributePrimary" "DOTA_ATTRIBUTE_STRENGTH" + "AttributeBaseStrength" "25" + "AttributeStrengthGain" "3.2" + "AttributeBaseAgility" "14" + "AttributeAgilityGain" "1.4" + "AttributeBaseIntelligence" "16" + "AttributeIntelligenceGain" "1.8" + "StatusHealthRegen" "1.2" + "MovementSpeed" "285" + "Facets" "" + } + "npc_dota_hero_lina" + { + "Ability1" "ability_lina_dragon_slave_custom" + "Ability2" "ability_lina_light_strike_array_custom" + "Ability3" "ability_lina_flame_cloak_custom" + "Ability4" "generic_hidden" + "Ability5" "ability_lina_scorch_affinity_innate" + "Ability6" "ability_lina_laguna_blade_custom" + "Ability7" "generic_hidden" + "Ability10" "special_bonus_armor_7" + "Ability11" "special_bonus_hp_200" + "Ability12" "special_bonus_magic_resistance_15" + "Ability13" "special_bonus_unique_lina_flame_cloak_custom_damage_bonus" + "Ability14" "special_bonus_attack_speed_60" + "Ability15" "special_bonus_unique_lina_laguna_blade_custom_cd" + "Ability16" "special_bonus_unique_lina_flame_cloak_custom_bonus_movespeed_attack_speed_pct" + "Ability17" "special_bonus_unique_lina_light_strike_array_five" + + "Facets" "" + + "ArmorPhysical" "1" + "AttackDamageMin" "21" + "AttackDamageMax" "29" + "AttackRate" "1.600000" + "AttackRange" "670" + "AttributePrimary" "DOTA_ATTRIBUTE_INTELLECT" + "AttributeBaseStrength" "20" + "AttributeStrengthGain" "2.400000" + "AttributeBaseAgility" "23" + "AttributeAgilityGain" "2.400000" + "AttributeBaseIntelligence" "30" + "AttributeIntelligenceGain" "3.800000" + "MovementSpeed" "290" + } + "npc_dota_hero_nevermore" + { + "AttributePrimary" "DOTA_ATTRIBUTE_AGILITY" + "Ability1" "nevermore_shadowraze1_custom" + "Ability2" "nevermore_shadowraze2_custom" + "Ability3" "nevermore_shadowraze3_custom" + "Ability4" "nevermore_necromastery_custom" + "Ability5" "nevermore_deadly_strike_custom" + "Ability6" "nevermore_requiem_custom" + "Ability7" "generic_hidden" + "Ability8" "nevermore_dark_lord_custom" + "Ability10" "special_bonus_unique_nevermore_custom_1" + "Ability11" "special_bonus_unique_nevermore_custom_5" + "Ability12" "special_bonus_unique_nevermore_custom_3" + "Ability13" "special_bonus_unique_nevermore_custom_4" + "Ability14" "special_bonus_unique_nevermore_custom_2" + "Ability15" "special_bonus_unique_nevermore_custom_6" + "Ability16" "special_bonus_unique_nevermore_custom_7" + "Ability17" "special_bonus_unique_nevermore_custom_8" + + "Facets" "" + } + "npc_dota_hero_crystal_maiden" + { + "Ability1" "ability_crystal_maiden_crystal_nova_custom" + "Ability2" "ability_crystal_maiden_frostbite_custom" + "Ability3" "ability_crystal_maiden_brilliance_aura_custom" + "Ability4" "generic_hidden" + "Ability5" "generic_hidden" + "Ability6" "ability_crystal_maiden_freezing_field_custom" + "Ability7" "crystal_maiden_blueheart_floe" + "Ability10" "special_bonus_magic_resistance_15" + "Ability11" "special_bonus_hp_200" + "Ability12" "special_bonus_armor_7" + "Ability13" "special_bonus_intelligence_15" + "Ability14" "special_bonus_movement_speed_25" + "Ability15" "special_bonus_spell_amplify_12" + "Ability16" "special_bonus_cooldown_reduction_15" + "Ability17" "special_bonus_hp_400" + + "Facets" "" + + "ArmorPhysical" "0" + "AttackCapabilities" "DOTA_UNIT_CAP_RANGED_ATTACK" + "AttackDamageMin" "26" + "AttackDamageMax" "32" + "AttackRate" "1.700000" + "AttackRange" "600" + "AttributePrimary" "DOTA_ATTRIBUTE_INTELLECT" + "AttributeBaseStrength" "17" + "AttributeStrengthGain" "2.200000" + "AttributeBaseAgility" "16" + "AttributeAgilityGain" "1.600000" + "AttributeBaseIntelligence" "16" + "AttributeIntelligenceGain" "3.300000" + "MovementSpeed" "280" + } + "npc_dota_hero_sven" + { + "Model" "models/heroes/sven/sven.vmdl" + "SoundSet" "Hero_Sven" + "Enabled" "1" + "Role" "Carry,Disabler,Initiator,Durable,Nuker" + "Rolelevels" "2,2,2,2,1" + "Complexity" "1" + "Team" "Good" + "ModelScale" "0.840000" + "Ability1" "ability_sven_storm_hammer_custom" + "Ability2" "ability_sven_great_cleave_custom" + "Ability3" "ability_sven_warcry_custom" + "Ability4" "generic_hidden" + "Ability5" "generic_hidden" + "Ability6" "ability_sven_gods_strength_custom" + "Ability7" "sven_vanquisher" + + "Ability10" "special_bonus_magic_resistance_15" + "Ability11" "special_bonus_unique_sven_warcry_duration_base" + "Ability12" "special_bonus_unique_sven_storm_hammer_stun_duration" + "Ability13" "special_bonus_unique_sven_gods_strength_duration_base" + "Ability14" "special_bonus_unique_sven_warcry_damage_bonus_per_armor" + "Ability15" "special_bonus_unique_sven_gods_strength_damage_per_health" + "Ability16" "special_bonus_unique_sven_gods_strength_damage_bonus_base" + "Ability17" "special_bonus_unique_sven_great_cleave_damage_pct" + "Facets" "" + + "ArmorPhysical" "7" + "AttackCapabilities" "DOTA_UNIT_CAP_MELEE_ATTACK" + "AttackDamageMin" "77" + "AttackDamageMax" "89" + "AttackRate" "1.900000" + "AttackRange" "150" + "AttributePrimary" "DOTA_ATTRIBUTE_STRENGTH" + "AttributeBaseStrength" "23" + "AttributeStrengthGain" "3.5" + "AttributeBaseAgility" "18" + "AttributeAgilityGain" "2.20000" + "AttributeBaseIntelligence" "16" + "AttributeIntelligenceGain" "1.500000" + "MovementSpeed" "325" + } + + "npc_dota_hero_vengefulspirit" + { + "Ability1" "vengefulspirit_magic_missile_custom" + "Ability2" "vengefulspirit_wave_of_terror_custom" + "Ability3" "ability_vengefulspirit_command_aura_custom" + "Ability4" "vengefulspirit_revenge" + "Ability6" "vengefulspirit_nether_swap_custom" + "Ability7" "ability_vengefulspirit_spirit_debt_innate" + "Ability10" "special_bonus_unique_vengefulspirit_4_1" + "Ability11" "special_bonus_attack_speed_25" + "Ability12" "special_bonus_unique_vengeful_spirit_4" + "Ability13" "special_bonus_unique_vengefulspirit_4_2" + "Ability14" "special_bonus_unique_vengeful_spirit_5" + "Ability15" "special_bonus_unique_vengeful_spirit_wave_of_terror_steal" + "Ability16" "special_bonus_unique_vengeful_spirit_2" + "Ability17" "special_bonus_unique_vengeful_spirit_9" + "Facets" "" + } + + "npc_dota_hero_sand_king" + { + "Model" "models/heroes/sand_king/sand_king.vmdl" + "SoundSet" "Hero_SandKing" + "Enabled" "1" + "Role" "Initiator,Disabler,Nuker,Escape,Jungler" + "Rolelevels" "3,2,2,2,1" + "Complexity" "2" + "Team" "Bad" + "ModelScale" "0.930000" + "Ability1" "sandking_burrowstrike_custom" + "Ability2" "sandking_sand_storm_custom" + "Ability3" "sandking_scorpion_strike_custom" + "Ability4" "sandking_caustic_finale_custom" + "Ability5" "generic_hidden" + "Ability6" "sandking_epicenter_custom" + "Ability7" "generic_hidden" + + "Ability10" "special_bonus_magic_resistance_15" + "Ability11" "special_bonus_unique_sandking_burrow_range" + "Ability12" "special_bonus_unique_sandking_storm_damage" + "Ability13" "special_bonus_unique_sandking_scorpion_cd" + "Ability14" "special_bonus_unique_sandking_epicenter_pulses" + "Ability15" "special_bonus_unique_sandking_burrow_stun" + "Ability16" "special_bonus_unique_sandking_epicenter_damage" + "Ability17" "special_bonus_unique_sandking_finale_radius" + "Facets" "" + + "ArmorPhysical" "2" + "AttackCapabilities" "DOTA_UNIT_CAP_MELEE_ATTACK" + "AttackDamageMin" "51" + "AttackDamageMax" "59" + "AttackRate" "1.700000" + "AttackRange" "150" + "AttributePrimary" "DOTA_ATTRIBUTE_ALL" + "AttributeBaseStrength" "22" + "AttributeStrengthGain" "2.800000" + "AttributeBaseAgility" "19" + "AttributeAgilityGain" "1.800000" + "AttributeBaseIntelligence" "16" + "AttributeIntelligenceGain" "1.800000" + "MovementSpeed" "290" + } + + // custom + + "npc_dota_hero_bloodhunter" + { + "baseclass" "npc_dota_hero_bloodseeker" + "HeroId" "181" + "Model" "models/heroes/blood_seeker/blood_seeker.vmdl" + "SoundSet" "hero_bloodseeker" + + "Ability1" "ability_bloodrage" + "Ability2" "ability_bloodstained_memory" + "Ability3" "ability_illusion_of_blood" + "Ability4" "ability_sacrifice_revenge" + "Ability5" "generic_hidden" + "Ability6" "ability_ring_of_corrosive" + "Ability10" "special_bonus_hp_125" + "Ability11" "special_bonus_attack_damage_20" + "Ability12" "special_bonus_unique_blood_hunter_blood_of_illusions_incoming_damage" + "Ability13" "special_bonus_unique_blood_hunter_bloodstained_health" + "Ability14" "special_bonus_unique_blood_hunter_blood_of_illusions_count" + "Ability15" "special_bonus_unique_blood_hunter_sacrifice_revenge_duration" + "Ability16" "special_bonus_unique_blood_hunter_pure_damage_bonus" + "Ability17" "special_bonus_unique_blood_hunter_duration_blood" + + "difficulty" "Pick_medium" + "role_hero" "Pick_Carry" + "AttackCapabilities" "DOTA_UNIT_CAP_MELEE_ATTACK" + "StatusMana" "0" + "StatusManaRegen" "0" + "ArmorPhysical" "-10" + "AttackDamageMin" "44" + "AttackDamageMax" "48" + "AttackRate" "1.600000" + "BaseAttackSpeed" "65" + "AttackAnimationPoint" "0.330000" + + "AttackRange" "150" + "AttributePrimary" "DOTA_ATTRIBUTE_AGILITY" + "AttributeBaseStrength" "21" + "AttributeStrengthGain" "1.20000" + "AttributeBaseAgility" "34" + "AttributeAgilityGain" "0.30000" + "AttributeBaseIntelligence" "15" + "AttributeIntelligenceGain" "1.400000" + "StatusHealthRegen" "0.25" + "MovementTurnRate" "0.500000" + "AttackAcquisitionRange" "800" + "MovementSpeed" "305" + + "Facets" "" + + "particle_folder" "particles/units/heroes/hero_bloodseeker" + "GameSoundsFile" "soundevents/game_sounds_heroes/game_sounds_bloodseeker.vsndevts" + "VoiceFile" "soundevents/voscripts/game_sounds_vo_bloodseeker.vsndevts" + + + + } + "npc_dota_hero_nagash" + { + "baseclass" "npc_dota_hero_phantom_assassin" + "HeroId" "180" + "Model" "models/heroes/phantom_assassin_persona/phantom_assassin_persona.vmdl" + "SoundSet" "Hero_PhantomAssassin" + "Ability1" "dark_friends" + "Ability2" "world_destroyer" + "Ability3" "fighting_up" + "Ability4" "leader_call" + "Ability5" "war_for_life" + "Ability6" "dark_golem" + "Ability10" "special_bonus_hp_125" + "Ability11" "special_bonus_exp_boost_15" + "Ability12" "special_bonus_magic_resistance_15" + "Ability13" "special_bonus_unique_nagash_leader_call_damage" + "Ability14" "special_bonus_hp_800" + "Ability15" "special_bonus_unique_nagash_dark_friend_damage_pct" + "Ability16" "special_bonus_strength_35" + "Ability17" "special_bonus_cooldown_reduction_15" + "BaseAttackSpeed" "160" + "ArmorPhysical" "3" + "AttackDamageMin" "37" + "AttackDamageMax" "43" + "AttackRate" "1.400000" + "AttackRange" "200" + "AttributePrimary" "DOTA_ATTRIBUTE_STRENGTH" + "AttributeBaseStrength" "25" + "AttributeStrengthGain" "3" + "AttributeBaseAgility" "12" + "AttributeAgilityGain" "1.600000" + "AttributeBaseIntelligence" "17" + "AttributeIntelligenceGain" "2.300000" + "StatusHealthRegen" "0.25" + "AttackAnimationPoint" "0.300000" + "MovementTurnRate" "0.800000" + "AttackAcquisitionRange" "800" + "AttackSpeedActivityModifiers" + { + "fast" "170" //"249" + "faster" "275" //"350" + "fastest" "350" //"450" + } + "MovementSpeed" "290" + "Facets" "" + + "animation_transitions" + { + "ACT_DOTA_IDLE" + { + "regular" "0.9" + "aggressive" "0.9" + } + "ACT_DOTA_RUN" + { + "regular" "1" + "aggressive" "1" + } + } + "MovementSpeedActivityModifiers" + { + "run" "345" + "run_fast" "400" + } + "particle_folder" "particles/units/heroes/hero_phantom_assassin_persona" + "GameSoundsFile" "soundevents/game_sounds_heroes/game_sounds_phantom_assassin.vsndevts" + "VoiceFile" "soundevents/voscripts/game_sounds_vo_phantom_assassin.vsndevts" + + + } + + "npc_dota_hero_yuki_onna" + { + "baseclass" "npc_dota_hero_crystal_maiden" + "Model" "models/heroes/crystal_maiden/crystal_maiden_arcana.vmdl" + "SoundSet" "Hero_CrystalMaiden" + "HeroId" "182" + + "Ability1" "ability_yuki_pohela" + "Ability2" "ability_yuki_frostshtorm" + "Ability3" "ability_yuki_revenge" + "Ability4" "ability_yuki_ritual" + "Ability5" "ability_yuki_challenge" + "Ability6" "ability_yuki_snowman" + "Ability10" "special_bonus_magic_resistance_15" + "Ability11" "special_bonus_exp_boost_15" + "Ability12" "special_bonus_cooldown_reduction_15" + "Ability13" "special_bonus_exp_boost_60" + "Ability14" "special_bonus_unique_yuki_frostshtorm_duration" + "Ability15" "special_bonus_unique_yuki_revenge_damage" + "Ability16" "special_bonus_unique_yuki_revenge_heal" + "Ability17" "special_bonus_strength_35" + "ArmorPhysical" "0" + "AttackCapabilities" "DOTA_UNIT_CAP_NO_ATTACK" + "AttackDamageMin" "0" + "AttackDamageMax" "0" + "AttackRate" "1.700000" + "AttackAnimationPoint" "0.250000" + "AttackAcquisitionRange" "800" + "AttackRange" "425" + + "AttributePrimary" "DOTA_ATTRIBUTE_INTELLECT" + "AttributeBaseStrength" "12" + "AttributeStrengthGain" "0.7" + "AttributeBaseIntelligence" "29" + "AttributeIntelligenceGain" "1.9" + "AttributeBaseAgility" "16" + "AttributeAgilityGain" "1.5" + "MovementTurnRate" "0.500000" + "MovementSpeed" "315" + "Facets" "" + "VoiceFile" "soundevents/voscripts/game_sounds_vo_crystalmaiden.vsndevts" + "particle_folder" "particles/units/heroes/hero_crystalmaiden" + "GameSoundsFile" "soundevents/game_sounds_heroes/game_sounds_crystalmaiden.vsndevts" + } + + "npc_dota_hero_sargatanas" + { + "Model" "models/items/terrorblade/dreadhunt_demon/dreadhunt_demon.vmdl" + "baseclass" "npc_dota_hero_terrorblade" + "SoundSet" "Hero_Spectre" + "HeroId" "183" + + + "Ability1" "ability_star_devour" + "Ability2" "ability_hellstep" + "Ability3" "ability_firecleave" + "Ability4" "ability_hell_summon" + "Ability5" "ability_sunflame" + "Ability6" "ability_metamorphosis" + "Ability10" "special_bonus_attack_speed_20" + "Ability11" "special_bonus_attack_damage_20" + "Ability12" "special_bonus_armor_7" + "Ability13" "special_bonus_unique_sargatanas_max_stack" + "Ability14" "special_bonus_unique_sargatanas_duration_1" + "Ability15" "special_bonus_strength_35" + "Ability16" "special_bonus_agility_20" + "Ability17" "special_bonus_unique_sargatanas_str_dmg" + "ArmorPhysical" "4" + "BaseAttackSpeed" "150" + "AttackCapabilities" "DOTA_UNIT_CAP_MELEE_ATTACK" + "AttackDamageMin" "24" + "AttackDamageMax" "28" + "AttackRate" "1.4500000" + "AttackRange" "170" + "AttributePrimary" "DOTA_ATTRIBUTE_STRENGTH" + "AttributeBaseStrength" "18" + "AttributeStrengthGain" "1.400000" + "AttributeBaseAgility" "14" + "AttributeAgilityGain" "2.200000" + "AttributeBaseIntelligence" "14" + "AttributeIntelligenceGain" "0.500000" + "MovementSpeed" "340" + "MovementTurnRate" "0.400000" + "StatusHealthRegen" "2.5" + "AttackAcquisitionRange" "800" + + "AttackSpeedActivityModifiers" + { + "fast" "130" + "faster" "200" + } + "particle_folder" "particles/units/heroes/hero_terrorblade" + "GameSoundsFile" "soundevents/game_sounds_heroes/game_sounds_spectre.vsndevts" + "VoiceFile" "soundevents/voscripts/game_sounds_vo_spectre.vsndevts" + + } + "npc_dota_hero_rubick" + { + "Ability1" "ability_rubick_telekinesis_custom" + "Ability2" "ability_rubick_fade_bolt_custom" + "Ability3" "ability_rubick_arcane_supremacy" + "Ability4" "rubick_empty1" + "Ability5" "rubick_empty2" + "Ability6" "ability_rubick_spellsteal_custom" + "Ability7" "generic_hidden" + "Ability8" "generic_hidden" + "Ability9" "generic_hidden" + "AbilityTalentStart" "10" + "Ability10" "special_bonus_spell_amplify_12" + "Ability11" "special_bonus_unique_rubick_telekinesis_stun" + "Ability12" "special_bonus_unique_rubick_fade_bolt_damage" + "Ability13" "special_bonus_unique_rubick_spellsteal_cooldown" + "Ability14" "special_bonus_unique_rubick_arcane_supremacy_damage_pct" + "Ability15" "special_bonus_unique_rubick_telekinesis_land_radius" + "Ability16" "special_bonus_unique_rubick_fade_bolt_convert" + "Ability17" "special_bonus_unique_rubick_telekinesis_charges" + "ArmorPhysical" "2" + "AttackCapabilities" "DOTA_UNIT_CAP_RANGED_ATTACK" + "AttackDamageMin" "24" + "AttackDamageMax" "30" + "AttackRate" "1.700000" + "AttackRange" "600" + "AttributePrimary" "DOTA_ATTRIBUTE_INTELLECT" + "AttributeBaseStrength" "21" + "AttributeStrengthGain" "2.100000" + "AttributeBaseAgility" "19" + "AttributeAgilityGain" "2.100000" + "AttributeBaseIntelligence" "25" + "AttributeIntelligenceGain" "2.900000" + "MovementSpeed" "290" + "Facets" "" + } + + "npc_dota_hero_elder_dragon_smaug" + { + "Model" "models/heroes/dragon_knight_persona/dk_persona_dragon.vmdl" + "baseclass" "npc_dota_hero_jakiro" + "SoundSet" "Hero_Jakiro" + "HeroId" "184" + + "Ability1" "ability_fire_punishment" + "Ability2" "ability_dragon_scales" + "Ability3" "ability_dragon_fear_aura" + "Ability4" "ability_dragon_gold_deal" + "Ability5" "ability_dragon_reward" + "Ability6" "ability_incandescent_fury" + "Ability10" "special_bonus_hp_150" + "Ability11" "special_bonus_spell_amplify_12" + "Ability12" "special_bonus_cooldown_reduction_15" + "Ability13" "special_bonus_unique_smaug_dragon_scales_reflect" + "Ability14" "special_bonus_unique_smaug_dragon_scales_cd" + "Ability15" "special_bonus_unique_smaug_dragon_gold_deal" + "Ability16" "special_bonus_unique_smaug_incandescent_fury" + "Ability17" "special_bonus_unique_smaug_fire_punishment" + "BaseAttackSpeed" "110" + "ArmorPhysical" "12" + "AttackDamageMin" "27" + "AttackDamageMax" "31" + "AttackRate" "1.7000000" + "AttackCapabilities" "DOTA_UNIT_CAP_RANGED_ATTACK" + "AttackRange" "400" + "AttackAcquisitionRange" "800" + "AttackAnimationPoint" "0.400000" + "AttributePrimary" "DOTA_ATTRIBUTE_ALL" + "AttributeBaseStrength" "5" + "AttributeStrengthGain" "1.2" + "AttributeBaseIntelligence" "5" + "AttributeIntelligenceGain" "1.2" + "AttributeBaseAgility" "5" + "AttributeAgilityGain" "1.2" + "MovementSpeed" "270" + "MovementTurnRate" "0.500000" + "ProjectileModel" "particles/units/heroes/hero_invoker/invoker_forged_spirit_projectile.vpcf" + "ProjectileSpeed" "900" + "particle_folder" "particles/units/heroes/hero_jakiro" + "GameSoundsFile" "soundevents/game_sounds_heroes/game_sounds_jakiro.vsndevts" + "VoiceFile" "soundevents/voscripts/game_sounds_vo_jakiro.vsndevts" + "Facets" "" + } + + "npc_dota_hero_axe" + { + "Ability1" "axe_berserkers_call_custom" + "Ability2" "axe_battle_hunger_custom" + "Ability3" "axe_counter_helix_custom" + "Ability4" "generic_hidden" + "Ability5" "generic_hidden" + "Ability6" "axe_culling_blade_custom" + "Ability7" "axe_one_man_army_custom" + "Ability8" "generic_hidden" + "Ability9" "generic_hidden" + "Ability10" "special_bonus_unique_axe_culling_blade_speed_duration" + "Ability11" "special_bonus_unique_axe_8" + "Ability12" "special_bonus_unique_axe" + "Ability13" "special_bonus_unique_axe_7" + "Ability14" "special_bonus_strength_15" + "Ability15" "special_bonus_unique_axe_4" + "Ability16" "special_bonus_unique_axe_2" + "Ability17" "special_bonus_unique_axe_5" + + "ArmorPhysical" "0" + "AttackCapabilities" "DOTA_UNIT_CAP_MELEE_ATTACK" + "AttackDamageMin" "31" + "AttackDamageMax" "35" + "AttackRate" "1.700000" + "AttackRange" "150" + "AttributePrimary" "DOTA_ATTRIBUTE_STRENGTH" + "AttributeBaseStrength" "25" + "AttributeStrengthGain" "2.7" + "AttributeBaseAgility" "20" + "AttributeAgilityGain" "1.7" + "AttributeBaseIntelligence" "18" + "AttributeIntelligenceGain" "1.6" + "StatusHealth" "400" + "StatusMana" "0" + "StatusManaRegen" "0" + "StatusHealthRegen" "2.5" + "MovementSpeed" "315" + "particle_folder" "particles/units/heroes/hero_axe" + "GameSoundsFile" "soundevents/game_sounds_heroes/game_sounds_axe.vsndevts" + "VoiceFile" "soundevents/voscripts/game_sounds_vo_axe.vsndevts" + } + + "npc_dota_hero_legion_commander" + { + "Ability1" "ability_legion_commander_overwhelming_odds_custom" + "Ability2" "ability_legion_commander_press_the_attack_custom" + "Ability3" "ability_legion_commander_moment_of_courage_custom" + "Ability4" "generic_hidden" + "Ability5" "generic_hidden" + "Ability6" "ability_legion_commander_duel_custom" + "Ability7" "generic_hidden" + "Ability8" "generic_hidden" + "Ability9" "generic_hidden" + "Ability10" "special_bonus_unique_legion_commander_odds_radius" + "Ability11" "special_bonus_unique_legion_commander_pta_duration" + "Ability12" "special_bonus_unique_legion_commander_moc_chance" + "Ability13" "special_bonus_unique_legion_commander_duel_duration" + "Ability14" "special_bonus_unique_legion_commander_moc_lifesteal" + "Ability15" "special_bonus_unique_legion_commander_odds_damage" + "Ability16" "special_bonus_unique_legion_commander_pta_regen" + "Ability17" "special_bonus_unique_legion_commander_duel_reward" + "Facets" "" + + "ArmorPhysical" "0" + "AttackCapabilities" "DOTA_UNIT_CAP_MELEE_ATTACK" + "AttackDamageMin" "57" + "AttackDamageMax" "65" + "AttackRate" "1.700000" + "AttackRange" "150" + "AttributePrimary" "DOTA_ATTRIBUTE_STRENGTH" + "AttributeBaseStrength" "26" + "AttributeStrengthGain" "3.1" + "AttributeBaseAgility" "18" + "AttributeAgilityGain" "2" + "AttributeBaseIntelligence" "14" + "AttributeIntelligenceGain" "2.2" + "StatusMana" "0" + "StatusManaRegen" "0" + "MovementSpeed" "330" + "particle_folder" "particles/units/heroes/hero_legion_commander" + "GameSoundsFile" "soundevents/game_sounds_heroes/game_sounds_legion_commander.vsndevts" + "VoiceFile" "soundevents/voscripts/game_sounds_vo_legion_commander.vsndevts" + } + + "npc_dota_hero_templar_assassin" + { + "AttributePrimary" "DOTA_ATTRIBUTE_AGILITY" + "Ability1" "ability_templar_assassin_refraction_custom" + "Ability2" "ability_templar_assassin_meld_custom" + "Ability3" "ability_templar_assassin_templar_secret_custom" + "Ability4" "ability_templar_assassin_refusion_trap_custom" + "Ability5" "generic_hidden" + "Ability6" "ability_templar_assassin_bedlam_custom" + "Ability7" "ability_templar_assassin_psi_blades_custom" + "Ability8" "generic_hidden" + "Ability9" "generic_hidden" + "Ability10" "special_bonus_movement_speed_25" + "Ability11" "special_bonus_hp_250" + "Ability12" "special_bonus_attack_speed_40" + "Ability13" "special_bonus_unique_templar_assassin_4" + "Ability14" "special_bonus_unique_templar_assassin_8" + "Ability15" "special_bonus_unique_templar_assassin_3" + "Ability16" "special_bonus_unique_templar_assassin_2" + "Ability17" "special_bonus_unique_templar_assassin_7" + } + + "npc_dota_hero_sniper" + { + "Ability1" "ability_sniper_shrapnel_custom" + "Ability2" "ability_sniper_critical_focus_custom" + "Ability3" "ability_sniper_keen_eye_custom" + "Ability4" "sniper_concussive_grenade" + "Ability5" "generic_hidden" + "Ability6" "ability_sniper_assassinate_custom" + "Ability7" "generic_hidden" + "Ability8" "generic_hidden" + "Ability9" "generic_hidden" + "Ability10" "special_bonus_unique_sniper_shrapnel_radius" + "Ability11" "special_bonus_unique_sniper_keen_range" + "Ability12" "special_bonus_unique_sniper_shrapnel_cooldown" + "Ability13" "special_bonus_unique_sniper_critical_focus_duration" + "Ability14" "special_bonus_unique_sniper_critical_focus_crit_mult" + "Ability15" "special_bonus_unique_sniper_keen_proc" + "Ability16" "special_bonus_unique_sniper_assassinate_aoe" + "Ability17" "special_bonus_unique_sniper_assassinate_damage" + "ArmorPhysical" "0" + "AttackCapabilities" "DOTA_UNIT_CAP_RANGED_ATTACK" + "AttackDamageMin" "40" + "AttackDamageMax" "46" + "AttackRate" "1.700000" + "BaseAttackSpeed" "100" + "AttackRange" "550" + "AttributePrimary" "DOTA_ATTRIBUTE_AGILITY" + "AttributeBaseStrength" "19" + "AttributeStrengthGain" "2.0" + "AttributeBaseAgility" "24" + "AttributeAgilityGain" "3.2" + "AttributeBaseIntelligence" "15" + "AttributeIntelligenceGain" "2.6" + "StatusHealth" "400" + "StatusHealthRegen" "1.5" + "MovementSpeed" "285" + "GameSoundsFile" "soundevents/game_sounds_heroes/game_sounds_sniper.vsndevts" + "VoiceFile" "soundevents/voscripts/game_sounds_vo_sniper.vsndevts" + "particle_folder" "particles/units/heroes/hero_sniper" + "Facets" "" + } + + "npc_dota_hero_queenofpain" + { + "AttributePrimary" "DOTA_ATTRIBUTE_INTELLECT" + "Ability1" "queenofpain_shadow_strike_custom" + "Ability2" "queenofpain_blink_custom" + "Ability3" "queenofpain_scream_of_pain_custom" + "Ability4" "generic_hidden" + "Ability5" "generic_hidden" + "Ability6" "queenofpain_sonic_wave" + "Ability7" "queenofpain_agony_innate" + "Ability10" "special_bonus_unique_queen_of_pain_1" + "Ability11" "special_bonus_unique_queen_of_pain_2_1" + "Ability12" "special_bonus_unique_queen_of_pain_4" + "Ability13" "special_bonus_unique_queen_of_pain_8" + "Ability14" "special_bonus_unique_queen_of_pain_2" + "Ability15" "special_bonus_unique_queen_of_pain_3" + "Ability16" "special_bonus_unique_queen_of_pain_6" + "Ability17" "special_bonus_unique_queen_of_pain_7" + + "Facets" "" + + "GameSoundsFile" "soundevents/game_sounds_heroes/game_sounds_queenofpain.vsndevts" + "VoiceFile" "soundevents/voscripts/game_sounds_vo_queenofpain.vsndevts" + "particle_folder" "particles/units/heroes/hero_queenofpain" + } + + "npc_dota_hero_skywrath_mage" + { + "Ability1" "skywrath_mage_arcane_bolt_custom" + "Ability2" "skywrath_mage_concussive_shot_custom" + "Ability3" "skywrath_mage_ancient_seal_custom" + "Ability4" "skywrath_mage_staff_of_the_scion_custom" + "Ability5" "generic_hidden" + "Ability6" "skywrath_mage_mystic_flare_custom" + "Ability7" "skywrath_mage_innate_custom" + "Ability8" "generic_hidden" + "Ability10" "special_bonus_mp_regen_2" + "Ability11" "special_bonus_unique_skywrath_6" + "Ability12" "special_bonus_unique_skywrath_concussive_shot_slow" + "Ability13" "special_bonus_unique_skywrath" + "Ability14" "special_bonus_unique_skywrath_2" + "Ability15" "special_bonus_unique_skywrath_4" + "Ability16" "special_bonus_unique_skywrath_3" + "Ability17" "special_bonus_unique_skywrath_5" + "ArmorPhysical" "0" + "AttackCapabilities" "DOTA_UNIT_CAP_RANGED_ATTACK" + "AttackDamageMin" "18" + "AttackDamageMax" "28" + "AttackRate" "1.700000" + "AttackRange" "625" + "AttributePrimary" "DOTA_ATTRIBUTE_INTELLECT" + "AttributeBaseStrength" "21" + "AttributeStrengthGain" "2.0" + "AttributeBaseAgility" "13" + "AttributeAgilityGain" "0.8" + "AttributeBaseIntelligence" "26" + "AttributeIntelligenceGain" "3.6" + "MovementSpeed" "325" + "Facets" "" + "GameSoundsFile" "soundevents/game_sounds_heroes/game_sounds_skywrath_mage.vsndevts" + "VoiceFile" "soundevents/voscripts/game_sounds_vo_skywrath_mage.vsndevts" + "particle_folder" "particles/units/heroes/hero_skywrath_mage" + } + "npc_dota_hero_silencer" + { + "HeroId" "75" + "AttributePrimary" "DOTA_ATTRIBUTE_INTELLECT" + "Ability1" "silencer_curse_of_the_silent" + "Ability2" "glaives_of_wisdom" + "Ability3" "razor_eye_of_the_storm_lua" + "Ability4" "int" + "Ability5" "ability_last_word" + "Ability6" "ability_global_silence" + "Ability10" "special_bonus_attack_speed_20" + "Ability11" "special_bonus_attack_damage_20" + "Ability12" "special_bonus_armor_7" + "Ability13" "special_bonus_unique_silencer_storm_interval" + "Ability14" "special_bonus_unique_silencer_grow_int" + "Ability15" "special_bonus_unique_silencer_storm_duration" + "Ability16" "special_bonus_unique_silencer_intellect_damage_pct" + "Ability17" "special_bonus_unique_silencer_glaives_bounces" + "Facets" "" + } + + "npc_dota_hero_troll_warlord" + { + "AttributePrimary" "DOTA_ATTRIBUTE_AGILITY" + "Ability1" "troll_warlord_switch_stance_custom" + "Ability2" "troll_warlord_active_axes_ranged" + "Ability3" "troll_warlord_active_axes_melee" + "Ability4" "troll_warlord_fervor_custom" + "Ability5" "generic_hidden" + "Ability6" "troll_warlord_battle_trance_custom" + "Ability10" "special_bonus_unique_troll_warlord_2_1" + "Ability11" "special_bonus_unique_troll_warlord_1_1" + "Ability12" "special_bonus_unique_troll_warlord_battle_trance_movespeed" + "Ability13" "special_bonus_unique_troll_warlord_1_3" + "Ability14" "special_bonus_unique_troll_warlord_5" + "Ability15" "special_bonus_unique_troll_warlord_4_1" + "Ability16" "special_bonus_unique_troll_warlord_1_2" + "Ability17" "special_bonus_unique_troll_warlord_1_4" + + "Facets" "" + + "GameSoundsFile" "soundevents/game_sounds_heroes/game_sounds_troll_warlord.vsndevts" + "VoiceFile" "soundevents/voscripts/game_sounds_vo_troll_warlord.vsndevts" + "particle_folder" "particles/units/heroes/hero_troll_warlord" + } + + "npc_dota_hero_keeper_of_the_light" + { + "AttributePrimary" "DOTA_ATTRIBUTE_INTELLECT" + "Ability1" "keeper_of_the_light_illuminate_custom" + "Ability2" "keeper_of_the_light_blinding_light_custom" + "Ability3" "keeper_of_the_light_chakra_magic_custom" + "Ability4" "keeper_of_the_light_recall_custom" + "Ability5" "keeper_of_the_light_radiant_bind_custom" + "Ability6" "keeper_of_the_light_will_o_wisp_custom" + "Ability7" "keeper_of_the_light_illuminate_end" + "Ability8" "keeper_of_the_light_special_reserve" + "Ability9" "generic_hidden" + "Ability10" "special_bonus_unique_keeper_of_the_light_4_1" + "Ability11" "special_bonus_unique_keeper_of_the_light_1_1" + "Ability12" "special_bonus_unique_keeper_of_the_light_7" + "Ability13" "special_bonus_unique_keeper_of_the_light_5" + "Ability14" "special_bonus_unique_keeper_of_the_light_3_1" + "Ability15" "special_bonus_unique_keeper_of_the_light_14" + "Ability16" "special_bonus_unique_keeper_of_the_light" + "Ability17" "special_bonus_unique_keeper_of_the_light_4_2" + + "Facets" "" + } + + "npc_dota_hero_bristleback" + { + "AttributePrimary" "DOTA_ATTRIBUTE_STRENGTH" + "Ability1" "bristleback_viscous_nasal_goo_custom" + "Ability2" "bristleback_quill_spray_custom" + "Ability3" "bristleback_bristleback_custom" + "Ability4" "bristleback_hairball_custom" + "Ability5" "generic_hidden" + "Ability6" "bristleback_rage_fortitude_custom" + "Ability7" "generic_hidden" + "Ability8" "bristleback_warpath" + "Ability10" "special_bonus_movement_speed_25" + "Ability11" "special_bonus_attack_speed_25" + "Ability12" "special_bonus_unique_bristleback_custom_3" + "Ability13" "special_bonus_armor_7" + "Ability14" "special_bonus_unique_bristleback_custom_5" + "Ability15" "special_bonus_unique_bristleback_custom_6" + "Ability16" "special_bonus_unique_bristleback_3" + "Ability17" "special_bonus_attack_speed_40" + + "Facets" "" + + "GameSoundsFile" "soundevents/game_sounds_heroes/game_sounds_bristleback.vsndevts" + "VoiceFile" "soundevents/voscripts/game_sounds_vo_bristleback.vsndevts" + "particle_folder" "particles/units/heroes/hero_bristleback" + } + + "npc_dota_hero_ogre_magi" + { + "Ability1" "ability_ogre_magi_fireblast_custom" + "Ability2" "ability_ogre_magi_ignite_custom" + "Ability3" "ability_ogre_magi_bloodlust_custom" + "Ability4" "ability_ogre_magi_unrefined_fireblast_custom" + "Ability5" "generic_hidden" + "Ability6" "ability_ogre_magi_multicast_custom" + "Ability7" "ability_ogre_magi_innate_custom" + "Ability8" "generic_hidden" + "Ability10" "special_bonus_unique_ogre_magi_fireblast_damage" + "Ability11" "special_bonus_unique_ogre_magi_ignite_damage" + "Ability12" "special_bonus_unique_ogre_magi_bloodlust_as" + "Ability13" "special_bonus_unique_ogre_magi_fireblast_attack_proc" + "Ability14" "special_bonus_unique_ogre_magi_ignite_stacks" + "Ability15" "special_bonus_unique_ogre_magi_ignite_proc_damage" + "Ability16" "special_bonus_unique_ogre_magi_ignite_on_cast" + "Ability17" "special_bonus_unique_ogre_magi_luck" + + "Facets" "" + + "GameSoundsFile" "soundevents/game_sounds_heroes/game_sounds_ogre_magi.vsndevts" + "VoiceFile" "soundevents/voscripts/game_sounds_vo_ogre_magi.vsndevts" + "particle_folder" "particles/units/heroes/hero_ogre_magi" + } + + "npc_dota_hero_spectre" + { + "Ability1" "ability_spectre_spectral_dagger_custom" + "Ability2" "ability_spectre_spectral_echo_custom" + "Ability3" "ability_spectre_dispersion_custom" + "Ability4" "ability_spectre_shadow_step_custom" + "Ability5" "ability_spectre_reality_custom" + "Ability6" "ability_spectre_haunt_custom" + "Ability7" "ability_spectre_desolate_custom" + "Ability8" "generic_hidden" + + "Ability10" "special_bonus_unique_spectre_dagger_cooldown" + "Ability11" "special_bonus_unique_spectre_echo_proc_chance" + "Ability12" "special_bonus_unique_spectre_spectral_echo_twin" + "Ability13" "special_bonus_unique_spectre_echo_double_strike" + "Ability14" "special_bonus_unique_spectre_dispersion_pct" + "Ability15" "special_bonus_unique_spectre_echo_shadow_damage" + "Ability16" "special_bonus_unique_spectre_desolate_hp_pct" + "Ability17" "special_bonus_unique_spectre_reality_all_haunts" + + "Facets" "" + + "ArmorPhysical" "0" + "AttackCapabilities" "DOTA_UNIT_CAP_MELEE_ATTACK" + "AttackDamageMin" "23" + "AttackDamageMax" "27" + "AttackRate" "1.800000" + "AttackRange" "150" + "AttributePrimary" "DOTA_ATTRIBUTE_AGILITY" + "AttributeBaseStrength" "21" + "AttributeStrengthGain" "2.000000" + "AttributeBaseAgility" "22" + "AttributeAgilityGain" "2.000000" + "AttributeBaseIntelligence" "16" + "AttributeIntelligenceGain" "1.700000" + "MovementSpeed" "290" + + "GameSoundsFile" "soundevents/game_sounds_heroes/game_sounds_spectre.vsndevts" + "VoiceFile" "soundevents/voscripts/game_sounds_vo_spectre.vsndevts" + "particle_folder" "particles/units/heroes/hero_spectre" + } + + "npc_dota_hero_hoodwink" + { + "Ability1" "hoodwink_acorn_shot_custom" + "Ability2" "hoodwink_bushwhack_custom" + "Ability3" "hoodwink_scurry_custom" + "Ability4" "generic_hidden" + "Ability5" "hoodwink_hunters_boomerang" + "Ability6" "hoodwink_sharpshooter_custom" + "Ability7" "hoodwink_sharpshooter_release_custom" + "Ability8" "hoodwink_lucky_innate_custom" + "Ability9" "generic_hidden" + "Ability10" "special_bonus_unique_hoodwink_scurry_charges" + "Ability11" "special_bonus_mp_regen_250" + "Ability12" "special_bonus_unique_hoodwink_bushwhack_damage" + "Ability13" "special_bonus_unique_hoodwink_1_1" + "Ability14" "special_bonus_unique_hoodwink_4_1" + "Ability15" "special_bonus_unique_hoodwink_acorn_shot_bounces" + "Ability16" "special_bonus_unique_hoodwink_3_1" + "Ability17" "special_bonus_unique_hoodwink_sharpshooter_damage" + "ArmorPhysical" "0" + "AttackCapabilities" "DOTA_UNIT_CAP_RANGED_ATTACK" + "AttackDamageMin" "98" + "AttackDamageMax" "105" + "AttackRate" "1.700000" + "AttackRange" "575" + "AttributePrimary" "DOTA_ATTRIBUTE_AGILITY" + "AttributeBaseStrength" "20" + "AttributeStrengthGain" "2.0" + "AttributeBaseAgility" "24" + "AttributeAgilityGain" "3.6" + "AttributeBaseIntelligence" "21" + "AttributeIntelligenceGain" "2.9" + "MovementSpeed" "310" + } + +} \ No newline at end of file diff --git a/scripts/npc/npc_items_custom.txt b/scripts/npc/npc_items_custom.txt new file mode 100644 index 0000000..58ed118 --- /dev/null +++ b/scripts/npc/npc_items_custom.txt @@ -0,0 +1,2516 @@ +#base "items/removed_items.kv" +#base "items/custom_items.kv" +#base "items/util_items.kv" +#base "items/quest_items.kv" +#base "items/blackshop/common.kv" +#base "items/blackshop/cursed.kv" +#base "items/blackshop/epic.kv" +#base "items/blackshop/heavenly.kv" +#base "items/blackshop/legendary.kv" +#base "items/blackshop/rare.kv" + + + + + + + +"DOTAAbilities" +{ + "item_madstone_bundle" + { + // General + //------------------------------------------------------------------------------------------------------------- + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_NO_TARGET | DOTA_ABILITY_BEHAVIOR_DONT_CANCEL_MOVEMENT | DOTA_ABILITY_BEHAVIOR_IMMEDIATE | DOTA_ABILITY_BEHAVIOR_IGNORE_CHANNEL" + "Model" "models/props_gameplay/crystal_shards/crystal_shards.vmdl" + + + // Stats + //------------------------------------------------------------------------------------------------------------- + "AbilityCastRange" "100" + "AbilityCastPoint" "0.0" + + // Item Info + //------------------------------------------------------------------------------------------------------------- + "ItemCost" "0" + "ItemPurchasable" "0" + "ItemShopTags" "consumable" + "ItemQuality" "consumable" + "ItemStackable" "1" + "ItemShareability" "ITEM_FULLY_SHAREABLE" + "ItemPermanent" "0" + "ItemInitialCharges" "1" + "ItemCastOnPickup" "1" + "ItemKillable" "0" + "AutoPickup" "1" + + "AbilityValues" + { + "madstone_primary" "1" + "madstone_secondary" "1 2" + + "die_time" "20" + } + } + //================================================================================================================= + // Teleport Scroll + //================================================================================================================= + "item_tpscroll" + { + // General + //------------------------------------------------------------------------------------------------------------- + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_POINT | DOTA_ABILITY_BEHAVIOR_CHANNELLED | DOTA_ABILITY_BEHAVIOR_DONT_RESUME_ATTACK | DOTA_ABILITY_BEHAVIOR_DONT_CANCEL_CHANNEL | DOTA_ABILITY_BEHAVIOR_ROOT_DISABLES" + "AbilityUnitTargetTeam" "DOTA_UNIT_TARGET_TEAM_FRIENDLY" + "AbilityUnitTargetType" "DOTA_UNIT_TARGET_BUILDING" + "AbilityUnitTargetFlags" "DOTA_UNIT_TARGET_FLAG_INVULNERABLE" + "Model" "models/props_gameplay/tpscroll01.vmdl" + + // Stats + //------------------------------------------------------------------------------------------------------------- + "AbilityCastRange" "0" + "AbilityCooldown" "5.0" + "AbilitySharedCooldown" "teleport" + "AbilityChannelTime" "1.0" + "AbilityCastPoint" "0.0" + + // Item Info + //------------------------------------------------------------------------------------------------------------- + "AbilityManaCost" "0" + "ItemCost" "50" + "ItemShopTags" "consumable;laning;tutorial" + "ItemQuality" "consumable" + "ItemStackable" "1" + "ItemSellable" "0" + "ItemShareability" "ITEM_FULLY_SHAREABLE" + "ItemPermanent" "0" + "ItemInitialCharges" "1" + //"SideShop" "1" + "ItemPurchasable" "1" + "ShouldNotSuggestMainGame" "1" + + + // Special + //------------------------------------------------------------------------------------------------------------- + "AbilityValues" + { + "minimum_distance" "70" + "maximum_distance" "800" + "vision_radius" "200" + "tooltip_channel_time" "1.0" + } + } + //================================================================================================================= + // Enhancement: Mystical + //================================================================================================================= + "item_enhancement_mystical" + { + // General + //------------------------------------------------------------------------------------------------------------- + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_PASSIVE" + + // Item Info + //------------------------------------------------------------------------------------------------------------- + "ItemCost" "0" + "ItemIsNeutralPassiveDrop" "1" + "ItemPurchasable" "0" + "ItemSellable" "0" + "Model" "models/props_gameplay/neutral_box.vmdl" + "MaxLevel" "4" + + // Special + //------------------------------------------------------------------------------------------------------------- + "AbilityValues" + { + "mana_regen" "2.5 4.5 6.5 9" + "magic_res" "5 10 15 20" + } + } + + //================================================================================================================= + // Energy Booster + //================================================================================================================= + "item_energy_booster" + { + // General + //------------------------------------------------------------------------------------------------------------- + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_PASSIVE" + + // Item Info + //------------------------------------------------------------------------------------------------------------- + "ItemCost" "400" + "ItemShopTags" "mana_pool" + "ItemQuality" "secret_shop" + "SecretShop" "1" + //"SideShop" "1" + + // Special + //------------------------------------------------------------------------------------------------------------- + "AbilityValues" + { + "bonus_mana" "250" + } + } + //================================================================================================================= + // Void Stone + //================================================================================================================= + "item_void_stone" + { + // General + //------------------------------------------------------------------------------------------------------------- + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_PASSIVE" + + // Item Info + //------------------------------------------------------------------------------------------------------------- + "ItemCost" "300" + "ItemShopTags" "regen_mana" + "ItemQuality" "component" + "SecretShop" "1" + //"SideShop" "1" + + // Special + //------------------------------------------------------------------------------------------------------------- + "AbilityValues" + { + "bonus_mana_regen" "1.75" + } + } + //================================================================================================================= + // Diadem + //================================================================================================================= + "item_diadem" + { + // General + //------------------------------------------------------------------------------------------------------------- + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_PASSIVE" + + // Item Info + //------------------------------------------------------------------------------------------------------------- + "ItemCost" "500" + "ItemShopTags" "agi;int;str" + "ItemQuality" "component" + //"SideShop" "1" + + // Special + //------------------------------------------------------------------------------------------------------------- + "AbilityValues" + { + "bonus_all_stats" "8" + } + } + //================================================================================================================= + // Enhancement: Alert + //================================================================================================================= + "item_enhancement_alert" + { + // General + //------------------------------------------------------------------------------------------------------------- + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_PASSIVE" + + // Item Info + //------------------------------------------------------------------------------------------------------------- + "ItemCost" "0" + "ItemIsNeutralPassiveDrop" "1" + "ItemPurchasable" "0" + "ItemSellable" "0" + "Model" "models/props_gameplay/neutral_box.vmdl" + "MaxLevel" "4" + + // Special + //------------------------------------------------------------------------------------------------------------- + "AbilityValues" + { + "bonus_attack_speed" "25 40 60 80" + "bonus_night_vision" "50 150 225 300" + } + } + + + //================================================================================================================= + // Enhancement: Brawny + //================================================================================================================= + "item_enhancement_brawny" + { + // General + //------------------------------------------------------------------------------------------------------------- + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_PASSIVE" + + // Item Info + //------------------------------------------------------------------------------------------------------------- + "ItemCost" "0" + "ItemIsNeutralPassiveDrop" "1" + "ItemPurchasable" "0" + "ItemSellable" "0" + "Model" "models/props_gameplay/neutral_box.vmdl" + "MaxLevel" "4" + + // Special + //------------------------------------------------------------------------------------------------------------- + "AbilityValues" + { + "health_bonus" "200 350 400 550" + "health_regen" "2 4 8 12" + } + } + + //================================================================================================================= + // Enhancement: Tough + //================================================================================================================= + "item_enhancement_tough" + { + // General + //------------------------------------------------------------------------------------------------------------- + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_PASSIVE" + + // Item Info + //------------------------------------------------------------------------------------------------------------- + "ItemCost" "0" + "ItemIsNeutralPassiveDrop" "1" + "ItemPurchasable" "0" + "ItemSellable" "0" + "Model" "models/props_gameplay/neutral_box.vmdl" + "MaxLevel" "4" + + // Special + //------------------------------------------------------------------------------------------------------------- + "AbilityValues" + { + "bonus_damage" "10 15 20 25" + "armor" "2 4 7 10" + } + } + + //================================================================================================================= + // Enhancement: Feverish + //================================================================================================================= + "item_enhancement_feverish" + { + // General + //------------------------------------------------------------------------------------------------------------- + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_PASSIVE" + + // Item Info + //------------------------------------------------------------------------------------------------------------- + "ItemCost" "0" + "ItemIsNeutralPassiveDrop" "1" + "ItemPurchasable" "0" + "ItemSellable" "0" + "Model" "models/props_gameplay/neutral_box.vmdl" + "MaxLevel" "2" + + // Special + //------------------------------------------------------------------------------------------------------------- + "AbilityValues" + { + "cooldown_reduction" "15" + "cost_increase" "10" + } + } + + //================================================================================================================= + // Enhancement: Fleetfooted + //================================================================================================================= + "item_enhancement_fleetfooted" + { + // General + //------------------------------------------------------------------------------------------------------------- + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_PASSIVE" + + // Item Info + //------------------------------------------------------------------------------------------------------------- + "ItemCost" "0" + "ItemIsNeutralPassiveDrop" "1" + "ItemPurchasable" "0" + "ItemSellable" "0" + "Model" "models/props_gameplay/neutral_box.vmdl" + "MaxLevel" "1" + + // Special + //------------------------------------------------------------------------------------------------------------- + "AbilityValues" + { + "movespeed" "115" + } + } + + //================================================================================================================= + // Crude Enhancement + //================================================================================================================= + "item_enhancement_crude" + { + // General + //------------------------------------------------------------------------------------------------------------- + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_PASSIVE" + + // Item Info + //------------------------------------------------------------------------------------------------------------- + "ItemCost" "0" + "ItemIsNeutralPassiveDrop" "1" + "ItemPurchasable" "0" + "ItemSellable" "0" + "Model" "models/props_gameplay/neutral_box.vmdl" + "MaxLevel" "2" + + // Special + //------------------------------------------------------------------------------------------------------------- + "AbilityValues" + { + "slow_resistance" "20 30" + "bat_reduce" "12 18" + "intelligence_pct" "-25" + } + } + + //================================================================================================================= + // Enhancement: Boundless + //================================================================================================================= + "item_enhancement_boundless" + { + // General + //------------------------------------------------------------------------------------------------------------- + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_PASSIVE" + + // Item Info + //------------------------------------------------------------------------------------------------------------- + "ItemCost" "0" + "ItemIsNeutralPassiveDrop" "1" + "ItemPurchasable" "0" + "ItemSellable" "0" + "Model" "models/props_gameplay/neutral_box.vmdl" + "MaxLevel" "1" + + // Special + //------------------------------------------------------------------------------------------------------------- + "AbilityValues" + { + "bonus_attack_range" "150" + "bonus_cast_range" "350" + } + } + + //================================================================================================================= + // Enhancement: Wise + //================================================================================================================= + "item_enhancement_wise" + { + // General + //------------------------------------------------------------------------------------------------------------- + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_PASSIVE" + + // Item Info + //------------------------------------------------------------------------------------------------------------- + "ItemCost" "0" + "ItemIsNeutralPassiveDrop" "1" + "ItemPurchasable" "0" + "ItemSellable" "0" + "Model" "models/props_gameplay/neutral_box.vmdl" + "MaxLevel" "1" + + // Special + //------------------------------------------------------------------------------------------------------------- + "AbilityValues" + { + "bonus_xpm" "1000" + } + } + + //================================================================================================================= + // Timeless Enhancement + //================================================================================================================= + "item_enhancement_timeless" + { + // General + //------------------------------------------------------------------------------------------------------------- + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_PASSIVE" + + // Item Info + //------------------------------------------------------------------------------------------------------------- + "ItemCost" "0" + "ItemIsNeutralPassiveDrop" "1" + "ItemPurchasable" "0" + "ItemSellable" "0" + "MaxLevel" "2" + + "Model" "models/props_gameplay/neutral_box.vmdl" + + // Special + //------------------------------------------------------------------------------------------------------------- + "AbilityValues" + { + "debuff_amp" "15 25" + "spell_amp" "15 25" + } + } + + //================================================================================================================= + // Greedy Enhancement + //================================================================================================================= + "item_enhancement_greedy" + { + // General + //------------------------------------------------------------------------------------------------------------- + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_PASSIVE" + + // Item Info + //------------------------------------------------------------------------------------------------------------- + "ItemCost" "0" + "ItemIsNeutralPassiveDrop" "1" + "DisplayOverheadAlertOnReceived" "0" + "ItemPurchasable" "0" + "ItemSellable" "0" + "ItemInitialCharges" "750" + "ItemPermanent" "0" + "Model" "models/props_gameplay/neutral_box.vmdl" + "MaxLevel" "2" + + + // Special + //------------------------------------------------------------------------------------------------------------- + "AbilityValues" + { + "bonus_gpm" "155 300" + "bonus_mana" "200 250" + "bonus_damage" "-90 -160" + } + } + + //================================================================================================================= + // Vampiric Enhancement + //================================================================================================================= + "item_enhancement_vampiric" + { + // General + //------------------------------------------------------------------------------------------------------------- + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_PASSIVE" + + // Item Info + //------------------------------------------------------------------------------------------------------------- + "ItemCost" "0" + "ItemIsNeutralPassiveDrop" "1" + "ItemPurchasable" "0" + "ItemSellable" "0" + "MaxLevel" "3" + + "Model" "models/props_gameplay/neutral_box.vmdl" + + // Special + //------------------------------------------------------------------------------------------------------------- + "AbilityValues" + { + "attack_lifesteal" "12 14 16" + "spell_lifesteal" "8 10 12" + "attack_damage" "0" + "vision_loss" "0" + } + } + + //================================================================================================================= + // Enhancement: Keen-eyes + //================================================================================================================= + "item_enhancement_keen_eyed" + { + // General + //------------------------------------------------------------------------------------------------------------- + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_PASSIVE" + + // Item Info + //------------------------------------------------------------------------------------------------------------- + "ItemCost" "0" + "ItemIsNeutralPassiveDrop" "1" + "ItemPurchasable" "0" + "ItemSellable" "0" + "MaxLevel" "2" + + + "Model" "models/props_gameplay/neutral_box.vmdl" + + // Special + //------------------------------------------------------------------------------------------------------------- + "AbilityValues" + { + "cast_range_bonus" "125 135" + "bonus_mana_regen" "1 1.5" + "mana_reduction_pct" "15" + } + } + + //================================================================================================================= + // Evolved Enhancement + //================================================================================================================= + "item_enhancement_evolved" + { + // General + //------------------------------------------------------------------------------------------------------------- + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_PASSIVE" + + // Item Info + //------------------------------------------------------------------------------------------------------------- + "ItemCost" "0" + "ItemIsNeutralPassiveDrop" "1" + "ItemPurchasable" "0" + "ItemSellable" "0" + + + "Model" "models/props_gameplay/neutral_box.vmdl" + + // Special + //------------------------------------------------------------------------------------------------------------- + "AbilityValues" + { + "primary_stat" "40" + "primary_stat_universal" "24" + } + } + + //================================================================================================================= + // Titanic Enhancement + //================================================================================================================= + "item_enhancement_titanic" + { + // General + //------------------------------------------------------------------------------------------------------------- + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_PASSIVE" + + // Item In + //------------------------------------------------------------------------------------------------------------- + "ItemCost" "0" + "ItemIsNeutralPassiveDrop" "1" + "ItemPurchasable" "0" + "ItemSellable" "0" + "MaxLevel" "2" + + "Model" "models/props_gameplay/neutral_box.vmdl" + + // Special + //------------------------------------------------------------------------------------------------------------- + "AbilityValues" + { + "base_attack_damage" "15 25" + //"magic_resistance" "12 16" + "status_resistance" "10 15" + //"attack_speed" "-25" + } + } + + + + "item_null_talisman" + { + "ID" "77" + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_PASSIVE" + "ItemCost" "150" + "ItemShopTags" "damage;int;agi;str" + "ItemQuality" "common" + "ItemAliases" "null talisman" + "ShouldBeInitiallySuggested" "1" + "ShouldBeSuggested" "1" + "AbilityValues" + { + + "bonus_intellect" "5" + + "bonus_strength" "2" + + "bonus_agility" "2" + + "bonus_spell_amp" "3" + + "bonus_mana_regen" "0.6" + + } + } + "item_recipe_null_talisman" + { + "ID" "76" + "ItemCost" "50" + "ItemShopTags" "" + "ItemRecipe" "1" + "ItemResult" "item_null_talisman" + "ItemRequirements" + { + "01" "item_circlet;item_mantle" + } + } + "item_wraith_band" + { + "ID" "75" + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_PASSIVE" + "ItemCost" "150" + "ItemShopTags" "damage;int;agi;str" + "ItemQuality" "common" + "ItemAliases" "wraith band" + "ShouldBeInitiallySuggested" "1" + "ShouldBeSuggested" "1" + "AbilityValues" + { + + "bonus_agility" "5" + + "bonus_strength" "2" + + "bonus_intellect" "2" + + "bonus_attack_speed" "5" + + "bonus_armor" "1.5" + + } + } + "item_recipe_wraith_band" + { + "ID" "74" + "ItemCost" "50" + "ItemShopTags" "" + "ItemRecipe" "1" + "ItemResult" "item_wraith_band" + "ItemRequirements" + { + "01" "item_circlet;item_slippers" + } + } + "item_bracer" + { + "ID" "73" + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_PASSIVE" + "ItemCost" "150" + "ItemShopTags" "damage;int;agi;str" + "ItemQuality" "common" + "ItemAliases" "bracer" + "ShouldBeInitiallySuggested" "1" + "ShouldBeSuggested" "1" + "AbilityValues" + { + "bonus_strength" "5" + "bonus_agility" "2" + "bonus_intellect" "2" + "bonus_damage" "3" + "bonus_health_regen" "0.75" + } + } + "item_recipe_bracer" + { + "ID" "72" + "ItemCost" "50" + "ItemShopTags" "" + "ItemRecipe" "1" + "ItemResult" "item_bracer" + "ItemRequirements" + { + "01" "item_circlet;item_gauntlets" + } + } + + "item_gauntlets" + { + + "ID" "13" + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_PASSIVE" + "ItemCost" "75" + "ItemShopTags" "str" + "ItemQuality" "component" + "ItemAliases" "gauntlets of strength" + "ShouldBeInitiallySuggested" "1" + "AbilityValues" + { + + "bonus_strength" "3" + + } + } + "item_slippers" + { + "ID" "14" + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_PASSIVE" + "ItemCost" "75" + "ItemShopTags" "agi" + "ItemQuality" "component" + "ItemAliases" "slippers of agility" + "ShouldBeInitiallySuggested" "1" + "AbilityValues" + { + "bonus_agility" "3" + } + } + "item_mantle" + { + "ID" "15" + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_PASSIVE" + "ItemCost" "75" + "ItemShopTags" "int" + "ItemQuality" "component" + "ItemAliases" "mantle of intelligence" + "ShouldBeInitiallySuggested" "1" + "AbilityValues" + { + + "bonus_intellect" "3" + + } + } + + "item_circlet" + { + "ID" "20" + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_PASSIVE" + "ItemCost" "150" + "ItemShopTags" "agi;int;str" + "ItemQuality" "component" + "ItemAliases" "circlet" + "ShouldBeInitiallySuggested" "1" + "AbilityValues" + { + + "bonus_all_stats" "3" + } + } + + + "item_recipe_vambrace" + { + + "ID" "329" + "ItemCost" "0" + "ItemShopTags" "" + "ItemIsNeutralDrop" "0" + "ItemPurchasable" "1" + "ItemSellable" "0" + + "Model" "models/props_gameplay/neutral_box.vmdl" + "ItemRecipe" "1" + "ItemResult" "item_vambrace" + "ItemRequirements" + { + "01" "item_wraith_band;item_bracer;item_null_talisman" + } + } + "item_vambrace" + { + + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_IMMEDIATE | DOTA_ABILITY_BEHAVIOR_NO_TARGET" + "ItemCost" "450" + "ItemShopTags" "damage;int;agi;str" + "ItemQuality" "common" + "ItemAliases" "bracer" + "ItemPurchasable" "1" + "ItemIsNeutralActiveDrop" "0" + "ItemShareability" "ITEM_FULLY_SHAREABLE" + "ItemSellable" "1" + "Model" "models/props_gameplay/neutral_box.vmdl" + "AbilityValues" + { + + "bonus_primary_stat" "9" + + "bonus_secondary_stat" "7" + + "bonus_spell_amp" "7" + + "bonus_magic_resistance" "12" + + "bonus_attack_speed" "20" + + } + } + //================================================================================================================= + // Recipe: Orb of Corrosion + //================================================================================================================= + "item_recipe_orb_of_corrosion" + { + // General + //------------------------------------------------------------------------------------------------------------- + "Model" "models/props_gameplay/recipe.vmdl" + + // Item Info + //------------------------------------------------------------------------------------------------------------- + "ItemCost" "0" + "ItemShopTags" "" + + // Recipe + //------------------------------------------------------------------------------------------------------------- + "ItemRecipe" "1" + "ItemResult" "item_orb_of_corrosion" + + "ItemRequirements" + { + "01" "item_orb_of_frost;item_blight_stone;item_slippers;item_slippers" + } + } + + + + //================================================================================================================= + // Orb of Corrosion + //================================================================================================================= + "item_orb_of_corrosion" + { + // General + //------------------------------------------------------------------------------------------------------------- + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_PASSIVE" + "SpellDispellableType" "SPELL_DISPELLABLE_YES" + + "ItemCost" "1050" + "ItemShopTags" "hard_to_tag" + "ItemQuality" "rare" + "SuggestEarlygame" "1" + //"ItemDisassembleRule" "DOTA_ITEM_DISASSEMBLE_ALWAYS" + + // Special + //------------------------------------------------------------------------------------------------------------- + "AbilityValues" + { + "corruption_armor" "-6" + "slow_melee" "-8" + "slow_ranged" "-16" + "heal_reduction" "45" + "duration" "3.0" + "bonus_agility" "6" + } + } + //================================================================================================================= + // Blight Stone + //================================================================================================================= + "item_blight_stone" + { + // General + //------------------------------------------------------------------------------------------------------------- + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_PASSIVE" + "SpellDispellableType" "SPELL_DISPELLABLE_YES" + + // Item Info + //------------------------------------------------------------------------------------------------------------- + "ItemCost" "150" + "ItemShopTags" "hard_to_tag" + "ItemQuality" "component" + "SuggestPregame" "1" + "SuggestEarlygame" "1" + "SpellImmunityType" "SPELL_IMMUNITY_ENEMIES_YES" + + // Special + //------------------------------------------------------------------------------------------------------------- + "AbilityValues" + { + "corruption_armor" "-2" + "corruption_duration" "3.0" + } + } + //================================================================================================================= + // Orb of Frost + //================================================================================================================= + "item_orb_of_frost" + { + // General + //------------------------------------------------------------------------------------------------------------- + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_PASSIVE" + + "ItemCost" "150" + "ItemShopTags" "hard_to_tag" + "ItemQuality" "rare" + "SuggestPregame" "1" + "SuggestEarlygame" "1" + + // Special + //------------------------------------------------------------------------------------------------------------- + "AbilityValues" + { + //"armor" "2" + "slow_melee" "-6" + "slow_ranged" "-13" + //"damage" "0" + "duration" "3" + //"attack_speed" "0" + "heal_reduction" "25" + } + } + "item_aghanims_shard" + { + "ID" "609" + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_PASSIVE" + "ItemCost" "0" + "ItemPurchasable" "0" + } + + "item_aghanims_shard_roshan" + { + "ID" "725" + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_NO_TARGET" + "ItemCost" "1400" + "ItemShopTags" "" + "ItemQuality" "rare" + "ItemAliases" "" + "ItemPurchasable" "0" + "ItemSellable" "0" + "ItemKillable" "1" + "IsTempestDoubleClonable" "0" + "ItemShareability" "ITEM_FULLY_SHAREABLE" + "ItemPermanent" "0" + } + + "item_blades_of_attack" + { + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_PASSIVE" + "ItemCost" "325" + "ItemShopTags" "damage;tutorial" + "ItemQuality" "component" + "ItemAliases" "blades of attack" + "AbilityValues" + { + "bonus_damage" "9" + } + } + + "item_broadsword" + { + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_PASSIVE" + "ItemCost" "625" + "ItemShopTags" "damage" + "ItemQuality" "component" + "ItemAliases" "broadsword" + "AbilityValues" + { + "bonus_damage" "15" + } + } + + "item_chainmail" + { + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_PASSIVE" + "ItemCost" "200" + "ItemShopTags" "armor" + "ItemQuality" "component" + "ItemAliases" "chainmail" + "AbilityValues" + { + "bonus_armor" "4" + } + } + + "item_claymore" + { + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_PASSIVE" + "ItemCost" "875" + "ItemShopTags" "damage" + "ItemQuality" "component" + "ItemAliases" "claymore" + "AbilityValues" + { + "bonus_damage" "20" + } + } + "item_branches" + { + // General + //------------------------------------------------------------------------------------------------------------- + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_PASSIVE" + "Model" "models/props_gameplay/branch.vmdl" + + // Stats + //------------------------------------------------------------------------------------------------------------- + "AbilityCastRange" "400" + "AbilityCastPoint" "0.0" + "AbilityCooldown" "0.0" + + + // Item Info + //------------------------------------------------------------------------------------------------------------- + "ItemCost" "50" + "ItemShopTags" "agi;int;str" + "ItemQuality" "consumable" + "ItemAliases" "gg branch;iron branch" + "SuggestPregame" "1" + + // Special + //------------------------------------------------------------------------------------------------------------- + "AbilityValues" + { + "bonus_all_stats" "1" + "tree_duration" "20" + } + } + "item_helm_of_iron_will" + { + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_PASSIVE" + "ItemCost" "450" + "ItemShopTags" "armor;regen_health" + "ItemQuality" "component" + "ItemAliases" "helm of iron will" + "AbilityValues" + { + "bonus_armor" "4" + "bonus_regen" "4" + } + } + "item_recipe_butterfly" + { + // General + //------------------------------------------------------------------------------------------------------------- + + // Item Info + //------------------------------------------------------------------------------------------------------------- + "ItemCost" "0" + "ItemShopTags" "" + + // Recipe + //------------------------------------------------------------------------------------------------------------- + "ItemRecipe" "1" + "ItemResult" "item_butterfly" + "ItemRequirements" + { + "01" "item_eagle;item_talisman_of_evasion;item_claymore" + } + } + "item_butterfly" + { + // General + //------------------------------------------------------------------------------------------------------------- + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_PASSIVE" + //"AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_IMMEDIATE | DOTA_ABILITY_BEHAVIOR_NO_TARGET" + + // Item Info + //------------------------------------------------------------------------------------------------------------- + "ItemCost" "5450" + "ItemShopTags" "agi;damage;evasion;attack_speed;dps;tank" + "ItemQuality" "epic" + "ItemDeclarations" "DECLARE_PURCHASES_TO_TEAMMATES | DECLARE_PURCHASES_IN_SPEECH | DECLARE_PURCHASES_TO_SPECTATORS" + "SuggestLategame" "1" + + // Special + //------------------------------------------------------------------------------------------------------------- + "AbilityValues" + { + "bonus_agility" "35" + "bonus_evasion" "9" + "bonus_attack_speed_pct" "20" + "bonus_damage" "25" + } + } + "item_recipe_king_fly" + { + // General + //------------------------------------------------------------------------------------------------------------- + "BaseClass" "item_datadriven" + // Item Info + //------------------------------------------------------------------------------------------------------------- + "ItemCost" "0" + "ItemShopTags" "" + + // Recipe + //------------------------------------------------------------------------------------------------------------- + "ItemRecipe" "1" + "ItemResult" "item_king_fly" + "ItemRequirements" + { + "01" "item_butterfly;item_butterfly;item_butterfly" + } + } + "item_king_fly" + { + "BaseClass" "item_butterfly" + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_PASSIVE" + //"AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_IMMEDIATE | DOTA_ABILITY_BEHAVIOR_NO_TARGET" + "AbilityTextureName" "default_items/king_fly" + // Item Info + //------------------------------------------------------------------------------------------------------------- + "ItemCost" "5450" + "ItemShopTags" "agi;damage;evasion;attack_speed;dps;tank" + "ItemQuality" "epic" + "ItemDeclarations" "DECLARE_PURCHASES_TO_TEAMMATES | DECLARE_PURCHASES_IN_SPEECH | DECLARE_PURCHASES_TO_SPECTATORS" + "SuggestLategame" "1" + + // Special + //------------------------------------------------------------------------------------------------------------- + "AbilityValues" + { + "bonus_agility" "105" + "bonus_evasion" "27" + "bonus_attack_speed_pct" "60" + "bonus_damage" "75" + } + } + "item_gem" + { + // General + //------------------------------------------------------------------------------------------------------------- + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_POINT | DOTA_ABILITY_BEHAVIOR_AOE" + "AbilityCastRange" "300" + "Model" "models/props_gameplay/gem01.vmdl" + "Effect" "particles/generic_gameplay/dropped_gem.vpcf" + "AbilityCooldown" "12" + + // Item Info + //------------------------------------------------------------------------------------------------------------- + "ItemCost" "200" + "ItemShopTags" "see_invis" + "ItemQuality" "component" + "ItemAliases" "gem of true sight" + "ItemSellable" "0" + "ItemInitiallySellable" "1" + "ItemShareability" "ITEM_FULLY_SHAREABLE" + "ItemDeclarations" "DECLARE_PURCHASES_TO_TEAMMATES | DECLARE_PURCHASES_TO_SPECTATORS" + "ItemStockMax" "1" + "ItemStockTime" "1600.0" + "ItemSupport" "1" + "ItemKillable" "0" + "ItemContributesToNetWorthWhenDropped" "0" + "AllowedInBackpack" "0" + "IsTempestDoubleClonable" "0" + "SuggestEarlygame" "1" + "SpeciallyBannedFromNeutralSlot" "1" + + + // Sound + //------------------------------------------------------------------------------------------------------------- + "UIPickupSound" "Item.PickUpGemShop" + "UIDropSound" "Item.DropGemShop" + "WorldDropSound" "Item.DropGemWorld" + + // Special + //------------------------------------------------------------------------------------------------------------- + "AbilityValues" + { + "radius" + { + "value" "900" + "affected_by_aoe_increase" "1" + } + "active_radius" + { + "value" "300" + "affected_by_aoe_increase" "1" + } + "duration" "4" + } + } + + "item_javelin" + { + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_PASSIVE" + "ItemCost" "400" + "ItemShopTags" "damage" + "ItemQuality" "component" + "ItemAliases" "javelin" + "AbilityValues" + { + "bonus_chance" "33" + "bonus_chance_damage" "45" + } + } + + "item_mithril_hammer" + { + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_PASSIVE" + "ItemCost" "900" + "ItemShopTags" "damage" + "ItemQuality" "component" + "ItemAliases" "mithril hammer" + "AbilityValues" + { + "bonus_damage" "24" + } + } + + "item_platemail" + { + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_PASSIVE" + "ItemCost" "1400" + "ItemShopTags" "armor" + "ItemQuality" "secret_shop" + "ItemAliases" "platemail" + "AbilityValues" + { + "bonus_armor" "10" + } + } + + "item_quarterstaff" + { + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_PASSIVE" + "ItemCost" "325" + "ItemShopTags" "damage;attack_speed" + "ItemQuality" "component" + "ItemAliases" "quarterstaff" + "IsObsolete" "1" + "AbilityValues" + { + "bonus_speed" "16" + "bonus_damage" "14" + } + } + + "item_quelling_blade" + { + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_UNIT_TARGET" + "AbilityUnitTargetType" "DOTA_UNIT_TARGET_TREE | DOTA_UNIT_TARGET_CUSTOM" + "AbilityUnitTargetTeam" "DOTA_UNIT_TARGET_TEAM_CUSTOM" + "AbilityCastRange" "350" + "AbilityCastPoint" "0.0" + "AbilityCooldown" "4.0" + "Model" "models/props_gameplay/quelling_blade.vmdl" + "AbilityManaCost" "0" + "ItemCost" "75" + "ItemShopTags" "damage" + "ItemQuality" "component" + "ItemAliases" "qb;quelling blade" + "SuggestPregame" "1" + "AbilityValues" + { + "damage_bonus" "8" + "damage_bonus_ranged" "4" + "quelling_range_tooltip" "350" + } + } + "item_wind_lace" + { + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_PASSIVE" + "ItemCost" "150" + "ItemShopTags" "armor" + "ItemQuality" "component" + "ItemAliases" "wind lace" + "ItemPermanent" "1" + "SuggestPregame" "1" + "UIPickupSound" "Item.PickUpRingShop" + "UIDropSound" "Item.DropRingShop" + "WorldDropSound" "Item.DropRingWorld" + "AbilityValues" + { + "movement_speed" "20" + } + } + + "item_ring_of_protection" + { + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_PASSIVE" + "ItemCost" "175" + "ItemShopTags" "armor" + "ItemQuality" "component" + "ItemAliases" "rop;ring of protection" + "SuggestPregame" "1" + "UIPickupSound" "Item.PickUpRingShop" + "UIDropSound" "Item.DropRingShop" + "WorldDropSound" "Item.DropRingWorld" + "AbilityValues" + { + "bonus_armor" "2" + } + } + + "item_stout_shield" + { + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_PASSIVE" + "Model" "models/props_gameplay/stout_shield.vmdl" + "ItemCost" "200" + "ItemShopTags" "block" + "ItemQuality" "component" + "ItemAliases" "stout shield" + "ItemPurchasable" "1" + "IsObsolete" "1" + "AbilityValues" + { + "damage_block_melee" "16" + "damage_block_ranged" "8" + "block_chance" "50" + } + } + + "item_recipe_moon_shard" + { + "Model" "models/props_gameplay/recipe.vmdl" + "ItemCost" "0" + "ItemRecipe" "1" + "ItemResult" "item_moon_shard" + "ItemRequirements" + { + "01" "item_hyperstone;item_hyperstone" + } + } + + "item_moon_shard" + { + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_UNIT_TARGET | DOTA_ABILITY_BEHAVIOR_IMMEDIATE" + "AbilityUnitTargetTeam" "DOTA_UNIT_TARGET_TEAM_FRIENDLY" + "AbilityUnitTargetType" "DOTA_UNIT_TARGET_HERO | DOTA_UNIT_TARGET_CUSTOM" + "AbilityUnitTargetFlags" "DOTA_UNIT_TARGET_FLAG_INVULNERABLE | DOTA_UNIT_TARGET_FLAG_OUT_OF_WORLD" + "ItemCost" "4000" + "ItemShopTags" "attack_speed" + "ItemQuality" "consumable" + "ItemAliases" "moon shard" + "SuggestLategame" "1" + "AbilityValues" + { + "bonus_attack_speed" "35" + "bonus_night_vision" "400" + "consumed_bonus" "70" + "consumed_bonus_night_vision" "200" + } + } + //================================================================================================================= + // Recipe: Trident + //================================================================================================================= + "item_recipe_trident" + { + // General + //------------------------------------------------------------------------------------------------------------- + + // Item Info + //------------------------------------------------------------------------------------------------------------- + "ItemCost" "0" + "ItemShopTags" "" + "ItemIsNeutralActiveDrop" "0" + "ItemPurchasable" "0" + "ItemSellable" "2500" + + "Model" "models/props_gameplay/neutral_box.vmdl" + + // Recipe + //------------------------------------------------------------------------------------------------------------- + "ItemRecipe" "1" + "ItemResult" "item_trident" + "ItemRequirements" + { + "01" "item_kaya;item_sange;item_yasha;" + "02" "item_kaya_and_sange;item_yasha;" + "03" "item_sange_and_yasha;item_kaya;" + "04" "item_yasha_and_kaya;item_sange;" + } + } + "item_trident" + { + // General + //------------------------------------------------------------------------------------------------------------- + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_PASSIVE" + + // Item Info + //------------------------------------------------------------------------------------------------------------- + "ItemCost" "8800" + + "ItemPurchasable" "1" + "ItemSellable" "1" + "ItemIsNeutralActiveDrop" "0" + "Model" "models/props_gameplay/neutral_box.vmdl" + + // Special + //------------------------------------------------------------------------------------------------------------- + "AbilityValues" + { + "bonus_strength" "30" + "bonus_agility" "30" + "bonus_intellect" "30" + "status_resistance" "30" + "bonus_attack_speed" "30" + "movement_speed_percent_bonus" "16" + "hp_regen_amp" "30" + "mana_regen_multiplier" "30" + "spell_amp" "30" + "magic_damage_attack" "30" + } + } + + //================================================================================================================= + // Eagle Horn + //================================================================================================================= + "item_eagle" + { + // General + //------------------------------------------------------------------------------------------------------------- + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_PASSIVE" + + // Item Info + //------------------------------------------------------------------------------------------------------------- + "ItemCost" "1400" + "ItemShopTags" "agi" + "ItemQuality" "secret_shop" + "SecretShop" "1" + + // Special + //------------------------------------------------------------------------------------------------------------- + "AbilityValues" + { + "bonus_agility" "25" + } + } + + //================================================================================================================= + // Reaver + //================================================================================================================= + "item_reaver" + { + // General + //------------------------------------------------------------------------------------------------------------- + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_PASSIVE" + + // Item Info + //------------------------------------------------------------------------------------------------------------- + "ItemCost" "1400" + "ItemShopTags" "str" + "ItemQuality" "secret_shop" + "SecretShop" "1" + + // Special + //------------------------------------------------------------------------------------------------------------- + "AbilityValues" + { + "bonus_strength" "25" + } + } + //================================================================================================================= + // Mystic Staff + //================================================================================================================= + "item_mystic_staff" + { + // General + //------------------------------------------------------------------------------------------------------------- + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_PASSIVE" + + // Item Info + //------------------------------------------------------------------------------------------------------------- + "ItemCost" "1400" + "ItemShopTags" "int" + "ItemQuality" "secret_shop" + "SecretShop" "1" + + // Special + //------------------------------------------------------------------------------------------------------------- + "AbilityValues" + { + "bonus_intellect" "25" + } + } + //================================================================================================================= + // Point Booster + //================================================================================================================= + "item_point_booster" + { + // General + //------------------------------------------------------------------------------------------------------------- + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_PASSIVE" + + // Item Info + //------------------------------------------------------------------------------------------------------------- + "ItemCost" "600" + "ItemShopTags" "mana_pool;health_pool" + "ItemQuality" "secret_shop" + "SecretShop" "1" + + // Special + //------------------------------------------------------------------------------------------------------------- + "AbilityValues" + { + "bonus_mana" "175" + "bonus_health" "175" + } + } + "item_belt_of_strength" + { + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_PASSIVE" + "ItemCost" "450" + "ItemShopTags" "str" + "ItemQuality" "component" + "ItemAliases" "belt of strength" + "AbilityValues" + { + "bonus_strength" "6" + } + } + + "item_boots_of_elves" + { + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_PASSIVE" + "ItemCost" "450" + "ItemShopTags" "agi" + "ItemQuality" "component" + "ItemAliases" "band of elvenskin" + "AbilityValues" + { + "bonus_agility" "6" + } + } + + "item_robe" + { + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_PASSIVE" + "ItemCost" "450" + "ItemShopTags" "int" + "ItemQuality" "component" + "ItemAliases" "robe of the magi" + "AbilityValues" + { + "bonus_intellect" "6" + } + } + "item_crown" + { + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_PASSIVE" + "ItemCost" "450" + "ItemShopTags" "agi;int;str" + "ItemQuality" "component" + "ItemAliases" "crown" + "AbilityValues" + { + "bonus_all_stats" "4" + } + } + "item_demon_edge" + { + // General + //------------------------------------------------------------------------------------------------------------- + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_PASSIVE" + + // Item Info + //------------------------------------------------------------------------------------------------------------- + "ItemCost" "1950" + "ItemShopTags" "damage" + "ItemQuality" "secret_shop" + "ItemAliases" "demon edge" + "SecretShop" "1" + + // Special + //------------------------------------------------------------------------------------------------------------- + "AbilityValues" + { + "bonus_damage" "40" + } + } + "item_relic" + { + // General + //------------------------------------------------------------------------------------------------------------- + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_PASSIVE" + + // Item Info + //------------------------------------------------------------------------------------------------------------- + "ItemCost" "2750" + "ItemShopTags" "damage" + "ItemQuality" "secret_shop" + "ItemAliases" "sacred relic" + "SecretShop" "1" + + // Special + //------------------------------------------------------------------------------------------------------------- + "AbilityValues" + { + "bonus_damage" "60" + } + } + + "item_diadem" + { + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_PASSIVE" + "ItemCost" "1000" + "ItemShopTags" "agi;int;str" + "ItemQuality" "component" + "ItemAliases" "diadem" + "AbilityValues" + { + "bonus_all_stats" "6" + } + } + + "item_ogre_axe" + { + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_PASSIVE" + "ItemCost" "500" + "ItemShopTags" "str" + "ItemQuality" "component" + "ItemAliases" "ogre club" + "AbilityValues" + { + "bonus_strength" "10" + } + } + + "item_blade_of_alacrity" + { + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_PASSIVE" + "ItemCost" "500" + "ItemShopTags" "agi" + "ItemQuality" "component" + "ItemAliases" "blade of alacrity" + "AbilityValues" + { + "bonus_agility" "10" + } + } + + "item_staff_of_wizardry" + { + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_PASSIVE" + "ItemCost" "500" + "ItemShopTags" "int" + "ItemQuality" "component" + "ItemAliases" "staff of wizardry" + "AbilityValues" + { + "bonus_intellect" "10" + } + } + + "item_ultimate_orb" + { + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_PASSIVE" + "ItemCost" "2800" + "ItemShopTags" "agi;int;str" + "ItemQuality" "secret_shop" + "ItemAliases" "ultimate orb" + "AbilityValues" + { + "bonus_all_stats" "15" + } + } + + "item_gloves" + { + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_PASSIVE" + "ItemCost" "250" + "ItemShopTags" "attack_speed" + "ItemQuality" "component" + "ItemAliases" "gloves of haste" + "AbilityValues" + { + "bonus_attack_speed" "20" + } + } + + "item_blitz_knuckles" + { + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_PASSIVE" + "ItemCost" "600" + "ItemShopTags" "attack_speed" + "ItemQuality" "component" + "ItemAliases" "blitz knuckles" + "AbilityValues" + { + "bonus_attack_speed" "35" + } + } + + + + "item_ring_of_regen" + { + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_PASSIVE" + "ItemCost" "175" + "ItemShopTags" "regen_health" + "ItemQuality" "component" + "ItemAliases" "ror" + "SuggestPregame" "1" + "UIPickupSound" "Item.PickUpRingShop" + "UIDropSound" "Item.DropRingShop" + "WorldDropSound" "Item.DropRingWorld" + "AbilityValues" + { + "bonus_health_regen" "2.25" + } + } + + "item_sobi_mask" + { + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_PASSIVE" + "ItemCost" "175" + "ItemShopTags" "regen_mana" + "ItemQuality" "component" + "ItemAliases" "sage\'s mask" + "SuggestPregame" "1" + "AbilityValues" + { + "bonus_mana_regen" "1.7" + } + } + + "item_boots" + { + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_PASSIVE" + "Model" "models/props_gameplay/boots_of_speed.vmdl" + "ItemCost" "100" + "ItemShopTags" "move_speed" + "ItemQuality" "component" + "ItemAliases" "boots of speed" + "SuggestPregame" "1" + "SuggestEarlygame" "1" + "SpeciallyBannedFromNeutralSlot" "1" + "AbilityValues" + { + "bonus_movement_speed" "25" + } + } + + "item_cloak" + { + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_PASSIVE" + "ItemCost" "400" + "ItemShopTags" "magic_resist" + "ItemQuality" "component" + "ItemAliases" "cloak" + "AbilityValues" + { + "bonus_magical_armor" "12" + "tooltip_resist" "12" + } + } + "item_mage_slayer" + { + // General + //------------------------------------------------------------------------------------------------------------- + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_PASSIVE | DOTA_ABILITY_BEHAVIOR_DONT_PROC_OTHER_ABILITIES" + "AbilityUnitTargetTeam" "DOTA_UNIT_TARGET_TEAM_ENEMY" + "AbilityUnitTargetType" "DOTA_UNIT_TARGET_HERO | DOTA_UNIT_TARGET_BASIC" + "SpellDispellableType" "SPELL_DISPELLABLE_YES" + "AbilityUnitDamageType" "DAMAGE_TYPE_PHYSICAL" + + // Stats + //------------------------------------------------------------------------------------------------------------- + + // Item Info + //------------------------------------------------------------------------------------------------------------- + "ItemCost" "3100" + "ItemShopTags" "hard_to_tag;dps;magic_resist" + "ItemQuality" "rare" + "SuggestLategame" "1" + + // Special + //------------------------------------------------------------------------------------------------------------- + "AbilityValues" + { + "bonus_magical_armor" "10" + "spell_amp_debuff" "10" + "bonus_health_regen" "5.5" + "bonus_mana_regen" "2.5" + "bonus_damage" "15" + "duration" "3" + "dps" "40" + } + } + "item_talisman_of_evasion" + { + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_PASSIVE" + "ItemCost" "1300" + "ItemShopTags" "evasion" + "ItemQuality" "secret_shop" + "ItemAliases" "talisman of evasion" + "AbilityValues" + { + "bonus_evasion" "6" + } + } + "item_tiara_of_selemene" + { + // General + //------------------------------------------------------------------------------------------------------------- + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_PASSIVE" + + // Item Info + //------------------------------------------------------------------------------------------------------------- + "ItemCost" "600" + "ItemShopTags" "regen_mana" + "ItemQuality" "component" + "ItemAliases" "tiara of selemene" + + + // Special + //------------------------------------------------------------------------------------------------------------- + "AbilityValues" + { + "bonus_mana_regen" "9" + } + } + "item_hyperstone" + { + // General + //------------------------------------------------------------------------------------------------------------- + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_PASSIVE" + + // Item Info + //------------------------------------------------------------------------------------------------------------- + "ItemCost" "1000" + "ItemShopTags" "attack_speed" + "ItemQuality" "secret_shop" + "ItemAliases" "hyperstone" + + // Special + //------------------------------------------------------------------------------------------------------------- + "AbilityValues" + { + "bonus_attack_speed" "35" + } + } + "item_magic_stick" + { + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_IMMEDIATE | DOTA_ABILITY_BEHAVIOR_NO_TARGET" + "Model" "models/props_gameplay/magic_wand.vmdl" + "AbilityCooldown" "17.0" + "AbilitySharedCooldown" "magicwand" + "AbilityCastRange" "1200" + "ItemCost" "100" + "ItemShopTags" "regen_health;regen_mana;boost_health;boost_mana" + "ItemQuality" "component" + "ItemAliases" "magic stick" + "ItemRequiresCharges" "1" + "ItemDisplayCharges" "1" + "SuggestPregame" "1" + "AbilityValues" + { + "max_charges" "14" + "charge_radius" + { + "value" "1200" + "affected_by_aoe_increase" "1" + } + "restore_per_charge" "12" + } + } + + + "item_recipe_magic_wand" + { + "Model" "models/props_gameplay/recipe.vmdl" + "ItemCost" "150" + "ItemShopTags" "" + "ItemRecipe" "1" + "ItemResult" "item_magic_wand" + "ItemRequirements" + { + "01" "item_magic_stick*;item_branches;item_branches" + } + } + + "item_magic_wand" + { + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_IMMEDIATE | DOTA_ABILITY_BEHAVIOR_NO_TARGET" + "Model" "models/props_gameplay/magic_wand.vmdl" + "AbilityCooldown" "15.0" + "AbilitySharedCooldown" "magicwand" + "AbilityCastRange" "1200" + "ItemCost" "450" + "ItemShopTags" "regen_health;regen_mana;boost_health;boost_mana;int;agi;str" + "ItemQuality" "common" + "ItemAliases" "magic wand" + "ItemRequiresCharges" "1" + "ItemDisplayCharges" "1" + "SuggestEarlygame" "1" + "AbilityValues" + { + "max_charges" "20" + "charge_radius" + { + "value" "1200" + "affected_by_aoe_increase" "1" + } + "bonus_all_stats" "3" + "restore_per_charge" "15" + } + } + "item_ring_of_health" + { + // General + //------------------------------------------------------------------------------------------------------------- + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_PASSIVE" + + // Item Info + //------------------------------------------------------------------------------------------------------------- + "ItemCost" "300" + "ItemShopTags" "regen_health" + "ItemQuality" "component" + "SuggestEarlygame" "1" + "SecretShop" "1" + + // Sound + //------------------------------------------------------------------------------------------------------------- + "UIPickupSound" "Item.PickUpRingShop" + "UIDropSound" "Item.DropRingShop" + "WorldDropSound" "Item.DropRingWorld" + + // Special + //------------------------------------------------------------------------------------------------------------- + "AbilityValues" + { + "bonus_health_regen" "4.5" + } + } + "item_vitality_booster" + { + // General + //------------------------------------------------------------------------------------------------------------- + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_PASSIVE" + + // Item Info + //------------------------------------------------------------------------------------------------------------- + "ItemCost" "500" + "ItemShopTags" "health_pool" + "ItemQuality" "secret_shop" + "SecretShop" "1" + //"SideShop" "1" + + // Special + //------------------------------------------------------------------------------------------------------------- + "AbilityValues" + { + "bonus_health" "125" + } + } + "item_ghost" + { + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_NO_TARGET | DOTA_ABILITY_BEHAVIOR_IMMEDIATE" + "FightRecapLevel" "1" + "AbilityCooldown" "22.0" + "AbilitySharedCooldown" "ethereal" + "SpellDispellableType" "SPELL_DISPELLABLE_YES" + "ItemCost" "1500" + "ItemShopTags" "int;agi;str;hard_to_tag" + "ItemQuality" "component" + "ItemAliases" "ghost scepter" + "SuggestLategame" "1" + "AbilityValues" + { + "bonus_all_stats" "5" + "duration" "4.0" + "extra_spell_damage_percent" "-40" + } + } + + "item_clarity" + { + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_UNIT_TARGET | DOTA_ABILITY_BEHAVIOR_IMMEDIATE | DOTA_ABILITY_BEHAVIOR_DONT_RESUME_ATTACK | DOTA_ABILITY_BEHAVIOR_SUPPRESS_ASSOCIATED_CONSUMABLE" + "AbilityUnitTargetTeam" "DOTA_UNIT_TARGET_TEAM_FRIENDLY" + "AbilityUnitTargetType" "DOTA_UNIT_TARGET_HERO" + "Model" "models/props_gameplay/clarity.vmdl" + "AbilityCastRange" "250" + "AbilityCastPoint" "0.0" + "SpellDispellableType" "SPELL_DISPELLABLE_YES" + "ItemCost" "50" + "ItemShopTags" "consumable" + "ItemQuality" "consumable" + "ItemAliases" "clarity" + "ItemStackable" "1" + "ItemPermanent" "0" + "ItemInitialCharges" "1" + "ItemStockMax" "4" + "ItemStockInitial" "4" + "ItemStockTime" "120" + "IsTempestDoubleClonable" "0" + "SuggestPregame" "1" + "SpeciallyBannedFromNeutralSlot" "1" + "AbilityValues" + { + "mana_regen" "6" + "buff_duration" "25" + } + } + + "item_enchanted_mango" + { + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_IMMEDIATE | DOTA_ABILITY_BEHAVIOR_NO_TARGET | DOTA_ABILITY_BEHAVIOR_OPTIONAL_UNIT_TARGET | DOTA_ABILITY_BEHAVIOR_DONT_RESUME_ATTACK | DOTA_ABILITY_BEHAVIOR_SUPPRESS_ASSOCIATED_CONSUMABLE" + "AbilityUnitTargetTeam" "DOTA_UNIT_TARGET_TEAM_FRIENDLY" + "AbilityUnitTargetType" "DOTA_UNIT_TARGET_HERO" + "Model" "models/props_gameplay/mango.vmdl" + "AbilityCastRange" "400" + "AbilityCastPoint" "0.0" + "ItemCost" "65" + "ItemShopTags" "consumable" + "ItemQuality" "consumable" + "ItemAliases" "enchanted mango" + "ItemStackable" "1" + "ItemStackableMax" "3" + "ItemStockMax" "4" + "ItemStockInitial" "4" + "ItemStockTime" "120" + "ItemInitialCharges" "1" + "ItemPermanent" "0" + "IsTempestDoubleClonable" "0" + "SuggestPregame" "1" + "SpeciallyBannedFromNeutralSlot" "1" + "AbilityValues" + { + "hp_regen" "0.4" + "replenish_amount" "100" + } + } + "item_bottle" + { + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_IMMEDIATE | DOTA_ABILITY_BEHAVIOR_NO_TARGET | DOTA_ABILITY_BEHAVIOR_OPTIONAL_UNIT_TARGET | DOTA_ABILITY_BEHAVIOR_SUPPRESS_ASSOCIATED_CONSUMABLE" + "AbilityUnitTargetTeam" "DOTA_UNIT_TARGET_TEAM_FRIENDLY" + "AbilityUnitTargetType" "DOTA_UNIT_TARGET_HERO" + "Model" "models/props_gameplay/bottle_blue.vmdl" + "ModelAlternate" "models/props_gameplay/bottle_empty.vmdl" + "AbilityCooldown" "0.5" + "AbilityCastRange" "350" + "AbilityCastPoint" "0.0" + "ItemCost" "675" + "ItemQuality" "common" + "ItemAliases" "bottle" + "ItemStackable" "0" + "ItemShareability" "ITEM_PARTIALLY_SHAREABLE" + "ItemPermanent" "1" + "ItemInitialCharges" "3" + "ItemDisplayCharges" "1" + "AbilityValues" + { + "health_restore" "110" + "mana_restore" "60" + "restore_time" "2.7" + "max_charges" "3" + } + } + "item_recipe_force_staff" + { + // General + //------------------------------------------------------------------------------------------------------------- + "Model" "models/props_gameplay/recipe.vmdl" + + // Item Info + //------------------------------------------------------------------------------------------------------------- + "ItemCost" "950" + "ItemShopTags" "" + + // Recipe + //------------------------------------------------------------------------------------------------------------- + "ItemRecipe" "1" + "ItemResult" "item_force_staff" + "ItemRequirements" + { + "01" "item_staff_of_wizardry;item_fluffy_hat" + } + } + + //================================================================================================================= + // Force Staff + //================================================================================================================= + "item_force_staff" + { + // General + //------------------------------------------------------------------------------------------------------------- + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_PASSIVE" + "FightRecapLevel" "1" + + // Stats + //------------------------------------------------------------------------------------------------------------- + "AbilityCastRange" "550" + "AbilityCastPoint" "0.0" + "AbilityCooldown" "19.0" + "AbilitySharedCooldown" "force" + + // Item Info + //------------------------------------------------------------------------------------------------------------- + "AbilityManaCost" "150" + "ItemCost" "2200" + "ItemShopTags" "int;damage;attack_speed;hard_to_tag" + "ItemQuality" "rare" + "ItemAliases" "fs;force staff" + "ItemDeclarations" "DECLARE_PURCHASES_TO_TEAMMATES | DECLARE_PURCHASES_TO_SPECTATORS" + "SuggestLategame" "1" + + // Special + //------------------------------------------------------------------------------------------------------------- + "AbilityValues" + { + "bonus_intellect" "10" + "bonus_health" "175" + "push_length" "600" + "enemy_cast_range" "850" + "push_time" "0.5" + } + } + + //================================================================================================================= + // Recipe: Hurricane Pike + //================================================================================================================= + "item_recipe_hurricane_pike" + { + // General + //------------------------------------------------------------------------------------------------------------- + "Model" "models/props_gameplay/recipe.vmdl" + + // Item Info + //------------------------------------------------------------------------------------------------------------- + "ItemCost" "350" + "ItemShopTags" "" + + // Recipe + //------------------------------------------------------------------------------------------------------------- + "ItemRecipe" "1" + "ItemResult" "item_hurricane_pike" + "ItemRequirements" + { + "01" "item_force_staff*;item_dragon_lance" + } + } + //================================================================================================================= + // Recipe: Specialists Array + //================================================================================================================= + "item_recipe_specialists_array" + { + // General + //------------------------------------------------------------------------------------------------------------- + "Model" "models/props_gameplay/recipe.vmdl" + + // Item Info + //------------------------------------------------------------------------------------------------------------- + "ItemCost" "550" + "ItemShopTags" "" + + // Recipe + //------------------------------------------------------------------------------------------------------------- + "ItemRecipe" "1" + "ItemResult" "item_specialists_array" + "ItemRequirements" + { + "01" "item_blade_of_alacrity;item_broadsword" + } + } + + + //================================================================================================================= + // Specialist's Array + //================================================================================================================= + "item_specialists_array" + { + // General + //------------------------------------------------------------------------------------------------------------- + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_PASSIVE" + // Stats + //------------------------------------------------------------------------------------------------------------- + "AbilityCooldown" "0.0" + + // Item Info + //------------------------------------------------------------------------------------------------------------- + "AbilityManaCost" "0" + "ItemCost" "2550" + "ItemShopTags" "int;damage;hard_to_tag;mobility;escape;save" + "ItemQuality" "rare" + "ItemDeclarations" "DECLARE_PURCHASES_TO_TEAMMATES | DECLARE_PURCHASES_TO_SPECTATORS" + "SuggestLategame" "1" + + + // Special + //------------------------------------------------------------------------------------------------------------- + "AbilityValues" + { + "all_stats" "0" + "agility" "12" + "damage" "20" + "count" "1" + "secondary_target_range_bonus" "150" + "secondary_target_angle" "120" + "proc_chance" "30" + "base_proc_dmg" "20" + "proc_dmg_pct" "75" + "proc_dmg_pct_primary_tooltip" "100" + } + } + //================================================================================================================= + // Recipe: Hydra's Breath + //================================================================================================================= + "item_recipe_hydras_breath" + { + // General + //------------------------------------------------------------------------------------------------------------- + "Model" "models/props_gameplay/recipe.vmdl" + + // Item Info + //------------------------------------------------------------------------------------------------------------- + "ItemCost" "1100" + "ItemShopTags" "" + "ItemPurchasable" "1" + + // Recipe + //------------------------------------------------------------------------------------------------------------- + "ItemRecipe" "1" + "ItemResult" "item_hydras_breath" + "ItemRequirements" + { + "01" "item_bearstbow;item_orb_of_venom;" + } + } + //================================================================================================================= + // Black King Bar + //================================================================================================================= + "item_black_king_bar" + { + // General + //------------------------------------------------------------------------------------------------------------- + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_IMMEDIATE | DOTA_ABILITY_BEHAVIOR_NO_TARGET" + "FightRecapLevel" "2" + + // Stats + //------------------------------------------------------------------------------------------------------------- + "AbilityCooldown" "18" + "AbilityManaCost" "25" + + // Item Info + //------------------------------------------------------------------------------------------------------------- + "ItemCost" "4050" + "ItemShopTags" "str;damage;hard_to_tag;teamfight;escape;tank" + "ItemQuality" "epic" + "ItemSellable" "1" + "ItemDeclarations" "DECLARE_PURCHASES_TO_TEAMMATES | DECLARE_PURCHASES_IN_SPEECH | DECLARE_PURCHASES_TO_SPECTATORS" + "SuggestLategame" "1" + + // Special + //------------------------------------------------------------------------------------------------------------- + "AbilityValues" + { + "bonus_strength" "10" + "bonus_damage" "24" + "duration" "8" + "max_level" "3" + "model_scale" "50" // Percentage over model scale + "spell_reduce" "100" + } + } + + //================================================================================================================= + // Hydra's Breath + //================================================================================================================= + "item_hydras_breath" + { + // General + //------------------------------------------------------------------------------------------------------------- + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_PASSIVE" + + // Stats + //------------------------------------------------------------------------------------------------------------- + "AbilityCooldown" "0" + + "AbilityCastRange" "0" + + // Item Info + //------------------------------------------------------------------------------------------------------------- + "AbilityManaCost" "0" + "ItemCost" "5900" + "ItemShopTags" "agi;damage;attack_speed;hard_to_tag;" + "ItemPurchasable" "1" + "ItemQuality" "rare" + "ItemDeclarations" "DECLARE_PURCHASES_TO_TEAMMATES | DECLARE_PURCHASES_TO_SPECTATORS" + "SuggestLategame" "1" + + + // Special + //------------------------------------------------------------------------------------------------------------- + "AbilityValues" + { + "strength" "15" + "agility" "10" + "damage" "45" + "base_count" "0" + "count" "3" + "secondary_target_range_bonus" "150" + "secondary_target_angle" "120" + "proc_chance" "30" + "proc_dmg_pct" "75" + "proc_dmg_pct_primary_tooltip" "100" + "base_proc_dmg" "20" + "base_attack_range" "150" + + "poison_duration" "3" + "poison_base_damage" "0" + "poison_damage_per_second" "0.15" + } + } + + //================================================================================================================= + // Hurricane Pike + //================================================================================================================= + "item_hurricane_pike" + { + // General + //------------------------------------------------------------------------------------------------------------- + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_PASSIVE" + "FightRecapLevel" "1" + + // Stats + //------------------------------------------------------------------------------------------------------------- + "AbilityCastRange" "650" + "AbilityCastPoint" "0.0" + "AbilityCooldown" "19.0" + "AbilitySharedCooldown" "force" + + // Item Info + //------------------------------------------------------------------------------------------------------------- + "AbilityManaCost" "150" + "ItemCost" "4450" + "ItemShopTags" "int;damage;attack_speed;hard_to_tag" + "ItemQuality" "epic" + "ItemAliases" "fs;force staff" + "ItemDeclarations" "DECLARE_PURCHASES_TO_TEAMMATES | DECLARE_PURCHASES_TO_SPECTATORS" + "SuggestLategame" "1" + + // Special + //------------------------------------------------------------------------------------------------------------- + "AbilityValues" + { + "bonus_intellect" "15" + "bonus_health" "200" + "bonus_agility" "20" + "bonus_strength" "15" + "base_attack_range" "150" + "push_length" "600" + "enemy_length" "450" + "range_duration" "6" + "cast_range_enemy" "450" + "max_attacks" "5" + "bonus_attack_speed" "100" + "push_time" "0.5" + "dizzy_duration" "0" + "dizzy_distance_pct" "0" + + } + } + // neutral items + + + "item_enhancement_mystical" + { + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_PASSIVE" + "ItemIsNeutralPassiveDrop" "1" + "MaxLevel" "4" + "AbilityValues" + { + "mana_regen" "2 3.5 5 7.5" + "magic_res" "4 8 12 16" + } + } + + "item_enhancement_alert" + { + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_PASSIVE" + "ItemIsNeutralPassiveDrop" "1" + "MaxLevel" "4" + "AbilityValues" + { + "bonus_attack_speed" "20 35 50 70" + "bonus_night_vision" "75 150 225 300" + } + } + + "item_enhancement_brawny" + { + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_PASSIVE" + "ItemIsNeutralPassiveDrop" "1" + "MaxLevel" "4" + "AbilityValues" + { + "health_bonus" "175 300 450 650" + "health_regen" "2 4 6 10" + } + } + + "item_enhancement_tough" + { + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_PASSIVE" + "ItemIsNeutralPassiveDrop" "1" + "MaxLevel" "4" + "AbilityValues" + { + "bonus_damage" "8 14 20 28" + "armor" "2 3 5 8" + } + } + + "item_enhancement_feverish" + { + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_PASSIVE" + "ItemIsNeutralPassiveDrop" "1" + "MaxLevel" "2" + "AbilityValues" + { + "cooldown_reduction" "12" + "cost_increase" "12" + } + } + + "item_enhancement_fleetfooted" + { + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_PASSIVE" + "ItemIsNeutralPassiveDrop" "1" + "MaxLevel" "1" + "AbilityValues" + { + "movespeed" "100" + } + } + + "item_enhancement_crude" + { + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_PASSIVE" + "ItemIsNeutralPassiveDrop" "1" + "MaxLevel" "2" + "AbilityValues" + { + "slow_resistance" "15 25" + "bat_reduce" "10 15" + "intelligence_pct" "-20" + } + } + + "item_enhancement_timeless" + { + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_PASSIVE" + "ItemIsNeutralPassiveDrop" "1" + "MaxLevel" "2" + "AbilityValues" + { + "debuff_amp" "12 20" + "spell_amp" "12 20" + } + } + + "item_enhancement_greedy" + { + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_PASSIVE" + "ItemIsNeutralPassiveDrop" "1" + "MaxLevel" "2" + "AbilityValues" + { + "bonus_gpm" "90 180" + "bonus_mana" "150 220" + "bonus_damage" "-70 -130" + } + } + + "item_enhancement_vampiric" + { + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_PASSIVE" + "ItemIsNeutralPassiveDrop" "1" + "MaxLevel" "3" + "AbilityValues" + { + "attack_lifesteal" "10 13 16" + "spell_lifesteal" "6 8 10" + "attack_damage" "0" + "vision_loss" "0" + } + } + + "item_enhancement_keen_eyed" + { + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_PASSIVE" + "ItemIsNeutralPassiveDrop" "1" + "MaxLevel" "2" + "AbilityValues" + { + "cast_range_bonus" "100 165" + "bonus_mana_regen" "0.8 1.2" + "mana_reduction_pct" "12" + } + } + + "item_enhancement_evolved" + { + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_PASSIVE" + "ItemIsNeutralPassiveDrop" "1" + "AbilityValues" + { + "primary_stat" "32" + "primary_stat_universal" "20" + } + } + + "item_enhancement_titanic" + { + "AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_PASSIVE" + "ItemIsNeutralPassiveDrop" "1" + "MaxLevel" "2" + "AbilityValues" + { + "base_attack_damage" "20 50" + "status_resistance" "18 32" + } + } + +} \ No newline at end of file diff --git a/scripts/npc/npc_units_custom.txt b/scripts/npc/npc_units_custom.txt new file mode 100644 index 0000000..67825c2 --- /dev/null +++ b/scripts/npc/npc_units_custom.txt @@ -0,0 +1,297 @@ +#base "wave/npc_wave_zombies.kv" +#base "wave/npc_wave_boss_zombies.kv" +#base "event_units/event_units.kv" +#base "spawn_manager/spawn_units.kv" +#base "spawn_manager/spawn_boss_units.kv" +#base "quest_npc/quest_npc.kv" +#base "summons/summons.kv" + + +"DOTAUnits" +{ + "scene_panel_npc_dota_hero_nagash" + { + "BaseClass" "npc_dota_base_additive" + "Model" "models/heroes/phantom_assassin_persona/phantom_assassin_persona.vmdl" + } + "scene_panel_npc_dota_hero_bloodhunter" + { + "BaseClass" "npc_dota_base_additive" + "Model" "models/heroes/blood_seeker/blood_seeker.vmdl" + } + "scene_panel_npc_dota_hero_yuki_onna" + { + "BaseClass" "npc_dota_base_additive" + "Model" "models/heroes/crystal_maiden/crystal_maiden_arcana.vmdl" + } + "scene_panel_npc_dota_hero_sargatanas" + { + "BaseClass" "npc_dota_base_additive" + "Model" "models/items/terrorblade/dreadhunt_demon/dreadhunt_demon.vmdl" + } + "npc_dota_hero_elder_dragon_smaug" + { + "BaseClass" "npc_dota_base_additive" + "Model" "models/heroes/dragon_knight_persona/dk_persona_dragon.vmdl" + } + "npc_campfire" + { + "BaseClass" "npc_dota_creature" + "Model" "models/campfire.vmdl" + "HealthBarOffset" "140" + "HasInventory" "0" + + "ArmorPhysical" "0" + "MagicalResistance" "0" + + "AttackCapabilities" "DOTA_UNIT_CAP_NO_ATTACK" + + "BoundsHullName" "DOTA_HULL_SIZE_SMALL" + "RingRadius" "125" + + "MovementCapabilities" "DOTA_UNIT_CAP_MOVE_GROUND" + "MovementSpeed" "0" + + "StatusHealth" "1" + "StatusHealthRegen" "0" + + "VisionDaytimeRange" "0" + "VisionNighttimeRange" "0" + + "Ability1" "invul_npc" + + "TeamName" "DOTA_TEAM_GOODGUYS" + "UnitRelationshipClass" "DOTA_NPC_UNIT_RELATIONSHIP_TYPE_SIEGE" + } + + "npc_dummy_test" + { + "BaseClass" "npc_dota_creature" + "Model" "models/props_gameplay/dummy/dummy_large.vmdl" + "HealthBarOffset" "140" + "HasInventory" "0" + + "ArmorPhysical" "0" + "MagicalResistance" "0" + + "AttackCapabilities" "DOTA_UNIT_CAP_NO_ATTACK" + + "BoundsHullName" "DOTA_HULL_SIZE_SMALL" + "RingRadius" "125" + + "MovementCapabilities" "DOTA_UNIT_CAP_MOVE_GROUND" + "MovementSpeed" "550" + + "StatusHealth" "5000" + "StatusHealthRegen" "0" + + "VisionDaytimeRange" "0" + "VisionNighttimeRange" "0" + + "Ability1" "dps_tracker" + + "TeamName" "DOTA_TEAM_GOODGUYS" + "UnitRelationshipClass" "DOTA_NPC_UNIT_RELATIONSHIP_TYPE_SIEGE" + } + "npc_attack_box" + { + "BaseClass" "npc_dota_creature" + "Model" "models/attack_box.vmdl" + "HealthBarOffset" "140" + "HasInventory" "0" + + "ArmorPhysical" "0" + "MagicalResistance" "0" + + "AttackCapabilities" "DOTA_UNIT_CAP_NO_ATTACK" + + "BoundsHullName" "DOTA_HULL_SIZE_SMALL" + "RingRadius" "125" + + "MovementCapabilities" "DOTA_UNIT_CAP_MOVE_GROUND" + "MovementSpeed" "0" + + "StatusHealth" "1" + "StatusHealthRegen" "0" + + "VisionDaytimeRange" "0" + "VisionNighttimeRange" "0" + + "Ability1" "invul_box" + + "TeamName" "DOTA_TEAM_GOODGUYS" + "UnitRelationshipClass" "DOTA_NPC_UNIT_RELATIONSHIP_TYPE_SIEGE" + } + "npc_capture_point" + { + "BaseClass" "npc_dota_creature" + "Model" "models/props_gameplay/lantern/lantern_of_sight.vmdl" + "HealthBarOffset" "140" + "HasInventory" "0" + + "ArmorPhysical" "0" + "MagicalResistance" "0" + + "AttackCapabilities" "DOTA_UNIT_CAP_NO_ATTACK" + + "BoundsHullName" "DOTA_HULL_SIZE_SMALL" + "RingRadius" "125" + + "MovementCapabilities" "DOTA_UNIT_CAP_MOVE_GROUND" + "MovementSpeed" "0" + + "StatusHealth" "1" + "StatusHealthRegen" "0" + + "VisionDaytimeRange" "0" + "VisionNighttimeRange" "0" + + "Ability1" "invul_npc" + "Ability2" "ability_capture_point" + + "TeamName" "DOTA_TEAM_GOODGUYS" + "UnitRelationshipClass" "DOTA_NPC_UNIT_RELATIONSHIP_TYPE_SIEGE" + } + "npc_teleport" + { + "BaseClass" "npc_dota_building" + "Model" "models/development/invisiblebox.vmdl" + "HealthBarOffset" "140" + "HasInventory" "0" + + "ArmorPhysical" "10" + "MagicalResistance" "25" + + "AttackCapabilities" "DOTA_UNIT_CAP_NO_ATTACK" + + "BoundsHullName" "DOTA_HULL_SIZE_SMALL" + "RingRadius" "125" + + "MovementCapabilities" "DOTA_UNIT_CAP_MOVE_GROUND" + "MovementSpeed" "0" + + "StatusHealth" "3000" + "StatusHealthRegen" "3" + + "VisionDaytimeRange" "3900" + "VisionNighttimeRange" "3900" + + "Ability1" "invul_npc" + + "TeamName" "DOTA_TEAM_GOODGUYS" + "UnitRelationshipClass" "DOTA_NPC_UNIT_RELATIONSHIP_TYPE_SIEGE" + } + "npc_dota_camera" + { + "BaseClass" "npc_dota_creature" + "Model" "models/development/invisiblebox.vmdl" + "Level" "0" + "ArmorPhysical" "0" + "MagicalResistance" "0" + "AttackCapabilities" "DOTA_UNIT_CAP_NO_ATTACK" + "BoundsHullName" "DOTA_HULL_SIZE_SMALL" + "MovementCapabilities" "DOTA_UNIT_CAP_MOVE_NONE" + "StatusHealth" "1" + "TeamName" "DOTA_TEAM_GOODGUYS" + "CombatClassDefend" "DOTA_COMBAT_CLASS_DEFEND_BASIC" + "UnitRelationshipClass" "DOTA_NPC_UNIT_RELATIONSHIP_TYPE_WARD" + "VisionDaytimeRange" "0" + "VisionNighttimeRange" "0" + "Ability1" "invul_cam" + } + "npc_grave" + { + "BaseClass" "npc_dota_creature" + "Model" "models/items/wraith_king/arcana/wk_arcana_tombstone.vmdl" + "HealthBarOffset" "140" + "HasInventory" "0" + "ModelScale" "1.2" + "ArmorPhysical" "10" + "MagicalResistance" "25" + + "AttackCapabilities" "DOTA_UNIT_CAP_NO_ATTACK" + + "BoundsHullName" "DOTA_HULL_SIZE_HERO" + "RingRadius" "125" + + "MovementCapabilities" "DOTA_UNIT_CAP_MOVE_GROUND" + "MovementSpeed" "0" + + "StatusHealth" "3000" + "StatusHealthRegen" "3" + + "VisionDaytimeRange" "3900" + "VisionNighttimeRange" "3900" + + "Ability1" "" + "Ability2" "invul_npc" + + "TeamName" "DOTA_TEAM_GOODGUYS" + "UnitRelationshipClass" "DOTA_NPC_UNIT_RELATIONSHIP_TYPE_SIEGE" + } + "npc_kotl_wisp" + { + "Model" "models/heroes/keeper_of_the_light/kotl_wisp.vmdl" + "BaseClass" "npc_dota_ignis_fatuus" + "SoundSet" "Phoenix_Sun" + "Level" "1" + "ModelScale" "1" + "MinimapIconSize" "300" + + "Ability1" "" + "Ability2" "" + "Ability3" "" + "Ability4" "" + + "ArmorPhysical" "0" + + "AttackCapabilities" "DOTA_UNIT_CAP_NO_ATTACK" + "AttackDamageMin" "0" + "AttackDamageMax" "0" + "AttackRate" "1.0" + "AttackAnimationPoint" "0.0" + "AttackAcquisitionRange" "0" + "AttackRange" "0" + "ProjectileModel" "" + "ProjectileSpeed" "0" + + "HealthBarOffset" "150" + "RingRadius" "85" + "BoundsHullName" "DOTA_HULL_SIZE_HERO" + + "BountyXP" "0" + "BountyGoldMin" "0" + "BountyGoldMax" "0" + + "MovementCapabilities" "DOTA_UNIT_CAP_MOVE_NONE" + "MovementSpeed" "0" + + "StatusHealth" "100" + "StatusHealthRegen" "0.0" + + "VisionDaytimeRange" "1800" + "VisionNighttimeRange" "800" + + "TeamName" "DOTA_TEAM_GOODGUYS" + "UnitRelationshipClass" "DOTA_NPC_UNIT_RELATIONSHIP_TYPE_WARD" + "AttackDesire" "1.5" + } + "npc_dota_donate_item" + { + "BaseClass" "npc_dota_base_additive" + "Model" "models/heroes/kunkka/kunkka.vmdl" + "MovementCapabilities" "DOTA_UNIT_CAP_MOVE_GROUND" + "AttackCapabilities" "DOTA_UNIT_CAP_NO_ATTACK" + "BoundsHullName" "DOTA_HULL_SIZE_SMALL" + "VisionDaytimeRange" "0" + "VisionNighttimeRange" "0" + "ModelScale" "1" + "MovementSpeed" "325" + "MovementTurnRate" "1.0" + "TeamName" "DOTA_TEAM_NEUTRALS" + "UnitRelationshipClass" "DOTA_NPC_UNIT_RELATIONSHIP_TYPE_DEFAULT" + } + + + +} \ No newline at end of file diff --git a/scripts/npc/quest_npc/quest_npc.kv b/scripts/npc/quest_npc/quest_npc.kv new file mode 100644 index 0000000..352ab1a --- /dev/null +++ b/scripts/npc/quest_npc/quest_npc.kv @@ -0,0 +1,600 @@ + +"DOTAUnits" +{ + "npc_homer" + { + "BaseClass" "npc_dota_creature" + "Model" "models/heroes/shopkeeper_dire/shopkeeper_dire.vmdl" + "HealthBarOffset" "250" + "HasInventory" "0" + + "ArmorPhysical" "10" + "MagicalResistance" "25" + + "AttackCapabilities" "DOTA_UNIT_CAP_NO_ATTACK" + + "BoundsHullName" "DOTA_HULL_SIZE_HERO" + "RingRadius" "75" + + "MovementCapabilities" "DOTA_UNIT_CAP_MOVE_GROUND" + "MovementSpeed" "0" + + "StatusHealth" "3000" + "StatusHealthRegen" "3" + + "VisionDaytimeRange" "3900" + "VisionNighttimeRange" "3900" + + "Ability1" "ability_glyph_custom" + "Ability2" "ability_unit_less_laggy" + + + "TeamName" "DOTA_TEAM_GOODGUYS" + "UnitRelationshipClass" "DOTA_NPC_UNIT_RELATIONSHIP_TYPE_SIEGE" + } + "npc_quest_giver_kunkka" + { + "BaseClass" "npc_dota_creature" + "Model" "models/heroes/kunkka/kunkka.vmdl" + "HealthBarOffset" "140" + "HasInventory" "0" + "ModelScale" "1.2" + "ArmorPhysical" "10" + "MagicalResistance" "25" + + "AttackCapabilities" "DOTA_UNIT_CAP_NO_ATTACK" + + "BoundsHullName" "DOTA_HULL_SIZE_HERO" + "RingRadius" "125" + + "MovementCapabilities" "DOTA_UNIT_CAP_MOVE_GROUND" + "MovementSpeed" "0" + + "StatusHealth" "3000" + "StatusHealthRegen" "3" + + "VisionDaytimeRange" "3900" + "VisionNighttimeRange" "3900" + + "Ability1" "" + "Ability2" "invul_npc" + + "TeamName" "DOTA_TEAM_GOODGUYS" + "UnitRelationshipClass" "DOTA_NPC_UNIT_RELATIONSHIP_TYPE_SIEGE" + "Creature" + { + "AttachWearables" + { + "Wearable1" { "ItemDef" "6487" } + "Wearable2" { "ItemDef" "4016" } + "Wearable3" { "ItemDef" "6179" } + "Wearable4" { "ItemDef" "5470" } + "Wearable6" { "ItemDef" "357" } + "Wearable7" { "ItemDef" "5321" } + + } + } + } + "npc_quest_giver_denny" + { + "BaseClass" "npc_dota_creature" + "Model" "models/heroes/dragon_knight_persona/dk_persona_base.vmdl" + "HealthBarOffset" "140" + "HasInventory" "1" + "ModelScale" "1.45" + "ArmorPhysical" "10" + "MagicalResistance" "25" + "ConsideredHero" "1" + + "AttackCapabilities" "DOTA_UNIT_CAP_NO_ATTACK" + + "BoundsHullName" "DOTA_HULL_SIZE_HERO" + "RingRadius" "125" + "AttackRange" "130" + "MovementCapabilities" "DOTA_UNIT_CAP_MOVE_GROUND" + "MovementSpeed" "1330" + "AttackDamageMin" "22" + "AttackDamageMax" "34" + "StatusHealth" "3000" + "StatusHealthRegen" "3" + + "VisionDaytimeRange" "3900" + "VisionNighttimeRange" "3900" + + "Ability1" "" + "Ability2" "invul_npc" + + "TeamName" "DOTA_TEAM_GOODGUYS" + "UnitRelationshipClass" "DOTA_NPC_UNIT_RELATIONSHIP_TYPE_SIEGE" + "Creature" + { + "AttachWearables" + { + + "Wearable1" { "ItemDef" "762" } + "Wearable2" { "ItemDef" "763" } + + } + + + } + } + "npc_quest_giver_oldmen" + { + "BaseClass" "npc_dota_creature" + "Model" "models/heroes/omniknight/omniknight.vmdl" + "HealthBarOffset" "140" + "HasInventory" "0" + "ModelScale" ".9" + "ArmorPhysical" "10" + "MagicalResistance" "25" + + "AttackCapabilities" "DOTA_UNIT_CAP_NO_ATTACK" + + "BoundsHullName" "DOTA_HULL_SIZE_HERO" + "RingRadius" "125" + + "MovementCapabilities" "DOTA_UNIT_CAP_MOVE_GROUND" + "MovementSpeed" "0" + + "StatusHealth" "3000" + "StatusHealthRegen" "3" + + "VisionDaytimeRange" "3900" + "VisionNighttimeRange" "3900" + + "Ability1" "" + "Ability2" "invul_npc" + + "TeamName" "DOTA_TEAM_GOODGUYS" + "UnitRelationshipClass" "DOTA_NPC_UNIT_RELATIONSHIP_TYPE_SIEGE" + "Creature" + { + "AttachWearables" + { + + "Wearable1" { "ItemDef" "7093" } + "Wearable2" { "ItemDef" "45" } + "Wearable3" { "ItemDef" "8172" } + } + + + } + + } + "npc_quest_giver_firestar" + { + "BaseClass" "npc_dota_creature" + "Model" "models/items/courier/courier_ti10_radiant/courier_ti10_radiant_lvl7/courier_ti10_radiant_lvl7.vmdl" + "HealthBarOffset" "140" + "HasInventory" "0" + "ModelScale" ".8" + "ArmorPhysical" "10" + "MagicalResistance" "25" + + "AttackCapabilities" "DOTA_UNIT_CAP_NO_ATTACK" + + "BoundsHullName" "DOTA_HULL_SIZE_SMALL" + "RingRadius" "125" + + "MovementCapabilities" "DOTA_UNIT_CAP_MOVE_GROUND" + "MovementSpeed" "0" + + "StatusHealth" "3000" + "StatusHealthRegen" "3" + + "VisionDaytimeRange" "3900" + "VisionNighttimeRange" "3900" + + "Ability1" "" + "Ability2" "invul_npc" + + "TeamName" "DOTA_TEAM_GOODGUYS" + "UnitRelationshipClass" "DOTA_NPC_UNIT_RELATIONSHIP_TYPE_SIEGE" + + } + "npc_quest_giver_lina" + { + "BaseClass" "npc_dota_creature" + "Model" "models/heroes/lina/lina.vmdl" + "HealthBarOffset" "140" + "HasInventory" "0" + "ModelScale" "1" + "ArmorPhysical" "10" + "MagicalResistance" "25" + + "AttackCapabilities" "DOTA_UNIT_CAP_NO_ATTACK" + + "BoundsHullName" "DOTA_HULL_SIZE_SMALL" + "RingRadius" "125" + + "MovementCapabilities" "DOTA_UNIT_CAP_MOVE_GROUND" + "MovementSpeed" "0" + + "StatusHealth" "3000" + "StatusHealthRegen" "3" + + "VisionDaytimeRange" "3900" + "VisionNighttimeRange" "3900" + + "Ability1" "" + "Ability2" "invul_npc" + + "TeamName" "DOTA_TEAM_GOODGUYS" + "UnitRelationshipClass" "DOTA_NPC_UNIT_RELATIONSHIP_TYPE_SIEGE" + "Creature" + { + "AttachWearables" + { + "Wearable1" { "ItemDef" "6707" } + "Wearable2" { "ItemDef" "6339" } + "Wearable3" { "ItemDef" "6598" } + "Wearable4" { "ItemDef" "5940" } + } + } + } + "npc_quest_giver_maiden" + { + "BaseClass" "npc_dota_creature" + "Model" "models/heroes/crystal_maiden/crystal_maiden.vmdl" + "HealthBarOffset" "140" + "HasInventory" "0" + "ModelScale" "1" + "ArmorPhysical" "10" + "MagicalResistance" "25" + + "AttackCapabilities" "DOTA_UNIT_CAP_NO_ATTACK" + + "BoundsHullName" "DOTA_HULL_SIZE_SMALL" + "RingRadius" "125" + + "MovementCapabilities" "DOTA_UNIT_CAP_MOVE_GROUND" + "MovementSpeed" "0" + + "StatusHealth" "3000" + "StatusHealthRegen" "3" + + "VisionDaytimeRange" "3900" + "VisionNighttimeRange" "3900" + + "Ability1" "" + "Ability2" "invul_npc" + + "TeamName" "DOTA_TEAM_GOODGUYS" + "UnitRelationshipClass" "DOTA_NPC_UNIT_RELATIONSHIP_TYPE_SIEGE" + "Creature" + { + "AttachWearables" + { + + "Wearable1" { "ItemDef" "8225" } + "Wearable2" { "ItemDef" "8226" } + "Wearable3" { "ItemDef" "8227" } + "Wearable4" { "ItemDef" "8229" } + } + } + } + "npc_quest_giver_friend" + { + "BaseClass" "npc_dota_creature" + "Model" "models/pets/icewrack_wolf_alt/icewrack_wolf_alt.vmdl" + "HealthBarOffset" "140" + "HasInventory" "0" + "ModelScale" "1.45" + "ArmorPhysical" "10" + "MagicalResistance" "25" + "HasInventory" "1" + "AttackCapabilities" "DOTA_UNIT_CAP_NO_ATTACK" + + "BoundsHullName" "DOTA_HULL_SIZE_SMALL" + "RingRadius" "125" + + "MovementCapabilities" "DOTA_UNIT_CAP_MOVE_GROUND" + "MovementSpeed" "0" + + "StatusHealth" "3000" + "StatusHealthRegen" "3" + + "VisionDaytimeRange" "0" + "VisionNighttimeRange" "0" + + "Ability1" "invul_npc" + "Ability2" "" + + "TeamName" "DOTA_TEAM_NEUTRALS" + "UnitRelationshipClass" "DOTA_NPC_UNIT_RELATIONSHIP_TYPE_SIEGE" + + } + "npc_mound" + { + "BaseClass" "npc_dota_creature" + "Model" "models/heroes/nerubian_assassin/mound.vmdl" + "HealthBarOffset" "140" + "HasInventory" "0" + "ModelScale" "1.45" + "ArmorPhysical" "10" + "MagicalResistance" "25" + "HasInventory" "0" + "AttackCapabilities" "DOTA_UNIT_CAP_NO_ATTACK" + + "BoundsHullName" "DOTA_HULL_SIZE_SMALL" + "RingRadius" "125" + + "MovementCapabilities" "DOTA_UNIT_CAP_MOVE_GROUND" + "MovementSpeed" "0" + + "StatusHealth" "3000" + "StatusHealthRegen" "3" + + "VisionDaytimeRange" "0" + "VisionNighttimeRange" "0" + + "Ability1" "invul_npc" + "Ability2" "" + + "TeamName" "DOTA_TEAM_NEUTRALS" + "UnitRelationshipClass" "DOTA_NPC_UNIT_RELATIONSHIP_TYPE_SIEGE" + + } + "npc_sakura_tree" + { + "BaseClass" "npc_dota_creature" + "Model" "models/sakura_tree.vmdl" + "HealthBarOffset" "640" + "HasInventory" "0" + "ModelScale" "1.45" + "ArmorPhysical" "30" + "MagicalResistance" "90" + "HasInventory" "0" + "AttackCapabilities" "DOTA_UNIT_CAP_NO_ATTACK" + + "BoundsHullName" "DOTA_HULL_SIZE_SMALL" + "RingRadius" "125" + + "MovementCapabilities" "DOTA_UNIT_CAP_MOVE_GROUND" + "MovementSpeed" "0" + + "StatusHealth" "10000" + "StatusHealthRegen" "0" + + "VisionDaytimeRange" "0" + "VisionNighttimeRange" "0" + + "Ability1" "ability_unit_less_laggy" + "Ability2" "" + + "TeamName" "DOTA_TEAM_NEUTRALS" + "UnitRelationshipClass" "DOTA_NPC_UNIT_RELATIONSHIP_TYPE_SIEGE" + + } + "npc_quest_giver_largo" + { + "BaseClass" "npc_dota_creature" + "Model" "models/heroes/bard/bard_frog_base.vmdl" + "HealthBarOffset" "140" + "HasInventory" "0" + "ModelScale" "1.5" + "ArmorPhysical" "10" + "MagicalResistance" "25" + + "AttackCapabilities" "DOTA_UNIT_CAP_NO_ATTACK" + + "BoundsHullName" "DOTA_HULL_SIZE_SMALL" + "RingRadius" "125" + + "MovementCapabilities" "DOTA_UNIT_CAP_MOVE_GROUND" + "MovementSpeed" "0" + + "StatusHealth" "3000" + "StatusHealthRegen" "3" + + "VisionDaytimeRange" "3900" + "VisionNighttimeRange" "3900" + + "Ability1" "" + "Ability2" "invul_npc" + + "TeamName" "DOTA_TEAM_GOODGUYS" + "UnitRelationshipClass" "DOTA_NPC_UNIT_RELATIONSHIP_TYPE_SIEGE" + "Creature" + { + "AttachWearables" + { + "Wearable1" { "ItemDef" "856" } + "Wearable2" { "ItemDef" "857" } + "Wearable3" { "ItemDef" "858" } + "Wearable4" { "ItemDef" "859" } + } + } + + } + "npc_quest_giver_doctor" + { + "BaseClass" "npc_dota_creature" + "Model" "models/heroes/witchdoctor/witchdoctor.vmdl" + "HealthBarOffset" "140" + "HasInventory" "0" + "ModelScale" "1.38" + "ArmorPhysical" "10" + "MagicalResistance" "25" + + "AttackCapabilities" "DOTA_UNIT_CAP_NO_ATTACK" + + "BoundsHullName" "DOTA_HULL_SIZE_SMALL" + "RingRadius" "125" + + "MovementCapabilities" "DOTA_UNIT_CAP_MOVE_GROUND" + "MovementSpeed" "0" + + "StatusHealth" "3000" + "StatusHealthRegen" "3" + + "VisionDaytimeRange" "3900" + "VisionNighttimeRange" "3900" + + "Ability1" "" + "Ability2" "invul_npc" + + "TeamName" "DOTA_TEAM_GOODGUYS" + "UnitRelationshipClass" "DOTA_NPC_UNIT_RELATIONSHIP_TYPE_SIEGE" + "Creature" + { + "AttachWearables" + { + "Wearable1" { "ItemDef" "8267" } + "Wearable1" { "ItemDef" "9172" } + "Wearable1" { "ItemDef" "9173" } + "Wearable1" { "ItemDef" "9174" } + "Wearable1" { "ItemDef" "9176" } + } + } + } + "npc_kot_roflik_1" + { + "BaseClass" "npc_dota_creature" + "Model" "models/items/courier/courier_ti10_radiant/courier_ti10_radiant.vmdl" + "HealthBarOffset" "140" + "HasInventory" "0" + "ModelScale" ".8" + "ArmorPhysical" "10" + "MagicalResistance" "25" + + "AttackCapabilities" "DOTA_UNIT_CAP_NO_ATTACK" + + "BoundsHullName" "DOTA_HULL_SIZE_SMALL" + "RingRadius" "125" + + "MovementCapabilities" "DOTA_UNIT_CAP_MOVE_GROUND" + "MovementSpeed" "0" + + "StatusHealth" "3000" + "StatusHealthRegen" "3" + + "VisionDaytimeRange" "3900" + "VisionNighttimeRange" "3900" + + "Ability1" "" + "Ability2" "invul_npc" + + "TeamName" "DOTA_TEAM_GOODGUYS" + "UnitRelationshipClass" "DOTA_NPC_UNIT_RELATIONSHIP_TYPE_SIEGE" + + } + "npc_kot_roflik_2" + { + "BaseClass" "npc_dota_creature" + "Model" "models/items/courier/courier_ti10_dire/courier_ti10_dire_lvl1/courier_ti10_dire_lvl1.vmdl" + "HealthBarOffset" "140" + "HasInventory" "0" + "ModelScale" ".8" + "ArmorPhysical" "10" + "MagicalResistance" "25" + + "AttackCapabilities" "DOTA_UNIT_CAP_NO_ATTACK" + + "BoundsHullName" "DOTA_HULL_SIZE_SMALL" + "RingRadius" "125" + + "MovementCapabilities" "DOTA_UNIT_CAP_MOVE_GROUND" + "MovementSpeed" "0" + + "StatusHealth" "3000" + "StatusHealthRegen" "3" + + "VisionDaytimeRange" "3900" + "VisionNighttimeRange" "3900" + + "Ability1" "" + "Ability2" "invul_npc" + + "TeamName" "DOTA_TEAM_GOODGUYS" + "UnitRelationshipClass" "DOTA_NPC_UNIT_RELATIONSHIP_TYPE_SIEGE" + + } + "npc_kot_roflik_0" + { + "BaseClass" "npc_dota_creature" + "Model" "models/items/courier/courier_ti10_dire/courier_ti10_dire_lvl4/courier_ti10_dire_lvl4.vmdl" + "HealthBarOffset" "140" + "HasInventory" "0" + "ModelScale" ".8" + "ArmorPhysical" "10" + "MagicalResistance" "25" + + "AttackCapabilities" "DOTA_UNIT_CAP_NO_ATTACK" + + "BoundsHullName" "DOTA_HULL_SIZE_SMALL" + "RingRadius" "125" + + "MovementCapabilities" "DOTA_UNIT_CAP_MOVE_GROUND" + "MovementSpeed" "0" + + "StatusHealth" "6969" + "StatusHealthRegen" "1337" + + "VisionDaytimeRange" "3900" + "VisionNighttimeRange" "3900" + + "Ability1" "" + "Ability2" "invul_npc" + + "TeamName" "DOTA_TEAM_GOODGUYS" + "UnitRelationshipClass" "DOTA_NPC_UNIT_RELATIONSHIP_TYPE_SIEGE" + + } + "npc_market_blackshop" + { + "BaseClass" "npc_dota_creature" + "Model" "models/heroes/grimstroke/grimstroke.vmdl" + "HealthBarOffset" "140" + "HasInventory" "0" + "ArmorPhysical" "10" + "MagicalResistance" "25" + "AttackCapabilities" "DOTA_UNIT_CAP_NO_ATTACK" + "BoundsHullName" "DOTA_HULL_SIZE_HERO" + "RingRadius" "70" + "MovementCapabilities" "DOTA_UNIT_CAP_MOVE_GROUND" + "MovementSpeed" "0" + "StatusHealth" "3000" + "StatusHealthRegen" "3" + "VisionDaytimeRange" "900" + "VisionNighttimeRange" "900" + "TeamName" "DOTA_TEAM_GOODGUYS" + "UnitRelationshipClass" "DOTA_NPC_UNIT_RELATIONSHIP_TYPE_SIEGE" + "Ability1" "" + "Ability2" "invul_npc" + "Creature" + { + "AttachWearables" + { + "Wearable1" { "ItemDef" "27489" } + "Wearable1" { "ItemDef" "13896" } + "Wearable1" { "ItemDef" "13898" } + "Wearable1" { "ItemDef" "13897" } + "Wearable1" { "ItemDef" "27490" } + } + } + } + "npc_kunkka_quest_anchor_marker" + { + "BaseClass" "npc_dota_creature" + "Model" "models/heroes/wisp/wisp.vmdl" + "ModelScale" "1.0" + "HealthBarOffset" "0" + "HasInventory" "0" + "ArmorPhysical" "0" + "MagicalResistance" "100" + "AttackCapabilities" "DOTA_UNIT_CAP_NO_ATTACK" + "BoundsHullName" "DOTA_HULL_SIZE_SMALL" + "RingRadius" "8" + "MovementCapabilities" "DOTA_UNIT_CAP_MOVE_NONE" + "MovementSpeed" "0" + "StatusHealth" "1" + "StatusHealthRegen" "0" + "VisionDaytimeRange" "0" + "VisionNighttimeRange" "0" + "Ability1" "invul_npc" + "TeamName" "DOTA_TEAM_GOODGUYS" + "UnitRelationshipClass" "DOTA_NPC_UNIT_RELATIONSHIP_TYPE_WARD" + } +} \ No newline at end of file diff --git a/scripts/npc/spawn_manager/spawn_boss_units.kv b/scripts/npc/spawn_manager/spawn_boss_units.kv new file mode 100644 index 0000000..2c1cbf6 --- /dev/null +++ b/scripts/npc/spawn_manager/spawn_boss_units.kv @@ -0,0 +1,245 @@ +"DOTAUnits" +{ + "npc_boss_lycan" + { + "Model" "models/items/lycan/ultimate/_ascension_of_the_hallowed_beast_form/_ascension_of_the_hallowed_beast_form.vmdl" + "BaseClass" "npc_dota_creature" + "SoundSet" "Hero_Lycan" + "GameSoundsFile" "soundevents/game_sounds_creeps.vsndevts" + "ModelScale" "1.35" + "BoundsHullName" "DOTA_HULL_SIZE_HERO" + "UseNeutralCreepBehavior" "1" + "ConsideredHero" "1" + "MinimapIcon" "minimap_death" + "MinimapIconSize" "650" + "Ability1" "necronomicon_warrior_mana_burn" + "Ability2" "lycan_summon_wolves_critical_strike" + "Ability3" "lycan_summon_wolves_hamstring" + "Ability4" "lycan_summon_wolves_hightail" + + + // Armor + //---------------------------------------------------------------- + "ArmorPhysical" "40" + "MagicalResistance" "22" + + // Attack + //---------------------------------------------------------------- + "AttackCapabilities" "DOTA_UNIT_CAP_MELEE_ATTACK" + "AttackDamageType" "DAMAGE_TYPE_ArmorPhysical" + "AttackDamageMin" "270" + "AttackDamageMax" "342" + "AttackRate" "1.25" + "AttackAnimationPoint" "0.4" + "AttackAcquisitionRange" "1100" + "BaseAttackSpeed" "140" + "AttackAcquisitionRange" "200" + "AttackRange" "200" + "Level" "10" + + + // Bounds + //---------------------------------------------------------------- + "RingRadius" "40" + "HealthBarOffset" "170" + + // Bounty + //---------------------------------------------------------------- + "BountyXP" "800" + "BountyGoldMin" "0" + "BountyGoldMax" "0" + + // Movement + //---------------------------------------------------------------- + "MovementCapabilities" "DOTA_UNIT_CAP_MOVE_GROUND" + "MovementSpeed" "666" + + + // Status + //---------------------------------------------------------------- + "StatusHealth" "6150" + "StatusHealthRegen" "5" + "StatusMana" "440" + "StatusManaRegen" "5.0" + + // Vision + //---------------------------------------------------------------- + "VisionDaytimeRange" "400" + "VisionNighttimeRange" "400" + + + // Team + //---------------------------------------------------------------- + "TeamName" "DOTA_TEAM_BADGUYS" + "CombatClassAttack" "DOTA_COMBAT_CLASS_ATTACK_PIERCE" + "CombatClassDefend" "DOTA_COMBAT_CLASS_DEFEND_BASIC" + "UnitRelationshipClass" "DOTA_NPC_UNIT_RELATIONSHIP_TYPE_DEFAULT" + "Creature" + { + + + + + } + + } + "npc_demon_dragon_satyr" + { + "vscripts" "ai/ai_demon_dragon_satyr" + + + "BaseClass" "npc_dota_creature" // Class of entity of link to. + "Model" "models/creeps/nian/nian_creep.vmdl" // Model. + "SoundSet" "Creep_Good_Melee" // Name of sound set. + "ModelScale" "1.2" + "ConsideredHero" "1" + "UseNeutralCreepBehavior" "0" + + // Abilities + //---------------------------------------------------------------- + "Ability1" "ability_grab" // Ability 1 + "Ability2" "fear" // Ability 2 + "Ability3" "back_stun" // Ability 3 + "Ability4" "face_stun" // Ability 4 + "Ability5" "ability_animation" // Ability 4 + "Ability6" "boss_ability" + // Stats + //---------------------------------------------------------------- + "ArmorPhysical" "100" // Physical protection. + "AttackCapabilities" "DOTA_UNIT_CAP_MELEE_ATTACK" + "AttackDamageMin" "1660" // Damage range min. + "AttackDamageMax" "1710" // Damage range max. + "AttackRate" "2.2" // Speed of attack. + "BaseAttackSpeed" "140" + "AttackAnimationPoint" "0.38" // Normalized time in animation cycle to attack. + "AttackAcquisitionRange" "500" // Range within a target can be acquired. + "AttackRange" "110" // Range within a target can be attacked. + "ProjectileModel" "" // Particle system model for projectile. + "ProjectileSpeed" "0" // Speed of projectile. + "MagicalResistance" "62" + + // Bounds + //---------------------------------------------------------------- + "RingRadius" "50" + "HealthBarOffset" "350" + + // Xp + //---------------------------------------------------------------- + "BountyXP" "0" // Experience earn. + + // Movement + //---------------------------------------------------------------- + "MovementCapabilities" "DOTA_UNIT_CAP_MOVE_GROUND" + "MovementSpeed" "330" // Speed. + "MovementTurnRate" "0.9" // Turning rate. + + // Status + //---------------------------------------------------------------- + "StatusHealth" "15200" // Base health. + "StatusHealthRegen" "50.0" // Health regeneration rate. + "StatusMana" "0" // Base mana. + "StatusManaRegen" "0.0" // Mana regeneration rate. + + + // Vision + //---------------------------------------------------------------- + "VisionDaytimeRange" "800" // Range of vision during day light. + "VisionNighttimeRange" "800" // Range of vision at night time. + + // Team + //---------------------------------------------------------------- + "TeamName" "DOTA_TEAM_NEUTRALS" // Team name. + "UnitRelationshipClass" "DOTA_NPC_UNIT_RELATIONSHIP_TYPE_DEFAULT" + } + "npc_boss_nevermore" + { + "Model" "models/heroes/shadow_fiend/shadow_fiend_arcana.vmdl" + "BaseClass" "npc_dota_creature" + "SoundSet" "Hero_Lycan" + "GameSoundsFile" "soundevents/game_sounds_creeps.vsndevts" + "ModelScale" "1.9" + "BoundsHullName" "DOTA_HULL_SIZE_HERO" + "UseNeutralCreepBehavior" "0" + "ConsideredHero" "0" + "IsAncient" "1" + "vscripts" "ai/nevermore_ai" + "MinimapIcon" "minimap_death" + "MinimapIconSize" "650" + "Ability1" "boss_nevermore_coil_wave" + "Ability2" "boss_nevermore_triple_coil_aoe" + "Ability3" "boss_nevermore_requiem_barrage" + "Ability4" "boss_nevermore_time_walk" + "Ability5" "boss_nevermore_coil_beam" + "Ability6" "no_healthbar" + "Ability7" "terrorblade_terror_wave" + "Ability8" "boss_nevermore_hub_crossburst" + + + // Armor + //---------------------------------------------------------------- + "ArmorPhysical" "218" + "MagicalResistance" "52" + + // Attack + //---------------------------------------------------------------- + "AttackCapabilities" "DOTA_UNIT_CAP_MELEE_ATTACK" + "AttackDamageType" "DAMAGE_TYPE_ArmorPhysical" + "AttackDamageMin" "500" + "AttackDamageMax" "570" + "AttackRate" "1.25" + "AttackAnimationPoint" "0.4" + "AttackAcquisitionRange" "1100" + "BaseAttackSpeed" "140" + "AttackAcquisitionRange" "200" + "AttackRange" "200" + "Level" "10" + + + // Bounds + //---------------------------------------------------------------- + "RingRadius" "40" + "HealthBarOffset" "170" + + // Bounty + //---------------------------------------------------------------- + "BountyXP" "800" + "BountyGoldMin" "0" + "BountyGoldMax" "0" + + // Movement + //---------------------------------------------------------------- + "MovementCapabilities" "DOTA_UNIT_CAP_MOVE_GROUND" + "MovementSpeed" "666" + + + // Status + //---------------------------------------------------------------- + "StatusHealth" "287500" + "StatusHealthRegen" "0" + "StatusMana" "4400" + "StatusManaRegen" "5.0" + + // Vision + //---------------------------------------------------------------- + "VisionDaytimeRange" "10000" + "VisionNighttimeRange" "10000" + + + // Team + //---------------------------------------------------------------- + "TeamName" "DOTA_TEAM_BADGUYS" + "CombatClassAttack" "DOTA_COMBAT_CLASS_ATTACK_PIERCE" + "CombatClassDefend" "DOTA_COMBAT_CLASS_DEFEND_BASIC" + "UnitRelationshipClass" "DOTA_NPC_UNIT_RELATIONSHIP_TYPE_DEFAULT" + "Creature" + { + "AttachWearables" + { + "Wearable1" { "ItemDef" "7279" } + "Wearable1" { "ItemDef" "8259" } + "Wearable1" { "ItemDef" "13505" } + } + } + + } +} \ No newline at end of file diff --git a/scripts/npc/spawn_manager/spawn_units.kv b/scripts/npc/spawn_manager/spawn_units.kv new file mode 100644 index 0000000..8b478e1 --- /dev/null +++ b/scripts/npc/spawn_manager/spawn_units.kv @@ -0,0 +1,1993 @@ +"DOTAUnits" +{ + "npc_pig" + { + "Model" "models/props_gameplay/pig.vmdl" + "BaseClass" "npc_dota_creature" + "SoundSet" "n_creep_Melee" + "GameSoundsFile" "soundevents/game_sounds_creeps.vsndevts" + "Level" "1" + "ModelScale" ".9" + "UseNeutralCreepBehavior" "0" + "Level" "1" + + "Ability1" "pig_charge" + "Ability2" "ability_unit_less_laggy" + "Ability3" "" + "Ability4" "" + + "ArmorPhysical" "0" + "MagicalResistance" "0" + + "AttackCapabilities" "DOTA_UNIT_CAP_MELEE_ATTACK" + "AttackDamageType" "DAMAGE_TYPE_ArmorPhysical" + "AttackDamageMin" "29" + "AttackDamageMax" "32" + "AttackRate" "3" + "AttackAnimationPoint" "0.4" + "AttackAcquisitionRange" "300" + "AttackRange" "90" + + "RingRadius" "40" + "HealthBarOffset" "170" + + "BountyXP" "7" + "BountyGoldMin" "10" + "BountyGoldMax" "15" + + "MovementCapabilities" "DOTA_UNIT_CAP_MOVE_GROUND" + "MovementSpeed" "260" + + "StatusHealth" "155" + "StatusHealthRegen" "5.0" + "StatusMana" "0" + "StatusManaRegen" "0.0" + + "VisionDaytimeRange" "500" + "VisionNighttimeRange" "300" + + "TeamName" "DOTA_TEAM_NEUTRALS" + "CombatClassAttack" "DOTA_COMBAT_CLASS_ATTACK_BASIC" + "CombatClassDefend" "DOTA_COMBAT_CLASS_DEFEND_BASIC" + "UnitRelationshipClass" "DOTA_NPC_UNIT_RELATIONSHIP_TYPE_DEFAULT" + "Creature" + { + "OffensiveAbilities" + { + "Ability1" + { + "Name" "pig_charge" + } + + } + } + + } + "npc_sheep" + { + "Model" "models/items/hex/sheep_hex/sheep_hex.vmdl" + "BaseClass" "npc_dota_creature" + "SoundSet" "n_creep_Melee" + "GameSoundsFile" "soundevents/game_sounds_creeps.vsndevts" + "Level" "1" + "ModelScale" ".9" + "UseNeutralCreepBehavior" "0" + "Level" "1" + + "Ability1" "sheep_coil" + "Ability2" "ability_unit_less_laggy" + "Ability3" "" + "Ability4" "" + "Ability6" "" + + "ArmorPhysical" "4" + "MagicalResistance" "55" + + "AttackCapabilities" "DOTA_UNIT_CAP_MELEE_ATTACK" + "AttackDamageType" "DAMAGE_TYPE_ArmorPhysical" + "AttackDamageMin" "16" + "AttackDamageMax" "24" + "AttackRate" "1" + "AttackAnimationPoint" "0.4" + "AttackAcquisitionRange" "300" + "AttackRange" "90" + + + "RingRadius" "40" + "HealthBarOffset" "150" + + "BountyXP" "7" + "BountyGoldMin" "14" + "BountyGoldMax" "19" + + "MovementCapabilities" "DOTA_UNIT_CAP_MOVE_GROUND" + "MovementSpeed" "325" + + "StatusHealth" "120" + "StatusHealthRegen" "1.2" + "StatusMana" "100" + "StatusManaRegen" "7.0" + + "VisionDaytimeRange" "500" + "VisionNighttimeRange" "300" + + "TeamName" "DOTA_TEAM_NEUTRALS" + "CombatClassAttack" "DOTA_COMBAT_CLASS_ATTACK_BASIC" + "CombatClassDefend" "DOTA_COMBAT_CLASS_DEFEND_BASIC" + "UnitRelationshipClass" "DOTA_NPC_UNIT_RELATIONSHIP_TYPE_DEFAULT" + //"Creature" + //{ + // "OffensiveAbilities" + // { + // "Ability1" + // { + // "Name" "sheep_coil" + // } + // } + //} + } + "npc_chicken" + { + "Model" "models/props_gameplay/chicken.vmdl" + "BaseClass" "npc_dota_creature" + "SoundSet" "n_creep_Melee" + "GameSoundsFile" "soundevents/game_sounds_creeps.vsndevts" + "Level" "1" + "ModelScale" ".9" + "UseNeutralCreepBehavior" "0" + "Level" "1" + + "Ability1" "chicken_attack" + "Ability2" "ability_unit_less_laggy" + "Ability3" "" + "Ability4" "" + + "ArmorPhysical" "0" + "MagicalResistance" "0" + + "AttackCapabilities" "DOTA_UNIT_CAP_MELEE_ATTACK" + "AttackDamageType" "DAMAGE_TYPE_ArmorPhysical" + "AttackDamageMin" "89" + "AttackDamageMax" "92" + "AttackRate" "3" + "AttackAnimationPoint" "0.4" + "AttackAcquisitionRange" "300" + "AttackRange" "90" + + "RingRadius" "40" + "HealthBarOffset" "170" + + "BountyXP" "13" + "BountyGoldMin" "24" + "BountyGoldMax" "29" + + "MovementCapabilities" "DOTA_UNIT_CAP_MOVE_GROUND" + "MovementSpeed" "260" + + "StatusHealth" "355" + "StatusHealthRegen" "2.0" + "StatusMana" "0" + "StatusManaRegen" "0.0" + + "VisionDaytimeRange" "500" + "VisionNighttimeRange" "300" + + "TeamName" "DOTA_TEAM_NEUTRALS" + "CombatClassAttack" "DOTA_COMBAT_CLASS_ATTACK_BASIC" + "CombatClassDefend" "DOTA_COMBAT_CLASS_DEFEND_BASIC" + "UnitRelationshipClass" "DOTA_NPC_UNIT_RELATIONSHIP_TYPE_DEFAULT" + "Creature" + { + "OffensiveAbilities" + { + "Ability1" + { + "Name" "chicken_attack" + } + + } + } + + } + "npc_wolf" + { + "Model" "models/items/lycan/wolves/_ascension_of_the_hallowed_beast_summons/_ascension_of_the_hallowed_beast_summons.vmdl" + "BaseClass" "npc_dota_creature" + "SoundSet" "n_creep_Melee" + "GameSoundsFile" "soundevents/game_sounds_creeps.vsndevts" + "Level" "1" + "ModelScale" ".9" + "UseNeutralCreepBehavior" "0" + "Level" "3" + + "Ability1" "necronomicon_warrior_mana_burn" + "Ability2" "lycan_summon_wolves_critical_strike" + "Ability3" "lycan_summon_wolves_hamstring" + "Ability4" "lycan_summon_wolves_hightail" + "Ability5" "ability_unit_less_laggy" + + + + "ArmorPhysical" "4" + "MagicalResistance" "55" + + "AttackCapabilities" "DOTA_UNIT_CAP_MELEE_ATTACK" + "AttackDamageType" "DAMAGE_TYPE_ArmorPhysical" + "AttackDamageMin" "42" + "AttackDamageMax" "52" + "AttackRate" "1" + "AttackAnimationPoint" "0.4" + "AttackAcquisitionRange" "300" + "AttackRange" "90" + + + "RingRadius" "40" + "HealthBarOffset" "150" + + "BountyXP" "29" + "BountyGoldMin" "36" + "BountyGoldMax" "41" + + "MovementCapabilities" "DOTA_UNIT_CAP_MOVE_GROUND" + "MovementSpeed" "325" + + "StatusHealth" "420" + "StatusHealthRegen" "1.2" + "StatusMana" "100" + "StatusManaRegen" "2.0" + + "VisionDaytimeRange" "500" + "VisionNighttimeRange" "300" + + "TeamName" "DOTA_TEAM_NEUTRALS" + "CombatClassAttack" "DOTA_COMBAT_CLASS_ATTACK_BASIC" + "CombatClassDefend" "DOTA_COMBAT_CLASS_DEFEND_BASIC" + "UnitRelationshipClass" "DOTA_NPC_UNIT_RELATIONSHIP_TYPE_DEFAULT" + "Creature" + { + "OffensiveAbilities" + { + "Ability4" + { + "Name" "lycan_summon_wolves_hightail" + } + + } + } + } + "npc_ent" + { + "Model" "models/items/furion/treant/allfather_of_nature_treant/allfather_of_nature_treant.vmdl" + "BaseClass" "npc_dota_creature" + "SoundSet" "n_creep_Melee" + "GameSoundsFile" "soundevents/game_sounds_creeps.vsndevts" + "Level" "1" + "ModelScale" ".9" + "UseNeutralCreepBehavior" "0" + "Level" "5" + + "Ability1" "warpine_raider_seed_shot" + "Ability2" "ability_unit_less_laggy" + "Ability3" "" + "Ability4" "" + "Ability6" "" + + + "ArmorPhysical" "10" + "MagicalResistance" "55" + + "AttackCapabilities" "DOTA_UNIT_CAP_MELEE_ATTACK" + "AttackDamageType" "DAMAGE_TYPE_ArmorPhysical" + "AttackDamageMin" "36" + "AttackDamageMax" "42" + "AttackRate" "1" + "AttackAnimationPoint" "0.4" + "AttackAcquisitionRange" "300" + "AttackRange" "90" + + + "RingRadius" "40" + "HealthBarOffset" "150" + + + "BountyXP" "30" + "BountyGoldMin" "22" + "BountyGoldMax" "32" + + "MovementCapabilities" "DOTA_UNIT_CAP_MOVE_GROUND" + "MovementSpeed" "325" + + "StatusHealth" "320" + "StatusHealthRegen" "3.2" + "StatusMana" "100" + "StatusManaRegen" "2.0" + + "VisionDaytimeRange" "500" + "VisionNighttimeRange" "300" + + "TeamName" "DOTA_TEAM_NEUTRALS" + "CombatClassAttack" "DOTA_COMBAT_CLASS_ATTACK_BASIC" + "CombatClassDefend" "DOTA_COMBAT_CLASS_DEFEND_BASIC" + "UnitRelationshipClass" "DOTA_NPC_UNIT_RELATIONSHIP_TYPE_DEFAULT" + "Creature" + { + "OffensiveAbilities" + { + "Ability1" + { + "Name" "warpine_raider_seed_shot" + } + + } + } + } + "npc_skeleton_zombie_undead" + { + "Model" "models/items/undying/idol_of_ruination/ruin_wight_minion.vmdl" + "BaseClass" "npc_dota_creature" + "SoundSet" "n_creep_Melee" + "GameSoundsFile" "soundevents/game_sounds_creeps.vsndevts" + "ModelScale" ".85" + "BoundsHullName" "DOTA_HULL_SIZE_REGULAR" + "UseNeutralCreepBehavior" "0" + "Level" "11" + + "Ability1" "" + "Ability2" "bone_armor" + "Ability3" "ability_unit_less_laggy" + "Ability4" "" + "Ability6" "" + + // Armor + //---------------------------------------------------------------- + "ArmorPhysical" "15" + "MagicalResistance" "0" + + // Attack + //---------------------------------------------------------------- + "AttackCapabilities" "DOTA_UNIT_CAP_MELEE_ATTACK" + "AttackDamageType" "DAMAGE_TYPE_ArmorPhysical" + "AttackDamageMin" "90" + "AttackDamageMax" "110" + "AttackRate" "1.25" + "AttackAnimationPoint" "0.4" + "AttackAcquisitionRange" "1100" + "AttackRange" "130" + "BaseAttackSpeed" "140" + + // Bounds + //---------------------------------------------------------------- + "RingRadius" "40" + "HealthBarOffset" "170" + + // Bounty + //---------------------------------------------------------------- + "BountyXP" "17" + "BountyGoldMin" "0" + "BountyGoldMax" "0" + + // Movement + //---------------------------------------------------------------- + "MovementCapabilities" "DOTA_UNIT_CAP_MOVE_GROUND" + "MovementSpeed" "330" + + + // Status + //---------------------------------------------------------------- + "StatusHealth" "350" + "StatusHealthRegen" "0" + "StatusMana" "400" + "StatusManaRegen" "0.0" + + // Vision + //---------------------------------------------------------------- + "VisionDaytimeRange" "400" + "VisionNighttimeRange" "400" + + + // Team + //---------------------------------------------------------------- + "TeamName" "DOTA_TEAM_NEUTRALS" + "CombatClassAttack" "DOTA_COMBAT_CLASS_ATTACK_PIERCE" + "CombatClassDefend" "DOTA_COMBAT_CLASS_DEFEND_BASIC" + "UnitRelationshipClass" "DOTA_NPC_UNIT_RELATIONSHIP_TYPE_DEFAULT" + + } + "npc_skeleton_zombie_half_undead" + { + "Model" "models/items/undying/idol_of_ruination/ruin_wight_minion_torso.vmdl" + "BaseClass" "npc_dota_creature" + "SoundSet" "n_creep_Melee" + "GameSoundsFile" "soundevents/game_sounds_creeps.vsndevts" + "ModelScale" ".85" + "BoundsHullName" "DOTA_HULL_SIZE_REGULAR" + "UseNeutralCreepBehavior" "0" + "Level" "8" + + "Ability1" "" + "Ability2" "bone_armor" + "Ability3" "ability_unit_less_laggy" + "Ability4" "" + "Ability6" "" + + // Armor + //---------------------------------------------------------------- + "ArmorPhysical" "-5" + "MagicalResistance" "0" + + // Attack + //---------------------------------------------------------------- + "AttackCapabilities" "DOTA_UNIT_CAP_MELEE_ATTACK" + "AttackDamageType" "DAMAGE_TYPE_ArmorPhysical" + "AttackDamageMin" "145" + "AttackDamageMax" "150" + "AttackRate" "1.25" + "AttackAnimationPoint" "0.4" + "AttackAcquisitionRange" "1100" + "AttackRange" "130" + "BaseAttackSpeed" "140" + + // Bounds + //---------------------------------------------------------------- + "RingRadius" "40" + "HealthBarOffset" "170" + + // Bounty + //---------------------------------------------------------------- + "BountyXP" "17" + "BountyGoldMin" "0" + "BountyGoldMax" "0" + + // Movement + //---------------------------------------------------------------- + "MovementCapabilities" "DOTA_UNIT_CAP_MOVE_GROUND" + "MovementSpeed" "330" + + + // Status + //---------------------------------------------------------------- + "StatusHealth" "150" + "StatusHealthRegen" "0" + "StatusMana" "400" + "StatusManaRegen" "0.0" + + // Vision + //---------------------------------------------------------------- + "VisionDaytimeRange" "400" + "VisionNighttimeRange" "400" + + + // Team + //---------------------------------------------------------------- + "TeamName" "DOTA_TEAM_NEUTRALS" + "CombatClassAttack" "DOTA_COMBAT_CLASS_ATTACK_PIERCE" + "CombatClassDefend" "DOTA_COMBAT_CLASS_DEFEND_BASIC" + "UnitRelationshipClass" "DOTA_NPC_UNIT_RELATIONSHIP_TYPE_DEFAULT" + + } + "npc_dead_skeleton_undead" + { + "Model" "models/creeps/neutral_creeps/n_creep_troll_skeleton/n_creep_skeleton_melee.vmdl" + "BaseClass" "npc_dota_creature" + "SoundSet" "n_creep_Melee" + "GameSoundsFile" "soundevents/game_sounds_creeps.vsndevts" + "ModelScale" ".85" + "BoundsHullName" "DOTA_HULL_SIZE_REGULAR" + "UseNeutralCreepBehavior" "0" + "Level" "9" + + "Ability1" "bone_armor" + "Ability2" "ability_unit_less_laggy" + "Ability3" "" + "Ability4" "" + "Ability6" "" + + // Armor + //---------------------------------------------------------------- + "ArmorPhysical" "-5" + "MagicalResistance" "0" + + // Attack + //---------------------------------------------------------------- + "AttackCapabilities" "DOTA_UNIT_CAP_MELEE_ATTACK" + "AttackDamageType" "DAMAGE_TYPE_ArmorPhysical" + "AttackDamageMin" "45" + "AttackDamageMax" "50" + "AttackRate" "1.25" + "AttackAnimationPoint" "0.4" + "AttackAcquisitionRange" "1100" + "AttackRange" "130" + "BaseAttackSpeed" "140" + + // Bounds + //---------------------------------------------------------------- + "RingRadius" "40" + "HealthBarOffset" "170" + + // Bounty + //---------------------------------------------------------------- + "BountyXP" "17" + "BountyGoldMin" "0" + "BountyGoldMax" "0" + + // Movement + //---------------------------------------------------------------- + "MovementCapabilities" "DOTA_UNIT_CAP_MOVE_GROUND" + "MovementSpeed" "330" + + + // Status + //---------------------------------------------------------------- + "StatusHealth" "450" + "StatusHealthRegen" "0" + "StatusMana" "400" + "StatusManaRegen" "0.0" + + // Vision + //---------------------------------------------------------------- + "VisionDaytimeRange" "400" + "VisionNighttimeRange" "400" + + + // Team + //---------------------------------------------------------------- + "TeamName" "DOTA_TEAM_NEUTRALS" + "CombatClassAttack" "DOTA_COMBAT_CLASS_ATTACK_PIERCE" + "CombatClassDefend" "DOTA_COMBAT_CLASS_DEFEND_BASIC" + "UnitRelationshipClass" "DOTA_NPC_UNIT_RELATIONSHIP_TYPE_DEFAULT" + + } + "npc_dead_skeleton_archer_undead" + { + "Model" "models/heroes/clinkz/clinkz_archer.vmdl" + "BaseClass" "npc_dota_creature" + "SoundSet" "n_creep_Melee" + "GameSoundsFile" "soundevents/game_sounds_creeps.vsndevts" + "ModelScale" ".85" + "BoundsHullName" "DOTA_HULL_SIZE_REGULAR" + "UseNeutralCreepBehavior" "0" + "Level" "7" + + "Ability1" "ability_unit_less_laggy" + "Ability2" "bone_armor" + "Ability3" "skeleton_archer_fire_arrow" + "Ability4" "" + + // Armor + //---------------------------------------------------------------- + "ArmorPhysical" "-5" + "MagicalResistance" "0" + + // Attack + //---------------------------------------------------------------- + "AttackCapabilities" "DOTA_UNIT_CAP_RANGED_ATTACK" + "AttackDamageMin" "70" + "AttackDamageMax" "80" + "AttackDamageType" "DAMAGE_TYPE_ArmorPhysical" + "AttackRate" "1.7" + "AttackAnimationPoint" "0.3" + "AttackAcquisitionRange" "1000" + "AttackRange" "500" + "ProjectileModel" "particles/econ/items/clinkz/clinkz_maraxiform/clinkz_maraxiform_searing_arrow_deso.vpcf" + "ProjectileSpeed" "900" + + // Bounds + //---------------------------------------------------------------- + "RingRadius" "40" + "HealthBarOffset" "170" + + // Bounty + //---------------------------------------------------------------- + "BountyXP" "17" + "BountyGoldMin" "0" + "BountyGoldMax" "0" + + // Movement + //---------------------------------------------------------------- + "MovementCapabilities" "DOTA_UNIT_CAP_MOVE_GROUND" + "MovementSpeed" "330" + + + // Status + //---------------------------------------------------------------- + "StatusHealth" "250" + "StatusHealthRegen" "0" + "StatusMana" "400" + "StatusManaRegen" "0.0" + + // Vision + //---------------------------------------------------------------- + "VisionDaytimeRange" "400" + "VisionNighttimeRange" "400" + + + // Team + //---------------------------------------------------------------- + "TeamName" "DOTA_TEAM_NEUTRALS" + "CombatClassAttack" "DOTA_COMBAT_CLASS_ATTACK_PIERCE" + "CombatClassDefend" "DOTA_COMBAT_CLASS_DEFEND_BASIC" + "UnitRelationshipClass" "DOTA_NPC_UNIT_RELATIONSHIP_TYPE_DEFAULT" + } + "npc_mini_frog" + { + // General + //---------------------------------------------------------------- + "Model" "models/creeps/neutral_creeps/n_creep_tadpole_c/n_creep_tadpole_c.vmdl" // Model. + + "BaseClass" "npc_dota_creep_neutral" + "SoundSet" "n_creep_Ranged" + "Level" "2" + "ModelScale" ".45" + "IsNeutralUnitType" "1" + "UseNeutralCreepBehavior" "0" + + // Abilities + //---------------------------------------------------------------- + "Ability1" "frogmen_riverborn_aura" // Ability 1 + "Ability2" "mini_frog_catchy_lick" // Ability 2 + "Ability3" "" // Ability 3 + "Ability4" "ability_unit_less_laggy" // Ability 4 + "Ability6" "" + + // Armor + //---------------------------------------------------------------- + "ArmorPhysical" "1" // Physical protection. + + // Attack + //---------------------------------------------------------------- + "AttackCapabilities" "DOTA_UNIT_CAP_MELEE_ATTACK" + "AttackDamageMin" "19" // Damage range min. + "AttackDamageMax" "21" // Damage range max. + "AttackRate" "2" // Speed of attack. + "BaseAttackSpeed" "125" + "AttackAnimationPoint" "0.4" // Normalized time in animation cycle to attack. + "AttackAcquisitionRange" "800" // Range within a target can be acquired. + "AttackRange" "100" // Range within a target can be attacked. + "ProjectileModel" "particles/neutral_fx/gnoll_base_attack.vpcf" // Particle system model for projectile. + "ProjectileSpeed" "1500" // Speed of projectile. + + // Bounds + //---------------------------------------------------------------- + "RingRadius" "40" + "HealthBarOffset" "170" + + // Bounty + //---------------------------------------------------------------- + "BountyXP" "48" // Experience earn. + "BountyGoldMin" "69" // Gold earned min. + "BountyGoldMax" "84" // Gold earned max. + + // Movement + //---------------------------------------------------------------- + "MovementCapabilities" "DOTA_UNIT_CAP_MOVE_GROUND" + "MovementSpeed" "270" // Speed. + "MovementTurnRate" "0.9" // Turning rate. + + // Status + //---------------------------------------------------------------- + "StatusHealth" "400" // Base health. + "StatusHealthRegen" "0.5" // Health regeneration rate. + "StatusMana" "200" // Base mana. + "StatusManaRegen" "1.0" // Mana regeneration rate. + + // Vision + //---------------------------------------------------------------- + "VisionDaytimeRange" "400" // Range of vision during day light. + "VisionNighttimeRange" "400" // Range of vision at night time. + + // Team + //---------------------------------------------------------------- + "TeamName" "DOTA_TEAM_NEUTRALS" // Team name. + "UnitRelationshipClass" "DOTA_NPC_UNIT_RELATIONSHIP_TYPE_DEFAULT" + } + "npc_frogman_magi" + { + // General + //---------------------------------------------------------------- + "Model" "models/creeps/neutral_creeps/n_creep_froglet/n_creep_froglet_mage.vmdl" // Model. + "BaseClass" "npc_dota_creep_neutral" + "SoundSet" "n_creep_Melee" + "Level" "4" + "ModelScale" "1.0" + "IsNeutralUnitType" "1" + "UseNeutralCreepBehavior" "0" + // Abilities + //---------------------------------------------------------------- + "Ability1" "" // Ability 1 + "Ability2" "frog_magi_wave" // Ability 2 + "Ability3" "frog_magi_torrent" // Ability 3 + "Ability4" "frogmen_riverborn_aura" // Ability 4 + "Ability5" "" // Ability 4 + "Ability6" "" + + // Armor + //---------------------------------------------------------------- + "ArmorPhysical" "3" // Physical protection. + + // Attack + //---------------------------------------------------------------- + "AttackCapabilities" "DOTA_UNIT_CAP_RANGED_ATTACK" + "AttackDamageMin" "40" // Damage range min. + "AttackDamageMax" "45" // Damage range max. + "AttackRate" "2" // Speed of attack. + "BaseAttackSpeed" "150" // Speed of attack. + "AttackAnimationPoint" "0.3" // Normalized time in animation cycle to attack. + "AttackAcquisitionRange" "450" // Range within a target can be acquired. + "AttackRange" "450" // Range within a target can be attacked. + "ProjectileModel" "particles/units/heroes/hero_morphling/morphling_adaptive_strike_agi_proj.vpcf" // Particle system model for projectile. + "ProjectileSpeed" "1200" // Speed of projectile. + + // Bounds + //---------------------------------------------------------------- + "RingRadius" "50" + "HealthBarOffset" "180" + + // Bounty + //---------------------------------------------------------------- + "BountyXP" "86" // Experience earn. + "BountyGoldMin" "82" // Gold earned min. + "BountyGoldMax" "87" // Gold earned max. + + // Movement + //---------------------------------------------------------------- + "MovementCapabilities" "DOTA_UNIT_CAP_MOVE_GROUND" + "MovementSpeed" "300" // Speed. + "MovementTurnRate" "0.9" // Turning rate. + + // Status + //---------------------------------------------------------------- + "StatusHealth" "900" // Base health. + "StatusHealthRegen" "0.5" // Health regeneration rate. + "StatusMana" "350" // Base mana. + "StatusManaRegen" "1.0" // Mana regeneration rate. + + // Vision + //---------------------------------------------------------------- + "VisionDaytimeRange" "800" // Range of vision during day light. + "VisionNighttimeRange" "800" // Range of vision at night time. + + // Team + //---------------------------------------------------------------- + "TeamName" "DOTA_TEAM_NEUTRALS" // Team name. + "UnitRelationshipClass" "DOTA_NPC_UNIT_RELATIONSHIP_TYPE_DEFAULT" + } + "npc_frop_tadpole" + { + // General + //---------------------------------------------------------------- + "Model" "models/creeps/neutral_creeps/n_creep_tadpole/n_creep_tadpole_v2.vmdl" // Model. + "BaseClass" "npc_dota_creep_neutral" + "SoundSet" "n_creep_Melee" + "Level" "4" + "ModelScale" "0.85" + "IsNeutralUnitType" "0" + "UseNeutralCreepBehavior" "0" + + // Abilities + //---------------------------------------------------------------- + "Ability1" "frogmen_acid_jump" // Ability 1 + "Ability2" "frogmen_riverborn_aura" // Ability 2 + "Ability3" "" // Ability 3 + "Ability4" "ability_unit_less_laggy" // Ability 4 + "Ability6" "" + + // Armor + //---------------------------------------------------------------- + "ArmorPhysical" "2" // Physical protection. + + // Attack + //---------------------------------------------------------------- + "AttackCapabilities" "DOTA_UNIT_CAP_MELEE_ATTACK" + "AttackDamageMin" "22" // Damage range min. + "AttackDamageMax" "24" // Damage range max. + "AttackRate" "2" // Speed of attack. + "BaseAttackSpeed" "150" // Speed of attack. + "AttackAnimationPoint" "0.3" // Normalized time in animation cycle to attack. + "AttackAcquisitionRange" "500" // Range within a target can be acquired. + "AttackRange" "100" // Range within a target can be attacked. + "ProjectileModel" "" // Particle system model for projectile. + "ProjectileSpeed" "0" // Speed of projectile. + + // Bounds + //---------------------------------------------------------------- + "RingRadius" "60" + "HealthBarOffset" "190" + + // Bounty + //---------------------------------------------------------------- + "BountyXP" "66" // Experience earn. + "BountyGoldMin" "66" // Gold earned min. + "BountyGoldMax" "71" // Gold earned max. + + // Movement + //---------------------------------------------------------------- + "MovementCapabilities" "DOTA_UNIT_CAP_MOVE_GROUND" + "MovementSpeed" "290" // Speed. + + // Status + //---------------------------------------------------------------- + "StatusHealth" "700" // Base health. + "StatusHealthRegen" "0.5" // Health regeneration rate. + "StatusMana" "300" // Base mana. + "StatusManaRegen" "1" // Mana regeneration rate. + + // Vision + //---------------------------------------------------------------- + "VisionDaytimeRange" "800" // Range of vision during day light. + "VisionNighttimeRange" "800" // Range of vision at night time. + + // Team + //---------------------------------------------------------------- + "TeamName" "DOTA_TEAM_NEUTRALS" // Team name. + "UnitRelationshipClass" "DOTA_NPC_UNIT_RELATIONSHIP_TYPE_DEFAULT" + + } + "npc_small_frog_froglet" + { + // General + //---------------------------------------------------------------- + "Model" "models/creeps/neutral_creeps/n_creep_froglet/n_creep_froglet.vmdl" // Model. + "BaseClass" "npc_dota_creep_neutral" + "SoundSet" "n_creep_Melee" + "Level" "4" + "ModelScale" "1.2" + "IsNeutralUnitType" "0" + "UseNeutralCreepBehavior" "0" + + // Abilities + //---------------------------------------------------------------- + "Ability1" "frogmen_acid_jump" // Ability 1 + "Ability2" "frogmen_riverborn_aura" // Ability 2 + "Ability3" "" // Ability 3 + "Ability4" "ability_unit_less_laggy" // Ability 4 + "Ability6" "" + + // Armor + //---------------------------------------------------------------- + "ArmorPhysical" "3" // Physical protection. + + // Attack + //---------------------------------------------------------------- + "AttackCapabilities" "DOTA_UNIT_CAP_MELEE_ATTACK" + "AttackDamageMin" "41" // Damage range min. + "AttackDamageMax" "46" // Damage range max. + "AttackRate" "2" // Speed of attack. + "BaseAttackSpeed" "135" // Speed of attack. + "AttackAnimationPoint" "0.3" // Normalized time in animation cycle to attack. + "AttackAcquisitionRange" "500" // Range within a target can be acquired. + "AttackRange" "100" // Range within a target can be attacked. + "ProjectileModel" "" // Particle system model for projectile. + "ProjectileSpeed" "0" // Speed of projectile. + + // Bounds + //---------------------------------------------------------------- + "RingRadius" "50" + "HealthBarOffset" "180" + + // Bounty + //---------------------------------------------------------------- + "BountyXP" "86" // Experience earn. + "BountyGoldMin" "69" // Gold earned min. + "BountyGoldMax" "74" // Gold earned max. + + // Movement + //---------------------------------------------------------------- + "MovementCapabilities" "DOTA_UNIT_CAP_MOVE_GROUND" + "MovementSpeed" "300" // Speed. + "MovementTurnRate" "0.9" // Turning rate. + + // Status + //---------------------------------------------------------------- + "StatusHealth" "900" // Base health. + "StatusHealthRegen" "0.5" // Health regeneration rate. + "StatusMana" "350" // Base mana. + "StatusManaRegen" "1.0" // Mana regeneration rate. + + // Vision + //---------------------------------------------------------------- + "VisionDaytimeRange" "800" // Range of vision during day light. + "VisionNighttimeRange" "800" // Range of vision at night time. + + // Team + //---------------------------------------------------------------- + "TeamName" "DOTA_TEAM_NEUTRALS" // Team name. + "UnitRelationshipClass" "DOTA_NPC_UNIT_RELATIONSHIP_TYPE_DEFAULT" + } + "npc_black_dragon" + { + // General + //---------------------------------------------------------------- + "Model" "models/creeps/neutral_creeps/n_creep_black_dragon/n_creep_black_dragon.vmdl" // Model. + "BaseClass" "npc_dota_creep_neutral" + "SoundSet" "n_creep_Ranged" + "Level" "6" + "IsAncient" "0" + "ModelScale" "1" + "IsNeutralUnitType" "0" + "UseNeutralCreepBehavior" "0" + // Abilities + //---------------------------------------------------------------- + "Ability1" "black_dragon_fireball" // Ability 1 + "Ability2" "black_dragon_splash_attack" // Ability 2 + "Ability3" "black_dragon_dragonhide_aura" // Ability 3 + "Ability4" "ability_unit_less_laggy" // Ability 4 + "Ability6" "" + + // Armor + //---------------------------------------------------------------- + "ArmorPhysical" "4" // Physical protection. + "MagicalResistance" "30" // Magical protection. + + // Attack + //---------------------------------------------------------------- + "AttackCapabilities" "DOTA_UNIT_CAP_RANGED_ATTACK" + "AttackDamageMin" "62" // Damage range min. + "AttackDamageMax" "68" // Damage range max. + "AttackRate" "2" // Speed of attack. + "BaseAttackSpeed" "135" // Speed of attack. + "AttackAnimationPoint" "0.5" // Normalized time in animation cycle to attack. + "AttackAcquisitionRange" "300" // Range within a target can be acquired. + "AttackRange" "400" // Range within a target can be attacked. + "ProjectileModel" "particles/neutral_fx/black_dragon_attack.vpcf" // Particle system model for projectile. + "ProjectileSpeed" "1500" // Speed of projectile. + + // Bounds + //---------------------------------------------------------------- + "RingRadius" "70" + "HealthBarOffset" "300" + + // Bounty + //---------------------------------------------------------------- + "BountyXP" "192" // Experience earn. + "BountyGoldMin" "94" // Gold earned min. + "BountyGoldMax" "99" // Gold earned max. + + // Movement + //---------------------------------------------------------------- + "MovementCapabilities" "DOTA_UNIT_CAP_MOVE_GROUND" + "MovementSpeed" "300" // Speed. + "MovementTurnRate" "0.9" // Turning rate. + + // Status + //---------------------------------------------------------------- + "StatusHealth" "4000" // Base health. + "StatusHealthRegen" "2" // Health regeneration rate. + "StatusMana" "500" // Base mana. + "StatusManaRegen" "1" // Mana regeneration rate. + + // Vision + //---------------------------------------------------------------- + "VisionDaytimeRange" "800" // Range of vision during day light. + "VisionNighttimeRange" "800" // Range of vision at night time. + + // Team + //---------------------------------------------------------------- + "TeamName" "DOTA_TEAM_NEUTRALS" // Team name. + "UnitRelationshipClass" "DOTA_NPC_UNIT_RELATIONSHIP_TYPE_DEFAULT" + } + "npc_blue_dragon" + { + // General + //---------------------------------------------------------------- + "Model" "models/creeps/neutral_creeps/n_creep_satyr_spawn_a/n_creep_satyr_spawn_a.vmdl" // Model. + "BaseClass" "npc_dota_creep_neutral" + "SoundSet" "n_creep_Melee" + "Level" "6" + "IsAncient" "0" + "ModelScale" "1.15" + "IsNeutralUnitType" "0" + "UseNeutralCreepBehavior" "0" + + // Abilities + //---------------------------------------------------------------- + "Ability1" "spawnlord_master_stomp" // Ability 1 + "Ability2" "ability_unit_less_laggy" // Ability 2 + "Ability3" "creep_piercing" // Ability 3 + "Ability4" "" // Ability 4 + "Ability6" "" + + // Armor + //---------------------------------------------------------------- + "ArmorPhysical" "12" // Physical protection. + + // Attack + //---------------------------------------------------------------- + "AttackCapabilities" "DOTA_UNIT_CAP_MELEE_ATTACK" + "AttackDamageMin" "129" // Damage range min. + "AttackDamageMax" "145" // Damage range max. + "AttackRate" "2" // Speed of attack. + "BaseAttackSpeed" "135" // Speed of attack. + "AttackAnimationPoint" "0.3" // Normalized time in animation cycle to attack. + "AttackAcquisitionRange" "500" // Range within a target can be acquired. + "AttackRange" "100" // Range within a target can be attacked. + "ProjectileModel" "" // Particle system model for projectile. + "ProjectileSpeed" "0" // Speed of projectile. + + // Bounds + //---------------------------------------------------------------- + "RingRadius" "80" + "HealthBarOffset" "220" + + // Bounty + //---------------------------------------------------------------- + "BountyXP" "192" // Experience earn. + "BountyGoldMin" "94" // Gold earned min. + "BountyGoldMax" "99" // Gold earned max. + + // Movement + //---------------------------------------------------------------- + "MovementCapabilities" "DOTA_UNIT_CAP_MOVE_GROUND" + "MovementSpeed" "300" // Speed. + "MovementTurnRate" "0.9" // Turning rate. + + // Status + //---------------------------------------------------------------- + "StatusHealth" "1400" // Base health. + "StatusHealthRegen" "0.5" // Health regeneration rate. + "StatusMana" "400" // Base mana. + "StatusManaRegen" "1" // Mana regeneration rate. + + // Vision + //---------------------------------------------------------------- + "VisionDaytimeRange" "800" // Range of vision during day light. + "VisionNighttimeRange" "800" // Range of vision at night time. + + // Team + //---------------------------------------------------------------- + "TeamName" "DOTA_TEAM_NEUTRALS" // Team name. + "UnitRelationshipClass" "DOTA_NPC_UNIT_RELATIONSHIP_TYPE_DEFAULT" + + } + "npc_blue_dragon_small" + { + // General + //---------------------------------------------------------------- + "Model" "models/creeps/neutral_creeps/n_creep_satyr_spawn_a/n_creep_satyr_spawn_b.vmdl" // Model. + "BaseClass" "npc_dota_creep_neutral" + "SoundSet" "n_creep_Ranged" + "Level" "6" + "IsAncient" "1" + "UseNeutralCreepBehavior" "0" + "ModelScale" "1.15" + "IsNeutralUnitType" "0" + + // Abilities + //---------------------------------------------------------------- + "Ability1" "spawnlord_aura" // Ability 1 + "Ability2" "spawnlord_master_freeze" // Ability 2 + "Ability3" "ability_unit_less_laggy" // Ability 3 + "Ability4" "creep_piercing" // Ability 4 + "Ability6" "" + + // Armor + //---------------------------------------------------------------- + "ArmorPhysical" "11" // Physical protection. + + // Attack + //---------------------------------------------------------------- + "AttackCapabilities" "DOTA_UNIT_CAP_RANGED_ATTACK" + "AttackDamageMin" "73" // Damage range min. + "AttackDamageMax" "89" // Damage range max. + "AttackRate" "2" // Speed of attack. + "BaseAttackSpeed" "135" // Speed of attack. + "AttackAnimationPoint" "0.3" // Normalized time in animation cycle to attack. + "AttackAcquisitionRange" "650" // Range within a target can be acquired. + "AttackRange" "650" // Range within a target can be attacked. + "ProjectileModel" "particles/units/heroes/hero_witchdoctor/witchdoctor_maledict_projectile.vpcf" // Particle system model for projectile. + "ProjectileSpeed" "1000" // Speed of projectile. + // Bounds + //---------------------------------------------------------------- + "RingRadius" "70" + "HealthBarOffset" "200" + + // Bounty + //---------------------------------------------------------------- + "BountyXP" "149" // Experience earn. + "BountyGoldMin" "121" // Gold earned min. + "BountyGoldMax" "129" // Gold earned max. + + // Movement + //---------------------------------------------------------------- + "MovementCapabilities" "DOTA_UNIT_CAP_MOVE_GROUND" + "MovementSpeed" "270" // Speed. + "MovementTurnRate" "0.9" // Turning rate. + + // Status + //---------------------------------------------------------------- + "StatusHealth" "750" // Base health. + "StatusHealthRegen" "0.5" // Health regeneration rate. + "StatusMana" "400" // Base mana. + "StatusManaRegen" "1" // Mana regeneration rate. + + // Vision + //---------------------------------------------------------------- + "VisionDaytimeRange" "800" // Range of vision during day light. + "VisionNighttimeRange" "800" // Range of vision at night time. + + + + + // Team + //---------------------------------------------------------------- + "TeamName" "DOTA_TEAM_NEUTRALS" // Team name. + "UnitRelationshipClass" "DOTA_NPC_UNIT_RELATIONSHIP_TYPE_DEFAULT" + } + "npc_red_dragon" + { + // General + //---------------------------------------------------------------- + "Model" "models/creeps/neutral_creeps/n_creep_dragonspawn_a/n_creep_dragonspawn_a.vmdl" // Model. + "BaseClass" "npc_dota_creep_neutral" + "SoundSet" "n_creep_Melee" + "Level" "6" + "IsAncient" "0" + "ModelScale" "1" + "IsNeutralUnitType" "0" + "UseNeutralCreepBehavior" "0" + // Abilities + //---------------------------------------------------------------- + "Ability1" "alpha_wolf_critical_strike" // Ability 1 + "Ability2" "blue_dragonspawn_overseer_devotion_aura" // Ability 2 + "Ability3" "blue_dragonspawn_sorcerer_evasion" // Ability 3 + "Ability4" "" // Ability 4 + "Ability5" "ability_unit_less_laggy" // Ability 4 + "Ability6" "" + // Armor + //---------------------------------------------------------------- + "ArmorPhysical" "12" // Physical protection. + "MagicalResistance" "30" // Magical protection. + + // Attack + //---------------------------------------------------------------- + "AttackCapabilities" "DOTA_UNIT_CAP_MELEE_ATTACK" + "AttackDamageMin" "92" // Damage range min. + "AttackDamageMax" "108" // Damage range max. + "AttackRate" "2" // Speed of attack. + "BaseAttackSpeed" "135" // Speed of attack. + "AttackAnimationPoint" "0.5" // Normalized time in animation cycle to attack. + "AttackAcquisitionRange" "300" // Range within a target can be acquired. + "AttackRange" "180" // Range within a target can be attacked. + + + // Bounds + //---------------------------------------------------------------- + "RingRadius" "70" + "HealthBarOffset" "300" + + // Bounty + //---------------------------------------------------------------- + "BountyXP" "192" // Experience earn. + "BountyGoldMin" "94" // Gold earned min. + "BountyGoldMax" "99" // Gold earned max. + + // Movement + //---------------------------------------------------------------- + "MovementCapabilities" "DOTA_UNIT_CAP_MOVE_GROUND" + "MovementSpeed" "300" // Speed. + "MovementTurnRate" "0.9" // Turning rate. + + // Status + //---------------------------------------------------------------- + "StatusHealth" "4000" // Base health. + "StatusHealthRegen" "2" // Health regeneration rate. + "StatusMana" "500" // Base mana. + "StatusManaRegen" "1" // Mana regeneration rate. + + + // Vision + //---------------------------------------------------------------- + "VisionDaytimeRange" "800" // Range of vision during day light. + "VisionNighttimeRange" "800" // Range of vision at night time. + + // Team + //---------------------------------------------------------------- + "TeamName" "DOTA_TEAM_NEUTRALS" // Team name. + "UnitRelationshipClass" "DOTA_NPC_UNIT_RELATIONSHIP_TYPE_DEFAULT" + } + "npc_red_dragon_small" + { + // General + //---------------------------------------------------------------- + "Model" "models/heroes/invoker_kid/invoker_kid_trainer_dragon.vmdl" // Model. + "BaseClass" "npc_dota_creep_neutral" + "SoundSet" "n_creep_Ranged" + "Level" "6" + "IsAncient" "0" + "ModelScale" "1" + "IsNeutralUnitType" "0" + "UseNeutralCreepBehavior" "0" + // Abilities + //---------------------------------------------------------------- + "Ability1" "" // Ability 1 + "Ability2" "black_dragon_splash_attack" // Ability 2 + "Ability3" "black_dragon_dragonhide_aura" // Ability 3 + "Ability4" "ability_unit_less_laggy" // Ability 4 + "Ability6" "" + + // Armor + //---------------------------------------------------------------- + "ArmorPhysical" "4" // Physical protection. + "MagicalResistance" "30" // Magical protection. + + // Attack + //---------------------------------------------------------------- + "AttackCapabilities" "DOTA_UNIT_CAP_RANGED_ATTACK" + "AttackDamageMin" "110" // Damage range min. + "AttackDamageMax" "110" // Damage range max. + "AttackDamageType" "DAMAGE_TYPE_ArmorPhysical" + "AttackRate" "1.35" // Speed of attack. + "AttackAnimationPoint" "0.2" // Normalized time in animation cycle to attack. + "AttackAcquisitionRange" "900" // Range within a target can be acquired. + "AttackRange" "900" // Range within a target can be attacked. + "ProjectileModel" "particles/units/heroes/hero_invoker/invoker_forged_spirit_projectile.vpcf" // Particle system model for projectile. + "ProjectileSpeed" "1000" // Speed of projectile. + + // Bounty + //---------------------------------------------------------------- + "BountyXP" "49" // Experience earn. + "BountyGoldMin" "114" // Gold earned min. + "BountyGoldMax" "119" // Gold earned max. + + // Bounds + //---------------------------------------------------------------- + "BoundsHullName" "DOTA_HULL_SIZE_REGULAR" // Hull type used for navigation/locomotion. + "HealthBarOffset" "270" + + // Movement + //---------------------------------------------------------------- + "MovementCapabilities" "DOTA_UNIT_CAP_MOVE_GROUND" // Type of locomotion - ground, air + "MovementSpeed" "420" // Speed + "MovementTurnRate" "0.5" // Turning rate. + + // Status + //---------------------------------------------------------------- + "StatusHealth" "750" // Base health. + "StatusHealthRegen" "0.25" // Health regeneration rate. + "StatusMana" "0" // Base mana. + "StatusManaRegen" "4.0" // Mana regeneration rate. + "StatusManaRegen" "1" // Mana regeneration rate. + + // Vision + //---------------------------------------------------------------- + "VisionDaytimeRange" "800" // Range of vision during day light. + "VisionNighttimeRange" "800" // Range of vision at night time. + + // Team + //---------------------------------------------------------------- + "TeamName" "DOTA_TEAM_NEUTRALS" // Team name. + "UnitRelationshipClass" "DOTA_NPC_UNIT_RELATIONSHIP_TYPE_DEFAULT" + } + + "npc_witch" + { + "Model" "models/heroes/death_prophet/death_prophet.vmdl" + "BaseClass" "npc_dota_creature" + "SoundSet" "n_creep_Ranged" + "GameSoundsFile" "soundevents/game_sounds_creeps.vsndevts" + "ModelScale" "1.1" + "IsAncient" "1" + "BoundsHullName" "DOTA_HULL_SIZE_REGULAR" + "UseNeutralCreepBehavior" "0" + "MinimapIcon" "minimap_roshancamp" + "MinimapIconSize" "450" + "Ability1" "death_prophet_carrion_swarm" + "Ability2" "witch_base" + "Ability3" "death_prophet_spirit_siphon" + "Ability4" "ability_unit_less_laggy" + "Level" "15" + + // Armor + //---------------------------------------------------------------- + "ArmorPhysical" "15" + "MagicalResistance" "0" + + // Attack + //---------------------------------------------------------------- + "AttackCapabilities" "DOTA_UNIT_CAP_RANGED_ATTACK" + "AttackDamageType" "DAMAGE_TYPE_ArmorPhysical" + "AttackDamageMin" "225" + "AttackDamageMax" "285" + "AttackRate" "1.25" + "AttackAnimationPoint" "0.4" + "AttackAcquisitionRange" "1100" + "BaseAttackSpeed" "140" + "AttackAcquisitionRange" "600" + "AttackRange" "600" + "ProjectileModel" "particles/units/heroes/hero_death_prophet/death_prophet_base_attack.vpcf" + "ProjectileSpeed" "1000" + + + // Bounds + //---------------------------------------------------------------- + "RingRadius" "40" + "HealthBarOffset" "170" + + // Bounty + //---------------------------------------------------------------- + "BountyXP" "624" + "BountyGoldMin" "0" + "BountyGoldMax" "0" + + // Movement + //---------------------------------------------------------------- + "MovementCapabilities" "DOTA_UNIT_CAP_MOVE_FLY" + "MovementSpeed" "330" + + + // Status + //---------------------------------------------------------------- + "StatusHealth" "2000" + "StatusHealthRegen" "15" + "StatusMana" "1200" + "StatusManaRegen" "10.0" + + // Vision + //---------------------------------------------------------------- + "VisionDaytimeRange" "400" + "VisionNighttimeRange" "400" + + + // Team + //---------------------------------------------------------------- + "TeamName" "DOTA_TEAM_BADGUYS" + "CombatClassAttack" "DOTA_COMBAT_CLASS_ATTACK_PIERCE" + "CombatClassDefend" "DOTA_COMBAT_CLASS_DEFEND_BASIC" + "UnitRelationshipClass" "DOTA_NPC_UNIT_RELATIONSHIP_TYPE_DEFAULT" + "Creature" + { + + "OffensiveAbilities" + { + "Ability1" + { + "Name" "death_prophet_carrion_swarm" + } + "Ability3" + { + "Name" "death_prophet_spirit_siphon" + } + + } + "AttachWearables" + { + "Wearable1" { "ItemDef" "5597" } + "Wearable1" { "ItemDef" "5598" } + "Wearable1" { "ItemDef" "5599" } + "Wearable1" { "ItemDef" "5600" } + "Wearable1" { "ItemDef" "5603" } + } + + } + + } + "npc_thief_backer" + { + // General + //---------------------------------------------------------------- + "Model" "models/creeps/thief_01.vmdl" // Model. + "BaseClass" "npc_dota_creature" + "SoundSet" "n_creep_Melee" + "ModelScale" "1" + "UseNeutralCreepBehavior" "0" + + // Abilities + //---------------------------------------------------------------- + "Ability1" "thief_charge" // Ability 1 + "Ability2" "ability_unit_less_laggy" // Ability 2 + "Ability3" "" // Ability 3 + "Ability4" "" // Ability 4 + + "ArmorPhysical" "25" // Physical protection. + "MagicalResistance" "40" + + + "AttackCapabilities" "DOTA_UNIT_CAP_MELEE_ATTACK" + "AttackDamageMin" "420" // Damage range min. + "AttackDamageMax" "495" // Damage range max. + "AttackRate" "1.7" // Speed of attack. + "BaseAttackSpeed" "220" + + "AttackAnimationPoint" "0.38" // Normalized time in animation cycle to attack. + "AttackAcquisitionRange" "500" // Range within a target can be acquired. + "AttackRange" "110" // Range within a target can be attacked. + "ProjectileModel" "" // Particle system model for projectile. + "ProjectileSpeed" "0" // Speed of projectile. + + // Bounds + //---------------------------------------------------------------- + "RingRadius" "50" + "HealthBarOffset" "180" + + // Xp + //---------------------------------------------------------------- + "BountyXP" "1" // Experience earn. + + // Movement + //---------------------------------------------------------------- + "MovementCapabilities" "DOTA_UNIT_CAP_MOVE_GROUND" + "MovementSpeed" "330" // Speed. + "MovementTurnRate" "0.9" // Turning rate. + + // Status + //---------------------------------------------------------------- + "StatusHealth" "2325" // Base health. + "StatusHealthRegen" "1.0" // Health regeneration rate. + "StatusMana" "0" // Base mana. + "StatusManaRegen" "1.0" // Mana regeneration rate. + + + // Vision + //---------------------------------------------------------------- + "VisionDaytimeRange" "800" // Range of vision during day light. + "VisionNighttimeRange" "800" // Range of vision at night time. + + // Team + //---------------------------------------------------------------- + "TeamName" "DOTA_TEAM_NEUTRALS" // Team name. + "UnitRelationshipClass" "DOTA_NPC_UNIT_RELATIONSHIP_TYPE_DEFAULT" + "Creature" + { + "OffensiveAbilities" + { + "Ability1" + { + "Name" "thief_charge" + } + + } + } + } + "npc_thief_leader" + { + // General + //---------------------------------------------------------------- + "Model" "models/creeps/thief_01_leader.vmdl" // Model. + "BaseClass" "npc_dota_creature" + "SoundSet" "n_creep_Melee" + "ModelScale" "1" + "UseNeutralCreepBehavior" "0" + + + // Abilities + //---------------------------------------------------------------- + "Ability1" "agro_leader" // Ability 1 + "Ability2" "ability_unit_less_laggy" // Ability 2 + "Ability3" "" // Ability 3 + "Ability4" "" // Ability 4 + + // Stats + //---------------------------------------------------------------- + "ArmorPhysical" "40" // Physical protection. + "MagicalResistance" "65" + "AttackCapabilities" "DOTA_UNIT_CAP_MELEE_ATTACK" + "AttackDamageMin" "748" // Damage range min. + "AttackDamageMax" "752" // Damage range max. + "AttackRate" "2" // Speed of attack. + "BaseAttackSpeed" "110" + "AttackAnimationPoint" "0.38" // Normalized time in animation cycle to attack. + "AttackAcquisitionRange" "500" // Range within a target can be acquired. + "AttackRange" "110" // Range within a target can be attacked. + "ProjectileModel" "" // Particle system model for projectile. + "ProjectileSpeed" "0" // Speed of projectile. + + // Bounds + //---------------------------------------------------------------- + "RingRadius" "50" + "HealthBarOffset" "180" + + // Xp + //---------------------------------------------------------------- + "BountyXP" "1" // Experience earn. + + // Movement + //---------------------------------------------------------------- + "MovementCapabilities" "DOTA_UNIT_CAP_MOVE_GROUND" + "MovementSpeed" "330" // Speed. + "MovementTurnRate" "0.9" // Turning rate. + + // Status + //---------------------------------------------------------------- + "StatusHealth" "6550" // Base health. + "StatusHealthRegen" "5.0" // Health regeneration rate. + "StatusMana" "0" // Base mana. + "StatusManaRegen" "1.0" // Mana regeneration rate. + + + // Vision + //---------------------------------------------------------------- + "VisionDaytimeRange" "800" // Range of vision during day light. + "VisionNighttimeRange" "800" // Range of vision at night time. + + // Team + //---------------------------------------------------------------- + "TeamName" "DOTA_TEAM_NEUTRALS" // Team name. + "UnitRelationshipClass" "DOTA_NPC_UNIT_RELATIONSHIP_TYPE_DEFAULT" + } + "npc_thief_archer" + { + // General + //---------------------------------------------------------------- + "Model" "models/creeps/thief_01_archer.vmdl" // Model. + "BaseClass" "npc_dota_creature" + "SoundSet" "n_creep_Melee" + "ModelScale" "1" + "UseNeutralCreepBehavior" "0" + + + // Abilities + //---------------------------------------------------------------- + "Ability1" "thief_arrow" // Ability 1 + "Ability2" "ability_unit_less_laggy" // Ability 2 + "Ability3" "" // Ability 3 + "Ability4" "" // Ability 4 + + // Stats + //---------------------------------------------------------------- + "AttackCapabilities" "DOTA_UNIT_CAP_RANGED_ATTACK" + "AttackDamageMin" "2230" // Damage range min. + "AttackDamageMax" "2440" // Damage range max. + "AttackDamageType" "DAMAGE_TYPE_ArmorPhysical" + "AttackRate" "1.7" // Speed of attack. + "AttackAnimationPoint" "0.3" // Normalized time in animation cycle to attack. + "AttackAcquisitionRange" "1000" // Range within a target can be acquired. + "AttackRange" "500" // Range within a target can be attacked. + "ProjectileModel" "particles/units/heroes/hero_drow/drow_base_attack.vpcf" // Particle system model for projectile. + "ProjectileSpeed" "900" // Speed of projectile. + "BaseAttackSpeed" "200" + + //---------------------------------------------------------------- + "RingRadius" "50" + "HealthBarOffset" "180" + + // Xp + //---------------------------------------------------------------- + "BountyXP" "1" // Experience earn. + + // Movement + //---------------------------------------------------------------- + "MovementCapabilities" "DOTA_UNIT_CAP_MOVE_GROUND" + "MovementSpeed" "330" // Speed. + "MovementTurnRate" "0.9" // Turning rate. + + // Status + //---------------------------------------------------------------- + "StatusHealth" "1275" // Base health. + "StatusHealthRegen" "1.0" // Health regeneration rate. + "StatusMana" "0" // Base mana. + "StatusManaRegen" "1.0" // Mana regeneration rate. + + + // Vision + //---------------------------------------------------------------- + "VisionDaytimeRange" "800" // Range of vision during day light. + "VisionNighttimeRange" "800" // Range of vision at night time. + + // Team + //---------------------------------------------------------------- + "TeamName" "DOTA_TEAM_NEUTRALS" // Team name. + "UnitRelationshipClass" "DOTA_NPC_UNIT_RELATIONSHIP_TYPE_DEFAULT" + } + "npc_ravenous_woodfang" + { + // General + //---------------------------------------------------------------- + "Model" "models/items/furion/treant/ravenous_woodfang/ravenous_woodfang.vmdl" + "BaseClass" "npc_dota_creep_neutral" + "SoundSet" "n_creep_Melee" + "Level" "6" + "ModelScale" "0.75" + "IsNeutralUnitType" "0" + "UseNeutralCreepBehavior" "0" + + // Abilities + //---------------------------------------------------------------- + "Ability1" "spider_bite" + "Ability2" "ability_unit_less_laggy" + "Ability3" "spider_snake_passive" + "Ability4" "chicken_attack" + "Ability6" "" + + // Armor + //---------------------------------------------------------------- + "ArmorPhysical" "5" + + // Attack + //---------------------------------------------------------------- + "AttackCapabilities" "DOTA_UNIT_CAP_MELEE_ATTACK" + "AttackDamageMin" "266" + "AttackDamageMax" "274" + "AttackRate" "1.9" + "BaseAttackSpeed" "155" + "AttackAnimationPoint" "0.35" + "AttackAcquisitionRange" "600" + "AttackRange" "100" + "ProjectileModel" "" + "ProjectileSpeed" "0" + + // Bounds + //---------------------------------------------------------------- + "RingRadius" "70" + "HealthBarOffset" "220" + + // Bounty + //---------------------------------------------------------------- + "BountyXP" "118" + "BountyGoldMin" "118" + "BountyGoldMax" "127" + + // Movement + //---------------------------------------------------------------- + "MovementCapabilities" "DOTA_UNIT_CAP_MOVE_GROUND" + "MovementSpeed" "315" + "MovementTurnRate" "0.8" + + // Status + //---------------------------------------------------------------- + "StatusHealth" "1650" + "StatusHealthRegen" "1.0" + "StatusMana" "250" + "StatusManaRegen" "1.0" + + // Vision + //---------------------------------------------------------------- + "VisionDaytimeRange" "800" + "VisionNighttimeRange" "800" + + // Team + //---------------------------------------------------------------- + "TeamName" "DOTA_TEAM_NEUTRALS" + "UnitRelationshipClass" "DOTA_NPC_UNIT_RELATIONSHIP_TYPE_DEFAULT" + } + "npc_venomancer_brute" + { + // General + //---------------------------------------------------------------- + "Model" "models/heroes/venomancer/venomancer.vmdl" + "BaseClass" "npc_dota_creep_neutral" + "SoundSet" "n_creep_Melee" + "Level" "6" + "ModelScale" "1.0" + "IsNeutralUnitType" "0" + "UseNeutralCreepBehavior" "0" + + // Abilities + //---------------------------------------------------------------- + "Ability1" "venomancer_poison" + "Ability2" "venomous_gale" + "Ability3" "spider_snake_passive" + "Ability4" "" + "Ability6" "ability_unit_less_laggy" + + // Armor + //---------------------------------------------------------------- + "ArmorPhysical" "6" + + // Attack + //---------------------------------------------------------------- + "AttackCapabilities" "DOTA_UNIT_CAP_MELEE_ATTACK" + "AttackDamageMin" "72" + "AttackDamageMax" "80" + "AttackRate" "1.8" + "BaseAttackSpeed" "160" + "AttackAnimationPoint" "0.33" + "AttackAcquisitionRange" "600" + "AttackRange" "110" + "ProjectileModel" "" + "ProjectileSpeed" "0" + + // Bounds + //---------------------------------------------------------------- + "RingRadius" "60" + "HealthBarOffset" "210" + + // Bounty + //---------------------------------------------------------------- + "BountyXP" "133" + "BountyGoldMin" "135" + "BountyGoldMax" "145" + + // Movement + //---------------------------------------------------------------- + "MovementCapabilities" "DOTA_UNIT_CAP_MOVE_GROUND" + "MovementSpeed" "420" + "MovementTurnRate" "0.85" + + // Status + //---------------------------------------------------------------- + "StatusHealth" "1800" + "StatusHealthRegen" "1.2" + "StatusMana" "250" + "StatusManaRegen" "1.0" + + // Vision + //---------------------------------------------------------------- + "VisionDaytimeRange" "800" + "VisionNighttimeRange" "800" + + // Team + //---------------------------------------------------------------- + "TeamName" "DOTA_TEAM_NEUTRALS" + "UnitRelationshipClass" "DOTA_NPC_UNIT_RELATIONSHIP_TYPE_DEFAULT" + } + "npc_lycosidae_stalker" + { + // General + //---------------------------------------------------------------- + "Model" "models/items/broodmother/spiderling/lycosidae_spiderling/lycosidae_spiderling.vmdl" + "BaseClass" "npc_dota_creep_neutral" + "SoundSet" "n_creep_Melee" + "Level" "7" + "ModelScale" "0.35" + "IsNeutralUnitType" "0" + "UseNeutralCreepBehavior" "0" + + // Abilities + //---------------------------------------------------------------- + "Ability1" "spider_snake_passive" + "Ability2" "spider_hunger" + "Ability3" "spider_bite" + "Ability4" "" + "Ability6" "ability_unit_less_laggy" + + // Armor + //---------------------------------------------------------------- + "ArmorPhysical" "7" + + // Attack + //---------------------------------------------------------------- + "AttackCapabilities" "DOTA_UNIT_CAP_MELEE_ATTACK" + "AttackDamageMin" "84" + "AttackDamageMax" "95" + "AttackRate" "1.7" + "BaseAttackSpeed" "165" + "AttackAnimationPoint" "0.3" + "AttackAcquisitionRange" "650" + "AttackRange" "110" + "ProjectileModel" "" + "ProjectileSpeed" "0" + + // Bounds + //---------------------------------------------------------------- + "RingRadius" "55" + "HealthBarOffset" "220" + + // Bounty + //---------------------------------------------------------------- + "BountyXP" "149" + "BountyGoldMin" "157" + "BountyGoldMax" "169" + + // Movement + //---------------------------------------------------------------- + "MovementCapabilities" "DOTA_UNIT_CAP_MOVE_GROUND" + "MovementSpeed" "550" + "MovementTurnRate" "0.9" + + // Status + //---------------------------------------------------------------- + "StatusHealth" "2100" + "StatusHealthRegen" "1.5" + "StatusMana" "300" + "StatusManaRegen" "1.2" + + // Vision + //---------------------------------------------------------------- + "VisionDaytimeRange" "850" + "VisionNighttimeRange" "850" + + // Team + //---------------------------------------------------------------- + "TeamName" "DOTA_TEAM_NEUTRALS" + "UnitRelationshipClass" "DOTA_NPC_UNIT_RELATIONSHIP_TYPE_DEFAULT" + } + "npc_bomb" + { + "Model" "maps/reef_assets/models/props/chains/darkreef_chains_mine.vmdl" + "BaseClass" "npc_dota_creature" + "SoundSet" "n_creep_Melee" + "GameSoundsFile" "soundevents/game_sounds_creeps.vsndevts" + "Level" "1" + "ModelScale" ".9" + "UseNeutralCreepBehavior" "0" + "Level" "1" + + "Ability1" "fish_basic" + "Ability2" "ability_unit_less_laggy" + "Ability3" "" + "Ability4" "" + + "ArmorPhysical" "0" + "MagicalResistance" "0" + + "AttackCapabilities" "DOTA_UNIT_CAP_MELEE_ATTACK" + "AttackDamageType" "DAMAGE_TYPE_ArmorPhysical" + "AttackDamageMin" "29" + "AttackDamageMax" "32" + "AttackRate" "3" + "AttackAnimationPoint" "0.4" + "AttackAcquisitionRange" "300" + "AttackRange" "90" + + "RingRadius" "40" + "HealthBarOffset" "170" + + "BountyXP" "7" + "BountyGoldMin" "10" + "BountyGoldMax" "15" + + "MovementCapabilities" "DOTA_UNIT_CAP_MOVE_GROUND" + "MovementSpeed" "260" + + "StatusHealth" "155" + "StatusHealthRegen" "15.0" + "StatusMana" "0" + "StatusManaRegen" "0.0" + + "VisionDaytimeRange" "500" + "VisionNighttimeRange" "300" + + "TeamName" "DOTA_TEAM_NEUTRALS" + "CombatClassAttack" "DOTA_COMBAT_CLASS_ATTACK_BASIC" + "CombatClassDefend" "DOTA_COMBAT_CLASS_DEFEND_BASIC" + "UnitRelationshipClass" "DOTA_NPC_UNIT_RELATIONSHIP_TYPE_DEFAULT" + + } + "npc_fish_1" + { + "Model" "models/items/hex/fish_hex/fish_hex.vmdl" + "BaseClass" "npc_dota_creature" + "SoundSet" "n_creep_Melee" + "GameSoundsFile" "soundevents/game_sounds_creeps.vsndevts" + "Level" "1" + "ModelScale" ".9" + "UseNeutralCreepBehavior" "0" + "Level" "1" + + "Ability1" "fish_basic" + "Ability2" "ability_unit_less_laggy" + "Ability3" "" + "Ability4" "" + + "ArmorPhysical" "0" + "MagicalResistance" "0" + + "AttackCapabilities" "DOTA_UNIT_CAP_MELEE_ATTACK" + "AttackDamageType" "DAMAGE_TYPE_ArmorPhysical" + "AttackDamageMin" "29" + "AttackDamageMax" "32" + "AttackRate" "3" + "AttackAnimationPoint" "0.4" + "AttackAcquisitionRange" "300" + "AttackRange" "90" + + "RingRadius" "40" + "HealthBarOffset" "170" + + "BountyXP" "7" + "BountyGoldMin" "10" + "BountyGoldMax" "15" + + "MovementCapabilities" "DOTA_UNIT_CAP_MOVE_GROUND" + "MovementSpeed" "260" + + "StatusHealth" "155" + "StatusHealthRegen" "15.0" + "StatusMana" "0" + "StatusManaRegen" "0.0" + + "VisionDaytimeRange" "500" + "VisionNighttimeRange" "300" + + "TeamName" "DOTA_TEAM_NEUTRALS" + "CombatClassAttack" "DOTA_COMBAT_CLASS_ATTACK_BASIC" + "CombatClassDefend" "DOTA_COMBAT_CLASS_DEFEND_BASIC" + "UnitRelationshipClass" "DOTA_NPC_UNIT_RELATIONSHIP_TYPE_DEFAULT" + + } + "npc_fish_2" + { + "Model" "models/items/hex/fish_hex_retro/fish_hex_retro.vmdl" + "BaseClass" "npc_dota_creature" + "SoundSet" "n_creep_Melee" + "GameSoundsFile" "soundevents/game_sounds_creeps.vsndevts" + "Level" "1" + "ModelScale" ".9" + "UseNeutralCreepBehavior" "0" + "Level" "1" + + "Ability1" "fish_basic" + "Ability2" "ability_unit_less_laggy" + "Ability3" "" + "Ability4" "" + + "ArmorPhysical" "0" + "MagicalResistance" "0" + + "AttackCapabilities" "DOTA_UNIT_CAP_MELEE_ATTACK" + "AttackDamageType" "DAMAGE_TYPE_ArmorPhysical" + "AttackDamageMin" "29" + "AttackDamageMax" "32" + "AttackRate" "3" + "AttackAnimationPoint" "0.4" + "AttackAcquisitionRange" "300" + "AttackRange" "90" + + "RingRadius" "40" + "HealthBarOffset" "170" + + "BountyXP" "7" + "BountyGoldMin" "10" + "BountyGoldMax" "15" + + "MovementCapabilities" "DOTA_UNIT_CAP_MOVE_GROUND" + "MovementSpeed" "260" + + "StatusHealth" "155" + "StatusHealthRegen" "15.0" + "StatusMana" "0" + "StatusManaRegen" "0.0" + + "VisionDaytimeRange" "500" + "VisionNighttimeRange" "300" + + "TeamName" "DOTA_TEAM_NEUTRALS" + "CombatClassAttack" "DOTA_COMBAT_CLASS_ATTACK_BASIC" + "CombatClassDefend" "DOTA_COMBAT_CLASS_DEFEND_BASIC" + "UnitRelationshipClass" "DOTA_NPC_UNIT_RELATIONSHIP_TYPE_DEFAULT" + + } + "npc_wisps" + { + "Model" "models/heroes/wisp/wisp.vmdl" + "BaseClass" "npc_dota_creature" + "SoundSet" "n_creep_Melee" + "GameSoundsFile" "soundevents/game_sounds_creeps.vsndevts" + "Level" "1" + "ModelScale" ".9" + "UseNeutralCreepBehavior" "0" + "Level" "1" + + "Ability1" "invul_npc" + "Ability2" "" + "Ability3" "" + "Ability4" "" + + "ArmorPhysical" "0" + "MagicalResistance" "0" + + "AttackCapabilities" "DOTA_UNIT_CAP_MELEE_ATTACK" + "AttackDamageType" "DAMAGE_TYPE_ArmorPhysical" + "AttackDamageMin" "29" + "AttackDamageMax" "32" + "AttackRate" "3" + "AttackAnimationPoint" "0.4" + "AttackAcquisitionRange" "300" + "AttackRange" "90" + + "RingRadius" "40" + "HealthBarOffset" "170" + + "BountyXP" "7" + "BountyGoldMin" "10" + "BountyGoldMax" "15" + + "MovementCapabilities" "DOTA_UNIT_CAP_MOVE_GROUND" + "MovementSpeed" "260" + + "StatusHealth" "155" + "StatusHealthRegen" "15.0" + "StatusMana" "0" + "StatusManaRegen" "0.0" + + "VisionDaytimeRange" "500" + "VisionNighttimeRange" "300" + + "TeamName" "DOTA_TEAM_NEUTRALS" + "CombatClassAttack" "DOTA_COMBAT_CLASS_ATTACK_BASIC" + "CombatClassDefend" "DOTA_COMBAT_CLASS_DEFEND_BASIC" + "UnitRelationshipClass" "DOTA_NPC_UNIT_RELATIONSHIP_TYPE_DEFAULT" + + } + + +} \ No newline at end of file diff --git a/scripts/npc/summons/summons.kv b/scripts/npc/summons/summons.kv new file mode 100644 index 0000000..c610df9 --- /dev/null +++ b/scripts/npc/summons/summons.kv @@ -0,0 +1,1014 @@ + +"DOTAUnits" +{ + "npc_dota_fire_summon" + { + "BaseClass" "npc_dota_creature" + "Model" "models/items/invoker/forge_spirit/infernus/infernus.vmdl" + "SoundSet" "n_creep_Ranged" + "GameSoundsFile" "soundevents/game_sounds_creeps.vsndevts" + "Level" "5" + "ModelScale" "0.66" + + "Ability1" "" + "Ability2" "" + + "ArmorPhysical" "130" + "MagicalResistance" "100" + + "AttackCapabilities" "DOTA_UNIT_CAP_RANGED_ATTACK" + "AttackDamageMin" "200" + "AttackDamageMax" "322" + "AttackDamageType" "DAMAGE_TYPE_PHYSICAL" + "AttackRate" "1.35" + "AttackAnimationPoint" "0.2" + "AttackAcquisitionRange" "500" + "AttackRange" "500" + "ProjectileModel" "particles/units/heroes/hero_invoker/invoker_forged_spirit_projectile.vpcf" + "ProjectileSpeed" "1450" + "BaseAttackSpeed" "110" + "MovementCapabilities" "DOTA_UNIT_CAP_MOVE_GROUND" + "MovementSpeed" "550" + + "TeamName" "DOTA_TEAM_GOODGUYS" + "UnitRelationshipClass" "DOTA_NPC_UNIT_RELATIONSHIP_TYPE_DEFAULT" + "Creature" + { + } + } + "npc_dota_melee_nagash_summon" + { + "BaseClass" "npc_dota_creature" + "Model" "models/creeps/item_creeps/i_creep_necro_warrior/necro_warrior.vmdl" + "SoundSet" "n_creep_Melee" + "GameSoundsFile" "soundevents/game_sounds_creeps.vsndevts" + "Level" "5" + "ModelScale" "0.66" + + "Ability1" "" + "Ability2" "" + + "ArmorPhysical" "130" + "MagicalResistance" "100" + + "AttackCapabilities" "DOTA_UNIT_CAP_MELEE_ATTACK" + "AttackDamageMin" "200" + "AttackDamageMax" "322" + "AttackDamageType" "DAMAGE_TYPE_PHYSICAL" + "AttackRate" "1.55" + "AttackAnimationPoint" "0.2" + "AttackAcquisitionRange" "500" + "AttackRange" "275" + "BaseAttackSpeed" "110" + "MovementCapabilities" "DOTA_UNIT_CAP_MOVE_GROUND" + "MovementSpeed" "550" + + "TeamName" "DOTA_TEAM_GOODGUYS" + "UnitRelationshipClass" "DOTA_NPC_UNIT_RELATIONSHIP_TYPE_DEFAULT" + "Creature" + { + } + } + "npc_dota_ranged_nagash_summon" + { + "BaseClass" "npc_dota_creature" + "Model" "models/creeps/item_creeps/i_creep_necro_archer/necro_archer.vmdl" + "SoundSet" "n_creep_Ranged" + "GameSoundsFile" "soundevents/game_sounds_creeps.vsndevts" + "Level" "5" + "ModelScale" "0.66" + + "Ability1" "" + "Ability2" "" + + "ArmorPhysical" "130" + "MagicalResistance" "100" + + "AttackCapabilities" "DOTA_UNIT_CAP_RANGED_ATTACK" + "AttackDamageMin" "200" + "AttackDamageMax" "322" + "AttackDamageType" "DAMAGE_TYPE_PHYSICAL" + "AttackRate" "1.20" + "AttackAnimationPoint" "0.2" + "AttackAcquisitionRange" "500" + "AttackRange" "500" + "ProjectileModel" "particles/units/heroes/hero_invoker/invoker_forged_spirit_projectile.vpcf" + "ProjectileSpeed" "1450" + "BaseAttackSpeed" "110" + "MovementCapabilities" "DOTA_UNIT_CAP_MOVE_GROUND" + "MovementSpeed" "550" + + "TeamName" "DOTA_TEAM_GOODGUYS" + "UnitRelationshipClass" "DOTA_NPC_UNIT_RELATIONSHIP_TYPE_DEFAULT" + "Creature" + { + } + } + "npc_dota_mage_nagash_summon" + { + "BaseClass" "npc_dota_creature" + "Model" "models/creeps/lane_creeps/creep_bird_dire/creep_bird_dire_ranged_mega.vmdl" + "SoundSet" "n_creep_Ranged" + "GameSoundsFile" "soundevents/game_sounds_creeps.vsndevts" + "Level" "5" + "ModelScale" "1" + + "Ability1" "" + "Ability2" "" + + "ArmorPhysical" "130" + "MagicalResistance" "100" + + "AttackCapabilities" "DOTA_UNIT_CAP_RANGED_ATTACK" + "AttackDamageMin" "200" + "AttackDamageMax" "322" + "AttackDamageType" "DAMAGE_TYPE_PHYSICAL" + "AttackRate" "1.35" + "AttackAnimationPoint" "0.2" + "AttackAcquisitionRange" "500" + "AttackRange" "500" + "ProjectileModel" "particles/units/heroes/hero_invoker/invoker_forged_spirit_projectile.vpcf" + "ProjectileSpeed" "1450" + "BaseAttackSpeed" "110" + "MovementCapabilities" "DOTA_UNIT_CAP_MOVE_GROUND" + "MovementSpeed" "550" + + "TeamName" "DOTA_TEAM_GOODGUYS" + "UnitRelationshipClass" "DOTA_NPC_UNIT_RELATIONSHIP_TYPE_DEFAULT" + "Creature" + { + } + } + "npc_dota_shield_nagash_summon" + { + "BaseClass" "npc_dota_creature" + "Model" "models/creeps/lane_creeps/creep_dire_hulk/creep_dire_winter_ancient_hulk.vmdl" + "SoundSet" "n_creep_Ranged" + "GameSoundsFile" "soundevents/game_sounds_creeps.vsndevts" + "Level" "5" + "ModelScale" "0.44" + + "Ability1" "" + "Ability2" "" + + "ArmorPhysical" "130" + "MagicalResistance" "100" + + "AttackCapabilities" "DOTA_UNIT_CAP_RANGED_ATTACK" + "AttackDamageMin" "200" + "AttackDamageMax" "322" + "AttackDamageType" "DAMAGE_TYPE_PHYSICAL" + "AttackRate" "1.55" + "AttackAnimationPoint" "0.2" + "AttackAcquisitionRange" "500" + "AttackRange" "500" + "ProjectileModel" "particles/units/heroes/hero_invoker/invoker_forged_spirit_projectile.vpcf" + "ProjectileSpeed" "1450" + "BaseAttackSpeed" "110" + "MovementCapabilities" "DOTA_UNIT_CAP_MOVE_GROUND" + "MovementSpeed" "550" + + "TeamName" "DOTA_TEAM_GOODGUYS" + "UnitRelationshipClass" "DOTA_NPC_UNIT_RELATIONSHIP_TYPE_DEFAULT" + "Creature" + { + } + } + + "npc_dota_nagash_soul_eater" + { + "BaseClass" "npc_dota_creature" + "Model" "models/props_structures/tower_dragon_black.vmdl" + "ModelScale" "1" + + "AttackCapabilities" "DOTA_UNIT_CAP_NO_ATTACK" + "AttackDamageType" "DAMAGE_TYPE_PHYSICAL" + + "MovementCapabilities" "DOTA_UNIT_CAP_MOVE_GROUND" + + + "TeamName" "DOTA_TEAM_GOODGUYS" + "UnitRelationshipClass" "DOTA_NPC_UNIT_RELATIONSHIP_TYPE_DEFAULT" + + } + + "npc_portal" + { + // General + //---------------------------------------------------------------- + "BaseClass" "npc_dota_creature" + "Model" "models/heroes/abyssal_underlord/abyssal_underlord_portal_model.vmdl" + "ModelScale" "1.0" + "Level" "1" + "HealthBarOffset" "150" + "HasInventory" "0" + "ConsideredHero" "0" + "IsNeutralUnitType" "1" + "IsAncient" "1" + + + // Abilities + //---------------------------------------------------------------- + "Ability1" "" + "Ability2" "" + "Ability3" "" + "Ability4" "" + + + // Armor + //---------------------------------------------------------------- + "ArmorPhysical" "10" + "MagicalResistance" "0" + + // Attack + //---------------------------------------------------------------- + "AttackCapabilities" "DOTA_UNIT_CAP_NO_ATTACK" + "AttackDamageType" "DAMAGE_TYPE_ArmorPhysical" + "AttackDamageMin" "0" + "AttackDamageMax" "0" + + // Bounty + //---------------------------------------------------------------- + "BountyGoldMin" "0.0" + "BountyGoldMax" "0.0" + + // Bounds + //---------------------------------------------------------------- + "BoundsHullName" "DOTA_HULL_SIZE_HERO" + "RingRadius" "70" + "CollisionSize" "1" + + + // Movement + //---------------------------------------------------------------- + "MovementCapabilities" "DOTA_UNIT_CAP_MOVE_GROUND" + "MovementSpeed" "0" + + // Status + //---------------------------------------------------------------- + "StatusHealth" "5000" + "StatusHealthRegen" "0" + "StatusMana" "0" + "StatusManaRegen" "0" + + // Vision + //---------------------------------------------------------------- + "VisionDaytimeRange" "900" + "VisionNighttimeRange" "900" + + // Team + //---------------------------------------------------------------- + "TeamName" "DOTA_TEAM_GOODGUYS" + "CombatClassAttack" "DOTA_COMBAT_CLASS_ATTACK_PIERCE" + "CombatClassDefend" "DOTA_COMBAT_CLASS_DEFEND_BASIC" + "UnitRelationshipClass" "DOTA_NPC_UNIT_RELATIONSHIP_TYPE_DEFAULT" + + } + "npc_spirit_sargatanas_hell_summon" + { + // General + // + "BaseClass" "npc_dota_creature" // Class of entity of link to. + "Model" "models/heroes/brewmaster/brewmaster_firespirit.vmdl" // Model. + "SoundSet" "Creep_Good_Range" // Name of sound set. + "ModelScale" "0.75" + "Level" "1" + "IsSummoned" "1" + "SelectionGroup" "Spirits" + "UnitLabel" "Spirits" + + // Abilities + //---------------------------------------------------------------- + + "Ability1" "ability_firecleave" // Ability 1. + "Ability2" "" // Ability 2. + "Ability3" "" // Ability 3. + "Ability4" "" // Ability 4. + "Ability5" "" // Ability 5. + "Ability6" "" // Ability 6 - Extra. + "Ability7" "" // Ability 7 - Extra. + "Ability8" "" // Ability 8 - Extra. + + // Armor + //---------------------------------------------------------------- + "ArmorPhysical" "2" // Physical protection. + "MagicalResistance" "40" // Magical protection. + + // Attack + //---------------------------------------------------------------- + "AttackCapabilities" "DOTA_UNIT_CAP_RANGED_ATTACK" + "AttackDamageMin" "2" // Damage range min. + "AttackDamageMax" "6" // Damage range max. + "AttackDamageType" "DAMAGE_TYPE_ArmorPhysical" + "AttackRate" "1.8" // Speed of attack. + "AttackAnimationPoint" "0.2" // Normalized time in animation cycle to attack. + "AttackAcquisitionRange" "800" // Range within a target can be acquired. + "AttackRange" "500" // Range within a target can be attacked. + "ProjectileModel" "particles/units/heroes/hero_lina/lina_base_attack.vpcf" // Particle system model for projectile. + "ProjectileSpeed" "900" // Speed of projectile. + + // Bounty + //---------------------------------------------------------------- + "BountyXP" "10" // Experience earn. + "BountyGoldMin" "18" // Gold earned min. + "BountyGoldMax" "24" // Gold earned max. + + // Bounds + //---------------------------------------------------------------- + "BoundsHullName" "DOTA_HULL_SIZE_SMALL" // Hull type used for navigation/locomotion. + "HealthBarOffset" "130" + + // Movement + //---------------------------------------------------------------- + "MovementCapabilities" "DOTA_UNIT_CAP_MOVE_GROUND" // Type of locomotion - ground, air + "MovementSpeed" "280" // Speed + "MovementTurnRate" "0.9" // Turning rate. + + // Status + //---------------------------------------------------------------- + "StatusHealth" "170" // Base health. + "StatusHealthRegen" "4" // Health regeneration rate. + "StatusMana" "0" // Base mana. + "StatusManaRegen" "0" // Mana regeneration rate. + + // Team + //---------------------------------------------------------------- + "TeamName" "DOTA_TEAM_GOODGUYS" // Team name. + "CombatClassAttack" "DOTA_COMBAT_CLASS_ATTACK_HERO" + "CombatClassDefend" "DOTA_COMBAT_CLASS_DEFEND_HERO" + "UnitRelationshipClass" "DOTA_NPC_UNIT_RELATIONSHIP_TYPE_DEFAULT" + + // Vision + //---------------------------------------------------------------- + "VisionDaytimeRange" "1200" // Range of vision during day light. + "VisionNighttimeRange" "800" // Range of vision at night time. + } + "npc_golem_sargatanas_hell_summon" + { + // General + // + "BaseClass" "npc_dota_creature" // Class of entity of link to. + "Model" "models/items/warlock/golem/ti_8_warlock_darkness_apostate_golem/ti_8_warlock_darkness_apostate_golem.vmdl" // Model. + "SoundSet" "Creep_Good_Range" // Name of sound set. + "ModelScale" "0.55" + "Level" "1" + "IsSummoned" "1" + "SelectionGroup" "Spirits" + "UnitLabel" "Spirits" + + // Abilities + //---------------------------------------------------------------- + + "Ability1" "ability_firecleave" // Ability 1. + "Ability2" "" // Ability 2. + "Ability3" "" // Ability 3. + "Ability4" "" // Ability 4. + "Ability5" "" // Ability 5. + "Ability6" "" // Ability 6 - Extra. + "Ability7" "" // Ability 7 - Extra. + "Ability8" "" // Ability 8 - Extra. + + // Armor + //---------------------------------------------------------------- + "ArmorPhysical" "2" // Physical protection. + "MagicalResistance" "40" // Magical protection. + + // Attack + //---------------------------------------------------------------- + "AttackCapabilities" "DOTA_UNIT_CAP_RANGED_ATTACK" + "AttackDamageMin" "54" // Damage range min. + "AttackDamageMax" "57" // Damage range max. + "AttackDamageType" "DAMAGE_TYPE_ArmorPhysical" + "AttackRate" "1.5" // Speed of attack. + "AttackAnimationPoint" "0.2" // Normalized time in animation cycle to attack. + "AttackAcquisitionRange" "800" // Range within a target can be acquired. + "AttackRange" "500" // Range within a target can be attacked. + "ProjectileModel" "particles/units/heroes/hero_lina/lina_base_attack.vpcf" // Particle system model for projectile. + "ProjectileSpeed" "900" // Speed of projectile. + + // Bounty + //---------------------------------------------------------------- + "BountyXP" "10" // Experience earn. + "BountyGoldMin" "18" // Gold earned min. + "BountyGoldMax" "24" // Gold earned max. + + // Bounds + //---------------------------------------------------------------- + "BoundsHullName" "DOTA_HULL_SIZE_SMALL" // Hull type used for navigation/locomotion. + "HealthBarOffset" "130" + + // Movement + //---------------------------------------------------------------- + "MovementCapabilities" "DOTA_UNIT_CAP_MOVE_GROUND" // Type of locomotion - ground, air + "MovementSpeed" "280" // Speed + "MovementTurnRate" "0.9" // Turning rate. + + // Status + //---------------------------------------------------------------- + "StatusHealth" "260" // Base health. + "StatusHealthRegen" "4" // Health regeneration rate. + "StatusMana" "0" // Base mana. + "StatusManaRegen" "0" // Mana regeneration rate. + + // Team + //---------------------------------------------------------------- + "TeamName" "DOTA_TEAM_GOODGUYS" // Team name. + "CombatClassAttack" "DOTA_COMBAT_CLASS_ATTACK_HERO" + "CombatClassDefend" "DOTA_COMBAT_CLASS_DEFEND_HERO" + "UnitRelationshipClass" "DOTA_NPC_UNIT_RELATIONSHIP_TYPE_DEFAULT" + + // Vision + //---------------------------------------------------------------- + "VisionDaytimeRange" "1200" // Range of vision during day light. + "VisionNighttimeRange" "800" // Range of vision at night time. + } + "npc_scorpion_sargatanas_hell_summon" + { + // General + // + "BaseClass" "npc_dota_creature" // Class of entity of link to. + "Model" "models/items/broodmother/spiderling/ti9_cache_brood_mother_of_thousands_spiderling/ti9_cache_brood_mother_of_thousands_spiderling.vmdl" // Model. + "SoundSet" "Creep_Good_Range" // Name of sound set. + "ModelScale" "0.75" + "Level" "1" + "IsSummoned" "1" + "SelectionGroup" "Spirits" + "UnitLabel" "Spirits" + + // Abilities + //---------------------------------------------------------------- + + "Ability1" "ability_firecleave" // Ability 1. + "Ability2" "" // Ability 2. + "Ability3" "" // Ability 3. + "Ability4" "" // Ability 4. + "Ability5" "" // Ability 5. + "Ability6" "" // Ability 6 - Extra. + "Ability7" "" // Ability 7 - Extra. + "Ability8" "" // Ability 8 - Extra. + + // Armor + //---------------------------------------------------------------- + "ArmorPhysical" "2" // Physical protection. + "MagicalResistance" "40" // Magical protection. + + // Attack + //---------------------------------------------------------------- + "AttackCapabilities" "DOTA_UNIT_CAP_RANGED_ATTACK" + "AttackDamageMin" "106" // Damage range min. + "AttackDamageMax" "117" // Damage range max. + "AttackDamageType" "DAMAGE_TYPE_ArmorPhysical" + "AttackRate" "1.5" // Speed of attack. + "AttackAnimationPoint" "0.2" // Normalized time in animation cycle to attack. + "AttackAcquisitionRange" "800" // Range within a target can be acquired. + "AttackRange" "500" // Range within a target can be attacked. + "ProjectileModel" "particles/units/heroes/hero_lina/lina_base_attack.vpcf" // Particle system model for projectile. + "ProjectileSpeed" "900" // Speed of projectile. + + // Bounty + //---------------------------------------------------------------- + "BountyXP" "10" // Experience earn. + "BountyGoldMin" "18" // Gold earned min. + "BountyGoldMax" "24" // Gold earned max. + + // Bounds + //---------------------------------------------------------------- + "BoundsHullName" "DOTA_HULL_SIZE_SMALL" // Hull type used for navigation/locomotion. + "HealthBarOffset" "130" + + // Movement + //---------------------------------------------------------------- + "MovementCapabilities" "DOTA_UNIT_CAP_MOVE_GROUND" // Type of locomotion - ground, air + "MovementSpeed" "280" // Speed + "MovementTurnRate" "0.9" // Turning rate. + + // Status + //---------------------------------------------------------------- + "StatusHealth" "1450" // Base health. + "StatusHealthRegen" "4" // Health regeneration rate. + "StatusMana" "0" // Base mana. + "StatusManaRegen" "0" // Mana regeneration rate. + + // Team + //---------------------------------------------------------------- + "TeamName" "DOTA_TEAM_GOODGUYS" // Team name. + "CombatClassAttack" "DOTA_COMBAT_CLASS_ATTACK_HERO" + "CombatClassDefend" "DOTA_COMBAT_CLASS_DEFEND_HERO" + "UnitRelationshipClass" "DOTA_NPC_UNIT_RELATIONSHIP_TYPE_DEFAULT" + + // Vision + //---------------------------------------------------------------- + "VisionDaytimeRange" "1200" // Range of vision during day light. + "VisionNighttimeRange" "800" // Range of vision at night time. + } + "npc_dragon_sargatanas_hell_summon" + { + // General + // + "BaseClass" "npc_dota_creature" // Class of entity of link to. + "Model" "models/creeps/neutral_creeps/n_creep_black_dragon/n_creep_black_dragon.vmdl" // Model. + "SoundSet" "Creep_Good_Range" // Name of sound set. + "ModelScale" "0.75" + "Level" "1" + "IsSummoned" "1" + "SelectionGroup" "Spirits" + "UnitLabel" "Spirits" + + // Abilities + //---------------------------------------------------------------- + + "Ability1" "ability_firecleave" // Ability 1. + "Ability2" "" // Ability 2. + "Ability3" "" // Ability 3. + "Ability4" "" // Ability 4. + "Ability5" "" // Ability 5. + "Ability6" "" // Ability 6 - Extra. + "Ability7" "" // Ability 7 - Extra. + "Ability8" "" // Ability 8 - Extra. + + // Armor + //---------------------------------------------------------------- + "ArmorPhysical" "8" // Physical protection. + "MagicalResistance" "40" // Magical protection. + + // Attack + //---------------------------------------------------------------- + "AttackCapabilities" "DOTA_UNIT_CAP_RANGED_ATTACK" + "AttackDamageMin" "140" // Damage range min. + "AttackDamageMax" "160" // Damage range max. + "AttackDamageType" "DAMAGE_TYPE_ArmorPhysical" + "AttackRate" "1.5" // Speed of attack. + "AttackAnimationPoint" "0.2" // Normalized time in animation cycle to attack. + "AttackAcquisitionRange" "800" // Range within a target can be acquired. + "AttackRange" "500" // Range within a target can be attacked. + "ProjectileModel" "particles/units/heroes/hero_lina/lina_base_attack.vpcf" // Particle system model for projectile. + "ProjectileSpeed" "900" // Speed of projectile. + + // Bounty + //---------------------------------------------------------------- + "BountyXP" "10" // Experience earn. + "BountyGoldMin" "18" // Gold earned min. + "BountyGoldMax" "24" // Gold earned max. + + // Bounds + //---------------------------------------------------------------- + "BoundsHullName" "DOTA_HULL_SIZE_SMALL" // Hull type used for navigation/locomotion. + "HealthBarOffset" "130" + + // Movement + //---------------------------------------------------------------- + "MovementCapabilities" "DOTA_UNIT_CAP_MOVE_GROUND" // Type of locomotion - ground, air + "MovementSpeed" "280" // Speed + "MovementTurnRate" "0.9" // Turning rate. + + // Status + //---------------------------------------------------------------- + "StatusHealth" "670" // Base health. + "StatusHealthRegen" "4" // Health regeneration rate. + "StatusMana" "0" // Base mana. + "StatusManaRegen" "0" // Mana regeneration rate. + + // Team + //---------------------------------------------------------------- + "TeamName" "DOTA_TEAM_GOODGUYS" // Team name. + "CombatClassAttack" "DOTA_COMBAT_CLASS_ATTACK_HERO" + "CombatClassDefend" "DOTA_COMBAT_CLASS_DEFEND_HERO" + "UnitRelationshipClass" "DOTA_NPC_UNIT_RELATIONSHIP_TYPE_DEFAULT" + + // Vision + //---------------------------------------------------------------- + "VisionDaytimeRange" "1200" // Range of vision during day light. + "VisionNighttimeRange" "800" // Range of vision at night time. + } + "npc_kaban_sargatanas_hell_summon" + { + // General + // + "BaseClass" "npc_dota_creature" // Class of entity of link to. + "Model" "models/heroes/beastmaster/beastmaster_beast.vmdl" // Model. + "SoundSet" "Creep_Good_Range" // Name of sound set. + "ModelScale" "0.75" + "Level" "1" + "IsSummoned" "1" + "SelectionGroup" "Spirits" + "UnitLabel" "Spirits" + + // Abilities + //---------------------------------------------------------------- + + "Ability1" "ability_firecleave" // Ability 1. + "Ability2" "" // Ability 2. + "Ability3" "" // Ability 3. + "Ability4" "" // Ability 4. + "Ability5" "" // Ability 5. + "Ability6" "" // Ability 6 - Extra. + "Ability7" "" // Ability 7 - Extra. + "Ability8" "" // Ability 8 - Extra. + + // Armor + //---------------------------------------------------------------- + "ArmorPhysical" "22" // Physical protection. + "MagicalResistance" "80" // Magical protection. + + // Attack + //---------------------------------------------------------------- + "AttackCapabilities" "DOTA_UNIT_CAP_RANGED_ATTACK" + "AttackDamageMin" "280" // Damage range min. + "AttackDamageMax" "292" // Damage range max. + "AttackDamageType" "DAMAGE_TYPE_ArmorPhysical" + "AttackRate" "1.2" // Speed of attack. + "AttackAnimationPoint" "0.2" // Normalized time in animation cycle to attack. + "AttackAcquisitionRange" "800" // Range within a target can be acquired. + "AttackRange" "700" // Range within a target can be attacked. + "ProjectileModel" "particles/units/heroes/hero_lina/lina_base_attack.vpcf" // Particle system model for projectile. + "ProjectileSpeed" "900" // Speed of projectile. + + // Bounty + //---------------------------------------------------------------- + "BountyXP" "10" // Experience earn. + "BountyGoldMin" "18" // Gold earned min. + "BountyGoldMax" "24" // Gold earned max. + + // Bounds + //---------------------------------------------------------------- + "BoundsHullName" "DOTA_HULL_SIZE_SMALL" // Hull type used for navigation/locomotion. + "HealthBarOffset" "130" + + // Movement + //---------------------------------------------------------------- + "MovementCapabilities" "DOTA_UNIT_CAP_MOVE_GROUND" // Type of locomotion - ground, air + "MovementSpeed" "550" // Speed + "MovementTurnRate" "0.25" // Turning rate. + + // Status + //---------------------------------------------------------------- + "StatusHealth" "1270" // Base health. + "StatusHealthRegen" "14" // Health regeneration rate. + "StatusMana" "0" // Base mana. + "StatusManaRegen" "0" // Mana regeneration rate. + + // Team + //---------------------------------------------------------------- + "TeamName" "DOTA_TEAM_GOODGUYS" // Team name. + "CombatClassAttack" "DOTA_COMBAT_CLASS_ATTACK_HERO" + "CombatClassDefend" "DOTA_COMBAT_CLASS_DEFEND_HERO" + "UnitRelationshipClass" "DOTA_NPC_UNIT_RELATIONSHIP_TYPE_DEFAULT" + + // Vision + //---------------------------------------------------------------- + "VisionDaytimeRange" "1200" // Range of vision during day light. + "VisionNighttimeRange" "800" // Range of vision at night time. + } + + "npc_dota_rofl_kaban_pumba" + { + "BaseClass" "npc_dota_creature" + "Model" "models/courier/mighty_boar/mighty_boar.vmdl" + "ModelScale" "1" + + "AttackCapabilities" "DOTA_UNIT_CAP_NO_ATTACK" + "AttackDamageType" "DAMAGE_TYPE_PHYSICAL" + + "MovementCapabilities" "DOTA_UNIT_CAP_MOVE_GROUND" + "Ability1" "kaban_rofl_ability" // Ability 1. + "StatusHealth" "1" // Base health. + "TeamName" "DOTA_TEAM_GOODGUYS" + "UnitRelationshipClass" "DOTA_NPC_UNIT_RELATIONSHIP_TYPE_DEFAULT" + + } + + "npc_dota_golden_fish" + { + "BaseClass" "npc_dota_creature" + "Model" "models/items/hex/fish_hex_retro/fish_hex_retro.vmdl" + "ModelScale" "1.2" + + "AttackCapabilities" "DOTA_UNIT_CAP_NO_ATTACK" + "AttackDamageType" "DAMAGE_TYPE_PHYSICAL" + + "MovementCapabilities" "DOTA_UNIT_CAP_MOVE_GROUND" + + "StatusHealth" "1" // Base health. + "TeamName" "DOTA_TEAM_GOODGUYS" + "UnitRelationshipClass" "DOTA_NPC_UNIT_RELATIONSHIP_TYPE_DEFAULT" + + } + "npc_classic_snowman" + { + // General + // + "BaseClass" "npc_dota_lone_druid_bear" // Class of entity of link to. + "Model" "models/props_frostivus/frostivus_snowman.vmdl" // Model. + "SoundSet" "Creep_Good_Range" // Name of sound set. + "ModelScale" "1.0" + "Level" "1" + "vscripts" "ai/ai_snowman.lua" + "UseNeutralCreepBehavior" "0" + + // Abilities + //---------------------------------------------------------------- + + "Ability1" "ability_yuki_snowball" // Ability 1. + "Ability2" "" // Ability 2. + "Ability3" "" // Ability 3. + "Ability4" "" // Ability 4. + "Ability5" "" // Ability 5. + "Ability6" "" // Ability 6 - Extra. + "Ability7" "" // Ability 7 - Extra. + "Ability8" "" // Ability 8 - Extra.\ + + // Armor + //---------------------------------------------------------------- + "ArmorPhysical" "8" // Physical protection. + "MagicalResistance" "60" // Magical protection. + + // Attack + //---------------------------------------------------------------- + "AttackCapabilities" "DOTA_UNIT_CAP_NO_ATTACK" + + // Bounty + //---------------------------------------------------------------- + "BountyXP" "10" // Experience earn. + "BountyGoldMin" "18" // Gold earned min. + "BountyGoldMax" "24" // Gold earned max. + + // Bounds + //---------------------------------------------------------------- + "BoundsHullName" "DOTA_HULL_SIZE_SMALL" // Hull type used for navigation/locomotion. + "HealthBarOffset" "130" + + // Movement + //---------------------------------------------------------------- + "MovementCapabilities" "DOTA_UNIT_CAP_MOVE_GROUND" // Type of locomotion - ground, air + "MovementSpeed" "420" // Speed + "MovementTurnRate" "1.0" // Turning rate. + + // Status + //---------------------------------------------------------------- + "StatusHealth" "200" // Base health. + "StatusHealthRegen" "4" // Health regeneration rate. + "StatusMana" "0" // Base mana. + "StatusManaRegen" "0" // Mana regeneration rate. + + // Team + //---------------------------------------------------------------- + "TeamName" "DOTA_TEAM_GOODGUYS" // Team name. + "CombatClassAttack" "DOTA_COMBAT_CLASS_ATTACK_HERO" + "CombatClassDefend" "DOTA_COMBAT_CLASS_DEFEND_HERO" + "UnitRelationshipClass" "DOTA_NPC_UNIT_RELATIONSHIP_TYPE_DEFAULT" + + // Vision + //---------------------------------------------------------------- + "VisionDaytimeRange" "1200" // Range of vision during day light. + "VisionNighttimeRange" "800" // Range of vision at night time. + } + "npc_dota_juggernaut_healing_ward_custom" + { + // General + // + "BaseClass" "npc_dota_base_additive" // Class of entity of link to. + "Model" "models/heroes/juggernaut/jugg_healing_ward.vmdl" // Model. + "SoundSet" "Creep_Good_Range" // Name of sound set. + "Level" "0" + "UnitLabel" "healing_ward" + "wearable" "8365" + + // Abilities + //---------------------------------------------------------------- + + "Ability1" "" // Ability 1. + "Ability2" "" // Ability 2. + "Ability3" "" // Ability 3. + "Ability4" "" // Ability 4. + "Ability5" "" // Ability 5. + "Ability6" "" // Ability 6 - Extra. + "Ability7" "" // Ability 7 - Extra. + "Ability8" "" // Ability 8 - Extra. + + // Armor + //---------------------------------------------------------------- + "ArmorPhysical" "0" // Physical protection. + "MagicalResistance" "0" // Magical protection. + + // Attack + //---------------------------------------------------------------- + "AttackCapabilities" "DOTA_UNIT_CAP_NO_ATTACK" // Type of attack - melee, ranged, etc. + "AttackDamageMin" "0" // Damage range min. + "AttackDamageMax" "0" // Damage range max. + "AttackDamageType" "DAMAGE_TYPE_ArmorPhysical" + "AttackRate" "1" // Speed of attack. + "AttackAnimationPoint" "0.5" // Normalized time in animation cycle to attack. + "AttackAcquisitionRange" "800" // Range within a target can be acquired. + "AttackRange" "500" // Range within a target can be attacked. + "ProjectileModel" "" // Particle system model for projectile. + "ProjectileSpeed" "900" // Speed of projectile. + + // Attributes + //---------------------------------------------------------------- + "AttributePrimary" "DOTA_ATTRIBUTE_STRENGTH" + "AttributeBaseStrength" "0" // Base strength + "AttributeStrengthGain" "0" // Strength bonus per level. + "AttributeBaseIntelligence" "0" // Base intelligence + "AttributeIntelligenceGain" "0" // Intelligence bonus per level. + "AttributeBaseAgility" "0" // Base agility + "AttributeAgilityGain" "0" // Agility bonus per level. + + // Bounty + //---------------------------------------------------------------- + "BountyXP" "75" // Experience earn. + "BountyGoldMin" "75" // Gold earned min. + "BountyGoldMax" "75" // Gold earned max. + + // Bounds + //---------------------------------------------------------------- + "BoundsHullName" "DOTA_HULL_SIZE_SMALL" // Hull type used for navigation/locomotion. + + // Movement + //---------------------------------------------------------------- + "MovementCapabilities" "DOTA_UNIT_CAP_MOVE_GROUND" // Type of locomotion - ground, air + "MovementSpeed" "325" // Speed + "MovementTurnRate" "0.5" // Turning rate. + "FollowRange" "250" // Distance to keep when following + + // Status + //---------------------------------------------------------------- + "StatusHealth" "1" // Base health. + "StatusHealthRegen" "0" // Health regeneration rate. + "StatusMana" "0" // Base mana. + "StatusManaRegen" "0" // Mana regeneration rate. + + // Team + //---------------------------------------------------------------- + "TeamName" "DOTA_TEAM_GOODGUYS" // Team name. + "UnitRelationshipClass" "DOTA_NPC_UNIT_RELATIONSHIP_TYPE_WARD" + + // Vision + //---------------------------------------------------------------- + "VisionDaytimeRange" "600" // Range of vision during day light. + "VisionNighttimeRange" "600" // Range of vision at night time. + + // Bots + //---------------------------------------------------------------- + "AttackDesire" "1.5" // How much bots want to attack them vs other non-hero things + } + "npc_templar_trap" + { + // General + // + "BaseClass" "npc_dota_base_additive" // Class of entity of link to. + "Model" "models/props_gameplay/moon_shard/moon_shard_002.vmdl" // Model. + "SoundSet" "Creep_Good_Range" // Name of sound set. + "Level" "0" + "UnitLabel" "healing_ward" + "ModelScale" "3.0" + "wearable" "8365" + + // Abilities + //---------------------------------------------------------------- + + "Ability1" "" // Ability 1. + "Ability2" "" // Ability 2. + "Ability3" "" // Ability 3. + "Ability4" "" // Ability 4. + "Ability5" "" // Ability 5. + "Ability6" "" // Ability 6 - Extra. + "Ability7" "" // Ability 7 - Extra. + "Ability8" "" // Ability 8 - Extra. + + // Armor + //---------------------------------------------------------------- + "ArmorPhysical" "0" // Physical protection. + "MagicalResistance" "0" // Magical protection. + + // Attack + //---------------------------------------------------------------- + "AttackCapabilities" "DOTA_UNIT_CAP_NO_ATTACK" // Type of attack - melee, ranged, etc. + "AttackDamageMin" "0" // Damage range min. + "AttackDamageMax" "0" // Damage range max. + "AttackDamageType" "DAMAGE_TYPE_ArmorPhysical" + "AttackRate" "1" // Speed of attack. + "AttackAnimationPoint" "0.5" // Normalized time in animation cycle to attack. + "AttackAcquisitionRange" "800" // Range within a target can be acquired. + "AttackRange" "500" // Range within a target can be attacked. + "ProjectileModel" "" // Particle system model for projectile. + "ProjectileSpeed" "900" // Speed of projectile. + + // Attributes + //---------------------------------------------------------------- + "AttributePrimary" "DOTA_ATTRIBUTE_STRENGTH" + "AttributeBaseStrength" "0" // Base strength + "AttributeStrengthGain" "0" // Strength bonus per level. + "AttributeBaseIntelligence" "0" // Base intelligence + "AttributeIntelligenceGain" "0" // Intelligence bonus per level. + "AttributeBaseAgility" "0" // Base agility + "AttributeAgilityGain" "0" // Agility bonus per level. + + // Bounty + //---------------------------------------------------------------- + "BountyXP" "75" // Experience earn. + "BountyGoldMin" "75" // Gold earned min. + "BountyGoldMax" "75" // Gold earned max. + + // Bounds + //---------------------------------------------------------------- + "BoundsHullName" "DOTA_HULL_SIZE_SMALL" // Hull type used for navigation/locomotion. + + // Movement + //---------------------------------------------------------------- + "MovementCapabilities" "DOTA_UNIT_CAP_MOVE_GROUND" // Type of locomotion - ground, air + "MovementSpeed" "325" // Speed + "MovementTurnRate" "0.5" // Turning rate. + "FollowRange" "250" // Distance to keep when following + + // Status + //---------------------------------------------------------------- + "StatusHealth" "112312" // Base health. + "StatusHealthRegen" "0" // Health regeneration rate. + "StatusMana" "0" // Base mana. + "StatusManaRegen" "0" // Mana regeneration rate. + + // Team + //---------------------------------------------------------------- + "TeamName" "DOTA_TEAM_GOODGUYS" // Team name. + "UnitRelationshipClass" "DOTA_NPC_UNIT_RELATIONSHIP_TYPE_WARD" + + // Vision + //---------------------------------------------------------------- + "VisionDaytimeRange" "600" // Range of vision during day light. + "VisionNighttimeRange" "600" // Range of vision at night time. + + // Bots + //---------------------------------------------------------------- + "AttackDesire" "1.5" // How much bots want to attack them vs other non-hero things + } + "npc_templar_secret_friend" + { + // General + //---------------------------------------------------------------- + "Model" "models/items/dark_willow/dark_willow_ether_wisp/dark_willow_ether_wisp.vmdl" + "ModelScale" "0.50" + "BaseClass" "npc_dota_creature" + "SoundSet" "n_creep_Melee" + "GameSoundsFile" "soundevents/game_sounds_creeps.vsndevts" + "Level" "1" + "CanBeDominated" "0" + + // Abilities + //---------------------------------------------------------------- + "Ability1" "" + + // Armor + //---------------------------------------------------------------- + "ArmorPhysical" "25" + "MagicalResistance" "50" + + // Attack + //---------------------------------------------------------------- + "AttackCapabilities" "DOTA_UNIT_CAP_NO_ATTACK" + "AttackDamageType" "DAMAGE_TYPE_ArmorPhysical" + "AttackDamageMin" "1" // Damage range min. + "AttackDamageMax" "1" // Damage range max. + "AttackRate" "0.6" // Speed of attack. + "AttackAnimationPoint" "0.2" // Normalized time in animation cycle to attack. + "AttackAcquisitionRange" "800" // Range within a target can be acquired. + "AttackRange" "0" // Range within a target can be attacked. + + + // Bounds + //---------------------------------------------------------------- + "RingRadius" "40" + "HealthBarOffset" "170" + + // Bounty + //---------------------------------------------------------------- + "BountyXP" "0" // Experience earn. + "BountyGoldMin" "0" // Gold earned min. + "BountyGoldMax" "0" // Gold earned max. + + // Movement + //---------------------------------------------------------------- + "MovementCapabilities" "DOTA_UNIT_CAP_MOVE_FLY" + "MovementSpeed" "0" // Speed. + + // Status + //---------------------------------------------------------------- + "StatusHealth" "1231" // Base health. + "StatusHealthRegen" "0" // Health regeneration rate. + "StatusMana" "0" // Base mana. + "StatusManaRegen" "0" // Mana regeneration rate. + + // Vision + //---------------------------------------------------------------- + "VisionDaytimeRange" "900" // Range of vision during day light. + "VisionNighttimeRange" "900" // Range of vision at night time. + + // Team + //---------------------------------------------------------------- + "TeamName" "DOTA_TEAM_GOODGUYS" + "CombatClassAttack" "DOTA_COMBAT_CLASS_ATTACK_PIERCE" + "CombatClassDefend" "DOTA_COMBAT_CLASS_DEFEND_BASIC" + "UnitRelationshipClass" "DOTA_NPC_UNIT_RELATIONSHIP_TYPE_DEFAULT" + } + + +} \ No newline at end of file diff --git a/scripts/npc/wave/npc_wave_boss_zombies.kv b/scripts/npc/wave/npc_wave_boss_zombies.kv new file mode 100644 index 0000000..2f770f0 --- /dev/null +++ b/scripts/npc/wave/npc_wave_boss_zombies.kv @@ -0,0 +1,519 @@ + +"DOTAUnits" +{ + "npc_wave_boss_zombie" + { + "Model" "models/items/undying/flesh_golem/incurable_pestilence_golem/incurable_pestilence_golem.vmdl" + "BaseClass" "npc_dota_creature" + "SoundSet" "n_creep_Melee" + "GameSoundsFile" "soundevents/game_sounds_creeps.vsndevts" + "ModelScale" "1.4" + "BoundsHullName" "DOTA_HULL_SIZE_REGULAR" + "UseNeutralCreepBehavior" "0" + + "Ability1" "zombie_rage" + "Ability2" "zombie_open_wounds" + "Ability3" "zombie_feast" + "Ability4" "ability_unit_less_laggy" + + // Armor + //---------------------------------------------------------------- + "ArmorPhysical" "12" + "MagicalResistance" "25" + + // Attack + //---------------------------------------------------------------- + "AttackCapabilities" "DOTA_UNIT_CAP_MELEE_ATTACK" + "AttackDamageType" "DAMAGE_TYPE_ArmorPhysical" + "AttackDamageMin" "140" + "AttackDamageMax" "210" + "AttackRate" "1.7" + "AttackAnimationPoint" "0.4" + "AttackAcquisitionRange" "1100" + "AttackRange" "130" + "BaseAttackSpeed" "140" + + // Bounds + //---------------------------------------------------------------- + "RingRadius" "40" + "HealthBarOffset" "170" + + // Bounty + //---------------------------------------------------------------- + "BountyXP" "400" + "BountyGoldMin" "0" + "BountyGoldMax" "0" + + // Movement + //---------------------------------------------------------------- + "MovementCapabilities" "DOTA_UNIT_CAP_MOVE_GROUND" + "MovementSpeed" "550" + + + // Status + //---------------------------------------------------------------- + "StatusHealth" "9000" + "StatusHealthRegen" "0" + "StatusMana" "400" + "StatusManaRegen" "0.0" + + // Vision + //---------------------------------------------------------------- + "VisionDaytimeRange" "900" + "VisionNighttimeRange" "900" + + + // Team + //---------------------------------------------------------------- + "TeamName" "DOTA_TEAM_NEUTRALS" + "CombatClassAttack" "DOTA_COMBAT_CLASS_ATTACK_PIERCE" + "CombatClassDefend" "DOTA_COMBAT_CLASS_DEFEND_BASIC" + "UnitRelationshipClass" "DOTA_NPC_UNIT_RELATIONSHIP_TYPE_DEFAULT" + "Creature" + { + "OffensiveAbilities" + { + "Ability1" + { + "Name" "zombie_rage" + } + "Ability2" + { + "Name" "zombie_open_wounds" + } + } + } + } + "npc_wave_boss_skeleton" + { + "Model" "models/items/wraith_king/arcana/wraith_king_arcana.vmdl" + "BaseClass" "npc_dota_creature" + "SoundSet" "n_creep_Melee" + "GameSoundsFile" "soundevents/game_sounds_creeps.vsndevts" + "ModelScale" "1.4" + "BoundsHullName" "DOTA_HULL_SIZE_REGULAR" + "UseNeutralCreepBehavior" "0" + + "Ability1" "skeleton_blast" + "Ability2" "skeleton_king_mortal_strike" + "Ability3" "zombie_feast" + "Ability4" "bone_armor" + "Ability5" "sven_great_cleave" + "Ability6" "ability_unit_less_laggy" + // Armor + //---------------------------------------------------------------- + "ArmorPhysical" "12" + "MagicalResistance" "25" + + // Attack + //---------------------------------------------------------------- + "AttackCapabilities" "DOTA_UNIT_CAP_MELEE_ATTACK" + "AttackDamageType" "DAMAGE_TYPE_ArmorPhysical" + "AttackDamageMin" "140" + "AttackDamageMax" "210" + "AttackRate" "1.7" + "AttackAnimationPoint" "0.4" + "AttackAcquisitionRange" "1100" + "AttackRange" "130" + "BaseAttackSpeed" "140" + + // Bounds + //---------------------------------------------------------------- + "RingRadius" "40" + "HealthBarOffset" "170" + + // Bounty + //---------------------------------------------------------------- + "BountyXP" "400" + "BountyGoldMin" "0" + "BountyGoldMax" "0" + + // Movement + //---------------------------------------------------------------- + "MovementCapabilities" "DOTA_UNIT_CAP_MOVE_GROUND" + "MovementSpeed" "550" + + + // Status + //---------------------------------------------------------------- + "StatusHealth" "15000" + "StatusHealthRegen" "5" + "StatusMana" "400" + "StatusManaRegen" "0.0" + + // Vision + //---------------------------------------------------------------- + "VisionDaytimeRange" "900" + "VisionNighttimeRange" "900" + + + // Team + //---------------------------------------------------------------- + "TeamName" "DOTA_TEAM_NEUTRALS" + "CombatClassAttack" "DOTA_COMBAT_CLASS_ATTACK_PIERCE" + "CombatClassDefend" "DOTA_COMBAT_CLASS_DEFEND_BASIC" + "UnitRelationshipClass" "DOTA_NPC_UNIT_RELATIONSHIP_TYPE_DEFAULT" + "Creature" + { + "OffensiveAbilities" + { + "Ability1" + { + "Name" "skeleton_blast" + } + } + } + } + "npc_wave_boss_lifestealer" + { + "Model" "models/heroes/life_stealer/life_stealer.vmdl" + "BaseClass" "npc_dota_creature" + "SoundSet" "n_creep_Melee" + "GameSoundsFile" "soundevents/game_sounds_creeps.vsndevts" + "ModelScale" "1.4" + "BoundsHullName" "DOTA_HULL_SIZE_REGULAR" + "UseNeutralCreepBehavior" "0" + + "Ability1" "zombie_rage" + "Ability2" "zombie_open_wounds" + "Ability3" "zombie_feast" + "Ability4" "bone_armor" + "Ability5" "skeleton_king_mortal_strike" + "Ability6" "ability_unit_less_laggy" + "Ability7" "zombie_armor_decress" + // Armor + //---------------------------------------------------------------- + "ArmorPhysical" "-12" + "MagicalResistance" "25" + + // Attack + //---------------------------------------------------------------- + "AttackCapabilities" "DOTA_UNIT_CAP_MELEE_ATTACK" + "AttackDamageType" "DAMAGE_TYPE_ArmorPhysical" + "AttackDamageMin" "140" + "AttackDamageMax" "210" + "AttackRate" "1.7" + "AttackAnimationPoint" "0.4" + "AttackAcquisitionRange" "1100" + "AttackRange" "130" + "BaseAttackSpeed" "140" + + // Bounds + //---------------------------------------------------------------- + "RingRadius" "40" + "HealthBarOffset" "170" + + // Bounty + //---------------------------------------------------------------- + "BountyXP" "400" + "BountyGoldMin" "0" + "BountyGoldMax" "0" + + // Movement + //---------------------------------------------------------------- + "MovementCapabilities" "DOTA_UNIT_CAP_MOVE_GROUND" + "MovementSpeed" "550" + + + // Status + //---------------------------------------------------------------- + "StatusHealth" "9000" + "StatusHealthRegen" "5" + "StatusMana" "400" + "StatusManaRegen" "0.0" + + // Vision + //---------------------------------------------------------------- + "VisionDaytimeRange" "900" + "VisionNighttimeRange" "900" + + + // Team + //---------------------------------------------------------------- + "TeamName" "DOTA_TEAM_NEUTRALS" + "CombatClassAttack" "DOTA_COMBAT_CLASS_ATTACK_PIERCE" + "CombatClassDefend" "DOTA_COMBAT_CLASS_DEFEND_BASIC" + "UnitRelationshipClass" "DOTA_NPC_UNIT_RELATIONSHIP_TYPE_DEFAULT" + "Creature" + { + "OffensiveAbilities" + { + "Ability1" + { + "Name" "zombie_rage" + } + "Ability2" + { + "Name" "zombie_open_wounds" + } + } + } + } + "npc_wave_boss_death_prophet" + { + "Model" "models/heroes/death_prophet/death_prophet.vmdl" + "BaseClass" "npc_dota_creature" + "SoundSet" "n_creep_Ranged" + "GameSoundsFile" "soundevents/game_sounds_creeps.vsndevts" + "ModelScale" "1.4" + "BoundsHullName" "DOTA_HULL_SIZE_REGULAR" + "UseNeutralCreepBehavior" "0" + + "Ability1" "death_prophet_carrion_swarm" + "Ability2" "death_prophet_silence" + "Ability3" "death_prophet_spirit_siphon" + "Ability4" "ghost_evasive" + "Ability5" "ability_unit_less_laggy" + "Ability6" "death_prophet_exorcism" + + // Armor + //---------------------------------------------------------------- + "ArmorPhysical" "12" + "MagicalResistance" "0" + + // Attack + //---------------------------------------------------------------- + "AttackCapabilities" "DOTA_UNIT_CAP_RANGED_ATTACK" + "AttackDamageType" "DAMAGE_TYPE_ArmorPhysical" + "AttackDamageMin" "140" + "AttackDamageMax" "210" + "AttackRate" "1.7" + "AttackAnimationPoint" "0.4" + "AttackAcquisitionRange" "1100" + "AttackRange" "630" + "BaseAttackSpeed" "140" + "ProjectileModel" "particles/units/heroes/hero_death_prophet/death_prophet_base_attack.vpcf" + "ProjectileSpeed" "1000" + + // Bounds + //---------------------------------------------------------------- + "RingRadius" "40" + "HealthBarOffset" "170" + + // Bounty + //---------------------------------------------------------------- + "BountyXP" "400" + "BountyGoldMin" "0" + "BountyGoldMax" "0" + + // Movement + //---------------------------------------------------------------- + "MovementCapabilities" "DOTA_UNIT_CAP_MOVE_GROUND" + "MovementSpeed" "550" + + + // Status + //---------------------------------------------------------------- + "StatusHealth" "6500" + "StatusHealthRegen" "0" + "StatusMana" "4000" + "StatusManaRegen" "0.0" + + // Vision + //---------------------------------------------------------------- + "VisionDaytimeRange" "900" + "VisionNighttimeRange" "900" + + + // Team + //---------------------------------------------------------------- + "TeamName" "DOTA_TEAM_NEUTRALS" + "CombatClassAttack" "DOTA_COMBAT_CLASS_ATTACK_PIERCE" + "CombatClassDefend" "DOTA_COMBAT_CLASS_DEFEND_BASIC" + "UnitRelationshipClass" "DOTA_NPC_UNIT_RELATIONSHIP_TYPE_DEFAULT" + "Creature" + { + "OffensiveAbilities" + { + "Ability1" + { + "Name" "death_prophet_carrion_swarm" + } + "Ability2" + { + "Name" "death_prophet_silence" + } + "Ability3" + { + "Name" "death_prophet_spirit_siphon" + } + "Ability6" + { + "Name" "death_prophet_exorcism" + } + } + "AttachWearables" + { + "Wearable1" { "ItemDef" "5597" } + "Wearable1" { "ItemDef" "5598" } + "Wearable1" { "ItemDef" "5599" } + "Wearable1" { "ItemDef" "5600" } + "Wearable1" { "ItemDef" "5603" } + } + } + } + "npc_wave_boss_suicide_zombie" + { + "Model" "models/items/courier/pw_zombie/pw_zombie_flying.vmdl" + "BaseClass" "npc_dota_creature" + "SoundSet" "n_creep_Ranged" + "GameSoundsFile" "soundevents/game_sounds_creeps.vsndevts" + "ModelScale" "1.4" + "BoundsHullName" "DOTA_HULL_SIZE_REGULAR" + "UseNeutralCreepBehavior" "0" + + "Ability1" "ghost_evasive" + "Ability2" "suicide_boys" + "Ability3" "" + "Ability4" "" + "Ability5" "" + "Ability6" "" + + // Armor + //---------------------------------------------------------------- + "ArmorPhysical" "20" + "MagicalResistance" "20" + + // Attack + //---------------------------------------------------------------- + "AttackCapabilities" "DOTA_UNIT_CAP_NO_ATTACK" + "AttackDamageType" "DAMAGE_TYPE_ArmorPhysical" + "AttackDamageMin" "0" + "AttackDamageMax" "0" + "AttackRate" "1.7" + "AttackAnimationPoint" "0.4" + "AttackAcquisitionRange" "1100" + "AttackRange" "630" + "BaseAttackSpeed" "140" + "ProjectileModel" "particles/units/heroes/hero_death_prophet/death_prophet_base_attack.vpcf" + "ProjectileSpeed" "1000" + + // Bounds + //---------------------------------------------------------------- + "RingRadius" "40" + "HealthBarOffset" "170" + + // Bounty + //---------------------------------------------------------------- + "BountyXP" "400" + "BountyGoldMin" "0" + "BountyGoldMax" "0" + + // Movement + //---------------------------------------------------------------- + "MovementCapabilities" "DOTA_UNIT_CAP_MOVE_GROUND" + "MovementSpeed" "100" + + + // Status + //---------------------------------------------------------------- + "StatusHealth" "200000" + "StatusHealthRegen" "0" + "StatusMana" "10000" + "StatusManaRegen" "0.0" + + // Vision + //---------------------------------------------------------------- + "VisionDaytimeRange" "900" + "VisionNighttimeRange" "900" + + + // Team + //---------------------------------------------------------------- + "TeamName" "DOTA_TEAM_NEUTRALS" + "CombatClassAttack" "DOTA_COMBAT_CLASS_ATTACK_PIERCE" + "CombatClassDefend" "DOTA_COMBAT_CLASS_DEFEND_BASIC" + "UnitRelationshipClass" "DOTA_NPC_UNIT_RELATIONSHIP_TYPE_DEFAULT" + "Creature" + { + + } + } + "npc_wave_boss_zombie_king" + { + "Model" "models/items/undying/flesh_golem/direstones_corruption_flesh_golem/direstones_corruption_flesh_golem.vmdl" + "BaseClass" "npc_dota_creature" + "SoundSet" "n_creep_Melee" + "GameSoundsFile" "soundevents/game_sounds_creeps.vsndevts" + "ModelScale" "1.4" + "BoundsHullName" "DOTA_HULL_SIZE_REGULAR" + "UseNeutralCreepBehavior" "0" + + "Ability1" "zombie_rage" + "Ability2" "undying_tombstone" + "Ability3" "undying_tombstone_zombie_aura" + "Ability4" "bone_armor" + "Ability5" "skeleton_king_mortal_strike" + "Ability6" "ability_unit_less_laggy" + "Ability7" "zombie_armor_decress" + // Armor + //---------------------------------------------------------------- + "ArmorPhysical" "-12" + "MagicalResistance" "25" + + // Attack + //---------------------------------------------------------------- + "AttackCapabilities" "DOTA_UNIT_CAP_MELEE_ATTACK" + "AttackDamageType" "DAMAGE_TYPE_ArmorPhysical" + "AttackDamageMin" "140" + "AttackDamageMax" "210" + "AttackRate" "1.7" + "AttackAnimationPoint" "0.4" + "AttackAcquisitionRange" "1100" + "AttackRange" "130" + "BaseAttackSpeed" "140" + + // Bounds + //---------------------------------------------------------------- + "RingRadius" "40" + "HealthBarOffset" "170" + + // Bounty + //---------------------------------------------------------------- + "BountyXP" "400" + "BountyGoldMin" "0" + "BountyGoldMax" "0" + + // Movement + //---------------------------------------------------------------- + "MovementCapabilities" "DOTA_UNIT_CAP_MOVE_GROUND" + "MovementSpeed" "550" + + + // Status + //---------------------------------------------------------------- + "StatusHealth" "9000" + "StatusHealthRegen" "5" + "StatusMana" "400" + "StatusManaRegen" "0.0" + + // Vision + //---------------------------------------------------------------- + "VisionDaytimeRange" "900" + "VisionNighttimeRange" "900" + + + // Team + //---------------------------------------------------------------- + "TeamName" "DOTA_TEAM_NEUTRALS" + "CombatClassAttack" "DOTA_COMBAT_CLASS_ATTACK_PIERCE" + "CombatClassDefend" "DOTA_COMBAT_CLASS_DEFEND_BASIC" + "UnitRelationshipClass" "DOTA_NPC_UNIT_RELATIONSHIP_TYPE_DEFAULT" + "Creature" + { + "OffensiveAbilities" + { + "Ability1" + { + "Name" "zombie_rage" + } + "Ability2" + { + "Name" "undying_tombstone" + } + } + } + } + + +} \ No newline at end of file diff --git a/scripts/npc/wave/npc_wave_zombies.kv b/scripts/npc/wave/npc_wave_zombies.kv new file mode 100644 index 0000000..5b922ed --- /dev/null +++ b/scripts/npc/wave/npc_wave_zombies.kv @@ -0,0 +1,1176 @@ + +"DOTAUnits" +{ + // 1 wave + "npc_wave_zombie" + { + "Model" "models/heroes/undying/undying_minion.vmdl" + "BaseClass" "npc_dota_creature" + "SoundSet" "n_creep_Melee" + "GameSoundsFile" "soundevents/game_sounds_creeps.vsndevts" + "ModelScale" ".9" + "BoundsHullName" "DOTA_HULL_SIZE_REGULAR" + "UseNeutralCreepBehavior" "0" + + "Ability1" "zombie_virus" + "Ability2" "ability_unit_less_laggy" + "Ability3" "" + "Ability4" "" + + // Armor + //---------------------------------------------------------------- + "ArmorPhysical" "8" + "MagicalResistance" "12" + + // Attack + //---------------------------------------------------------------- + "AttackCapabilities" "DOTA_UNIT_CAP_MELEE_ATTACK" + "AttackDamageType" "DAMAGE_TYPE_ArmorPhysical" + "AttackDamageMin" "19" + "AttackDamageMax" "25" + "AttackRate" "1.65" + "AttackAnimationPoint" "0.4" + "AttackAcquisitionRange" "1100" + "AttackRange" "130" + "BaseAttackSpeed" "140" + + // Bounds + //---------------------------------------------------------------- + "RingRadius" "40" + "HealthBarOffset" "170" + + // Bounty + //---------------------------------------------------------------- + "BountyXP" "0" + "BountyGoldMin" "0" + "BountyGoldMax" "0" + + // Movement + //---------------------------------------------------------------- + "MovementCapabilities" "DOTA_UNIT_CAP_MOVE_GROUND" + "MovementSpeed" "520" + + + // Status + //---------------------------------------------------------------- + "StatusHealth" "415" + "StatusHealthRegen" "0" + "StatusMana" "400" + "StatusManaRegen" "0.0" + + // Vision + //---------------------------------------------------------------- + "VisionDaytimeRange" "900" + "VisionNighttimeRange" "900" + + + // Team + //---------------------------------------------------------------- + "TeamName" "DOTA_TEAM_NEUTRALS" + "CombatClassAttack" "DOTA_COMBAT_CLASS_ATTACK_PIERCE" + "CombatClassDefend" "DOTA_COMBAT_CLASS_DEFEND_BASIC" + "UnitRelationshipClass" "DOTA_NPC_UNIT_RELATIONSHIP_TYPE_DEFAULT" + + } + "npc_wave_half_zombie" + { + "Model" "models/heroes/undying/undying_minion_torso.vmdl" + "BaseClass" "npc_dota_creature" + "SoundSet" "n_creep_Melee" + "GameSoundsFile" "soundevents/game_sounds_creeps.vsndevts" + "ModelScale" ".7" + "BoundsHullName" "DOTA_HULL_SIZE_REGULAR" + "UseNeutralCreepBehavior" "0" + + "Ability1" "zombie_virus" + "Ability2" "ability_unit_less_laggy" + "Ability3" "" + "Ability4" "" + + // Armor + //---------------------------------------------------------------- + "ArmorPhysical" "3" + "MagicalResistance" "0" + + // Attack + //---------------------------------------------------------------- + "AttackCapabilities" "DOTA_UNIT_CAP_MELEE_ATTACK" + "AttackDamageType" "DAMAGE_TYPE_ArmorPhysical" + "AttackDamageMin" "10" + "AttackDamageMax" "13" + "AttackRate" "1.65" + "AttackAnimationPoint" "0.4" + "AttackAcquisitionRange" "1100" + "AttackRange" "130" + "BaseAttackSpeed" "140" + + // Bounds + //---------------------------------------------------------------- + "RingRadius" "40" + "HealthBarOffset" "170" + + // Bounty + //---------------------------------------------------------------- + "BountyXP" "0" + "BountyGoldMin" "0" + "BountyGoldMax" "0" + + // Movement + //---------------------------------------------------------------- + "MovementCapabilities" "DOTA_UNIT_CAP_MOVE_GROUND" + "MovementSpeed" "535" + + + // Status + //---------------------------------------------------------------- + "StatusHealth" "240" + "StatusHealthRegen" "0" + "StatusMana" "400" + "StatusManaRegen" "0.0" + + // Vision + //---------------------------------------------------------------- + "VisionDaytimeRange" "900" + "VisionNighttimeRange" "900" + + + // Team + //---------------------------------------------------------------- + "TeamName" "DOTA_TEAM_NEUTRALS" + "CombatClassAttack" "DOTA_COMBAT_CLASS_ATTACK_PIERCE" + "CombatClassDefend" "DOTA_COMBAT_CLASS_DEFEND_BASIC" + "UnitRelationshipClass" "DOTA_NPC_UNIT_RELATIONSHIP_TYPE_DEFAULT" + + } + "npc_wave_bearst_zombie" + { + "Model" "models/items/undying/flesh_golem/deathmatch_dominator_golem/deathmatch_dominator_golem.vmdl" + "BaseClass" "npc_dota_creature" + "SoundSet" "n_creep_Melee" + "GameSoundsFile" "soundevents/game_sounds_creeps.vsndevts" + "ModelScale" ".9" + "BoundsHullName" "DOTA_HULL_SIZE_REGULAR" + "UseNeutralCreepBehavior" "0" + "Level" "7" + "Ability1" "zombie_virus" + "Ability2" "sven_great_cleave" + "Ability3" "ability_unit_less_laggy" + "Ability4" "" + + // Armor + //---------------------------------------------------------------- + "ArmorPhysical" "-5" + "MagicalResistance" "0" + + // Attack + //---------------------------------------------------------------- + "AttackCapabilities" "DOTA_UNIT_CAP_MELEE_ATTACK" + "AttackDamageType" "DAMAGE_TYPE_ArmorPhysical" + "AttackDamageMin" "112" + "AttackDamageMax" "136" + "AttackRate" "4.6" + "AttackAnimationPoint" "0.4" + "AttackAcquisitionRange" "1100" + "AttackRange" "130" + "BaseAttackSpeed" "140" + + // Bounds + //---------------------------------------------------------------- + "RingRadius" "40" + "HealthBarOffset" "170" + + // Bounty + //---------------------------------------------------------------- + "BountyXP" "0" + "BountyGoldMin" "0" + "BountyGoldMax" "0" + + // Movement + //---------------------------------------------------------------- + "MovementCapabilities" "DOTA_UNIT_CAP_MOVE_GROUND" + "MovementSpeed" "500" + + + // Status + //---------------------------------------------------------------- + "StatusHealth" "535" + "StatusHealthRegen" "0" + "StatusMana" "400" + "StatusManaRegen" "0.0" + + // Vision + //---------------------------------------------------------------- + "VisionDaytimeRange" "900" + "VisionNighttimeRange" "900" + + + // Team + //---------------------------------------------------------------- + "TeamName" "DOTA_TEAM_NEUTRALS" + "CombatClassAttack" "DOTA_COMBAT_CLASS_ATTACK_PIERCE" + "CombatClassDefend" "DOTA_COMBAT_CLASS_DEFEND_BASIC" + "UnitRelationshipClass" "DOTA_NPC_UNIT_RELATIONSHIP_TYPE_DEFAULT" + } + "npc_wave_toxin_zombie" + { + "Model" "models/heroes/undying/undying_flesh_golem_rubick.vmdl" + "BaseClass" "npc_dota_creature" + "SoundSet" "n_creep_Melee" + "GameSoundsFile" "soundevents/game_sounds_creeps.vsndevts" + "ModelScale" ".7" + "BoundsHullName" "DOTA_HULL_SIZE_REGULAR" + "UseNeutralCreepBehavior" "0" + "Level" "2" + + "Ability1" "toxin" + "Ability2" "zombie_virus" + "Ability3" "viper_corrosive_skin" + "Ability4" "ability_unit_less_laggy" + + // Armor + //---------------------------------------------------------------- + "ArmorPhysical" "13" + "MagicalResistance" "0" + + // Attack + //---------------------------------------------------------------- + "AttackCapabilities" "DOTA_UNIT_CAP_MELEE_ATTACK" + "AttackDamageType" "DAMAGE_TYPE_ArmorPhysical" + "AttackDamageMin" "54" + "AttackDamageMax" "60" + "AttackRate" "1.28" + "AttackAnimationPoint" "0.4" + "AttackAcquisitionRange" "1100" + "AttackRange" "130" + "BaseAttackSpeed" "140" + + // Bounds + //---------------------------------------------------------------- + "RingRadius" "40" + "HealthBarOffset" "170" + + // Bounty + //---------------------------------------------------------------- + "BountyXP" "0" + "BountyGoldMin" "0" + "BountyGoldMax" "0" + + // Movement + //---------------------------------------------------------------- + "MovementCapabilities" "DOTA_UNIT_CAP_MOVE_GROUND" + "MovementSpeed" "510" + + + // Status + //---------------------------------------------------------------- + "StatusHealth" "695" + "StatusHealthRegen" "0" + "StatusMana" "400" + "StatusManaRegen" "0.0" + + // Vision + //---------------------------------------------------------------- + "VisionDaytimeRange" "900" + "VisionNighttimeRange" "900" + + + // Team + //---------------------------------------------------------------- + "TeamName" "DOTA_TEAM_NEUTRALS" + "CombatClassAttack" "DOTA_COMBAT_CLASS_ATTACK_PIERCE" + "CombatClassDefend" "DOTA_COMBAT_CLASS_DEFEND_BASIC" + "UnitRelationshipClass" "DOTA_NPC_UNIT_RELATIONSHIP_TYPE_DEFAULT" + } + // 2 wave + "npc_wave_skeleton_zombie" + { + "Model" "models/items/undying/idol_of_ruination/ruin_wight_minion.vmdl" + "BaseClass" "npc_dota_creature" + "SoundSet" "n_creep_Melee" + "GameSoundsFile" "soundevents/game_sounds_creeps.vsndevts" + "ModelScale" "1.1" + "BoundsHullName" "DOTA_HULL_SIZE_REGULAR" + "UseNeutralCreepBehavior" "0" + + "Ability1" "" + "Ability2" "bone_armor" + "Ability3" "ability_unit_less_laggy" + "Ability4" "" + + // Armor + //---------------------------------------------------------------- + "ArmorPhysical" "11" + "MagicalResistance" "0" + + // Attack + //---------------------------------------------------------------- + "AttackCapabilities" "DOTA_UNIT_CAP_MELEE_ATTACK" + "AttackDamageType" "DAMAGE_TYPE_ArmorPhysical" + "AttackDamageMin" "48" + "AttackDamageMax" "55" + "AttackRate" "1.28" + "AttackAnimationPoint" "0.4" + "AttackAcquisitionRange" "1100" + "AttackRange" "130" + "BaseAttackSpeed" "140" + + // Bounds + //---------------------------------------------------------------- + "RingRadius" "40" + "HealthBarOffset" "170" + + // Bounty + //---------------------------------------------------------------- + "BountyXP" "0" + "BountyGoldMin" "0" + "BountyGoldMax" "0" + + // Movement + //---------------------------------------------------------------- + "MovementCapabilities" "DOTA_UNIT_CAP_MOVE_GROUND" + "MovementSpeed" "515" + + + // Status + //---------------------------------------------------------------- + "StatusHealth" "395" + "StatusHealthRegen" "0" + "StatusMana" "400" + "StatusManaRegen" "0.0" + + // Vision + //---------------------------------------------------------------- + "VisionDaytimeRange" "900" + "VisionNighttimeRange" "900" + + + // Team + //---------------------------------------------------------------- + "TeamName" "DOTA_TEAM_NEUTRALS" + "CombatClassAttack" "DOTA_COMBAT_CLASS_ATTACK_PIERCE" + "CombatClassDefend" "DOTA_COMBAT_CLASS_DEFEND_BASIC" + "UnitRelationshipClass" "DOTA_NPC_UNIT_RELATIONSHIP_TYPE_DEFAULT" + + } + "npc_wave_skeleton_zombie_half" + { + "Model" "models/items/undying/idol_of_ruination/ruin_wight_minion_torso.vmdl" + "BaseClass" "npc_dota_creature" + "SoundSet" "n_creep_Melee" + "GameSoundsFile" "soundevents/game_sounds_creeps.vsndevts" + "ModelScale" "1.1" + "BoundsHullName" "DOTA_HULL_SIZE_REGULAR" + "UseNeutralCreepBehavior" "0" + + "Ability1" "" + "Ability2" "bone_armor" + "Ability3" "ability_unit_less_laggy" + "Ability4" "" + + // Armor + //---------------------------------------------------------------- + "ArmorPhysical" "10" + "MagicalResistance" "0" + + // Attack + //---------------------------------------------------------------- + "AttackCapabilities" "DOTA_UNIT_CAP_MELEE_ATTACK" + "AttackDamageType" "DAMAGE_TYPE_ArmorPhysical" + "AttackDamageMin" "35" + "AttackDamageMax" "41" + "AttackRate" "1.32" + "AttackAnimationPoint" "0.4" + "AttackAcquisitionRange" "1100" + "AttackRange" "130" + "BaseAttackSpeed" "140" + + // Bounds + //---------------------------------------------------------------- + "RingRadius" "40" + "HealthBarOffset" "170" + + // Bounty + //---------------------------------------------------------------- + "BountyXP" "0" + "BountyGoldMin" "0" + "BountyGoldMax" "0" + + // Movement + //---------------------------------------------------------------- + "MovementCapabilities" "DOTA_UNIT_CAP_MOVE_GROUND" + "MovementSpeed" "530" + + + // Status + //---------------------------------------------------------------- + "StatusHealth" "295" + "StatusHealthRegen" "0" + "StatusMana" "400" + "StatusManaRegen" "0.0" + + // Vision + //---------------------------------------------------------------- + "VisionDaytimeRange" "900" + "VisionNighttimeRange" "900" + + + // Team + //---------------------------------------------------------------- + "TeamName" "DOTA_TEAM_NEUTRALS" + "CombatClassAttack" "DOTA_COMBAT_CLASS_ATTACK_PIERCE" + "CombatClassDefend" "DOTA_COMBAT_CLASS_DEFEND_BASIC" + "UnitRelationshipClass" "DOTA_NPC_UNIT_RELATIONSHIP_TYPE_DEFAULT" + + } + "npc_wave_dead_skeleton" + { + "Model" "models/creeps/neutral_creeps/n_creep_troll_skeleton/n_creep_skeleton_melee.vmdl" + "BaseClass" "npc_dota_creature" + "SoundSet" "n_creep_Melee" + "GameSoundsFile" "soundevents/game_sounds_creeps.vsndevts" + "ModelScale" "1.2" + "BoundsHullName" "DOTA_HULL_SIZE_REGULAR" + "UseNeutralCreepBehavior" "0" + + "Ability1" "bone_armor" + "Ability2" "ability_unit_less_laggy" + "Ability3" "" + "Ability4" "" + + // Armor + //---------------------------------------------------------------- + "ArmorPhysical" "11" + "MagicalResistance" "0" + + // Attack + //---------------------------------------------------------------- + "AttackCapabilities" "DOTA_UNIT_CAP_MELEE_ATTACK" + "AttackDamageType" "DAMAGE_TYPE_ArmorPhysical" + "AttackDamageMin" "30" + "AttackDamageMax" "38" + "AttackRate" "1.28" + "AttackAnimationPoint" "0.4" + "AttackAcquisitionRange" "1100" + "AttackRange" "130" + "BaseAttackSpeed" "140" + + // Bounds + //---------------------------------------------------------------- + "RingRadius" "40" + "HealthBarOffset" "170" + + // Bounty + //---------------------------------------------------------------- + "BountyXP" "0" + "BountyGoldMin" "0" + "BountyGoldMax" "0" + + // Movement + //---------------------------------------------------------------- + "MovementCapabilities" "DOTA_UNIT_CAP_MOVE_GROUND" + "MovementSpeed" "510" + + + // Status + //---------------------------------------------------------------- + "StatusHealth" "368" + "StatusHealthRegen" "0" + "StatusMana" "400" + "StatusManaRegen" "0.0" + + // Vision + //---------------------------------------------------------------- + "VisionDaytimeRange" "900" + "VisionNighttimeRange" "900" + + + // Team + //---------------------------------------------------------------- + "TeamName" "DOTA_TEAM_NEUTRALS" + "CombatClassAttack" "DOTA_COMBAT_CLASS_ATTACK_PIERCE" + "CombatClassDefend" "DOTA_COMBAT_CLASS_DEFEND_BASIC" + "UnitRelationshipClass" "DOTA_NPC_UNIT_RELATIONSHIP_TYPE_DEFAULT" + + } + "npc_wave_dead_skeleton_archer" + { + "Model" "models/heroes/clinkz/clinkz_archer.vmdl" + "BaseClass" "npc_dota_creature" + "SoundSet" "n_creep_Melee" + "GameSoundsFile" "soundevents/game_sounds_creeps.vsndevts" + "ModelScale" "1.2" + "BoundsHullName" "DOTA_HULL_SIZE_REGULAR" + "UseNeutralCreepBehavior" "0" + + "Ability1" "ability_unit_less_laggy" + "Ability2" "bone_armor" + "Ability3" "skeleton_archer_fire_arrow" + "Ability4" "" + + // Armor + //---------------------------------------------------------------- + "ArmorPhysical" "7" + "MagicalResistance" "0" + + // Attack + //---------------------------------------------------------------- + "AttackCapabilities" "DOTA_UNIT_CAP_RANGED_ATTACK" + "AttackDamageMin" "52" + "AttackDamageMax" "64" + "AttackDamageType" "DAMAGE_TYPE_ArmorPhysical" + "AttackRate" "1.72" + "AttackAnimationPoint" "0.3" + "AttackAcquisitionRange" "1000" + "AttackRange" "500" + "ProjectileModel" "particles/econ/items/clinkz/clinkz_maraxiform/clinkz_maraxiform_searing_arrow_deso.vpcf" + "ProjectileSpeed" "900" + + // Bounds + //---------------------------------------------------------------- + "RingRadius" "40" + "HealthBarOffset" "170" + + // Bounty + //---------------------------------------------------------------- + "BountyXP" "0" + "BountyGoldMin" "0" + "BountyGoldMax" "0" + + // Movement + //---------------------------------------------------------------- + "MovementCapabilities" "DOTA_UNIT_CAP_MOVE_GROUND" + "MovementSpeed" "505" + + + // Status + //---------------------------------------------------------------- + "StatusHealth" "158" + "StatusHealthRegen" "0" + "StatusMana" "400" + "StatusManaRegen" "0.0" + + // Vision + //---------------------------------------------------------------- + "VisionDaytimeRange" "900" + "VisionNighttimeRange" "900" + + + // Team + //---------------------------------------------------------------- + "TeamName" "DOTA_TEAM_NEUTRALS" + "CombatClassAttack" "DOTA_COMBAT_CLASS_ATTACK_PIERCE" + "CombatClassDefend" "DOTA_COMBAT_CLASS_DEFEND_BASIC" + "UnitRelationshipClass" "DOTA_NPC_UNIT_RELATIONSHIP_TYPE_DEFAULT" + } + // 3 wave + "npc_wave_ghost_ranged" + { + // General + //---------------------------------------------------------------- + "Model" "models/creeps/neutral_creeps/n_creep_ghost_a/n_creep_ghost_a.vmdl" // Model. + "BaseClass" "npc_dota_creep_neutral" + "SoundSet" "n_creep_Ranged" + "Level" "3" + "ModelScale" ".90" + "UseNeutralCreepBehavior" "0" + "IsNeutralUnitType" "0" + + // Abilities + //---------------------------------------------------------------- + "Ability1" "ghost_frost_attack" // Ability 1 + "Ability2" "weaking_impetus" // Ability 2 + "Ability3" "ghost_evasive" // Ability 3 + "Ability4" "ability_unit_less_laggy" // Ability 4 + + // Armor + //---------------------------------------------------------------- + "ArmorPhysical" "7" // Physical protection. + + // Attack + //---------------------------------------------------------------- + "AttackCapabilities" "DOTA_UNIT_CAP_RANGED_ATTACK" + "AttackDamageMin" "54" // Damage range min. + "AttackDamageMax" "78" // Damage range max. + "AttackRate" "1.08" // Speed of attack. + "AttackAnimationPoint" "0.3" // Normalized time in animation cycle to attack. + "AttackAcquisitionRange" "300" // Range within a target can be acquired. + "AttackRange" "400" // Range within a target can be attacked. + "ProjectileModel" "particles/neutral_fx/ghost_base_attack.vpcf" // Particle system model for projectile. + "ProjectileSpeed" "900" // Speed of projectile. + + // Bounds + //---------------------------------------------------------------- + "RingRadius" "45" + "HealthBarOffset" "190" + + // Bounty + //---------------------------------------------------------------- + "BountyXP" "0" // Experience earn. + "BountyGoldMin" "12" // Gold earned min. + "BountyGoldMax" "14" // Gold earned max. + + // Movement + //---------------------------------------------------------------- + "MovementCapabilities" "DOTA_UNIT_CAP_MOVE_GROUND" + "MovementSpeed" "335" // Speed. + + // Status + //---------------------------------------------------------------- + "StatusHealth" "455" // Base health. + "StatusHealthRegen" "0.5" // Health regeneration rate. + "StatusMana" "400" // Base mana. + "StatusManaRegen" "1.0" // Mana regeneration rate. + + // Vision + //---------------------------------------------------------------- + "VisionDaytimeRange" "400" // Range of vision during day light. + "VisionNighttimeRange" "400" // Range of vision at night time. + + // Team + //---------------------------------------------------------------- + "TeamName" "DOTA_TEAM_NEUTRALS" // Team name. + "UnitRelationshipClass" "DOTA_NPC_UNIT_RELATIONSHIP_TYPE_DEFAULT" + } + + "npc_wave_ghost" + { + "Model" "models/creeps/neutral_creeps/n_creep_ghost_b/n_creep_ghost_b.vmdl" + "BaseClass" "npc_dota_creature" + "SoundSet" "n_creep_Melee" + "GameSoundsFile" "soundevents/game_sounds_creeps.vsndevts" + "ModelScale" ".9" + "BoundsHullName" "DOTA_HULL_SIZE_REGULAR" + "UseNeutralCreepBehavior" "0" + + "Ability1" "ghost_evasive" + "Ability2" "ability_unit_less_laggy" + "Ability3" "ancient_rock_golem_weakening_aura" + "Ability4" "" + + // Armor + //---------------------------------------------------------------- + "ArmorPhysical" "10" + "MagicalResistance" "18" + + // Attack + //---------------------------------------------------------------- + "AttackCapabilities" "DOTA_UNIT_CAP_MELEE_ATTACK" + "AttackDamageType" "DAMAGE_TYPE_ArmorPhysical" + "AttackDamageMin" "85" + "AttackDamageMax" "94" + "AttackRate" "1.62" + "AttackAnimationPoint" "0.4" + "AttackAcquisitionRange" "1100" + "AttackRange" "130" + "BaseAttackSpeed" "140" + + // Bounds + //---------------------------------------------------------------- + "RingRadius" "40" + "HealthBarOffset" "170" + + // Bounty + //---------------------------------------------------------------- + "BountyXP" "0" + "BountyGoldMin" "0" + "BountyGoldMax" "0" + + // Movement + //---------------------------------------------------------------- + "MovementCapabilities" "DOTA_UNIT_CAP_MOVE_GROUND" + "MovementSpeed" "522" + + + // Status + //---------------------------------------------------------------- + "StatusHealth" "472" + "StatusHealthRegen" "0" + "StatusMana" "400" + "StatusManaRegen" "0.0" + + // Vision + //---------------------------------------------------------------- + "VisionDaytimeRange" "900" + "VisionNighttimeRange" "900" + + + // Team + //---------------------------------------------------------------- + "TeamName" "DOTA_TEAM_NEUTRALS" + "CombatClassAttack" "DOTA_COMBAT_CLASS_ATTACK_PIERCE" + "CombatClassDefend" "DOTA_COMBAT_CLASS_DEFEND_BASIC" + "UnitRelationshipClass" "DOTA_NPC_UNIT_RELATIONSHIP_TYPE_DEFAULT" + + } + "npc_wave_toxin_2_zombie" + { + "Model" "models/items/undying/flesh_golem/davy_jones_set_davy_jones_set_kraken/davy_jones_set_davy_jones_set_kraken.vmdl" + "BaseClass" "npc_dota_creature" + "SoundSet" "n_creep_Melee" + "GameSoundsFile" "soundevents/game_sounds_creeps.vsndevts" + "ModelScale" ".7" + "BoundsHullName" "DOTA_HULL_SIZE_REGULAR" + "UseNeutralCreepBehavior" "0" + + "Ability1" "ability_unit_less_laggy" + "Ability2" "toxin" + "Ability3" "viper_corrosive_skin" + "Ability4" "zombie_virus" + + // Armor + //---------------------------------------------------------------- + "ArmorPhysical" "18" + "MagicalResistance" "0" + + // Attack + //---------------------------------------------------------------- + "AttackCapabilities" "DOTA_UNIT_CAP_MELEE_ATTACK" + "AttackDamageType" "DAMAGE_TYPE_ArmorPhysical" + "AttackDamageMin" "56" + "AttackDamageMax" "63" + "AttackRate" "1.28" + "AttackAnimationPoint" "0.4" + "AttackAcquisitionRange" "1100" + "AttackRange" "130" + "BaseAttackSpeed" "140" + + // Bounds + //---------------------------------------------------------------- + "RingRadius" "40" + "HealthBarOffset" "170" + + // Bounty + //---------------------------------------------------------------- + "BountyXP" "0" + "BountyGoldMin" "0" + "BountyGoldMax" "0" + + // Movement + //---------------------------------------------------------------- + "MovementCapabilities" "DOTA_UNIT_CAP_MOVE_GROUND" + "MovementSpeed" "505" + + + // Status + //---------------------------------------------------------------- + "StatusHealth" "1180" + "StatusHealthRegen" "0" + "StatusMana" "400" + "StatusManaRegen" "0.0" + + // Vision + //---------------------------------------------------------------- + "VisionDaytimeRange" "900" + "VisionNighttimeRange" "900" + + + // Team + //---------------------------------------------------------------- + "TeamName" "DOTA_TEAM_NEUTRALS" + "CombatClassAttack" "DOTA_COMBAT_CLASS_ATTACK_PIERCE" + "CombatClassDefend" "DOTA_COMBAT_CLASS_DEFEND_BASIC" + "UnitRelationshipClass" "DOTA_NPC_UNIT_RELATIONSHIP_TYPE_DEFAULT" + "Creature" + { + + } + } + "npc_wave_dead_harpy" + { + "Model" "models/creeps/neutral_creeps/n_creep_gargoyle/n_creep_gargoyle.vmdl" // Model. + "BaseClass" "npc_dota_creep_neutral" + "SoundSet" "n_creep_Ranged" + "Level" "3" + "ModelScale" "0.925" + "UseNeutralCreepBehavior" "0" + + "Ability1" "ability_unit_less_laggy" // Ability 1 + "Ability2" "harpy_storm_chain_lightning" // Ability 2 + "Ability3" "zombie_virus" // Ability 3 + "Ability4" "" // Ability 4 + + // Armor + //---------------------------------------------------------------- + "ArmorPhysical" "11" // Physical protection. + "MagicalResistance" "62" + + // Attack + //---------------------------------------------------------------- + "AttackCapabilities" "DOTA_UNIT_CAP_RANGED_ATTACK" + "AttackDamageMin" "148" // Damage range min. + "AttackDamageMax" "164" // Damage range max. + "AttackRate" "2.08" // Speed of attack. + "BaseAttackSpeed" "125" + "AttackAnimationPoint" "0.3" // Normalized time in animation cycle to attack. + "AttackAcquisitionRange" "300" // Range within a target can be acquired. + "AttackRange" "450" // Range within a target can be attacked. + "ProjectileModel" "particles/base_attacks/ranged_badguy.vpcf" // Particle system model for projectile. + "ProjectileSpeed" "1200" // Speed of projectile. + + // Bounds + //---------------------------------------------------------------- + "RingRadius" "50" + "HealthBarOffset" "190" + + // Bounty + //---------------------------------------------------------------- + "BountyXP" "0" // Experience earn. + "BountyGoldMin" "0" // Gold earned min. + "BountyGoldMax" "0" // Gold earned max. + + // Movement + //---------------------------------------------------------------- + "MovementCapabilities" "DOTA_UNIT_CAP_MOVE_FLY" + "MovementSpeed" "322" // Speed. + "MovementTurnRate" "0.9" // Turning rate. + + // Status + //---------------------------------------------------------------- + "StatusHealth" "655" // Base health. + "StatusHealthRegen" "0.5" // Health regeneration rate. + "StatusMana" "125" // Base mana. + "StatusManaRegen" "3" // Mana regeneration rate. + + // Vision + //---------------------------------------------------------------- + "VisionDaytimeRange" "1800" // Range of vision during day light. + "VisionNighttimeRange" "1800" // Range of vision at night time. + + // Team + //---------------------------------------------------------------- + "TeamName" "DOTA_TEAM_NEUTRALS" // Team name. + "UnitRelationshipClass" "DOTA_NPC_UNIT_RELATIONSHIP_TYPE_DEFAULT" + "Creature" + { + "OffensiveAbilities" + { + "Ability2" + { + "Name" "harpy_storm_chain_lightning" + "AOE" "1" + "Radius" "600" + "MinimumTargets" "1" + } + + } + } + } + + + + + + + + // ???? wave + "npc_wave_dead_enchantress" + { + "Model" "models/heroes/enchantress/enchantress.vmdl" + "BaseClass" "npc_dota_creature" + "SoundSet" "n_creep_Melee" + "GameSoundsFile" "soundevents/game_sounds_creeps.vsndevts" + "ModelScale" ".9" + "BoundsHullName" "DOTA_HULL_SIZE_REGULAR" + "UseNeutralCreepBehavior" "0" + + "Ability1" "weaking_impetus" + "Ability2" "ability_unit_less_laggy" + "Ability3" "" + "Ability4" "" + + // Armor + //---------------------------------------------------------------- + "ArmorPhysical" "10" + "MagicalResistance" "14" + + // Attack + //---------------------------------------------------------------- + "AttackCapabilities" "DOTA_UNIT_CAP_RANGED_ATTACK" + "AttackDamageMin" "78" + "AttackDamageMax" "85" + "AttackDamageType" "DAMAGE_TYPE_ArmorPhysical" + "AttackRate" "1.62" + "AttackAnimationPoint" "0.3" + "AttackAcquisitionRange" "800" + "AttackRange" "575" + + "ProjectileModel" "particles/enchantress_impetus_custom_gay.vpcf" + "ProjectileSpeed" "1100" + + // Bounds + //---------------------------------------------------------------- + "RingRadius" "40" + "HealthBarOffset" "170" + + // Bounty + //---------------------------------------------------------------- + "BountyXP" "0" + "BountyGoldMin" "0" + "BountyGoldMax" "0" + + // Movement + //---------------------------------------------------------------- + "MovementCapabilities" "DOTA_UNIT_CAP_MOVE_GROUND" + "MovementSpeed" "518" + + + // Status + //---------------------------------------------------------------- + "StatusHealth" "530" + "StatusHealthRegen" "0" + "StatusMana" "400" + "StatusManaRegen" "4.2" + + // Vision + //---------------------------------------------------------------- + "VisionDaytimeRange" "900" + "VisionNighttimeRange" "900" + + + // Team + //---------------------------------------------------------------- + "TeamName" "DOTA_TEAM_NEUTRALS" + "CombatClassAttack" "DOTA_COMBAT_CLASS_ATTACK_PIERCE" + "CombatClassDefend" "DOTA_COMBAT_CLASS_DEFEND_BASIC" + "UnitRelationshipClass" "DOTA_NPC_UNIT_RELATIONSHIP_TYPE_DEFAULT" + "Creature" + { + "AttachWearables" + { + "Wearable1" { "ItemDef" "17801" } + "Wearable2" { "ItemDef" "17802" } + "Wearable3" { "ItemDef" "17803" } + "Wearable4" { "ItemDef" "17804" } + "Wearable5" { "ItemDef" "17805" } + "Wearable6" { "ItemDef" "17806" } + "Wearable7" { "ItemDef" "21697" } + + } + } + + } + "npc_wave_ghoul" + { + "Model" "models/heroes/life_stealer/life_stealer.vmdl" + "BaseClass" "npc_dota_creature" + "SoundSet" "n_creep_Melee" + "GameSoundsFile" "soundevents/game_sounds_creeps.vsndevts" + "ModelScale" ".9" + "BoundsHullName" "DOTA_HULL_SIZE_REGULAR" + "UseNeutralCreepBehavior" "0" + + "Ability1" "" + "Ability2" "zombie_open_wounds" + "Ability3" "" + "Ability4" "bone_armor" + "Ability5" "" + "Ability6" "ability_unit_less_laggy" + "Ability7" "zombie_armor_decress" + + // Armor + //---------------------------------------------------------------- + "ArmorPhysical" "16" + "MagicalResistance" "28" + + // Attack + //---------------------------------------------------------------- + "AttackCapabilities" "DOTA_UNIT_CAP_MELEE_ATTACK" + "AttackDamageType" "DAMAGE_TYPE_ArmorPhysical" + "AttackDamageMin" "244" + "AttackDamageMax" "272" + "AttackRate" "1.72" + "AttackAnimationPoint" "0.4" + "AttackAcquisitionRange" "1100" + "AttackRange" "130" + "BaseAttackSpeed" "140" + + // Bounds + //---------------------------------------------------------------- + "RingRadius" "40" + "HealthBarOffset" "170" + + // Bounty + //---------------------------------------------------------------- + "BountyXP" "0" + "BountyGoldMin" "0" + "BountyGoldMax" "0" + + // Movement + //---------------------------------------------------------------- + "MovementCapabilities" "DOTA_UNIT_CAP_MOVE_GROUND" + "MovementSpeed" "528" + + + // Status + //---------------------------------------------------------------- + "StatusHealth" "1205" + "StatusHealthRegen" "0" + "StatusMana" "400" + "StatusManaRegen" "0.0" + + // Vision + //---------------------------------------------------------------- + "VisionDaytimeRange" "900" + "VisionNighttimeRange" "900" + + + // Team + //---------------------------------------------------------------- + "TeamName" "DOTA_TEAM_NEUTRALS" + "CombatClassAttack" "DOTA_COMBAT_CLASS_ATTACK_PIERCE" + "CombatClassDefend" "DOTA_COMBAT_CLASS_DEFEND_BASIC" + "UnitRelationshipClass" "DOTA_NPC_UNIT_RELATIONSHIP_TYPE_DEFAULT" + "Creature" + { + "AttachWearables" + { + "Wearable1" { "ItemDef" "7475" } + "Wearable2" { "ItemDef" "7474" } + "Wearable3" { "ItemDef" "7473" } + "Wearable4" { "ItemDef" "7471" } + } + } + } + "npc_wave_skeleton_assassin" + { + "Model" "models/items/wraith_king/arcana/wk_arcana_skeleton.vmdl" + "BaseClass" "npc_dota_creature" + "SoundSet" "n_creep_Melee" + "GameSoundsFile" "soundevents/game_sounds_creeps.vsndevts" + "ModelScale" ".8" + "BoundsHullName" "DOTA_HULL_SIZE_REGULAR" + "UseNeutralCreepBehavior" "0" + + "Ability1" "skeleton_king_mortal_strike" + "Ability2" "ability_unit_less_laggy" + "Ability3" "zombie_armor_decress" + "Ability4" "bone_armor" + + + // Armor + //---------------------------------------------------------------- + "ArmorPhysical" "22" + "MagicalResistance" "22" + + // Attack + //---------------------------------------------------------------- + "AttackCapabilities" "DOTA_UNIT_CAP_MELEE_ATTACK" + "AttackDamageType" "DAMAGE_TYPE_ArmorPhysical" + "AttackDamageMin" "168" + "AttackDamageMax" "212" + "AttackRate" "1.32" + "AttackAnimationPoint" "0.4" + "AttackAcquisitionRange" "1100" + "AttackRange" "130" + "BaseAttackSpeed" "140" + + // Bounds + //---------------------------------------------------------------- + "RingRadius" "40" + "HealthBarOffset" "170" + + // Bounty + //---------------------------------------------------------------- + "BountyXP" "0" + "BountyGoldMin" "0" + "BountyGoldMax" "0" + + // Movement + //---------------------------------------------------------------- + "MovementCapabilities" "DOTA_UNIT_CAP_MOVE_GROUND" + "MovementSpeed" "545" + + + // Status + //---------------------------------------------------------------- + "StatusHealth" "615" + "StatusHealthRegen" "0" + "StatusMana" "400" + "StatusManaRegen" "0.0" + + // Vision + //---------------------------------------------------------------- + "VisionDaytimeRange" "900" + "VisionNighttimeRange" "900" + + + // Team + //---------------------------------------------------------------- + "TeamName" "DOTA_TEAM_NEUTRALS" + "CombatClassAttack" "DOTA_COMBAT_CLASS_ATTACK_PIERCE" + "CombatClassDefend" "DOTA_COMBAT_CLASS_DEFEND_BASIC" + "UnitRelationshipClass" "DOTA_NPC_UNIT_RELATIONSHIP_TYPE_DEFAULT" + + } + "npc_wave_skeleton_warrior" + { + "Model" "models/items/wraith_king/wk_ti8_creep/wk_ti8_creep_crimson.vmdl" + "BaseClass" "npc_dota_creature" + "SoundSet" "n_creep_Melee" + "GameSoundsFile" "soundevents/game_sounds_creeps.vsndevts" + "ModelScale" ".8" + "BoundsHullName" "DOTA_HULL_SIZE_REGULAR" + "UseNeutralCreepBehavior" "0" + + "Ability1" "skeleton_king_mortal_strike" + "Ability2" "ability_unit_less_laggy" + "Ability3" "zombie_armor_decress" + "Ability4" "bone_armor" + + + // Armor + //---------------------------------------------------------------- + "ArmorPhysical" "31" + "MagicalResistance" "22" + + // Attack + //---------------------------------------------------------------- + "AttackCapabilities" "DOTA_UNIT_CAP_MELEE_ATTACK" + "AttackDamageType" "DAMAGE_TYPE_ArmorPhysical" + "AttackDamageMin" "182" + "AttackDamageMax" "208" + "AttackRate" "2.85" + "AttackAnimationPoint" "0.4" + "AttackAcquisitionRange" "1100" + "AttackRange" "130" + "BaseAttackSpeed" "140" + + // Bounds + //---------------------------------------------------------------- + "RingRadius" "40" + "HealthBarOffset" "170" + + // Bounty + //---------------------------------------------------------------- + "BountyXP" "0" + "BountyGoldMin" "0" + "BountyGoldMax" "0" + + // Movement + //---------------------------------------------------------------- + "MovementCapabilities" "DOTA_UNIT_CAP_MOVE_GROUND" + "MovementSpeed" "495" + + + // Status + //---------------------------------------------------------------- + "StatusHealth" "1080" + "StatusHealthRegen" "0" + "StatusMana" "400" + "StatusManaRegen" "0.0" + + // Vision + //---------------------------------------------------------------- + "VisionDaytimeRange" "900" + "VisionNighttimeRange" "900" + + + // Team + //---------------------------------------------------------------- + "TeamName" "DOTA_TEAM_NEUTRALS" + "CombatClassAttack" "DOTA_COMBAT_CLASS_ATTACK_PIERCE" + "CombatClassDefend" "DOTA_COMBAT_CLASS_DEFEND_BASIC" + "UnitRelationshipClass" "DOTA_NPC_UNIT_RELATIONSHIP_TYPE_DEFAULT" + + } +} diff --git a/scripts/patch_notes.kv b/scripts/patch_notes.kv new file mode 100644 index 0000000..9cfdd8a --- /dev/null +++ b/scripts/patch_notes.kv @@ -0,0 +1,771 @@ +// Патчноуты для сайта /patch-notes (game/scripts/patch_notes.kv). +// В блоке «Герои»: [hero:…] портрет героя; [abil:id_способности] иконка+имя из addon (dota_tooltip_ability_). +// В блоке «Предметы»: [item:item_] иконка+имя из addon (dota_tooltip_ability_item_). +// В любом тексте патча: [card:N] — иконка карты из Panorama `content/panorama/images/custom_game/cards/card_.png` (сайт: /api/game-cardicon?id=N). +// [hero:…]: для ванильных — суффикс имени как в npc_dota_hero_* (axe, drow_ranger). Для кастомных — полное имя юнита +// (npc_dota_hero_bloodhunter, npc_dota_hero_elder_dragon_smaug, …), чтобы портрет совпадал с файлами Panorama: +// content/panorama/images/heroes/.png. Сервер лобби отдаёт их по HTTP (тот же репозиторий): +// GET {ORIGIN_СЕРВЕРА}/zi-icons/heroes/npc_dota_hero_bloodhunter.png +// (см. server/index.js — express.static на content/panorama/images/heroes). Во фронте патчноутов подставляйте этот URL +// для кастомных npc_dota_hero_*; Steam CDN для них пустой. +// Рендереру сайта: если тег уже начинается с npc_dota_hero_, не добавлять префикс повторно. +// После [abil:…] текст режется на строки: явный перенос \n; «; » между крупными пунктами; запятая+пробел, если дальше есть «→» (отдельная правка на строку). + +"ZI3PatchNotes" +{ + "ru" + { + "1" + { + "date" "2026-05-14" + "version" "0.12.0" + "title" "0.12.0" + "general" + { + "1" "Теперь способности крипов также усиляются на множитель сложности." + "2" "Удача: итоговый шанс не может превышать шанс способности более чем в два раза. Точные числа — в подсказках в игре." + } + "heroes" + { + "1" "[hero:axe][abil:axe_battle_hunger_custom]Урон/сек по цели 25/50/75/100 → 16/32/56/72, талант к урон/с +8 → +28. [abil:axe_counter_helix_custom]Урон 110/140/170/200 → 55/90/125/160, талант +200 → +160, шанс 20/25/30/35% → 14/18/22/26%." + "2" "[hero:npc_dota_hero_bloodhunter][abil:ability_bloodrage]Бонусный урон за стак 40/60/80/100 → 20/40/60/80." + "3" "[hero:bristleback][abil:bristleback_quill_spray_custom]Базовый урон ёжиков 25/50/75/100 → 12/16/20/24, талант +150 → +16; урон со стака 30/35/40/45 → 30/35/40, талант +40. [abil:bristleback_bristleback_custom]Со спины 16/24/32/40% → 14/18/22/26%. [abil:bristleback_warpath]Урон за стак 20/27/34 → 20/30/40, талант +26 → +25." + "4" "[hero:drow_ranger][abil:ability_drow_ranger_frost_arrows_custom]Урон в % от метки 25/45/65/85 → 12/24/36/48, талант +30% → +12%. [abil:ability_drow_ranger_gust_custom]Урон 280/320/460/540 → 180/240/300/360. [abil:ability_drow_ranger_multishot_custom]% урона от стрел 90/120/140/160 → 45/75/105/135, талант +160% → +45%. [abil:ability_drow_ranger_marksmanship_custom]% ловкости 55/85/115 → 25/45/65, бонусный урон 320/480/640 → 120/200/280, радиус отключения выстрела у союзников 400 → 250. [abil:drow_ranger_trueshot]Базовый бонус ловкости 15/20/25/30 → 8/12/16/20." + "5" "[hero:juggernaut][abil:ability_juggernaut_omnislash_custom]Длительность 3/3.5/4 с → 1.5/2/2.5 с." + "6" "[hero:legion_commander][abil:ability_legion_commander_duel_custom]Заряды в AbilityCharges внутри AbilityValues (1 +2 от Aghanim's Scepter), восстановление заряда 75/70/65. [abil:ability_legion_commander_overwhelming_odds_custom]Aghanim's Shard: авто-каст во время дуэли каждые 2 с (shard_overwhelming_interval)." + "7" "[hero:phantom_assassin][abil:ability_phantom_assassin_coup_de_grace_custom]Шанс крита 15/25/35/45% → 15/20/30%, множитель крита 250/500/750% → 250/400/550%. [abil:ability_phantom_assassin_phantom_bash_custom]Оглушение 0.75 → 0.15 с, шаг перезарядки между проками за уровень 0.25 → 0.10 с." + "8" "[hero:silencer][abil:glaives_of_wisdom]Урон в % от интеллекта 20/32/44/68 → 12/16/20/24, талант +32% → +16%." + "9" "[hero:skywrath_mage][abil:skywrath_mage_arcane_bolt_custom]Время восстановления заряда 5.0/4.0/3.0/2.0 → 2.0/1.5/1.0/0.5 с." + "10" "[hero:npc_dota_hero_elder_dragon_smaug][abil:ability_fire_punishment]Снижение сопр. магии цели −1/−2/−3/−4% → −5/−7/−9/−11%, талант −2% → −4%; больше не накладывает новый модификатор, а продлевает текущий." + } + "cards" + { + "1" "[card:5]Карта 5 «Карточное безумие»: бонус к атрибутам считается только по картам, взятым до неё; % за карту по уровням: 0.5/1/1.5 → 5/7/9." + } + } + "2" + { + "date" "2026-05-15" + "version" "0.13.0" + "title" "0.13.0" + "general" + { + "1" "Свиньи в зоне: с 5 до 3." + "2" "Интервал респавна свиней: с 12 до 7 секунд." + "3" "Овцы в зоне: с 5 до 3." + "4" "Интервал респавна овец: с 14 до 7 секунд." + "5" "Курицы в зоне: с 12 до 8." + "6" "Волки (энты в зоне): с 3 до 2; интервал зоны волков: с 10 до 9 секунд." + "7" "Скелеты (обе зоны): интервал респавна с 3 до 5 секунд." + "8" "Рыбы/бомбы: бомбы в зоне с 8 до 6." + "9" "Лягушки: маг/головастик с 4 до 3, лягушонок/мини с 4 до 2; интервал зоны с 8 до 6 секунд." + "10" "Змеи и пауки: каждый тип в зоне с 4 до 3." + "11" "«Смертельный приговор»: усложнения с контракта на врага при спавне больше не вешаются всегда — шанс 25% на каждую строку усложнения отдельно. Может выпасть сразу несколько разных эффектов (взрыв, токсин, вирус, фазинг и др.)." + "12" "Токсин (лужа при смерти): доля урона от макс. HP крипа снижена со 100% до 15%." + "13" "Взрыв при смерти (zombie_death_explosion): доля урона от макс. HP крипа снижена со 100% до 15%." + "14" "Арсенал: максимум экземпляров в инвентаре снижен с 507 до 207 (игра, API и сетка UI)." + "15" "За победу после матча больше нет доп. осколков за NPC-квесты." + } + } + "3" + { + "date" "2024-05-16" + "version" "0.14.0" + "title" "0.14.0" + "general" + { + "1" "За победу после матча больше нет доп. осколков за NPC-квесты." + "2" "«Смертельный приговор»: у контрактов прочность с 1 до 5; на нуле ломается." + "3" "Новый Battle Pass." + "4" "Арсенал: подсказки и выдача." + "5" "Чат-колесо: новые звуки и эффекты." + "6" "Конец матча: у контракта DS верно показывается макс. прочности." + "7" "Скины: починены частицы эффектов (BP, самоцветы и т.п.)." + "8" "За поражение в матче у контракта DS −1 прочности." + "9" "Магазин: обновлён интерфейс." + "10" "Профиль: список покупок — новые сверху." + "11" "Лобби: загрузка и отображение контрактов «Смертельный приговор»." + "12" "Свиньи и овцы в зонах спавна: с 3 до 5 в каждой зоне." + } + "heroes" + { + "1" "[hero:sand_king]Новый герой Sand King." + "2" "[hero:sniper][abil:ability_sniper_critical_focus_custom]КД 20/18/16/14 с → 30/26/22/18 с; длительность баффа 6 с → 2.5/3/3.5/4 с по уровням; талант к длительности +3 с → +1.5 с." + } + } + "4" + { + "date" "2026-05-16" + "version" "0.15.0" + "title" "0.15.0" + "general" + { + "1" "Поражение при выходе из матча засчитывается быстрее: ~90 с игрового времени после отключения, серверная проверка без heartbeat — 3 мин." + "2" "В лобби (gamesetup): при входе закрываются зависшие матчи как поражение; у контракта «Смертельный приговор» −1 прочность." + "3" "Heartbeat матча: повтор через 5 с, если API не принял сигнал." + "4" "Новые и переработанные предметы в магазине (см. раздел «Предметы»)." + } + "items" + { + "1" "Blazing Radiance — рецепт: Fire Cape + Radiance + Ultimate Crown." + "2" "Radiance (кастом) — жжение с поджиганием атакующих." + "3" "Graded Shiva's — рецепт: Crimson Guard + Shiva's Guard + Ultimate Crown." + "4" "Ultimate Crown — три Ultimate Orb." + "5" "Satanic (кастом) — вампиризм с усилением от силы." + "6" "Шляпа волка — квестовый предмет." + } + "heroes" + { + "1" "[hero:bristleback][abil:bristleback_viscous_nasal_goo_custom]Слизь: AOE 200, без КД (10 маны), броня и замедление в % от стаков. [abil:bristleback_quill_spray_custom]Ёжики: КД 0.1 с, 10 маны, автокаст. [abil:bristleback_warpath]Warpath — врождёнка: урон за стак растёт с уровнем героя." + "2" "[hero:lina][abil:ability_lina_flame_cloak_custom]Пламенная мантия: КД 18/16/14/12 → 18/15/12/9 с; усиление заклинаний и сопр. магии 5 → 5/10/15/20% по уровням." + "3" "[hero:pudge][abil:pudge_dismember]Разорвание: мана 60 → 25; урон от силы 28/34/40% → 50/100/150%." + "4" "[hero:luna][abil:ability_luna_lucent_beam_custom]Луч: КД 9/8/7/6 → 9/7/5/3 с; талант к КД −3 → −2.5 с; урон по мане 50/100/150/200%; Moon Glaives facet 4% → 12/16/20/24%." + "5" "[hero:sven][abil:sven_great_cleave]Рассекающий удар: cleave 30/45/60/75% → 45/60/75/95%; талант +115% → +105%. [abil:sven_gods_strength]Бог силы: талант к бонусу урона +100% → +300%." + } + } + "5" + { + "date" "2026-05-17" + "version" "0.16.0" + "title" "0.16.0" + "general" + { + "1" "Карты: новые 75–83; баланс и правки 38, 71, 73; проклятие — +25% входящего за стак (было 5%)." + "2" "Магазин: в продаже [hero:lina]Lina — 25 000 зомби-осколков." + "3" "Арсенал: боевые % (исходящий/входящий, скорость атаки и передвижения, урон атаки) снова считаются напрямую на герое; % атрибутов — через множитель статов. Снижение входящего урона суммируется по правилам линейной формулы." + } + "cards" + { + "1" "Проклятие (все карты с проклятием): входящий урон за стак 5% → 25%. После сборки [card:80]Фростморна проклятие даёт исходящий урон, а не входящий." + "2" "[card:38]«Кристальный рассвет»: множитель кристаллов 1.5/2/2.5 → 1.25/1.5/1.75." + "3" "[card:71]«Тяжесть долга»: +1% макс. HP за стак проклятия; самоурон 2.5/2/1.5% макс. HP/с — теперь снимает HP напрямую (не убивает, мин. 1 HP)." + "4" "[card:73]«Адмиральский ром»: откладывает 30/40/50% урона после брони и маг. сопр.; отложенное — чистый DoT за 3/3.5/4 с (раньше считалось от «сырого» урона до смягчения)." + "5" "[card:75]«Мановой разряд» (новая): каждые 5/4/3 с −15% макс. маны; через 1.2/1/0.8 с взрыв 90/120/150 + 5/7/11% макс. маны чистым уроном (радиус 175, поиск 1000)." + "6" "[card:76]«Резонанс заклинаний» (новая): после каста бьёт 1/2/3 врагов в 250/300/350 на 20/40/60% текущей маны; лечение = урон." + "7" "[card:77]«Мана-щит» (новая): поглощает до 60/70/80% входящего за 1/0.8/0.67 маны за 1 ед. урона." + "8" "[card:78]«Пульс маны» (новая): каст → стак 10/12/14 с, +0.5/1/1.5% к макс. мане (стаки суммируются)." + "9" "[card:79]«Эхо выбора» (новая, золотая): дублирует следующий выбор из окна (404 не дублируется; макс. 2 копии)." + "10" "[card:80]«Фростморн» (новая, врождённая): в колоду осколки 81–83; после сборки +5/7/9% исходящего за стак проклятия, проклятия больше не увеличивают урон по вам." + "11" "[card:81]Осколок «лезвие» (новый): +15% исходящего, +15% усиления заклинаний; 1 проклятие." + "12" "[card:82]Осколок «рукоять» (новый): +20% макс. HP и маны; 1 проклятие." + "13" "[card:83]Осколок «душа» (новый): +20% размера, +10% физ. и маг. вампиризма; 1 проклятие. Линия 80–83 не дублируется картами 57 и 79." + } + "heroes" + { + "1" "[hero:npc_dota_hero_bloodhunter][abil:ability_bloodrage]КД 16 → 40 с; стаки урона/скорости атаки 4/8/12/16 и 8/12/16/20 → 4/6/8/10. [abil:ability_bloodstained_memory]Бонус HP% 6/8/10/12 → 2/3/4/5, талант +6% → +2%. [abil:ability_sacrifice_revenge]Базовое время атаки 1.2/1.1/1 → 1.35/1.3/1.25; чистый урон за стак без 4-го уровня." + "2" "[hero:keeper_of_the_light][abil:keeper_of_the_light_chakra_magic_custom]На союзника: снижение входящего урона 12/16/20/24% на 4/5/6/7 с; урон способностей +50% от текущей маны цели." + "3" "[hero:lina][abil:ability_lina_scorch_affinity_innate]Новая врождёнка: бонус к урону, сопр. магии и усилению заклинаний за каждого горящего врага в радиусе 900. [abil:ability_lina_dragon_slave_custom][abil:ability_lina_light_strike_array_custom][abil:ability_lina_flame_cloak_custom][abil:ability_lina_laguna_blade_custom]Доп. урон от % текущей маны (50%)." + "4" "[hero:nevermore][abil:nevermore_requiem_custom]КД 90 → 20 с; доля душ при касте 25% → 70%." + "5" "[hero:queenofpain][abil:queenofpain_scream_of_pain_custom][abil:queenofpain_shadow_strike_custom][abil:queenofpain_sonic_wave]Урон от % маны: 50 → 50/100/150/200 по уровням способности." + "6" "[hero:silencer][abil:glaives_of_wisdom]Урон от интеллекта 12/16/20/24% → 100/150/200/250%, талант +16% → +150%. [abil:ability_global_silence]Длительность глобальной тишины 15 → 20 с, талант +5 → +6 с." + "7" "[hero:templar_assassin][abil:templar_assassin_refraction_custom]Заряды 4/6/8/10 → 8/12/16/20, талант +2 → +10. [abil:templar_assassin_psi_blades_custom]Дальность +80 → +50, +4 за уровень → +5; пробивание 80% → 40%, талант +20% → +40%." + } + } + "6" + { + "date" "2026-05-19" + "version" "0.17.0" + "title" "0.17.0" + "general" + { + "1" "Редактор колод: переработан интерфейс; импорт/экспорт строки колоды (копировать код, загрузить, применить, новая колода из кода)." + "2" "У некоторых карт также появилась стоимость слотов в колоде: теперь часть карт занимает больше одного слота из лимита 20; число слотов показывается бейджем снизу на карте в декбилдере и магазине." + "3" "Нейтралы — опыт ↑, золото ↓: свинья/рыба 6→7 XP, золото 16–21→10–15; овца 6→7, 20–25→14–19; курица 11→13, 30–35→24–29; волк 24→29, 42–47→36–41; энт 25→30, 28–38→22–32; скелеты (4 типа) XP 14→17, золота нет." + "4" "Лягушки: мини 40→48 XP, 75–90→69–84 з; magi 72→86, 88–93→82–87; головастик 55→66, 72–77→66–71; лягушонок 72→86, 75–80→69–74. Драконы: чёрный/синий/красный 160→192 XP, 100–105→94–99 з; малый синий 124→149, 127–135→121–129; малый красный 41→49, 120–125→114–119." + "5" "Квесты Denny: цепочка перестроена — старт «Убить 20 овец» → «Найти питомца» → «Тренировка» (20 овец, награда 600 з / 100 опыта / 5 крист.) и параллельно у Kunkka «Найти Ром» (раньше доступен сразу, теперь LOCKED до питомца) → «Убить воров». Oldmen: «Молоко» открывает «Сыровар» (оба раньше были AVAILABLE)." + "6" "Battle Pass: исправлено начисление XP при одновременном автозаборе нескольких квестов (раньше засчитывался только один левел-ап)." + "7" "Крипы: взрыв при смерти — доля урона от макс. HP 15% → 35%; костяная броня — штраф изолированному дальнику ×2 → ×1.5." + } + "cards" + { + "1" "[card:43]«Быстрые руки»: вместо плоской скорости атаки — снижение BAT на 0.1/0.15/0.2 (мин. BAT 0.35)." + "2" "3 слота в колоде: [card:38]«Кристальный рассвет», [card:45]«Энергетик», [card:57]«Зеркало судьбы», [card:67]«Сердце титана», [card:80]«Фростморн»." + "3" "2 слота в колоде: [card:1]«Кровавый путь», [card:2]«Лёгкость пера», [card:21]«Солнечный клинок», [card:47]«Хороший старт», [card:58]«Буйный рост», [card:74]«Снайперский прицел», [card:79]«Эхо выбора»." + } + "items" + { + "1" "[item:item_soul_devourer_staff]Восстановление макс. маны за убийство 10% → 25%; рост стаков благословения за интервал 20% → 10%." + "2" "[item:item_blazing_radiance_custom]Доп. бонус ко всем базовым атрибутам 5% → 3%." + "3" "[item:item_ultimate_crown]Доп. бонус ко всем базовым атрибутам 5% → 3%." + "4" "[item:item_crimson_shivas_custom]Доп. бонус ко всем базовым атрибутам 5% → 3%." + } + "heroes" + { + "1" "[hero:sniper][abil:ability_sniper_critical_focus_custom]Длительность 2.5/3/3.5/4 с → 1.5/2/2.5/3 с; шанс крита 100% → 75%; крит 150% (+40) → 125/145/165/185% (+25)." + "2" "[hero:hoodwink][abil:hoodwink_hunters_boomerang]Ульт: КД 45 → 25 с." + "3" "[hero:silencer][abil:razor_eye_of_the_storm_lua]Глаз бури: эффекты старта на клиенте; снятие брони с целей стабильнее." + } + } + "7" + { + "date" "2026-05-21" + "version" "0.18.0" + "title" "0.18.0" + "general" + { + "1" "Лимит колоды увеличен с 20 до 30 слотов." + "2" "Валюта «пыль»: улучшение и разбор карт, арсенала и приговоров «Смертельного приговора»." + "3" "Снижение входящего урона от нескольких источников суммируется корректно — больше не конфликтуют.\nКарты [card:51][card:77][card:70], способности [abil:ability_crystal_maiden_frostbite_custom][abil:hoodwink_scurry_custom][abil:keeper_of_the_light_chakra_magic_custom][abil:ability_lina_scorch_affinity_innate][abil:war_for_life][abil:ability_metamorphosis][abil:ability_yuki_pohela], ауры [item:item_crimson_shivas_custom][item:item_mjolnir_custom][item:item_demon_slayer]." + "4" "Квесты NPC: командные награды делятся между игроками в матче (до 4); в подсказках показана доля на игрока." + "5" "Зомби не держат цель и не переключаются на героев в неуязвимости или с иммунитетом к атакам." + "6" "[hero:rubick]Telekinesis: нельзя поднимать боссов." + "7" "Магазин: вкладка «Акции» (бандлы за ₽, ежедневные и еженедельные предложения); донат через Robokassa из игры; промокод DUST20K — 20 000 пыли." + "8" "Карты в магазине покупаются и улучшаются за пыль." + } + "cards" + { + "1" "[card:1]«Кровавый путь»: макс. здоровье −25% / −20% / −15% по уровням." + "2" "[card:6]«Счастливая рука»: мин. карт в выборе всегда 4; ур. 2 — +2 бесплатных реролла; ур. 3 — доп. выбор только из легендарных." + "3" "[card:21]«Солнечный клинок»: больше не врождённая." + "4" "[card:22]«Монеты рассвета»: утро 700 золота, −100 за каждую ночь; ур. 2 — +50% при взятии; ур. 3 — +50% перед ночью." + "5" "[card:45]«Энергетик»: 4 слота в колоде (было 3)." + "6" "[card:47]«Хороший старт»: вместо +1 уровня — +100/150/200% к опыту на 3 мин.; можно качать." + "7" "[card:57]«Зеркало судьбы»: больше не врождённое." + "8" "[card:59][card:60][card:61]Дары опыта, золота и кристаллов — только через [card:62]«Карту желания», не в колоду; награды и уровни 59–61 масштабируются." + "9" "[card:62]«Карта желания»: при получении выбор одного из трёх даров." + "10" "[card:67]«Сердце титана»: бонус силы к HP и исходящему урону снижен (0.05/0.1/0.15 HP и +0.1% урона за ед. силы)." + } + "items" + { + "1" "[item:item_crimson_shivas_custom]Снижение входящего урона от ауры — общая формула суммирования." + "2" "[item:item_mjolnir_custom]Снижение входящего урона от ауры — общая формула суммирования." + "3" "[item:item_demon_slayer]Снижение входящего урона от ауры — общая формула суммирования." + } + "heroes" + { + "1" "[hero:bristleback][abil:bristleback_quill_spray_custom]КД 0.1 → 2.5 с." + "2" "[hero:npc_dota_hero_elder_dragon_smaug][abil:ability_fire_punishment]Длительность дебаффа 2.5 → 1.5 с." + } + } + "8" + { + "date" "2026-05-22" + "version" "0.18.1" + "title" "0.18.1" + "general" + { + "1" "Механика «Алчность»: общий бафф для карт — число на иконке = сколько таких карт активно; +1 ко всем атрибутам за каждые 100 нетворса (золото + стоимость предметов в инвентаре)." + "2" "Редактор колод; сетка 30 слотов — 6 рядов по 5" + "3" "Бонусы от атрибутов заданы явно (раньше — как в ванильной Dota 2). За 1 ед.: сила — +4 к урону атаки; ловкость — +0,05 брони, +0,5 к скорости атаки, +2 к урону атаки; интеллект — +4 маны, +3 к урону атаки; ко всем атрибутам — +1,25 к урону атаки за каждую единицу." + } + "cards" + { + "1" "Пул алчности: [card:9][card:20][card:22][card:23][card:24][card:39][card:40][card:51][card:60] — скрытые одноразовые (39, 40, 60) дают +1 стак при получении." + "2" "[card:9]«Проклятие алчного мидаса»: переработка — +1 стак алчности; при смерти теряется всё золото и один случайный предмет из основного инвентаря (раньше: только выкуп без золота и бонус золота за убийства)." + "3" "[card:2]«Лёгкость пера»: ур. 1 — только скорость передвижения; ур. 2 — лимит скорости; ур. 3 — проход сквозь юнитов и снижение BAT. Убраны игнор лимитов скорости передвижения и атаки на всех уровнях." + "4" "[card:43]«Быстрые руки»: ур. 2 — дальнобойным +300 к скорости снарядов; ур. 3 — время анимации атаки −33%." + } + } + "9" + { + "date" "2026-05-22" + "version" "0.18.2" + "title" "0.18.2" + "general" + { + "1" "Волновые зомби больше не дают опыта." + "2" "Ночь 1: Зонбу-Ходун 15→2; Зонбу-падальщик 18→4 (1-я волна) / 18→2 (2–3); Зонбу-токсик — / 18→4 (2–3); в конце 1-й волны 3× Зонбу-Зверь 25→2." + "3" "Ночь 2: Зонбу-токсик 15→4 / 18→4; Зонбу-Зверь 18→4 / 20→4; Скелет 15→3 / 25→3; Скелет-падальщик 18→3 / 20→2; Скелет-лучник 25→3 / 25→2; Скелет-мечник — / 18→3." + "4" "Ночь 3: Гаргулья 18→4; Зонбу-токсик II 18→6 / 20→4; Призрачный стрелок 15→3 / 15→5; Зонбу-Зверь 18→4; Призрак 15 (без изменений, макс. 6 за волну вместо 25)." + "5" "Ночь 4: Упырь 15→7; Зонбу-токсик II 18→7 / 18→6; Скелет-ассасин — / 15→5; Скелет-воин — / 18→4." + "6" "Ночь 5: Упырь 18→5 / 18→6; Призрак 18→6 / 18→7; Гаргулья 22→5 / 22→8; Скелет-ассасин 20→5 / 20→7; Зонбу-токсик II 20→5; Скелет-воин — / 20→5; Призрачный стрелок — / 18→6; Скелет-лучник — / 25→5 / 25→6." + "7" "Ночь 6: Зонбу-токсик II 22→6 / 22→5; Призрак 20→7 / 20→6; Упырь 20→7 / 20→5; Скелет-воин 22→8 / 22→7; Гаргулья 24→8 / 24→6; Скелет-ассасин — / 22→7 / 22→8; Скелет-лучник — / 26→6 / 26→5; Призрачный стрелок — / 20→8." + "8" "Ночь 7 (финал): Зонбу-Ходун 15→6; Зонбу-падальщик 18→7; Зонбу-токсик 18→8 / 18→9; Зонбу-Зверь — / 20→9; Скелет 25→9; Скелет-падальщик — / 20→9; Скелет-мечник — / 18→9; Скелет-лучник — / 25→9; Гаргулья 18→9; Зонбу-токсик II 20→9 / 18→9; Призрак 15→9; Призрачный стрелок 15→9; Упырь 15→9; Скелет-ассасин 15→9; Скелет-воин 18→9." + } + "cards" + { + "1" "[card:84]«Маленькое буйство маны» (новая): разово фиксирует усиление заклинаний = 15/20/25% от макс. маны на момент взятия (×0.1 в формуле); не в колоду — только через [card:33]." + "2" "[card:33]«Буйство маны»: ур. 3 — выбор «Маленького буйства маны» (как лезвия у [card:41])." + "3" "[card:25]«Ядро маны»: ур. 3 — +25% к максимальной мане." + "4" "[card:41]«Резерв лезвий»: теперь можно качать; ур. 2 — сразу выбор «Лезвие маны» или «Лезвие крови»; ур. 3 — ещё по одному лезвию каждого типа в пул (всего 4 карты)." + "5" "[card:30]«Проклятие роста», [card:31]«Проклятие ловкости», [card:54]«Проклятие интеллекта»: бонус атрибута за уровень 1/2/3 → 0.5/1/1.5; штраф к другим атрибутам смягчён." + "6" "[card:60]Дар золота (желание): 500/800/1200 → 250/500/750. [card:61]Дар кристаллов: 25/40/60 → 12/18/24." + "7" "[card:79]«Эхо выбора»: 3 слота в колоде (было 2)." + } + } + "10" + { + "date" "2026-05-24" + "version" "0.19.0" + "title" "0.19.0" + "general" + { + "1" "У всех сняты контракты «Смертельный приговор» и арсенал (включая лоты на торговой площадке). Взамен начислена пыль по правилам разбора. Всего выдано ~18 млн пыли, 990 игрокам." + "2" "Магазин — вкладка «Аркада»: обычный пак (3 карты за 25 000 зомби-осколков) и премиум-пак (3 карты за 80 донат-осколков). Дубликаты → пыль." + "3" "Акции для новичков: бандл «Зайка» 500₽ (7 дней после регистрации) — BP Premium, звук «Зайка», 20 000 пыли, 300 донат-осколков, 250 000 зомби-осколков. Набор 8 обычных + 2 премиум паков — 199₽ (21 день, можно покупать повторно)." + "4" "Месячная подписка 299₽: +30 дней за покупку (срок суммируется). Ежедневно при первом входе: 30 донат-осколков и 5 000 зомби-осколков. Админка подписок на сервере." + "5" "Рейтинг после матча умножается на множитель сложности. Пол рейтинга по пику MMR (шаг 1000, колонка rating_peak) — ниже пола рейтинг не падает." + "6" "Скины Battle Pass: эмблемы Diretide (3 варианта). Карта invasion обновлена." + "7" "Квест Кункки «Клад под крестом»: лопата, кресты на карте, копание за золото; награда — Позорная труба (+450 дальности, иммунитет к костяной броне крипов)." + "8" "Крипы: токсин и взрыв при смерти считают урон от атаки крипа (не от макс. HP); «полная жестокость» без лишнего скейла сложности. Костяная броня: штраф изолированному дальнику ×1.5 (было ×5); отражение только если рядом герой; волновой дебафф −0.5 брони/маг. сопр. за стак (было −3)." + "9" "Босс Nevermore: ниже 50% HP — −80% входящего, пока не скастует реквием 3 раза. [hero:axe] Berserker's Call не таунтит босса." + "10" "Выбор карт: веса пула и даунгрейд редкости переработаны (копии «тратятся» между слотами). Арсенал: в сводке — эффективный % снижения входящего (макс. ~92%), подпись «сумма по предметам» для линейной суммы." + "11" "Добавлены новые герои: [hero:mirana], [hero:vengefulspirit], [hero:spectre] (донат), [hero:ogre_magi] (бесплатно)." + } + "items" + { + "1" "[item:item_shovel]Волшебная лопата (квест): канал 1 с, до 5 копаний на крестах, 145–325 золота, +675 HP." + "2" "[item:item_shameful_pipe]Позорная труба (награда квеста): +450 дальности атаки дальникам; иммунитет к штрафу и отражению костяной брони." + } + "cards" + { + "1" "[card:6]«Счастливая рука»: ур. 3 — доп. выбор из легендарных и мифических карт пула (раньше только легендарные)." + "2" "[card:43]«Быстрые руки»: BAT через SetBaseAttackTime, копии стакаются; синергия с [card:2] на ур. 3." + "3" "[card:2]«Лёгкость пера»: снижение BAT на ур. 3 через общую формулу BAT (если нет [card:43])." + } + "heroes" + { + "1" "[hero:mirana]Новый герой в магазине (донат). Лунная охотница: урон заклинаний растёт от максимальной маны и с уровнем героя. [abil:ability_mirana_starstorm_custom]Звёздный дождь — урон по области, второй удар с задержкой. [abil:ability_mirana_sacred_arrow_custom]Священная стрела — чем дальше полетела, тем дольше оглушение; шард вызывает звёздный дождь в точке попадания. [abil:ability_mirana_leap_custom]Прыжок за долю маны, при приземлении бьёт врагов вокруг (урон от маны). [abil:ability_mirana_moonlight_shadow_custom]Ульта — невидимость и усиление урона союзникам на всей карте; сценарий делит с союзниками получаемый урон. Рядом с [hero:luna] в радиусе 1200 обе получают −25% входящего урона." + "2" "[hero:vengefulspirit]Новый герой в магазине (донат). Поддержка и инициация: усиливает союзников, ломает врагов. [abil:ability_vengefulspirit_command_aura_custom]Аура — бонус к урону атаки и вампиризму (физ. и маг.). [abil:vengefulspirit_magic_missile_custom]Magic Missile — урон, оглушение, сжигание маны; после попадания отскакивает к ближайшему врагу. [abil:vengefulspirit_wave_of_terror_custom]Волна ужаса — урон, снятие брони (плоско и в % от базовой), ослабление урона атак, обзор на пути. [abil:vengefulspirit_nether_swap_custom]Обмен позициями с героем или крипом; у цели не опускается ниже порога HP. Шард [abil:vengefulspirit_revenge] — краткий режим с усиленным критом. Врождёнка: метка на врагов, лечение и восстановление маны при убийствах." + "3" "[hero:spectre]Новый герой в магазине (донат). Керри-тень для длительных боёв и глобального давления. [abil:ability_spectre_spectral_dagger_custom]Спектральный кинжал — след на земле, замедление, мобильность по тропе. [abil:ability_spectre_spectral_echo_custom]Эхо — шанс призвать тень, копирующую атаки. [abil:ability_spectre_dispersion_custom]Dispersion — отражает часть входящего урона; шард включает усиленный режим за ману. [abil:ability_spectre_desolate_custom]Врождёнка Desolate — чистый урон по целям без союзников рядом (% от их макс. HP). [abil:ability_spectre_haunt_custom]Haunt — иллюзии на всех вражеских героев; [abil:ability_spectre_reality_custom]Reality телепортирует к выбранной иллюзии и наносит удар." + "4" "[hero:ogre_magi]Новый герой — доступен всем в пике. Толстый саппорт с мультикастом: одно заклинание может сработать несколько раз подряд. [abil:ability_ogre_magi_fireblast_custom]Fireblast — стан и урон по области, плюс доля от макс. HP цели. [abil:ability_ogre_magi_ignite_custom]Ignite — поджог с расползанием, замедление и уроном от макс. HP в секунду. [abil:ability_ogre_magi_bloodlust_custom]Bloodlust — ускорение атаки и передвижения союзнику (и себе). [abil:ability_ogre_magi_multicast_custom]Ульта Multicast — шансы на двойное, тройное и четверное применение способностей. Сценарий — [abil:ability_ogre_magi_unrefined_fireblast_custom]мощный Fireblast за долю маны." + "5" "[hero:pudge][abil:ability_pudge_meat_hook_custom]+50/100/150/200% урона от макс. HP цели. [abil:ability_pudge_rot_custom]+0.6/0.9/1.2/1.5% макс. HP/с к DoT. [abil:ability_pudge_dismember_custom]+0.8/1.2/1.6% макс. HP/с к урону от силы." + "6" "[hero:sniper][abil:ability_sniper_shrapnel_custom]урон зоны — 25% от урона атаки каждую 1 с (10 с), вместо фикс. 12–30 маг. урона; +25% входящего по врагам в зоне." + "7" "[hero:rubick][abil:ability_rubick_telekinesis_custom]×2 дистанция сдвига цели до разрыва канала." + "8" "[hero:luna][abil:ability_luna_lunar_blessing_custom]1 ур. врождёнки; +2.5 урона за уровень героя; шард +0.45% усиления заклинаний/ур.; синергия с Mirana." + "9" "[hero:legion_commander][abil:ability_legion_commander_duel_custom]случайный стат за убийство +1 (+4 со сценарием); кап стаков урона 40 000 (было 400)." + "10" "[hero:nagash]Aghanim's Shard: 3 с неуязвимости." + "11" "[hero:yuki_onna][abil:ability_yuki_challenge]бонус атрибута за испытание до +12 (было +16); бафф фиксирует плоский бонус при прохождении." + "12" "[hero:templar_assassin]исправлено зависание ловушки Refraction Trap после срабатывания." + } + } + "11" + { + "date" "2026-05-26" + "version" "0.19.1" + "title" "0.19.1" + "general" + { + "1" "Алчность: +1 ко всем атрибутам за каждые 250 нетворса (было 100)." + "2" "Арсенал: снижены верхние границы и рост при улучшении для статов «ко всем атрибутам», одиночных атрибутов и их %%." + } + "cards" + { + "1" "[card:85]«Рыбаловство» (новая): при взятии — доп. выбор карт (1/2/3 по уровню) и шлак в пул; [card:86]«Шлак» — пустая карта без эффекта, только из пула." + "2" "[card:51]«Midas Chestplate»: переработка — золото за полученный урон (% от урона), входящий урон +база и +0.1% за каждые 100 полученного урона (вместо поглощения за золото)." + "3" "[card:58]«Буйный рост»: врождённая; 3/4/5 доп. выборов в начале игры по 3/4/5 карт; на рассвете стандартный выбор карт не показывается." + "4" "[card:57]«Зеркало судьбы»: 5 слотов в колоде (было 3)." + "5" "[card:74]: 3 слота в колоде (было 2)." + "6" "[card:69]«Проклятая сделка»: сила других карт 75/50/25% → 50/35/20%." + "7" "[card:68]«Три долга»: с [card:69] в колоде бонусный выбор — любые 3 карты из пула, не только проклятые; шлак не попадает в варианты." + "8" "[card:23]«Золотой дождь»: доход %/мин 2/2.5/3.5 → 2/3.5/5; тик раз в 60 с на всех уровнях." + "9" "[card:36]«Кристальный рост»: бонус урона за шаг и потолок усилены (макс. бонус 40/55/70% → 80/160/320%)." + } + "items" + { + "1" "[new][item:item_skadi_custom]Eye of Skadi (кастом) — рецепт: Ultimate Crown + Orb of Corrosion; холодная атака, +45 ко всем атрибутам и +5% к базовым атрибутам." + "2" "[new][item:item_shovel]Волшебная лопата — квестовая: копать на крестах (до 5 раз), золото и +675 HP." + "3" "[new][item:item_shameful_pipe]Позорная труба — награда квеста: +450 дальности дальникам, иммунитет к костяной броне." + "4" "[item:item_desolator_custom] / [item:item_desolator_2_custom]: макс. бонус урона 30→90 и 45→125." + } + "heroes" + { + "1" "[hero:rubick][abil:ability_rubick_telekinesis_custom]Нельзя поднимать npc_homer и боссов; союзникам без Scepter — конечное время в воздухе (раньше у союзников кроме Гомера было бесконечно)." + "2" "[hero:vengefulspirit][abil:vengefulspirit_nether_swap_custom]С союзником не копируются баффы телекинеза Рубика; Гомера нельзя лечить свапом до полного HP." + "3" "[hero:npc_dota_hero_nagash]Потеря душ при смерти: Aghanim's Shard снижает lost_souls_pct на 30." + } + } + "12" + { + "date" "2026-05-28" + "version" "0.19.2" + "title" "0.19.2" + "general" + { + "1" "Карточная система: крупная переработка ядра выбора/обработки карт для стабильности и производительности." + "2" "Оптимизации и техправки игровых систем: трекинг урона, источники модификаторов, HUD босса, служебные менеджеры и инициализация." + "3" "Локализация и каталоги: обновлены тексты/справочники карт (RU/EN/CN) и манифесты UI-ресурсов." + "4" "Арсенал: процентные статы к атрибутам (% к силе/ловкости/интеллекту и % ко всем атрибутам) применяются только к базовым характеристикам." + } + "cards" + { + "1" "[card:87] «Король шлака»: за каждую карту шлака [card:86], оставшуюся в пуле, даёт +2.5% к базовой силе, ловкости и интеллекту (бонус пересчитывается динамически)." + "2" "[card:88] «Прикормка»: бесплатные рероллы теперь начисляются за каждый полученный шлак (умножаются на количество шлака за событие)." + "3" "[card:23] добавлен анти-абуз дивидендов: выплата срабатывает только если за тик набрано минимум 25% от суммы дивиденда." + "4" "[card:51] ослаблен потолок утреннего золота: 400/600/800 → 250/400/550 (и масштабируется от числа активных копий карты)." + "5" "[card:67] ребаланс: HP от силы 0.05/0.1/0.15 → 0.1; % исходящего за силу 0.1 → 0.05; шанс прока 60/75/90 → 40/45/50; КД прока 3/2.5/2 → 3; урон от макс. HP 100/200/300 → 60/80/100." + "6" "[card:69] порог активации по HP 60% → 30%; цена здоровья унифицирована в 5%; бонус урона за стак проклятия 10/14/18 → 10/20/30." + "7" "[card:47] ослабление XP-бонуса: 100/150/200% → 35/50/65%." + "8" "Карты [card:12], [card:13], [card:14], [card:19], [card:26], [card:35], [card:48], [card:55], [card:56], [card:78], [card:80], а также модификаторы проклятия/алчности переведены на единый source ability при выдаче модификаторов (стабильнее стаки, события и источники урона)." + } + } + } + "en" + { + "1" + { + "date" "2026-05-14" + "version" "0.12.0" + "title" "0.12.0" + "general" + { + "1" "Creep abilities now also scale with the difficulty multiplier." + "2" "Luck: the final proc chance cannot exceed twice the ability's base chance. Exact numbers are in in-game tooltips." + } + "heroes" + { + "1" "[hero:axe][abil:axe_battle_hunger_custom]Damage/sec on target 25/50/75/100 → 16/32/56/72, talent +8 → +28. [abil:axe_counter_helix_custom]Damage 110/140/170/200 → 55/90/125/160, talent +200 → +160, proc chance 20/25/30/35% → 14/18/22/26%." + "2" "[hero:npc_dota_hero_bloodhunter][abil:ability_bloodrage]Bonus damage per stack 40/60/80/100 → 20/40/60/80." + "3" "[hero:bristleback][abil:bristleback_quill_spray_custom]Base quill damage 25/50/75/100 → 12/16/20/24, talent +150 → +16; stack damage 30/35/40/45 → 30/35/40, talent +40. [abil:bristleback_bristleback_custom]Back damage reduction 16/24/32/40% → 14/18/22/26%. [abil:bristleback_warpath]Damage per stack 20/27/34 → 20/30/40, talent +26 → +25." + "4" "[hero:drow_ranger][abil:ability_drow_ranger_frost_arrows_custom]Mark damage % 25/45/65/85 → 12/24/36/48, talent +30% → +12%. [abil:ability_drow_ranger_gust_custom]Damage 280/320/460/540 → 180/240/300/360. [abil:ability_drow_ranger_multishot_custom]Arrow damage % 90/120/140/160 → 45/75/105/135, talent +160% → +45%. [abil:ability_drow_ranger_marksmanship_custom]Agility % 55/85/115 → 25/45/65, bonus damage 320/480/640 → 120/200/280, ally disable radius 400 → 250. [abil:drow_ranger_trueshot]Base agility bonus 15/20/25/30 → 8/12/16/20." + "5" "[hero:juggernaut][abil:ability_juggernaut_omnislash_custom]Duration 3/3.5/4s → 1.5/2/2.5s." + "6" "[hero:legion_commander][abil:ability_legion_commander_duel_custom]Charges in AbilityCharges inside AbilityValues (1 +2 from Aghanim's Scepter), charge restore 75/70/65. [abil:ability_legion_commander_overwhelming_odds_custom]Aghanim's Shard: auto-cast during Duel every 2s (shard_overwhelming_interval)." + "7" "[hero:phantom_assassin][abil:ability_phantom_assassin_coup_de_grace_custom]Crit chance 15/25/35/45% → 15/20/30%, crit mult 250/500/750% → 250/400/550%. [abil:ability_phantom_assassin_phantom_bash_custom]Stun 0.75 → 0.15s, per-level internal CD step 0.25 → 0.10s." + "8" "[hero:silencer][abil:glaives_of_wisdom]INT damage % 20/32/44/68 → 12/16/20/24, talent +32% → +16%." + "9" "[hero:skywrath_mage][abil:skywrath_mage_arcane_bolt_custom]Charge restore time 5.0/4.0/3.0/2.0 → 2.0/1.5/1.0/0.5s." + "10" "[hero:npc_dota_hero_elder_dragon_smaug][abil:ability_fire_punishment]Magic resist reduction on targets −1/−2/−3/−4% → −5/−7/−9/−11%, talent −2% → −4%; no longer applies a new modifier, extends the current one." + } + "cards" + { + "1" "[card:5]Card 5 (Card madness): the stat bonus counts only cards picked before it; % per card per level: 0.5/1/1.5 → 5/7/9." + } + } + "2" + { + "date" "2026-05-15" + "version" "0.13.0" + "title" "0.13.0" + "general" + { + "1" "Pigs per zone: reduced from 5 to 3." + "2" "Pig respawn interval: reduced from 12 to 7 seconds." + "3" "Sheep per zone: reduced from 5 to 3." + "4" "Sheep respawn interval: reduced from 14 to 7 seconds." + "5" "Chickens per zone: reduced from 12 to 8." + "6" "Wolves (ents in zone): reduced from 3 to 2; wolf zone interval: from 10 to 9 seconds." + "7" "Skeleton zones (both): respawn interval increased from 3 to 5 seconds." + "8" "Fish/bomb zone: bombs reduced from 8 to 6." + "9" "Frogs: magi/tadpole from 4 to 3, froglet/mini from 4 to 2; zone interval from 8 to 6 seconds." + "10" "Snakes and spiders: each type per zone reduced from 4 to 3." + "11" "Death Sentence: contract complications on enemy spawn are no longer guaranteed — 25% chance per complication line, rolled independently. Multiple different effects can apply at once (explosion, toxin, virus, phasing, etc.)." + "12" "Toxin (death pool): HP-based damage fraction reduced from 100% max HP to 15%." + "13" "Zombie death explosion: HP-based damage fraction reduced from 100% max HP to 15%." + "14" "Arsenal: max item instances in inventory reduced from 507 to 207 (game, API, and UI grid)." + "15" "No extra win shards from NPC quests after a match." + } + } + "3" + { + "date" "2024-05-16" + "version" "0.14.0" + "title" "0.14.0" + "general" + { + "1" "No extra win shards from NPC quests after a match." + "2" "Death Sentence: contract durability 1–5; breaks at zero." + "3" "New Battle Pass." + "4" "Arsenal: tooltips and grants." + "5" "Chat wheel: new sounds and effects." + "6" "Match end: DS contract max durability shows correctly." + "7" "Skins: particle effects fixed (BP, gems, etc.)." + "8" "DS contracts: −1 durability on a match loss." + "9" "Store: updated UI." + "10" "Profile: purchases — newest first." + "11" "Lobby: Death Sentence contracts load and display." + "12" "Pigs and sheep per spawn zone: 3 → 5." + } + "heroes" + { + "1" "[hero:sand_king]New hero Sand King." + "2" "[hero:sniper][abil:ability_sniper_critical_focus_custom]Cooldown 20/18/16/14s → 30/26/22/18s; buff duration 6s → 2.5/3/3.5/4s per level; duration talent +3s → +1.5s." + } + } + "4" + { + "date" "2026-05-16" + "version" "0.15.0" + "title" "0.15.0" + "general" + { + "1" "Match loss on disconnect is applied faster: ~90s game time after drop, server timeout without heartbeat — 3 min." + "2" "Lobby (game setup): stale in-progress matches are closed as losses; Death Sentence contracts −1 durability." + "3" "Match heartbeat: retry after 5s if the API did not accept the ping." + "4" "New and reworked shop items (see Items section)." + } + "items" + { + "1" "Blazing Radiance — recipe: Fire Cape + Radiance + Ultimate Crown." + "2" "Radiance (custom) — burn aura with attacker ignite." + "3" "Graded Shiva's — recipe: Crimson Guard + Shiva's Guard + Ultimate Crown." + "4" "Ultimate Crown — three Ultimate Orbs." + "5" "Satanic (custom) — lifesteal scaling with Strength." + "6" "Wolf Hat — quest item." + } + "heroes" + { + "1" "[hero:bristleback][abil:bristleback_viscous_nasal_goo_custom]Goo: 200 AoE, no cooldown (10 mana), armor and slow as % per stack. [abil:bristleback_quill_spray_custom]Quill Spray: 0.1s CD, 10 mana, autocast. [abil:bristleback_warpath]Warpath innate: damage per stack scales with hero level." + "2" "[hero:lina][abil:ability_lina_flame_cloak_custom]Flame Cloak: CD 18/16/14/12 → 18/15/12/9s; spell amp and magic resist 5 → 5/10/15/20% per level." + "3" "[hero:pudge][abil:pudge_dismember]Dismember: mana 60 → 25; STR damage 28/34/40% → 50/100/150%." + "4" "[hero:luna][abil:ability_luna_lucent_beam_custom]Lucent Beam: CD 9/8/7/6 → 9/7/5/3s; CD talent −3 → −2.5s; mana damage 50/100/150/200%; Moon Glaives facet 4% → 12/16/20/24%." + "5" "[hero:sven][abil:sven_great_cleave]Great Cleave: 30/45/60/75% → 45/60/75/95%; talent +115% → +105%. [abil:sven_gods_strength]God's Strength: damage talent +100% → +300%." + } + } + "5" + { + "date" "2026-05-17" + "version" "0.16.0" + "title" "0.16.0" + "general" + { + "1" "Cards: new 75–83; balance and fixes for 38, 71, 73; curse stacks now +25% incoming damage each (was 5%)." + "2" "Store: [hero:lina]Lina available for 25,000 zombie shards." + "3" "Arsenal: combat % (outgoing/incoming, attack speed, move speed, base damage) apply directly on the hero again; attribute % still use the stats multiplier. Incoming damage reduction stacks with the linear combine formula." + "4" "Mana-based damage: several heroes and Keeper of the Light now use KV values for % of current/max mana (see Heroes)." + } + "cards" + { + "1" "Curse (all curse cards): incoming damage per stack 5% → 25%. After completing [card:80]Frostmourne, curse stacks grant outgoing damage instead of incoming." + "2" "[card:38]Crystal Dawn: crystal multiplier 1.5/2/2.5 → 1.25/1.5/1.75." + "3" "[card:71]Burden of Debt: +1% max HP per curse stack; self-damage 2.5/2/1.5% max HP/s — now drains HP directly (non-lethal, min 1 HP)." + "4" "[card:73]Admiral's Rum: defers 30/40/50% of post-mitigation damage; deferred part is pure DoT over 3/3.5/4s (was calculated from pre-mitigation damage)." + "5" "[card:75]Mana Blast (new): every 5/4/3s spends 15% max mana; after 1.2/1/0.8s deals 90/120/150 + 5/7/11% max mana pure (radius 175, search 1000)." + "6" "[card:76]Spell Resonance (new): after a cast, hits 1/2/3 enemies in 250/300/350 for 20/40/60% current mana; heal = damage." + "7" "[card:77]Mana Shield (new): absorbs up to 60/70/80% incoming for 1/0.8/0.67 mana per damage point." + "8" "[card:78]Mana Pulse (new): cast → stack 10/12/14s, +0.5/1/1.5% max mana (stacks stack)." + "9" "[card:79]Echo of Choice (new, gold): duplicates next draft pick (not 404; max 2 copies)." + "10" "[card:80]Frostmourne (new, innate): shuffles shards 81–83; when complete +5/7/9% outgoing per curse stack." + "11" "[card:81]Shard — Blade (new): +15% outgoing, +15% spell amp; 1 curse." + "12" "[card:82]Shard — Hilt (new): +20% max HP and mana; 1 curse." + "13" "[card:83]Shard — Soul (new): +20% size, +10% physical and magical lifesteal; 1 curse. Line 80–83 not duplicated by cards 57 or 79." + } + "heroes" + { + "1" "[hero:npc_dota_hero_bloodhunter][abil:ability_bloodrage]CD 16 → 40s; stack damage/attack speed 4/8/12/16 and 8/12/16/20 → 4/6/8/10. [abil:ability_bloodstained_memory]HP% bonus 6/8/10/12 → 2/3/4/5, talent +6% → +2%. [abil:ability_sacrifice_revenge]Base attack time 1.2/1.1/1 → 1.35/1.3/1.25; pure damage per stack no longer has a 4th level." + "2" "[hero:keeper_of_the_light][abil:keeper_of_the_light_chakra_magic_custom]On ally: 12/16/20/24% incoming damage reduction for 4/5/6/7s; ability damage +50% of target's current mana." + "3" "[hero:lina][abil:ability_lina_scorch_affinity_innate]New innate: damage, magic resist, and spell amp per burning enemy in 900 radius. [abil:ability_lina_dragon_slave_custom][abil:ability_lina_light_strike_array_custom][abil:ability_lina_flame_cloak_custom][abil:ability_lina_laguna_blade_custom]Bonus damage from 50% of current mana." + "4" "[hero:nevermore][abil:nevermore_requiem_custom]CD 90 → 20s; souls released on cast 25% → 70%." + "5" "[hero:queenofpain][abil:queenofpain_scream_of_pain_custom][abil:queenofpain_shadow_strike_custom][abil:queenofpain_sonic_wave]Mana damage %: flat 50 → 50/100/150/200 per ability level." + "6" "[hero:silencer][abil:glaives_of_wisdom]INT damage % 12/16/20/24 → 100/150/200/250, talent +16% → +150%. [abil:ability_global_silence]Global Silence duration 15 → 20s, talent +5 → +6s." + "7" "[hero:templar_assassin][abil:templar_assassin_refraction_custom]Instances 4/6/8/10 → 8/12/16/20, talent +2 → +10. [abil:templar_assassin_psi_blades_custom]Range bonus +80 → +50, +4 per level → +5; spill 80% → 40%, talent +20% → +40%." + } + } + "6" + { + "date" "2026-05-19" + "version" "0.17.0" + "title" "0.17.0" + "general" + { + "1" "Deck builder: UI overhaul; deck string import/export (copy code, load, apply, new deck from code)." + "2" "Some cards now have a deck slot cost: one copy can take more than one slot from the 20-slot limit; the count is shown on a badge at the bottom of the card in the deck builder and store." + "3" "Neutrals — more XP, less gold: pig/fish 6→7 XP, gold 16–21→10–15; sheep 6→7, 20–25→14–19; chicken 11→13, 30–35→24–29; wolf 24→29, 42–47→36–41; ent 25→30, 28–38→22–32; skeletons (4 types) XP 14→17, no gold." + "4" "Frogs: mini 40→48 XP, 75–90→69–84 g; magi 72→86, 88–93→82–87; tadpole 55→66, 72–77→66–71; froglet 72→86, 75–80→69–74. Dragons: black/blue/red 160→192 XP, 100–105→94–99 g; small blue 124→149, 127–135→121–129; small red 41→49, 120–125→114–119." + "5" "Denny quests rebuilt: start Kill 20 sheep → Find a pet → Training (20 sheep, 600 g / 100 XP / 5 crystals) and in parallel Kunkka Find Rom (was AVAILABLE at start, now LOCKED until pet) → Kill thieves. Oldmen: Milk unlocks Cheesemaker (both used to be AVAILABLE)." + "6" "Battle Pass: fixed XP grant when multiple quests are claimed at once (only one level-up was applied before)." + "7" "Creeps: death explosion HP fraction 15% → 35%; bone armor isolated ranged penalty ×2 → ×1.5." + } + "cards" + { + "1" "[card:43]Quick Hands: flat attack speed → BAT reduction by 0.1/0.15/0.2 (min BAT 0.35)." + "2" "3 deck slots: [card:38]«Crystal Dawn», [card:45]«Energy Drink», [card:57]«Mirror of Fate», [card:67]«Titan's Heart», [card:80]«Frostmourne»." + "3" "2 deck slots: [card:1]«Bloodway», [card:2]«Light pen», [card:21]«Solar Blade», [card:47]«Good Start», [card:58]«Rampant Growth», [card:74]«Sniper Scope», [card:79]«Echo Pick»." + } + "items" + { + "1" "[item:item_soul_devourer_staff]Max mana restored on kill 10% → 25%; blessing stack growth per interval 20% → 10%." + "2" "[item:item_blazing_radiance_custom]Additional all base attributes bonus 5% → 3%." + "3" "[item:item_ultimate_crown]Additional all base attributes bonus 5% → 3%." + "4" "[item:item_crimson_shivas_custom]Additional all base attributes bonus 5% → 3%." + } + "heroes" + { + "1" "[hero:sniper][abil:ability_sniper_critical_focus_custom]Duration 2.5/3/3.5/4s → 1.5/2/2.5/3s; crit chance 100% → 75%; crit 150% (+40) → 125/145/165/185% (+25)." + "2" "[hero:hoodwink][abil:hoodwink_hunters_boomerang]Ultimate CD 45 → 25s." + "3" "[hero:silencer][abil:razor_eye_of_the_storm_lua]Eye of the Storm: start FX on client; armor reduction on targets more reliable." + } + } + "7" + { + "date" "2026-05-21" + "version" "0.18.0" + "title" "0.18.0" + "general" + { + "1" "Deck slot limit increased from 20 to 30." + "2" "Dust currency: card upgrades and disassembly, arsenal upgrades/disassembly, and Death Sentence contract dismantling." + "3" "Incoming damage reduction from multiple sources stacks correctly — no longer conflicts.\nCards [card:51][card:77][card:70], abilities [abil:ability_crystal_maiden_frostbite_custom][abil:hoodwink_scurry_custom][abil:keeper_of_the_light_chakra_magic_custom][abil:ability_lina_scorch_affinity_innate][abil:war_for_life][abil:ability_metamorphosis][abil:ability_yuki_pohela], auras [item:item_crimson_shivas_custom][item:item_mjolnir_custom][item:item_demon_slayer]." + "4" "NPC quests: team rewards are split among players in the match (up to 4); tooltips show each player's share." + "5" "Zombies no longer keep or switch to heroes that are invulnerable or attack-immune." + "6" "[hero:rubick]Telekinesis: cannot lift bosses." + "7" "Store: Promotions tab (rub bundles, daily and weekly deals); Robokassa donation from in-game; promo code DUST20K — 20,000 dust." + "8" "Store cards are purchased and upgraded with dust." + } + "cards" + { + "1" "[card:1]Bloodway: max HP −25% / −20% / −15% per level." + "2" "[card:6]Lucky Hand: minimum draft size always 4; level 2 — +2 free rerolls; level 3 — bonus pick is legendary-only." + "3" "[card:21]Solar Blade: no longer innate." + "4" "[card:22]Dawn Coins: 700 gold at dawn, −100 per survived night; level 2 — +50% on pickup; level 3 — +50% before night." + "5" "[card:45]Energy Drink: 4 deck slots (was 3)." + "6" "[card:47]Good Start: instead of +1 level — +100/150/200% XP for 3 min.; upgradeable." + "7" "[card:57]Mirror of Fate: no longer innate." + "8" "[card:59][card:60][card:61]XP, gold, and crystal gifts — only via [card:62]Wish Card, not deckable; rewards and levels 59–61 scale." + "9" "[card:62]Wish Card: on pickup, choose one of three gifts." + "10" "[card:67]Titan's Heart: Strength bonuses to HP and outgoing damage reduced (0.05/0.1/0.15 HP and +0.1% damage per STR)." + } + "items" + { + "1" "[item:item_crimson_shivas_custom]Aura incoming damage reduction uses the shared stacking formula." + "2" "[item:item_mjolnir_custom]Aura incoming damage reduction uses the shared stacking formula." + "3" "[item:item_demon_slayer]Aura incoming damage reduction uses the shared stacking formula." + } + "heroes" + { + "1" "[hero:bristleback][abil:bristleback_quill_spray_custom]Cooldown 0.1 → 2.5s." + "2" "[hero:npc_dota_hero_elder_dragon_smaug][abil:ability_fire_punishment]Debuff duration 2.5 → 1.5s." + } + } + "8" + { + "date" "2026-05-22" + "version" "0.18.1" + "title" "0.18.1" + "general" + { + "1" "Greed mechanic: shared buff for gold cards — icon stack count = active gold cards; +1 to all attributes per 100 net worth (gold + item costs in inventory)." + "2" "Deck builder: fixed Panorama script caching (deck_builder.js no longer included twice); 30-slot grid — 6 rows of 5, fixed cell width; window open/close fixed." + "3" "Attribute bonuses are set explicitly (was vanilla Dota 2 defaults). Per point: Strength — +4 attack damage; Agility — +0.05 armor, +0.5 attack speed, +2 attack damage; Intelligence — +4 mana, +3 attack damage; All Stats — +1.25 attack damage per point." + } + "cards" + { + "1" "Greed pool: [card:9][card:20][card:22][card:23][card:24][card:39][card:40][card:51][card:60] — one-time hidden cards (39, 40, 60) grant +1 stack on pickup." + "2" "[card:9]Curse of Greedy Midas: rework — +1 Greed stack; on death lose all gold and one random main-inventory item (was: buyback-only respawn with no gold cost and bonus kill gold)." + "3" "[card:2]Light pen: level 1 — move speed only; level 2 — move speed limit; level 3 — unit phasing and BAT reduction. Removed move speed and attack speed limit ignore at all levels." + "4" "[card:43]Quick Hands: level 2 — ranged heroes +300 projectile speed; level 3 — attack animation time −33%." + } + } + "9" + { + "date" "2026-05-22" + "version" "0.18.2" + "title" "0.18.2" + "general" + { + "1" "Wave zombies no longer grant XP." + "2" "Night 1: Shambler 15→2; Scavenger 18→4 (wave 1) / 18→2 (waves 2–3); Toxin — / 18→4 (waves 2–3); end of wave 1: 3× Beast 25→2." + "3" "Night 2: Toxin 15→4 / 18→4; Beast 18→4 / 20→4; Skeleton 15→3 / 25→3; Skeleton scavenger 18→3 / 20→2; Skeleton archer 25→3 / 25→2; Skeleton swordsman — / 18→3." + "4" "Night 3: Gargoyle 18→4; Toxin II 18→6 / 20→4; Ghost shooter 15→3 / 15→5; Beast 18→4; Ghost 15 (unchanged, max 6 per wave instead of 25)." + "5" "Night 4: Ghoul 15→7; Toxin II 18→7 / 18→6; Skeleton assassin — / 15→5; Skeleton warrior — / 18→4." + "6" "Night 5: Ghoul 18→5 / 18→6; Ghost 18→6 / 18→7; Gargoyle 22→5 / 22→8; Skeleton assassin 20→5 / 20→7; Toxin II 20→5; Skeleton warrior — / 20→5; Ghost shooter — / 18→6; Skeleton archer — / 25→5 / 25→6." + "7" "Night 6: Toxin II 22→6 / 22→5; Ghost 20→7 / 20→6; Ghoul 20→7 / 20→5; Skeleton warrior 22→8 / 22→7; Gargoyle 24→8 / 24→6; Skeleton assassin — / 22→7 / 22→8; Skeleton archer — / 26→6 / 26→5; Ghost shooter — / 20→8." + "8" "Night 7 (finale): Shambler 15→6; Scavenger 18→7; Toxin 18→8 / 18→9; Beast — / 20→9; Skeleton 25→9; Skeleton scavenger — / 20→9; Skeleton swordsman — / 18→9; Skeleton archer — / 25→9; Gargoyle 18→9; Toxin II 20→9 / 18→9; Ghost 15→9; Ghost shooter 15→9; Ghoul 15→9; Skeleton assassin 15→9; Skeleton warrior 18→9." + } + "cards" + { + "1" "[card:84]Small Mana Surge (new): one-time fixed spell amp = 15/20/25% of max mana at pickup (×0.1 in formula); not deckable — only via [card:33]." + "2" "[card:33]Mana Surge: level 3 — pick Small Mana Surge (like blades from [card:41])." + "3" "[card:25]Mana Core: level 3 — +25% max mana." + "4" "[card:41]Blade Reserve: now upgradeable; level 2 — instant Mana Blade or Blood Blade pick; level 3 — +1 of each blade in pool (4 cards total)." + "5" "[card:30]Curse of Growth, [card:31]Curse of Agility, [card:54]Curse of Intellect: attribute per level 1/2/3 → 0.5/1/1.5; penalties to other stats softened." + "6" "[card:60]Gold gift (wish): 500/800/1200 → 250/500/750. [card:61]Crystal gift: 25/40/60 → 12/18/24." + "7" "[card:79]Echo of Choice: 3 deck slots (was 2)." + } + } + "10" + { + "date" "2026-05-24" + "version" "0.19.0" + "title" "0.19.0" + "general" + { + "1" "All players: Death Sentence contracts and arsenal inventory removed (including active marketplace listings). Dust compensation granted using disassemble values. Total ~18M dust to 990 players." + "2" "Store — Arcade tab: standard pack (3 cards for 25,000 zombie shards) and premium pack (3 cards for 80 donate shards). Duplicates → dust." + "3" "Newbie promos: Starter «Zaika» bundle 500₽ (7 days after signup) — BP Premium, Zaika chat wheel sound, 20,000 dust, 300 donate shards, 250,000 zombie shards. Card pack bundle 8 standard + 2 premium — 199₽ (21 days, repeatable)." + "4" "Monthly subscription 299₽: +30 days per purchase (stacks). Daily on first login: 30 donate shards and 5,000 zombie shards." + "5" "Post-match rating change multiplied by difficulty multiplier. Rating floor from peak MMR (1000 step) — rating cannot drop below floor." + "6" "BP skins: Diretide emblem effects (3 variants). invasion map updated." + "7" "Kunkka quest «Treasure under the cross»: shovel, crosses on the map, dig for gold; reward — Shameful Pipe (+450 attack range, immune to creep bone armor)." + "8" "Creeps: toxin and death explosion use creep attack damage (not max HP); Full Brutality no extra difficulty scale. Bone armor: isolated ranged penalty ×1.5 (was ×5); reflect only if a hero is nearby; wave armor/magic debuff −0.5 per stack (was −3)." + "9" "Nevermore boss: below 50% HP — −80% incoming until Requiem cast 3 times. [hero:axe] Berserker's Call does not taunt the boss." + "10" "Card draft: pool weights and rarity downgrades reworked (copies consumed across slots). Arsenal summary: effective incoming reduction % (cap ~92%), «sum from items» label for linear sum." + "11" "New heroes: [hero:mirana], [hero:vengefulspirit], [hero:spectre] (donate), [hero:ogre_magi] (free)." + } + "items" + { + "1" "[item:item_shovel]Magic Shovel (quest): 1s channel, up to 5 digs on crosses, 145–325 gold, +675 HP." + "2" "[item:item_shameful_pipe]Shameful Pipe (quest reward): +450 attack range for ranged; immune to bone armor penalty and reflect." + } + "cards" + { + "1" "[card:6]Lucky Hand: level 3 — bonus pick from legendary and mythic pool cards (was legendary only)." + "2" "[card:43]Quick Hands: BAT via SetBaseAttackTime, copies stack; synergy with [card:2] at level 3." + "3" "[card:2]Light pen: level 3 BAT reduction via shared BAT formula (when [card:43] absent)." + } + "heroes" + { + "1" "[hero:mirana]New store hero (donate). Moon huntress: spell damage scales with max mana and hero level. [abil:ability_mirana_starstorm_custom]Starstorm — AoE damage, delayed second strike. [abil:ability_mirana_sacred_arrow_custom]Sacred Arrow — longer flight means longer stun; Shard triggers Starstorm on impact. [abil:ability_mirana_leap_custom]Leap costs a mana share and deals landing damage around you (mana-based). [abil:ability_mirana_moonlight_shadow_custom]Ultimate — global invisibility and damage amp for allies; Scepter shares damage taken. Within 1200 of [hero:luna], both Moon Sisters take −25% incoming damage." + "2" "[hero:vengefulspirit]New store hero (donate). Support and initiation: buffs allies, breaks enemies. [abil:ability_vengefulspirit_command_aura_custom]Command Aura — bonus attack damage and lifesteal (physical and spell). [abil:vengefulspirit_magic_missile_custom]Magic Missile — damage, stun, mana burn; bounces to a nearby enemy after hit. [abil:vengefulspirit_wave_of_terror_custom]Wave of Terror — damage, armor reduction (flat and % of base), reduced attack damage, vision along the path. [abil:vengefulspirit_nether_swap_custom]Nether Swap — swap positions with a unit; target HP cannot drop below a threshold. Shard [abil:vengefulspirit_revenge] — short empowered crit window. Innate: marks enemies; heals and restores mana on kills." + "3" "[hero:spectre]New store hero (donate). Spectral carry for long fights and map-wide pressure. [abil:ability_spectre_spectral_dagger_custom]Spectral Dagger — ground path, slow, mobility along the trail. [abil:ability_spectre_spectral_echo_custom]Spectral Echo — chance to spawn attack-copying shadows. [abil:ability_spectre_dispersion_custom]Dispersion — reflects incoming damage; Shard adds a boosted active for mana. [abil:ability_spectre_desolate_custom]Innate Desolate — pure damage vs targets with no allies nearby (% of their max HP). [abil:ability_spectre_haunt_custom]Haunt — illusions on every enemy hero; [abil:ability_spectre_reality_custom]Reality teleports to one illusion and strikes." + "4" "[hero:ogre_magi]New hero — free in the hero pool. Beefy support with Multicast: one cast can fire multiple times. [abil:ability_ogre_magi_fireblast_custom]Fireblast — AoE stun and damage plus a slice of target max HP. [abil:ability_ogre_magi_ignite_custom]Ignite — spreading burn, slow, and max-HP damage over time. [abil:ability_ogre_magi_bloodlust_custom]Bloodlust — attack and move speed for an ally (and yourself). [abil:ability_ogre_magi_multicast_custom]Ultimate Multicast — chances to cast abilities 2×, 3×, or 4×. Scepter — [abil:ability_ogre_magi_unrefined_fireblast_custom]heavy Fireblast for a mana cost." + "5" "[hero:pudge][abil:ability_pudge_meat_hook_custom]+50/100/150/200% max HP damage. [abil:ability_pudge_rot_custom]+0.6/0.9/1.2/1.5% max HP/s to DoT. [abil:ability_pudge_dismember_custom]+0.8/1.2/1.6% max HP/s to STR damage." + "6" "[hero:sniper][abil:ability_sniper_shrapnel_custom]zone damage — 25% attack damage per 1s (10s), instead of flat 12–30 spell damage; +25% incoming to enemies in zone." + "7" "[hero:rubick][abil:ability_rubick_telekinesis_custom]×2 lift distance before channel break." + "8" "[hero:luna][abil:ability_luna_lunar_blessing_custom]1 innate level; +2.5 damage per hero level; Shard +0.45% spell amp/level; synergy with Mirana." + "9" "[hero:legion_commander][abil:ability_legion_commander_duel_custom]random stat on kill +1 (+4 with Scepter); damage stack cap 40,000 (was 400)." + "10" "[hero:nagash]Aghanim's Shard: 3s invulnerability." + "11" "[hero:yuki_onna][abil:ability_yuki_challenge]attribute bonus per trial up to +12 (was +16); buff locks flat bonus on completion." + "12" "[hero:templar_assassin]fixed Refraction Trap stuck after trigger." + } + } + "11" + { + "date" "2026-05-26" + "version" "0.19.1" + "title" "0.19.1" + "general" + { + "1" "Greed: +1 to all attributes per 250 net worth (was 100)" + "2" "Arsenal: lower caps and upgrade growth for all-stats, single attributes, and their % lines." + } + "cards" + { + "1" "[card:85]Fishing (new): on pickup — extra card picks (1/2/3 by level) and Slag in pool; [card:86]Slag — empty no-effect card, pool only." + "2" "[card:51]Midas Chestplate: rework — gold from damage taken (% of damage), incoming damage +base and +0.1% per 100 damage taken (was damage mitigation for gold)." + "3" "[card:58]Rampant Growth: innate; 3/4/5 extra picks at game start with 3/4/5 cards each; no standard dawn card selection." + "4" "[card:57]Mirror of Fate: 5 deck slots (was 3)." + "5" "[card:74]: 3 deck slots (was 2)." + "6" "[card:69]Cursed Bargain: other cards effectiveness 75/50/25% → 50/35/20%." + "7" "[card:68]Three Debts: with [card:69] in deck, bonus pick is any 3 cards from pool, not only cursed; Slag excluded from options." + "8" "[card:23]Golden Rain: income %/min 2/2.5/3.5 → 2/3.5/5; tick every 60s at all levels." + "9" "[card:36]Crystal Growth: damage per step and cap increased (max bonus 40/55/70% → 80/160/320%)." + } + "items" + { + "1" "[new][item:item_skadi_custom]Eye of Skadi (custom) — recipe: Ultimate Crown + Orb of Corrosion; cold attack, +45 all stats and +5% base attributes." + "2" "[new][item:item_shovel]Magic Shovel — quest item: dig at crosses (up to 5 times), gold and +675 HP." + "3" "[new][item:item_shameful_pipe]Shameful Pipe — quest reward: +450 attack range for ranged, immune to bone armor." + "4" "[item:item_desolator_custom] / [item:item_desolator_2_custom]: max damage bonus 30→90 and 45→125." + } + "heroes" + { + "1" "[hero:rubick][abil:ability_rubick_telekinesis_custom]Cannot lift npc_homer or bosses; allies without Scepter have finite air time (was infinite for allies except Homer)." + "2" "[hero:vengefulspirit][abil:vengefulspirit_nether_swap_custom]Does not copy Rubick telekinesis buffs to allies; cannot full-heal Homer on swap." + "3" "[hero:npc_dota_hero_nagash]Soul loss on death: Aghanim's Shard reduces lost_souls_pct by 30." + } + } + "12" + { + "date" "2026-05-28" + "version" "0.19.2" + "title" "0.19.2" + "general" + { + "1" "Card system: major rework of core draft/processing logic for stability and performance." + "2" "Optimization and technical gameplay fixes: damage tracking, modifier source handling, boss HUD, service managers, and initialization flow." + "3" "Localization and catalogs: card texts/references updated (RU/EN/CN) with related UI manifests." + "4" "Arsenal: percentage attribute stats (% Strength/% Agility/% Intelligence/% All Stats) apply only to base attributes." + } + "cards" + { + "1" "[card:87]\"King of Slag\": grants +2.5% to base Strength, Agility, and Intelligence for each [card:86] slag card left in the pool (updates dynamically)." + "2" "[card:88] free rerolls now grant per each slag card received (scaled by slag amount in one event)." + "3" "[card:23] anti-abuse payout gate added: dividend triggers only if earned gold per tick is at least 25% of that dividend." + "4" "[card:51] morning gold cap nerf: 400/600/800 → 250/400/550 (and now scales with active card copies)." + "5" "[card:67] rebalance: HP per Strength 0.05/0.1/0.15 → 0.1; outgoing % per Strength 0.1 → 0.05; proc chance 60/75/90 → 40/45/50; proc CD 3/2.5/2 → 3; max-HP damage 100/200/300 → 60/80/100." + "6" "[card:69] HP activation threshold 60% → 30%; health cost unified to 5%; curse stack damage bonus 10/14/18 → 10/20/30." + "7" "[card:47] XP bonus nerf: 100/150/200% → 35/50/65%." + "8" "[card:12], [card:13], [card:14], [card:19], [card:26], [card:35], [card:48], [card:55], [card:56], [card:78], [card:80], plus curse/greed modifiers now use unified source ability when applying modifiers (more stable stacks/events/damage source attribution)." + } + } + } +} diff --git a/scripts/shops/invasion_shops.txt b/scripts/shops/invasion_shops.txt new file mode 100644 index 0000000..2a6d812 --- /dev/null +++ b/scripts/shops/invasion_shops.txt @@ -0,0 +1,428 @@ +"dota_shops" +{ + "consumables" + { + "item" "item_tpscroll" + "item" "item_clarity" + "item" "item_coffe_bean" + "item" "item_faerie_fire" + "item" "item_smoke_of_deceit" + "item" "item_ward_observer" + "item" "item_ward_sentry" + "item" "item_enchanted_mango" + "item" "item_flask" + "item" "item_tango" + "item" "item_blood_grenade" + "item" "item_dust" + "item" "item_bottle" + "item" "item_aghanims_shard" + + } + + "attributes" + { + "item" "item_branches" + + "item" "item_gauntlets" + "item" "item_slippers" + "item" "item_mantle" + + "item" "item_circlet" + + "item" "item_belt_of_strength" + "item" "item_boots_of_elves" + "item" "item_robe" + + "item" "item_crown" + + "item" "item_ogre_axe" + "item" "item_blade_of_alacrity" + "item" "item_staff_of_wizardry" + "item" "item_diadem" + + } + + "weapons_armor" + { + "item" "item_quelling_blade" + "item" "item_ring_of_protection" + "item" "item_magic_wand" + "item" "item_null_talisman" + "item" "item_wraith_band" + "item" "item_bracer" + "item" "item_orb_of_venom" + "item" "item_blight_stone" + "item" "item_orb_of_frost" + "item" "item_orb_of_fire" + "item" "item_ice_spine" + "item" "item_blades_of_attack" + "item" "item_gloves" + "item" "item_chainmail" + "item" "item_quarterstaff" + "item" "item_helm_of_iron_will" + "item" "item_broadsword" + "item" "item_blitz_knuckles" + "item" "item_javelin" + "item" "item_magical_quiver" + "item" "item_claymore" + "item" "item_mithril_hammer" + "item" "item_poor_shield" + } + + "misc" + { + "item" "item_ring_of_regen" + "item" "item_sobi_mask" + "item" "item_magic_stick" + "item" "item_fluffy_hat" + "item" "item_wind_lace" + "item" "item_cloak" + "item" "item_boots" + //"item" "item_ring_of_tarrasque" + "item" "item_gem" + "item" "item_lifesteal_custom" + "item" "item_voodoo_mask_custom" + "item" "item_shadow_amulet" + "item" "item_ghost" + "item" "item_blink" + "item" "item_ring_of_health" + "item" "item_void_stone" + } + + + // Level 1 - Green Recipes + "basics" + { + //"item" "item_magic_wand" + //"item" "item_null_talisman" + //"item" "item_wraith_band" + //"item" "item_bracer" + + "item" "item_vampire_claw" + "item" "item_magic_stone" + "item" "item_orb_of_corrosion" + "item" "item_vambrace" + + //"item" "item_soul_ring" + "item" "item_falcon_blade" + "item" "item_phase_boots" + "item" "item_power_treads" + + //"item" "item_oblivion_staff" + "item" "item_pers" + "item" "item_mask_of_madness_custom" + "item" "item_mega_treads" + + "item" "item_moon_shard" + "item" "item_soul_booster" + + + + //"item" "item_hand_of_midas" + //"item" "item_travel_boots" + } + + // Level 2 - Blue Recipes + "support" + { + "item" "item_buckler" + "item" "item_ring_of_basilius" + "item" "item_headdress" + "item" "item_holy_locket" + + "item" "item_urn_of_shadows" + "item" "item_pavise" + "item" "item_arcane_boots" + "item" "item_tranquil_boots" + + "item" "item_spirit_vessel" + "item" "item_solar_crest" + "item" "item_mekansm" + "item" "item_ancient_janggo" + + "item" "item_vladmir" + "item" "item_pipe" + "item" "item_guardian_greaves" + "item" "item_boots_of_bearing" + } + + "magics" + { + "item" "item_glimmer_cape" + "item" "item_force_staff" + "item" "item_dagon" + "item" "item_meteor_hammer" + + "item" "item_rod_of_atos" + "item" "item_aether_lens" + "item" "item_cyclone" + "item" "item_orchid" + + "item" "item_gungir" + "item" "item_ethereal_blade_custom" + "item" "item_wind_waker" + "item" "item_refresher" + + "item" "item_octarine_core" + "item" "item_sheepstick" + "item" "item_bloodstone_magical" + "item" "item_ultimate_scepter" + + "item" "item_soul_devourer_staff" + "item" "item_magic_rapier" + "item" "item_magical_divine_rapier" + } + + // Level 3 - Purple Recipes + "defense" + { + "item" "item_vanguard" + "item" "item_blade_mail" + "item" "item_blademail_2" + "item" "item_helm_of_the_dominator" + "item" "item_armlet" + "item" "item_armlet_of_eternal_hunger" + + "item" "item_crimson_guard" + "item" "item_crimson_shivas_custom" + "item" "item_aeon_disk" + "item" "item_helm_of_the_overlord" + "item" "item_heart" + + "item" "item_veil_of_discord" + "item" "item_eternal_shroud" + "item" "item_lotus_orb" + "item" "item_black_king_bar" + "item" "item_warrior_shield" + + "item" "item_shivas_guard" + "item" "item_skadi_custom" + "item" "item_sphere" + "item" "item_fire_cape" + "item" "item_assault" + + // Not purchasable anymore + "item" "item_hood_of_defiance" + + } + + "weapons" + { + "item" "item_mini_bfury" + "item" "item_battle_fury_custom" + "item" "item_mega_fury" + "item" "item_mage_slayer" + "item" "item_zombie_slayer" + "item" "item_desolator_custom" + "item" "item_desolator_custom_2" + "item" "item_nullifier" + + "item" "item_crystalys" + "item" "item_magical_crit" + "item" "item_witch_blade" + "item" "item_basher" + "item" "item_invis_sword" + + "item" "item_revenants_brooch" + "item" "item_devastator" + "item" "item_abyssal_blade" + "item" "item_silver_edge" + + "item" "item_dark_crystalys" + "item" "item_balist_custom" + "item" "item_diffusal_blade" + "item" "item_radiance_custom" + "item" "item_blazing_radiance_custom" + "item" "item_monkey_king_bar" + + "item" "item_rapier_custom" + "item" "item_divine_rapier_custom" + "item" "item_disperser" + "item" "item_butterfly" + "item" "item_king_fly" + "item" "item_bloodthorn" + + // Not purchasable items + "item" "item_aetherial_halo" + "item" "item_caster_rapier" + } + + // Level 4 - Orange / Orb / Artifacts + "artifacts" + { + "item" "item_dragon_lance" + "item" "item_storm" + + "item" "item_echo_sabre" + "item" "item_maelstrom" + "item" "item_phylactery" + + "item" "item_hurricane_pike" + "item" "item_harpoon" + "item" "item_mjolnir_custom" + "item" "item_angels_demise" + + "item" "item_bearstbow" + "item" "item_demonic_bow" + "item" "item_sange" + "item" "item_yasha" + "item" "item_kaya" + "item" "item_heavens_halberd" + + "item" "item_kaya_and_sange" + "item" "item_sange_and_yasha" + "item" "item_yasha_and_kaya" + "item" "item_trident" + "item" "item_manta" + + "item" "item_overwhelming_blink" + "item" "item_swift_blink" + "item" "item_arcane_blink" + "item" "item_satanic_custom" + + } + + "sideshop1" + { + "item" "item_tpscroll" + "item" "item_magic_stick" + "item" "item_quelling_blade" + "item" "item_boots" + "item" "item_boots_of_elves" + "item" "item_belt_of_strength" + "item" "item_robe" + "item" "item_crown" + } + + "sideshop2" + { + "item" "item_gloves" + "item" "item_chainmail" + "item" "item_cloak" + "item" "item_void_stone" + "item" "item_helm_of_iron_will" + "item" "item_energy_booster" + "item" "item_vitality_booster" + "item" "item_lifesteal_custom" + "item" "item_broadsword" + "item" "item_blink" + } + + "secretshop" + { + + "item" "item_ring_of_tarrasque" + "item" "item_tiara_of_selemene" + "item" "item_cornucopia" + "item" "item_energy_booster" + "item" "item_vitality_booster" + "item" "item_point_booster" + "item" "item_talisman_of_evasion" + "item" "item_platemail" + "item" "item_hyperstone" + "item" "item_ultimate_orb" + "item" "item_ultimate_crown" + "item" "item_demon_edge" + "item" "item_mystic_staff" + "item" "item_reaver" + "item" "item_eagle" + "item" "item_relic" + } + + "pregame" + { + "item" "item_clarity" + "item" "item_faerie_fire" + "item" "item_enchanted_mango" + "item" "item_tango" + "item" "item_flask" + "item" "item_smoke_of_deceit" + "item" "item_dust" + "item" "item_ward_observer" + "item" "item_ward_sentry" + "item" "item_blood_grenade" + "item" "item_branches" + "item" "item_gauntlets" + "item" "item_slippers" + "item" "item_mantle" + "item" "item_circlet" + "item" "item_magic_wand" + "item" "item_ring_of_protection" + "item" "item_quelling_blade" + "item" "item_fluffy_hat" + "item" "item_blight_stone" + "item" "item_orb_of_venom" + "item" "item_orb_of_frost" + "item" "item_orb_of_fire" + "item" "item_wind_lace" + "item" "item_magic_stick" + "item" "item_sobi_mask" + "item" "item_ring_of_regen" + "item" "item_boots" + "item" "item_gloves" + "item" "item_crown" + "item" "item_wraith_band" + "item" "item_null_talisman" + "item" "item_bracer" + "item" "item_blades_of_attack" + "item" "item_ring_of_basilius" + "item" "item_buckler" + "item" "item_headdress" + } + + "pregame_consumables" + { + "item" "item_tango" + "item" "item_faerie_fire" + "item" "item_flask" + "item" "item_smoke_of_deceit" + + "item" "item_clarity" + "item" "item_enchanted_mango" + "item" "item_magic_stick" + "item" "item_magic_wand" + + "item" "item_ward_observer" + "item" "item_ward_sentry" + "item" "item_dust" + "item" "item_blood_grenade" + } + + "pregame_attributes" + { + "item" "item_branches" + "item" "item_gauntlets" + "item" "item_slippers" + "item" "item_mantle" + + "item" "item_circlet" + "item" "item_bracer" + "item" "item_wraith_band" + "item" "item_null_talisman" + + "item" "item_crown" + "item" "item_fluffy_hat" + "item" "item_wind_lace" + "item" "item_boots" + + } + + "pregame_accessories" + { + "item" "item_quelling_blade" + "item" "item_ring_of_protection" + "item" "item_sobi_mask" + "item" "item_ring_of_basilius" + + "item" "item_blight_stone" + "item" "item_orb_of_venom" + "item" "item_orb_of_frost" + "item" "item_orb_of_fire" + "item" "item_ring_of_regen" + + "item" "item_gloves" + "item" "item_blades_of_attack" + "item" "item_buckler" + "item" "item_headdress" + } +} \ No newline at end of file diff --git a/scripts/vscripts/abilities/ability_capture_point.lua b/scripts/vscripts/abilities/ability_capture_point.lua new file mode 100644 index 0000000..da6f2f0 --- /dev/null +++ b/scripts/vscripts/abilities/ability_capture_point.lua @@ -0,0 +1,125 @@ +local ____lualib = require("lualib_bundle") +local __TS__Class = ____lualib.__TS__Class +local __TS__ClassExtends = ____lualib.__TS__ClassExtends +local __TS__Decorate = ____lualib.__TS__Decorate +local ____exports = {} +local ____dota_ts_adapter = require("lib.dota_ts_adapter") +local BaseAbility = ____dota_ts_adapter.BaseAbility +local BaseModifier = ____dota_ts_adapter.BaseModifier +local registerAbility = ____dota_ts_adapter.registerAbility +local registerModifier = ____dota_ts_adapter.registerModifier +--- Пассивка для npc_capture_point: при приближении героя спавнит npc_teleport один раз. +____exports.ability_capture_point = __TS__Class() +local ability_capture_point = ____exports.ability_capture_point +ability_capture_point.name = "ability_capture_point" +ability_capture_point.____file_path = "scripts/vscripts/abilities/ability_capture_point.lua" +__TS__ClassExtends(ability_capture_point, BaseAbility) +function ability_capture_point.prototype.GetIntrinsicModifierName(self) + return ____exports.modifier_ability_capture_point.name +end +ability_capture_point = __TS__Decorate( + ability_capture_point, + ability_capture_point, + {registerAbility(nil)}, + {kind = "class", name = "ability_capture_point"} +) +____exports.ability_capture_point = ability_capture_point +____exports.modifier_ability_capture_point = __TS__Class() +local modifier_ability_capture_point = ____exports.modifier_ability_capture_point +modifier_ability_capture_point.name = "modifier_ability_capture_point" +modifier_ability_capture_point.____file_path = "scripts/vscripts/abilities/ability_capture_point.lua" +__TS__ClassExtends(modifier_ability_capture_point, BaseModifier) +function modifier_ability_capture_point.prototype.____constructor(self, ...) + BaseModifier.prototype.____constructor(self, ...) + self.activated = false + self.intervalThinkCount = 0 +end +function modifier_ability_capture_point.prototype.IsHidden(self) + return true +end +function modifier_ability_capture_point.prototype.IsPurgable(self) + return false +end +function modifier_ability_capture_point.prototype.OnCreated(self) + if IsServer() then + local p = self:GetParent() + local o = p and p:GetAbsOrigin() + local ____opt_2 = p and p.GetEntityIndex + local idx = ____opt_2 and ____opt_2(p) or -1 + self:StartIntervalThink(0.25) + end +end +function modifier_ability_capture_point.prototype.OnIntervalThink(self) + if not IsServer() or self.activated then + return + end + self:GetParent():StartGesture(ACT_DOTA_RUN) + local parent = self:GetParent() + if not parent or not IsValidEntity(parent) or not parent:IsAlive() then + return + end + local ability = self:GetAbility() + local radius = ability:GetSpecialValueFor("activation_radius") + local spawnDistance = ability:GetSpecialValueFor("spawn_distance") + local heroes = FindUnitsInRadius( + parent:GetTeamNumber(), + parent:GetAbsOrigin(), + nil, + radius, + DOTA_UNIT_TARGET_TEAM_FRIENDLY, + DOTA_UNIT_TARGET_HERO, + DOTA_UNIT_TARGET_FLAG_NONE, + FIND_ANY_ORDER, + false + ) + self.intervalThinkCount = self.intervalThinkCount + 1 + local realHeroCount = 0 + for ____, u in ipairs(heroes) do + if u and u:IsAlive() and u:IsRealHero() then + realHeroCount = realHeroCount + 1 + end + end + for ____, h in ipairs(heroes) do + if h and h:IsAlive() and h:IsRealHero() then + parent:RemoveGesture(ACT_DOTA_RUN) + self:spawnTeleport(parent, spawnDistance) + self.activated = true + self:StartIntervalThink(-1) + return + end + end +end +function modifier_ability_capture_point.prototype.spawnTeleport(self, capture, distance) + local origin = capture:GetAbsOrigin() + local forward = capture:GetForwardVector() + local rawPos = Vector(origin.x + forward.x * distance, origin.y + forward.y * distance, -origin.z) + local pos = GetGroundPosition(rawPos, nil) + capture:StartGesture(ACT_DOTA_CAPTURE) + local tp = CreateUnitByName( + "npc_teleport", + pos, + false, + nil, + nil, + capture:GetTeamNumber() + ) + if tp and IsValidEntity(tp) then + local pfx = ParticleManager:CreateParticle( + "particles/econ/events/fall_2021/fountain_regen_fall_2021_lvl3.vpcf", + PATTACH_ABSORIGIN_FOLLOW, + self:GetParent() + ) + ParticleManager:ReleaseParticleIndex(pfx) + tp:SetEntityName("npc_teleport") + tp:SetAbsOrigin(pos) + tp:SetForwardVector(forward) + end +end +modifier_ability_capture_point = __TS__Decorate( + modifier_ability_capture_point, + modifier_ability_capture_point, + {registerModifier(nil)}, + {kind = "class", name = "modifier_ability_capture_point"} +) +____exports.modifier_ability_capture_point = modifier_ability_capture_point +return ____exports diff --git a/scripts/vscripts/abilities/creep/agro_leader.lua b/scripts/vscripts/abilities/creep/agro_leader.lua new file mode 100644 index 0000000..09c5851 --- /dev/null +++ b/scripts/vscripts/abilities/creep/agro_leader.lua @@ -0,0 +1,136 @@ +local ____lualib = require("lualib_bundle") +local __TS__Class = ____lualib.__TS__Class +local __TS__ClassExtends = ____lualib.__TS__ClassExtends +local __TS__ArrayForEach = ____lualib.__TS__ArrayForEach +local __TS__Decorate = ____lualib.__TS__Decorate +local ____exports = {} +local ____dota_ts_adapter = require("lib.dota_ts_adapter") +local BaseAbility = ____dota_ts_adapter.BaseAbility +local BaseModifier = ____dota_ts_adapter.BaseModifier +local registerAbility = ____dota_ts_adapter.registerAbility +local registerModifier = ____dota_ts_adapter.registerModifier +____exports.agro_leader = __TS__Class() +local agro_leader = ____exports.agro_leader +agro_leader.name = "agro_leader" +agro_leader.____file_path = "scripts/vscripts/abilities/creep/agro_leader.lua" +__TS__ClassExtends(agro_leader, BaseAbility) +function agro_leader.prototype.Precache(self, context) + PrecacheResource("particle", "particles/units/heroes/hero_axe/axe_beserkers_call_owner.vpcf", context) + PrecacheResource("particle", "particles/status_fx/status_effect_beserkers_call.vpcf", context) + PrecacheResource("soundfile", "soundevents/game_sounds_heroes/game_sounds_axe.vsndevts", context) +end +function agro_leader.prototype.OnAbilityPhaseStart(self) + if IsServer() then + self:GetCaster():StartGestureWithPlaybackRate(ACT_DOTA_CAST_ABILITY_4, 0.7) + self.preParticle = ParticleManager:CreateParticle( + "particles/darkmoon_creep_warning.vpcf", + PATTACH_ABSORIGIN_FOLLOW, + self:GetCaster() + ) + ParticleManager:SetParticleControlEnt( + self.preParticle, + 0, + self:GetCaster(), + PATTACH_ABSORIGIN_FOLLOW, + "", + self:GetCaster():GetOrigin(), + true + ) + ParticleManager:SetParticleControl( + self.preParticle, + 1, + Vector(100, 100, 100) + ) + end + return true +end +function agro_leader.prototype.OnAbilityPhaseInterrupted(self) + if IsClient() or not self.preParticle then + return + end + self:GetCaster():FadeGesture(ACT_DOTA_CAST_ABILITY_4) + ParticleManager:DestroyParticle(self.preParticle, false) +end +function agro_leader.prototype.OnSpellStart(self) + local caster = self:GetCaster() + if self.preParticle then + ParticleManager:DestroyParticle(self.preParticle, false) + end + caster:FadeGesture(ACT_DOTA_CAST_ABILITY_4) + local radius = self:GetSpecialValueFor("radius") + local particle = ParticleManager:CreateParticle("particles/units/heroes/hero_axe/axe_beserkers_call_owner.vpcf", PATTACH_ABSORIGIN_FOLLOW, caster) + ParticleManager:SetParticleControl( + particle, + 2, + Vector(radius, radius, radius) + ) + caster:EmitSound("Hero_Axe.Berserkers_Call") + local enemies = FindUnitsInRadius( + caster:GetTeamNumber(), + caster:GetAbsOrigin(), + nil, + radius, + DOTA_UNIT_TARGET_TEAM_ENEMY, + DOTA_UNIT_TARGET_HERO + DOTA_UNIT_TARGET_BASIC, + DOTA_UNIT_TARGET_FLAG_MAGIC_IMMUNE_ENEMIES, + FIND_ANY_ORDER, + false + ) + __TS__ArrayForEach( + enemies, + function(____, enemy) + enemy:MoveToTargetToAttack(caster) + ____exports.modifier_agro_leader_debuff:apply( + enemy, + caster, + self, + {duration = self:GetSpecialValueFor("duration")} + ) + end + ) +end +agro_leader = __TS__Decorate( + agro_leader, + agro_leader, + {registerAbility(nil)}, + {kind = "class", name = "agro_leader"} +) +____exports.agro_leader = agro_leader +____exports.modifier_agro_leader_debuff = __TS__Class() +local modifier_agro_leader_debuff = ____exports.modifier_agro_leader_debuff +modifier_agro_leader_debuff.name = "modifier_agro_leader_debuff" +modifier_agro_leader_debuff.____file_path = "scripts/vscripts/abilities/creep/agro_leader.lua" +__TS__ClassExtends(modifier_agro_leader_debuff, BaseModifier) +function modifier_agro_leader_debuff.prototype.IsHidden(self) + return false +end +function modifier_agro_leader_debuff.prototype.IsPurgable(self) + return false +end +function modifier_agro_leader_debuff.prototype.IsDebuff(self) + return true +end +function modifier_agro_leader_debuff.prototype.CheckState(self) + return {[MODIFIER_STATE_COMMAND_RESTRICTED] = true} +end +function modifier_agro_leader_debuff.prototype.DeclareFunctions(self) + return {MODIFIER_EVENT_ON_DEATH} +end +function modifier_agro_leader_debuff.prototype.OnDeath(self, event) + if event.unit == self:GetCaster() then + self:Destroy() + end +end +function modifier_agro_leader_debuff.prototype.OnDestroy(self) +end +function modifier_agro_leader_debuff.prototype.GetStatusEffectName(self) + return "particles/status_fx/status_effect_beserkers_call.vpcf" +end +modifier_agro_leader_debuff = __TS__Decorate( + modifier_agro_leader_debuff, + modifier_agro_leader_debuff, + {registerModifier(nil)}, + {kind = "class", name = "modifier_agro_leader_debuff"} +) +____exports.modifier_agro_leader_debuff = modifier_agro_leader_debuff +return ____exports diff --git a/scripts/vscripts/abilities/creep/bone_armor.lua b/scripts/vscripts/abilities/creep/bone_armor.lua new file mode 100644 index 0000000..fdf0464 --- /dev/null +++ b/scripts/vscripts/abilities/creep/bone_armor.lua @@ -0,0 +1,150 @@ +local ____lualib = require("lualib_bundle") +local __TS__Class = ____lualib.__TS__Class +local __TS__ClassExtends = ____lualib.__TS__ClassExtends +local __TS__Decorate = ____lualib.__TS__Decorate +local ____exports = {} +local ____dota_ts_adapter = require("lib.dota_ts_adapter") +local BaseAbility = ____dota_ts_adapter.BaseAbility +local BaseModifier = ____dota_ts_adapter.BaseModifier +local registerAbility = ____dota_ts_adapter.registerAbility +local registerModifier = ____dota_ts_adapter.registerModifier +local ____item_shameful_pipe = require("items.quest_items.item_shameful_pipe") +local modifier_item_shameful_pipe = ____item_shameful_pipe.modifier_item_shameful_pipe +local ____luck = require("utils.luck") +local rollLuckChance = ____luck.rollLuckChance +____exports.bone_armor = __TS__Class() +local bone_armor = ____exports.bone_armor +bone_armor.name = "bone_armor" +bone_armor.____file_path = "scripts/vscripts/abilities/creep/bone_armor.lua" +__TS__ClassExtends(bone_armor, BaseAbility) +function bone_armor.prototype.Precache(self, context) + PrecacheResource("particle", "particles/econ/items/centaur/centaur_crownfall_belt/centaur_crownfall_belt_retaliate.vpcf", context) +end +function bone_armor.prototype.GetIntrinsicModifierName(self) + return "modifier_bone_armor_passive" +end +bone_armor = __TS__Decorate( + bone_armor, + bone_armor, + {registerAbility(nil)}, + {kind = "class", name = "bone_armor"} +) +____exports.bone_armor = bone_armor +____exports.modifier_bone_armor_passive = __TS__Class() +local modifier_bone_armor_passive = ____exports.modifier_bone_armor_passive +modifier_bone_armor_passive.name = "modifier_bone_armor_passive" +modifier_bone_armor_passive.____file_path = "scripts/vscripts/abilities/creep/bone_armor.lua" +__TS__ClassExtends(modifier_bone_armor_passive, BaseModifier) +function modifier_bone_armor_passive.prototype.IsHidden(self) + return true +end +function modifier_bone_armor_passive.prototype.IsDebuff(self) + return false +end +function modifier_bone_armor_passive.prototype.IsPurgable(self) + return false +end +function modifier_bone_armor_passive.prototype.DeclareFunctions(self) + return {MODIFIER_PROPERTY_PHYSICAL_ARMOR_BONUS, MODIFIER_EVENT_ON_ATTACK_LANDED} +end +function modifier_bone_armor_passive.prototype.GetModifierPhysicalArmorBonus(self) + local ____opt_0 = self:GetAbility() + return ____opt_0 and ____opt_0:GetSpecialValueFor("armor_bonus") or 0 +end +function modifier_bone_armor_passive.prototype.playRetaliateParticle(self) + ParticleManager:CreateParticle( + "particles/econ/items/centaur/centaur_crownfall_belt/centaur_crownfall_belt_retaliate.vpcf", + PATTACH_ABSORIGIN_FOLLOW, + self:GetParent() + ) +end +function modifier_bone_armor_passive.prototype.hasHeroNearCreep(self, creep, radius) + local heroes = FindUnitsInRadius( + creep:GetTeamNumber(), + creep:GetAbsOrigin(), + nil, + radius, + DOTA_UNIT_TARGET_TEAM_ENEMY, + DOTA_UNIT_TARGET_HERO, + DOTA_UNIT_TARGET_FLAG_NONE, + FIND_ANY_ORDER, + false + ) + for ____, hero in ipairs(heroes) do + if hero and IsValidEntity(hero) and hero:IsAlive() and hero:IsRealHero() then + return true + end + end + return false +end +function modifier_bone_armor_passive.prototype.applyReflectedDamage(self, attacker, parent, ability, eventDamage) + local reflectChance = ability:GetSpecialValueFor("damage_reflect_chance") + if not rollLuckChance(nil, attacker, reflectChance / 100) then + return + end + local reflectPct = ability:GetSpecialValueFor("damage_reflect_pct") + local reflectDamage = eventDamage * (reflectPct / 100) + if reflectDamage <= 0 then + return + end + ApplyDamage({ + victim = attacker, + attacker = parent, + damage = reflectDamage, + damage_type = DAMAGE_TYPE_PHYSICAL, + ability = ability + }) + self:playRetaliateParticle() +end +function modifier_bone_armor_passive.prototype.OnAttackLanded(self, event) + if not IsServer() then + return + end + if event.target ~= self:GetParent() then + return + end + local attacker = event.attacker + if not attacker or not IsValidEntity(attacker) or not attacker:IsAlive() then + return + end + if not attacker:IsHero() or not attacker:IsRealHero() then + return + end + if not attacker:IsRangedAttacker() then + return + end + if attacker:HasModifier(modifier_item_shameful_pipe.name) then + return + end + local ability = self:GetAbility() + if not ability then + return + end + local parent = self:GetParent() + local heroProximityRadius = ability:GetSpecialValueFor("hero_proximity_radius") + local heroNearCreep = self:hasHeroNearCreep(parent, heroProximityRadius) + if not heroNearCreep then + local isolatedMultiplier = ability:GetSpecialValueFor("isolated_damage_multiplier") + local damage = event.damage * isolatedMultiplier + if damage > 0 then + ApplyDamage({ + victim = attacker, + attacker = parent, + damage = damage, + damage_type = DAMAGE_TYPE_PHYSICAL, + ability = ability + }) + self:playRetaliateParticle() + end + return + end + self:applyReflectedDamage(attacker, parent, ability, event.damage) +end +modifier_bone_armor_passive = __TS__Decorate( + modifier_bone_armor_passive, + modifier_bone_armor_passive, + {registerModifier(nil)}, + {kind = "class", name = "modifier_bone_armor_passive"} +) +____exports.modifier_bone_armor_passive = modifier_bone_armor_passive +return ____exports diff --git a/scripts/vscripts/abilities/creep/boss_nevermore_coil_beam.lua b/scripts/vscripts/abilities/creep/boss_nevermore_coil_beam.lua new file mode 100644 index 0000000..60b772a --- /dev/null +++ b/scripts/vscripts/abilities/creep/boss_nevermore_coil_beam.lua @@ -0,0 +1,362 @@ +local ____lualib = require("lualib_bundle") +local __TS__Class = ____lualib.__TS__Class +local __TS__ClassExtends = ____lualib.__TS__ClassExtends +local __TS__Decorate = ____lualib.__TS__Decorate +local ____exports = {} +local modifier_boss_nevermore_coil_beam_lock +local ____dota_ts_adapter = require("lib.dota_ts_adapter") +local BaseAbility = ____dota_ts_adapter.BaseAbility +local BaseModifier = ____dota_ts_adapter.BaseModifier +local registerAbility = ____dota_ts_adapter.registerAbility +local registerModifier = ____dota_ts_adapter.registerModifier +local ____modifier_boss_nevermore_coil_debuff = require("abilities.creep.modifier_boss_nevermore_coil_debuff") +local modifier_boss_nevermore_coil_debuff = ____modifier_boss_nevermore_coil_debuff.modifier_boss_nevermore_coil_debuff +--- Как 1-я способность (две волны по «шахматке»), но слоты в линию от босса к точке каста — «к врагу». +____exports.boss_nevermore_coil_beam = __TS__Class() +local boss_nevermore_coil_beam = ____exports.boss_nevermore_coil_beam +boss_nevermore_coil_beam.name = "boss_nevermore_coil_beam" +boss_nevermore_coil_beam.____file_path = "scripts/vscripts/abilities/creep/boss_nevermore_coil_beam.lua" +__TS__ClassExtends(boss_nevermore_coil_beam, BaseAbility) +function boss_nevermore_coil_beam.prototype.Precache(self, context) + PrecacheResource("particle", "particles/units/heroes/hero_nevermore/nevermore_shadowraze.vpcf", context) + PrecacheResource("particle", "particles/darkmoon_creep_warning.vpcf", context) + PrecacheResource("particle", "particles/econ/events/darkmoon_2017/darkmoon_generic_aoe.vpcf", context) + PrecacheResource("soundfile", "sounds/units/heroes/nevermore/shadowraze.vsnd", context) +end +function boss_nevermore_coil_beam.prototype.GetAOERadius(self) + local r = self:GetSpecialValueFor("radius") + local start = self:getSpecialOrDefault("beam_start_dist", ____exports.boss_nevermore_coil_beam.DEFAULT_BEAM_START) + local step = self:getBeamStepDistance() + local slots = self:getSlotCountForPhase(self:getBossPhase()) + return start + step * math.max(0, slots - 1) + r * 2 +end +function boss_nevermore_coil_beam.prototype.getSpecialOrDefault(self, name, fallback) + local value = self:GetSpecialValueFor(name) + if not value or value <= 0 then + return fallback + end + return value +end +function boss_nevermore_coil_beam.prototype.getBossPhase(self) + local caster = self:GetCaster() + if not caster or caster:IsNull() then + return 1 + end + local hp = caster:GetHealthPercent() + if hp <= 25 then + return 4 + end + if hp <= 50 then + return 3 + end + if hp <= 75 then + return 2 + end + return 1 +end +function boss_nevermore_coil_beam.prototype.getSlotCountForPhase(self, phase) + local baseSlots = math.floor(self:getSpecialOrDefault("lane_slot_count", ____exports.boss_nevermore_coil_beam.DEFAULT_SLOT_COUNT)) + local perPhase = math.floor(self:getSpecialOrDefault("lane_slot_phase_bonus", ____exports.boss_nevermore_coil_beam.DEFAULT_SLOT_PHASE_BONUS)) + return baseSlots + (phase - 1) * perPhase +end +function boss_nevermore_coil_beam.prototype.getBeamStepDistance(self) + local radius = self:GetSpecialValueFor("radius") + local stepKv = self:GetSpecialValueFor("beam_step") + if stepKv > 0 then + return stepKv + end + return math.max(radius * 1.22, ____exports.boss_nevermore_coil_beam.DEFAULT_BEAM_STEP) +end +function boss_nevermore_coil_beam.prototype.buildBeamSlots(self, origin, direction, phase) + local dir2d = direction:Normalized() + dir2d.z = 0 + local slotCount = self:getSlotCountForPhase(phase) + local startDist = self:getSpecialOrDefault("beam_start_dist", ____exports.boss_nevermore_coil_beam.DEFAULT_BEAM_START) + local step = self:getBeamStepDistance() + local firstWaveHitsEvenIndex = RandomInt(0, 1) == 0 + local slots = {} + do + local i = 0 + while i < slotCount do + local along = startDist + i * step + local pos = GetGroundPosition(origin + dir2d * along, nil) + local isEven = i % 2 == 0 + local ____firstWaveHitsEvenIndex_0 + if firstWaveHitsEvenIndex then + ____firstWaveHitsEvenIndex_0 = isEven + else + ____firstWaveHitsEvenIndex_0 = not isEven + end + local hitsOnFirstWave = ____firstWaveHitsEvenIndex_0 + slots[#slots + 1] = {position = pos, hitsOnFirstWave = hitsOnFirstWave} + i = i + 1 + end + end + return slots +end +function boss_nevermore_coil_beam.prototype.OnSpellStart(self) + if not IsServer() then + return + end + local caster = self:GetCaster() + local origin = caster:GetAbsOrigin() + local point = self:GetCursorPosition() + local toPoint = point - origin + local direction = toPoint:Length2D() < 1 and caster:GetForwardVector() or toPoint:Normalized() + local phase = self:getBossPhase() + self:spawnBeamPattern(origin, direction, phase) +end +function boss_nevermore_coil_beam.prototype.spawnBeamPattern(self, origin, direction, phase) + local caster = self:GetCaster() + local slots = self:buildBeamSlots(origin, direction, phase) + if #slots == 0 then + return + end + local radius = self:GetSpecialValueFor("radius") + local startDelay = self:getSpecialOrDefault("start_delay", ____exports.boss_nevermore_coil_beam.DEFAULT_START_DELAY) + local warningTime = self:getSpecialOrDefault("precast_warning_time", ____exports.boss_nevermore_coil_beam.DEFAULT_WARNING_TIME) + local secondDelayRaw = self:GetSpecialValueFor("second_wave_delay") + local secondWaveDelay = secondDelayRaw > 0 and secondDelayRaw or ____exports.boss_nevermore_coil_beam.DEFAULT_SECOND_WAVE_DELAY + local firstHitTime = startDelay + local secondHitTime = firstHitTime + secondWaveDelay + local firstWarningStart = math.max(0, firstHitTime - warningTime) + local secondWarningStart = math.max(firstHitTime + 0.1, secondHitTime - warningTime) + caster:AddNewModifier(caster, self, modifier_boss_nevermore_coil_beam_lock.name, {duration = secondHitTime + 0.35}) + local warnings = {} + local function destroyWarningPair(____, pair) + ParticleManager:DestroyParticle(pair[1], true) + ParticleManager:ReleaseParticleIndex(pair[1]) + ParticleManager:DestroyParticle(pair[2], true) + ParticleManager:ReleaseParticleIndex(pair[2]) + end + local function clearWarning(____, idx) + local pair = warnings[idx + 1] + if not pair then + return + end + destroyWarningPair(nil, pair) + warnings[idx + 1] = nil + end + Timers:CreateTimer( + firstWarningStart, + function() + if not caster or caster:IsNull() or not caster:IsAlive() then + return nil + end + do + local i = 0 + while i < #slots do + do + if not slots[i + 1].hitsOnFirstWave then + goto __continue25 + end + warnings[i + 1] = self:createPulseWarningColored(slots[i + 1].position, radius, true) + end + ::__continue25:: + i = i + 1 + end + end + return nil + end + ) + Timers:CreateTimer( + firstHitTime, + function() + if not caster or caster:IsNull() or not caster:IsAlive() then + return nil + end + local impactPhase = self:getBossPhase() + local baseDamage = caster:GetAttackDamage() + local bonusDamagePerStack = self:GetSpecialValueFor("coil_stack_bonus_damage") + local damageMultiplier = 1 + (impactPhase - 1) * 0.3 + do + local i = 0 + while i < #slots do + do + if not slots[i + 1].hitsOnFirstWave then + goto __continue29 + end + clearWarning(nil, i) + self:applyPulseDamage( + slots[i + 1].position, + radius, + baseDamage, + bonusDamagePerStack, + damageMultiplier + ) + end + ::__continue29:: + i = i + 1 + end + end + return nil + end + ) + Timers:CreateTimer( + secondWarningStart, + function() + if not caster or caster:IsNull() or not caster:IsAlive() then + return nil + end + do + local i = 0 + while i < #slots do + do + if slots[i + 1].hitsOnFirstWave then + goto __continue33 + end + if warnings[i + 1] then + goto __continue33 + end + warnings[i + 1] = self:createPulseWarningColored(slots[i + 1].position, radius, false) + end + ::__continue33:: + i = i + 1 + end + end + return nil + end + ) + Timers:CreateTimer( + secondHitTime, + function() + if not caster or caster:IsNull() or not caster:IsAlive() then + return nil + end + local impactPhase = self:getBossPhase() + local baseDamage = caster:GetAttackDamage() + local bonusDamagePerStack = self:GetSpecialValueFor("coil_stack_bonus_damage") + local damageMultiplier = 1 + (impactPhase - 1) * 0.3 + do + local i = 0 + while i < #slots do + do + if slots[i + 1].hitsOnFirstWave then + goto __continue38 + end + clearWarning(nil, i) + self:applyPulseDamage( + slots[i + 1].position, + radius, + baseDamage, + bonusDamagePerStack, + damageMultiplier + ) + end + ::__continue38:: + i = i + 1 + end + end + return nil + end + ) +end +function boss_nevermore_coil_beam.prototype.createPulseWarningColored(self, pulsePoint, radius, firstWaveStrike) + local caster = self:GetCaster() + local warningParticle = ParticleManager:CreateParticle("particles/darkmoon_creep_warning.vpcf", PATTACH_CUSTOMORIGIN, caster) + local aoeParticle = ParticleManager:CreateParticle("particles/econ/events/darkmoon_2017/darkmoon_generic_aoe.vpcf", PATTACH_CUSTOMORIGIN, caster) + ParticleManager:SetParticleControl(warningParticle, 0, pulsePoint) + ParticleManager:SetParticleControl( + warningParticle, + 1, + Vector(radius, 100, 100) + ) + ParticleManager:SetParticleControl(aoeParticle, 0, pulsePoint) + ParticleManager:SetParticleControl( + aoeParticle, + 1, + Vector(radius, 0, 0) + ) + ParticleManager:SetParticleControl( + aoeParticle, + 2, + Vector(6, 0, 1) + ) + local rgb = firstWaveStrike and Vector(240, 40, 40) or Vector(70, 170, 255) + ParticleManager:SetParticleControl(aoeParticle, 3, rgb) + ParticleManager:SetParticleControl(aoeParticle, 4, pulsePoint) + return {warningParticle, aoeParticle} +end +function boss_nevermore_coil_beam.prototype.applyPulseDamage(self, pulsePoint, radius, baseDamage, bonusDamagePerStack, damageMultiplier) + if not IsServer() then + return + end + local caster = self:GetCaster() + if not caster or caster:IsNull() or not caster:IsAlive() then + return + end + local enemies = FindUnitsInRadius( + caster:GetTeamNumber(), + pulsePoint, + nil, + radius, + DOTA_UNIT_TARGET_TEAM_ENEMY, + bit.bor(DOTA_UNIT_TARGET_HERO, DOTA_UNIT_TARGET_BASIC), + DOTA_UNIT_TARGET_FLAG_NONE, + FIND_ANY_ORDER, + false + ) + for ____, enemy in ipairs(enemies) do + local coilDebuff = enemy:FindModifierByName(modifier_boss_nevermore_coil_debuff.name) + local stacks = coilDebuff and coilDebuff:GetStackCount() or 0 + local damage = (baseDamage + stacks * bonusDamagePerStack) * damageMultiplier + ApplyDamage({ + victim = enemy, + attacker = caster, + damage = damage, + damage_type = DAMAGE_TYPE_MAGICAL, + ability = self + }) + local updatedDebuff = enemy:AddNewModifier(caster, self, modifier_boss_nevermore_coil_debuff.name, {}) + if updatedDebuff ~= nil then + if stacks > 0 then + updatedDebuff:SetStackCount(stacks + 1) + else + updatedDebuff:SetStackCount(1) + end + end + end + EmitSoundOnLocationWithCaster(pulsePoint, "Hero_Nevermore.Shadowraze", caster) + local hitParticle = ParticleManager:CreateParticle("particles/units/heroes/hero_nevermore/nevermore_shadowraze.vpcf", PATTACH_WORLDORIGIN, nil) + ParticleManager:SetParticleControl(hitParticle, 0, pulsePoint) + ParticleManager:SetParticleControl( + hitParticle, + 1, + Vector(radius, 1, 1) + ) + ParticleManager:ReleaseParticleIndex(hitParticle) +end +boss_nevermore_coil_beam.DEFAULT_START_DELAY = 0.9 +boss_nevermore_coil_beam.DEFAULT_SECOND_WAVE_DELAY = 1 +boss_nevermore_coil_beam.DEFAULT_WARNING_TIME = 0.85 +boss_nevermore_coil_beam.DEFAULT_SLOT_COUNT = 12 +boss_nevermore_coil_beam.DEFAULT_SLOT_PHASE_BONUS = 2 +boss_nevermore_coil_beam.DEFAULT_BEAM_START = 140 +boss_nevermore_coil_beam.DEFAULT_BEAM_STEP = 195 +boss_nevermore_coil_beam = __TS__Decorate( + boss_nevermore_coil_beam, + boss_nevermore_coil_beam, + {registerAbility(nil)}, + {kind = "class", name = "boss_nevermore_coil_beam"} +) +____exports.boss_nevermore_coil_beam = boss_nevermore_coil_beam +modifier_boss_nevermore_coil_beam_lock = __TS__Class() +modifier_boss_nevermore_coil_beam_lock.name = "modifier_boss_nevermore_coil_beam_lock" +modifier_boss_nevermore_coil_beam_lock.____file_path = "scripts/vscripts/abilities/creep/boss_nevermore_coil_beam.lua" +__TS__ClassExtends(modifier_boss_nevermore_coil_beam_lock, BaseModifier) +function modifier_boss_nevermore_coil_beam_lock.prototype.IsHidden(self) + return true +end +function modifier_boss_nevermore_coil_beam_lock.prototype.IsPurgable(self) + return false +end +function modifier_boss_nevermore_coil_beam_lock.prototype.CheckState(self) + return {[MODIFIER_STATE_DISARMED] = true} +end +modifier_boss_nevermore_coil_beam_lock = __TS__Decorate( + modifier_boss_nevermore_coil_beam_lock, + modifier_boss_nevermore_coil_beam_lock, + {registerModifier(nil)}, + {kind = "class", name = "modifier_boss_nevermore_coil_beam_lock"} +) +return ____exports diff --git a/scripts/vscripts/abilities/creep/boss_nevermore_coil_wave.lua b/scripts/vscripts/abilities/creep/boss_nevermore_coil_wave.lua new file mode 100644 index 0000000..2d21eae --- /dev/null +++ b/scripts/vscripts/abilities/creep/boss_nevermore_coil_wave.lua @@ -0,0 +1,353 @@ +local ____lualib = require("lualib_bundle") +local __TS__Class = ____lualib.__TS__Class +local __TS__ClassExtends = ____lualib.__TS__ClassExtends +local __TS__Decorate = ____lualib.__TS__Decorate +local ____exports = {} +local modifier_boss_nevermore_coil_wave_lock +local ____dota_ts_adapter = require("lib.dota_ts_adapter") +local BaseAbility = ____dota_ts_adapter.BaseAbility +local BaseModifier = ____dota_ts_adapter.BaseModifier +local registerAbility = ____dota_ts_adapter.registerAbility +local registerModifier = ____dota_ts_adapter.registerModifier +local ____modifier_boss_nevermore_coil_debuff = require("abilities.creep.modifier_boss_nevermore_coil_debuff") +local modifier_boss_nevermore_coil_debuff = ____modifier_boss_nevermore_coil_debuff.modifier_boss_nevermore_coil_debuff +____exports.boss_nevermore_coil_wave = __TS__Class() +local boss_nevermore_coil_wave = ____exports.boss_nevermore_coil_wave +boss_nevermore_coil_wave.name = "boss_nevermore_coil_wave" +boss_nevermore_coil_wave.____file_path = "scripts/vscripts/abilities/creep/boss_nevermore_coil_wave.lua" +__TS__ClassExtends(boss_nevermore_coil_wave, BaseAbility) +function boss_nevermore_coil_wave.prototype.Precache(self, context) + PrecacheResource("particle", "particles/units/heroes/hero_nevermore/nevermore_shadowraze.vpcf", context) + PrecacheResource("particle", "particles/darkmoon_creep_warning.vpcf", context) + PrecacheResource("particle", "particles/econ/events/darkmoon_2017/darkmoon_generic_aoe.vpcf", context) + PrecacheResource("soundfile", "sounds/units/heroes/nevermore/shadowraze.vsnd", context) +end +function boss_nevermore_coil_wave.prototype.GetAOERadius(self) + local r = self:GetSpecialValueFor("radius") + local forward = self:getSpecialOrDefault("lane_forward_dist", ____exports.boss_nevermore_coil_wave.DEFAULT_FORWARD_DIST) + return forward + r * 2 +end +function boss_nevermore_coil_wave.prototype.getSpecialOrDefault(self, name, fallback) + local value = self:GetSpecialValueFor(name) + if not value or value <= 0 then + return fallback + end + return value +end +function boss_nevermore_coil_wave.prototype.getBossPhase(self) + local caster = self:GetCaster() + if not caster or caster:IsNull() then + return 1 + end + local hp = caster:GetHealthPercent() + if hp <= 25 then + return 4 + end + if hp <= 50 then + return 3 + end + if hp <= 75 then + return 2 + end + return 1 +end +function boss_nevermore_coil_wave.prototype.buildGapLaneSlots(self, origin, direction, phase) + local radius = self:GetSpecialValueFor("radius") + local baseSlots = math.floor(self:getSpecialOrDefault("lane_slot_count", ____exports.boss_nevermore_coil_wave.DEFAULT_SLOT_COUNT)) + local slotCount = baseSlots + (phase - 1) + local forwardDist = self:getSpecialOrDefault("lane_forward_dist", ____exports.boss_nevermore_coil_wave.DEFAULT_FORWARD_DIST) + (phase - 1) * 35 + local spacing = math.max( + radius * self:getSpecialOrDefault("lane_slot_spacing", ____exports.boss_nevermore_coil_wave.DEFAULT_SLOT_SPACING_FACTOR), + 185 + ) + local dir2d = direction:Normalized() + dir2d.z = 0 + local right = Vector(-dir2d.y, dir2d.x, 0):Normalized() + local centerRow = GetGroundPosition(origin + dir2d * forwardDist, nil) + local firstWaveHitsEvenIndex = RandomInt(0, 1) == 0 + local slots = {} + local mid = (slotCount - 1) / 2 + do + local i = 0 + while i < slotCount do + local lateral = (i - mid) * spacing + local pos = GetGroundPosition(centerRow + right * lateral, nil) + local isEven = i % 2 == 0 + local ____firstWaveHitsEvenIndex_0 + if firstWaveHitsEvenIndex then + ____firstWaveHitsEvenIndex_0 = isEven + else + ____firstWaveHitsEvenIndex_0 = not isEven + end + local hitsOnFirstWave = ____firstWaveHitsEvenIndex_0 + slots[#slots + 1] = {position = pos, hitsOnFirstWave = hitsOnFirstWave} + i = i + 1 + end + end + return slots +end +function boss_nevermore_coil_wave.prototype.OnSpellStart(self) + if not IsServer() then + return + end + local caster = self:GetCaster() + local origin = caster:GetAbsOrigin() + local point = self:GetCursorPosition() + local toPoint = point - origin + local direction = toPoint:Length2D() < 1 and caster:GetForwardVector() or toPoint:Normalized() + local phase = self:getBossPhase() + self:spawnGapLanePattern(origin, direction, phase) +end +function boss_nevermore_coil_wave.prototype.spawnGapLanePattern(self, origin, direction, phase) + local caster = self:GetCaster() + local slots = self:buildGapLaneSlots(origin, direction, phase) + if #slots == 0 then + return + end + local radius = self:GetSpecialValueFor("radius") + local startDelay = self:getSpecialOrDefault("start_delay", ____exports.boss_nevermore_coil_wave.DEFAULT_START_DELAY) + local warningTime = self:getSpecialOrDefault("precast_warning_time", ____exports.boss_nevermore_coil_wave.DEFAULT_WARNING_TIME) + local secondDelayRaw = self:GetSpecialValueFor("second_wave_delay") + local secondWaveDelay = secondDelayRaw > 0 and secondDelayRaw or ____exports.boss_nevermore_coil_wave.DEFAULT_SECOND_WAVE_DELAY + local firstHitTime = startDelay + local secondHitTime = firstHitTime + secondWaveDelay + local firstWarningStart = math.max(0, firstHitTime - warningTime) + local secondWarningStart = math.max(firstHitTime + 0.1, secondHitTime - warningTime) + caster:AddNewModifier(caster, self, modifier_boss_nevermore_coil_wave_lock.name, {duration = secondHitTime + 0.35}) + local warnings = {} + local function destroyWarningPair(____, pair) + ParticleManager:DestroyParticle(pair[1], true) + ParticleManager:ReleaseParticleIndex(pair[1]) + ParticleManager:DestroyParticle(pair[2], true) + ParticleManager:ReleaseParticleIndex(pair[2]) + end + local function clearWarning(____, idx) + local pair = warnings[idx + 1] + if not pair then + return + end + destroyWarningPair(nil, pair) + warnings[idx + 1] = nil + end + Timers:CreateTimer( + firstWarningStart, + function() + if not caster or caster:IsNull() or not caster:IsAlive() then + return nil + end + do + local i = 0 + while i < #slots do + do + if not slots[i + 1].hitsOnFirstWave then + goto __continue22 + end + warnings[i + 1] = self:createPulseWarningColored(slots[i + 1].position, radius, true) + end + ::__continue22:: + i = i + 1 + end + end + return nil + end + ) + Timers:CreateTimer( + firstHitTime, + function() + if not caster or caster:IsNull() or not caster:IsAlive() then + return nil + end + local impactPhase = self:getBossPhase() + local baseDamage = caster:GetAttackDamage() + local bonusDamagePerStack = self:GetSpecialValueFor("coil_stack_bonus_damage") + local damageMultiplier = 1 + (impactPhase - 1) * 0.3 + do + local i = 0 + while i < #slots do + do + if not slots[i + 1].hitsOnFirstWave then + goto __continue26 + end + clearWarning(nil, i) + self:applyPulseDamage( + slots[i + 1].position, + radius, + baseDamage, + bonusDamagePerStack, + damageMultiplier + ) + end + ::__continue26:: + i = i + 1 + end + end + return nil + end + ) + Timers:CreateTimer( + secondWarningStart, + function() + if not caster or caster:IsNull() or not caster:IsAlive() then + return nil + end + do + local i = 0 + while i < #slots do + do + if slots[i + 1].hitsOnFirstWave then + goto __continue30 + end + if warnings[i + 1] then + goto __continue30 + end + warnings[i + 1] = self:createPulseWarningColored(slots[i + 1].position, radius, false) + end + ::__continue30:: + i = i + 1 + end + end + return nil + end + ) + Timers:CreateTimer( + secondHitTime, + function() + if not caster or caster:IsNull() or not caster:IsAlive() then + return nil + end + local impactPhase = self:getBossPhase() + local baseDamage = caster:GetAttackDamage() + local bonusDamagePerStack = self:GetSpecialValueFor("coil_stack_bonus_damage") + local damageMultiplier = 1 + (impactPhase - 1) * 0.3 + do + local i = 0 + while i < #slots do + do + if slots[i + 1].hitsOnFirstWave then + goto __continue35 + end + clearWarning(nil, i) + self:applyPulseDamage( + slots[i + 1].position, + radius, + baseDamage, + bonusDamagePerStack, + damageMultiplier + ) + end + ::__continue35:: + i = i + 1 + end + end + return nil + end + ) +end +function boss_nevermore_coil_wave.prototype.createPulseWarningColored(self, pulsePoint, radius, firstWaveStrike) + local caster = self:GetCaster() + local warningParticle = ParticleManager:CreateParticle("particles/darkmoon_creep_warning.vpcf", PATTACH_CUSTOMORIGIN, caster) + local aoeParticle = ParticleManager:CreateParticle("particles/econ/events/darkmoon_2017/darkmoon_generic_aoe.vpcf", PATTACH_CUSTOMORIGIN, caster) + ParticleManager:SetParticleControl(warningParticle, 0, pulsePoint) + ParticleManager:SetParticleControl( + warningParticle, + 1, + Vector(radius, 100, 100) + ) + ParticleManager:SetParticleControl(aoeParticle, 0, pulsePoint) + ParticleManager:SetParticleControl( + aoeParticle, + 1, + Vector(radius, 0, 0) + ) + ParticleManager:SetParticleControl( + aoeParticle, + 2, + Vector(6, 0, 1) + ) + local rgb = firstWaveStrike and Vector(240, 40, 40) or Vector(70, 170, 255) + ParticleManager:SetParticleControl(aoeParticle, 3, rgb) + ParticleManager:SetParticleControl(aoeParticle, 4, pulsePoint) + return {warningParticle, aoeParticle} +end +function boss_nevermore_coil_wave.prototype.applyPulseDamage(self, pulsePoint, radius, baseDamage, bonusDamagePerStack, damageMultiplier) + if not IsServer() then + return + end + local caster = self:GetCaster() + if not caster or caster:IsNull() or not caster:IsAlive() then + return + end + local enemies = FindUnitsInRadius( + caster:GetTeamNumber(), + pulsePoint, + nil, + radius, + DOTA_UNIT_TARGET_TEAM_ENEMY, + bit.bor(DOTA_UNIT_TARGET_HERO, DOTA_UNIT_TARGET_BASIC), + DOTA_UNIT_TARGET_FLAG_NONE, + FIND_ANY_ORDER, + false + ) + for ____, enemy in ipairs(enemies) do + local coilDebuff = enemy:FindModifierByName(modifier_boss_nevermore_coil_debuff.name) + local stacks = coilDebuff and coilDebuff:GetStackCount() or 0 + local damage = (baseDamage + stacks * bonusDamagePerStack) * damageMultiplier + ApplyDamage({ + victim = enemy, + attacker = caster, + damage = damage, + damage_type = DAMAGE_TYPE_MAGICAL, + ability = self + }) + local updatedDebuff = enemy:AddNewModifier(caster, self, modifier_boss_nevermore_coil_debuff.name, {}) + if updatedDebuff ~= nil then + if stacks > 0 then + updatedDebuff:SetStackCount(stacks + 1) + else + updatedDebuff:SetStackCount(1) + end + end + end + EmitSoundOnLocationWithCaster(pulsePoint, "Hero_Nevermore.Shadowraze", caster) + local hitParticle = ParticleManager:CreateParticle("particles/units/heroes/hero_nevermore/nevermore_shadowraze.vpcf", PATTACH_WORLDORIGIN, nil) + ParticleManager:SetParticleControl(hitParticle, 0, pulsePoint) + ParticleManager:SetParticleControl( + hitParticle, + 1, + Vector(radius, 1, 1) + ) + ParticleManager:ReleaseParticleIndex(hitParticle) +end +boss_nevermore_coil_wave.DEFAULT_START_DELAY = 0.9 +boss_nevermore_coil_wave.DEFAULT_SECOND_WAVE_DELAY = 1 +boss_nevermore_coil_wave.DEFAULT_WARNING_TIME = 0.85 +boss_nevermore_coil_wave.DEFAULT_SLOT_COUNT = 5 +boss_nevermore_coil_wave.DEFAULT_FORWARD_DIST = 360 +boss_nevermore_coil_wave.DEFAULT_SLOT_SPACING_FACTOR = 1.32 +boss_nevermore_coil_wave = __TS__Decorate( + boss_nevermore_coil_wave, + boss_nevermore_coil_wave, + {registerAbility(nil)}, + {kind = "class", name = "boss_nevermore_coil_wave"} +) +____exports.boss_nevermore_coil_wave = boss_nevermore_coil_wave +modifier_boss_nevermore_coil_wave_lock = __TS__Class() +modifier_boss_nevermore_coil_wave_lock.name = "modifier_boss_nevermore_coil_wave_lock" +modifier_boss_nevermore_coil_wave_lock.____file_path = "scripts/vscripts/abilities/creep/boss_nevermore_coil_wave.lua" +__TS__ClassExtends(modifier_boss_nevermore_coil_wave_lock, BaseModifier) +function modifier_boss_nevermore_coil_wave_lock.prototype.IsHidden(self) + return true +end +function modifier_boss_nevermore_coil_wave_lock.prototype.IsPurgable(self) + return false +end +function modifier_boss_nevermore_coil_wave_lock.prototype.CheckState(self) + return {[MODIFIER_STATE_DISARMED] = true} +end +modifier_boss_nevermore_coil_wave_lock = __TS__Decorate( + modifier_boss_nevermore_coil_wave_lock, + modifier_boss_nevermore_coil_wave_lock, + {registerModifier(nil)}, + {kind = "class", name = "modifier_boss_nevermore_coil_wave_lock"} +) +return ____exports diff --git a/scripts/vscripts/abilities/creep/boss_nevermore_hub_crossburst.lua b/scripts/vscripts/abilities/creep/boss_nevermore_hub_crossburst.lua new file mode 100644 index 0000000..e9b060f --- /dev/null +++ b/scripts/vscripts/abilities/creep/boss_nevermore_hub_crossburst.lua @@ -0,0 +1,284 @@ +local ____lualib = require("lualib_bundle") +local __TS__Class = ____lualib.__TS__Class +local __TS__ClassExtends = ____lualib.__TS__ClassExtends +local __TS__Decorate = ____lualib.__TS__Decorate +local ____exports = {} +local modifier_boss_nevermore_hub_crossburst_lock +local ____dota_ts_adapter = require("lib.dota_ts_adapter") +local BaseAbility = ____dota_ts_adapter.BaseAbility +local BaseModifier = ____dota_ts_adapter.BaseModifier +local registerAbility = ____dota_ts_adapter.registerAbility +local registerModifier = ____dota_ts_adapter.registerModifier +local ____modifier_boss_nevermore_coil_debuff = require("abilities.creep.modifier_boss_nevermore_coil_debuff") +local modifier_boss_nevermore_coil_debuff = ____modifier_boss_nevermore_coil_debuff.modifier_boss_nevermore_coil_debuff +--- Случайная точка-хаб: от неё волны взрывов по 4 лучам (плюс или крест). +____exports.boss_nevermore_hub_crossburst = __TS__Class() +local boss_nevermore_hub_crossburst = ____exports.boss_nevermore_hub_crossburst +boss_nevermore_hub_crossburst.name = "boss_nevermore_hub_crossburst" +boss_nevermore_hub_crossburst.____file_path = "scripts/vscripts/abilities/creep/boss_nevermore_hub_crossburst.lua" +__TS__ClassExtends(boss_nevermore_hub_crossburst, BaseAbility) +function boss_nevermore_hub_crossburst.prototype.Precache(self, context) + PrecacheResource("particle", "particles/units/heroes/hero_nevermore/nevermore_shadowraze.vpcf", context) + PrecacheResource("particle", "particles/darkmoon_creep_warning.vpcf", context) + PrecacheResource("particle", "particles/econ/events/darkmoon_2017/darkmoon_generic_aoe.vpcf", context) + PrecacheResource("soundfile", "sounds/units/heroes/nevermore/shadowraze.vsnd", context) +end +function boss_nevermore_hub_crossburst.prototype.GetAOERadius(self) + local pick = self:getSpecialOrDefault("spawn_pick_radius", ____exports.boss_nevermore_hub_crossburst.DEFAULT_PICK_RADIUS) + local start = self:getSpecialOrDefault("ring_start_dist", ____exports.boss_nevermore_hub_crossburst.DEFAULT_RING_START) + local step = self:getSpecialOrDefault("ring_step", ____exports.boss_nevermore_hub_crossburst.DEFAULT_RING_STEP) + local n = self:getRingCountForPhase(self:getBossPhase()) + local reach = start + (n - 1) * step + self:GetSpecialValueFor("radius") + return pick + reach +end +function boss_nevermore_hub_crossburst.prototype.getSpecialOrDefault(self, name, fallback) + local v = self:GetSpecialValueFor(name) + if not v or v <= 0 then + return fallback + end + return v +end +function boss_nevermore_hub_crossburst.prototype.getBossPhase(self) + local c = self:GetCaster() + if not c or c:IsNull() then + return 1 + end + local hp = c:GetHealthPercent() + if hp <= 25 then + return 4 + end + if hp <= 50 then + return 3 + end + if hp <= 75 then + return 2 + end + return 1 +end +function boss_nevermore_hub_crossburst.prototype.getRingCountForPhase(self, phase) + local base = math.floor(self:getSpecialOrDefault("ring_count", ____exports.boss_nevermore_hub_crossburst.DEFAULT_RING_COUNT)) + local perPhase = math.floor(self:getSpecialOrDefault("ring_count_phase_bonus", ____exports.boss_nevermore_hub_crossburst.DEFAULT_RING_COUNT_PHASE_BONUS)) + return math.max(1, base + (phase - 1) * perPhase) +end +function boss_nevermore_hub_crossburst.prototype.pickRandomHub(self, groundOrigin) + local maxR = self:getSpecialOrDefault("spawn_pick_radius", ____exports.boss_nevermore_hub_crossburst.DEFAULT_PICK_RADIUS) + local ang = RandomFloat(0, 360) + local dist = maxR * math.sqrt(RandomFloat(0.001, 1)) + local rad = ang * math.pi / 180 + local dx = math.cos(rad) * dist + local dy = math.sin(rad) * dist + return GetGroundPosition( + groundOrigin + Vector(dx, dy, 0), + nil + ) +end +function boss_nevermore_hub_crossburst.prototype.getDirections(self, pattern) + if pattern == "plus" then + return { + Vector(1, 0, 0), + Vector(-1, 0, 0), + Vector(0, 1, 0), + Vector(0, -1, 0) + } + end + local s = 1 / math.sqrt(2) + return { + Vector(s, s, 0), + Vector(s, -s, 0), + Vector(-s, s, 0), + Vector(-s, -s, 0) + } +end +function boss_nevermore_hub_crossburst.prototype.OnSpellStart(self) + if not IsServer() then + return + end + local caster = self:GetCaster() + if not caster or caster:IsNull() or not caster:IsAlive() then + return + end + local bossGround = GetGroundPosition( + caster:GetAbsOrigin(), + nil + ) + local hub = self:pickRandomHub(bossGround) + local pattern = RandomInt(0, 1) == 0 and "plus" or "cross" + local radius = self:GetSpecialValueFor("radius") + local rEff = radius > 0 and radius or ____exports.boss_nevermore_hub_crossburst.DEFAULT_RADIUS + local phaseNow = self:getBossPhase() + local ringCount = self:getRingCountForPhase(phaseNow) + local ringStep = self:getSpecialOrDefault("ring_step", ____exports.boss_nevermore_hub_crossburst.DEFAULT_RING_STEP) + local ringStart = self:getSpecialOrDefault("ring_start_dist", ____exports.boss_nevermore_hub_crossburst.DEFAULT_RING_START) + local baseInterval = self:getSpecialOrDefault("ring_interval", ____exports.boss_nevermore_hub_crossburst.DEFAULT_RING_INTERVAL) + --- Ниже фаза — чуть быстрее волны (до ~−12% на фазе 4). + local ringInterval = baseInterval * math.max(0.72, 1 - (phaseNow - 1) * 0.06) + local firstDelay = self:getSpecialOrDefault("first_ring_delay", ____exports.boss_nevermore_hub_crossburst.DEFAULT_FIRST_DELAY) + local warningTime = self:getSpecialOrDefault("precast_warning_time", ____exports.boss_nevermore_hub_crossburst.DEFAULT_WARNING_TIME) + local dirs = self:getDirections(pattern) + local function ringPositionsForIndex(____, ringIdx) + local along = ringStart + ringIdx * ringStep + local out = {} + for ____, d in ipairs(dirs) do + local scaled = Vector(d.x * along, d.y * along, 0) + out[#out + 1] = GetGroundPosition(hub + scaled, nil) + end + return out + end + local lastHitTime = firstDelay + (ringCount - 1) * ringInterval + caster:AddNewModifier(caster, self, modifier_boss_nevermore_hub_crossburst_lock.name, {duration = lastHitTime + 0.5}) + do + local r = 0 + while r < ringCount do + local hitTime = firstDelay + r * ringInterval + local warnT = math.max(0, hitTime - warningTime) + local positions = ringPositionsForIndex(nil, r) + local isRedWave = r % 2 == 0 + Timers:CreateTimer( + warnT, + function() + if not caster or caster:IsNull() or not caster:IsAlive() then + return nil + end + local ____pairs = {} + for ____, p in ipairs(positions) do + ____pairs[#____pairs + 1] = self:createWarningPair(p, rEff, isRedWave) + end + Timers:CreateTimer( + hitTime - warnT, + function() + if not caster or caster:IsNull() or not caster:IsAlive() then + return nil + end + for ____, pair in ipairs(____pairs) do + do + if not pair then + goto __continue28 + end + ParticleManager:DestroyParticle(pair[1], true) + ParticleManager:ReleaseParticleIndex(pair[1]) + ParticleManager:DestroyParticle(pair[2], true) + ParticleManager:ReleaseParticleIndex(pair[2]) + end + ::__continue28:: + end + for ____, p in ipairs(positions) do + self:applyRingDamage(p, rEff) + end + return nil + end + ) + return nil + end + ) + r = r + 1 + end + end +end +function boss_nevermore_hub_crossburst.prototype.createWarningPair(self, pos, rad, firstStyle) + local caster = self:GetCaster() + local warningParticle = ParticleManager:CreateParticle("particles/darkmoon_creep_warning.vpcf", PATTACH_CUSTOMORIGIN, caster) + local aoeParticle = ParticleManager:CreateParticle("particles/econ/events/darkmoon_2017/darkmoon_generic_aoe.vpcf", PATTACH_CUSTOMORIGIN, caster) + ParticleManager:SetParticleControl(warningParticle, 0, pos) + ParticleManager:SetParticleControl( + warningParticle, + 1, + Vector(rad, 100, 100) + ) + ParticleManager:SetParticleControl(aoeParticle, 0, pos) + ParticleManager:SetParticleControl( + aoeParticle, + 1, + Vector(rad, 0, 0) + ) + ParticleManager:SetParticleControl( + aoeParticle, + 2, + Vector(6, 0, 1) + ) + local rgb = firstStyle and Vector(230, 80, 40) or Vector(180, 80, 230) + ParticleManager:SetParticleControl(aoeParticle, 3, rgb) + ParticleManager:SetParticleControl(aoeParticle, 4, pos) + return {warningParticle, aoeParticle} +end +function boss_nevermore_hub_crossburst.prototype.applyRingDamage(self, pulsePoint, rad) + local caster = self:GetCaster() + if not caster or caster:IsNull() or not caster:IsAlive() then + return + end + local phase = self:getBossPhase() + local baseDamage = caster:GetAttackDamage() + local bonusDamagePerStack = self:GetSpecialValueFor("coil_stack_bonus_damage") + local damageMultiplier = 1 + (phase - 1) * 0.3 + local enemies = FindUnitsInRadius( + caster:GetTeamNumber(), + pulsePoint, + nil, + rad, + DOTA_UNIT_TARGET_TEAM_ENEMY, + bit.bor(DOTA_UNIT_TARGET_HERO, DOTA_UNIT_TARGET_BASIC), + DOTA_UNIT_TARGET_FLAG_NONE, + FIND_ANY_ORDER, + false + ) + for ____, enemy in ipairs(enemies) do + local coilDebuff = enemy:FindModifierByName(modifier_boss_nevermore_coil_debuff.name) + local stacks = coilDebuff and coilDebuff:GetStackCount() or 0 + local damage = (baseDamage + stacks * bonusDamagePerStack) * damageMultiplier * 0.85 + ApplyDamage({ + victim = enemy, + attacker = caster, + damage = damage, + damage_type = DAMAGE_TYPE_MAGICAL, + ability = self + }) + local updatedDebuff = enemy:AddNewModifier(caster, self, modifier_boss_nevermore_coil_debuff.name, {}) + if updatedDebuff ~= nil then + updatedDebuff:SetStackCount(stacks > 0 and stacks + 1 or 1) + end + end + EmitSoundOnLocationWithCaster(pulsePoint, "Hero_Nevermore.Shadowraze", caster) + local hitParticle = ParticleManager:CreateParticle("particles/units/heroes/hero_nevermore/nevermore_shadowraze.vpcf", PATTACH_WORLDORIGIN, nil) + ParticleManager:SetParticleControl(hitParticle, 0, pulsePoint) + ParticleManager:SetParticleControl( + hitParticle, + 1, + Vector(rad, 1, 1) + ) + ParticleManager:ReleaseParticleIndex(hitParticle) +end +boss_nevermore_hub_crossburst.DEFAULT_PICK_RADIUS = 1500 +boss_nevermore_hub_crossburst.DEFAULT_RADIUS = 165 +boss_nevermore_hub_crossburst.DEFAULT_RING_COUNT = 7 +boss_nevermore_hub_crossburst.DEFAULT_RING_COUNT_PHASE_BONUS = 1 +boss_nevermore_hub_crossburst.DEFAULT_RING_STEP = 190 +boss_nevermore_hub_crossburst.DEFAULT_RING_START = 90 +boss_nevermore_hub_crossburst.DEFAULT_RING_INTERVAL = 0.52 +boss_nevermore_hub_crossburst.DEFAULT_FIRST_DELAY = 0.55 +boss_nevermore_hub_crossburst.DEFAULT_WARNING_TIME = 0.82 +boss_nevermore_hub_crossburst = __TS__Decorate( + boss_nevermore_hub_crossburst, + boss_nevermore_hub_crossburst, + {registerAbility(nil)}, + {kind = "class", name = "boss_nevermore_hub_crossburst"} +) +____exports.boss_nevermore_hub_crossburst = boss_nevermore_hub_crossburst +modifier_boss_nevermore_hub_crossburst_lock = __TS__Class() +modifier_boss_nevermore_hub_crossburst_lock.name = "modifier_boss_nevermore_hub_crossburst_lock" +modifier_boss_nevermore_hub_crossburst_lock.____file_path = "scripts/vscripts/abilities/creep/boss_nevermore_hub_crossburst.lua" +__TS__ClassExtends(modifier_boss_nevermore_hub_crossburst_lock, BaseModifier) +function modifier_boss_nevermore_hub_crossburst_lock.prototype.IsHidden(self) + return true +end +function modifier_boss_nevermore_hub_crossburst_lock.prototype.IsPurgable(self) + return false +end +function modifier_boss_nevermore_hub_crossburst_lock.prototype.CheckState(self) + return {[MODIFIER_STATE_DISARMED] = true} +end +modifier_boss_nevermore_hub_crossburst_lock = __TS__Decorate( + modifier_boss_nevermore_hub_crossburst_lock, + modifier_boss_nevermore_hub_crossburst_lock, + {registerModifier(nil)}, + {kind = "class", name = "modifier_boss_nevermore_hub_crossburst_lock"} +) +return ____exports diff --git a/scripts/vscripts/abilities/creep/boss_nevermore_requiem_barrage.lua b/scripts/vscripts/abilities/creep/boss_nevermore_requiem_barrage.lua new file mode 100644 index 0000000..798ce29 --- /dev/null +++ b/scripts/vscripts/abilities/creep/boss_nevermore_requiem_barrage.lua @@ -0,0 +1,494 @@ +local ____lualib = require("lualib_bundle") +local __TS__Class = ____lualib.__TS__Class +local __TS__ClassExtends = ____lualib.__TS__ClassExtends +local __TS__ArrayMap = ____lualib.__TS__ArrayMap +local __TS__ArrayPush = ____lualib.__TS__ArrayPush +local __TS__SparseArrayNew = ____lualib.__TS__SparseArrayNew +local __TS__SparseArrayPush = ____lualib.__TS__SparseArrayPush +local __TS__SparseArraySpread = ____lualib.__TS__SparseArraySpread +local __TS__Decorate = ____lualib.__TS__Decorate +local ____exports = {} +local modifier_boss_nevermore_requiem_barrage_casting +local ____nevermore_boss_requiem_bridge = require("ai.nevermore_boss_requiem_bridge") +local nevermoreIncrementRequiemCastCount = ____nevermore_boss_requiem_bridge.nevermoreIncrementRequiemCastCount +local ____dota_ts_adapter = require("lib.dota_ts_adapter") +local BaseAbility = ____dota_ts_adapter.BaseAbility +local BaseModifier = ____dota_ts_adapter.BaseModifier +local registerAbility = ____dota_ts_adapter.registerAbility +local registerModifier = ____dota_ts_adapter.registerModifier +local ____modifier_boss_nevermore_requiem_magic_resist_debuff = require("abilities.creep.modifier_boss_nevermore_requiem_magic_resist_debuff") +local modifier_boss_nevermore_requiem_magic_resist_debuff = ____modifier_boss_nevermore_requiem_magic_resist_debuff.modifier_boss_nevermore_requiem_magic_resist_debuff +--- Глобальный множитель урона залпа (подогнан под KV-бафф ~+60%). +local REQUIEM_BARRAGE_DAMAGE_MULT = 1.6 +--- Доп. дальность волны за стадию HP босса (согласовано с KV wave_distance ×1.6). +local REQUIEM_PHASE_DISTANCE_BONUS = 192 +____exports.boss_nevermore_requiem_barrage = __TS__Class() +local boss_nevermore_requiem_barrage = ____exports.boss_nevermore_requiem_barrage +boss_nevermore_requiem_barrage.name = "boss_nevermore_requiem_barrage" +boss_nevermore_requiem_barrage.____file_path = "scripts/vscripts/abilities/creep/boss_nevermore_requiem_barrage.lua" +__TS__ClassExtends(boss_nevermore_requiem_barrage, BaseAbility) +function boss_nevermore_requiem_barrage.prototype.____constructor(self, ...) + BaseAbility.prototype.____constructor(self, ...) + self.castSerial = 0 + self.phasePreviewParticles = {} + self.phasePreviewRotationOffset = 0 + self.activePreviewParticles = {} + self.gestureLoopSerial = 0 +end +function boss_nevermore_requiem_barrage.prototype.Precache(self, context) + PrecacheResource("particle", "particles/units/heroes/hero_nevermore/nevermore_requiemofsouls_line.vpcf", context) + PrecacheResource("particle", "particles/boss_tinker_laser_preview_vector.vpcf", context) + PrecacheResource("soundfile", "soundevents/game_sounds_heroes/game_sounds_nevermore.vsndevts", context) +end +function boss_nevermore_requiem_barrage.prototype.GetChannelTime(self) + local channel = self:GetSpecialValueFor("channel_time") + return channel > 0 and channel or 3 +end +function boss_nevermore_requiem_barrage.prototype.getBossPhase(self) + local caster = self:GetCaster() + if not caster or caster:IsNull() then + return 1 + end + local hp = caster:GetHealthPercent() + if hp <= 25 then + return 4 + end + if hp <= 50 then + return 3 + end + if hp <= 75 then + return 2 + end + return 1 +end +function boss_nevermore_requiem_barrage.prototype.OnAbilityPhaseStart(self) + if not IsServer() then + return true + end + local caster = self:GetCaster() + local point = self:GetCursorPosition() + local toPoint = point - caster:GetAbsOrigin() + local baseDirection = toPoint:Length2D() < 1 and caster:GetForwardVector() or toPoint:Normalized() + self:clearPhasePreview() + self.phasePreviewRotationOffset = self:getWaveRotationOffset(1) + local previewDirections = self:buildBurstDirections( + baseDirection, + self.phasePreviewRotationOffset, + self:getBossPhase() + ) + local previewDuration = self:GetCastPoint() > 0 and self:GetCastPoint() or 0.3 + self.phasePreviewParticles = self:createWavePreview(previewDirections, previewDuration) + return true +end +function boss_nevermore_requiem_barrage.prototype.OnAbilityPhaseInterrupted(self) + if not IsServer() then + return + end + self:clearPhasePreview() +end +function boss_nevermore_requiem_barrage.prototype.OnSpellStart(self) + if not IsServer() then + return + end + local caster = self:GetCaster() + nevermoreIncrementRequiemCastCount(nil, caster) + local point = self:GetCursorPosition() + local toPoint = point - caster:GetAbsOrigin() + local baseDirection = toPoint:Length2D() < 1 and caster:GetForwardVector() or toPoint:Normalized() + self.castSerial = self.castSerial + 1 + local currentCast = self.castSerial + local channelTime = self:GetChannelTime() + local phase = self:getBossPhase() + local baseWaveInterval = self:GetSpecialValueFor("wave_interval") > 0 and self:GetSpecialValueFor("wave_interval") or 1 + local waveInterval = math.max(0.35, baseWaveInterval - (phase - 1) * 0.12) + local wavesCount = math.max( + 1, + math.floor(channelTime / waveInterval) + ) + local previewTime = self:GetSpecialValueFor("wave_preview_time") > 0 and self:GetSpecialValueFor("wave_preview_time") or 0.35 + local waveDistanceKv = self:GetSpecialValueFor("wave_distance") > 0 and self:GetSpecialValueFor("wave_distance") or 850 + local travelDist = waveDistanceKv + (phase - 1) * REQUIEM_PHASE_DISTANCE_BONUS + local waveTravelTimeMax = self:getMaxWaveTravelTime(travelDist) + self:clearPhasePreview() + caster:AddNewModifier(caster, self, modifier_boss_nevermore_requiem_barrage_casting.name, {duration = channelTime + 0.1}) + EmitSoundOn("Hero_Nevermore.RequiemOfSoulsCast", caster) + self:startGestureLoop(caster) + local firstRotationOffset = self.phasePreviewRotationOffset + self:fireSoulWaveBurst(baseDirection, firstRotationOffset) + Timers:CreateTimer( + waveTravelTimeMax, + function() + self:destroyPreviewParticles(self.phasePreviewParticles) + self.phasePreviewParticles = {} + return nil + end + ) + do + local i = 1 + while i <= wavesCount do + local fireTime = i * waveInterval + local waveRotationOffset = self:getWaveRotationOffset(i + 1) + local wavePreviewParticles = {} + local wasPreviewShown = false + local speedsForWave + Timers:CreateTimer( + math.max(0, fireTime - previewTime), + function() + if self.castSerial ~= currentCast then + return nil + end + if not caster or caster:IsNull() or not caster:IsAlive() then + return nil + end + local previewDirections = self:buildBurstDirections( + baseDirection, + waveRotationOffset, + self:getBossPhase() + ) + speedsForWave = __TS__ArrayMap( + previewDirections, + function() return self:sampleWaveSpeed() end + ) + wavePreviewParticles = self:createWavePreview(previewDirections, previewTime, speedsForWave) + wasPreviewShown = true + return nil + end + ) + Timers:CreateTimer( + fireTime, + function() + if not wasPreviewShown then + return nil + end + if not caster or caster:IsNull() or not caster:IsAlive() then + return nil + end + self:fireSoulWaveBurst(baseDirection, waveRotationOffset, speedsForWave) + Timers:CreateTimer( + waveTravelTimeMax, + function() + self:destroyPreviewParticles(wavePreviewParticles) + return nil + end + ) + return nil + end + ) + i = i + 1 + end + end +end +function boss_nevermore_requiem_barrage.prototype.OnChannelFinish(self, _bInterrupted) + if not IsServer() then + return + end + self.castSerial = self.castSerial + 1 + local caster = self:GetCaster() + self:clearPhasePreview() + self:clearAllActivePreviews() + self:stopGestureLoop(caster) + caster:RemoveModifierByName(modifier_boss_nevermore_requiem_barrage_casting.name) + StopSoundOn("Hero_Nevermore.RequiemOfSoulsCast", caster) +end +function boss_nevermore_requiem_barrage.prototype.startGestureLoop(self, caster) + self.gestureLoopSerial = self.gestureLoopSerial + 1 + local serial = self.gestureLoopSerial + local interval = 0.45 + local tick + tick = function() + if self.gestureLoopSerial ~= serial then + return + end + if not caster or caster:IsNull() or not caster:IsAlive() then + return + end + if not caster:HasModifier(modifier_boss_nevermore_requiem_barrage_casting.name) then + return + end + caster:StartGestureWithPlaybackRate(ACT_DOTA_RAZE_1, 1) + Timers:CreateTimer( + interval, + function() + tick(nil) + return nil + end + ) + end + tick(nil) +end +function boss_nevermore_requiem_barrage.prototype.stopGestureLoop(self, caster) + self.gestureLoopSerial = self.gestureLoopSerial + 1 + if not caster or caster:IsNull() then + return + end + caster:FadeGesture(ACT_DOTA_RAZE_1) +end +function boss_nevermore_requiem_barrage.prototype.getWaveSpeedBounds(self) + local base = self:GetSpecialValueFor("wave_speed") > 0 and self:GetSpecialValueFor("wave_speed") or 800 + local minKv = self:GetSpecialValueFor("wave_speed_min") + local maxKv = self:GetSpecialValueFor("wave_speed_max") + if minKv > 0 and maxKv > 0 and maxKv >= minKv then + return {minKv, maxKv} + end + local lo = math.max(120, base * 0.72) + local hi = base * 1.32 + return {lo, hi} +end +function boss_nevermore_requiem_barrage.prototype.sampleWaveSpeed(self) + local mn, mx = unpack(self:getWaveSpeedBounds()) + return RandomFloat(mn, mx) +end +function boss_nevermore_requiem_barrage.prototype.getMaxWaveTravelTime(self, distance) + local mn, _ = unpack(self:getWaveSpeedBounds()) + return distance / mn +end +function boss_nevermore_requiem_barrage.prototype.fireSoulWaveBurst(self, baseDirection, rotationOffset, precomputedSpeeds) + local phase = self:getBossPhase() + local directions = self:buildBurstDirections(baseDirection, rotationOffset, phase) + local waveDistanceBase = self:GetSpecialValueFor("wave_distance") > 0 and self:GetSpecialValueFor("wave_distance") or 850 + local startRadius = self:GetSpecialValueFor("wave_width_start") > 0 and self:GetSpecialValueFor("wave_width_start") or 45 + local endRadius = self:GetSpecialValueFor("wave_width_end") > 0 and self:GetSpecialValueFor("wave_width_end") or 45 + local distance = waveDistanceBase + (phase - 1) * REQUIEM_PHASE_DISTANCE_BONUS + do + local i = 0 + while i < #directions do + local direction = directions[i + 1] + local spd = precomputedSpeeds ~= nil and precomputedSpeeds[i + 1] ~= nil and precomputedSpeeds[i + 1] or self:sampleWaveSpeed() + self:fireSoulWave( + direction, + distance, + spd, + startRadius, + endRadius + ) + i = i + 1 + end + end + EmitSoundOn( + "Hero_Nevermore.RequiemOfSouls", + self:GetCaster() + ) +end +function boss_nevermore_requiem_barrage.prototype.getWaveRotationOffset(self, waveIndex) + local randomOffset = RandomInt(0, 359) + local waveOffset = waveIndex * 17 + return (randomOffset + waveOffset) % 360 +end +function boss_nevermore_requiem_barrage.prototype.buildBurstDirections(self, baseDirection, rotationOffset, phase) + local dirs = {} + local baseAngles = { + 0, + 90, + 180, + 270, + 45, + 135, + 225, + 315 + } + local phaseAngles = {} + if phase >= 1 then + __TS__ArrayPush( + phaseAngles, + 22.5, + 112.5, + 202.5, + 292.5 + ) + end + if phase >= 2 then + __TS__ArrayPush( + phaseAngles, + 30, + 120, + 210, + 300 + ) + end + if phase >= 3 then + __TS__ArrayPush( + phaseAngles, + 67.5, + 157.5, + 247.5, + 337.5 + ) + end + if phase >= 4 then + __TS__ArrayPush( + phaseAngles, + 15, + 105, + 195, + 285 + ) + end + local ____array_0 = __TS__SparseArrayNew(unpack(baseAngles)) + __TS__SparseArrayPush( + ____array_0, + unpack(phaseAngles) + ) + local angles = {__TS__SparseArraySpread(____array_0)} + for ____, angle in ipairs(angles) do + local q = QAngle(0, angle + rotationOffset, 0) + dirs[#dirs + 1] = RotatePosition( + Vector(0, 0, 0), + q, + baseDirection + ) + end + return dirs +end +function boss_nevermore_requiem_barrage.prototype.createWavePreview(self, directions, previewDuration, speeds) + local caster = self:GetCaster() + local phase = self:getBossPhase() + local distance = (self:GetSpecialValueFor("wave_distance") > 0 and self:GetSpecialValueFor("wave_distance") or 850) + (phase - 1) * REQUIEM_PHASE_DISTANCE_BONUS + local defaultSpeed = self:GetSpecialValueFor("wave_speed") > 0 and self:GetSpecialValueFor("wave_speed") or 800 + local casterPos = caster:GetAbsOrigin() + local casterZ = casterPos.z + local particles = {} + do + local i = 0 + while i < #directions do + local direction = directions[i + 1] + local spd = speeds ~= nil and speeds[i + 1] ~= nil and speeds[i + 1] or defaultSpeed + local travelTime = distance / spd + local startPos = casterPos + direction * 110 + startPos.z = casterZ + local endPos = startPos + direction * distance + endPos.z = casterZ + local previewFx = ParticleManager:CreateParticle("particles/boss_tinker_laser_preview_vector.vpcf", PATTACH_WORLDORIGIN, nil) + ParticleManager:SetParticleControl(previewFx, 0, startPos) + ParticleManager:SetParticleControl(previewFx, 1, endPos) + ParticleManager:SetParticleControl( + previewFx, + 2, + Vector( + math.max(previewDuration, travelTime), + 0, + 0 + ) + ) + particles[#particles + 1] = previewFx + local ____self_activePreviewParticles_1 = self.activePreviewParticles + ____self_activePreviewParticles_1[#____self_activePreviewParticles_1 + 1] = previewFx + i = i + 1 + end + end + return particles +end +function boss_nevermore_requiem_barrage.prototype.destroyPreviewParticles(self, particles) + if #particles == 0 then + return + end + local toRemove = {} + for ____, p in ipairs(particles) do + toRemove[p] = true + end + for ____, particle in ipairs(particles) do + ParticleManager:DestroyParticle(particle, false) + ParticleManager:ReleaseParticleIndex(particle) + end + local nextActive = {} + for ____, p in ipairs(self.activePreviewParticles) do + if not (toRemove[p] ~= nil) then + nextActive[#nextActive + 1] = p + end + end + self.activePreviewParticles = nextActive +end +function boss_nevermore_requiem_barrage.prototype.clearPhasePreview(self) + self:destroyPreviewParticles(self.phasePreviewParticles) + self.phasePreviewParticles = {} +end +function boss_nevermore_requiem_barrage.prototype.clearAllActivePreviews(self) + self:destroyPreviewParticles(self.activePreviewParticles) + self.activePreviewParticles = {} +end +function boss_nevermore_requiem_barrage.prototype.fireSoulWave(self, direction, distance, speed, startRadius, endRadius) + local caster = self:GetCaster() + local casterPos = caster:GetAbsOrigin() + local startPos = casterPos + direction * 110 + startPos.z = casterPos.z + local velocity = direction * speed + velocity.z = 0 + ProjectileManager:CreateLinearProjectile({ + Ability = self, + EffectName = "", + vSpawnOrigin = startPos, + fDistance = distance, + fStartRadius = startRadius, + fEndRadius = endRadius, + Source = caster, + bHasFrontalCone = false, + iUnitTargetTeam = DOTA_UNIT_TARGET_TEAM_ENEMY, + iUnitTargetFlags = DOTA_UNIT_TARGET_FLAG_NONE, + iUnitTargetType = bit.bor(DOTA_UNIT_TARGET_HERO, DOTA_UNIT_TARGET_BASIC), + vVelocity = velocity, + bProvidesVision = false + }) + local travelTime = distance / speed + local lineParticle = ParticleManager:CreateParticle("particles/units/heroes/hero_nevermore/nevermore_requiemofsouls_line.vpcf", PATTACH_WORLDORIGIN, nil) + ParticleManager:SetParticleControl(lineParticle, 0, startPos) + ParticleManager:SetParticleControl(lineParticle, 1, velocity) + ParticleManager:SetParticleControl( + lineParticle, + 2, + Vector(0, travelTime, 0) + ) + ParticleManager:ReleaseParticleIndex(lineParticle) +end +function boss_nevermore_requiem_barrage.prototype.OnProjectileHit(self, target, _location) + if not target then + return false + end + local caster = self:GetCaster() + local phase = self:getBossPhase() + local baseDamage = caster:GetAverageTrueAttackDamage(target) + local damage = baseDamage * (1 + (phase - 1) * 0.28) * REQUIEM_BARRAGE_DAMAGE_MULT + ApplyDamage({ + victim = target, + attacker = caster, + damage = damage, + damage_type = DAMAGE_TYPE_MAGICAL, + ability = self + }) + local debuff = target:AddNewModifier(caster, self, modifier_boss_nevermore_requiem_magic_resist_debuff.name, {duration = -1}) + if debuff ~= nil then + debuff:SetStackCount(debuff:GetStackCount() + 1) + end + return false +end +boss_nevermore_requiem_barrage = __TS__Decorate( + boss_nevermore_requiem_barrage, + boss_nevermore_requiem_barrage, + {registerAbility(nil)}, + {kind = "class", name = "boss_nevermore_requiem_barrage"} +) +____exports.boss_nevermore_requiem_barrage = boss_nevermore_requiem_barrage +modifier_boss_nevermore_requiem_barrage_casting = __TS__Class() +modifier_boss_nevermore_requiem_barrage_casting.name = "modifier_boss_nevermore_requiem_barrage_casting" +modifier_boss_nevermore_requiem_barrage_casting.____file_path = "scripts/vscripts/abilities/creep/boss_nevermore_requiem_barrage.lua" +__TS__ClassExtends(modifier_boss_nevermore_requiem_barrage_casting, BaseModifier) +function modifier_boss_nevermore_requiem_barrage_casting.prototype.IsHidden(self) + return true +end +function modifier_boss_nevermore_requiem_barrage_casting.prototype.IsPurgable(self) + return false +end +function modifier_boss_nevermore_requiem_barrage_casting.prototype.CheckState(self) + return { + [MODIFIER_STATE_STUNNED] = false, + [MODIFIER_STATE_DISARMED] = true, + [MODIFIER_STATE_SILENCED] = false, + [MODIFIER_STATE_ROOTED] = true, + [MODIFIER_STATE_COMMAND_RESTRICTED] = true + } +end +modifier_boss_nevermore_requiem_barrage_casting = __TS__Decorate( + modifier_boss_nevermore_requiem_barrage_casting, + modifier_boss_nevermore_requiem_barrage_casting, + {registerModifier(nil)}, + {kind = "class", name = "modifier_boss_nevermore_requiem_barrage_casting"} +) +return ____exports diff --git a/scripts/vscripts/abilities/creep/boss_nevermore_time_walk.lua b/scripts/vscripts/abilities/creep/boss_nevermore_time_walk.lua new file mode 100644 index 0000000..fe1e14f --- /dev/null +++ b/scripts/vscripts/abilities/creep/boss_nevermore_time_walk.lua @@ -0,0 +1,431 @@ +local ____lualib = require("lualib_bundle") +local __TS__Class = ____lualib.__TS__Class +local __TS__ClassExtends = ____lualib.__TS__ClassExtends +local __TS__NumberToFixed = ____lualib.__TS__NumberToFixed +local __TS__Decorate = ____lualib.__TS__Decorate +local ____exports = {} +local modifier_boss_nevermore_time_walk +local ____dota_ts_adapter = require("lib.dota_ts_adapter") +local BaseAbility = ____dota_ts_adapter.BaseAbility +local BaseModifierMotionHorizontal = ____dota_ts_adapter.BaseModifierMotionHorizontal +local registerAbility = ____dota_ts_adapter.registerAbility +local registerModifier = ____dota_ts_adapter.registerModifier +local DEBUG_TIME_WALK = false +local twDebugNextAt = {} +local function timeWalkDebug(self, tag, message, throttle) + if throttle == nil then + throttle = 0.35 + end + if not DEBUG_TIME_WALK then + return + end + local now = GameRules:GetGameTime() + local nextAt = twDebugNextAt[tag] or 0 + if now < nextAt then + return + end + twDebugNextAt[tag] = now + throttle + print((("[NevermoreTimeWalk][" .. tag) .. "] ") .. message) +end +____exports.boss_nevermore_time_walk = __TS__Class() +local boss_nevermore_time_walk = ____exports.boss_nevermore_time_walk +boss_nevermore_time_walk.name = "boss_nevermore_time_walk" +boss_nevermore_time_walk.____file_path = "scripts/vscripts/abilities/creep/boss_nevermore_time_walk.lua" +__TS__ClassExtends(boss_nevermore_time_walk, BaseAbility) +function boss_nevermore_time_walk.prototype.Precache(self, context) + PrecacheResource("particle", "particles/units/heroes/hero_nevermore/nevermore_shadowraze.vpcf", context) + PrecacheResource("particle", "particles/econ/events/darkmoon_2017/darkmoon_generic_aoe.vpcf", context) + PrecacheResource("soundfile", "sounds/units/heroes/nevermore/shadowraze.vsnd", context) + PrecacheResource("particle", "particles/units/heroes/hero_faceless_void/faceless_void_time_walk_slow.vpcf", context) + PrecacheResource("particle", "particles/units/heroes/hero_faceless_void/faceless_void_time_walk_preimage.vpcf", context) + PrecacheResource("particle", "particles/units/heroes/hero_faceless_void/faceless_void_time_walk.vpcf", context) +end +function boss_nevermore_time_walk.prototype.GetCastRange(self, _location, _target) + return self:GetSpecialValueFor("range") +end +function boss_nevermore_time_walk.prototype.OnSpellStart(self) + if not IsServer() then + return + end + local caster = self:GetCaster() + if caster:HasModifier(modifier_boss_nevermore_time_walk.name) then + timeWalkDebug(nil, "cast_skip", "already moving, skip recast", 0.2) + return + end + local range = self:GetSpecialValueFor("range") + local speed = math.max( + 1, + self:GetSpecialValueFor("speed") + ) + local radius = self:GetSpecialValueFor("radius") + local point = self:GetCursorPosition() + local origin = caster:GetAbsOrigin() + local direction = point - origin + local distance = direction:Length2D() + if distance < 1 then + direction = caster:GetForwardVector() + distance = range + else + direction = direction:Normalized() + end + local clampedDistance = math.min(distance, range) + local targetPosition = GetGroundPosition(origin + direction * clampedDistance, nil) + local duration = clampedDistance / speed + timeWalkDebug( + nil, + "cast", + (((("distance=" .. __TS__NumberToFixed(clampedDistance, 0)) .. " duration=") .. __TS__NumberToFixed(duration, 2)) .. " speed=") .. tostring(speed), + 0.1 + ) + EmitSoundOn("Hero_FacelessVoid.TimeWalk", caster) + local startFx = ParticleManager:CreateParticle("particles/units/heroes/hero_faceless_void/faceless_void_time_walk_slow.vpcf", PATTACH_WORLDORIGIN, caster) + ParticleManager:SetParticleControl(startFx, 0, origin) + ParticleManager:SetParticleControl( + startFx, + 1, + Vector(radius, 0, 0) + ) + ParticleManager:ReleaseParticleIndex(startFx) + local preimageFx = ParticleManager:CreateParticle("particles/units/heroes/hero_faceless_void/faceless_void_time_walk_preimage.vpcf", PATTACH_WORLDORIGIN, caster) + ParticleManager:SetParticleControl(preimageFx, 0, origin) + ParticleManager:SetParticleControl(preimageFx, 1, targetPosition) + ParticleManager:SetParticleControl(preimageFx, 2, targetPosition) + ParticleManager:ReleaseParticleIndex(preimageFx) + caster:AddNewModifier( + caster, + self, + modifier_boss_nevermore_time_walk.name, + { + duration = duration, + target_x = targetPosition.x, + target_y = targetPosition.y, + target_z = targetPosition.z, + speed = speed, + radius = radius, + damage = self:GetSpecialValueFor("damage") + } + ) + ProjectileManager:ProjectileDodge(caster) +end +function boss_nevermore_time_walk.prototype.OnProjectileHit(self, target, _location) + if not IsServer() or not target then + return false + end + local caster = self:GetCaster() + if not caster or caster:IsNull() or not caster:IsAlive() then + return false + end + local base = self:GetSpecialValueFor("damage") + local damage = math.max(1, base * 0.35) + ApplyDamage({ + victim = target, + attacker = caster, + damage = damage, + damage_type = DAMAGE_TYPE_MAGICAL, + ability = self + }) + return false +end +boss_nevermore_time_walk = __TS__Decorate( + boss_nevermore_time_walk, + boss_nevermore_time_walk, + {registerAbility(nil)}, + {kind = "class", name = "boss_nevermore_time_walk"} +) +____exports.boss_nevermore_time_walk = boss_nevermore_time_walk +modifier_boss_nevermore_time_walk = __TS__Class() +modifier_boss_nevermore_time_walk.name = "modifier_boss_nevermore_time_walk" +modifier_boss_nevermore_time_walk.____file_path = "scripts/vscripts/abilities/creep/boss_nevermore_time_walk.lua" +__TS__ClassExtends(modifier_boss_nevermore_time_walk, BaseModifierMotionHorizontal) +function modifier_boss_nevermore_time_walk.prototype.____constructor(self, ...) + BaseModifierMotionHorizontal.prototype.____constructor(self, ...) + self.direction = Vector(0, 0, 0) + self.remainingDistance = 0 + self.speed = 0 + self.damage = 0 + self.radius = 0 + self.coilInterval = 0.25 + self.sideWaveInterval = 0.7 + self.nextSideWaveAt = 0 + self.sideWaveToggle = false + self.maxTargetsPerTick = 8 + self.nextCoilSoundAllowedAt = 0 + self.coilPulseIndex = 0 +end +function modifier_boss_nevermore_time_walk.prototype.IsHidden(self) + return true +end +function modifier_boss_nevermore_time_walk.prototype.IsPurgable(self) + return false +end +function modifier_boss_nevermore_time_walk.prototype.OnCreated(self, params) + if not IsServer() then + return + end + local parent = self:GetParent() + local target = Vector( + params.target_x or parent:GetAbsOrigin().x, + params.target_y or parent:GetAbsOrigin().y, + params.target_z or parent:GetAbsOrigin().z + ) + local toTarget = target - parent:GetAbsOrigin() + self.remainingDistance = toTarget:Length2D() + self.direction = self.remainingDistance < 1 and parent:GetForwardVector() or toTarget:Normalized() + local ____math_max_3 = math.max + local ____params_speed_2 = params.speed + if ____params_speed_2 == nil then + local ____opt_0 = self:GetAbility() + ____params_speed_2 = ____opt_0 and ____opt_0:GetSpecialValueFor("speed") + end + self.speed = ____math_max_3(1, ____params_speed_2 or 1) + local ____math_max_7 = math.max + local ____params_radius_6 = params.radius + if ____params_radius_6 == nil then + local ____opt_4 = self:GetAbility() + ____params_radius_6 = ____opt_4 and ____opt_4:GetSpecialValueFor("radius") + end + self.radius = ____math_max_7(1, ____params_radius_6 or 180) + local ____params_damage_10 = params.damage + if ____params_damage_10 == nil then + local ____opt_8 = self:GetAbility() + ____params_damage_10 = ____opt_8 and ____opt_8:GetSpecialValueFor("damage") + end + self.damage = ____params_damage_10 or 0 + local ____math_max_13 = math.max + local ____opt_11 = self:GetAbility() + self.coilInterval = ____math_max_13( + 0.22, + ____opt_11 and ____opt_11:GetSpecialValueFor("coil_interval") or 0.25 + ) + local ____math_max_16 = math.max + local ____opt_14 = self:GetAbility() + self.sideWaveInterval = ____math_max_16( + 0.45, + ____opt_14 and ____opt_14:GetSpecialValueFor("side_wave_interval") or 0.7 + ) + local ____math_max_19 = math.max + local ____opt_17 = self:GetAbility() + self.maxTargetsPerTick = ____math_max_19( + 1, + ____opt_17 and ____opt_17:GetSpecialValueFor("max_targets_per_tick") or 8 + ) + local now = GameRules:GetGameTime() + self.nextSideWaveAt = now + self.sideWaveInterval + self.nextCoilSoundAllowedAt = now + timeWalkDebug( + nil, + "start", + (((((("dist=" .. __TS__NumberToFixed(self.remainingDistance, 0)) .. " speed=") .. tostring(self.speed)) .. " coilInt=") .. __TS__NumberToFixed(self.coilInterval, 2)) .. " sideInt=") .. __TS__NumberToFixed(self.sideWaveInterval, 2), + 0.1 + ) + if not self:ApplyHorizontalMotionController() then + self:Destroy() + return + end + self.cachedRequiemAbility = parent:FindAbilityByName("boss_nevermore_requiem_barrage") or nil + self:StartIntervalThink(self.coilInterval) + self:runCoilPulse(now) +end +function modifier_boss_nevermore_time_walk.prototype.OnIntervalThink(self) + if not IsServer() then + return + end + local parent = self:GetParent() + local ability = self:GetAbility() + if not parent or parent:IsNull() or not ability then + return + end + local now = GameRules:GetGameTime() + if now >= self.nextSideWaveAt then + self.nextSideWaveAt = now + self.sideWaveInterval + self.sideWaveToggle = not self.sideWaveToggle + self:fireSideWaves( + parent, + ability, + GetGroundPosition( + parent:GetAbsOrigin(), + nil + ), + self.sideWaveToggle + ) + timeWalkDebug(nil, "side_waves", "spawn side=" .. (self.sideWaveToggle and "L" or "R"), 0.25) + end + self:runCoilPulse(now) +end +function modifier_boss_nevermore_time_walk.prototype.runCoilPulse(self, now) + local parent = self:GetParent() + local ability = self:GetAbility() + if not parent or parent:IsNull() or not ability then + return + end + local pulsePoint = GetGroundPosition( + parent:GetAbsOrigin(), + nil + ) + local enemies = FindUnitsInRadius( + parent:GetTeamNumber(), + pulsePoint, + nil, + self.radius, + DOTA_UNIT_TARGET_TEAM_ENEMY, + bit.bor(DOTA_UNIT_TARGET_HERO, DOTA_UNIT_TARGET_BASIC), + DOTA_UNIT_TARGET_FLAG_NONE, + FIND_ANY_ORDER, + false + ) + local targetCount = math.min(#enemies, self.maxTargetsPerTick) + do + local i = 0 + while i < targetCount do + local enemy = enemies[i + 1] + ApplyDamage({ + victim = enemy, + attacker = parent, + damage = self.damage, + damage_type = DAMAGE_TYPE_MAGICAL, + ability = ability + }) + i = i + 1 + end + end + self.coilPulseIndex = self.coilPulseIndex + 1 + timeWalkDebug( + nil, + "coil_tick", + (("enemies=" .. tostring(#enemies)) .. " applied=") .. tostring(targetCount), + 0.25 + ) + if now >= self.nextCoilSoundAllowedAt then + self.nextCoilSoundAllowedAt = now + 0.45 + EmitSoundOnLocationWithCaster(pulsePoint, "Hero_Nevermore.Shadowraze", parent) + end + if self.coilPulseIndex % 2 == 0 then + local hitParticle = ParticleManager:CreateParticle("particles/units/heroes/hero_nevermore/nevermore_shadowraze.vpcf", PATTACH_WORLDORIGIN, nil) + ParticleManager:SetParticleControl(hitParticle, 0, pulsePoint) + ParticleManager:SetParticleControl( + hitParticle, + 1, + Vector(self.radius, 1, 1) + ) + ParticleManager:ReleaseParticleIndex(hitParticle) + else + local aoeFx = ParticleManager:CreateParticle("particles/econ/events/darkmoon_2017/darkmoon_generic_aoe.vpcf", PATTACH_CUSTOMORIGIN, parent) + ParticleManager:SetParticleControl(aoeFx, 0, pulsePoint) + ParticleManager:SetParticleControl( + aoeFx, + 1, + Vector(self.radius, 0, 0) + ) + ParticleManager:SetParticleControl( + aoeFx, + 2, + Vector(5, 0, 1) + ) + ParticleManager:SetParticleControl( + aoeFx, + 3, + Vector(200, 50, 0) + ) + ParticleManager:SetParticleControl(aoeFx, 4, pulsePoint) + ParticleManager:ReleaseParticleIndex(aoeFx) + end +end +function modifier_boss_nevermore_time_walk.prototype.fireSideWaves(self, caster, sourceAbility, origin, fireLeft) + local requiem = self.cachedRequiemAbility + if not requiem or requiem:IsNull() then + requiem = caster:FindAbilityByName("boss_nevermore_requiem_barrage") or nil + self.cachedRequiemAbility = requiem + end + local distance = requiem and not requiem:IsNull() and math.max( + 200, + requiem:GetSpecialValueFor("wave_distance") or 900 + ) or 900 + local waveSpeed = requiem and not requiem:IsNull() and math.max( + 200, + requiem:GetSpecialValueFor("wave_speed") or 700 + ) or 700 + local startRadius = requiem and not requiem:IsNull() and math.max( + 10, + requiem:GetSpecialValueFor("wave_width_start") or 45 + ) or 45 + local endRadius = requiem and not requiem:IsNull() and math.max( + 10, + requiem:GetSpecialValueFor("wave_width_end") or 45 + ) or 45 + local dir = fireLeft and Vector(-self.direction.y, self.direction.x, 0):Normalized() or Vector(self.direction.y, -self.direction.x, 0):Normalized() + local startPos = origin + dir * 80 + startPos.z = origin.z + local velocity = dir * waveSpeed + velocity.z = 0 + ProjectileManager:CreateLinearProjectile({ + Ability = sourceAbility, + EffectName = "", + vSpawnOrigin = startPos, + fDistance = distance, + fStartRadius = startRadius, + fEndRadius = endRadius, + Source = caster, + bHasFrontalCone = false, + iUnitTargetTeam = DOTA_UNIT_TARGET_TEAM_ENEMY, + iUnitTargetFlags = DOTA_UNIT_TARGET_FLAG_NONE, + iUnitTargetType = bit.bor(DOTA_UNIT_TARGET_HERO, DOTA_UNIT_TARGET_BASIC), + vVelocity = velocity, + bProvidesVision = false + }) +end +function modifier_boss_nevermore_time_walk.prototype.OnDestroy(self) + if not IsServer() then + return + end + local parent = self:GetParent() + parent:RemoveHorizontalMotionController(self) + FindClearSpaceForUnit( + parent, + parent:GetAbsOrigin(), + true + ) + parent:StartGesture(ACT_DOTA_CAST_ABILITY_1_END) + timeWalkDebug(nil, "end", "modifier destroyed", 0.1) +end +function modifier_boss_nevermore_time_walk.prototype.UpdateHorizontalMotion(self, me, dt) + if not IsServer() then + return + end + local step = self.speed * dt + if self.remainingDistance > 0 then + local oldPos = me:GetAbsOrigin() + local nextPos = GetGroundPosition( + oldPos + self.direction * math.min(step, self.remainingDistance), + nil + ) + me:SetAbsOrigin(nextPos) + self.remainingDistance = self.remainingDistance - step + return + end + self:Destroy() +end +function modifier_boss_nevermore_time_walk.prototype.OnHorizontalMotionInterrupted(self) + if not IsServer() then + return + end + self:Destroy() +end +function modifier_boss_nevermore_time_walk.prototype.CheckState(self) + return {[MODIFIER_STATE_COMMAND_RESTRICTED] = true, [MODIFIER_STATE_NO_UNIT_COLLISION] = true, [MODIFIER_STATE_FLYING_FOR_PATHING_PURPOSES_ONLY] = true} +end +function modifier_boss_nevermore_time_walk.prototype.GetEffectName(self) + return "particles/units/heroes/hero_faceless_void/faceless_void_time_walk.vpcf" +end +function modifier_boss_nevermore_time_walk.prototype.GetStatusEffectName(self) + return "particles/status_fx/status_effect_abaddon_borrowed_time.vpcf" +end +function modifier_boss_nevermore_time_walk.prototype.StatusEffectPriority(self) + return 10 +end +modifier_boss_nevermore_time_walk = __TS__Decorate( + modifier_boss_nevermore_time_walk, + modifier_boss_nevermore_time_walk, + {registerModifier(nil)}, + {kind = "class", name = "modifier_boss_nevermore_time_walk"} +) +return ____exports diff --git a/scripts/vscripts/abilities/creep/boss_nevermore_triple_coil_aoe.lua b/scripts/vscripts/abilities/creep/boss_nevermore_triple_coil_aoe.lua new file mode 100644 index 0000000..8fecd0e --- /dev/null +++ b/scripts/vscripts/abilities/creep/boss_nevermore_triple_coil_aoe.lua @@ -0,0 +1,382 @@ +local ____lualib = require("lualib_bundle") +local __TS__Class = ____lualib.__TS__Class +local __TS__ClassExtends = ____lualib.__TS__ClassExtends +local __TS__Decorate = ____lualib.__TS__Decorate +local ____exports = {} +local modifier_boss_nevermore_triple_coil_aoe_lock +local ____dota_ts_adapter = require("lib.dota_ts_adapter") +local BaseAbility = ____dota_ts_adapter.BaseAbility +local BaseModifier = ____dota_ts_adapter.BaseModifier +local registerAbility = ____dota_ts_adapter.registerAbility +local registerModifier = ____dota_ts_adapter.registerModifier +local ____modifier_boss_nevermore_coil_debuff = require("abilities.creep.modifier_boss_nevermore_coil_debuff") +local modifier_boss_nevermore_coil_debuff = ____modifier_boss_nevermore_coil_debuff.modifier_boss_nevermore_coil_debuff +____exports.boss_nevermore_triple_coil_aoe = __TS__Class() +local boss_nevermore_triple_coil_aoe = ____exports.boss_nevermore_triple_coil_aoe +boss_nevermore_triple_coil_aoe.name = "boss_nevermore_triple_coil_aoe" +boss_nevermore_triple_coil_aoe.____file_path = "scripts/vscripts/abilities/creep/boss_nevermore_triple_coil_aoe.lua" +__TS__ClassExtends(boss_nevermore_triple_coil_aoe, BaseAbility) +function boss_nevermore_triple_coil_aoe.prototype.Precache(self, context) + PrecacheResource("particle", "particles/units/heroes/hero_nevermore/nevermore_shadowraze.vpcf", context) + PrecacheResource("particle", "particles/darkmoon_creep_warning.vpcf", context) + PrecacheResource("particle", "particles/econ/events/darkmoon_2017/darkmoon_generic_aoe.vpcf", context) + PrecacheResource("soundfile", "sounds/units/heroes/nevermore/shadowraze.vsnd", context) +end +function boss_nevermore_triple_coil_aoe.prototype.GetAOERadius(self) + return self:GetSpecialValueFor("radius") +end +function boss_nevermore_triple_coil_aoe.prototype.getSpecialOrDefault(self, name, fallback) + local value = self:GetSpecialValueFor(name) + if not value or value <= 0 then + return fallback + end + return value +end +function boss_nevermore_triple_coil_aoe.prototype.getBossPhase(self) + local caster = self:GetCaster() + if not caster or caster:IsNull() then + return 1 + end + local hp = caster:GetHealthPercent() + if hp <= 25 then + return 4 + end + if hp <= 50 then + return 3 + end + if hp <= 75 then + return 2 + end + return 1 +end +function boss_nevermore_triple_coil_aoe.prototype.getConfiguredDelay(self) + local delay = self:GetSpecialValueFor("delay") + local startDelay = self:GetSpecialValueFor("start_delay") + local best = math.max(delay or 0, startDelay or 0) + if best > 0 then + return best + end + return ____exports.boss_nevermore_triple_coil_aoe.DEFAULT_DELAY +end +function boss_nevermore_triple_coil_aoe.prototype.buildCoilPoints(self, centerPoint, forward, sideDir) + local sideOffset = self:getSpecialOrDefault("side_offset", ____exports.boss_nevermore_triple_coil_aoe.DEFAULT_SIDE_OFFSET) + local forwardOffset = self:getSpecialOrDefault("forward_offset", ____exports.boss_nevermore_triple_coil_aoe.DEFAULT_FORWARD_OFFSET) + local leftPoint = GetGroundPosition(centerPoint + sideDir * sideOffset, nil) + local rightPoint = GetGroundPosition(centerPoint - sideDir * sideOffset, nil) + local frontPoint = GetGroundPosition(centerPoint + forward * forwardOffset, nil) + local backPoint = GetGroundPosition(centerPoint - forward * forwardOffset, nil) + return { + centerPoint, + leftPoint, + rightPoint, + frontPoint, + backPoint + } +end +function boss_nevermore_triple_coil_aoe.prototype.getCoilRadiusForPhase(self, phase) + return self:getSpecialOrDefault("coil_radius", ____exports.boss_nevermore_triple_coil_aoe.DEFAULT_COIL_RADIUS) + (phase - 1) * 15 +end +function boss_nevermore_triple_coil_aoe.prototype.randomPointNearHero(self, heroOrigin, minDist, maxDist) + local angleDeg = RandomFloat(0, 360) + local dist = RandomFloat(minDist, maxDist) + local rad = angleDeg * math.pi / 180 + local ox = math.cos(rad) * dist + local oy = math.sin(rad) * dist + return GetGroundPosition( + heroOrigin + Vector(ox, oy, 0), + nil + ) +end +function boss_nevermore_triple_coil_aoe.prototype.spawnAoeAtPoint(self, centerPoint, phase, lockDuration, delayOverride) + local caster = self:GetCaster() + local castOrigin = caster:GetAbsOrigin() + local toCenter = centerPoint - castOrigin + local forward = toCenter:Length2D() < 1 and caster:GetForwardVector() or toCenter:Normalized() + local sideDir = Vector(-forward.y, forward.x, 0):Normalized() + local delay = delayOverride ~= nil and delayOverride or self:getConfiguredDelay() + local warningTime = self:getSpecialOrDefault("precast_warning_time", delay) + local coilRadius = self:getCoilRadiusForPhase(phase) + local coilPoints = self:buildCoilPoints(centerPoint, forward, sideDir) + caster:AddNewModifier(caster, self, modifier_boss_nevermore_triple_coil_aoe_lock.name, {duration = lockDuration}) + local warningParticles = {} + for ____, coilPoint in ipairs(coilPoints) do + warningParticles[#warningParticles + 1] = self:createWarning(coilPoint, coilRadius, warningTime) + end + Timers:CreateTimer( + delay, + function() + for ____, ____value in ipairs(warningParticles) do + local warningParticle = ____value[1] + local aoeParticle = ____value[2] + ParticleManager:DestroyParticle(warningParticle, true) + ParticleManager:ReleaseParticleIndex(warningParticle) + ParticleManager:DestroyParticle(aoeParticle, true) + ParticleManager:ReleaseParticleIndex(aoeParticle) + end + local impactPhase = self:getBossPhase() + local baseDamage = caster:GetAttackDamage() + local bonusDamagePerStack = self:GetSpecialValueFor("coil_stack_bonus_damage") + local damageMultiplier = 1 + (impactPhase - 1) * 0.3 + EmitSoundOn("Hero_Nevermore.Shadowraze", caster) + for ____, coilPoint in ipairs(coilPoints) do + self:applyMiniCoil( + coilPoint, + coilRadius, + baseDamage, + bonusDamagePerStack, + damageMultiplier + ) + end + return nil + end + ) +end +function boss_nevermore_triple_coil_aoe.prototype.scheduleHeroProximityCoils(self, phase, mainHitDelay, lockDuration) + if not IsServer() or phase < 1 then + return + end + local caster = self:GetCaster() + if not caster or caster:IsNull() then + return + end + local minD = self:getSpecialOrDefault("hero_coil_radius_min", ____exports.boss_nevermore_triple_coil_aoe.DEFAULT_HERO_RING_MIN) + local maxD = self:getSpecialOrDefault("hero_coil_radius_max", ____exports.boss_nevermore_triple_coil_aoe.DEFAULT_HERO_RING_MAX) + local stagger = self:getSpecialOrDefault("hero_coil_stagger", ____exports.boss_nevermore_triple_coil_aoe.DEFAULT_HERO_COIL_STAGGER) + local afterMain = self:getSpecialOrDefault("hero_coil_start_after_main", ____exports.boss_nevermore_triple_coil_aoe.DEFAULT_HERO_COIL_AFTER_MAIN) + local warningTime = self:getSpecialOrDefault("precast_warning_time", mainHitDelay) + local coilRadius = self:getCoilRadiusForPhase(phase) + local heroes = FindUnitsInRadius( + caster:GetTeamNumber(), + caster:GetAbsOrigin(), + nil, + ____exports.boss_nevermore_triple_coil_aoe.HERO_SEARCH_RADIUS, + DOTA_UNIT_TARGET_TEAM_ENEMY, + DOTA_UNIT_TARGET_HERO, + DOTA_UNIT_TARGET_FLAG_NONE, + FIND_ANY_ORDER, + false + ) + local slot = 0 + for ____, hero in ipairs(heroes) do + do + if not hero or hero:IsNull() or not hero:IsAlive() then + goto __continue27 + end + local heroGround = GetGroundPosition( + hero:GetAbsOrigin(), + nil + ) + do + local j = 0 + while j < phase do + local coilPos = self:randomPointNearHero(heroGround, minD, maxD) + local hitTime = mainHitDelay + afterMain + slot * stagger + slot = slot + 1 + self:spawnDelayedSingleCoil( + coilPos, + hitTime, + warningTime, + coilRadius, + lockDuration + ) + j = j + 1 + end + end + end + ::__continue27:: + end +end +function boss_nevermore_triple_coil_aoe.prototype.spawnDelayedSingleCoil(self, coilPoint, hitTime, warningTime, coilRadius, _lockDuration) + local caster = self:GetCaster() + local warnAt = math.max(0, hitTime - warningTime) + local state = {pair = nil} + Timers:CreateTimer( + warnAt, + function() + if not caster or caster:IsNull() or not caster:IsAlive() then + return nil + end + state.pair = self:createWarning(coilPoint, coilRadius, warningTime) + return nil + end + ) + Timers:CreateTimer( + hitTime, + function() + if state.pair then + ParticleManager:DestroyParticle(state.pair[1], true) + ParticleManager:ReleaseParticleIndex(state.pair[1]) + ParticleManager:DestroyParticle(state.pair[2], true) + ParticleManager:ReleaseParticleIndex(state.pair[2]) + state.pair = nil + end + if not caster or caster:IsNull() or not caster:IsAlive() then + return nil + end + local impactPhase = self:getBossPhase() + local baseDamage = caster:GetAttackDamage() + local bonusDamagePerStack = self:GetSpecialValueFor("coil_stack_bonus_damage") + local damageMultiplier = 1 + (impactPhase - 1) * 0.3 + self:applyMiniCoil( + coilPoint, + coilRadius, + baseDamage, + bonusDamagePerStack, + damageMultiplier + ) + return nil + end + ) +end +function boss_nevermore_triple_coil_aoe.prototype.OnSpellStart(self) + if not IsServer() then + return + end + local phase = self:getBossPhase() + local centerPoint = GetGroundPosition( + self:GetCursorPosition(), + nil + ) + local mainDelay = self:getConfiguredDelay() + local stagger = self:getSpecialOrDefault("hero_coil_stagger", ____exports.boss_nevermore_triple_coil_aoe.DEFAULT_HERO_COIL_STAGGER) + local afterMain = self:getSpecialOrDefault("hero_coil_start_after_main", ____exports.boss_nevermore_triple_coil_aoe.DEFAULT_HERO_COIL_AFTER_MAIN) + local caster = self:GetCaster() + local heroes = FindUnitsInRadius( + caster:GetTeamNumber(), + caster:GetAbsOrigin(), + nil, + ____exports.boss_nevermore_triple_coil_aoe.HERO_SEARCH_RADIUS, + DOTA_UNIT_TARGET_TEAM_ENEMY, + DOTA_UNIT_TARGET_HERO, + DOTA_UNIT_TARGET_FLAG_NONE, + FIND_ANY_ORDER, + false + ) + local heroCount = 0 + for ____, h in ipairs(heroes) do + if h and not h:IsNull() and h:IsAlive() then + heroCount = heroCount + 1 + end + end + local extraCoils = heroCount * phase + local lastHeroCoilTime = extraCoils > 0 and mainDelay + afterMain + (extraCoils - 1) * stagger or mainDelay + local lockDuration = lastHeroCoilTime + 0.55 + self:spawnAoeAtPoint(centerPoint, phase, lockDuration) + self:scheduleHeroProximityCoils(phase, mainDelay, lockDuration) +end +function boss_nevermore_triple_coil_aoe.prototype.createWarning(self, point, radius, _duration) + if not IsServer() then + return {-1, -1} + end + local caster = self:GetCaster() + local warningParticle = ParticleManager:CreateParticle("particles/darkmoon_creep_warning.vpcf", PATTACH_CUSTOMORIGIN, caster) + local aoeParticle = ParticleManager:CreateParticle("particles/econ/events/darkmoon_2017/darkmoon_generic_aoe.vpcf", PATTACH_CUSTOMORIGIN, caster) + ParticleManager:SetParticleControl(warningParticle, 0, point) + ParticleManager:SetParticleControl( + warningParticle, + 1, + Vector(radius, 100, 100) + ) + ParticleManager:SetParticleControl(aoeParticle, 0, point) + ParticleManager:SetParticleControl( + aoeParticle, + 1, + Vector(radius, 0, 0) + ) + ParticleManager:SetParticleControl( + aoeParticle, + 2, + Vector(6, 0, 1) + ) + ParticleManager:SetParticleControl( + aoeParticle, + 3, + Vector(220, 0, 0) + ) + ParticleManager:SetParticleControl(aoeParticle, 4, point) + return {warningParticle, aoeParticle} +end +function boss_nevermore_triple_coil_aoe.prototype.applyMiniCoil(self, point, radius, baseDamage, bonusDamagePerStack, damageMultiplier) + if not IsServer() then + return + end + local caster = self:GetCaster() + if not caster or caster:IsNull() or not caster:IsAlive() then + return + end + local enemies = FindUnitsInRadius( + caster:GetTeamNumber(), + point, + nil, + radius, + DOTA_UNIT_TARGET_TEAM_ENEMY, + bit.bor(DOTA_UNIT_TARGET_HERO, DOTA_UNIT_TARGET_BASIC), + DOTA_UNIT_TARGET_FLAG_NONE, + FIND_ANY_ORDER, + false + ) + for ____, enemy in ipairs(enemies) do + local coilDebuff = enemy:FindModifierByName(modifier_boss_nevermore_coil_debuff.name) + local stacks = coilDebuff and coilDebuff:GetStackCount() or 0 + local damage = (baseDamage + stacks * bonusDamagePerStack) * damageMultiplier + ApplyDamage({ + victim = enemy, + attacker = caster, + damage = damage, + damage_type = DAMAGE_TYPE_MAGICAL, + ability = self + }) + local updatedDebuff = enemy:AddNewModifier(caster, self, modifier_boss_nevermore_coil_debuff.name, {}) + if updatedDebuff ~= nil then + if stacks > 0 then + updatedDebuff:SetStackCount(stacks + 1) + else + updatedDebuff:SetStackCount(1) + end + end + end + local hitParticle = ParticleManager:CreateParticle("particles/units/heroes/hero_nevermore/nevermore_shadowraze.vpcf", PATTACH_WORLDORIGIN, nil) + ParticleManager:SetParticleControl(hitParticle, 0, point) + ParticleManager:SetParticleControl( + hitParticle, + 1, + Vector(radius, 1, 1) + ) + ParticleManager:ReleaseParticleIndex(hitParticle) +end +boss_nevermore_triple_coil_aoe.DEFAULT_DELAY = 2 +boss_nevermore_triple_coil_aoe.DEFAULT_SIDE_OFFSET = 170 +boss_nevermore_triple_coil_aoe.DEFAULT_FORWARD_OFFSET = 170 +boss_nevermore_triple_coil_aoe.DEFAULT_COIL_RADIUS = 120 +boss_nevermore_triple_coil_aoe.DEFAULT_HERO_RING_MIN = 100 +boss_nevermore_triple_coil_aoe.DEFAULT_HERO_RING_MAX = 200 +boss_nevermore_triple_coil_aoe.DEFAULT_HERO_COIL_STAGGER = 0.32 +boss_nevermore_triple_coil_aoe.DEFAULT_HERO_COIL_AFTER_MAIN = 0.45 +boss_nevermore_triple_coil_aoe.HERO_SEARCH_RADIUS = 3200 +boss_nevermore_triple_coil_aoe = __TS__Decorate( + boss_nevermore_triple_coil_aoe, + boss_nevermore_triple_coil_aoe, + {registerAbility(nil)}, + {kind = "class", name = "boss_nevermore_triple_coil_aoe"} +) +____exports.boss_nevermore_triple_coil_aoe = boss_nevermore_triple_coil_aoe +modifier_boss_nevermore_triple_coil_aoe_lock = __TS__Class() +modifier_boss_nevermore_triple_coil_aoe_lock.name = "modifier_boss_nevermore_triple_coil_aoe_lock" +modifier_boss_nevermore_triple_coil_aoe_lock.____file_path = "scripts/vscripts/abilities/creep/boss_nevermore_triple_coil_aoe.lua" +__TS__ClassExtends(modifier_boss_nevermore_triple_coil_aoe_lock, BaseModifier) +function modifier_boss_nevermore_triple_coil_aoe_lock.prototype.IsHidden(self) + return true +end +function modifier_boss_nevermore_triple_coil_aoe_lock.prototype.IsPurgable(self) + return false +end +function modifier_boss_nevermore_triple_coil_aoe_lock.prototype.CheckState(self) + return {[MODIFIER_STATE_DISARMED] = true} +end +modifier_boss_nevermore_triple_coil_aoe_lock = __TS__Decorate( + modifier_boss_nevermore_triple_coil_aoe_lock, + modifier_boss_nevermore_triple_coil_aoe_lock, + {registerModifier(nil)}, + {kind = "class", name = "modifier_boss_nevermore_triple_coil_aoe_lock"} +) +return ____exports diff --git a/scripts/vscripts/abilities/creep/contract_head_leap.lua b/scripts/vscripts/abilities/creep/contract_head_leap.lua new file mode 100644 index 0000000..37c0c60 --- /dev/null +++ b/scripts/vscripts/abilities/creep/contract_head_leap.lua @@ -0,0 +1,468 @@ +local ____lualib = require("lualib_bundle") +local __TS__Class = ____lualib.__TS__Class +local __TS__ClassExtends = ____lualib.__TS__ClassExtends +local __TS__Decorate = ____lualib.__TS__Decorate +local ____exports = {} +local ____difficulty_manager = require("difficulty_manager") +local Difficulty = ____difficulty_manager.Difficulty +local ____dota_ts_adapter = require("lib.dota_ts_adapter") +local BaseAbility = ____dota_ts_adapter.BaseAbility +local BaseModifier = ____dota_ts_adapter.BaseModifier +local BaseModifierMotionBoth = ____dota_ts_adapter.BaseModifierMotionBoth +local registerAbility = ____dota_ts_adapter.registerAbility +local registerModifier = ____dota_ts_adapter.registerModifier +--- Та же физика дуги, что у `frogmen_acid_jump` / `modifier_acid_blob_jump`. +local CONTRACT_HEAD_LEAP_MIN_HEIGHT_ABOVE_LOWEST = 400 +local CONTRACT_HEAD_LEAP_MIN_HEIGHT_ABOVE_HIGHEST = 200 +local CONTRACT_HEAD_LEAP_ACCELERATION_Z = 1500 +local CONTRACT_HEAD_LEAP_MAX_HORIZONTAL_ACCELERATION = 1500 +--- Пассивка контракта: прыжок дугой к точке, где стоял герой; урон только если к моменту приземления он не ушёл из зоны. +____exports.contract_head_leap = __TS__Class() +local contract_head_leap = ____exports.contract_head_leap +contract_head_leap.name = "contract_head_leap" +contract_head_leap.____file_path = "scripts/vscripts/abilities/creep/contract_head_leap.lua" +__TS__ClassExtends(contract_head_leap, BaseAbility) +function contract_head_leap.prototype.Precache(self, context) + PrecacheResource("particle", "particles/generic_gameplay/generic_hit_blood.vpcf", context) + PrecacheResource("particle", "particles/econ/events/darkmoon_2017/darkmoon_generic_aoe.vpcf", context) + PrecacheResource("particle", "particles/econ/events/darkmoon_2017/darkmoon_calldown_marker_arrows.vpcf", context) +end +function contract_head_leap.prototype.GetIntrinsicModifierName(self) + return "modifier_contract_head_leap_passive" +end +function contract_head_leap.prototype.showLeapPrecastVisuals(self, caster, strikeGround, damageRadius, warningDuration) + if not IsServer() or warningDuration <= 0 then + return + end + local groundPos = GetGroundPosition(strikeGround, nil) + local r = math.max(1, damageRadius) + local particleAoe = ParticleManager:CreateParticle("particles/econ/events/darkmoon_2017/darkmoon_generic_aoe.vpcf", PATTACH_CUSTOMORIGIN, nil) + if particleAoe ~= nil then + ParticleManager:SetParticleControl(particleAoe, 0, groundPos) + ParticleManager:SetParticleControl( + particleAoe, + 1, + Vector(r, 0, 0) + ) + ParticleManager:SetParticleControl( + particleAoe, + 2, + Vector(6, 0, 1) + ) + ParticleManager:SetParticleControl( + particleAoe, + 3, + Vector(200, 0, 0) + ) + ParticleManager:SetParticleControl(particleAoe, 4, groundPos) + Timers:CreateTimer( + warningDuration, + function() + ParticleManager:DestroyParticle(particleAoe, false) + ParticleManager:ReleaseParticleIndex(particleAoe) + end + ) + end + local casterPos = GetGroundPosition( + caster:GetAbsOrigin(), + nil + ) + local particleArrow = ParticleManager:CreateParticle("particles/econ/events/darkmoon_2017/darkmoon_calldown_marker_arrows.vpcf", PATTACH_CUSTOMORIGIN, nil) + if particleArrow ~= nil then + ParticleManager:SetParticleControl(particleArrow, 0, casterPos) + ParticleManager:SetParticleControl(particleArrow, 1, groundPos) + ParticleManager:SetParticleControl( + particleArrow, + 2, + Vector(warningDuration, 0, 0) + ) + Timers:CreateTimer( + warningDuration, + function() + ParticleManager:DestroyParticle(particleArrow, false) + ParticleManager:ReleaseParticleIndex(particleArrow) + end + ) + end +end +function contract_head_leap.prototype.performLeapLanding(self, heroEntIndex, strikeGround) + if not IsServer() then + return + end + local creep = self:GetCaster() + if not creep or not IsValidEntity(creep) or not creep:IsAlive() then + return + end + local hero = EntIndexToHScript(heroEntIndex) + local hitRadius = math.max( + 0, + self:GetSpecialValueFor("damage_radius") + ) + local hpos = hero and IsValidEntity(hero) and hero:IsAlive() and hero:GetAbsOrigin() or nil + local inZone = false + if hpos ~= nil then + local dx = hpos.x - strikeGround.x + local dy = hpos.y - strikeGround.y + inZone = math.sqrt(dx * dx + dy * dy) <= hitRadius + end + if hero and IsValidEntity(hero) and hero:IsAlive() and not hero:IsInvulnerable() and inZone then + local scale = Difficulty:getNpcStatScale() + local flatDamage = math.max( + 1, + math.floor(self:GetSpecialValueFor("leap_damage") * scale + 0.5) + ) + local pctFromAttack = math.max( + 0, + self:GetSpecialValueFor("damage_from_attack_pct") + ) + local avgAtk = creep:GetAverageTrueAttackDamage(hero) + local fromAttack = math.floor(avgAtk * (pctFromAttack / 100) * scale + 0.5) + local totalDamage = flatDamage + fromAttack + ApplyDamage({ + victim = hero, + attacker = creep, + damage = totalDamage, + damage_type = DAMAGE_TYPE_PHYSICAL, + ability = self + }) + local hitFx = ParticleManager:CreateParticle("particles/generic_gameplay/generic_hit_blood.vpcf", PATTACH_CUSTOMORIGIN, nil) + ParticleManager:SetParticleControlEnt( + hitFx, + 0, + hero, + PATTACH_POINT_FOLLOW, + "attach_hitloc", + hero:GetAbsOrigin(), + true + ) + ParticleManager:ReleaseParticleIndex(hitFx) + EmitSoundOn("Hero_MonkeyKing.Spring.Channel", creep) + end +end +contract_head_leap = __TS__Decorate( + contract_head_leap, + contract_head_leap, + {registerAbility(nil)}, + {kind = "class", name = "contract_head_leap"} +) +____exports.contract_head_leap = contract_head_leap +____exports.modifier_contract_head_leap_passive = __TS__Class() +local modifier_contract_head_leap_passive = ____exports.modifier_contract_head_leap_passive +modifier_contract_head_leap_passive.name = "modifier_contract_head_leap_passive" +modifier_contract_head_leap_passive.____file_path = "scripts/vscripts/abilities/creep/contract_head_leap.lua" +__TS__ClassExtends(modifier_contract_head_leap_passive, BaseModifier) +function modifier_contract_head_leap_passive.prototype.____constructor(self, ...) + BaseModifier.prototype.____constructor(self, ...) + self.precastLockUntil = -1 +end +function modifier_contract_head_leap_passive.prototype.IsHidden(self) + return true +end +function modifier_contract_head_leap_passive.prototype.IsDebuff(self) + return false +end +function modifier_contract_head_leap_passive.prototype.IsPurgable(self) + return false +end +function modifier_contract_head_leap_passive.prototype.OnCreated(self) + if not IsServer() then + return + end + local ability = self:GetAbility() + local interval = ability and math.max( + 0.35, + ability:GetSpecialValueFor("leap_interval") + ) or 2.4 + self:StartIntervalThink(interval) +end +function modifier_contract_head_leap_passive.prototype.OnRefresh(self) + if not IsServer() then + return + end + local ability = self:GetAbility() + local interval = ability and math.max( + 0.35, + ability:GetSpecialValueFor("leap_interval") + ) or 2.4 + self:StartIntervalThink(interval) +end +function modifier_contract_head_leap_passive.prototype.OnIntervalThink(self) + if not IsServer() then + return + end + local parent = self:GetParent() + local ability = self:GetAbility() + if not ability or not IsValidEntity(parent) or not parent:IsAlive() or parent:IsStunned() or parent:IsHexed() then + return + end + local now = GameRules:GetDOTATime(false, false) + if now < self.precastLockUntil then + return + end + local primary = parent:FindAbilityByName("contract_head_leap") + if not primary or primary ~= ability then + return + end + if parent:HasModifier("modifier_contract_head_leap_arc") then + return + end + local radius = math.max( + 50, + ability:GetSpecialValueFor("search_radius") + ) + local heroes = FindUnitsInRadius( + parent:GetTeamNumber(), + parent:GetAbsOrigin(), + nil, + radius, + DOTA_UNIT_TARGET_TEAM_ENEMY, + DOTA_UNIT_TARGET_HERO, + DOTA_UNIT_TARGET_FLAG_NONE, + FIND_CLOSEST, + false + ) + local hero + for ____, u in ipairs(heroes) do + do + if not u or not IsValidEntity(u) or not u:IsAlive() or not u:IsHero() then + goto __continue28 + end + if not u:IsRealHero() then + goto __continue28 + end + if u:IsInvulnerable() then + goto __continue28 + end + if u:GetUnitName() == "npc_homer" then + goto __continue28 + end + hero = u + break + end + ::__continue28:: + end + if not hero then + return + end + local height = math.max( + 40, + ability:GetSpecialValueFor("height_above_hero") + ) + local strikeGround = GetGroundPosition( + hero:GetAbsOrigin(), + hero + ) + local headPos = Vector(strikeGround.x, strikeGround.y, strikeGround.z + height) + local warningTime = math.max( + 0, + ability:GetSpecialValueFor("precast_warning_time") + ) + local dmgRadius = math.max( + 1, + ability:GetSpecialValueFor("damage_radius") + ) + local arcPayload = { + vLocX = headPos.x, + vLocY = headPos.y, + vLocZ = headPos.z, + strikeX = strikeGround.x, + strikeY = strikeGround.y, + strikeZ = strikeGround.z, + hero_ei = hero:entindex() + } + if warningTime > 0 then + self.precastLockUntil = now + warningTime + ability:showLeapPrecastVisuals(parent, strikeGround, dmgRadius, warningTime) + local ____self = self + Timers:CreateTimer( + warningTime, + function() + ____self.precastLockUntil = -1 + if not IsServer() then + return + end + if not IsValidEntity(parent) or not parent:IsAlive() then + return + end + if parent:HasModifier("modifier_contract_head_leap_arc") then + return + end + local ab = parent:FindAbilityByName("contract_head_leap") + if not ab then + return + end + parent:AddNewModifier(parent, ab, "modifier_contract_head_leap_arc", arcPayload) + end + ) + else + parent:AddNewModifier(parent, ability, "modifier_contract_head_leap_arc", arcPayload) + end +end +modifier_contract_head_leap_passive = __TS__Decorate( + modifier_contract_head_leap_passive, + modifier_contract_head_leap_passive, + {registerModifier(nil)}, + {kind = "class", name = "modifier_contract_head_leap_passive"} +) +____exports.modifier_contract_head_leap_passive = modifier_contract_head_leap_passive +____exports.modifier_contract_head_leap_arc = __TS__Class() +local modifier_contract_head_leap_arc = ____exports.modifier_contract_head_leap_arc +modifier_contract_head_leap_arc.name = "modifier_contract_head_leap_arc" +modifier_contract_head_leap_arc.____file_path = "scripts/vscripts/abilities/creep/contract_head_leap.lua" +__TS__ClassExtends(modifier_contract_head_leap_arc, BaseModifierMotionBoth) +function modifier_contract_head_leap_arc.prototype.____constructor(self, ...) + BaseModifierMotionBoth.prototype.____constructor(self, ...) + self.bHorizontalMotionInterrupted = false + self.flCurrentTimeHoriz = 0 + self.flCurrentTimeVert = 0 + self.flInitialVelocityZ = 0 + self.flPredictedTotalTime = 0 +end +function modifier_contract_head_leap_arc.prototype.IsHidden(self) + return true +end +function modifier_contract_head_leap_arc.prototype.IsPurgable(self) + return false +end +function modifier_contract_head_leap_arc.prototype.RemoveOnDeath(self) + return true +end +function modifier_contract_head_leap_arc.prototype.OnCreated(self, params) + if not IsServer() then + return + end + local parent = self:GetParent() + local ability = self:GetAbility() + if not parent or not ability then + self:Destroy() + return + end + local hx = params.hero_ei + if hx == nil or hx == nil then + self:Destroy() + return + end + self.heroEntIndex = hx + local sx = params.strikeX or parent:GetOrigin().x + local sy = params.strikeY or parent:GetOrigin().y + local sz = params.strikeZ or parent:GetOrigin().z + self.strikeGround = Vector(sx, sy, sz) + self.bHorizontalMotionInterrupted = false + if not self:ApplyHorizontalMotionController() or not self:ApplyVerticalMotionController() then + self:Destroy() + return + end + self.vStartPosition = GetGroundPosition( + parent:GetOrigin(), + parent + ) + self.flCurrentTimeHoriz = 0 + self.flCurrentTimeVert = 0 + local x = params.vLocX or parent:GetOrigin().x + local y = params.vLocY or parent:GetOrigin().y + local z = params.vLocZ or parent:GetOrigin().z + self.vLoc = Vector(x, y, z) + self.vLastKnownTargetPos = self.vLoc + local duration = math.max( + 0.08, + ability:GetSpecialValueFor("jump_duration") + ) + local flDesiredHeight = CONTRACT_HEAD_LEAP_MIN_HEIGHT_ABOVE_LOWEST * duration * duration + local flLowZ = math.min(self.vLastKnownTargetPos.z, self.vStartPosition.z) + local flHighZ = math.max(self.vLastKnownTargetPos.z, self.vStartPosition.z) + local flArcTopZ = math.max(flLowZ + flDesiredHeight, flHighZ + CONTRACT_HEAD_LEAP_MIN_HEIGHT_ABOVE_HIGHEST) + local flArcDeltaZ = flArcTopZ - self.vStartPosition.z + self.flInitialVelocityZ = math.sqrt(2 * flArcDeltaZ * CONTRACT_HEAD_LEAP_ACCELERATION_Z) + local flDeltaZ = self.vLastKnownTargetPos.z - self.vStartPosition.z + local flSqrtDet = math.sqrt(math.max(0, self.flInitialVelocityZ * self.flInitialVelocityZ - 2 * CONTRACT_HEAD_LEAP_ACCELERATION_Z * flDeltaZ)) + self.flPredictedTotalTime = math.max((self.flInitialVelocityZ + flSqrtDet) / CONTRACT_HEAD_LEAP_ACCELERATION_Z, (self.flInitialVelocityZ - flSqrtDet) / CONTRACT_HEAD_LEAP_ACCELERATION_Z) + self.vHorizontalVelocity = (self.vLastKnownTargetPos - self.vStartPosition) / self.flPredictedTotalTime + self.vHorizontalVelocity.z = 0 +end +function modifier_contract_head_leap_arc.prototype.OnDestroy(self) + if not IsServer() then + return + end + local parent = self:GetParent() + if parent ~= nil then + parent:RemoveHorizontalMotionController(self) + parent:RemoveVerticalMotionController(self) + end +end +function modifier_contract_head_leap_arc.prototype.CheckState(self) + return {[MODIFIER_STATE_STUNNED] = true, [MODIFIER_STATE_UNSELECTABLE] = true} +end +function modifier_contract_head_leap_arc.prototype.UpdateHorizontalMotion(self, me, dt) + if not IsServer() then + return + end + self.flCurrentTimeHoriz = math.min(self.flCurrentTimeHoriz + dt, self.flPredictedTotalTime) + local t = self.flCurrentTimeHoriz / self.flPredictedTotalTime + local vStartToTarget = self.vLastKnownTargetPos - self.vStartPosition + local vDesiredPos = self.vStartPosition + vStartToTarget * t + local vOldPos = me:GetOrigin() + local vToDesired = vDesiredPos - vOldPos + vToDesired.z = 0 + local vDesiredVel = vToDesired / dt + local vVelDif = vDesiredVel - self.vHorizontalVelocity + local flVelDif = vVelDif:Length2D() + vVelDif = vVelDif:Normalized() + local flVelDelta = math.min(flVelDif, CONTRACT_HEAD_LEAP_MAX_HORIZONTAL_ACCELERATION) + self.vHorizontalVelocity = self.vHorizontalVelocity + vVelDif * flVelDelta * dt + local vNewPos = vOldPos + self.vHorizontalVelocity * dt + me:SetOrigin(vNewPos) +end +function modifier_contract_head_leap_arc.prototype.UpdateVerticalMotion(self, me, dt) + if not IsServer() then + return + end + self.flCurrentTimeVert = self.flCurrentTimeVert + dt + local bGoingDown = -CONTRACT_HEAD_LEAP_ACCELERATION_Z * self.flCurrentTimeVert + self.flInitialVelocityZ < 0 + local vNewPos = me:GetOrigin() + vNewPos.z = self.vStartPosition.z + (-0.5 * CONTRACT_HEAD_LEAP_ACCELERATION_Z * self.flCurrentTimeVert * self.flCurrentTimeVert + self.flInitialVelocityZ * self.flCurrentTimeVert) + local flGroundHeight = GetGroundHeight( + vNewPos, + self:GetParent() + ) + local bLanded = false + if vNewPos.z < flGroundHeight and bGoingDown then + vNewPos.z = flGroundHeight + bLanded = true + end + me:SetOrigin(vNewPos) + if bLanded then + if not self.bHorizontalMotionInterrupted then + local ability = self:GetAbility() + if ability then + ability:performLeapLanding(self.heroEntIndex, self.strikeGround) + end + end + self:Destroy() + end +end +function modifier_contract_head_leap_arc.prototype.OnHorizontalMotionInterrupted(self) + if not IsServer() then + return + end + self.bHorizontalMotionInterrupted = true +end +function modifier_contract_head_leap_arc.prototype.OnVerticalMotionInterrupted(self) + if not IsServer() then + return + end + self:Destroy() +end +function modifier_contract_head_leap_arc.prototype.DeclareFunctions(self) + return {MODIFIER_PROPERTY_OVERRIDE_ANIMATION} +end +function modifier_contract_head_leap_arc.prototype.GetOverrideAnimation(self) + return ACT_DOTA_CAST_ABILITY_1 +end +modifier_contract_head_leap_arc = __TS__Decorate( + modifier_contract_head_leap_arc, + modifier_contract_head_leap_arc, + {registerModifier(nil)}, + {kind = "class", name = "modifier_contract_head_leap_arc"} +) +____exports.modifier_contract_head_leap_arc = modifier_contract_head_leap_arc +return ____exports diff --git a/scripts/vscripts/abilities/creep/dead_harpy.lua b/scripts/vscripts/abilities/creep/dead_harpy.lua new file mode 100644 index 0000000..fc0fa5e --- /dev/null +++ b/scripts/vscripts/abilities/creep/dead_harpy.lua @@ -0,0 +1,50 @@ +local ____lualib = require("lualib_bundle") +local __TS__Class = ____lualib.__TS__Class +local __TS__ClassExtends = ____lualib.__TS__ClassExtends +local __TS__Decorate = ____lualib.__TS__Decorate +local ____exports = {} +local ____dota_ts_adapter = require("lib.dota_ts_adapter") +local BaseAbility = ____dota_ts_adapter.BaseAbility +local BaseModifier = ____dota_ts_adapter.BaseModifier +local registerAbility = ____dota_ts_adapter.registerAbility +local registerModifier = ____dota_ts_adapter.registerModifier +____exports.harpy_passive = __TS__Class() +local harpy_passive = ____exports.harpy_passive +harpy_passive.name = "harpy_passive" +harpy_passive.____file_path = "scripts/vscripts/abilities/creep/dead_harpy.lua" +__TS__ClassExtends(harpy_passive, BaseAbility) +function harpy_passive.prototype.GetIntrinsicModifierName(self) + return "modifier_harpy_passive" +end +harpy_passive = __TS__Decorate( + harpy_passive, + harpy_passive, + {registerAbility(nil)}, + {kind = "class", name = "harpy_passive"} +) +____exports.harpy_passive = harpy_passive +____exports.modifier_harpy_passive = __TS__Class() +local modifier_harpy_passive = ____exports.modifier_harpy_passive +modifier_harpy_passive.name = "modifier_harpy_passive" +modifier_harpy_passive.____file_path = "scripts/vscripts/abilities/creep/dead_harpy.lua" +__TS__ClassExtends(modifier_harpy_passive, BaseModifier) +function modifier_harpy_passive.prototype.IsHidden(self) + return true +end +function modifier_harpy_passive.prototype.DeclareFunctions(self) + return {MODIFIER_PROPERTY_VISUAL_Z_DELTA} +end +function modifier_harpy_passive.prototype.CheckState(self) + return {[MODIFIER_STATE_FLYING] = true, [MODIFIER_STATE_NO_UNIT_COLLISION] = true} +end +function modifier_harpy_passive.prototype.GetVisualZDelta(self) + return 100 +end +modifier_harpy_passive = __TS__Decorate( + modifier_harpy_passive, + modifier_harpy_passive, + {registerModifier(nil)}, + {kind = "class", name = "modifier_harpy_passive"} +) +____exports.modifier_harpy_passive = modifier_harpy_passive +return ____exports diff --git a/scripts/vscripts/abilities/creep/demon_dragon_satyr/animation.lua b/scripts/vscripts/abilities/creep/demon_dragon_satyr/animation.lua new file mode 100644 index 0000000..313fc54 --- /dev/null +++ b/scripts/vscripts/abilities/creep/demon_dragon_satyr/animation.lua @@ -0,0 +1,117 @@ +local ____lualib = require("lualib_bundle") +local __TS__Class = ____lualib.__TS__Class +local __TS__ClassExtends = ____lualib.__TS__ClassExtends +local __TS__Decorate = ____lualib.__TS__Decorate +local ____exports = {} +local ____dota_ts_adapter = require("lib.dota_ts_adapter") +local BaseAbility = ____dota_ts_adapter.BaseAbility +local BaseModifier = ____dota_ts_adapter.BaseModifier +local registerAbility = ____dota_ts_adapter.registerAbility +local registerModifier = ____dota_ts_adapter.registerModifier +local ____incoming_damage_reduction_combine = require("utils.incoming_damage_reduction_combine") +local removeIncomingDamageReductionSource = ____incoming_damage_reduction_combine.removeIncomingDamageReductionSource +local setIncomingDamageReductionSource = ____incoming_damage_reduction_combine.setIncomingDamageReductionSource +local ANIMATION_STUN_INCOMING_SOURCE = "modifier_animation_stun" +____exports.ability_animation = __TS__Class() +local ability_animation = ____exports.ability_animation +ability_animation.name = "ability_animation" +ability_animation.____file_path = "scripts/vscripts/abilities/creep/demon_dragon_satyr/animation.lua" +__TS__ClassExtends(ability_animation, BaseAbility) +function ability_animation.prototype.GetIntrinsicModifierName(self) + return "modifier_animation_checker" +end +ability_animation = __TS__Decorate( + ability_animation, + ability_animation, + {registerAbility(nil)}, + {kind = "class", name = "ability_animation"} +) +____exports.ability_animation = ability_animation +____exports.modifier_animation_checker = __TS__Class() +local modifier_animation_checker = ____exports.modifier_animation_checker +modifier_animation_checker.name = "modifier_animation_checker" +modifier_animation_checker.____file_path = "scripts/vscripts/abilities/creep/demon_dragon_satyr/animation.lua" +__TS__ClassExtends(modifier_animation_checker, BaseModifier) +function modifier_animation_checker.prototype.IsHidden(self) + return true +end +function modifier_animation_checker.prototype.DeclareFunctions(self) + return {MODIFIER_EVENT_ON_TAKEDAMAGE} +end +function modifier_animation_checker.prototype.OnTakeDamage(self, event) + if not IsServer() then + return + end + local unit = event.unit + if unit ~= self:GetParent() then + return + end + local maxHealth = unit:GetMaxHealth() + local currentHealth = unit:GetHealth() + local ability = self:GetAbility() + if currentHealth <= maxHealth * 0.75 and self:GetStackCount() < 1 then + self:IncrementStackCount() + unit:AddNewModifier(unit, ability, "modifier_animation_stun", {duration = 3.7}) + end + if currentHealth <= maxHealth * 0.5 and self:GetStackCount() < 2 then + self:IncrementStackCount() + unit:AddNewModifier(unit, ability, "modifier_animation_stun", {duration = 3.7}) + end + if currentHealth <= maxHealth * 0.25 and self:GetStackCount() < 3 then + self:IncrementStackCount() + unit:AddNewModifier(unit, ability, "modifier_animation_stun", {duration = 3.7}) + end +end +modifier_animation_checker = __TS__Decorate( + modifier_animation_checker, + modifier_animation_checker, + {registerModifier(nil)}, + {kind = "class", name = "modifier_animation_checker"} +) +____exports.modifier_animation_checker = modifier_animation_checker +____exports.modifier_animation_stun = __TS__Class() +local modifier_animation_stun = ____exports.modifier_animation_stun +modifier_animation_stun.name = "modifier_animation_stun" +modifier_animation_stun.____file_path = "scripts/vscripts/abilities/creep/demon_dragon_satyr/animation.lua" +__TS__ClassExtends(modifier_animation_stun, BaseModifier) +function modifier_animation_stun.prototype.IsHidden(self) + return false +end +function modifier_animation_stun.prototype.OnCreated(self) + if not IsServer() then + return + end + local parent = self:GetParent() + parent:StartGestureWithFade(ACT_DOTA_NIAN_PIN_TO_STUN, 0.5, 0) + setIncomingDamageReductionSource( + nil, + parent, + ANIMATION_STUN_INCOMING_SOURCE, + function() return 50 end + ) +end +function modifier_animation_stun.prototype.OnDestroy(self) + if not IsServer() then + return + end + local parent = self:GetParent() + removeIncomingDamageReductionSource(nil, parent, ANIMATION_STUN_INCOMING_SOURCE) + parent:RemoveGesture(ACT_DOTA_NIAN_PIN_TO_STUN) +end +function modifier_animation_stun.prototype.CheckState(self) + return { + [MODIFIER_STATE_COMMAND_RESTRICTED] = false, + [MODIFIER_STATE_DISARMED] = true, + [MODIFIER_STATE_IGNORING_MOVE_AND_ATTACK_ORDERS] = true, + [MODIFIER_STATE_IGNORING_MOVE_ORDERS] = true, + [MODIFIER_STATE_SILENCED] = true + } +end +modifier_animation_stun = __TS__Decorate( + modifier_animation_stun, + modifier_animation_stun, + {registerModifier(nil)}, + {kind = "class", name = "modifier_animation_stun"} +) +____exports.modifier_animation_stun = modifier_animation_stun +return ____exports diff --git a/scripts/vscripts/abilities/creep/demon_dragon_satyr/back_stun.lua b/scripts/vscripts/abilities/creep/demon_dragon_satyr/back_stun.lua new file mode 100644 index 0000000..c62caa1 --- /dev/null +++ b/scripts/vscripts/abilities/creep/demon_dragon_satyr/back_stun.lua @@ -0,0 +1,92 @@ +local ____lualib = require("lualib_bundle") +local __TS__Class = ____lualib.__TS__Class +local __TS__ClassExtends = ____lualib.__TS__ClassExtends +local __TS__Decorate = ____lualib.__TS__Decorate +local ____exports = {} +local ____dota_ts_adapter = require("lib.dota_ts_adapter") +local BaseAbility = ____dota_ts_adapter.BaseAbility +local BaseModifier = ____dota_ts_adapter.BaseModifier +local registerAbility = ____dota_ts_adapter.registerAbility +local registerModifier = ____dota_ts_adapter.registerModifier +____exports.back_stun = __TS__Class() +local back_stun = ____exports.back_stun +back_stun.name = "back_stun" +back_stun.____file_path = "scripts/vscripts/abilities/creep/demon_dragon_satyr/back_stun.lua" +__TS__ClassExtends(back_stun, BaseAbility) +function back_stun.prototype.Precache(self, context) + PrecacheResource("particle", "particles/econ/items/earthshaker/deep_magma/deep_magma_10th/deep_magma_10th_echoslam_start.vpcf", context) + PrecacheResource("particle", "particles/generic_gameplay/generic_stunned.vpcf", context) +end +function back_stun.prototype.GetCastRange(self) + return self:GetSpecialValueFor("cast_range") +end +function back_stun.prototype.OnSpellStart(self) + local caster = self:GetCaster() + local duration = self:GetSpecialValueFor("duration") + local radius = self:GetSpecialValueFor("cast_range") + if not caster then + return + end + local direction = caster:GetForwardVector() + local backPosition = caster:GetOrigin():__sub(direction:__mul(radius)) + self.particle = ParticleManager:CreateParticle("particles/econ/items/earthshaker/deep_magma/deep_magma_10th/deep_magma_10th_echoslam_start.vpcf", PATTACH_WORLDORIGIN, nil) + ParticleManager:SetParticleControl(self.particle, 0, backPosition) + local units = FindUnitsInRadius( + caster:GetTeamNumber(), + backPosition, + nil, + radius, + DOTA_UNIT_TARGET_TEAM_ENEMY, + DOTA_UNIT_TARGET_HERO + DOTA_UNIT_TARGET_BASIC, + DOTA_UNIT_TARGET_FLAG_NONE, + FIND_ANY_ORDER, + false + ) + for ____, unit in ipairs(units) do + unit:AddNewModifier(caster, self, "modifier_back_stunned", {duration = duration}) + local damage = self:GetSpecialValueFor("damage") + self:GetCaster():GetAverageTrueAttackDamage(unit) * 3 + ApplyDamage({victim = unit, attacker = caster, damage = damage, damage_type = DAMAGE_TYPE_PURE}) + EmitSoundOn("Hero_Centaur.HoofStomp", unit) + end +end +back_stun = __TS__Decorate( + back_stun, + back_stun, + {registerAbility(nil)}, + {kind = "class", name = "back_stun"} +) +____exports.back_stun = back_stun +____exports.modifier_back_stunned = __TS__Class() +local modifier_back_stunned = ____exports.modifier_back_stunned +modifier_back_stunned.name = "modifier_back_stunned" +modifier_back_stunned.____file_path = "scripts/vscripts/abilities/creep/demon_dragon_satyr/back_stun.lua" +__TS__ClassExtends(modifier_back_stunned, BaseModifier) +function modifier_back_stunned.prototype.IsHidden(self) + return false +end +function modifier_back_stunned.prototype.IsDebuff(self) + return true +end +function modifier_back_stunned.prototype.IsStunDebuff(self) + return true +end +function modifier_back_stunned.prototype.IsPurgable(self) + return true +end +function modifier_back_stunned.prototype.GetEffectName(self) + return "particles/generic_gameplay/generic_stunned.vpcf" +end +function modifier_back_stunned.prototype.GetEffectAttachType(self) + return PATTACH_OVERHEAD_FOLLOW +end +function modifier_back_stunned.prototype.CheckState(self) + return {[MODIFIER_STATE_STUNNED] = true} +end +modifier_back_stunned = __TS__Decorate( + modifier_back_stunned, + modifier_back_stunned, + {registerModifier(nil)}, + {kind = "class", name = "modifier_back_stunned"} +) +____exports.modifier_back_stunned = modifier_back_stunned +return ____exports diff --git a/scripts/vscripts/abilities/creep/demon_dragon_satyr/face_stun.lua b/scripts/vscripts/abilities/creep/demon_dragon_satyr/face_stun.lua new file mode 100644 index 0000000..a13d85e --- /dev/null +++ b/scripts/vscripts/abilities/creep/demon_dragon_satyr/face_stun.lua @@ -0,0 +1,92 @@ +local ____lualib = require("lualib_bundle") +local __TS__Class = ____lualib.__TS__Class +local __TS__ClassExtends = ____lualib.__TS__ClassExtends +local __TS__Decorate = ____lualib.__TS__Decorate +local ____exports = {} +local ____dota_ts_adapter = require("lib.dota_ts_adapter") +local BaseAbility = ____dota_ts_adapter.BaseAbility +local BaseModifier = ____dota_ts_adapter.BaseModifier +local registerAbility = ____dota_ts_adapter.registerAbility +local registerModifier = ____dota_ts_adapter.registerModifier +____exports.face_stun = __TS__Class() +local face_stun = ____exports.face_stun +face_stun.name = "face_stun" +face_stun.____file_path = "scripts/vscripts/abilities/creep/demon_dragon_satyr/face_stun.lua" +__TS__ClassExtends(face_stun, BaseAbility) +function face_stun.prototype.Precache(self, context) + PrecacheResource("particle", "particles/econ/items/earthshaker/deep_magma/deep_magma_10th/deep_magma_10th_echoslam_start.vpcf", context) + PrecacheResource("particle", "particles/generic_gameplay/generic_stunned.vpcf", context) +end +function face_stun.prototype.GetCastRange(self) + return self:GetSpecialValueFor("cast_range") +end +function face_stun.prototype.OnSpellStart(self) + local caster = self:GetCaster() + local duration = self:GetSpecialValueFor("duration") + local radius = self:GetSpecialValueFor("cast_range") + if not caster then + return + end + local direction = caster:GetForwardVector() + local frontPosition = caster:GetOrigin():__add(direction:__mul(radius)) + self.particle = ParticleManager:CreateParticle("particles/econ/items/earthshaker/deep_magma/deep_magma_10th/deep_magma_10th_echoslam_start.vpcf", PATTACH_WORLDORIGIN, nil) + ParticleManager:SetParticleControl(self.particle, 0, frontPosition) + local units = FindUnitsInRadius( + caster:GetTeamNumber(), + frontPosition, + nil, + radius, + DOTA_UNIT_TARGET_TEAM_ENEMY, + DOTA_UNIT_TARGET_HERO + DOTA_UNIT_TARGET_BASIC, + DOTA_UNIT_TARGET_FLAG_NONE, + FIND_ANY_ORDER, + false + ) + for ____, unit in ipairs(units) do + unit:AddNewModifier(caster, self, "modifier_face_stunned", {duration = duration}) + local damage = self:GetSpecialValueFor("damage") + self:GetCaster():GetAverageTrueAttackDamage(unit) * 3 + ApplyDamage({victim = unit, attacker = caster, damage = damage, damage_type = DAMAGE_TYPE_PURE}) + EmitSoundOn("Hero_Centaur.HoofStomp", unit) + end +end +face_stun = __TS__Decorate( + face_stun, + face_stun, + {registerAbility(nil)}, + {kind = "class", name = "face_stun"} +) +____exports.face_stun = face_stun +____exports.modifier_face_stunned = __TS__Class() +local modifier_face_stunned = ____exports.modifier_face_stunned +modifier_face_stunned.name = "modifier_face_stunned" +modifier_face_stunned.____file_path = "scripts/vscripts/abilities/creep/demon_dragon_satyr/face_stun.lua" +__TS__ClassExtends(modifier_face_stunned, BaseModifier) +function modifier_face_stunned.prototype.IsHidden(self) + return false +end +function modifier_face_stunned.prototype.IsDebuff(self) + return true +end +function modifier_face_stunned.prototype.IsStunDebuff(self) + return true +end +function modifier_face_stunned.prototype.IsPurgable(self) + return true +end +function modifier_face_stunned.prototype.GetEffectName(self) + return "particles/generic_gameplay/generic_stunned.vpcf" +end +function modifier_face_stunned.prototype.GetEffectAttachType(self) + return PATTACH_OVERHEAD_FOLLOW +end +function modifier_face_stunned.prototype.CheckState(self) + return {[MODIFIER_STATE_STUNNED] = true} +end +modifier_face_stunned = __TS__Decorate( + modifier_face_stunned, + modifier_face_stunned, + {registerModifier(nil)}, + {kind = "class", name = "modifier_face_stunned"} +) +____exports.modifier_face_stunned = modifier_face_stunned +return ____exports diff --git a/scripts/vscripts/abilities/creep/demon_dragon_satyr/fear.lua b/scripts/vscripts/abilities/creep/demon_dragon_satyr/fear.lua new file mode 100644 index 0000000..fc880d8 --- /dev/null +++ b/scripts/vscripts/abilities/creep/demon_dragon_satyr/fear.lua @@ -0,0 +1,132 @@ +local ____lualib = require("lualib_bundle") +local __TS__Class = ____lualib.__TS__Class +local __TS__ClassExtends = ____lualib.__TS__ClassExtends +local __TS__Decorate = ____lualib.__TS__Decorate +local ____exports = {} +local ____dota_ts_adapter = require("lib.dota_ts_adapter") +local BaseAbility = ____dota_ts_adapter.BaseAbility +local BaseModifier = ____dota_ts_adapter.BaseModifier +local registerAbility = ____dota_ts_adapter.registerAbility +local registerModifier = ____dota_ts_adapter.registerModifier +____exports.fear = __TS__Class() +local fear = ____exports.fear +fear.name = "fear" +fear.____file_path = "scripts/vscripts/abilities/creep/demon_dragon_satyr/fear.lua" +__TS__ClassExtends(fear, BaseAbility) +function fear.prototype.GetCastRange(self) + return self:GetSpecialValueFor("cast_range") +end +function fear.prototype.OnSpellStart(self) + local caster = self:GetCaster() + local duration = self:GetSpecialValueFor("duration") + if not caster then + return + end + local units = FindUnitsInRadius( + caster:GetTeamNumber(), + caster:GetAbsOrigin(), + nil, + 1200, + DOTA_UNIT_TARGET_TEAM_ENEMY, + DOTA_UNIT_TARGET_HERO + DOTA_UNIT_TARGET_BASIC, + DOTA_UNIT_TARGET_FLAG_NONE, + FIND_ANY_ORDER, + false + ) + local particle = ParticleManager:CreateParticle("particles/econ/items/terrorblade/terrorblade_back_ti8/terrorblade_sunder_ti8_swirl_rope.vpcf", PATTACH_WORLDORIGIN, nil) + ParticleManager:SetParticleControl( + particle, + 0, + caster:GetAbsOrigin() + ) + ParticleManager:ReleaseParticleIndex(particle) + for ____, unit in ipairs(units) do + unit:AddNewModifier(caster, self, "modifier_fear_debuff", {duration = duration}) + EmitSoundOn("Hero_NightStalker.Void", unit) + end +end +fear = __TS__Decorate( + fear, + fear, + {registerAbility(nil)}, + {kind = "class", name = "fear"} +) +____exports.fear = fear +____exports.modifier_fear_debuff = __TS__Class() +local modifier_fear_debuff = ____exports.modifier_fear_debuff +modifier_fear_debuff.name = "modifier_fear_debuff" +modifier_fear_debuff.____file_path = "scripts/vscripts/abilities/creep/demon_dragon_satyr/fear.lua" +__TS__ClassExtends(modifier_fear_debuff, BaseModifier) +function modifier_fear_debuff.prototype.____constructor(self, ...) + BaseModifier.prototype.____constructor(self, ...) + self.interval = 0.03 +end +function modifier_fear_debuff.prototype.IsHidden(self) + return false +end +function modifier_fear_debuff.prototype.IsDebuff(self) + return true +end +function modifier_fear_debuff.prototype.IsPurgable(self) + return true +end +function modifier_fear_debuff.prototype.GetEffectName(self) + return "particles/econ/items/nightstalker/nightstalker_black_nihility/nightstalker_black_nihility_void.vpcf" +end +function modifier_fear_debuff.prototype.GetEffectAttachType(self) + return PATTACH_CENTER_FOLLOW +end +function modifier_fear_debuff.prototype.OnCreated(self) + if not IsServer() then + return + end + self.particle = ParticleManager:CreateParticle( + "particles/generic_gameplay/generic_feared.vpcf", + PATTACH_OVERHEAD_FOLLOW, + self:GetParent() + ) + self:StartIntervalThink(self.interval) + local ____opt_0 = self:GetCaster() + self.casterPos = ____opt_0 and ____opt_0:GetAbsOrigin() +end +function modifier_fear_debuff.prototype.OnDestroy(self) + if not IsServer() then + return + end + if self.particle then + ParticleManager:DestroyParticle(self.particle, false) + ParticleManager:ReleaseParticleIndex(self.particle) + end +end +function modifier_fear_debuff.prototype.OnIntervalThink(self) + if not IsServer() or not self.casterPos then + return + end + local unit = self:GetParent() + local unitPos = unit:GetAbsOrigin() + local direction = unitPos:__sub(self.casterPos):Normalized() + unit:MoveToPosition(unitPos:__add(direction:__mul(150))) +end +function modifier_fear_debuff.prototype.DeclareFunctions(self) + return {MODIFIER_PROPERTY_PROVIDES_FOW_POSITION, MODIFIER_PROPERTY_BONUS_VISION_PERCENTAGE, MODIFIER_PROPERTY_MOVESPEED_BONUS_PERCENTAGE} +end +function modifier_fear_debuff.prototype.BonusVisionPercentage(self) + return -100 +end +function modifier_fear_debuff.prototype.MovespeedBonusPercentage(self) + return -100 +end +function modifier_fear_debuff.prototype.GetModifierProvidesFOWVision(self) + return 0 +end +function modifier_fear_debuff.prototype.CheckState(self) + return {[MODIFIER_STATE_FEARED] = true, [MODIFIER_STATE_COMMAND_RESTRICTED] = true} +end +modifier_fear_debuff = __TS__Decorate( + modifier_fear_debuff, + modifier_fear_debuff, + {registerModifier(nil)}, + {kind = "class", name = "modifier_fear_debuff"} +) +____exports.modifier_fear_debuff = modifier_fear_debuff +return ____exports diff --git a/scripts/vscripts/abilities/creep/demon_dragon_satyr/grab.lua b/scripts/vscripts/abilities/creep/demon_dragon_satyr/grab.lua new file mode 100644 index 0000000..f6a3bfb --- /dev/null +++ b/scripts/vscripts/abilities/creep/demon_dragon_satyr/grab.lua @@ -0,0 +1,171 @@ +local ____lualib = require("lualib_bundle") +local __TS__Class = ____lualib.__TS__Class +local __TS__ClassExtends = ____lualib.__TS__ClassExtends +local __TS__Decorate = ____lualib.__TS__Decorate +local ____exports = {} +local ____dota_ts_adapter = require("lib.dota_ts_adapter") +local BaseAbility = ____dota_ts_adapter.BaseAbility +local BaseModifier = ____dota_ts_adapter.BaseModifier +local registerAbility = ____dota_ts_adapter.registerAbility +local registerModifier = ____dota_ts_adapter.registerModifier +____exports.ability_grab = __TS__Class() +local ability_grab = ____exports.ability_grab +ability_grab.name = "ability_grab" +ability_grab.____file_path = "scripts/vscripts/abilities/creep/demon_dragon_satyr/grab.lua" +__TS__ClassExtends(ability_grab, BaseAbility) +function ability_grab.prototype.CancelGrabCast(self) + self:EndChannel(true) + self:EndCooldown() + self:RefundManaCost() +end +function ability_grab.prototype.GetCastRange(self) + return 150 +end +function ability_grab.prototype.GetChannelTime(self) + return 4.37 +end +function ability_grab.prototype.GetBehavior(self) + return bit.bor(DOTA_ABILITY_BEHAVIOR_POINT, DOTA_ABILITY_BEHAVIOR_CHANNELLED) +end +function ability_grab.prototype.OnSpellStart(self) + if not IsServer() then + return + end + local caster = self:GetCaster() + local point = self:GetCursorPosition() + local direction = (point - caster:GetAbsOrigin()):Normalized() + local units = FindUnitsInLine( + caster:GetTeamNumber(), + caster:GetAbsOrigin(), + caster:GetAbsOrigin() + direction * self:GetCastRange(), + nil, + 100, + DOTA_UNIT_TARGET_TEAM_ENEMY, + DOTA_UNIT_TARGET_HERO + DOTA_UNIT_TARGET_BASIC, + DOTA_UNIT_TARGET_FLAG_NONE + ) + if #units == 0 then + self:CancelGrabCast() + return + end + local target = units[1] + if not target then + self:CancelGrabCast() + return + end + target:AddNewModifier( + caster, + self, + "modifier_grab_stunned", + {duration = self:GetChannelTime()} + ) + caster:AddNewModifier( + caster, + self, + "modifier_grab_caster", + {duration = self:GetChannelTime()} + ) + caster:StartGesture(ACT_DOTA_NIAN_PIN_LOOP) + target:SetParent(caster, "attach_pindown") + target:SetOrigin(caster:GetAbsOrigin()) + self.target = target + self.damageTimer = Timers:CreateTimer( + 0.33, + function() + if self.target and not self.target:IsNull() and self.target:IsAlive() then + ParticleManager:CreateParticle("particles/econ/items/lifestealer/ls_ti9_immortal/ls_ti9_open_wounds_blood_bulk.vpcf", PATTACH_ABSORIGIN_FOLLOW, self.target) + EmitSoundOn("Hero_Pudge.Dismember", self.target) + ApplyDamage({ + victim = self.target, + attacker = caster, + damage = self:GetSpecialValueFor("damage") + self:GetCaster():GetAverageTrueAttackDamage(target) * 3, + damage_type = DAMAGE_TYPE_PURE + }) + return 0.33 + end + return nil + end + ) +end +function ability_grab.prototype.OnChannelFinish(self, interrupted) + if not IsServer() then + return + end + self:GetCaster():RemoveGesture(ACT_DOTA_NIAN_PIN_LOOP) + if self.damageTimer then + Timers:RemoveTimer(self.damageTimer) + self.damageTimer = nil + end + if self.target then + self.target:RemoveModifierByName("modifier_grab_stunned") + self.target:SetParent(nil, "") + self.target:RemoveHorizontalMotionController(self.target) + self.target:RemoveVerticalMotionController(self.target) + self.target:InterruptMotionControllers(true) + local casterPos = self:GetCaster():GetAbsOrigin() + local casterForward = self:GetCaster():GetForwardVector() + local dropPoint = casterPos + casterForward * 100 + FindClearSpaceForUnit(self.target, dropPoint, true) + ApplyDamage({ + victim = self.target, + attacker = self:GetCaster(), + damage = self:GetSpecialValueFor("damage"), + damage_type = DAMAGE_TYPE_PHYSICAL + }) + end + self:GetCaster():RemoveModifierByName("modifier_grab_caster") + self.target = nil +end +ability_grab = __TS__Decorate( + ability_grab, + ability_grab, + {registerAbility(nil)}, + {kind = "class", name = "ability_grab"} +) +____exports.ability_grab = ability_grab +____exports.modifier_grab_stunned = __TS__Class() +local modifier_grab_stunned = ____exports.modifier_grab_stunned +modifier_grab_stunned.name = "modifier_grab_stunned" +modifier_grab_stunned.____file_path = "scripts/vscripts/abilities/creep/demon_dragon_satyr/grab.lua" +__TS__ClassExtends(modifier_grab_stunned, BaseModifier) +function modifier_grab_stunned.prototype.IsDebuff(self) + return true +end +function modifier_grab_stunned.prototype.IsStunDebuff(self) + return true +end +function modifier_grab_stunned.prototype.CheckState(self) + return {[MODIFIER_STATE_STUNNED] = true, [MODIFIER_STATE_NO_UNIT_COLLISION] = true} +end +function modifier_grab_stunned.prototype.GetEffectName(self) + return "particles/generic_gameplay/generic_stunned.vpcf" +end +function modifier_grab_stunned.prototype.GetEffectAttachType(self) + return PATTACH_OVERHEAD_FOLLOW +end +modifier_grab_stunned = __TS__Decorate( + modifier_grab_stunned, + modifier_grab_stunned, + {registerModifier(nil)}, + {kind = "class", name = "modifier_grab_stunned"} +) +____exports.modifier_grab_stunned = modifier_grab_stunned +____exports.modifier_grab_caster = __TS__Class() +local modifier_grab_caster = ____exports.modifier_grab_caster +modifier_grab_caster.name = "modifier_grab_caster" +modifier_grab_caster.____file_path = "scripts/vscripts/abilities/creep/demon_dragon_satyr/grab.lua" +__TS__ClassExtends(modifier_grab_caster, BaseModifier) +function modifier_grab_caster.prototype.IsHidden(self) + return false +end +function modifier_grab_caster.prototype.CheckState(self) + return {[MODIFIER_STATE_DISARMED] = true, [MODIFIER_STATE_ROOTED] = true} +end +modifier_grab_caster = __TS__Decorate( + modifier_grab_caster, + modifier_grab_caster, + {registerModifier(nil)}, + {kind = "class", name = "modifier_grab_caster"} +) +____exports.modifier_grab_caster = modifier_grab_caster +return ____exports diff --git a/scripts/vscripts/abilities/creep/frogmen_acid_jump.lua b/scripts/vscripts/abilities/creep/frogmen_acid_jump.lua new file mode 100644 index 0000000..ba8d0ea --- /dev/null +++ b/scripts/vscripts/abilities/creep/frogmen_acid_jump.lua @@ -0,0 +1,290 @@ +local ____lualib = require("lualib_bundle") +local __TS__Class = ____lualib.__TS__Class +local __TS__ClassExtends = ____lualib.__TS__ClassExtends +local __TS__Decorate = ____lualib.__TS__Decorate +local ____exports = {} +local ____dota_ts_adapter = require("lib.dota_ts_adapter") +local BaseAbility = ____dota_ts_adapter.BaseAbility +local BaseModifierMotionBoth = ____dota_ts_adapter.BaseModifierMotionBoth +local registerAbility = ____dota_ts_adapter.registerAbility +local registerModifier = ____dota_ts_adapter.registerModifier +____exports.frogmen_acid_jump = __TS__Class() +local frogmen_acid_jump = ____exports.frogmen_acid_jump +frogmen_acid_jump.name = "frogmen_acid_jump" +frogmen_acid_jump.____file_path = "scripts/vscripts/abilities/creep/frogmen_acid_jump.lua" +__TS__ClassExtends(frogmen_acid_jump, BaseAbility) +function frogmen_acid_jump.prototype.____constructor(self, ...) + BaseAbility.prototype.____constructor(self, ...) + self.radius = 0 + self.stun_duration = 0 + self.land_damage = 0 +end +function frogmen_acid_jump.prototype.Precache(self, context) + PrecacheResource("particle", "particles/neutral_fx/ogre_bruiser_smash.vpcf", context) +end +function frogmen_acid_jump.prototype.OnAbilityPhaseStart(self) + if not IsServer() then + return true + end + return true +end +function frogmen_acid_jump.prototype.OnAbilityPhaseInterrupted(self) + if not IsServer() then + return + end +end +function frogmen_acid_jump.prototype.OnSpellStart(self) + if not IsServer() then + return + end + local caster = self:GetCaster() + if not caster then + return + end + local cursorPos = self:GetCursorPosition() + local kv = {vLocX = cursorPos.x, vLocY = cursorPos.y, vLocZ = cursorPos.z} + caster:AddNewModifier(caster, self, "modifier_acid_blob_jump", kv) + self.radius = self:GetSpecialValueFor("radius") + self.stun_duration = self:GetSpecialValueFor("stun_duration") + self.land_damage = self:GetSpecialValueFor("land_damage") +end +function frogmen_acid_jump.prototype.Smash(self) + if not IsServer() then + return + end + local caster = self:GetCaster() + if not caster then + return + end + local origin = caster:GetOrigin() + EmitSoundOnLocationWithCaster(origin, "OgreTank.GroundSmash", caster) + local smashFX = ParticleManager:CreateParticle("particles/neutral_fx/ogre_bruiser_smash.vpcf", PATTACH_WORLDORIGIN, caster) + ParticleManager:SetParticleControl(smashFX, 0, origin) + ParticleManager:SetParticleControl( + smashFX, + 1, + Vector(self.radius, self.radius, self.radius) + ) + ParticleManager:ReleaseParticleIndex(smashFX) + caster:Interrupt() + local enemies = FindUnitsInRadius( + caster:GetTeamNumber(), + origin, + caster, + self.radius, + DOTA_UNIT_TARGET_TEAM_ENEMY, + DOTA_UNIT_TARGET_HERO + DOTA_UNIT_TARGET_BASIC, + DOTA_UNIT_TARGET_FLAG_MAGIC_IMMUNE_ENEMIES, + FIND_ANY_ORDER, + false + ) + for ____, enemy in ipairs(enemies) do + do + if not enemy then + goto __continue13 + end + if enemy:IsInvulnerable() then + goto __continue13 + end + ApplyDamage({ + victim = enemy, + attacker = caster, + damage = self.land_damage + self:GetCaster():GetAverageTrueAttackDamage(enemy) * 2, + damage_type = DAMAGE_TYPE_PHYSICAL, + ability = self + }) + if not enemy:IsAlive() then + local critFX = ParticleManager:CreateParticle("particles/units/heroes/hero_phantom_assassin/phantom_assassin_crit_impact.vpcf", PATTACH_CUSTOMORIGIN, nil) + ParticleManager:SetParticleControlEnt( + critFX, + 0, + enemy, + PATTACH_POINT_FOLLOW, + "attach_hitloc", + enemy:GetOrigin(), + true + ) + ParticleManager:SetParticleControl( + critFX, + 1, + enemy:GetOrigin() + ) + local forward = caster:GetForwardVector() + ParticleManager:SetParticleControlForward(critFX, 1, forward * -1) + ParticleManager:SetParticleControlEnt( + critFX, + 10, + enemy, + PATTACH_ABSORIGIN_FOLLOW, + nil, + enemy:GetOrigin(), + true + ) + ParticleManager:ReleaseParticleIndex(critFX) + EmitSoundOn("Dungeon.BloodSplatterImpact", enemy) + else + enemy:AddNewModifier(caster, self, "modifier_stunned", {duration = self.stun_duration}) + end + end + ::__continue13:: + end +end +frogmen_acid_jump = __TS__Decorate( + frogmen_acid_jump, + frogmen_acid_jump, + {registerAbility(nil)}, + {kind = "class", name = "frogmen_acid_jump"} +) +____exports.frogmen_acid_jump = frogmen_acid_jump +local AMOEBA_MINIMUM_HEIGHT_ABOVE_LOWEST = 400 +local AMOEBA_MINIMUM_HEIGHT_ABOVE_HIGHEST = 200 +local AMOEBA_ACCELERATION_Z = 1500 +local AMOEBA_MAX_HORIZONTAL_ACCELERATION = 1500 +____exports.modifier_acid_blob_jump = __TS__Class() +local modifier_acid_blob_jump = ____exports.modifier_acid_blob_jump +modifier_acid_blob_jump.name = "modifier_acid_blob_jump" +modifier_acid_blob_jump.____file_path = "scripts/vscripts/abilities/creep/frogmen_acid_jump.lua" +__TS__ClassExtends(modifier_acid_blob_jump, BaseModifierMotionBoth) +function modifier_acid_blob_jump.prototype.____constructor(self, ...) + BaseModifierMotionBoth.prototype.____constructor(self, ...) + self.bHorizontalMotionInterrupted = false + self.flCurrentTimeHoriz = 0 + self.flCurrentTimeVert = 0 + self.flInitialVelocityZ = 0 + self.flPredictedTotalTime = 0 +end +function modifier_acid_blob_jump.prototype.IsHidden(self) + return true +end +function modifier_acid_blob_jump.prototype.IsPurgable(self) + return false +end +function modifier_acid_blob_jump.prototype.RemoveOnDeath(self) + return false +end +function modifier_acid_blob_jump.prototype.OnCreated(self, params) + if not IsServer() then + return + end + local parent = self:GetParent() + local ability = self:GetAbility() + if not parent or not ability then + self:Destroy() + return + end + self.bHorizontalMotionInterrupted = false + if not self:ApplyHorizontalMotionController() or not self:ApplyVerticalMotionController() then + self:Destroy() + return + end + self.vStartPosition = GetGroundPosition( + parent:GetOrigin(), + parent + ) + self.flCurrentTimeHoriz = 0 + self.flCurrentTimeVert = 0 + local x = params.vLocX or parent:GetOrigin().x + local y = params.vLocY or parent:GetOrigin().y + local z = params.vLocZ or parent:GetOrigin().z + self.vLoc = Vector(x, y, z) + self.vLastKnownTargetPos = self.vLoc + local duration = ability:GetSpecialValueFor("duration") + local flDesiredHeight = AMOEBA_MINIMUM_HEIGHT_ABOVE_LOWEST * duration * duration + local flLowZ = math.min(self.vLastKnownTargetPos.z, self.vStartPosition.z) + local flHighZ = math.max(self.vLastKnownTargetPos.z, self.vStartPosition.z) + local flArcTopZ = math.max(flLowZ + flDesiredHeight, flHighZ + AMOEBA_MINIMUM_HEIGHT_ABOVE_HIGHEST) + local flArcDeltaZ = flArcTopZ - self.vStartPosition.z + self.flInitialVelocityZ = math.sqrt(2 * flArcDeltaZ * AMOEBA_ACCELERATION_Z) + local flDeltaZ = self.vLastKnownTargetPos.z - self.vStartPosition.z + local flSqrtDet = math.sqrt(math.max(0, self.flInitialVelocityZ * self.flInitialVelocityZ - 2 * AMOEBA_ACCELERATION_Z * flDeltaZ)) + self.flPredictedTotalTime = math.max((self.flInitialVelocityZ + flSqrtDet) / AMOEBA_ACCELERATION_Z, (self.flInitialVelocityZ - flSqrtDet) / AMOEBA_ACCELERATION_Z) + self.vHorizontalVelocity = (self.vLastKnownTargetPos - self.vStartPosition) / self.flPredictedTotalTime + self.vHorizontalVelocity.z = 0 +end +function modifier_acid_blob_jump.prototype.OnDestroy(self) + if not IsServer() then + return + end + local parent = self:GetParent() + if parent ~= nil then + parent:RemoveHorizontalMotionController(self) + parent:RemoveVerticalMotionController(self) + end +end +function modifier_acid_blob_jump.prototype.CheckState(self) + return {[MODIFIER_STATE_STUNNED] = true, [MODIFIER_STATE_UNSELECTABLE] = true} +end +function modifier_acid_blob_jump.prototype.UpdateHorizontalMotion(self, me, dt) + if not IsServer() then + return + end + self.flCurrentTimeHoriz = math.min(self.flCurrentTimeHoriz + dt, self.flPredictedTotalTime) + local t = self.flCurrentTimeHoriz / self.flPredictedTotalTime + local vStartToTarget = self.vLastKnownTargetPos - self.vStartPosition + local vDesiredPos = self.vStartPosition + vStartToTarget * t + local vOldPos = me:GetOrigin() + local vToDesired = vDesiredPos - vOldPos + vToDesired.z = 0 + local vDesiredVel = vToDesired / dt + local vVelDif = vDesiredVel - self.vHorizontalVelocity + local flVelDif = vVelDif:Length2D() + vVelDif = vVelDif:Normalized() + local flVelDelta = math.min(flVelDif, AMOEBA_MAX_HORIZONTAL_ACCELERATION) + self.vHorizontalVelocity = self.vHorizontalVelocity + vVelDif * flVelDelta * dt + local vNewPos = vOldPos + self.vHorizontalVelocity * dt + me:SetOrigin(vNewPos) +end +function modifier_acid_blob_jump.prototype.UpdateVerticalMotion(self, me, dt) + if not IsServer() then + return + end + self.flCurrentTimeVert = self.flCurrentTimeVert + dt + local bGoingDown = -AMOEBA_ACCELERATION_Z * self.flCurrentTimeVert + self.flInitialVelocityZ < 0 + local vNewPos = me:GetOrigin() + vNewPos.z = self.vStartPosition.z + (-0.5 * AMOEBA_ACCELERATION_Z * self.flCurrentTimeVert * self.flCurrentTimeVert + self.flInitialVelocityZ * self.flCurrentTimeVert) + local flGroundHeight = GetGroundHeight( + vNewPos, + self:GetParent() + ) + local bLanded = false + if vNewPos.z < flGroundHeight and bGoingDown then + vNewPos.z = flGroundHeight + bLanded = true + end + me:SetOrigin(vNewPos) + if bLanded then + if not self.bHorizontalMotionInterrupted then + local ability = self:GetAbility() + if ability then + ability:Smash() + end + end + self:Destroy() + end +end +function modifier_acid_blob_jump.prototype.OnHorizontalMotionInterrupted(self) + if not IsServer() then + return + end + self.bHorizontalMotionInterrupted = true +end +function modifier_acid_blob_jump.prototype.OnVerticalMotionInterrupted(self) + if not IsServer() then + return + end + self:Destroy() +end +function modifier_acid_blob_jump.prototype.DeclareFunctions(self) + return {MODIFIER_PROPERTY_OVERRIDE_ANIMATION} +end +function modifier_acid_blob_jump.prototype.GetOverrideAnimation(self) + return ACT_DOTA_CAST_ABILITY_1 +end +modifier_acid_blob_jump = __TS__Decorate( + modifier_acid_blob_jump, + modifier_acid_blob_jump, + {registerModifier(nil)}, + {kind = "class", name = "modifier_acid_blob_jump"} +) +____exports.modifier_acid_blob_jump = modifier_acid_blob_jump +return ____exports diff --git a/scripts/vscripts/abilities/creep/ghost_evasive.lua b/scripts/vscripts/abilities/creep/ghost_evasive.lua new file mode 100644 index 0000000..cc50e69 --- /dev/null +++ b/scripts/vscripts/abilities/creep/ghost_evasive.lua @@ -0,0 +1,60 @@ +local ____lualib = require("lualib_bundle") +local __TS__Class = ____lualib.__TS__Class +local __TS__ClassExtends = ____lualib.__TS__ClassExtends +local __TS__Decorate = ____lualib.__TS__Decorate +local ____exports = {} +local ____dota_ts_adapter = require("lib.dota_ts_adapter") +local BaseAbility = ____dota_ts_adapter.BaseAbility +local BaseModifier = ____dota_ts_adapter.BaseModifier +local registerAbility = ____dota_ts_adapter.registerAbility +local registerModifier = ____dota_ts_adapter.registerModifier +____exports.ghost_evasive = __TS__Class() +local ghost_evasive = ____exports.ghost_evasive +ghost_evasive.name = "ghost_evasive" +ghost_evasive.____file_path = "scripts/vscripts/abilities/creep/ghost_evasive.lua" +__TS__ClassExtends(ghost_evasive, BaseAbility) +function ghost_evasive.prototype.GetIntrinsicModifierName(self) + return "modifier_ghost_evasive_passive" +end +ghost_evasive = __TS__Decorate( + ghost_evasive, + ghost_evasive, + {registerAbility(nil)}, + {kind = "class", name = "ghost_evasive"} +) +____exports.ghost_evasive = ghost_evasive +____exports.modifier_ghost_evasive_passive = __TS__Class() +local modifier_ghost_evasive_passive = ____exports.modifier_ghost_evasive_passive +modifier_ghost_evasive_passive.name = "modifier_ghost_evasive_passive" +modifier_ghost_evasive_passive.____file_path = "scripts/vscripts/abilities/creep/ghost_evasive.lua" +__TS__ClassExtends(modifier_ghost_evasive_passive, BaseModifier) +function modifier_ghost_evasive_passive.prototype.IsHidden(self) + return true +end +function modifier_ghost_evasive_passive.prototype.IsDebuff(self) + return false +end +function modifier_ghost_evasive_passive.prototype.IsPurgable(self) + return false +end +function modifier_ghost_evasive_passive.prototype.DeclareFunctions(self) + return {MODIFIER_PROPERTY_EVASION_CONSTANT} +end +function modifier_ghost_evasive_passive.prototype.GetModifierEvasion_Constant(self, event) + local ab = self:GetAbility() + if not ab then + return 0 + end + return ab:GetSpecialValueFor("evasive") +end +function modifier_ghost_evasive_passive.prototype.CheckState(self) + return {[MODIFIER_STATE_FLYING] = true, [MODIFIER_STATE_NO_UNIT_COLLISION] = true} +end +modifier_ghost_evasive_passive = __TS__Decorate( + modifier_ghost_evasive_passive, + modifier_ghost_evasive_passive, + {registerModifier(nil)}, + {kind = "class", name = "modifier_ghost_evasive_passive"} +) +____exports.modifier_ghost_evasive_passive = modifier_ghost_evasive_passive +return ____exports diff --git a/scripts/vscripts/abilities/creep/kaban_rofl_ability.lua b/scripts/vscripts/abilities/creep/kaban_rofl_ability.lua new file mode 100644 index 0000000..dc642e3 --- /dev/null +++ b/scripts/vscripts/abilities/creep/kaban_rofl_ability.lua @@ -0,0 +1,158 @@ +local ____lualib = require("lualib_bundle") +local __TS__Class = ____lualib.__TS__Class +local __TS__ClassExtends = ____lualib.__TS__ClassExtends +local __TS__Decorate = ____lualib.__TS__Decorate +local ____exports = {} +local ____dota_ts_adapter = require("lib.dota_ts_adapter") +local BaseAbility = ____dota_ts_adapter.BaseAbility +local BaseModifier = ____dota_ts_adapter.BaseModifier +local registerAbility = ____dota_ts_adapter.registerAbility +local registerModifier = ____dota_ts_adapter.registerModifier +____exports.kaban_rofl_ability = __TS__Class() +local kaban_rofl_ability = ____exports.kaban_rofl_ability +kaban_rofl_ability.name = "kaban_rofl_ability" +kaban_rofl_ability.____file_path = "scripts/vscripts/abilities/creep/kaban_rofl_ability.lua" +__TS__ClassExtends(kaban_rofl_ability, BaseAbility) +function kaban_rofl_ability.prototype.GetIntrinsicModifierName(self) + return "modifier_kaban_rofl_ability" +end +kaban_rofl_ability = __TS__Decorate( + kaban_rofl_ability, + kaban_rofl_ability, + {registerAbility(nil)}, + {kind = "class", name = "kaban_rofl_ability"} +) +____exports.kaban_rofl_ability = kaban_rofl_ability +____exports.modifier_kaban_rofl_ability = __TS__Class() +local modifier_kaban_rofl_ability = ____exports.modifier_kaban_rofl_ability +modifier_kaban_rofl_ability.name = "modifier_kaban_rofl_ability" +modifier_kaban_rofl_ability.____file_path = "scripts/vscripts/abilities/creep/kaban_rofl_ability.lua" +__TS__ClassExtends(modifier_kaban_rofl_ability, BaseModifier) +function modifier_kaban_rofl_ability.prototype.IsHidden(self) + return true +end +function modifier_kaban_rofl_ability.prototype.OnCreated(self, params) + Timers:CreateTimer( + 30, + function() + UTIL_Remove(self:GetParent()) + end + ) +end +function modifier_kaban_rofl_ability.prototype.DeclareFunctions(self) + return {MODIFIER_EVENT_ON_TAKEDAMAGE, MODIFIER_PROPERTY_MIN_HEALTH} +end +function modifier_kaban_rofl_ability.prototype.CheckState(self) + return {[MODIFIER_STATE_NO_HEALTH_BAR] = true} +end +function modifier_kaban_rofl_ability.prototype.GetMinHealth(self) + return 1 +end +function modifier_kaban_rofl_ability.prototype.OnTakeDamage(self, event) + if not IsServer() then + return + end + if event.attacker ~= self:GetParent():GetOwner() then + return + end + if event.unit ~= self:GetParent() then + return + end + local parent = self:GetParent() + local parentPos = parent:GetAbsOrigin() + local attacker = event.attacker + local hasEasterEgg = self:GetParent():HasModifier("modifier_easter_egg_dropped") + if not hasEasterEgg and RandomFloat(0, 100) <= 2 then + local easterEgg = CreateItem("item_easter_egg", nil, nil) + if easterEgg then + local physicalItem = CreateItemOnPositionForLaunch(parentPos, easterEgg) + if physicalItem ~= nil then + local randomAngle = RandomFloat(0, 2 * math.pi) + local randomDistance = RandomFloat(100, 500) + local randomX = parentPos.x + randomDistance * math.cos(randomAngle) + local randomY = parentPos.y + randomDistance * math.sin(randomAngle) + local randomPos = Vector(randomX, randomY, parentPos.z) + local dropRadius = RandomFloat(50, 100) + easterEgg:LaunchLootInitialHeight( + false, + 0, + 150, + 0.5, + randomPos + RandomVector(dropRadius) + ) + self:GetParent():AddNewModifier( + self:GetParent(), + getModifierSourceAbility( + nil, + self:GetParent() + ), + "modifier_easter_egg_dropped", + {} + ) + end + end + elseif RandomFloat(0, 100) <= 20 then + local item = CreateItem("item_candy", nil, nil) + if item then + local physicalItem = CreateItemOnPositionForLaunch(parentPos, item) + if physicalItem ~= nil then + local randomAngle = RandomFloat(0, 2 * math.pi) + local randomDistance = RandomFloat(100, 500) + local randomX = parentPos.x + randomDistance * math.cos(randomAngle) + local randomY = parentPos.y + randomDistance * math.sin(randomAngle) + local randomPos = Vector(randomX, randomY, parentPos.z) + local dropRadius = RandomFloat(50, 100) + item:LaunchLootInitialHeight( + false, + 0, + 150, + 0.5, + randomPos + RandomVector(dropRadius) + ) + Timers:CreateTimer( + 30, + function() + if IsValidEntity(physicalItem) then + UTIL_Remove(physicalItem) + end + end + ) + end + end + end + if attacker and attacker:IsAlive() then + self:MakeUnitFlee(parent, attacker) + end +end +function modifier_kaban_rofl_ability.prototype.MakeUnitFlee(self, fleeingUnit, fromUnit) + local fleeingPos = fleeingUnit:GetAbsOrigin() + local fromPos = fromUnit:GetAbsOrigin() + local directionX = fleeingPos.x - fromPos.x + local directionY = fleeingPos.y - fromPos.y + local distance = math.sqrt(directionX * directionX + directionY * directionY) + if distance > 0 then + local normalizedX = directionX / distance + local normalizedY = directionY / distance + local fleeDistance = math.min(800, distance * 2) + local fleeX = fleeingPos.x + normalizedX * fleeDistance + local fleeY = fleeingPos.y + normalizedY * fleeDistance + local fleePos = Vector(fleeX, fleeY, fleeingPos.z) + local groundHeight = GetGroundHeight(fleePos, nil) + local validPos = Vector(fleeX, fleeY, groundHeight) + fleeingUnit:MoveToPosition(toVectorWS(nil, validPos)) + fleeingUnit:AddNewModifier( + fleeingUnit, + getModifierSourceAbility(nil, fleeingUnit), + "modifier_phased", + {duration = 2} + ) + end +end +modifier_kaban_rofl_ability = __TS__Decorate( + modifier_kaban_rofl_ability, + modifier_kaban_rofl_ability, + {registerModifier(nil)}, + {kind = "class", name = "modifier_kaban_rofl_ability"} +) +____exports.modifier_kaban_rofl_ability = modifier_kaban_rofl_ability +return ____exports diff --git a/scripts/vscripts/abilities/creep/modifier_boss_nevermore_coil_debuff.lua b/scripts/vscripts/abilities/creep/modifier_boss_nevermore_coil_debuff.lua new file mode 100644 index 0000000..9f6c24b --- /dev/null +++ b/scripts/vscripts/abilities/creep/modifier_boss_nevermore_coil_debuff.lua @@ -0,0 +1,54 @@ +local ____lualib = require("lualib_bundle") +local __TS__Class = ____lualib.__TS__Class +local __TS__ClassExtends = ____lualib.__TS__ClassExtends +local __TS__Decorate = ____lualib.__TS__Decorate +local ____exports = {} +local ____dota_ts_adapter = require("lib.dota_ts_adapter") +local BaseModifier = ____dota_ts_adapter.BaseModifier +local registerModifier = ____dota_ts_adapter.registerModifier +____exports.modifier_boss_nevermore_coil_debuff = __TS__Class() +local modifier_boss_nevermore_coil_debuff = ____exports.modifier_boss_nevermore_coil_debuff +modifier_boss_nevermore_coil_debuff.name = "modifier_boss_nevermore_coil_debuff" +modifier_boss_nevermore_coil_debuff.____file_path = "scripts/vscripts/abilities/creep/modifier_boss_nevermore_coil_debuff.lua" +__TS__ClassExtends(modifier_boss_nevermore_coil_debuff, BaseModifier) +function modifier_boss_nevermore_coil_debuff.prototype.IsHidden(self) + return false +end +function modifier_boss_nevermore_coil_debuff.prototype.IsPurgable(self) + return true +end +function modifier_boss_nevermore_coil_debuff.prototype.IsDebuff(self) + return true +end +function modifier_boss_nevermore_coil_debuff.prototype.DeclareFunctions(self) + return {MODIFIER_PROPERTY_MOVESPEED_BONUS_PERCENTAGE, MODIFIER_PROPERTY_TOOLTIP} +end +function modifier_boss_nevermore_coil_debuff.prototype.GetModifierMoveSpeedBonus_Percentage(self) + local ability = self:GetAbility() + if not ability then + return 0 + end + local slowPerStack = ability:GetSpecialValueFor("coil_slow_per_stack") + return -slowPerStack * self:GetStackCount() +end +function modifier_boss_nevermore_coil_debuff.prototype.OnTooltip(self) + local ability = self:GetAbility() + if not ability then + return 0 + end + return ability:GetSpecialValueFor("coil_stack_bonus_damage") * self:GetStackCount() +end +function modifier_boss_nevermore_coil_debuff.prototype.GetEffectName(self) + return "particles/units/heroes/hero_nevermore/nevermore_requiemofsouls_debuff.vpcf" +end +function modifier_boss_nevermore_coil_debuff.prototype.GetEffectAttachType(self) + return PATTACH_ABSORIGIN_FOLLOW +end +modifier_boss_nevermore_coil_debuff = __TS__Decorate( + modifier_boss_nevermore_coil_debuff, + modifier_boss_nevermore_coil_debuff, + {registerModifier(nil)}, + {kind = "class", name = "modifier_boss_nevermore_coil_debuff"} +) +____exports.modifier_boss_nevermore_coil_debuff = modifier_boss_nevermore_coil_debuff +return ____exports diff --git a/scripts/vscripts/abilities/creep/modifier_boss_nevermore_debuff_immune.lua b/scripts/vscripts/abilities/creep/modifier_boss_nevermore_debuff_immune.lua new file mode 100644 index 0000000..f5b561b --- /dev/null +++ b/scripts/vscripts/abilities/creep/modifier_boss_nevermore_debuff_immune.lua @@ -0,0 +1,47 @@ +local ____lualib = require("lualib_bundle") +local __TS__Class = ____lualib.__TS__Class +local __TS__ClassExtends = ____lualib.__TS__ClassExtends +local __TS__Decorate = ____lualib.__TS__Decorate +local ____exports = {} +local ____dota_ts_adapter = require("lib.dota_ts_adapter") +local BaseModifier = ____dota_ts_adapter.BaseModifier +local registerModifier = ____dota_ts_adapter.registerModifier +--- Только для npc_boss_nevermore: без DEBUFF_IMMUNE на общем no_healthbar (Луна и др.), +-- чтобы там не ломать диспелы. Боссу сайленс/орчид и прочие дебаффы с героев не цепляются. +____exports.modifier_boss_nevermore_debuff_immune = __TS__Class() +local modifier_boss_nevermore_debuff_immune = ____exports.modifier_boss_nevermore_debuff_immune +modifier_boss_nevermore_debuff_immune.name = "modifier_boss_nevermore_debuff_immune" +modifier_boss_nevermore_debuff_immune.____file_path = "scripts/vscripts/abilities/creep/modifier_boss_nevermore_debuff_immune.lua" +__TS__ClassExtends(modifier_boss_nevermore_debuff_immune, BaseModifier) +function modifier_boss_nevermore_debuff_immune.prototype.IsHidden(self) + return true +end +function modifier_boss_nevermore_debuff_immune.prototype.IsPurgable(self) + return false +end +function modifier_boss_nevermore_debuff_immune.prototype.IsDebuff(self) + return false +end +function modifier_boss_nevermore_debuff_immune.prototype.GetPriority(self) + return MODIFIER_PRIORITY_SUPER_ULTRA +end +function modifier_boss_nevermore_debuff_immune.prototype.CheckState(self) + return {[MODIFIER_STATE_SILENCED] = false, [MODIFIER_STATE_ROOTED] = false} +end +function modifier_boss_nevermore_debuff_immune.prototype.DeclareFunctions(self) + return {MODIFIER_PROPERTY_PROVIDES_FOW_POSITION} +end +function modifier_boss_nevermore_debuff_immune.prototype.providesFOWPosition(self) + return true +end +function modifier_boss_nevermore_debuff_immune.prototype.GetModifierProvidesFOWVision(self) + return 1 +end +modifier_boss_nevermore_debuff_immune = __TS__Decorate( + modifier_boss_nevermore_debuff_immune, + modifier_boss_nevermore_debuff_immune, + {registerModifier(nil)}, + {kind = "class", name = "modifier_boss_nevermore_debuff_immune"} +) +____exports.modifier_boss_nevermore_debuff_immune = modifier_boss_nevermore_debuff_immune +return ____exports diff --git a/scripts/vscripts/abilities/creep/modifier_boss_nevermore_phase_terror_wave.lua b/scripts/vscripts/abilities/creep/modifier_boss_nevermore_phase_terror_wave.lua new file mode 100644 index 0000000..72b824b --- /dev/null +++ b/scripts/vscripts/abilities/creep/modifier_boss_nevermore_phase_terror_wave.lua @@ -0,0 +1,152 @@ +local ____lualib = require("lualib_bundle") +local __TS__Class = ____lualib.__TS__Class +local __TS__ClassExtends = ____lualib.__TS__ClassExtends +local __TS__Decorate = ____lualib.__TS__Decorate +local ____exports = {} +local ____dota_ts_adapter = require("lib.dota_ts_adapter") +local BaseModifier = ____dota_ts_adapter.BaseModifier +local registerModifier = ____dota_ts_adapter.registerModifier +local ____nevermore_boss_requiem_bridge = require("ai.nevermore_boss_requiem_bridge") +local tryForceNevermoreRequiemCast = ____nevermore_boss_requiem_bridge.tryForceNevermoreRequiemCast +local TERROR_WAVE_ABILITY = "terrorblade_terror_wave" +--- Задержка между несколькими волнами, если за один удар пересечено несколько порогов HP. +local MULTI_WAVE_STAGGER = 0.22 +--- После последней Terror Wave — пауза перед телепортом в центр и реквиемом. +local AFTER_TERROR_WAVE_TO_REQUIEM = 0.72 +--- Фаза босса Nevermore по HP (как у coil_wave / ИИ). +local function getNevermoreBossHpPhase(self, unit) + local hp = unit:GetHealthPercent() + if hp <= 25 then + return 4 + end + if hp <= 50 then + return 3 + end + if hp <= 75 then + return 2 + end + return 1 +end +local function tryCastTerrorWave(self, boss) + if not IsServer() then + return + end + if not boss or boss:IsNull() or not boss:IsAlive() then + return + end + local ab = boss:FindAbilityByName(TERROR_WAVE_ABILITY) + if not ab or ab:IsNull() then + return + end + if ab:GetLevel() < 1 then + ab:SetLevel(1) + end + ab:EndCooldown() + ExecuteOrderFromTable({ + UnitIndex = boss:entindex(), + OrderType = DOTA_UNIT_ORDER_CAST_NO_TARGET, + AbilityIndex = ab:entindex() + }) +end +--- Вешается на npc_boss_nevermore: при каждом переходе на новую фазу по HP — Terror Wave. +function ____exports.applyNevermorePhaseTerrorWave(self, boss) + if not IsServer() or not boss or boss:IsNull() or not boss:IsAlive() then + return + end + if boss:HasModifier(____exports.modifier_boss_nevermore_phase_terror_wave.name) then + return + end + boss:AddNewModifier( + boss, + getModifierSourceAbility(nil, boss), + ____exports.modifier_boss_nevermore_phase_terror_wave.name, + {} + ) +end +____exports.modifier_boss_nevermore_phase_terror_wave = __TS__Class() +local modifier_boss_nevermore_phase_terror_wave = ____exports.modifier_boss_nevermore_phase_terror_wave +modifier_boss_nevermore_phase_terror_wave.name = "modifier_boss_nevermore_phase_terror_wave" +modifier_boss_nevermore_phase_terror_wave.____file_path = "scripts/vscripts/abilities/creep/modifier_boss_nevermore_phase_terror_wave.lua" +__TS__ClassExtends(modifier_boss_nevermore_phase_terror_wave, BaseModifier) +function modifier_boss_nevermore_phase_terror_wave.prototype.____constructor(self, ...) + BaseModifier.prototype.____constructor(self, ...) + self.storedPhase = 1 +end +function modifier_boss_nevermore_phase_terror_wave.prototype.IsHidden(self) + return true +end +function modifier_boss_nevermore_phase_terror_wave.prototype.IsPurgable(self) + return false +end +function modifier_boss_nevermore_phase_terror_wave.prototype.RemoveOnDeath(self) + return true +end +function modifier_boss_nevermore_phase_terror_wave.prototype.OnCreated(self) + if not IsServer() then + return + end + self.parent = self:GetParent() + self.storedPhase = getNevermoreBossHpPhase(nil, self.parent) + self:StartIntervalThink(0.2) +end +function modifier_boss_nevermore_phase_terror_wave.prototype.DeclareFunctions(self) + return {MODIFIER_EVENT_ON_TAKEDAMAGE} +end +function modifier_boss_nevermore_phase_terror_wave.prototype.OnTakeDamage(self) + self:checkPhaseTransitions() +end +function modifier_boss_nevermore_phase_terror_wave.prototype.OnIntervalThink(self) + if not IsValidEntity(self.parent) or not self.parent:IsAlive() then + self:StartIntervalThink(-1) + return + end + self:checkPhaseTransitions() +end +function modifier_boss_nevermore_phase_terror_wave.prototype.checkPhaseTransitions(self) + if not IsServer() then + return + end + if not IsValidEntity(self.parent) or not self.parent:IsAlive() then + return + end + local newPhase = getNevermoreBossHpPhase(nil, self.parent) + if newPhase <= self.storedPhase then + return + end + local wavesToFire = newPhase - self.storedPhase + self.storedPhase = newPhase + local entIndex = self.parent:entindex() + do + local i = 0 + while i < wavesToFire do + local delay = i * MULTI_WAVE_STAGGER + Timers:CreateTimer( + delay, + function() + local u = EntIndexToHScript(entIndex) + tryCastTerrorWave(nil, u) + end + ) + i = i + 1 + end + end + local lastWaveDelay = wavesToFire > 0 and (wavesToFire - 1) * MULTI_WAVE_STAGGER or 0 + local requiemDelay = lastWaveDelay + AFTER_TERROR_WAVE_TO_REQUIEM + Timers:CreateTimer( + requiemDelay, + function() + local u = EntIndexToHScript(entIndex) + if u and not u:IsNull() and u:IsAlive() then + tryForceNevermoreRequiemCast(nil, u) + end + end + ) +end +modifier_boss_nevermore_phase_terror_wave = __TS__Decorate( + modifier_boss_nevermore_phase_terror_wave, + modifier_boss_nevermore_phase_terror_wave, + {registerModifier(nil)}, + {kind = "class", name = "modifier_boss_nevermore_phase_terror_wave"} +) +____exports.modifier_boss_nevermore_phase_terror_wave = modifier_boss_nevermore_phase_terror_wave +return ____exports diff --git a/scripts/vscripts/abilities/creep/modifier_boss_nevermore_requiem_gate.lua b/scripts/vscripts/abilities/creep/modifier_boss_nevermore_requiem_gate.lua new file mode 100644 index 0000000..aa20bf8 --- /dev/null +++ b/scripts/vscripts/abilities/creep/modifier_boss_nevermore_requiem_gate.lua @@ -0,0 +1,116 @@ +local ____lualib = require("lualib_bundle") +local __TS__Class = ____lualib.__TS__Class +local __TS__ClassExtends = ____lualib.__TS__ClassExtends +local __TS__Decorate = ____lualib.__TS__Decorate +local ____exports = {} +local ____dota_ts_adapter = require("lib.dota_ts_adapter") +local BaseModifier = ____dota_ts_adapter.BaseModifier +local registerModifier = ____dota_ts_adapter.registerModifier +local ____nevermore_boss_requiem_bridge = require("ai.nevermore_boss_requiem_bridge") +local NEVERMORE_REQUIEM_DAMAGE_REDUCTION_PCT = ____nevermore_boss_requiem_bridge.NEVERMORE_REQUIEM_DAMAGE_REDUCTION_PCT +local NEVERMORE_REQUIEM_HP_THRESHOLD = ____nevermore_boss_requiem_bridge.NEVERMORE_REQUIEM_HP_THRESHOLD +local NEVERMORE_REQUIEM_REQUIRED_CASTS = ____nevermore_boss_requiem_bridge.NEVERMORE_REQUIEM_REQUIRED_CASTS +local nevermoreGetRequiemCastCount = ____nevermore_boss_requiem_bridge.nevermoreGetRequiemCastCount +local nevermoreNeedsMandatoryRequiem = ____nevermore_boss_requiem_bridge.nevermoreNeedsMandatoryRequiem +local tryForceNevermoreRequiemCast = ____nevermore_boss_requiem_bridge.tryForceNevermoreRequiemCast +--- Снижение входящего урона, пока HP < 50% и реквием не скастован 3 раза; дожим каста ульты. +____exports.modifier_boss_nevermore_requiem_gate = __TS__Class() +local modifier_boss_nevermore_requiem_gate = ____exports.modifier_boss_nevermore_requiem_gate +modifier_boss_nevermore_requiem_gate.name = "modifier_boss_nevermore_requiem_gate" +modifier_boss_nevermore_requiem_gate.____file_path = "scripts/vscripts/abilities/creep/modifier_boss_nevermore_requiem_gate.lua" +__TS__ClassExtends(modifier_boss_nevermore_requiem_gate, BaseModifier) +function modifier_boss_nevermore_requiem_gate.prototype.____constructor(self, ...) + BaseModifier.prototype.____constructor(self, ...) + self.nextForceCastAt = 0 +end +function modifier_boss_nevermore_requiem_gate.prototype.IsHidden(self) + return false +end +function modifier_boss_nevermore_requiem_gate.prototype.IsPurgable(self) + return false +end +function modifier_boss_nevermore_requiem_gate.prototype.IsDebuff(self) + return false +end +function modifier_boss_nevermore_requiem_gate.prototype.RemoveOnDeath(self) + return true +end +function modifier_boss_nevermore_requiem_gate.prototype.OnCreated(self) + if not IsServer() then + return + end + self.parent = self:GetParent() + self:syncStacks() + self:StartIntervalThink(0.35) +end +function modifier_boss_nevermore_requiem_gate.prototype.OnIntervalThink(self) + if not IsServer() then + return + end + if not IsValidEntity(self.parent) or not self.parent:IsAlive() then + self:StartIntervalThink(-1) + return + end + self:syncStacks() + if not nevermoreNeedsMandatoryRequiem(nil, self.parent) then + return + end + local now = GameRules:GetGameTime() + if now < self.nextForceCastAt then + return + end + if self.parent:IsChanneling() then + return + end + if tryForceNevermoreRequiemCast(nil, self.parent) then + self.nextForceCastAt = now + 5 + else + self.nextForceCastAt = now + 1.25 + end +end +function modifier_boss_nevermore_requiem_gate.prototype.DeclareFunctions(self) + return {MODIFIER_PROPERTY_INCOMING_DAMAGE_PERCENTAGE, MODIFIER_EVENT_ON_TAKEDAMAGE} +end +function modifier_boss_nevermore_requiem_gate.prototype.OnTakeDamage(self) + if not IsServer() then + return + end + self:syncStacks() +end +function modifier_boss_nevermore_requiem_gate.prototype.GetModifierIncomingDamage_Percentage(self, event) + if event.target ~= self.parent then + return 0 + end + if nevermoreGetRequiemCastCount(nil, self.parent) >= NEVERMORE_REQUIEM_REQUIRED_CASTS then + return 0 + end + if self.parent:GetHealthPercent() >= NEVERMORE_REQUIEM_HP_THRESHOLD then + return 0 + end + return -NEVERMORE_REQUIEM_DAMAGE_REDUCTION_PCT +end +function modifier_boss_nevermore_requiem_gate.prototype.syncStacks(self) + self:SetStackCount(nevermoreGetRequiemCastCount(nil, self.parent)) +end +modifier_boss_nevermore_requiem_gate = __TS__Decorate( + modifier_boss_nevermore_requiem_gate, + modifier_boss_nevermore_requiem_gate, + {registerModifier(nil)}, + {kind = "class", name = "modifier_boss_nevermore_requiem_gate"} +) +____exports.modifier_boss_nevermore_requiem_gate = modifier_boss_nevermore_requiem_gate +function ____exports.applyNevermoreRequiemGate(self, boss) + if not IsServer() or not boss or boss:IsNull() or not boss:IsAlive() then + return + end + if boss:HasModifier(____exports.modifier_boss_nevermore_requiem_gate.name) then + return + end + boss:AddNewModifier( + boss, + getModifierSourceAbility(nil, boss), + ____exports.modifier_boss_nevermore_requiem_gate.name, + {} + ) +end +return ____exports diff --git a/scripts/vscripts/abilities/creep/modifier_boss_nevermore_requiem_magic_resist_debuff.lua b/scripts/vscripts/abilities/creep/modifier_boss_nevermore_requiem_magic_resist_debuff.lua new file mode 100644 index 0000000..dcc40da --- /dev/null +++ b/scripts/vscripts/abilities/creep/modifier_boss_nevermore_requiem_magic_resist_debuff.lua @@ -0,0 +1,57 @@ +local ____lualib = require("lualib_bundle") +local __TS__Class = ____lualib.__TS__Class +local __TS__ClassExtends = ____lualib.__TS__ClassExtends +local __TS__Decorate = ____lualib.__TS__Decorate +local ____exports = {} +local ____dota_ts_adapter = require("lib.dota_ts_adapter") +local BaseModifier = ____dota_ts_adapter.BaseModifier +local registerModifier = ____dota_ts_adapter.registerModifier +____exports.modifier_boss_nevermore_requiem_magic_resist_debuff = __TS__Class() +local modifier_boss_nevermore_requiem_magic_resist_debuff = ____exports.modifier_boss_nevermore_requiem_magic_resist_debuff +modifier_boss_nevermore_requiem_magic_resist_debuff.name = "modifier_boss_nevermore_requiem_magic_resist_debuff" +modifier_boss_nevermore_requiem_magic_resist_debuff.____file_path = "scripts/vscripts/abilities/creep/modifier_boss_nevermore_requiem_magic_resist_debuff.lua" +__TS__ClassExtends(modifier_boss_nevermore_requiem_magic_resist_debuff, BaseModifier) +function modifier_boss_nevermore_requiem_magic_resist_debuff.prototype.IsHidden(self) + return false +end +function modifier_boss_nevermore_requiem_magic_resist_debuff.prototype.IsDebuff(self) + return true +end +function modifier_boss_nevermore_requiem_magic_resist_debuff.prototype.IsPurgable(self) + return false +end +function modifier_boss_nevermore_requiem_magic_resist_debuff.prototype.RemoveOnDeath(self) + return false +end +function modifier_boss_nevermore_requiem_magic_resist_debuff.prototype.DeclareFunctions(self) + return {MODIFIER_PROPERTY_MAGICAL_RESISTANCE_DECREPIFY_UNIQUE, MODIFIER_PROPERTY_TOOLTIP} +end +function modifier_boss_nevermore_requiem_magic_resist_debuff.prototype.GetModifierMagicalResistanceDecrepifyUnique(self) + local ability = self:GetAbility() + if not ability then + return 0 + end + local reducePerStack = ability:GetSpecialValueFor("permanent_magic_resist_reduce_per_hit") + return -reducePerStack * self:GetStackCount() +end +function modifier_boss_nevermore_requiem_magic_resist_debuff.prototype.OnTooltip(self) + local ability = self:GetAbility() + if not ability then + return 0 + end + return -ability:GetSpecialValueFor("permanent_magic_resist_reduce_per_hit") * self:GetStackCount() +end +function modifier_boss_nevermore_requiem_magic_resist_debuff.prototype.GetEffectName(self) + return "particles/items4_fx/nullifier_mute_debuff.vpcf" +end +function modifier_boss_nevermore_requiem_magic_resist_debuff.prototype.GetEffectAttachType(self) + return PATTACH_ABSORIGIN_FOLLOW +end +modifier_boss_nevermore_requiem_magic_resist_debuff = __TS__Decorate( + modifier_boss_nevermore_requiem_magic_resist_debuff, + modifier_boss_nevermore_requiem_magic_resist_debuff, + {registerModifier(nil)}, + {kind = "class", name = "modifier_boss_nevermore_requiem_magic_resist_debuff"} +) +____exports.modifier_boss_nevermore_requiem_magic_resist_debuff = modifier_boss_nevermore_requiem_magic_resist_debuff +return ____exports diff --git a/scripts/vscripts/abilities/creep/sheep_coil.lua b/scripts/vscripts/abilities/creep/sheep_coil.lua new file mode 100644 index 0000000..f82efce --- /dev/null +++ b/scripts/vscripts/abilities/creep/sheep_coil.lua @@ -0,0 +1,242 @@ +local ____lualib = require("lualib_bundle") +local __TS__Class = ____lualib.__TS__Class +local __TS__ClassExtends = ____lualib.__TS__ClassExtends +local __TS__Decorate = ____lualib.__TS__Decorate +local ____exports = {} +local ____dota_ts_adapter = require("lib.dota_ts_adapter") +local BaseAbility = ____dota_ts_adapter.BaseAbility +local BaseModifier = ____dota_ts_adapter.BaseModifier +local registerModifier = ____dota_ts_adapter.registerModifier +local ____dota_ts_adapter = require("lib.dota_ts_adapter") +local registerAbility = ____dota_ts_adapter.registerAbility +____exports.sheep_coil = __TS__Class() +local sheep_coil = ____exports.sheep_coil +sheep_coil.name = "sheep_coil" +sheep_coil.____file_path = "scripts/vscripts/abilities/creep/sheep_coil.lua" +__TS__ClassExtends(sheep_coil, BaseAbility) +function sheep_coil.prototype.GetAOERadius(self) + return self:GetSpecialValueFor("radius") +end +function sheep_coil.prototype.OnSpellStart(self) + local caster = self:GetCaster() + local cursorPt = self:GetCursorPosition() + local targetPoint = GetGroundPosition(cursorPt, nil) + local spawnOrigin + do + local function ____catch() + spawnOrigin = caster:GetAbsOrigin() + end + local ____try = pcall(function() + local attachId = caster:ScriptLookupAttachment("attach_attack1") + if attachId ~= nil and attachId >= 0 then + spawnOrigin = caster:GetAttachmentOrigin(attachId) + else + spawnOrigin = caster:GetAbsOrigin() + end + end) + if not ____try then + ____catch() + end + end + local projectileSpeed = self:GetSpecialValueFor("projectile_speed") + local direction = targetPoint - spawnOrigin + direction.z = 0 + local directionLength = math.sqrt(direction.x * direction.x + direction.y * direction.y) + if directionLength < 0.01 then + return + end + direction = direction:Normalized() + local distance = directionLength + local particle = ParticleManager:CreateParticle("particles/units/heroes/hero_abaddon/abaddon_death_coil.vpcf", PATTACH_CUSTOMORIGIN, nil) + ParticleManager:SetParticleControl(particle, 0, spawnOrigin) + ParticleManager:SetParticleControl(particle, 1, targetPoint) + ParticleManager:SetParticleControl( + particle, + 2, + Vector(projectileSpeed, 0, 0) + ) + ParticleManager:SetParticleControl( + particle, + 3, + Vector(distance, 0, 0) + ) + ParticleManager:SetParticleControl( + particle, + 4, + Vector(0, 0, 0) + ) + ParticleManager:SetParticleControl(particle, 9, spawnOrigin) + ParticleManager:SetParticleControl(particle, 13, direction) + self.projectile_particle = particle + self.projectile_start_pos = spawnOrigin + self.projectile_target_pos = targetPoint + self.projectile_direction = direction + self.projectile_speed = projectileSpeed + self.projectile_distance = distance + local info = { + Ability = self, + EffectName = "", + vSpawnOrigin = spawnOrigin, + fDistance = distance, + fStartRadius = 0, + fEndRadius = 0, + Source = caster, + bHasFrontalCone = false, + bReplaceExisting = false, + iUnitTargetTeam = DOTA_UNIT_TARGET_TEAM_ENEMY, + iUnitTargetFlags = DOTA_UNIT_TARGET_FLAG_NONE, + iUnitTargetType = bit.bor(DOTA_UNIT_TARGET_HERO, DOTA_UNIT_TARGET_BASIC), + fExpireTime = GameRules:GetGameTime() + 10, + bDeleteOnHit = true, + vVelocity = direction * projectileSpeed, + iMoveSpeed = projectileSpeed, + bProvidesVision = true, + iVisionRadius = 200, + iVisionTeamNumber = caster:GetTeamNumber() + } + ProjectileManager:CreateLinearProjectile(info) + Timers:CreateTimer( + distance / projectileSpeed + 0.1, + function() + if self.projectile_particle ~= nil then + ParticleManager:DestroyParticle(self.projectile_particle, false) + ParticleManager:ReleaseParticleIndex(self.projectile_particle) + self.projectile_particle = nil + end + return nil + end + ) + self.target_point = targetPoint + local travelTime = distance / projectileSpeed + Timers:CreateTimer( + travelTime, + function() + if not self.projectile_hit then + self:OnProjectileHit(nil, targetPoint) + end + return nil + end + ) + local effectCast = ParticleManager:CreateParticle("particles/units/heroes/hero_abaddon/abaddon_death_coil_abaddon.vpcf", PATTACH_ABSORIGIN_FOLLOW, caster) + ParticleManager:ReleaseParticleIndex(effectCast) + EmitSoundOn("Hero_Abaddon.DeathCoil.Cast", caster) +end +function sheep_coil.prototype.OnProjectileThink(self, location) + if self.projectile_particle ~= nil then + local currentPos = GetGroundPosition(location, nil) + ParticleManager:SetParticleControl(self.projectile_particle, 0, currentPos) + ParticleManager:SetParticleControl(self.projectile_particle, 9, currentPos) + local startPos = self.projectile_start_pos + local targetPos = self.projectile_target_pos + local totalDist = self.projectile_distance + local currentDist = math.sqrt((currentPos.x - startPos.x) * (currentPos.x - startPos.x) + (currentPos.y - startPos.y) * (currentPos.y - startPos.y)) + local progress = math.min(currentDist / totalDist, 1) + ParticleManager:SetParticleControl( + self.projectile_particle, + 3, + Vector(totalDist * (1 - progress), 0, 0) + ) + end +end +function sheep_coil.prototype.OnProjectileHit(self, target, location) + self.projectile_hit = true + if self.projectile_particle ~= nil then + ParticleManager:DestroyParticle(self.projectile_particle, false) + ParticleManager:ReleaseParticleIndex(self.projectile_particle) + self.projectile_particle = nil + end + local caster = self:GetCaster() + local slow_duration = self:GetSpecialValueFor("slow_duration") + local radius = self:GetSpecialValueFor("radius") + local hitPoint = self.target_point or location + local units = FindUnitsInRadius( + caster:GetTeamNumber(), + hitPoint, + nil, + radius, + DOTA_UNIT_TARGET_TEAM_ENEMY, + bit.bor(DOTA_UNIT_TARGET_HERO, DOTA_UNIT_TARGET_BASIC), + DOTA_UNIT_TARGET_FLAG_NONE, + FIND_ANY_ORDER, + false + ) + for ____, unit in ipairs(units) do + ApplyDamage({ + victim = unit, + attacker = caster, + damage = caster:GetAttackDamage(), + damage_type = DAMAGE_TYPE_MAGICAL, + ability = self + }) + local existingModifier = unit:FindModifierByName("modifier_sheep_coil_slow") + if existingModifier then + existingModifier:IncrementStackCount() + existingModifier:SetDuration(slow_duration, true) + else + local modifier = unit:AddNewModifier(caster, self, "modifier_sheep_coil_slow", {duration = slow_duration}) + if modifier ~= nil then + modifier:SetStackCount(1) + end + end + EmitSoundOn("Hero_Abaddon.DeathCoil.Target", unit) + end + local particle = ParticleManager:CreateParticle("particles/units/heroes/hero_abaddon/abaddon_death_coil_explosion.vpcf", PATTACH_CUSTOMORIGIN, nil) + ParticleManager:SetParticleControl(particle, 0, hitPoint) + ParticleManager:SetParticleControl( + particle, + 1, + Vector(radius, 0, 0) + ) + ParticleManager:ReleaseParticleIndex(particle) + return true +end +function sheep_coil.prototype.GetAbilityTextureName(self) + return "abaddon_death_coil" +end +sheep_coil = __TS__Decorate( + sheep_coil, + sheep_coil, + {registerAbility(nil)}, + {kind = "class", name = "sheep_coil"} +) +____exports.sheep_coil = sheep_coil +____exports.modifier_sheep_coil_slow = __TS__Class() +local modifier_sheep_coil_slow = ____exports.modifier_sheep_coil_slow +modifier_sheep_coil_slow.name = "modifier_sheep_coil_slow" +modifier_sheep_coil_slow.____file_path = "scripts/vscripts/abilities/creep/sheep_coil.lua" +__TS__ClassExtends(modifier_sheep_coil_slow, BaseModifier) +function modifier_sheep_coil_slow.prototype.DeclareFunctions(self) + return {MODIFIER_PROPERTY_MOVESPEED_BONUS_PERCENTAGE} +end +function modifier_sheep_coil_slow.prototype.GetModifierMoveSpeedBonus_Percentage(self) + local ability = self:GetAbility() + if not ability then + return 0 + end + return ability:GetSpecialValueFor("movement_slow") * self:GetStackCount() +end +function modifier_sheep_coil_slow.prototype.OnCreated(self) + if not IsServer() then + return + end +end +function modifier_sheep_coil_slow.prototype.GetEffectName(self) + return "particles/items4_fx/nullifier_slow.vpcf" +end +function modifier_sheep_coil_slow.prototype.GetEffectAttachType(self) + return PATTACH_ABSORIGIN_FOLLOW +end +function modifier_sheep_coil_slow.prototype.RemoveOnDeath(self) + return true +end +function modifier_sheep_coil_slow.prototype.IsPurgable(self) + return true +end +modifier_sheep_coil_slow = __TS__Decorate( + modifier_sheep_coil_slow, + modifier_sheep_coil_slow, + {registerModifier(nil)}, + {kind = "class", name = "modifier_sheep_coil_slow"} +) +____exports.modifier_sheep_coil_slow = modifier_sheep_coil_slow +return ____exports diff --git a/scripts/vscripts/abilities/creep/skeleton_archer.lua b/scripts/vscripts/abilities/creep/skeleton_archer.lua new file mode 100644 index 0000000..21f5be9 --- /dev/null +++ b/scripts/vscripts/abilities/creep/skeleton_archer.lua @@ -0,0 +1,91 @@ +local ____lualib = require("lualib_bundle") +local __TS__Class = ____lualib.__TS__Class +local __TS__ClassExtends = ____lualib.__TS__ClassExtends +local __TS__Decorate = ____lualib.__TS__Decorate +local ____exports = {} +local ____dota_ts_adapter = require("lib.dota_ts_adapter") +local BaseAbility = ____dota_ts_adapter.BaseAbility +local BaseModifier = ____dota_ts_adapter.BaseModifier +local registerModifier = ____dota_ts_adapter.registerModifier +local ____dota_ts_adapter = require("lib.dota_ts_adapter") +local registerAbility = ____dota_ts_adapter.registerAbility +____exports.skeleton_archer_fire_arrow = __TS__Class() +local skeleton_archer_fire_arrow = ____exports.skeleton_archer_fire_arrow +skeleton_archer_fire_arrow.name = "skeleton_archer_fire_arrow" +skeleton_archer_fire_arrow.____file_path = "scripts/vscripts/abilities/creep/skeleton_archer.lua" +__TS__ClassExtends(skeleton_archer_fire_arrow, BaseAbility) +function skeleton_archer_fire_arrow.prototype.GetIntrinsicModifierName(self) + return "modifier_skeleton_archer_fire_arrow" +end +skeleton_archer_fire_arrow = __TS__Decorate( + skeleton_archer_fire_arrow, + skeleton_archer_fire_arrow, + {registerAbility(nil)}, + {kind = "class", name = "skeleton_archer_fire_arrow"} +) +____exports.skeleton_archer_fire_arrow = skeleton_archer_fire_arrow +____exports.modifier_skeleton_archer_fire_arrow = __TS__Class() +local modifier_skeleton_archer_fire_arrow = ____exports.modifier_skeleton_archer_fire_arrow +modifier_skeleton_archer_fire_arrow.name = "modifier_skeleton_archer_fire_arrow" +modifier_skeleton_archer_fire_arrow.____file_path = "scripts/vscripts/abilities/creep/skeleton_archer.lua" +__TS__ClassExtends(modifier_skeleton_archer_fire_arrow, BaseModifier) +function modifier_skeleton_archer_fire_arrow.prototype.____constructor(self, ...) + BaseModifier.prototype.____constructor(self, ...) + self.bonusDamage = 0 +end +function modifier_skeleton_archer_fire_arrow.prototype.IsHidden(self) + return true +end +function modifier_skeleton_archer_fire_arrow.prototype.IsPurgable(self) + return false +end +function modifier_skeleton_archer_fire_arrow.prototype.OnCreated(self, params) + if not IsServer() then + return + end + if not self:GetAbility() then + return + end + self.bonusDamage = self:GetAbility():GetSpecialValueFor("bonus_damage") +end +function modifier_skeleton_archer_fire_arrow.prototype.OnRefresh(self, params) + if not IsServer() then + return + end + if not self:GetAbility() then + return + end + self.bonusDamage = self:GetAbility():GetSpecialValueFor("bonus_damage") +end +function modifier_skeleton_archer_fire_arrow.prototype.DeclareFunctions(self) + return {MODIFIER_EVENT_ON_ATTACK_LANDED} +end +function modifier_skeleton_archer_fire_arrow.prototype.OnAttackLanded(self, event) + if not IsServer() then + return + end + if event.attacker ~= self:GetParent() then + return + end + if not event.target then + return + end + if not event.target:IsAlive() then + return + end + ApplyDamage({ + victim = event.target, + attacker = event.attacker, + damage = self.bonusDamage, + damage_type = DAMAGE_TYPE_MAGICAL, + ability = self:GetAbility() + }) +end +modifier_skeleton_archer_fire_arrow = __TS__Decorate( + modifier_skeleton_archer_fire_arrow, + modifier_skeleton_archer_fire_arrow, + {registerModifier(nil)}, + {kind = "class", name = "modifier_skeleton_archer_fire_arrow"} +) +____exports.modifier_skeleton_archer_fire_arrow = modifier_skeleton_archer_fire_arrow +return ____exports diff --git a/scripts/vscripts/abilities/creep/thief_arrow.lua b/scripts/vscripts/abilities/creep/thief_arrow.lua new file mode 100644 index 0000000..a88b73b --- /dev/null +++ b/scripts/vscripts/abilities/creep/thief_arrow.lua @@ -0,0 +1,123 @@ +local ____lualib = require("lualib_bundle") +local __TS__Class = ____lualib.__TS__Class +local __TS__ClassExtends = ____lualib.__TS__ClassExtends +local __TS__Decorate = ____lualib.__TS__Decorate +local ____exports = {} +local ____dota_ts_adapter = require("lib.dota_ts_adapter") +local BaseAbility = ____dota_ts_adapter.BaseAbility +local registerAbility = ____dota_ts_adapter.registerAbility +____exports.thief_arrow = __TS__Class() +local thief_arrow = ____exports.thief_arrow +thief_arrow.name = "thief_arrow" +thief_arrow.____file_path = "scripts/vscripts/abilities/creep/thief_arrow.lua" +__TS__ClassExtends(thief_arrow, BaseAbility) +function thief_arrow.prototype.Precache(self, context) + PrecacheResource("soundfile", "sounds/units/heroes/mirana/arrow.vsnd", context) + PrecacheResource("soundfile", "sounds/units/heroes/mirana/arrow_target.vsnd", context) + PrecacheResource("particle", "particles/units/heroes/hero_phantom_assassin/phantom_assassin_crit_impact.vpcf", context) +end +function thief_arrow.prototype.OnAbilityPhaseStart(self) + if IsServer() then + self:GetCaster():StartGestureWithPlaybackRate(ACT_DOTA_ATTACK, 0.5) + self.preParticle = ParticleManager:CreateParticle( + "particles/darkmoon_creep_warning.vpcf", + PATTACH_ABSORIGIN_FOLLOW, + self:GetCaster() + ) + ParticleManager:SetParticleControlEnt( + self.preParticle, + 0, + self:GetCaster(), + PATTACH_ABSORIGIN_FOLLOW, + "", + self:GetCaster():GetOrigin(), + true + ) + ParticleManager:SetParticleControl( + self.preParticle, + 1, + Vector(100, 100, 100) + ) + end + return true +end +function thief_arrow.prototype.OnAbilityPhaseInterrupted(self) + if IsClient() or not self.preParticle then + return + end + self:GetCaster():FadeGesture(ACT_DOTA_ATTACK) + ParticleManager:DestroyParticle(self.preParticle, false) +end +function thief_arrow.prototype.OnSpellStart(self) + if self.preParticle then + ParticleManager:DestroyParticle(self.preParticle, false) + end + self:GetCaster():FadeGesture(ACT_DOTA_ATTACK) + local caster = self:GetCaster() + local origin = caster:GetOrigin() + local point = self:GetCursorPosition() + local projectile_speed = self:GetSpecialValueFor("arrow_speed") + local projectile_distance = self:GetSpecialValueFor("arrow_range") + local projectile_start_radius = self:GetSpecialValueFor("arrow_width") + local projectile_end_radius = self:GetSpecialValueFor("arrow_width") + local projectile_direction = Vector(point.x - origin.x, point.y - origin.y, 0):Normalized() + ProjectileManager:CreateLinearProjectile({ + Source = caster, + Ability = self, + vSpawnOrigin = caster:GetOrigin(), + iUnitTargetTeam = DOTA_UNIT_TARGET_TEAM_ENEMY, + iUnitTargetFlags = DOTA_UNIT_TARGET_FLAG_NONE, + iUnitTargetType = bit.bor(DOTA_UNIT_TARGET_HERO, DOTA_UNIT_TARGET_BASIC), + EffectName = "particles/units/heroes/hero_mirana/mirana_spell_arrow.vpcf", + fDistance = projectile_distance, + fStartRadius = projectile_start_radius, + fEndRadius = projectile_end_radius, + vVelocity = projectile_direction * projectile_speed, + bHasFrontalCone = false, + fExpireTime = GameRules:GetGameTime() + 10 + }) + self:EmitSound("Hero_Mirana.ArrowCast") +end +function thief_arrow.prototype.OnProjectileHit_ExtraData(self, target, location) + if not target then + return + end + local effect_cast = ParticleManager:CreateParticle("particles/units/heroes/hero_phantom_assassin/phantom_assassin_crit_impact.vpcf", PATTACH_POINT_FOLLOW, target) + ParticleManager:SetParticleControlEnt( + effect_cast, + 0, + target, + PATTACH_POINT_FOLLOW, + "attach_hitloc", + target:GetOrigin(), + true + ) + ParticleManager:SetParticleControl( + effect_cast, + 1, + target:GetAbsOrigin() + ) + ParticleManager:SetParticleControlForward( + effect_cast, + 1, + (self:GetCaster():GetOrigin() - target:GetOrigin()):Normalized() + ) + ParticleManager:ReleaseParticleIndex(effect_cast) + ApplyDamage({ + victim = target, + attacker = self:GetCaster(), + damage = self:GetCaster():GetAttackDamage(), + damage_type = DAMAGE_TYPE_PHYSICAL, + ability = self + }) + EmitSoundOn("Hero_Mirana.ArrowImpact", target) + return true +end +thief_arrow = __TS__Decorate( + thief_arrow, + thief_arrow, + {registerAbility(nil)}, + {kind = "class", name = "thief_arrow"} +) +____exports.thief_arrow = thief_arrow +return ____exports diff --git a/scripts/vscripts/abilities/creep/toxin.lua b/scripts/vscripts/abilities/creep/toxin.lua new file mode 100644 index 0000000..e7b5fee --- /dev/null +++ b/scripts/vscripts/abilities/creep/toxin.lua @@ -0,0 +1,844 @@ +local ____lualib = require("lualib_bundle") +local __TS__NumberIsFinite = ____lualib.__TS__NumberIsFinite +local __TS__ArraySort = ____lualib.__TS__ArraySort +local Map = ____lualib.Map +local __TS__New = ____lualib.__TS__New +local __TS__ArrayFilter = ____lualib.__TS__ArrayFilter +local __TS__Class = ____lualib.__TS__Class +local __TS__ClassExtends = ____lualib.__TS__ClassExtends +local __TS__Decorate = ____lualib.__TS__Decorate +local ____exports = {} +local clampToxinPoolRadius, toxinIsValidUnit, isToxinPoolThinker, toxinIsValidAbility, toxinPoolsOverlap, getToxinPoolRadiusForThinker, getToxinPoolMergeStackForThinker, getToxinDamagePerTickForThinker, getToxinPoolRemainingDuration, toxinDistSqHoriz, buildToxinOverlapCluster, computeMergedPoolStats, toxinSortDedupeEntIndices, toxinEnqueuePoolMergeForThinker, toxinDrainToxinMergeQueueFrame, toxinTryResolvePoolMergeForThinkerIndex, TOXIN_POOL_MODIFIER_NAME, TOXIN_MERGE_SCAN_RADIUS, TOXIN_MERGE_OVERLAP_EPSILON, TOXIN_THINKER_CLASS, TOXIN_POOL_MAX_RADIUS, TOXIN_MAX_MERGE_SCAN_THINKERS, toxinMergeQueue, toxinMergeDrainScheduled, toxinIsDrainingMerge +local ____difficulty_manager = require("difficulty_manager") +local Difficulty = ____difficulty_manager.Difficulty +local ____dota_ts_adapter = require("lib.dota_ts_adapter") +local BaseAbility = ____dota_ts_adapter.BaseAbility +local BaseModifier = ____dota_ts_adapter.BaseModifier +local registerAbility = ____dota_ts_adapter.registerAbility +local registerModifier = ____dota_ts_adapter.registerModifier +local ____creep_render_color = require("utils.creep_render_color") +local trySetIntrinsicCreepRenderColor = ____creep_render_color.trySetIntrinsicCreepRenderColor +local ____entity_radius = require("utils.entity_radius") +local findAllByClassnameInRadius = ____entity_radius.findAllByClassnameInRadius +function clampToxinPoolRadius(self, r) + return math.max( + 1, + math.min(r, TOXIN_POOL_MAX_RADIUS) + ) +end +function toxinIsValidUnit(self, unit) + return unit ~= nil and unit ~= nil and not unit:IsNull() and IsValidEntity(unit) +end +function isToxinPoolThinker(self, unit) + return toxinIsValidUnit(nil, unit) and unit:GetClassname() == TOXIN_THINKER_CLASS +end +function toxinIsValidAbility(self, ab) + return ab ~= nil and ab ~= nil and not ab:IsNull() and IsValidEntity(ab) +end +function toxinPoolsOverlap(self, centerA, radiusA, centerB, radiusB) + local dx = centerA.x - centerB.x + local dy = centerA.y - centerB.y + local dist = math.sqrt(dx * dx + dy * dy) + return dist <= radiusA + radiusB + TOXIN_MERGE_OVERLAP_EPSILON +end +function getToxinPoolRadiusForThinker(self, thinker, poolMod, ability) + if not toxinIsValidAbility(nil, ability) then + return 0 + end + local raw + if poolMod and poolMod.effectiveRadius > 0 then + raw = poolMod.effectiveRadius + else + raw = ability:GetSpecialValueFor("radius") + end + return clampToxinPoolRadius(nil, raw) +end +function getToxinPoolMergeStackForThinker(self, poolMod, _ability) + if poolMod ~= nil and poolMod.poolMergeStackCount > 0 then + return math.floor(poolMod.poolMergeStackCount) + end + return 1 +end +function getToxinDamagePerTickForThinker(self, poolMod, ability) + if not toxinIsValidAbility(nil, ability) then + return 0 + end + if poolMod and poolMod.damagePerTick > 0 then + return poolMod.damagePerTick + end + return ability:GetSpecialValueFor("damage") * 0.33 +end +function getToxinPoolRemainingDuration(self, poolMod, fallbackDuration) + local getter = poolMod.GetRemainingTime + if getter ~= nil then + local v = getter(poolMod) + if type(v) == "number" and __TS__NumberIsFinite(v) then + return math.max(0, v) + end + end + return math.max(0, fallbackDuration) +end +function toxinDistSqHoriz(self, ax, bx) + local dx = ax.x - bx.x + local dy = ax.y - bx.y + return dx * dx + dy * dy +end +function buildToxinOverlapCluster(self, seed, ability) + if not toxinIsValidUnit(nil, seed) or not toxinIsValidAbility(nil, ability) then + return {} + end + local seedTeam = seed:GetTeamNumber() + local origin = seed:GetAbsOrigin() + local raw = findAllByClassnameInRadius("npc_dota_thinker", origin, TOXIN_MERGE_SCAN_RADIUS) + local toxinThinkers = {} + for ____, ent in ipairs(raw) do + do + local npc = ent + if not toxinIsValidUnit(nil, npc) then + goto __continue22 + end + if npc:GetTeamNumber() ~= seedTeam then + goto __continue22 + end + if not npc:FindModifierByName(TOXIN_POOL_MODIFIER_NAME) then + goto __continue22 + end + toxinThinkers[#toxinThinkers + 1] = npc + end + ::__continue22:: + end + if #toxinThinkers > TOXIN_MAX_MERGE_SCAN_THINKERS then + __TS__ArraySort( + toxinThinkers, + function(____, a, b) return toxinDistSqHoriz( + nil, + a:GetAbsOrigin(), + origin + ) - toxinDistSqHoriz( + nil, + b:GetAbsOrigin(), + origin + ) end + ) + while #toxinThinkers > TOXIN_MAX_MERGE_SCAN_THINKERS do + table.remove(toxinThinkers) + end + end + local cluster = {} + local visited = __TS__New(Map) + local queue = {seed} + while #queue > 0 do + do + local cur = table.remove(queue) + if not toxinIsValidUnit(nil, cur) then + goto __continue30 + end + local idx = cur:GetEntityIndex() + if visited:get(idx) then + goto __continue30 + end + visited:set(idx, true) + cluster[#cluster + 1] = cur + local curMod = cur:FindModifierByName(TOXIN_POOL_MODIFIER_NAME) + local rCur = getToxinPoolRadiusForThinker(nil, cur, curMod, ability) + local pCur = cur:GetAbsOrigin() + for ____, other in ipairs(toxinThinkers) do + do + if not toxinIsValidUnit(nil, other) then + goto __continue33 + end + local oIdx = other:GetEntityIndex() + if visited:get(oIdx) then + goto __continue33 + end + local oMod = other:FindModifierByName(TOXIN_POOL_MODIFIER_NAME) + local rO = getToxinPoolRadiusForThinker(nil, other, oMod, ability) + if toxinPoolsOverlap( + nil, + pCur, + rCur, + other:GetAbsOrigin(), + rO + ) then + queue[#queue + 1] = other + end + end + ::__continue33:: + end + end + ::__continue30:: + end + return cluster +end +function computeMergedPoolStats(self, cluster, ability) + if not toxinIsValidAbility(nil, ability) then + return { + centroid = Vector(0, 0, 0), + damagePerTick = 0, + radius = 1, + duration = 0.05, + mergeStackCount = 1 + } + end + local valid = __TS__ArrayFilter( + cluster, + function(____, t) return toxinIsValidUnit(nil, t) end + ) + local n = #valid + if n <= 0 then + local baseRadius = ability:GetSpecialValueFor("radius") + local baseDur = ability:GetSpecialValueFor("duration") + return { + centroid = Vector(0, 0, 0), + damagePerTick = ability:GetSpecialValueFor("damage") * 0.33, + radius = clampToxinPoolRadius(nil, baseRadius), + duration = math.max(0.05, baseDur), + mergeStackCount = 1 + } + end + local baseDuration = ability:GetSpecialValueFor("duration") + local radiusBonusPerMerge = math.max( + 0, + ability:GetSpecialValueFor("merge_radius_bonus") + ) + local durationBonusPerMerge = math.max( + 0, + ability:GetSpecialValueFor("merge_duration_bonus") + ) + local sumR2 = 0 + local sumDmg = 0 + local sumStacks = 0 + local maxRem = 0 + local sx = 0 + local sy = 0 + local sz = 0 + for ____, t in ipairs(valid) do + local mod = t:FindModifierByName(TOXIN_POOL_MODIFIER_NAME) + local r = getToxinPoolRadiusForThinker(nil, t, mod, ability) + sumR2 = sumR2 + r * r + sumDmg = sumDmg + getToxinDamagePerTickForThinker(nil, mod, ability) + sumStacks = sumStacks + getToxinPoolMergeStackForThinker(nil, mod, ability) + local p = t:GetAbsOrigin() + sx = sx + p.x + sy = sy + p.y + sz = sz + p.z + if mod then + maxRem = math.max( + maxRem, + getToxinPoolRemainingDuration(nil, mod, baseDuration) + ) + else + maxRem = math.max(maxRem, baseDuration) + end + end + local mergedRadiusUncapped = math.max( + 1, + math.sqrt(sumR2) + math.max(0, n - 1) * radiusBonusPerMerge + ) + local mergedRadius = clampToxinPoolRadius(nil, mergedRadiusUncapped) + local mergedDuration = math.max( + 0.05, + maxRem + math.max(0, n - 1) * durationBonusPerMerge + ) + return { + centroid = Vector(sx / n, sy / n, sz / n), + damagePerTick = sumDmg, + radius = mergedRadius, + duration = mergedDuration, + mergeStackCount = math.max(1, sumStacks) + } +end +function toxinSortDedupeEntIndices(self, arr) + if #arr <= 1 then + return arr + end + local sorted = {unpack(arr)} + __TS__ArraySort( + sorted, + function(____, a, b) return a - b end + ) + local out = {} + local prev = -2147483648 + for ____, x in ipairs(sorted) do + local n = x + if n ~= prev then + out[#out + 1] = x + prev = n + end + end + return out +end +function toxinEnqueuePoolMergeForThinker(self, entIndex) + if not IsServer() then + return + end + toxinMergeQueue[#toxinMergeQueue + 1] = entIndex + if toxinMergeDrainScheduled or toxinIsDrainingMerge then + return + end + toxinMergeDrainScheduled = true + Timers:CreateTimer(0, toxinDrainToxinMergeQueueFrame) +end +function toxinDrainToxinMergeQueueFrame(self) + if not IsServer() then + return + end + toxinIsDrainingMerge = true + toxinMergeDrainScheduled = false + local batch = toxinMergeQueue + toxinMergeQueue = {} + local uniq = toxinSortDedupeEntIndices(nil, batch) + for ____, idx in ipairs(uniq) do + toxinTryResolvePoolMergeForThinkerIndex(nil, idx) + end + toxinIsDrainingMerge = false + if #toxinMergeQueue > 0 then + toxinMergeDrainScheduled = true + Timers:CreateTimer(0, toxinDrainToxinMergeQueueFrame) + end +end +function toxinTryResolvePoolMergeForThinkerIndex(self, entIndex) + if not IsServer() then + return + end + local npc = EntIndexToHScript(entIndex) + if not isToxinPoolThinker(nil, npc) then + return + end + local buff = npc:FindModifierByName(TOXIN_POOL_MODIFIER_NAME) + if not buff then + return + end + local poolMod = buff + local abilityRaw = buff:GetAbility() + if not toxinIsValidAbility(nil, abilityRaw) then + return + end + local ability = abilityRaw + local caster = buff:GetCaster() + if not toxinIsValidUnit(nil, caster) then + return + end + local cluster = buildToxinOverlapCluster(nil, npc, ability) + if #cluster < 2 then + poolMod:initializeSinglePoolFromAbility() + return + end + local mergeLeader + local minIdx = 2147483647 + for ____, t in ipairs(cluster) do + do + if not toxinIsValidUnit(nil, t) then + goto __continue141 + end + local ei = t:GetEntityIndex() + if ei < minIdx then + minIdx = ei + mergeLeader = t + end + end + ::__continue141:: + end + if not mergeLeader or not toxinIsValidUnit(nil, mergeLeader) then + return + end + if npc:GetEntityIndex() ~= mergeLeader:GetEntityIndex() then + toxinEnqueuePoolMergeForThinker( + nil, + mergeLeader:GetEntityIndex() + ) + return + end + local merged = computeMergedPoolStats(nil, cluster, ability) + local spawnParams = {duration = merged.duration, merged_damage_per_tick = merged.damagePerTick, merged_radius = merged.radius, merged_stack_count = merged.mergeStackCount} + for ____, t in ipairs(cluster) do + if toxinIsValidUnit(nil, t) then + UTIL_Remove(t) + end + end + if not toxinIsValidAbility(nil, ability) or not toxinIsValidUnit(nil, caster) then + return + end + CreateModifierThinker( + caster, + ability, + TOXIN_POOL_MODIFIER_NAME, + spawnParams, + merged.centroid, + caster:GetTeamNumber(), + false + ) +end +TOXIN_POOL_MODIFIER_NAME = "modifier_spider_nethertoxin_lua" +TOXIN_MERGE_SCAN_RADIUS = 2400 +TOXIN_MERGE_OVERLAP_EPSILON = 12 +TOXIN_THINKER_CLASS = "npc_dota_thinker" +TOXIN_POOL_MAX_RADIUS = 900 +TOXIN_MAX_MERGE_SCAN_THINKERS = 220 +toxinMergeQueue = {} +toxinMergeDrainScheduled = false +toxinIsDrainingMerge = false +____exports.toxin = __TS__Class() +local toxin = ____exports.toxin +toxin.name = "toxin" +toxin.____file_path = "scripts/vscripts/abilities/creep/toxin.lua" +__TS__ClassExtends(toxin, BaseAbility) +function toxin.prototype.Precache(self, context) + PrecacheResource("particle", "particles/units/heroes/hero_alchemist/alchemist_acid_spray.vpcf", context) + PrecacheResource("particle", "particles/units/heroes/hero_viper/viper_nethertoxin.vpcf", context) + PrecacheResource("particle", "particles/units/heroes/hero_viper/viper_nethertoxin_debuff.vpcf", context) + PrecacheResource("soundfile", "soundevents/game_sounds_heroes/game_sounds_broodmother.vsndevts", context) + PrecacheResource("soundfile", "soundevents/game_sounds_heroes/game_sounds_viper.vsndevts", context) +end +function toxin.prototype.GetIntrinsicModifierName(self) + return "modifier_spider_toxin_death_listener" +end +toxin = __TS__Decorate( + toxin, + toxin, + {registerAbility(nil)}, + {kind = "class", name = "toxin"} +) +____exports.toxin = toxin +____exports.modifier_spider_toxin_death_listener = __TS__Class() +local modifier_spider_toxin_death_listener = ____exports.modifier_spider_toxin_death_listener +modifier_spider_toxin_death_listener.name = "modifier_spider_toxin_death_listener" +modifier_spider_toxin_death_listener.____file_path = "scripts/vscripts/abilities/creep/toxin.lua" +__TS__ClassExtends(modifier_spider_toxin_death_listener, BaseModifier) +function modifier_spider_toxin_death_listener.prototype.IsHidden(self) + return true +end +function modifier_spider_toxin_death_listener.prototype.IsPurgable(self) + return false +end +function modifier_spider_toxin_death_listener.prototype.OnCreated(self, _params) + if not IsServer() then + return + end + local parent = self:GetParent() + if not toxinIsValidUnit(nil, parent) then + return + end + trySetIntrinsicCreepRenderColor( + nil, + parent, + 51, + 102, + 0 + ) +end +function modifier_spider_toxin_death_listener.prototype.DeclareFunctions(self) + return {MODIFIER_EVENT_ON_DEATH} +end +function modifier_spider_toxin_death_listener.prototype.OnDeath(self, event) + if not IsServer() then + return + end + if event.unit ~= self:GetParent() then + return + end + local ability = self:GetAbility() + if not toxinIsValidAbility(nil, ability) then + return + end + local caster = self:GetCaster() + if not toxinIsValidUnit(nil, caster) then + return + end + local parent = self:GetParent() + local spawnOrigin = parent:GetAbsOrigin() + local duration = ability:GetSpecialValueFor("duration") + local radius = clampToxinPoolRadius( + nil, + ability:GetSpecialValueFor("radius") + ) + local splashPfx = ParticleManager:CreateParticle("particles/units/heroes/hero_alchemist/alchemist_acid_spray.vpcf", PATTACH_WORLDORIGIN, nil) + ParticleManager:SetParticleControl(splashPfx, 0, spawnOrigin) + ParticleManager:SetParticleControl( + splashPfx, + 1, + Vector(radius, 1, 1) + ) + ParticleManager:SetParticleControl( + splashPfx, + 15, + Vector(255, 153, 102) + ) + ParticleManager:SetParticleControl( + splashPfx, + 16, + Vector(1, 0, 0) + ) + Timers:CreateTimer( + 4, + function() + if not IsServer() then + return + end + ParticleManager:DestroyParticle(splashPfx, false) + ParticleManager:ReleaseParticleIndex(splashPfx) + end + ) + EmitSoundOn("Hero_Broodmother.SpawnSpiderlings", parent) + CreateModifierThinker( + caster, + ability, + TOXIN_POOL_MODIFIER_NAME, + {duration = duration}, + spawnOrigin, + caster:GetTeamNumber(), + false + ) +end +modifier_spider_toxin_death_listener = __TS__Decorate( + modifier_spider_toxin_death_listener, + modifier_spider_toxin_death_listener, + {registerModifier(nil)}, + {kind = "class", name = "modifier_spider_toxin_death_listener"} +) +____exports.modifier_spider_toxin_death_listener = modifier_spider_toxin_death_listener +____exports.modifier_spider_nethertoxin_lua = __TS__Class() +local modifier_spider_nethertoxin_lua = ____exports.modifier_spider_nethertoxin_lua +modifier_spider_nethertoxin_lua.name = "modifier_spider_nethertoxin_lua" +modifier_spider_nethertoxin_lua.____file_path = "scripts/vscripts/abilities/creep/toxin.lua" +__TS__ClassExtends(modifier_spider_nethertoxin_lua, BaseModifier) +function modifier_spider_nethertoxin_lua.prototype.____constructor(self, ...) + BaseModifier.prototype.____constructor(self, ...) + self.effectiveRadius = 0 + self.damagePerTick = 0 + self.poolMergeStackCount = 0 + self.auraPenaltyStack = 1 + self.damageTable = {} +end +function modifier_spider_nethertoxin_lua.prototype.GetTexture(self) + return "viper_nethertoxin" +end +function modifier_spider_nethertoxin_lua.prototype.IsHidden(self) + return false +end +function modifier_spider_nethertoxin_lua.prototype.IsDebuff(self) + return true +end +function modifier_spider_nethertoxin_lua.prototype.IsStunDebuff(self) + return false +end +function modifier_spider_nethertoxin_lua.prototype.IsPurgable(self) + return false +end +function modifier_spider_nethertoxin_lua.prototype.initializeAuraVictimDebuff(self) + local ability = self:GetAbility() + if toxinIsValidAbility(nil, ability) then + self:SetDuration( + math.max( + 0.05, + self:GetAuraDuration() + ), + true + ) + end + self.effectiveRadius = 0 + self.damagePerTick = 0 + self.poolMergeStackCount = 0 + if IsServer() then + self:syncAuraPenaltyFromCasterThinker() + self:SetStackCount(math.max( + 1, + math.floor(self.auraPenaltyStack) + )) + end +end +function modifier_spider_nethertoxin_lua.prototype.syncAuraPenaltyFromCasterThinker(self) + local parent = self:GetParent() + if isToxinPoolThinker(nil, parent) then + return + end + local source = self:GetCaster() + if not toxinIsValidUnit(nil, source) then + return + end + local srcMod = source:FindModifierByName(TOXIN_POOL_MODIFIER_NAME) + if srcMod ~= nil and srcMod.poolMergeStackCount > 0 then + self.auraPenaltyStack = math.floor(srcMod.poolMergeStackCount) + else + self.auraPenaltyStack = math.max(1, self.auraPenaltyStack) + end +end +function modifier_spider_nethertoxin_lua.prototype.initializeSinglePoolFromAbility(self) + local ability = self:GetAbility() + local caster = self:GetCaster() + local parent = self:GetParent() + if not toxinIsValidAbility(nil, ability) or not toxinIsValidUnit(nil, caster) or not toxinIsValidUnit(nil, parent) then + return + end + local baseDamage = ability:GetSpecialValueFor("damage") + local baseRadius = ability:GetSpecialValueFor("radius") + local baseDuration = ability:GetSpecialValueFor("duration") + self.damagePerTick = (baseDamage + self:GetCaster():GetAttackDamage() * 0.15) * 0.33 + local totaldamage = self:GetParent():IsRangedAttacker() and self.damagePerTick or self.damagePerTick * 0.25 + self.effectiveRadius = clampToxinPoolRadius(nil, baseRadius) + self.poolMergeStackCount = 1 + self:SetDuration( + math.max(0.05, baseDuration), + true + ) + self.damageTable = { + victim = caster, + attacker = caster, + damage = totaldamage, + damage_type = ability:GetAbilityDamageType(), + ability = ability + } + self:StartIntervalThink(0.33) + self:PlayEffects() +end +function modifier_spider_nethertoxin_lua.prototype.initializeMergedPoolFromParams(self, params) + local ability = self:GetAbility() + local caster = self:GetCaster() + local parent = self:GetParent() + if not toxinIsValidAbility(nil, ability) or not toxinIsValidUnit(nil, caster) or not toxinIsValidUnit(nil, parent) then + return + end + self.damagePerTick = params.merged_damage_per_tick + self.effectiveRadius = clampToxinPoolRadius(nil, params.merged_radius) + self.poolMergeStackCount = math.max( + 1, + math.floor(params.merged_stack_count or 1) + ) + self:SetDuration( + math.max(0.05, params.duration), + true + ) + self.damageTable = { + victim = caster, + attacker = caster, + damage = self.damagePerTick, + damage_type = ability:GetAbilityDamageType(), + ability = ability + } + self:StartIntervalThink(0.33) + self:PlayEffects() +end +function modifier_spider_nethertoxin_lua.prototype.OnCreated(self, params) + if not IsServer() then + return + end + local parent = self:GetParent() + if not isToxinPoolThinker(nil, parent) then + self:initializeAuraVictimDebuff() + return + end + local p = params + if p.merged_damage_per_tick ~= nil and p.merged_radius ~= nil and p.duration ~= nil then + self:initializeMergedPoolFromParams(p) + return + end + toxinEnqueuePoolMergeForThinker( + nil, + parent:GetEntityIndex() + ) +end +function modifier_spider_nethertoxin_lua.prototype.OnRefresh(self, _params) + if not IsServer() then + return + end + local ability = self:GetAbility() + if not toxinIsValidAbility(nil, ability) then + return + end + if not isToxinPoolThinker( + nil, + self:GetParent() + ) then + self:syncAuraPenaltyFromCasterThinker() + self:SetStackCount(math.max( + 1, + math.floor(self.auraPenaltyStack) + )) + return + end + self.damageTable.damage = self.damagePerTick + self.damageTable.damage_type = ability:GetAbilityDamageType() +end +function modifier_spider_nethertoxin_lua.prototype.DeclareFunctions(self) + return {MODIFIER_PROPERTY_PHYSICAL_ARMOR_BONUS, MODIFIER_PROPERTY_MAGICAL_RESISTANCE_BONUS} +end +function modifier_spider_nethertoxin_lua.prototype.GetModifierPhysicalArmorBonus(self) + local parent = self:GetParent() + if isToxinPoolThinker(nil, parent) then + return 0 + end + local ability = self:GetAbility() + if not toxinIsValidAbility(nil, ability) then + return 0 + end + local per = math.max( + 0, + ability:GetSpecialValueFor("armor_magic_reduce_per_stack") + ) + local scale = Difficulty:getNpcStatScale() + return -per * math.max( + 1, + math.floor(self.auraPenaltyStack) + ) * scale +end +function modifier_spider_nethertoxin_lua.prototype.GetModifierMagicalResistanceBonus(self) + local parent = self:GetParent() + if isToxinPoolThinker(nil, parent) then + return 0 + end + local ability = self:GetAbility() + if not toxinIsValidAbility(nil, ability) then + return 0 + end + local per = math.max( + 0, + ability:GetSpecialValueFor("armor_magic_reduce_per_stack") + ) + local scale = Difficulty:getNpcStatScale() + return -per * math.max( + 1, + math.floor(self.auraPenaltyStack) + ) * scale +end +function modifier_spider_nethertoxin_lua.prototype.OnDestroy(self) + if not IsServer() then + return + end + local parent = self:GetParent() + if not isToxinPoolThinker(nil, parent) then + return + end + local entIndex = parent:GetEntityIndex() + Timers:CreateTimer( + 0, + function() + if not IsServer() then + return + end + local npc = EntIndexToHScript(entIndex) + if not toxinIsValidUnit(nil, npc) then + return + end + if npc:GetClassname() ~= TOXIN_THINKER_CLASS then + return + end + UTIL_Remove(npc) + end + ) +end +function modifier_spider_nethertoxin_lua.prototype.CheckState(self) + return {[MODIFIER_STATE_PASSIVES_DISABLED] = true} +end +function modifier_spider_nethertoxin_lua.prototype.OnIntervalThink(self) + local parent = self:GetParent() + if not isToxinPoolThinker(nil, parent) then + return + end + local caster = self:GetCaster() + local ability = self:GetAbility() + if not toxinIsValidUnit(nil, caster) or not toxinIsValidAbility(nil, ability) then + return + end + local units = FindUnitsInRadius( + caster:GetTeamNumber(), + parent:GetAbsOrigin(), + nil, + self.effectiveRadius, + self:GetAuraSearchTeam(), + self:GetAuraSearchType(), + DOTA_UNIT_TARGET_FLAG_NONE, + FIND_ANY_ORDER, + false + ) + self.damageTable.attacker = caster + self.damageTable.ability = ability + self.damageTable.damage_type = ability:GetAbilityDamageType() + local damagedAny = false + for ____, unit in ipairs(units) do + do + if not toxinIsValidUnit(nil, unit) or not unit:IsAlive() then + goto __continue104 + end + self.damageTable.victim = unit + ApplyDamage(self.damageTable) + damagedAny = true + end + ::__continue104:: + end + if damagedAny then + EmitSoundOn("Hero_Viper.NetherToxin.Damage", parent) + end +end +function modifier_spider_nethertoxin_lua.prototype.IsAura(self) + return isToxinPoolThinker( + nil, + self:GetParent() + ) +end +function modifier_spider_nethertoxin_lua.prototype.GetModifierAura(self) + return TOXIN_POOL_MODIFIER_NAME +end +function modifier_spider_nethertoxin_lua.prototype.GetAuraRadius(self) + local ability = self:GetAbility() + if self.effectiveRadius > 0 then + return self.effectiveRadius + end + if toxinIsValidAbility(nil, ability) then + return clampToxinPoolRadius( + nil, + ability:GetSpecialValueFor("radius") + ) + end + return 0 +end +function modifier_spider_nethertoxin_lua.prototype.GetAuraDuration(self) + return 0.25 +end +function modifier_spider_nethertoxin_lua.prototype.GetAuraSearchTeam(self) + return DOTA_UNIT_TARGET_TEAM_ENEMY +end +function modifier_spider_nethertoxin_lua.prototype.GetAuraSearchType(self) + return DOTA_UNIT_TARGET_HERO + DOTA_UNIT_TARGET_BASIC +end +function modifier_spider_nethertoxin_lua.prototype.GetEffectName(self) + return "particles/units/heroes/hero_viper/viper_nethertoxin_debuff.vpcf" +end +function modifier_spider_nethertoxin_lua.prototype.GetEffectAttachType(self) + return PATTACH_ABSORIGIN_FOLLOW +end +function modifier_spider_nethertoxin_lua.prototype.PlayEffects(self) + local parent = self:GetParent() + if not toxinIsValidUnit(nil, parent) then + return + end + local particle_cast = "particles/units/heroes/hero_viper/viper_nethertoxin.vpcf" + local sound_cast = "Hero_Viper.NetherToxin" + local effect_cast = ParticleManager:CreateParticle(particle_cast, PATTACH_WORLDORIGIN, nil) + ParticleManager:SetParticleControl( + effect_cast, + 0, + parent:GetOrigin() + ) + ParticleManager:SetParticleControl( + effect_cast, + 1, + Vector(self.effectiveRadius, 1, 1) + ) + self:AddParticle( + effect_cast, + false, + false, + -1, + false, + false + ) + EmitSoundOn(sound_cast, parent) +end +modifier_spider_nethertoxin_lua = __TS__Decorate( + modifier_spider_nethertoxin_lua, + modifier_spider_nethertoxin_lua, + {registerModifier(nil)}, + {kind = "class", name = "modifier_spider_nethertoxin_lua"} +) +____exports.modifier_spider_nethertoxin_lua = modifier_spider_nethertoxin_lua +return ____exports diff --git a/scripts/vscripts/abilities/creep/wave_desperate_vampirism.lua b/scripts/vscripts/abilities/creep/wave_desperate_vampirism.lua new file mode 100644 index 0000000..89722b0 --- /dev/null +++ b/scripts/vscripts/abilities/creep/wave_desperate_vampirism.lua @@ -0,0 +1,94 @@ +local ____lualib = require("lualib_bundle") +local __TS__Class = ____lualib.__TS__Class +local __TS__ClassExtends = ____lualib.__TS__ClassExtends +local __TS__Decorate = ____lualib.__TS__Decorate +local ____exports = {} +local ____difficulty_manager = require("difficulty_manager") +local Difficulty = ____difficulty_manager.Difficulty +local ____dota_ts_adapter = require("lib.dota_ts_adapter") +local BaseAbility = ____dota_ts_adapter.BaseAbility +local BaseModifier = ____dota_ts_adapter.BaseModifier +local registerAbility = ____dota_ts_adapter.registerAbility +local registerModifier = ____dota_ts_adapter.registerModifier +--- Пассивка для волновых крипов: при здоровье ниже порога атаки лечат на долю нанесённого урона. +____exports.wave_desperate_vampirism = __TS__Class() +local wave_desperate_vampirism = ____exports.wave_desperate_vampirism +wave_desperate_vampirism.name = "wave_desperate_vampirism" +wave_desperate_vampirism.____file_path = "scripts/vscripts/abilities/creep/wave_desperate_vampirism.lua" +__TS__ClassExtends(wave_desperate_vampirism, BaseAbility) +function wave_desperate_vampirism.prototype.GetIntrinsicModifierName(self) + return "modifier_wave_desperate_vampirism" +end +function wave_desperate_vampirism.prototype.Precache(self, context) + PrecacheResource("particle", "particles/units/heroes/hero_bloodseeker/bloodseeker_bloodbath.vpcf", context) +end +wave_desperate_vampirism = __TS__Decorate( + wave_desperate_vampirism, + wave_desperate_vampirism, + {registerAbility(nil)}, + {kind = "class", name = "wave_desperate_vampirism"} +) +____exports.wave_desperate_vampirism = wave_desperate_vampirism +____exports.modifier_wave_desperate_vampirism = __TS__Class() +local modifier_wave_desperate_vampirism = ____exports.modifier_wave_desperate_vampirism +modifier_wave_desperate_vampirism.name = "modifier_wave_desperate_vampirism" +modifier_wave_desperate_vampirism.____file_path = "scripts/vscripts/abilities/creep/wave_desperate_vampirism.lua" +__TS__ClassExtends(modifier_wave_desperate_vampirism, BaseModifier) +function modifier_wave_desperate_vampirism.prototype.IsHidden(self) + return true +end +function modifier_wave_desperate_vampirism.prototype.IsDebuff(self) + return false +end +function modifier_wave_desperate_vampirism.prototype.IsPurgable(self) + return false +end +function modifier_wave_desperate_vampirism.prototype.DeclareFunctions(self) + return {MODIFIER_EVENT_ON_ATTACK_LANDED} +end +function modifier_wave_desperate_vampirism.prototype.OnAttackLanded(self, event) + if not IsServer() then + return + end + local parent = self:GetParent() + local ability = self:GetAbility() + if not ability or not parent or event.attacker ~= parent then + return + end + local target = event.target + if not target or not target:IsAlive() or target:IsBuilding() or target:IsOther() then + return + end + local thresholdPct = ability:GetSpecialValueFor("hp_threshold_pct") + if parent:GetHealthPercent() >= thresholdPct then + return + end + local vampPct = ability:GetSpecialValueFor("vamp_pct") + local damage = event.damage or 0 + if damage <= 0 then + damage = parent:GetAverageTrueAttackDamage(target) + end + if damage <= 0 then + return + end + local heal = damage * vampPct / 100 * Difficulty:getNpcStatScale() + if heal <= 0 then + return + end + parent:Heal(heal, ability) + local p = ParticleManager:CreateParticle("particles/units/heroes/hero_bloodseeker/bloodseeker_bloodbath.vpcf", PATTACH_ABSORIGIN_FOLLOW, parent) + ParticleManager:SetParticleControl( + p, + 0, + parent:GetAbsOrigin() + ) + ParticleManager:ReleaseParticleIndex(p) +end +modifier_wave_desperate_vampirism = __TS__Decorate( + modifier_wave_desperate_vampirism, + modifier_wave_desperate_vampirism, + {registerModifier(nil)}, + {kind = "class", name = "modifier_wave_desperate_vampirism"} +) +____exports.modifier_wave_desperate_vampirism = modifier_wave_desperate_vampirism +return ____exports diff --git a/scripts/vscripts/abilities/creep/wave_full_brutality.lua b/scripts/vscripts/abilities/creep/wave_full_brutality.lua new file mode 100644 index 0000000..7f7e2a8 --- /dev/null +++ b/scripts/vscripts/abilities/creep/wave_full_brutality.lua @@ -0,0 +1,139 @@ +local ____lualib = require("lualib_bundle") +local __TS__Class = ____lualib.__TS__Class +local __TS__ClassExtends = ____lualib.__TS__ClassExtends +local __TS__Decorate = ____lualib.__TS__Decorate +local ____exports = {} +local ____dota_ts_adapter = require("lib.dota_ts_adapter") +local BaseAbility = ____dota_ts_adapter.BaseAbility +local BaseModifier = ____dota_ts_adapter.BaseModifier +local registerAbility = ____dota_ts_adapter.registerAbility +local registerModifier = ____dota_ts_adapter.registerModifier +local ____creep_render_color = require("utils.creep_render_color") +local trySetIntrinsicCreepRenderColor = ____creep_render_color.trySetIntrinsicCreepRenderColor +--- Пассив: усиливает юнита на bonus_pct процентов (по умолчанию 100 = удвоение по смыслу «+100%»). +-- HP/мана — плоский бонус от базового макс.; урон/IAS/мувспид/спелламп/реген HP% — через модификаторы. +____exports.wave_full_brutality = __TS__Class() +local wave_full_brutality = ____exports.wave_full_brutality +wave_full_brutality.name = "wave_full_brutality" +wave_full_brutality.____file_path = "scripts/vscripts/abilities/creep/wave_full_brutality.lua" +__TS__ClassExtends(wave_full_brutality, BaseAbility) +function wave_full_brutality.prototype.GetIntrinsicModifierName(self) + return "modifier_wave_full_brutality_passive" +end +wave_full_brutality = __TS__Decorate( + wave_full_brutality, + wave_full_brutality, + {registerAbility(nil)}, + {kind = "class", name = "wave_full_brutality"} +) +____exports.wave_full_brutality = wave_full_brutality +____exports.modifier_wave_full_brutality_passive = __TS__Class() +local modifier_wave_full_brutality_passive = ____exports.modifier_wave_full_brutality_passive +modifier_wave_full_brutality_passive.name = "modifier_wave_full_brutality_passive" +modifier_wave_full_brutality_passive.____file_path = "scripts/vscripts/abilities/creep/wave_full_brutality.lua" +__TS__ClassExtends(modifier_wave_full_brutality_passive, BaseModifier) +function modifier_wave_full_brutality_passive.prototype.____constructor(self, ...) + BaseModifier.prototype.____constructor(self, ...) + self.bonusPct = 100 + self.effectiveBonusPct = 100 + self.healthBonusFlat = 0 + self.manaBonusFlat = 0 +end +function modifier_wave_full_brutality_passive.prototype.IsHidden(self) + return false +end +function modifier_wave_full_brutality_passive.prototype.IsDebuff(self) + return false +end +function modifier_wave_full_brutality_passive.prototype.IsPurgable(self) + return false +end +function modifier_wave_full_brutality_passive.prototype.OnCreated(self) + local ability = self:GetAbility() + local parent = self:GetParent() + if not parent then + return + end + if IsServer() then + trySetIntrinsicCreepRenderColor( + nil, + self:GetParent(), + 52, + 0, + 0 + ) + end + self.bonusPct = ability and ability:GetSpecialValueFor("bonus_pct") or 100 + if self.bonusPct < 0 then + self.bonusPct = 0 + end + self.effectiveBonusPct = self.bonusPct + local baseHp = math.max( + 1, + parent:GetMaxHealth() + ) + local baseMana = math.max( + 0, + parent:GetMaxMana() + ) + self.healthBonusFlat = math.floor(baseHp * (self.effectiveBonusPct / 100)) + self.manaBonusFlat = math.floor(baseMana * (self.effectiveBonusPct / 100)) + if IsServer() then + Timers:CreateTimer( + 0, + function() + local p = self:GetParent() + if not p or not IsValidEntity(p) or not p:IsAlive() then + return nil + end + p:SetHealth(p:GetMaxHealth() * 2) + if p:GetMaxMana() > 0 then + p:SetMana(p:GetMaxMana() * 2) + end + return nil + end + ) + end +end +function modifier_wave_full_brutality_passive.prototype.DeclareFunctions(self) + return { + MODIFIER_PROPERTY_HEALTH_BONUS, + MODIFIER_PROPERTY_MANA_BONUS, + MODIFIER_PROPERTY_DAMAGEOUTGOING_PERCENTAGE, + MODIFIER_PROPERTY_ATTACKSPEED_PERCENTAGE, + MODIFIER_PROPERTY_MOVESPEED_BONUS_PERCENTAGE, + MODIFIER_PROPERTY_SPELL_AMPLIFY_PERCENTAGE + } +end +function modifier_wave_full_brutality_passive.prototype.GetModifierHealthBonus(self) + return self.healthBonusFlat +end +function modifier_wave_full_brutality_passive.prototype.GetModifierManaBonus(self) + return self.manaBonusFlat +end +function modifier_wave_full_brutality_passive.prototype.GetModifierDamageOutgoing_Percentage(self, _event) + return self.effectiveBonusPct +end +function modifier_wave_full_brutality_passive.prototype.GetModifierAttackSpeedPercentage(self) + return self.effectiveBonusPct +end +function modifier_wave_full_brutality_passive.prototype.GetModifierMoveSpeedBonus_Percentage(self) + return self.effectiveBonusPct +end +function modifier_wave_full_brutality_passive.prototype.GetModifierSpellAmplify_Percentage(self, _event) + return self.effectiveBonusPct +end +function modifier_wave_full_brutality_passive.prototype.GetEffectName(self) + return "particles/items2_fx/mask_of_madness.vpcf" +end +function modifier_wave_full_brutality_passive.prototype.GetEffectAttachType(self) + return PATTACH_ABSORIGIN_FOLLOW +end +modifier_wave_full_brutality_passive = __TS__Decorate( + modifier_wave_full_brutality_passive, + modifier_wave_full_brutality_passive, + {registerModifier(nil)}, + {kind = "class", name = "modifier_wave_full_brutality_passive"} +) +____exports.modifier_wave_full_brutality_passive = modifier_wave_full_brutality_passive +return ____exports diff --git a/scripts/vscripts/abilities/creep/wave_phasing_march.lua b/scripts/vscripts/abilities/creep/wave_phasing_march.lua new file mode 100644 index 0000000..b89188b --- /dev/null +++ b/scripts/vscripts/abilities/creep/wave_phasing_march.lua @@ -0,0 +1,63 @@ +local ____lualib = require("lualib_bundle") +local __TS__Class = ____lualib.__TS__Class +local __TS__ClassExtends = ____lualib.__TS__ClassExtends +local __TS__Decorate = ____lualib.__TS__Decorate +local ____exports = {} +local ____difficulty_manager = require("difficulty_manager") +local Difficulty = ____difficulty_manager.Difficulty +local ____dota_ts_adapter = require("lib.dota_ts_adapter") +local BaseAbility = ____dota_ts_adapter.BaseAbility +local BaseModifier = ____dota_ts_adapter.BaseModifier +local registerAbility = ____dota_ts_adapter.registerAbility +local registerModifier = ____dota_ts_adapter.registerModifier +--- Пассив волны: проход сквозь юнитов и бонус к скорости передвижения. +____exports.wave_phasing_march = __TS__Class() +local wave_phasing_march = ____exports.wave_phasing_march +wave_phasing_march.name = "wave_phasing_march" +wave_phasing_march.____file_path = "scripts/vscripts/abilities/creep/wave_phasing_march.lua" +__TS__ClassExtends(wave_phasing_march, BaseAbility) +function wave_phasing_march.prototype.GetIntrinsicModifierName(self) + return "modifier_wave_phasing_march_passive" +end +wave_phasing_march = __TS__Decorate( + wave_phasing_march, + wave_phasing_march, + {registerAbility(nil)}, + {kind = "class", name = "wave_phasing_march"} +) +____exports.wave_phasing_march = wave_phasing_march +____exports.modifier_wave_phasing_march_passive = __TS__Class() +local modifier_wave_phasing_march_passive = ____exports.modifier_wave_phasing_march_passive +modifier_wave_phasing_march_passive.name = "modifier_wave_phasing_march_passive" +modifier_wave_phasing_march_passive.____file_path = "scripts/vscripts/abilities/creep/wave_phasing_march.lua" +__TS__ClassExtends(modifier_wave_phasing_march_passive, BaseModifier) +function modifier_wave_phasing_march_passive.prototype.IsHidden(self) + return true +end +function modifier_wave_phasing_march_passive.prototype.IsDebuff(self) + return false +end +function modifier_wave_phasing_march_passive.prototype.IsPurgable(self) + return false +end +function modifier_wave_phasing_march_passive.prototype.DeclareFunctions(self) + return {MODIFIER_PROPERTY_MOVESPEED_BONUS_CONSTANT} +end +function modifier_wave_phasing_march_passive.prototype.GetModifierMoveSpeedBonus_Constant(self) + local ab = self:GetAbility() + if not ab then + return 0 + end + return ab:GetSpecialValueFor("bonus_movement_speed") * Difficulty:getNpcStatScale() +end +function modifier_wave_phasing_march_passive.prototype.CheckState(self) + return {[MODIFIER_STATE_NO_UNIT_COLLISION] = true} +end +modifier_wave_phasing_march_passive = __TS__Decorate( + modifier_wave_phasing_march_passive, + modifier_wave_phasing_march_passive, + {registerModifier(nil)}, + {kind = "class", name = "modifier_wave_phasing_march_passive"} +) +____exports.modifier_wave_phasing_march_passive = modifier_wave_phasing_march_passive +return ____exports diff --git a/scripts/vscripts/abilities/creep/weaking_impetus.lua b/scripts/vscripts/abilities/creep/weaking_impetus.lua new file mode 100644 index 0000000..a6ddd3f --- /dev/null +++ b/scripts/vscripts/abilities/creep/weaking_impetus.lua @@ -0,0 +1,137 @@ +local ____lualib = require("lualib_bundle") +local __TS__Class = ____lualib.__TS__Class +local __TS__ClassExtends = ____lualib.__TS__ClassExtends +local __TS__Decorate = ____lualib.__TS__Decorate +local ____exports = {} +local ____dota_ts_adapter = require("lib.dota_ts_adapter") +local BaseAbility = ____dota_ts_adapter.BaseAbility +local BaseModifier = ____dota_ts_adapter.BaseModifier +local registerAbility = ____dota_ts_adapter.registerAbility +local registerModifier = ____dota_ts_adapter.registerModifier +____exports.weaking_impetus = __TS__Class() +local weaking_impetus = ____exports.weaking_impetus +weaking_impetus.name = "weaking_impetus" +weaking_impetus.____file_path = "scripts/vscripts/abilities/creep/weaking_impetus.lua" +__TS__ClassExtends(weaking_impetus, BaseAbility) +function weaking_impetus.prototype.GetIntrinsicModifierName(self) + return "modifier_weaking_impetus_passive" +end +weaking_impetus = __TS__Decorate( + weaking_impetus, + weaking_impetus, + {registerAbility(nil)}, + {kind = "class", name = "weaking_impetus"} +) +____exports.weaking_impetus = weaking_impetus +____exports.modifier_weaking_impetus_passive = __TS__Class() +local modifier_weaking_impetus_passive = ____exports.modifier_weaking_impetus_passive +modifier_weaking_impetus_passive.name = "modifier_weaking_impetus_passive" +modifier_weaking_impetus_passive.____file_path = "scripts/vscripts/abilities/creep/weaking_impetus.lua" +__TS__ClassExtends(modifier_weaking_impetus_passive, BaseModifier) +function modifier_weaking_impetus_passive.prototype.IsHidden(self) + return true +end +function modifier_weaking_impetus_passive.prototype.IsDebuff(self) + return false +end +function modifier_weaking_impetus_passive.prototype.IsPurgable(self) + return false +end +function modifier_weaking_impetus_passive.prototype.GetStatusEffectName(self) + return "particles/status_fx/status_effect_abaddon_borrowed_time.vpcf" +end +function modifier_weaking_impetus_passive.prototype.GetEffectName(self) + return "particles/units/heroes/hero_abaddon/abaddon_borrowed_time.vpcf" +end +function modifier_weaking_impetus_passive.prototype.StatusEffectPriority(self) + return 10 +end +function modifier_weaking_impetus_passive.prototype.DeclareFunctions(self) + return {MODIFIER_EVENT_ON_ATTACK_LANDED} +end +function modifier_weaking_impetus_passive.prototype.OnAttackLanded(self, event) + if not IsServer() then + return + end + if event.attacker ~= self:GetParent() then + return + end + local target = event.target + if not target then + return + end + local parent = self:GetParent() + local ability = self:GetAbility() + if parent:GetMana() < self:GetAbility():GetSpecialValueFor("mana_hit") then + return + end + parent:SpendMana( + self:GetAbility():GetSpecialValueFor("mana_hit"), + ability + ) + local duration = ability:GetSpecialValueFor("debuff_duration") + local modifier = target:FindModifierByName("modifier_weaking_impetus_debuff") + if modifier then + modifier:SetDuration(duration, true) + if modifier:GetStackCount() < self:GetAbility():GetSpecialValueFor("max_stacks") then + modifier:IncrementStackCount() + end + else + modifier = target:AddNewModifier( + self:GetParent(), + ability, + "modifier_weaking_impetus_debuff", + {duration = duration} + ) + modifier:SetStackCount(1) + end +end +modifier_weaking_impetus_passive = __TS__Decorate( + modifier_weaking_impetus_passive, + modifier_weaking_impetus_passive, + {registerModifier(nil)}, + {kind = "class", name = "modifier_weaking_impetus_passive"} +) +____exports.modifier_weaking_impetus_passive = modifier_weaking_impetus_passive +____exports.modifier_weaking_impetus_debuff = __TS__Class() +local modifier_weaking_impetus_debuff = ____exports.modifier_weaking_impetus_debuff +modifier_weaking_impetus_debuff.name = "modifier_weaking_impetus_debuff" +modifier_weaking_impetus_debuff.____file_path = "scripts/vscripts/abilities/creep/weaking_impetus.lua" +__TS__ClassExtends(modifier_weaking_impetus_debuff, BaseModifier) +function modifier_weaking_impetus_debuff.prototype.____constructor(self, ...) + BaseModifier.prototype.____constructor(self, ...) + self.damage_reduction = 0 +end +function modifier_weaking_impetus_debuff.prototype.IsHidden(self) + return false +end +function modifier_weaking_impetus_debuff.prototype.IsDebuff(self) + return true +end +function modifier_weaking_impetus_debuff.prototype.IsPurgable(self) + return true +end +function modifier_weaking_impetus_debuff.prototype.OnCreated(self) + local ability = self:GetAbility() + if not ability then + return + end + self.damage_reduction = ability:GetSpecialValueFor("damage_reduction") +end +function modifier_weaking_impetus_debuff.prototype.DeclareFunctions(self) + return {MODIFIER_PROPERTY_DAMAGEOUTGOING_PERCENTAGE, MODIFIER_PROPERTY_SPELL_AMPLIFY_PERCENTAGE} +end +function modifier_weaking_impetus_debuff.prototype.GetModifierDamageOutgoing_Percentage(self, event) + return -self.damage_reduction * self:GetStackCount() +end +function modifier_weaking_impetus_debuff.prototype.GetModifierSpellAmplify_Percentage(self, event) + return -self.damage_reduction * self:GetStackCount() +end +modifier_weaking_impetus_debuff = __TS__Decorate( + modifier_weaking_impetus_debuff, + modifier_weaking_impetus_debuff, + {registerModifier(nil)}, + {kind = "class", name = "modifier_weaking_impetus_debuff"} +) +____exports.modifier_weaking_impetus_debuff = modifier_weaking_impetus_debuff +return ____exports diff --git a/scripts/vscripts/abilities/creep/witch_base.lua b/scripts/vscripts/abilities/creep/witch_base.lua new file mode 100644 index 0000000..70338b1 --- /dev/null +++ b/scripts/vscripts/abilities/creep/witch_base.lua @@ -0,0 +1,72 @@ +local ____lualib = require("lualib_bundle") +local __TS__Class = ____lualib.__TS__Class +local __TS__ClassExtends = ____lualib.__TS__ClassExtends +local __TS__Decorate = ____lualib.__TS__Decorate +local ____exports = {} +local ____difficulty_manager = require("difficulty_manager") +local Difficulty = ____difficulty_manager.Difficulty +local ____dota_ts_adapter = require("lib.dota_ts_adapter") +local BaseAbility = ____dota_ts_adapter.BaseAbility +local BaseModifier = ____dota_ts_adapter.BaseModifier +local registerAbility = ____dota_ts_adapter.registerAbility +local registerModifier = ____dota_ts_adapter.registerModifier +____exports.witch_base = __TS__Class() +local witch_base = ____exports.witch_base +witch_base.name = "witch_base" +witch_base.____file_path = "scripts/vscripts/abilities/creep/witch_base.lua" +__TS__ClassExtends(witch_base, BaseAbility) +function witch_base.prototype.GetIntrinsicModifierName(self) + return "modifier_witch_base" +end +witch_base = __TS__Decorate( + witch_base, + witch_base, + {registerAbility(nil)}, + {kind = "class", name = "witch_base"} +) +____exports.witch_base = witch_base +____exports.modifier_witch_base = __TS__Class() +local modifier_witch_base = ____exports.modifier_witch_base +modifier_witch_base.name = "modifier_witch_base" +modifier_witch_base.____file_path = "scripts/vscripts/abilities/creep/witch_base.lua" +__TS__ClassExtends(modifier_witch_base, BaseModifier) +function modifier_witch_base.prototype.IsHidden(self) + return true +end +function modifier_witch_base.prototype.OnCreated(self, params) + local difficulty = Difficulty:getNpcStatScale() + local gameTime = GameRules:GetGameTime() / 60 + self:SetStackCount(math.floor(gameTime)) +end +function modifier_witch_base.prototype.OnRefresh(self, params) + self:OnCreated(params) +end +function modifier_witch_base.prototype.DeclareFunctions(self) + return { + MODIFIER_EVENT_ON_DEATH, + MODIFIER_PROPERTY_SPELL_AMPLIFY_PERCENTAGE, + MODIFIER_PROPERTY_PREATTACK_BONUS_DAMAGE, + MODIFIER_PROPERTY_EXTRA_HEALTH_BONUS, + MODIFIER_PROPERTY_PHYSICAL_ARMOR_BONUS + } +end +function modifier_witch_base.prototype.GetModifierSpellAmplify_Percentage(self, event) + return self:GetStackCount() * self:GetAbility():GetSpecialValueFor("amp_death_bonus") * Difficulty:getNpcStatScale() +end +function modifier_witch_base.prototype.GetModifierPreAttack_BonusDamage(self) + return self:GetStackCount() * self:GetAbility():GetSpecialValueFor("attack_death_bonus") * Difficulty:getNpcStatScale() +end +function modifier_witch_base.prototype.GetModifierExtraHealthBonus(self) + return self:GetStackCount() * self:GetAbility():GetSpecialValueFor("health_death_bonus") * Difficulty:getNpcStatScale() +end +function modifier_witch_base.prototype.GetModifierPhysicalArmorBonus(self, event) + return self:GetStackCount() * self:GetAbility():GetSpecialValueFor("armor_death_bonus") * Difficulty:getNpcStatScale() +end +modifier_witch_base = __TS__Decorate( + modifier_witch_base, + modifier_witch_base, + {registerModifier(nil)}, + {kind = "class", name = "modifier_witch_base"} +) +____exports.modifier_witch_base = modifier_witch_base +return ____exports diff --git a/scripts/vscripts/abilities/creep/zombie_armor_decress.lua b/scripts/vscripts/abilities/creep/zombie_armor_decress.lua new file mode 100644 index 0000000..f4f2e40 --- /dev/null +++ b/scripts/vscripts/abilities/creep/zombie_armor_decress.lua @@ -0,0 +1,115 @@ +local ____lualib = require("lualib_bundle") +local __TS__Class = ____lualib.__TS__Class +local __TS__ClassExtends = ____lualib.__TS__ClassExtends +local __TS__Decorate = ____lualib.__TS__Decorate +local ____exports = {} +local ____difficulty_manager = require("difficulty_manager") +local Difficulty = ____difficulty_manager.Difficulty +local ____dota_ts_adapter = require("lib.dota_ts_adapter") +local BaseAbility = ____dota_ts_adapter.BaseAbility +local BaseModifier = ____dota_ts_adapter.BaseModifier +local registerAbility = ____dota_ts_adapter.registerAbility +local registerModifier = ____dota_ts_adapter.registerModifier +____exports.zombie_armor_decress = __TS__Class() +local zombie_armor_decress = ____exports.zombie_armor_decress +zombie_armor_decress.name = "zombie_armor_decress" +zombie_armor_decress.____file_path = "scripts/vscripts/abilities/creep/zombie_armor_decress.lua" +__TS__ClassExtends(zombie_armor_decress, BaseAbility) +function zombie_armor_decress.prototype.GetIntrinsicModifierName(self) + return "modifier_zombie_armor_decress" +end +zombie_armor_decress = __TS__Decorate( + zombie_armor_decress, + zombie_armor_decress, + {registerAbility(nil)}, + {kind = "class", name = "zombie_armor_decress"} +) +____exports.zombie_armor_decress = zombie_armor_decress +____exports.modifier_zombie_armor_decress = __TS__Class() +local modifier_zombie_armor_decress = ____exports.modifier_zombie_armor_decress +modifier_zombie_armor_decress.name = "modifier_zombie_armor_decress" +modifier_zombie_armor_decress.____file_path = "scripts/vscripts/abilities/creep/zombie_armor_decress.lua" +__TS__ClassExtends(modifier_zombie_armor_decress, BaseModifier) +function modifier_zombie_armor_decress.prototype.IsHidden(self) + return true +end +function modifier_zombie_armor_decress.prototype.IsPurgable(self) + return false +end +function modifier_zombie_armor_decress.prototype.DeclareFunctions(self) + return {MODIFIER_EVENT_ON_ATTACK_LANDED} +end +function modifier_zombie_armor_decress.prototype.OnAttackLanded(self, event) + if not IsServer() then + return + end + if event.attacker ~= self:GetParent() then + return + end + if not event.target or not event.target:IsAlive() then + return + end + local ability = self:GetAbility() + if not ability then + return + end + local duration = ability:GetSpecialValueFor("corruption_duration") + event.target:AddNewModifier( + self:GetParent(), + ability, + ____exports.modifier_zombie_armor_decress_debuff.name, + {duration = duration} + ) +end +modifier_zombie_armor_decress = __TS__Decorate( + modifier_zombie_armor_decress, + modifier_zombie_armor_decress, + {registerModifier(nil)}, + {kind = "class", name = "modifier_zombie_armor_decress"} +) +____exports.modifier_zombie_armor_decress = modifier_zombie_armor_decress +____exports.modifier_zombie_armor_decress_debuff = __TS__Class() +local modifier_zombie_armor_decress_debuff = ____exports.modifier_zombie_armor_decress_debuff +modifier_zombie_armor_decress_debuff.name = "modifier_zombie_armor_decress_debuff" +modifier_zombie_armor_decress_debuff.____file_path = "scripts/vscripts/abilities/creep/zombie_armor_decress.lua" +__TS__ClassExtends(modifier_zombie_armor_decress_debuff, BaseModifier) +function modifier_zombie_armor_decress_debuff.prototype.____constructor(self, ...) + BaseModifier.prototype.____constructor(self, ...) + self.armorDebuffBase = 0 +end +function modifier_zombie_armor_decress_debuff.prototype.IsHidden(self) + return true +end +function modifier_zombie_armor_decress_debuff.prototype.GetAttributes(self) + return MODIFIER_ATTRIBUTE_MULTIPLE +end +function modifier_zombie_armor_decress_debuff.prototype.IsDebuff(self) + return true +end +function modifier_zombie_armor_decress_debuff.prototype.IsPurgable(self) + return true +end +function modifier_zombie_armor_decress_debuff.prototype.DeclareFunctions(self) + return {MODIFIER_PROPERTY_PHYSICAL_ARMOR_BONUS} +end +function modifier_zombie_armor_decress_debuff.prototype.OnCreated(self) + local ability = self:GetAbility() + if not ability then + return + end + self.armorDebuffBase = ability:GetSpecialValueFor("armor_debuff") +end +function modifier_zombie_armor_decress_debuff.prototype.OnRefresh(self) + self:OnCreated() +end +function modifier_zombie_armor_decress_debuff.prototype.GetModifierPhysicalArmorBonus(self) + return self.armorDebuffBase * Difficulty:getNpcStatScale() +end +modifier_zombie_armor_decress_debuff = __TS__Decorate( + modifier_zombie_armor_decress_debuff, + modifier_zombie_armor_decress_debuff, + {registerModifier(nil)}, + {kind = "class", name = "modifier_zombie_armor_decress_debuff"} +) +____exports.modifier_zombie_armor_decress_debuff = modifier_zombie_armor_decress_debuff +return ____exports diff --git a/scripts/vscripts/abilities/creep/zombie_death_explosion.lua b/scripts/vscripts/abilities/creep/zombie_death_explosion.lua new file mode 100644 index 0000000..0183064 --- /dev/null +++ b/scripts/vscripts/abilities/creep/zombie_death_explosion.lua @@ -0,0 +1,123 @@ +local ____lualib = require("lualib_bundle") +local __TS__Class = ____lualib.__TS__Class +local __TS__ClassExtends = ____lualib.__TS__ClassExtends +local __TS__Decorate = ____lualib.__TS__Decorate +local ____exports = {} +local ____dota_ts_adapter = require("lib.dota_ts_adapter") +local BaseAbility = ____dota_ts_adapter.BaseAbility +local BaseModifier = ____dota_ts_adapter.BaseModifier +local registerAbility = ____dota_ts_adapter.registerAbility +local registerModifier = ____dota_ts_adapter.registerModifier +local ____creep_render_color = require("utils.creep_render_color") +local trySetIntrinsicCreepRenderColor = ____creep_render_color.trySetIntrinsicCreepRenderColor +--- Пассив: при смерти взрыв по радиусу — урон и союзникам, и врагам (герои и крипы). +____exports.zombie_death_explosion = __TS__Class() +local zombie_death_explosion = ____exports.zombie_death_explosion +zombie_death_explosion.name = "zombie_death_explosion" +zombie_death_explosion.____file_path = "scripts/vscripts/abilities/creep/zombie_death_explosion.lua" +__TS__ClassExtends(zombie_death_explosion, BaseAbility) +function zombie_death_explosion.prototype.GetIntrinsicModifierName(self) + return "modifier_zombie_death_explosion_listener" +end +function zombie_death_explosion.prototype.Precache(self, context) + PrecacheResource("particle", "particles/units/heroes/hero_techies/techies_remote_mines_detonate.vpcf", context) +end +zombie_death_explosion = __TS__Decorate( + zombie_death_explosion, + zombie_death_explosion, + {registerAbility(nil)}, + {kind = "class", name = "zombie_death_explosion"} +) +____exports.zombie_death_explosion = zombie_death_explosion +____exports.modifier_zombie_death_explosion_listener = __TS__Class() +local modifier_zombie_death_explosion_listener = ____exports.modifier_zombie_death_explosion_listener +modifier_zombie_death_explosion_listener.name = "modifier_zombie_death_explosion_listener" +modifier_zombie_death_explosion_listener.____file_path = "scripts/vscripts/abilities/creep/zombie_death_explosion.lua" +__TS__ClassExtends(modifier_zombie_death_explosion_listener, BaseModifier) +function modifier_zombie_death_explosion_listener.prototype.IsHidden(self) + return true +end +function modifier_zombie_death_explosion_listener.prototype.IsPurgable(self) + return false +end +function modifier_zombie_death_explosion_listener.prototype.DeclareFunctions(self) + return {MODIFIER_EVENT_ON_DEATH} +end +function modifier_zombie_death_explosion_listener.prototype.OnCreated(self, params) + if IsServer() then + trySetIntrinsicCreepRenderColor( + nil, + self:GetParent(), + 255, + 0, + 0 + ) + end +end +function modifier_zombie_death_explosion_listener.prototype.OnDeath(self, event) + if not IsServer() then + return + end + local parent = self:GetParent() + if event.unit ~= parent then + return + end + local ability = self:GetAbility() + if not ability then + return + end + local radius = ability:GetSpecialValueFor("radius") + local baseDamage = ability:GetSpecialValueFor("explosion_damage") + self:GetCaster():GetAttackDamage() * 0.35 + local origin = parent:GetAbsOrigin() + local particle = ParticleManager:CreateParticle("particles/units/heroes/hero_techies/techies_remote_mines_detonate.vpcf", PATTACH_WORLDORIGIN, nil) + ParticleManager:SetParticleControl(particle, 0, origin) + ParticleManager:SetParticleControl( + particle, + 1, + Vector(radius, radius, radius) + ) + ParticleManager:ReleaseParticleIndex(particle) + EmitSoundOn("Hero_Techies.RemoteMine.Detonate", parent) + local units = FindUnitsInRadius( + parent:GetTeamNumber(), + origin, + nil, + radius, + DOTA_UNIT_TARGET_TEAM_BOTH, + bit.bor(DOTA_UNIT_TARGET_HERO, DOTA_UNIT_TARGET_BASIC), + DOTA_UNIT_TARGET_FLAG_NONE, + FIND_ANY_ORDER, + false + ) + local deadIndex = parent:entindex() + for ____, target in ipairs(units) do + do + if not target or not IsValidEntity(target) or not target:IsAlive() then + goto __continue13 + end + if target:entindex() == deadIndex then + goto __continue13 + end + local damage = target:IsRangedAttacker() and baseDamage or baseDamage * 0.25 + if target:GetTeamNumber() == parent:GetTeamNumber() then + damage = damage * 0.5 + end + ApplyDamage({ + victim = target, + attacker = parent, + damage = damage, + damage_type = DAMAGE_TYPE_PHYSICAL, + ability = ability + }) + end + ::__continue13:: + end +end +modifier_zombie_death_explosion_listener = __TS__Decorate( + modifier_zombie_death_explosion_listener, + modifier_zombie_death_explosion_listener, + {registerModifier(nil)}, + {kind = "class", name = "modifier_zombie_death_explosion_listener"} +) +____exports.modifier_zombie_death_explosion_listener = modifier_zombie_death_explosion_listener +return ____exports diff --git a/scripts/vscripts/abilities/creep/zombie_virus.lua b/scripts/vscripts/abilities/creep/zombie_virus.lua new file mode 100644 index 0000000..7d8bdf2 --- /dev/null +++ b/scripts/vscripts/abilities/creep/zombie_virus.lua @@ -0,0 +1,227 @@ +local ____lualib = require("lualib_bundle") +local __TS__Class = ____lualib.__TS__Class +local __TS__ClassExtends = ____lualib.__TS__ClassExtends +local __TS__Decorate = ____lualib.__TS__Decorate +local ____exports = {} +local ____difficulty_manager = require("difficulty_manager") +local Difficulty = ____difficulty_manager.Difficulty +local ____dota_ts_adapter = require("lib.dota_ts_adapter") +local BaseAbility = ____dota_ts_adapter.BaseAbility +local BaseModifier = ____dota_ts_adapter.BaseModifier +local registerAbility = ____dota_ts_adapter.registerAbility +local registerModifier = ____dota_ts_adapter.registerModifier +local ZOMBIE_VIRUS_DEBUFF_NAME = "modifier_zombie_virus_debuff" +local ZOMBIE_VIRUS_MAX_INSTANCES = 9 +local function countZombieVirusDebuffInstances(self, unit) + local n = 0 + do + local i = 0 + while i < unit:GetModifierCount() do + if unit:GetModifierNameByIndex(i) == ZOMBIE_VIRUS_DEBUFF_NAME then + n = n + 1 + end + i = i + 1 + end + end + return n +end +____exports.zombie_virus = __TS__Class() +local zombie_virus = ____exports.zombie_virus +zombie_virus.name = "zombie_virus" +zombie_virus.____file_path = "scripts/vscripts/abilities/creep/zombie_virus.lua" +__TS__ClassExtends(zombie_virus, BaseAbility) +function zombie_virus.prototype.GetIntrinsicModifierName(self) + return "modifier_zombie_virus_intrinsic" +end +zombie_virus = __TS__Decorate( + zombie_virus, + zombie_virus, + {registerAbility(nil)}, + {kind = "class", name = "zombie_virus"} +) +____exports.zombie_virus = zombie_virus +____exports.modifier_zombie_virus_intrinsic = __TS__Class() +local modifier_zombie_virus_intrinsic = ____exports.modifier_zombie_virus_intrinsic +modifier_zombie_virus_intrinsic.name = "modifier_zombie_virus_intrinsic" +modifier_zombie_virus_intrinsic.____file_path = "scripts/vscripts/abilities/creep/zombie_virus.lua" +__TS__ClassExtends(modifier_zombie_virus_intrinsic, BaseModifier) +function modifier_zombie_virus_intrinsic.prototype.IsHidden(self) + return true +end +function modifier_zombie_virus_intrinsic.prototype.IsDebuff(self) + return false +end +function modifier_zombie_virus_intrinsic.prototype.IsPurgable(self) + return false +end +function modifier_zombie_virus_intrinsic.prototype.OnCreated(self) + if not IsServer() then + return + end +end +function modifier_zombie_virus_intrinsic.prototype.DeclareFunctions(self) + return {MODIFIER_EVENT_ON_ATTACK_LANDED} +end +function modifier_zombie_virus_intrinsic.prototype.OnAttackLanded(self, event) + if not IsServer() then + return + end + if event.attacker ~= self:GetParent() then + return + end + local ability = self:GetAbility() + if not ability then + return + end + local primaryZombieVirus = event.attacker:FindAbilityByName("zombie_virus") + if not primaryZombieVirus or ability ~= primaryZombieVirus then + return + end + local target = event.target + if not target or target:GetUnitName() == "npc_homer" then + return + end + local duration = ability:GetSpecialValueFor("duration") + if RandomInt(1, 100) > ability:GetSpecialValueFor("chance") then + return + end + if countZombieVirusDebuffInstances(nil, target) >= ZOMBIE_VIRUS_MAX_INSTANCES then + return + end + target:AddNewModifier( + self:GetParent(), + ability, + ZOMBIE_VIRUS_DEBUFF_NAME, + {duration = duration} + ) +end +modifier_zombie_virus_intrinsic = __TS__Decorate( + modifier_zombie_virus_intrinsic, + modifier_zombie_virus_intrinsic, + {registerModifier(nil)}, + {kind = "class", name = "modifier_zombie_virus_intrinsic"} +) +____exports.modifier_zombie_virus_intrinsic = modifier_zombie_virus_intrinsic +____exports.modifier_zombie_virus_debuff = __TS__Class() +local modifier_zombie_virus_debuff = ____exports.modifier_zombie_virus_debuff +modifier_zombie_virus_debuff.name = "modifier_zombie_virus_debuff" +modifier_zombie_virus_debuff.____file_path = "scripts/vscripts/abilities/creep/zombie_virus.lua" +__TS__ClassExtends(modifier_zombie_virus_debuff, BaseModifier) +function modifier_zombie_virus_debuff.prototype.IsHidden(self) + return true +end +function modifier_zombie_virus_debuff.prototype.IsDebuff(self) + return true +end +function modifier_zombie_virus_debuff.prototype.IsPurgable(self) + return true +end +function modifier_zombie_virus_debuff.prototype.DeclareFunctions(self) + return {MODIFIER_PROPERTY_MOVESPEED_BONUS_PERCENTAGE, MODIFIER_PROPERTY_PHYSICAL_ARMOR_BONUS} +end +function modifier_zombie_virus_debuff.prototype.GetModifierMoveSpeedBonus_Percentage(self) + local ability = self:GetAbility() + if not ability then + return 0 + end + return -ability:GetSpecialValueFor("slow_movespeed") * Difficulty:getNpcStatScale() +end +function modifier_zombie_virus_debuff.prototype.GetModifierPhysicalArmorBonus(self) + local ability = self:GetAbility() + if not ability then + return 0 + end + return -ability:GetSpecialValueFor("armor") * Difficulty:getNpcStatScale() +end +function modifier_zombie_virus_debuff.prototype.OnCreated(self) + local ability = self:GetAbility() + if not ability then + return + end + self.damage = ability:GetSpecialValueFor("damage") + self.tickInterval = ability:GetSpecialValueFor("tick_interval") + if IsServer() then + local caster = self:GetCaster() + if not caster or caster:IsNull() or not IsValidEntity(caster) or not caster:IsAlive() then + self:Destroy() + return + end + self:StartIntervalThink(self.tickInterval) + self.particleId = ParticleManager:CreateParticle( + "particles/econ/items/viper/viper_ti7_immortal/viper_poison_debuff_ti7.vpcf", + PATTACH_ABSORIGIN_FOLLOW, + self:GetParent() + ) + ParticleManager:SetParticleControl( + self.particleId, + 0, + self:GetParent():GetAbsOrigin() + ) + ParticleManager:SetParticleControl( + self.particleId, + 1, + self:GetParent():GetAbsOrigin() + ) + end +end +function modifier_zombie_virus_debuff.prototype.OnRefresh(self, params) + if self.particleId then + ParticleManager:DestroyParticle(self.particleId, false) + ParticleManager:ReleaseParticleIndex(self.particleId) + end +end +function modifier_zombie_virus_debuff.prototype.OnIntervalThink(self) + if not IsServer() then + return + end + local ability = self:GetAbility() + local caster = self:GetCaster() + local target = self:GetParent() + if not ability or ability:IsNull() or not caster or caster:IsNull() or not IsValidEntity(caster) or not caster:IsAlive() then + self:Destroy() + return + end + local tickDamage = self.damage * self.tickInterval * Difficulty:getNpcStatScale() + if target and target:IsAlive() then + ApplyDamage({ + victim = target, + attacker = caster, + damage = tickDamage, + damage_type = DAMAGE_TYPE_PHYSICAL, + ability = ability + }) + SendOverheadEventMessage( + nil, + OVERHEAD_ALERT_DAMAGE, + target, + tickDamage, + nil + ) + end +end +function modifier_zombie_virus_debuff.prototype.OnDestroy(self) + if IsClient() then + return + end + if self.particleId then + ParticleManager:DestroyParticle(self.particleId, false) + ParticleManager:ReleaseParticleIndex(self.particleId) + end +end +function modifier_zombie_virus_debuff.prototype.GetEffectName(self) + return "particles/econ/items/void_spirit/void_spirit_immortal_2021/void_spirit_immortal_2021_astral_step_debuff.vpcf" +end +function modifier_zombie_virus_debuff.prototype.GetTexture(self) + return "life_stealer_open_wounds" +end +function modifier_zombie_virus_debuff.prototype.GetEffectAttachType(self) + return PATTACH_ABSORIGIN_FOLLOW +end +modifier_zombie_virus_debuff = __TS__Decorate( + modifier_zombie_virus_debuff, + modifier_zombie_virus_debuff, + {registerModifier(nil)}, + {kind = "class", name = "modifier_zombie_virus_debuff"} +) +____exports.modifier_zombie_virus_debuff = modifier_zombie_virus_debuff +return ____exports diff --git a/scripts/vscripts/abilities/fish_basic.lua b/scripts/vscripts/abilities/fish_basic.lua new file mode 100644 index 0000000..f184356 --- /dev/null +++ b/scripts/vscripts/abilities/fish_basic.lua @@ -0,0 +1,56 @@ +local ____lualib = require("lualib_bundle") +local __TS__Class = ____lualib.__TS__Class +local __TS__ClassExtends = ____lualib.__TS__ClassExtends +local __TS__Decorate = ____lualib.__TS__Decorate +local ____exports = {} +local ____dota_ts_adapter = require("lib.dota_ts_adapter") +local BaseAbility = ____dota_ts_adapter.BaseAbility +local BaseModifier = ____dota_ts_adapter.BaseModifier +local registerAbility = ____dota_ts_adapter.registerAbility +local registerModifier = ____dota_ts_adapter.registerModifier +____exports.fish_basic = __TS__Class() +local fish_basic = ____exports.fish_basic +fish_basic.name = "fish_basic" +fish_basic.____file_path = "scripts/vscripts/abilities/fish_basic.lua" +__TS__ClassExtends(fish_basic, BaseAbility) +function fish_basic.prototype.GetIntrinsicModifierName(self) + return "modifier_fish_basic" +end +fish_basic = __TS__Decorate( + fish_basic, + fish_basic, + {registerAbility(nil)}, + {kind = "class", name = "fish_basic"} +) +____exports.fish_basic = fish_basic +____exports.modifier_fish_basic = __TS__Class() +local modifier_fish_basic = ____exports.modifier_fish_basic +modifier_fish_basic.name = "modifier_fish_basic" +modifier_fish_basic.____file_path = "scripts/vscripts/abilities/fish_basic.lua" +__TS__ClassExtends(modifier_fish_basic, BaseModifier) +function modifier_fish_basic.prototype.IsHidden(self) + return true +end +function modifier_fish_basic.prototype.IsPurgable(self) + return false +end +function modifier_fish_basic.prototype.IsDebuff(self) + return false +end +function modifier_fish_basic.prototype.CheckState(self) + return {[MODIFIER_STATE_NO_HEALTH_BAR] = true, [MODIFIER_STATE_UNSELECTABLE] = true, [MODIFIER_STATE_UNTARGETABLE] = true} +end +function modifier_fish_basic.prototype.DeclareFunctions(self) + return {MODIFIER_PROPERTY_MIN_HEALTH} +end +function modifier_fish_basic.prototype.GetMinHealth(self) + return self:GetParent():GetHealth() +end +modifier_fish_basic = __TS__Decorate( + modifier_fish_basic, + modifier_fish_basic, + {registerModifier(nil)}, + {kind = "class", name = "modifier_fish_basic"} +) +____exports.modifier_fish_basic = modifier_fish_basic +return ____exports diff --git a/scripts/vscripts/abilities/hero_rage/apply_hero_rage.lua b/scripts/vscripts/abilities/hero_rage/apply_hero_rage.lua new file mode 100644 index 0000000..e4b534f --- /dev/null +++ b/scripts/vscripts/abilities/hero_rage/apply_hero_rage.lua @@ -0,0 +1,39 @@ +--[[ Generated with https://github.com/TypeScriptToLua/TypeScriptToLua ]] +local ____exports = {} +local ____hero_rage_whitelist = require("abilities.hero_rage.hero_rage_whitelist") +local isUnitNameAllowedForHeroRage = ____hero_rage_whitelist.isUnitNameAllowedForHeroRage +--- Параметры в духе infinity_levels / hero_rage. +local DEFAULT_HERO_RAGE = { + max_rage = 100, + rage_per_attack = 3, + rage_per_damage = 1, + time_decrase_rage = 4, + tick_decrase_rage = 0.5 +} +local MOD_NAME = "modifier_hero_rage" +--- Вешает систему «ярости» (мана = ярость) на героя, если ещё не висит. +-- Нужна любая способность-носитель для AddNewModifier (у всех наших героев есть ability_stacking_crit). +-- **Только** герои из `hero_rage_whitelist.ts`. +function ____exports.tryApplyDefaultHeroRage(self, hero) + if not IsServer() then + return + end + if not hero:IsRealHero() or hero:IsIllusion() then + return + end + if not isUnitNameAllowedForHeroRage( + nil, + hero:GetUnitName() + ) then + return + end + if hero:HasModifier(MOD_NAME) then + return + end + local host = hero:FindAbilityByName("ability_stacking_crit") + if not host then + return + end + hero:AddNewModifier(hero, host, MOD_NAME, DEFAULT_HERO_RAGE) +end +return ____exports diff --git a/scripts/vscripts/abilities/hero_rage/hero_rage_modifiers.lua b/scripts/vscripts/abilities/hero_rage/hero_rage_modifiers.lua new file mode 100644 index 0000000..d8f6d8b --- /dev/null +++ b/scripts/vscripts/abilities/hero_rage/hero_rage_modifiers.lua @@ -0,0 +1,222 @@ +local ____lualib = require("lualib_bundle") +local __TS__Class = ____lualib.__TS__Class +local __TS__ClassExtends = ____lualib.__TS__ClassExtends +local __TS__Decorate = ____lualib.__TS__Decorate +local ____exports = {} +local ____dota_ts_adapter = require("lib.dota_ts_adapter") +local BaseModifier = ____dota_ts_adapter.BaseModifier +local registerModifier = ____dota_ts_adapter.registerModifier +local ____hero_rage_nettable = require("abilities.hero_rage.hero_rage_nettable") +local clearHeroRageNetTable = ____hero_rage_nettable.clearHeroRageNetTable +local syncHeroRageNetTable = ____hero_rage_nettable.syncHeroRageNetTable +local DEFAULT_MAX = 100 +local DEFAULT_RAGE_PER_ATTACK = 3 +local DEFAULT_RAGE_WHEN_HIT = 1 +local DEFAULT_OUT_OF_COMBAT = 4 +--- Как у infinity: шаг, с которым списывается 1 ед. ярости после тайм-аута «без прироста». +local DEFAULT_DECAY_COOLDOWN = 0.5 +--- Реже тик — меньше нагрузка (раньше 0.05 сильно лагало). +local MANA_SYNC = 0.12 +local function readParam(self, params, key, def) + if params == nil then + return def + end + local t = params + local v = t[key] + if v == nil or v == nil then + return def + end + local n = tonumber(v) + return n ~= nil and n ~= nil and n or def +end +--- Максимальная и текущая «мана» героя = шкала ярости; списание маны при кастах = ярость. +-- Накопление: удары и получение автоатак, затухание после простоя. +____exports.modifier_hero_rage = __TS__Class() +local modifier_hero_rage = ____exports.modifier_hero_rage +modifier_hero_rage.name = "modifier_hero_rage" +modifier_hero_rage.____file_path = "scripts/vscripts/abilities/hero_rage/hero_rage_modifiers.lua" +__TS__ClassExtends(modifier_hero_rage, BaseModifier) +function modifier_hero_rage.prototype.____constructor(self, ...) + BaseModifier.prototype.____constructor(self, ...) + self.maxRage = DEFAULT_MAX + self.ragePerAttack = DEFAULT_RAGE_PER_ATTACK + self.rageWhenHit = DEFAULT_RAGE_WHEN_HIT + self.timeOutForDecay = DEFAULT_OUT_OF_COMBAT + self.decayStep = DEFAULT_DECAY_COOLDOWN + self.manaDelta = 0 + self.timeSinceRageGain = 0 + self.decayAcc = 0 + self.lastNetCur = -1 + self.lastNetMax = -1 + self.intellectReadLock = false +end +function modifier_hero_rage.prototype.IsHidden(self) + return true +end +function modifier_hero_rage.prototype.IsDebuff(self) + return false +end +function modifier_hero_rage.prototype.IsPurgable(self) + return false +end +function modifier_hero_rage.prototype.RemoveOnDeath(self) + return false +end +function modifier_hero_rage.prototype.DeclareFunctions(self) + return {MODIFIER_PROPERTY_MANA_BONUS, MODIFIER_PROPERTY_STATS_INTELLECT_BONUS, MODIFIER_EVENT_ON_ATTACK_LANDED, MODIFIER_EVENT_ON_DEATH} +end +function modifier_hero_rage.prototype.OnCreated(self, params) + self.parentHero = self:GetParent() + self.maxRage = readParam(nil, params, "max_rage", DEFAULT_MAX) + self.ragePerAttack = readParam(nil, params, "rage_per_attack", DEFAULT_RAGE_PER_ATTACK) + self.rageWhenHit = readParam(nil, params, "rage_per_damage", DEFAULT_RAGE_WHEN_HIT) + self.timeOutForDecay = readParam(nil, params, "time_decrase_rage", DEFAULT_OUT_OF_COMBAT) + self.decayStep = readParam(nil, params, "tick_decrase_rage", DEFAULT_DECAY_COOLDOWN) + self.timeSinceRageGain = 0 + self.decayAcc = 0 + self.manaDelta = 0 + if IsServer() then + self:StartIntervalThink(MANA_SYNC) + self.parentHero:SetMana(0) + self.lastNetCur = -1 + self.lastNetMax = -1 + self.parentHero:CalculateStatBonus(true) + syncHeroRageNetTable( + nil, + self.parentHero, + 0, + self.maxRage, + true + ) + end +end +function modifier_hero_rage.prototype.OnRefresh(self, params) + self.maxRage = readParam(nil, params, "max_rage", self.maxRage) +end +function modifier_hero_rage.prototype.GetModifierManaBonus(self) + return self.manaDelta +end +function modifier_hero_rage.prototype.GetModifierBonusStats_Intellect(self) + if self.intellectReadLock then + return 0 + end + local p = self:GetParent() + if not p or p:IsNull() then + return 0 + end + self.intellectReadLock = true + local int = p:GetIntellect(true) + self.intellectReadLock = false + return int * -1 +end +function modifier_hero_rage.prototype.OnAttackLanded(self, event) + if not IsServer() then + return + end + local p = self.parentHero + if event.attacker == p then + self:bumpRage(self.ragePerAttack) + elseif event.target == p then + self:bumpRage(self.rageWhenHit) + end +end +function modifier_hero_rage.prototype.OnDeath(self, event) + if not IsServer() then + return + end + if event.unit ~= self:GetParent() then + return + end + self.parentHero:SetMana(0) + self:pushNet(0, self.maxRage, true) +end +function modifier_hero_rage.prototype.bumpRage(self, amt) + if amt <= 0 then + return + end + self.timeSinceRageGain = 0 + self.decayAcc = 0 + local p = self.parentHero + local m = p:GetMaxMana() + p:SetMana(math.min( + m, + p:GetMana() + amt + )) +end +function modifier_hero_rage.prototype.stripPassiveManaRegen(self, p) + if not p or p:IsNull() then + return + end + local regenPerSec = p:GetManaRegen() + if regenPerSec > 0 then + p:SetMana(math.max( + 0, + p:GetMana() - regenPerSec * MANA_SYNC + )) + end +end +function modifier_hero_rage.prototype.pushNet(self, cur, max, force) + if force == nil then + force = false + end + if not force and self.lastNetCur == cur and self.lastNetMax == max then + return + end + self.lastNetCur = cur + self.lastNetMax = max + syncHeroRageNetTable( + nil, + self.parentHero, + cur, + max, + true + ) +end +function modifier_hero_rage.prototype.OnIntervalThink(self) + if not IsServer() then + return + end + local p = self.parentHero + if not p or p:IsNull() or not p:IsAlive() then + return + end + self.manaDelta = self.maxRage - p:GetMaxMana() + (self.manaDelta or 0) + p:CalculateStatBonus(true) + p:SetMana(math.min( + p:GetMana(), + p:GetMaxMana() + )) + self:stripPassiveManaRegen(p) + self.timeSinceRageGain = self.timeSinceRageGain + MANA_SYNC + if self.timeSinceRageGain >= self.timeOutForDecay then + self.decayAcc = self.decayAcc + MANA_SYNC + if self.decayAcc >= self.decayStep then + p:SetMana(math.max( + 0, + p:GetMana() - 1 + )) + self.decayAcc = self.decayAcc - self.decayStep + end + end + self:pushNet( + p:GetMana(), + math.min( + p:GetMaxMana(), + self.maxRage + ), + false + ) +end +function modifier_hero_rage.prototype.OnDestroy(self) + if IsServer() then + clearHeroRageNetTable(nil, self.parentHero) + end +end +modifier_hero_rage = __TS__Decorate( + modifier_hero_rage, + modifier_hero_rage, + {registerModifier(nil)}, + {kind = "class", name = "modifier_hero_rage"} +) +____exports.modifier_hero_rage = modifier_hero_rage +return ____exports diff --git a/scripts/vscripts/abilities/hero_rage/hero_rage_nettable.lua b/scripts/vscripts/abilities/hero_rage/hero_rage_nettable.lua new file mode 100644 index 0000000..384345c --- /dev/null +++ b/scripts/vscripts/abilities/hero_rage/hero_rage_nettable.lua @@ -0,0 +1,41 @@ +--[[ Generated with https://github.com/TypeScriptToLua/TypeScriptToLua ]] +local ____exports = {} +--- Синхронизация «ярости» (игровая мана) с клиентом для кастомного HUD. +local TABLE = "hero_rage" +function ____exports.syncHeroRageNetTable(self, hero, current, max, active) + if not IsServer() then + return + end + local pid = hero:GetPlayerOwnerID() + if pid < 0 then + return + end + local row = { + current = math.floor(current + 0.5), + max = math.max( + 0, + math.floor(max + 0.5) + ), + active = active and 1 or 0 + } + CustomNetTables:SetTableValue( + TABLE, + tostring(pid), + row + ) +end +function ____exports.clearHeroRageNetTable(self, hero) + if not IsServer() then + return + end + local pid = hero:GetPlayerOwnerID() + if pid < 0 then + return + end + CustomNetTables:SetTableValue( + TABLE, + tostring(pid), + {current = 0, max = 0, active = 0} + ) +end +return ____exports diff --git a/scripts/vscripts/abilities/hero_rage/hero_rage_whitelist.lua b/scripts/vscripts/abilities/hero_rage/hero_rage_whitelist.lua new file mode 100644 index 0000000..ad70b1b --- /dev/null +++ b/scripts/vscripts/abilities/hero_rage/hero_rage_whitelist.lua @@ -0,0 +1,13 @@ +--[[ Generated with https://github.com/TypeScriptToLua/TypeScriptToLua ]] +local ____exports = {} +--- Ярость выдаётся **только** героям, чьё `GetUnitName()` есть в этой таблице. +-- Имена — как в KV/npc: `npc_dota_hero_*`, кастомы — `npc_из_твоей_таверны_hero_...` если так заведено. +local HERO_RAGE_UNIT_NAMES = {npc_dota_hero_axe = true, npc_dota_hero_sven = true, npc_dota_hero_juggernaut = true} +--- Проверка, входит ли юнит в белый список ярости. +function ____exports.isUnitNameAllowedForHeroRage(self, unitName) + if unitName == nil or unitName == nil or unitName == "" then + return false + end + return HERO_RAGE_UNIT_NAMES[unitName] == true +end +return ____exports diff --git a/scripts/vscripts/abilities/heroes/axe/axe_battle_hunger_custom.lua b/scripts/vscripts/abilities/heroes/axe/axe_battle_hunger_custom.lua new file mode 100644 index 0000000..a2c2423 --- /dev/null +++ b/scripts/vscripts/abilities/heroes/axe/axe_battle_hunger_custom.lua @@ -0,0 +1,352 @@ +local ____lualib = require("lualib_bundle") +local __TS__Class = ____lualib.__TS__Class +local __TS__ClassExtends = ____lualib.__TS__ClassExtends +local __TS__Decorate = ____lualib.__TS__Decorate +local ____exports = {} +local ____dota_ts_adapter = require("lib.dota_ts_adapter") +local BaseAbility = ____dota_ts_adapter.BaseAbility +local BaseModifier = ____dota_ts_adapter.BaseModifier +local registerAbility = ____dota_ts_adapter.registerAbility +local registerModifier = ____dota_ts_adapter.registerModifier +local ____modifier_general_hunger = require("abilities.modifiers.modifier_general_hunger") +local modifier_general_hunger = ____modifier_general_hunger.modifier_general_hunger +local ____heal_tracker = require("utils.heal_tracker") +local HealWithBattlePass = ____heal_tracker.HealWithBattlePass +____exports.AXE_BATTLE_HUNGER_CUSTOM = "axe_battle_hunger_custom" +--- Вызывается из Berserker's Call (шард): вешает голод даже без клика, если способность прокачана. +function ____exports.axeApplyBattleHungerFromCall(self, caster, target, duration) + if not IsServer() then + return + end + local hunger = caster:FindAbilityByName(____exports.AXE_BATTLE_HUNGER_CUSTOM) + if not hunger or hunger:GetLevel() <= 0 then + return + end + if target:GetTeamNumber() == caster:GetTeamNumber() then + target:AddNewModifier(caster, hunger, ____exports.modifier_axe_battle_hunger_ally_buff.name, {duration = duration}) + else + target:AddNewModifier(caster, hunger, ____exports.modifier_axe_battle_hunger_debuff.name, {duration = duration}) + end +end +____exports.axe_battle_hunger_custom = __TS__Class() +local axe_battle_hunger_custom = ____exports.axe_battle_hunger_custom +axe_battle_hunger_custom.name = "axe_battle_hunger_custom" +axe_battle_hunger_custom.____file_path = "scripts/vscripts/abilities/heroes/axe/axe_battle_hunger_custom.lua" +__TS__ClassExtends(axe_battle_hunger_custom, BaseAbility) +function axe_battle_hunger_custom.prototype.Precache(self, context) + PrecacheResource("particle", "particles/units/heroes/hero_axe/axe_battle_hunger.vpcf", context) + PrecacheResource("soundfile", "soundevents/game_sounds_heroes/game_sounds_axe.vsndevts", context) +end +function axe_battle_hunger_custom.prototype.OnSpellStart(self) + if not IsServer() then + return + end + local caster = self:GetCaster() + local target = self:GetCursorTarget() + if not target then + return + end + local duration = self:GetSpecialValueFor("duration") + if target:GetTeamNumber() == caster:GetTeamNumber() then + target:AddNewModifier(caster, self, ____exports.modifier_axe_battle_hunger_ally_buff.name, {duration = duration}) + else + target:AddNewModifier(caster, self, ____exports.modifier_axe_battle_hunger_debuff.name, {duration = duration}) + end + EmitSoundOn("Hero_Axe.Battle_Hunger", target) +end +function axe_battle_hunger_custom.prototype.GetCastRange(self) + return self:GetSpecialValueFor("AbilityCastRange") +end +function axe_battle_hunger_custom.prototype.CastFilterResultTarget(self, target) + if target:IsInvulnerable() then + return UF_FAIL_CUSTOM + end + return UF_SUCCESS +end +function axe_battle_hunger_custom.prototype.GetAbilityTargetTeam(self) + return DOTA_UNIT_TARGET_TEAM_BOTH +end +axe_battle_hunger_custom.ALLY_HUNGER_STACKS_PER_SECOND = 5 +axe_battle_hunger_custom.ALLY_HEAL_PCT_PER_SECOND = 1.5 +axe_battle_hunger_custom = __TS__Decorate( + axe_battle_hunger_custom, + axe_battle_hunger_custom, + {registerAbility(nil)}, + {kind = "class", name = "axe_battle_hunger_custom"} +) +____exports.axe_battle_hunger_custom = axe_battle_hunger_custom +--- На Аксе: +% скорости за каждого врага с Battle Hunger. +____exports.modifier_axe_battle_hunger_owner_counter = __TS__Class() +local modifier_axe_battle_hunger_owner_counter = ____exports.modifier_axe_battle_hunger_owner_counter +modifier_axe_battle_hunger_owner_counter.name = "modifier_axe_battle_hunger_owner_counter" +modifier_axe_battle_hunger_owner_counter.____file_path = "scripts/vscripts/abilities/heroes/axe/axe_battle_hunger_custom.lua" +__TS__ClassExtends(modifier_axe_battle_hunger_owner_counter, BaseModifier) +function modifier_axe_battle_hunger_owner_counter.prototype.IsHidden(self) + return true +end +function modifier_axe_battle_hunger_owner_counter.prototype.IsPurgable(self) + return false +end +function modifier_axe_battle_hunger_owner_counter.changeStacksOnCaster(self, caster, delta) + local ab = caster:FindAbilityByName(____exports.AXE_BATTLE_HUNGER_CUSTOM) + if ab then + local m = caster:FindModifierByName(____exports.modifier_axe_battle_hunger_owner_counter.name) + if not m and delta > 0 then + m = caster:AddNewModifier(caster, ab, ____exports.modifier_axe_battle_hunger_owner_counter.name, {}) + end + if m and not m:IsNull() then + local next = math.max( + 0, + m:GetStackCount() + delta + ) + m:SetStackCount(next) + if next <= 0 then + m:Destroy() + end + end + end +end +function modifier_axe_battle_hunger_owner_counter.prototype.DeclareFunctions(self) + return {MODIFIER_PROPERTY_MOVESPEED_BONUS_PERCENTAGE} +end +function modifier_axe_battle_hunger_owner_counter.prototype.GetModifierMoveSpeedBonus_Percentage(self) + local ab = self:GetAbility() + if not ab then + return 0 + end + local pct = ab:GetSpecialValueFor("speed_per_enemy_pct") + return self:GetStackCount() * pct +end +modifier_axe_battle_hunger_owner_counter = __TS__Decorate( + modifier_axe_battle_hunger_owner_counter, + modifier_axe_battle_hunger_owner_counter, + {registerModifier(nil)}, + {kind = "class", name = "modifier_axe_battle_hunger_owner_counter"} +) +____exports.modifier_axe_battle_hunger_owner_counter = modifier_axe_battle_hunger_owner_counter +____exports.modifier_axe_battle_hunger_debuff = __TS__Class() +local modifier_axe_battle_hunger_debuff = ____exports.modifier_axe_battle_hunger_debuff +modifier_axe_battle_hunger_debuff.name = "modifier_axe_battle_hunger_debuff" +modifier_axe_battle_hunger_debuff.____file_path = "scripts/vscripts/abilities/heroes/axe/axe_battle_hunger_custom.lua" +__TS__ClassExtends(modifier_axe_battle_hunger_debuff, BaseModifier) +function modifier_axe_battle_hunger_debuff.prototype.IsHidden(self) + return false +end +function modifier_axe_battle_hunger_debuff.prototype.IsDebuff(self) + return true +end +function modifier_axe_battle_hunger_debuff.prototype.GetAttributes(self) + return MODIFIER_ATTRIBUTE_MULTIPLE +end +function modifier_axe_battle_hunger_debuff.prototype.IsPurgable(self) + return true +end +function modifier_axe_battle_hunger_debuff.prototype.GetTexture(self) + return "axe_battle_hunger" +end +function modifier_axe_battle_hunger_debuff.prototype.OnCreated(self) + if not IsServer() then + return + end + self:StartIntervalThink(1) + ____exports.modifier_axe_battle_hunger_owner_counter:changeStacksOnCaster( + self:GetCaster(), + 1 + ) + local parent = self:GetParent() + self.killListener = ListenToGameEvent( + "entity_killed", + function(event) + if not parent or not parent:IsAlive() then + return + end + local attacker = EntIndexToHScript(event.entindex_attacker) + if attacker == parent then + self:Destroy() + end + end, + nil + ) +end +function modifier_axe_battle_hunger_debuff.prototype.OnIntervalThink(self) + if not IsServer() then + return + end + local parent = self:GetParent() + local caster = self:GetCaster() + local ability = self:GetAbility() + if not parent or not caster or not ability then + return + end + local basePerSec = ability:GetSpecialValueFor("damage_per_second") + local armorMult = ability:GetSpecialValueFor("armor_mult") * 0.01 + local armorBonus = caster:GetPhysicalArmorBaseValue() * armorMult + local dmg = math.max(1, basePerSec + armorBonus) + local dealtDamage = ApplyDamage({ + victim = parent, + attacker = caster, + damage = dmg, + damage_type = ability:GetAbilityDamageType(), + ability = ability + }) + SendOverheadEventMessage( + nil, + OVERHEAD_ALERT_BONUS_SPELL_DAMAGE, + parent, + dealtDamage, + caster:GetPlayerOwner() + ) +end +function modifier_axe_battle_hunger_debuff.prototype.DeclareFunctions(self) + return {MODIFIER_PROPERTY_MOVESPEED_BONUS_PERCENTAGE} +end +function modifier_axe_battle_hunger_debuff.prototype.GetModifierMoveSpeedBonus_Percentage(self) + local ability = self:GetAbility() + if not ability then + return 0 + end + local slow = ability:GetSpecialValueFor("slow") + local parent = self:GetParent() + local caster = self:GetCaster() + if not parent or not caster then + return 0 + end + local toAxe = caster:GetAbsOrigin():__sub(parent:GetAbsOrigin()):Normalized() + local fwd = parent:GetForwardVector() + local facingAxe = fwd:Dot(toAxe) + if facingAxe < 0.25 then + return -slow + end + return 0 +end +function modifier_axe_battle_hunger_debuff.prototype.GetEffectName(self) + return "particles/units/heroes/hero_axe/axe_battle_hunger.vpcf" +end +function modifier_axe_battle_hunger_debuff.prototype.GetEffectAttachType(self) + return PATTACH_OVERHEAD_FOLLOW +end +function modifier_axe_battle_hunger_debuff.prototype.OnRefresh(self) + if IsServer() then + self:SetDuration( + self:GetAbility():GetSpecialValueFor("duration"), + true + ) + end +end +function modifier_axe_battle_hunger_debuff.prototype.OnDestroy(self) + if IsServer() then + local caster = self:GetCaster() + if caster then + ____exports.modifier_axe_battle_hunger_owner_counter:changeStacksOnCaster(caster, -1) + end + if self.killListener ~= nil then + StopListeningToGameEvent(self.killListener) + self.killListener = nil + end + end +end +modifier_axe_battle_hunger_debuff = __TS__Decorate( + modifier_axe_battle_hunger_debuff, + modifier_axe_battle_hunger_debuff, + {registerModifier(nil)}, + {kind = "class", name = "modifier_axe_battle_hunger_debuff"} +) +____exports.modifier_axe_battle_hunger_debuff = modifier_axe_battle_hunger_debuff +____exports.modifier_axe_battle_hunger_ally_buff = __TS__Class() +local modifier_axe_battle_hunger_ally_buff = ____exports.modifier_axe_battle_hunger_ally_buff +modifier_axe_battle_hunger_ally_buff.name = "modifier_axe_battle_hunger_ally_buff" +modifier_axe_battle_hunger_ally_buff.____file_path = "scripts/vscripts/abilities/heroes/axe/axe_battle_hunger_custom.lua" +__TS__ClassExtends(modifier_axe_battle_hunger_ally_buff, BaseModifier) +function modifier_axe_battle_hunger_ally_buff.prototype.IsHidden(self) + return false +end +function modifier_axe_battle_hunger_ally_buff.prototype.GetAttributes(self) + return MODIFIER_ATTRIBUTE_MULTIPLE +end +function modifier_axe_battle_hunger_ally_buff.prototype.IsDebuff(self) + return false +end +function modifier_axe_battle_hunger_ally_buff.prototype.IsPurgable(self) + return true +end +function modifier_axe_battle_hunger_ally_buff.prototype.GetTexture(self) + return "axe_battle_hunger" +end +function modifier_axe_battle_hunger_ally_buff.prototype.OnCreated(self) + if not IsServer() then + return + end + self:StartIntervalThink(1) +end +function modifier_axe_battle_hunger_ally_buff.prototype.OnIntervalThink(self) + if not IsServer() then + return + end + local parent = self:GetParent() + local caster = self:GetCaster() + local ability = self:GetAbility() + if not parent or not caster or not ability then + return + end + local hunger = parent:FindModifierByName(modifier_general_hunger.name) + if not hunger then + hunger = parent:AddNewModifier(caster, ability, modifier_general_hunger.name, {}) + end + if hunger and not hunger:IsNull() then + do + local i = 0 + while i < ____exports.axe_battle_hunger_custom.ALLY_HUNGER_STACKS_PER_SECOND do + hunger:IncrementStackCount() + i = i + 1 + end + end + end + local heal = parent:GetMaxHealth() * (____exports.axe_battle_hunger_custom.ALLY_HEAL_PCT_PER_SECOND * 0.01) + if heal > 0 then + HealWithBattlePass( + nil, + parent, + heal, + ability, + caster + ) + SendOverheadEventMessage( + nil, + OVERHEAD_ALERT_HEAL, + parent, + heal, + parent:GetPlayerOwner() + ) + end +end +function modifier_axe_battle_hunger_ally_buff.prototype.DeclareFunctions(self) + return {MODIFIER_PROPERTY_BASEDAMAGEOUTGOING_PERCENTAGE, MODIFIER_PROPERTY_TOOLTIP} +end +function modifier_axe_battle_hunger_ally_buff.prototype.GetModifierBaseDamageOutgoing_Percentage(self) + local ability = self:GetAbility() + if not ability then + return 0 + end + return ability:GetSpecialValueFor("speed_bonus") +end +function modifier_axe_battle_hunger_ally_buff.prototype.GetModifierTooltip(self) + local parent = self:GetParent() + if not parent then + return 0 + end + return math.floor(parent:GetMaxHealth() * (____exports.axe_battle_hunger_custom.ALLY_HEAL_PCT_PER_SECOND * 0.01) + 0.5) +end +function modifier_axe_battle_hunger_ally_buff.prototype.GetEffectName(self) + return "particles/units/heroes/hero_axe/axe_battle_hunger.vpcf" +end +function modifier_axe_battle_hunger_ally_buff.prototype.GetEffectAttachType(self) + return PATTACH_OVERHEAD_FOLLOW +end +modifier_axe_battle_hunger_ally_buff = __TS__Decorate( + modifier_axe_battle_hunger_ally_buff, + modifier_axe_battle_hunger_ally_buff, + {registerModifier(nil)}, + {kind = "class", name = "modifier_axe_battle_hunger_ally_buff"} +) +____exports.modifier_axe_battle_hunger_ally_buff = modifier_axe_battle_hunger_ally_buff +return ____exports diff --git a/scripts/vscripts/abilities/heroes/axe/axe_berserkers_call_custom.lua b/scripts/vscripts/abilities/heroes/axe/axe_berserkers_call_custom.lua new file mode 100644 index 0000000..d619298 --- /dev/null +++ b/scripts/vscripts/abilities/heroes/axe/axe_berserkers_call_custom.lua @@ -0,0 +1,257 @@ +local ____lualib = require("lualib_bundle") +local __TS__Class = ____lualib.__TS__Class +local __TS__ClassExtends = ____lualib.__TS__ClassExtends +local __TS__Decorate = ____lualib.__TS__Decorate +local ____exports = {} +local ____dota_ts_adapter = require("lib.dota_ts_adapter") +local BaseAbility = ____dota_ts_adapter.BaseAbility +local BaseModifier = ____dota_ts_adapter.BaseModifier +local registerAbility = ____dota_ts_adapter.registerAbility +local registerModifier = ____dota_ts_adapter.registerModifier +require("utils.utils") +local ____axe_battle_hunger_custom = require("abilities.heroes.axe.axe_battle_hunger_custom") +local axeApplyBattleHungerFromCall = ____axe_battle_hunger_custom.axeApplyBattleHungerFromCall +local NEVERMORE_RAID_BOSS = "npc_boss_nevermore" +local function isNevermoreRaidBoss(self, unit) + return unit:GetUnitName() == NEVERMORE_RAID_BOSS +end +--- Berserker's Call — как в доте: тантует врагов в радиусе, броня на Акса; шард вешает Battle Hunger. +____exports.axe_berserkers_call_custom = __TS__Class() +local axe_berserkers_call_custom = ____exports.axe_berserkers_call_custom +axe_berserkers_call_custom.name = "axe_berserkers_call_custom" +axe_berserkers_call_custom.____file_path = "scripts/vscripts/abilities/heroes/axe/axe_berserkers_call_custom.lua" +__TS__ClassExtends(axe_berserkers_call_custom, BaseAbility) +function axe_berserkers_call_custom.prototype.Precache(self, context) + PrecacheResource("particle", "particles/units/heroes/hero_axe/axe_beserkers_call_owner.vpcf", context) + PrecacheResource("particle", "particles/status_fx/status_effect_beserkers_call.vpcf", context) + PrecacheResource("soundfile", "soundevents/game_sounds_heroes/game_sounds_axe.vsndevts", context) +end +function axe_berserkers_call_custom.prototype.OnAbilityPhaseStart(self) + if IsServer() then + EmitSoundOn( + "Hero_Axe.BerserkersCall.Start", + self:GetCaster() + ) + end + return true +end +function axe_berserkers_call_custom.prototype.OnAbilityPhaseInterrupted(self) + if IsServer() then + StopSoundOn( + "Hero_Axe.BerserkersCall.Start", + self:GetCaster() + ) + end +end +function axe_berserkers_call_custom.prototype.OnSpellStart(self) + if not IsServer() then + return + end + local caster = self:GetCaster() + local radius = self:GetSpecialValueFor("radius") + local duration = self:GetSpecialValueFor("duration") + caster:AddNewModifier(caster, self, ____exports.modifier_axe_berserkers_call_armor.name, {duration = duration}) + local enemies = FindUnitsInRadius( + caster:GetTeamNumber(), + caster:GetAbsOrigin(), + nil, + radius, + DOTA_UNIT_TARGET_TEAM_ENEMY, + DOTA_UNIT_TARGET_HERO + DOTA_UNIT_TARGET_BASIC, + DOTA_UNIT_TARGET_FLAG_MAGIC_IMMUNE_ENEMIES, + FIND_ANY_ORDER, + false + ) + local shard = HasShard(nil, caster) + local hungerDur = self:GetSpecialValueFor("shard_hunger_duration") + for ____, enemy in ipairs(enemies) do + do + if not enemy or not enemy:IsAlive() then + goto __continue10 + end + if isNevermoreRaidBoss(nil, enemy) then + goto __continue10 + end + self:issueAttackOrder(enemy, caster) + enemy:AddNewModifier(caster, self, ____exports.modifier_axe_berserkers_call_taunt.name, {duration = duration}) + if shard then + axeApplyBattleHungerFromCall(nil, caster, enemy, hungerDur) + end + end + ::__continue10:: + end + if shard then + local allies = FindUnitsInRadius( + caster:GetTeamNumber(), + caster:GetAbsOrigin(), + nil, + radius, + DOTA_UNIT_TARGET_TEAM_FRIENDLY, + DOTA_UNIT_TARGET_HERO, + DOTA_UNIT_TARGET_FLAG_NONE, + FIND_ANY_ORDER, + false + ) + for ____, ally in ipairs(allies) do + do + if not ally or not ally:IsAlive() then + goto __continue16 + end + axeApplyBattleHungerFromCall(nil, caster, ally, hungerDur) + end + ::__continue16:: + end + end + if #enemies > 0 then + EmitSoundOn("Hero_Axe.Berserkers_Call", caster) + end + local particle_cast = "particles/units/heroes/hero_axe/axe_beserkers_call_owner.vpcf" + local effect_cast = ParticleManager:CreateParticle(particle_cast, PATTACH_ABSORIGIN_FOLLOW, caster) + ParticleManager:SetParticleControlEnt( + effect_cast, + 1, + caster, + PATTACH_ABSORIGIN_FOLLOW, + "attach_mouth", + Vector(0, 0, 0), + true + ) + ParticleManager:SetParticleControl( + effect_cast, + 2, + Vector(radius, radius, radius) + ) + ParticleManager:ReleaseParticleIndex(effect_cast) +end +function axe_berserkers_call_custom.prototype.issueAttackOrder(self, attacker, target) + local canAttack = attacker:GetAttackCapability() ~= DOTA_UNIT_CAP_NO_ATTACK and not attacker:IsDisarmed() + if not canAttack then + ExecuteOrderFromTable({ + UnitIndex = attacker:entindex(), + OrderType = DOTA_UNIT_ORDER_MOVE_TO_POSITION, + Position = target:GetAbsOrigin(), + Queue = false + }) + return + end + ExecuteOrderFromTable({ + UnitIndex = attacker:entindex(), + OrderType = DOTA_UNIT_ORDER_ATTACK_TARGET, + TargetIndex = target:entindex(), + Queue = false + }) +end +axe_berserkers_call_custom = __TS__Decorate( + axe_berserkers_call_custom, + axe_berserkers_call_custom, + {registerAbility(nil)}, + {kind = "class", name = "axe_berserkers_call_custom"} +) +____exports.axe_berserkers_call_custom = axe_berserkers_call_custom +____exports.modifier_axe_berserkers_call_armor = __TS__Class() +local modifier_axe_berserkers_call_armor = ____exports.modifier_axe_berserkers_call_armor +modifier_axe_berserkers_call_armor.name = "modifier_axe_berserkers_call_armor" +modifier_axe_berserkers_call_armor.____file_path = "scripts/vscripts/abilities/heroes/axe/axe_berserkers_call_custom.lua" +__TS__ClassExtends(modifier_axe_berserkers_call_armor, BaseModifier) +function modifier_axe_berserkers_call_armor.prototype.IsHidden(self) + return false +end +function modifier_axe_berserkers_call_armor.prototype.IsPurgable(self) + return false +end +function modifier_axe_berserkers_call_armor.prototype.DeclareFunctions(self) + return {MODIFIER_PROPERTY_PHYSICAL_ARMOR_BONUS} +end +function modifier_axe_berserkers_call_armor.prototype.GetModifierPhysicalArmorBonus(self) + return self:GetAbility():GetSpecialValueFor("bonus_armor") +end +function modifier_axe_berserkers_call_armor.prototype.GetStatusEffectName(self) + return "particles/status_fx/status_effect_beserkers_call.vpcf" +end +modifier_axe_berserkers_call_armor = __TS__Decorate( + modifier_axe_berserkers_call_armor, + modifier_axe_berserkers_call_armor, + {registerModifier(nil)}, + {kind = "class", name = "modifier_axe_berserkers_call_armor"} +) +____exports.modifier_axe_berserkers_call_armor = modifier_axe_berserkers_call_armor +____exports.modifier_axe_berserkers_call_taunt = __TS__Class() +local modifier_axe_berserkers_call_taunt = ____exports.modifier_axe_berserkers_call_taunt +modifier_axe_berserkers_call_taunt.name = "modifier_axe_berserkers_call_taunt" +modifier_axe_berserkers_call_taunt.____file_path = "scripts/vscripts/abilities/heroes/axe/axe_berserkers_call_custom.lua" +__TS__ClassExtends(modifier_axe_berserkers_call_taunt, BaseModifier) +function modifier_axe_berserkers_call_taunt.prototype.IsHidden(self) + return false +end +function modifier_axe_berserkers_call_taunt.prototype.IsPurgable(self) + return false +end +function modifier_axe_berserkers_call_taunt.prototype.IsDebuff(self) + return true +end +function modifier_axe_berserkers_call_taunt.prototype.CheckState(self) + return {[MODIFIER_STATE_COMMAND_RESTRICTED] = true} +end +function modifier_axe_berserkers_call_taunt.prototype.GetStatusEffectName(self) + return "particles/status_fx/status_effect_beserkers_call.vpcf" +end +function modifier_axe_berserkers_call_taunt.prototype.OnCreated(self) + if not IsServer() then + return + end + local parent = self:GetParent() + local caster = self:GetCaster() + if not caster or not parent then + return + end + self.reissueTimer = Timers:CreateTimer( + 0.25, + function() + if not IsValidEntity(parent) or not IsValidEntity(caster) then + return nil + end + if not parent:IsAlive() or not caster:IsAlive() then + return nil + end + local canAttack = parent:GetAttackCapability() ~= DOTA_UNIT_CAP_NO_ATTACK and not parent:IsDisarmed() + if not canAttack then + ExecuteOrderFromTable({ + UnitIndex = parent:entindex(), + OrderType = DOTA_UNIT_ORDER_MOVE_TO_POSITION, + Position = caster:GetAbsOrigin(), + Queue = false + }) + return 0.25 + end + ExecuteOrderFromTable({ + UnitIndex = parent:entindex(), + OrderType = DOTA_UNIT_ORDER_ATTACK_TARGET, + TargetIndex = caster:entindex(), + Queue = false + }) + return 0.25 + end + ) +end +function modifier_axe_berserkers_call_taunt.prototype.OnDestroy(self) + if self.reissueTimer ~= nil then + Timers:RemoveTimer(self.reissueTimer) + self.reissueTimer = nil + end +end +function modifier_axe_berserkers_call_taunt.prototype.OnDeath(self, event) + if IsServer() and event.unit == self:GetCaster() then + self:Destroy() + end +end +function modifier_axe_berserkers_call_taunt.prototype.DeclareFunctions(self) + return {MODIFIER_EVENT_ON_DEATH} +end +modifier_axe_berserkers_call_taunt = __TS__Decorate( + modifier_axe_berserkers_call_taunt, + modifier_axe_berserkers_call_taunt, + {registerModifier(nil)}, + {kind = "class", name = "modifier_axe_berserkers_call_taunt"} +) +____exports.modifier_axe_berserkers_call_taunt = modifier_axe_berserkers_call_taunt +return ____exports diff --git a/scripts/vscripts/abilities/heroes/axe/axe_counter_helix_custom.lua b/scripts/vscripts/abilities/heroes/axe/axe_counter_helix_custom.lua new file mode 100644 index 0000000..884e1ce --- /dev/null +++ b/scripts/vscripts/abilities/heroes/axe/axe_counter_helix_custom.lua @@ -0,0 +1,158 @@ +local ____lualib = require("lualib_bundle") +local __TS__Class = ____lualib.__TS__Class +local __TS__ClassExtends = ____lualib.__TS__ClassExtends +local __TS__Decorate = ____lualib.__TS__Decorate +local ____exports = {} +local ____dota_ts_adapter = require("lib.dota_ts_adapter") +local BaseAbility = ____dota_ts_adapter.BaseAbility +local BaseModifier = ____dota_ts_adapter.BaseModifier +local registerAbility = ____dota_ts_adapter.registerAbility +local registerModifier = ____dota_ts_adapter.registerModifier +____exports.axe_counter_helix_custom = __TS__Class() +local axe_counter_helix_custom = ____exports.axe_counter_helix_custom +axe_counter_helix_custom.name = "axe_counter_helix_custom" +axe_counter_helix_custom.____file_path = "scripts/vscripts/abilities/heroes/axe/axe_counter_helix_custom.lua" +__TS__ClassExtends(axe_counter_helix_custom, BaseAbility) +function axe_counter_helix_custom.prototype.Precache(self, context) + PrecacheResource("particle", "particles/units/heroes/hero_axe/axe_counterhelix.vpcf", context) + PrecacheResource("particle", "particles/econ/items/axe/axe_weapon_bloodchaser/axe_attack_blur_counterhelix_bloodchaser.vpcf", context) + PrecacheResource("soundfile", "soundevents/game_sounds_heroes/game_sounds_axe.vsndevts", context) +end +function axe_counter_helix_custom.prototype.GetIntrinsicModifierName(self) + return ____exports.modifier_axe_counter_helix_passive.name +end +axe_counter_helix_custom = __TS__Decorate( + axe_counter_helix_custom, + axe_counter_helix_custom, + {registerAbility(nil)}, + {kind = "class", name = "axe_counter_helix_custom"} +) +____exports.axe_counter_helix_custom = axe_counter_helix_custom +____exports.modifier_axe_counter_helix_passive = __TS__Class() +local modifier_axe_counter_helix_passive = ____exports.modifier_axe_counter_helix_passive +modifier_axe_counter_helix_passive.name = "modifier_axe_counter_helix_passive" +modifier_axe_counter_helix_passive.____file_path = "scripts/vscripts/abilities/heroes/axe/axe_counter_helix_custom.lua" +__TS__ClassExtends(modifier_axe_counter_helix_passive, BaseModifier) +function modifier_axe_counter_helix_passive.prototype.____constructor(self, ...) + BaseModifier.prototype.____constructor(self, ...) + self.lastHelixGameTime = 0 + self.scepterLandCounter = 0 +end +function modifier_axe_counter_helix_passive.prototype.IsHidden(self) + return true +end +function modifier_axe_counter_helix_passive.prototype.IsPurgable(self) + return false +end +function modifier_axe_counter_helix_passive.prototype.DeclareFunctions(self) + return {MODIFIER_EVENT_ON_TAKEDAMAGE} +end +function modifier_axe_counter_helix_passive.prototype.OnTakeDamage(self, event) + if not IsServer() then + return + end + local parent = self:GetParent() + local ability = self:GetAbility() + if parent:PassivesDisabled() then + return + end + if not ability or event.unit ~= parent then + return + end + local attacker = event.attacker + if not attacker or attacker == parent or not attacker:IsAlive() then + return + end + if attacker:GetTeamNumber() == parent:GetTeamNumber() then + return + end + local cd = ability:GetSpecialValueFor("duration") + local now = GameRules:GetGameTime() + if now < self.lastHelixGameTime + cd then + return + end + local chance = ability:GetSpecialValueFor("trigger_chance") + local needScepter = parent:HasScepter() + local attacksForProc = needScepter and ability:GetSpecialValueFor("scepter_attacks") or 0 + local helixed = false + if attacksForProc > 0 then + self.scepterLandCounter = self.scepterLandCounter + 1 + if self.scepterLandCounter >= attacksForProc then + self.scepterLandCounter = 0 + self:triggerHelix(ability) + helixed = true + end + end + if not helixed and RollPercentage(chance) then + self:triggerHelix(ability) + helixed = true + end + if helixed then + self.lastHelixGameTime = now + end +end +function modifier_axe_counter_helix_passive.prototype.triggerHelix(self, ability) + if self:GetParent():PassivesDisabled() then + return + end + local parent = self:GetParent() + local radius = ability:GetSpecialValueFor("radius") + local damage = ability:GetSpecialValueFor("damage") + parent:StartGestureWithPlaybackRate(ACT_DOTA_CAST_ABILITY_3, 1) + local helix_pfx = ParticleManager:CreateParticle("particles/econ/items/axe/axe_weapon_bloodchaser/axe_attack_blur_counterhelix_bloodchaser.vpcf", PATTACH_ABSORIGIN_FOLLOW, parent) + EmitSoundOn("Hero_Axe.CounterHelix", parent) + ParticleManager:SetParticleControl( + helix_pfx, + 0, + parent:GetAbsOrigin() + ) + ParticleManager:ReleaseParticleIndex(helix_pfx) + local enemies = FindUnitsInRadius( + parent:GetTeamNumber(), + parent:GetAbsOrigin(), + nil, + radius, + DOTA_UNIT_TARGET_TEAM_ENEMY, + DOTA_UNIT_TARGET_HERO + DOTA_UNIT_TARGET_BASIC, + DOTA_UNIT_TARGET_FLAG_NONE, + FIND_ANY_ORDER, + false + ) + for ____, enemy in ipairs(enemies) do + do + if not enemy or not enemy:IsAlive() then + goto __continue20 + end + ApplyDamage({ + victim = enemy, + attacker = parent, + damage = damage, + damage_type = ability:GetAbilityDamageType(), + ability = ability + }) + parent:PerformAttack( + enemy, + false, + true, + true, + true, + false, + false, + true + ) + end + ::__continue20:: + end + local icd = ability:GetSpecialValueFor("duration") + if icd > 0 then + ability:StartCooldown(icd) + end +end +modifier_axe_counter_helix_passive = __TS__Decorate( + modifier_axe_counter_helix_passive, + modifier_axe_counter_helix_passive, + {registerModifier(nil)}, + {kind = "class", name = "modifier_axe_counter_helix_passive"} +) +____exports.modifier_axe_counter_helix_passive = modifier_axe_counter_helix_passive +return ____exports diff --git a/scripts/vscripts/abilities/heroes/axe/axe_culling_blade_custom.lua b/scripts/vscripts/abilities/heroes/axe/axe_culling_blade_custom.lua new file mode 100644 index 0000000..aea1cda --- /dev/null +++ b/scripts/vscripts/abilities/heroes/axe/axe_culling_blade_custom.lua @@ -0,0 +1,334 @@ +local ____lualib = require("lualib_bundle") +local __TS__Class = ____lualib.__TS__Class +local __TS__ClassExtends = ____lualib.__TS__ClassExtends +local __TS__Decorate = ____lualib.__TS__Decorate +local ____exports = {} +local ____dota_ts_adapter = require("lib.dota_ts_adapter") +local BaseAbility = ____dota_ts_adapter.BaseAbility +local BaseModifier = ____dota_ts_adapter.BaseModifier +local registerAbility = ____dota_ts_adapter.registerAbility +local registerModifier = ____dota_ts_adapter.registerModifier +--- Навсегда броня: стаки × armor_per_creep_kill (крип +0.1, герой — через armor_per_stack). +local function axeCullingAddPermanentArmor(self, caster, ability, flatArmor) + if flatArmor <= 0 then + return + end + local step = ability:GetSpecialValueFor("armor_per_creep_kill") + if step <= 0 then + return + end + local addStacks = math.floor(flatArmor / step + 0.5) + if addStacks < 1 then + addStacks = 1 + end + local m = caster:FindModifierByName(____exports.modifier_axe_culling_blade_armor_stack.name) + if not m then + m = caster:AddNewModifier(caster, ability, ____exports.modifier_axe_culling_blade_armor_stack.name, {}) + end + if m ~= nil then + m:SetStackCount(m:GetStackCount() + addStacks) + end +end +local function axeReduceAllAbilityCooldowns(self, caster, seconds) + if seconds <= 0 then + return + end + do + local i = 0 + while i < 24 do + do + local ab = caster:GetAbilityByIndex(i) + if not ab or ab:IsNull() then + goto __continue10 + end + local remaining = ab:GetCooldownTimeRemaining() + if remaining <= 0 then + goto __continue10 + end + ab:EndCooldown() + local next = math.max(0, remaining - seconds) + if next > 0 then + ab:StartCooldown(next) + end + end + ::__continue10:: + i = i + 1 + end + end +end +____exports.axe_culling_blade_custom = __TS__Class() +local axe_culling_blade_custom = ____exports.axe_culling_blade_custom +axe_culling_blade_custom.name = "axe_culling_blade_custom" +axe_culling_blade_custom.____file_path = "scripts/vscripts/abilities/heroes/axe/axe_culling_blade_custom.lua" +__TS__ClassExtends(axe_culling_blade_custom, BaseAbility) +function axe_culling_blade_custom.prototype.GetIntrinsicModifierName(self) + return ____exports.modifier_axe_culling_blade_shard_cdr.name +end +function axe_culling_blade_custom.prototype.Precache(self, context) + PrecacheResource("particle", "particles/units/heroes/hero_axe/axe_culling_blade.vpcf", context) + PrecacheResource("particle", "particles/econ/items/axe/ti9_jungle_axe/ti9_jungle_axe_culling_blade_sprint_fire.vpcf", context) + PrecacheResource("soundfile", "soundevents/game_sounds_heroes/game_sounds_axe.vsndevts", context) +end +function axe_culling_blade_custom.prototype.GetCastRange(self, location, target) + return self:GetSpecialValueFor("AbilityCastRange") +end +function axe_culling_blade_custom.prototype.GetAOERadius(self) + return self:GetSpecialValueFor("cull_radius") +end +function axe_culling_blade_custom.prototype.OnSpellStart(self) + if not IsServer() then + return + end + local caster = self:GetCaster() + local point = self:GetCursorPosition() + local cullRadius = self:GetSpecialValueFor("cull_radius") + local buffRadius = self:GetSpecialValueFor("speed_aoe") + local creepArmor = self:GetSpecialValueFor("armor_per_creep_kill") + local heroArmor = self:GetSpecialValueFor("armor_per_stack") + EmitSoundOn("Hero_Axe.Culling_Blade", caster) + local enemies = FindUnitsInRadius( + caster:GetTeamNumber(), + point, + nil, + cullRadius, + DOTA_UNIT_TARGET_TEAM_ENEMY, + DOTA_UNIT_TARGET_HERO + DOTA_UNIT_TARGET_BASIC, + DOTA_UNIT_TARGET_FLAG_MAGIC_IMMUNE_ENEMIES, + FIND_ANY_ORDER, + false + ) + local anyExecuted = false + for ____, target in ipairs(enemies) do + do + if not target or not target:IsAlive() or target:IsInvulnerable() then + goto __continue20 + end + self:playCullingBladeParticle(caster, target) + EmitSoundOn("Hero_Axe.Culling_Blade", target) + local wasHero = target:IsHero() + local bonusAttackDamage = caster:GetAverageTrueAttackDamage(target) + ApplyDamage({ + victim = target, + attacker = caster, + damage = bonusAttackDamage, + damage_type = self:GetAbilityDamageType(), + ability = self + }) + if target:IsAlive() then + caster:PerformAttack( + target, + false, + true, + true, + true, + false, + false, + true + ) + end + if not target:IsAlive() then + anyExecuted = true + EmitSoundOn("Hero_Axe.Culling_Blade_Success", target) + if not wasHero and creepArmor > 0 then + axeCullingAddPermanentArmor(nil, caster, self, creepArmor) + elseif wasHero and heroArmor > 0 then + axeCullingAddPermanentArmor(nil, caster, self, heroArmor) + end + end + end + ::__continue20:: + end + if anyExecuted then + local durBase = self:GetSpecialValueFor("speed_duration") + local dur = caster:HasScepter() and self:GetSpecialValueFor("scepter_speed_duration") or durBase + local tm = caster:GetTeamNumber() + local unitList = FindUnitsInRadius( + tm, + caster:GetAbsOrigin(), + nil, + buffRadius, + DOTA_UNIT_TARGET_TEAM_FRIENDLY, + DOTA_UNIT_TARGET_HERO + DOTA_UNIT_TARGET_BASIC, + DOTA_UNIT_TARGET_FLAG_NONE, + FIND_ANY_ORDER, + false + ) + for ____, unit in ipairs(unitList) do + do + if not unit or not unit:IsAlive() then + goto __continue28 + end + unit:AddNewModifier(caster, self, ____exports.modifier_axe_culling_blade_movespeed.name, {duration = dur}) + end + ::__continue28:: + end + end +end +function axe_culling_blade_custom.prototype.playCullingBladeParticle(self, caster, target) + local particle = ParticleManager:CreateParticle("particles/units/heroes/hero_axe/axe_culling_blade.vpcf", PATTACH_ABSORIGIN_FOLLOW, target) + ParticleManager:SetParticleControl( + particle, + 0, + caster:GetAbsOrigin() + ) + ParticleManager:SetParticleControl( + particle, + 1, + target:GetAbsOrigin() + ) + ParticleManager:SetParticleControlEnt( + particle, + 2, + target, + PATTACH_ABSORIGIN_FOLLOW, + "attach_hitloc", + target:GetAbsOrigin(), + true + ) + ParticleManager:ReleaseParticleIndex(particle) +end +axe_culling_blade_custom = __TS__Decorate( + axe_culling_blade_custom, + axe_culling_blade_custom, + {registerAbility(nil)}, + {kind = "class", name = "axe_culling_blade_custom"} +) +____exports.axe_culling_blade_custom = axe_culling_blade_custom +____exports.modifier_axe_culling_blade_shard_cdr = __TS__Class() +local modifier_axe_culling_blade_shard_cdr = ____exports.modifier_axe_culling_blade_shard_cdr +modifier_axe_culling_blade_shard_cdr.name = "modifier_axe_culling_blade_shard_cdr" +modifier_axe_culling_blade_shard_cdr.____file_path = "scripts/vscripts/abilities/heroes/axe/axe_culling_blade_custom.lua" +__TS__ClassExtends(modifier_axe_culling_blade_shard_cdr, BaseModifier) +function modifier_axe_culling_blade_shard_cdr.prototype.IsHidden(self) + return true +end +function modifier_axe_culling_blade_shard_cdr.prototype.IsPurgable(self) + return false +end +function modifier_axe_culling_blade_shard_cdr.prototype.RemoveOnDeath(self) + return false +end +function modifier_axe_culling_blade_shard_cdr.prototype.OnCreated(self) + if not IsServer() then + return + end + local parent = self:GetParent() + self.killListener = ListenToGameEvent( + "entity_killed", + function(event) + local attacker = EntIndexToHScript(event.entindex_attacker) + local killed = EntIndexToHScript(event.entindex_killed) + local ____temp_0 + if event.entindex_inflictor ~= nil and event.entindex_inflictor ~= 0 then + ____temp_0 = EntIndexToHScript(event.entindex_inflictor) + else + ____temp_0 = nil + end + local inflictor = ____temp_0 + if not attacker or not killed then + return + end + if attacker ~= parent then + return + end + if inflictor and not inflictor:IsNull() then + return + end + if not parent:IsRealHero() or parent:IsIllusion() then + return + end + if parent:PassivesDisabled() then + return + end + if not parent:HasScepter() then + return + end + if not killed:IsCreep() then + return + end + axeReduceAllAbilityCooldowns(nil, parent, 1) + end, + nil + ) +end +function modifier_axe_culling_blade_shard_cdr.prototype.OnDestroy(self) + if self.killListener ~= nil then + StopListeningToGameEvent(self.killListener) + self.killListener = nil + end +end +modifier_axe_culling_blade_shard_cdr = __TS__Decorate( + modifier_axe_culling_blade_shard_cdr, + modifier_axe_culling_blade_shard_cdr, + {registerModifier(nil)}, + {kind = "class", name = "modifier_axe_culling_blade_shard_cdr"} +) +____exports.modifier_axe_culling_blade_shard_cdr = modifier_axe_culling_blade_shard_cdr +____exports.modifier_axe_culling_blade_armor_stack = __TS__Class() +local modifier_axe_culling_blade_armor_stack = ____exports.modifier_axe_culling_blade_armor_stack +modifier_axe_culling_blade_armor_stack.name = "modifier_axe_culling_blade_armor_stack" +modifier_axe_culling_blade_armor_stack.____file_path = "scripts/vscripts/abilities/heroes/axe/axe_culling_blade_custom.lua" +__TS__ClassExtends(modifier_axe_culling_blade_armor_stack, BaseModifier) +function modifier_axe_culling_blade_armor_stack.prototype.IsHidden(self) + return false +end +function modifier_axe_culling_blade_armor_stack.prototype.IsPurgable(self) + return false +end +function modifier_axe_culling_blade_armor_stack.prototype.IsDebuff(self) + return false +end +function modifier_axe_culling_blade_armor_stack.prototype.DeclareFunctions(self) + return {MODIFIER_PROPERTY_PHYSICAL_ARMOR_BONUS} +end +function modifier_axe_culling_blade_armor_stack.prototype.GetModifierPhysicalArmorBonus(self) + local ab = self:GetAbility() + if not ab then + return 0 + end + return self:GetStackCount() * ab:GetSpecialValueFor("armor_per_creep_kill") +end +modifier_axe_culling_blade_armor_stack = __TS__Decorate( + modifier_axe_culling_blade_armor_stack, + modifier_axe_culling_blade_armor_stack, + {registerModifier(nil)}, + {kind = "class", name = "modifier_axe_culling_blade_armor_stack"} +) +____exports.modifier_axe_culling_blade_armor_stack = modifier_axe_culling_blade_armor_stack +____exports.modifier_axe_culling_blade_movespeed = __TS__Class() +local modifier_axe_culling_blade_movespeed = ____exports.modifier_axe_culling_blade_movespeed +modifier_axe_culling_blade_movespeed.name = "modifier_axe_culling_blade_movespeed" +modifier_axe_culling_blade_movespeed.____file_path = "scripts/vscripts/abilities/heroes/axe/axe_culling_blade_custom.lua" +__TS__ClassExtends(modifier_axe_culling_blade_movespeed, BaseModifier) +function modifier_axe_culling_blade_movespeed.prototype.IsHidden(self) + return false +end +function modifier_axe_culling_blade_movespeed.prototype.IsPurgable(self) + return true +end +function modifier_axe_culling_blade_movespeed.prototype.DeclareFunctions(self) + return {MODIFIER_PROPERTY_MOVESPEED_BONUS_PERCENTAGE, MODIFIER_PROPERTY_ATTACKSPEED_BONUS_CONSTANT} +end +function modifier_axe_culling_blade_movespeed.prototype.GetModifierMoveSpeedBonus_Percentage(self) + return self:GetAbility():GetSpecialValueFor("speed_bonus") +end +function modifier_axe_culling_blade_movespeed.prototype.CheckState(self) + return {[MODIFIER_STATE_DEBUFF_IMMUNE] = true} +end +function modifier_axe_culling_blade_movespeed.prototype.GetModifierAttackSpeedBonus_Constant(self) + return self:GetAbility():GetSpecialValueFor("attack_speed_bonus") +end +function modifier_axe_culling_blade_movespeed.prototype.GetEffectName(self) + return "particles/econ/items/axe/ti9_jungle_axe/ti9_jungle_axe_culling_blade_sprint_fire.vpcf" +end +function modifier_axe_culling_blade_movespeed.prototype.GetEffectAttachType(self) + return PATTACH_ABSORIGIN_FOLLOW +end +modifier_axe_culling_blade_movespeed = __TS__Decorate( + modifier_axe_culling_blade_movespeed, + modifier_axe_culling_blade_movespeed, + {registerModifier(nil)}, + {kind = "class", name = "modifier_axe_culling_blade_movespeed"} +) +____exports.modifier_axe_culling_blade_movespeed = modifier_axe_culling_blade_movespeed +return ____exports diff --git a/scripts/vscripts/abilities/heroes/axe/axe_one_man_army_custom.lua b/scripts/vscripts/abilities/heroes/axe/axe_one_man_army_custom.lua new file mode 100644 index 0000000..42da104 --- /dev/null +++ b/scripts/vscripts/abilities/heroes/axe/axe_one_man_army_custom.lua @@ -0,0 +1,83 @@ +local ____lualib = require("lualib_bundle") +local __TS__Class = ____lualib.__TS__Class +local __TS__ClassExtends = ____lualib.__TS__ClassExtends +local __TS__Decorate = ____lualib.__TS__Decorate +local ____exports = {} +local ____dota_ts_adapter = require("lib.dota_ts_adapter") +local BaseAbility = ____dota_ts_adapter.BaseAbility +local BaseModifier = ____dota_ts_adapter.BaseModifier +local registerAbility = ____dota_ts_adapter.registerAbility +local registerModifier = ____dota_ts_adapter.registerModifier +____exports.axe_one_man_army_custom = __TS__Class() +local axe_one_man_army_custom = ____exports.axe_one_man_army_custom +axe_one_man_army_custom.name = "axe_one_man_army_custom" +axe_one_man_army_custom.____file_path = "scripts/vscripts/abilities/heroes/axe/axe_one_man_army_custom.lua" +__TS__ClassExtends(axe_one_man_army_custom, BaseAbility) +function axe_one_man_army_custom.prototype.GetIntrinsicModifierName(self) + return ____exports.modifier_axe_one_man_army_custom.name +end +axe_one_man_army_custom = __TS__Decorate( + axe_one_man_army_custom, + axe_one_man_army_custom, + {registerAbility(nil)}, + {kind = "class", name = "axe_one_man_army_custom"} +) +____exports.axe_one_man_army_custom = axe_one_man_army_custom +____exports.modifier_axe_one_man_army_custom = __TS__Class() +local modifier_axe_one_man_army_custom = ____exports.modifier_axe_one_man_army_custom +modifier_axe_one_man_army_custom.name = "modifier_axe_one_man_army_custom" +modifier_axe_one_man_army_custom.____file_path = "scripts/vscripts/abilities/heroes/axe/axe_one_man_army_custom.lua" +__TS__ClassExtends(modifier_axe_one_man_army_custom, BaseModifier) +function modifier_axe_one_man_army_custom.prototype.IsHidden(self) + return true +end +function modifier_axe_one_man_army_custom.prototype.IsPurgable(self) + return false +end +function modifier_axe_one_man_army_custom.prototype.DeclareFunctions(self) + return {MODIFIER_PROPERTY_STATS_STRENGTH_BONUS} +end +function modifier_axe_one_man_army_custom.prototype.GetModifierBonusStats_Strength(self) + local parent = self:GetParent() + if parent:PassivesDisabled() then + return 0 + end + local ability = self:GetAbility() + if not ability or parent:IsIllusion() then + return 0 + end + local factor = ability:GetSpecialValueFor("armor_to_str") + local allies = FindUnitsInRadius( + parent:GetTeamNumber(), + parent:GetAbsOrigin(), + nil, + ability:GetSpecialValueFor("radius"), + DOTA_UNIT_TARGET_TEAM_FRIENDLY, + DOTA_UNIT_TARGET_HERO, + DOTA_UNIT_TARGET_FLAG_NONE, + FIND_ANY_ORDER, + false + ) + for ____, u in ipairs(allies) do + do + if u == parent then + goto __continue9 + end + if not u:IsRealHero() or u:IsIllusion() then + goto __continue9 + end + return 0 + end + ::__continue9:: + end + local armor = parent:GetPhysicalArmorValue(false) + return math.floor(armor * factor) +end +modifier_axe_one_man_army_custom = __TS__Decorate( + modifier_axe_one_man_army_custom, + modifier_axe_one_man_army_custom, + {registerModifier(nil)}, + {kind = "class", name = "modifier_axe_one_man_army_custom"} +) +____exports.modifier_axe_one_man_army_custom = modifier_axe_one_man_army_custom +return ____exports diff --git a/scripts/vscripts/abilities/heroes/bloodhunter/ability_bloodrage.lua b/scripts/vscripts/abilities/heroes/bloodhunter/ability_bloodrage.lua new file mode 100644 index 0000000..1a83175 --- /dev/null +++ b/scripts/vscripts/abilities/heroes/bloodhunter/ability_bloodrage.lua @@ -0,0 +1,221 @@ +local ____lualib = require("lualib_bundle") +local __TS__Class = ____lualib.__TS__Class +local __TS__ClassExtends = ____lualib.__TS__ClassExtends +local __TS__Decorate = ____lualib.__TS__Decorate +local ____exports = {} +local ____dota_ts_adapter = require("lib.dota_ts_adapter") +local BaseAbility = ____dota_ts_adapter.BaseAbility +local BaseModifier = ____dota_ts_adapter.BaseModifier +local registerAbility = ____dota_ts_adapter.registerAbility +local registerModifier = ____dota_ts_adapter.registerModifier +local ____vampirism = require("utils.vampirism") +local addPhysicalVampirism = ____vampirism.addPhysicalVampirism +local reducePhysicalVampirism = ____vampirism.reducePhysicalVampirism +____exports.ability_bloodrage = __TS__Class() +local ability_bloodrage = ____exports.ability_bloodrage +ability_bloodrage.name = "ability_bloodrage" +ability_bloodrage.____file_path = "scripts/vscripts/abilities/heroes/bloodhunter/ability_bloodrage.lua" +__TS__ClassExtends(ability_bloodrage, BaseAbility) +function ability_bloodrage.prototype.OnSpellStart(self) + if not IsServer() then + return + end + local caster = self:GetCaster() + caster:AddNewModifier( + caster, + self, + ____exports.modifier_bloodrage_buff.name, + {duration = self:GetSpecialValueFor("duration")} + ) + EmitSoundOn("hero_bloodseeker.bloodRage", caster) +end +ability_bloodrage = __TS__Decorate( + ability_bloodrage, + ability_bloodrage, + {registerAbility(nil)}, + {kind = "class", name = "ability_bloodrage"} +) +____exports.ability_bloodrage = ability_bloodrage +____exports.modifier_bloodrage_buff = __TS__Class() +local modifier_bloodrage_buff = ____exports.modifier_bloodrage_buff +modifier_bloodrage_buff.name = "modifier_bloodrage_buff" +modifier_bloodrage_buff.____file_path = "scripts/vscripts/abilities/heroes/bloodhunter/ability_bloodrage.lua" +__TS__ClassExtends(modifier_bloodrage_buff, BaseModifier) +function modifier_bloodrage_buff.prototype.____constructor(self, ...) + BaseModifier.prototype.____constructor(self, ...) + self.bonusDamage = 0 + self.bonusAttackSpeed = 0 + self.stackDamage = 0 + self.stackAttackspeed = 0 + self.physicalVampirism = 0 + self.stackPhysicalVampirism = 0 +end +function modifier_bloodrage_buff.prototype.IsHidden(self) + return false +end +function modifier_bloodrage_buff.prototype.IsPurgable(self) + return false +end +function modifier_bloodrage_buff.prototype.IsDebuff(self) + return false +end +function modifier_bloodrage_buff.prototype.IsBuff(self) + return true +end +function modifier_bloodrage_buff.prototype.RemoveOnDeath(self) + return true +end +function modifier_bloodrage_buff.prototype.GetEffectName(self) + return "particles/econ/items/bloodseeker/bloodseeker_eztzhok_weapon/bloodseeker_bloodrage_eztzhok.vpcf" +end +function modifier_bloodrage_buff.prototype.GetEffectAttachType(self) + return PATTACH_ABSORIGIN_FOLLOW +end +function modifier_bloodrage_buff.prototype.DeclareFunctions(self) + return {MODIFIER_PROPERTY_PREATTACK_BONUS_DAMAGE, MODIFIER_PROPERTY_ATTACKSPEED_BONUS_CONSTANT, MODIFIER_PROPERTY_PROCATTACK_FEEDBACK} +end +function modifier_bloodrage_buff.prototype.OnCreated(self) + local ability = self:GetAbility() + if not ability then + return + end + self.bonusDamage = ability:GetSpecialValueFor("bonus_damage") + self.bonusAttackSpeed = ability:GetSpecialValueFor("bonus_attack_speed") + self.stackDamage = ability:GetSpecialValueFor("stack_damage") + self.stackAttackspeed = ability:GetSpecialValueFor("stack_attackspeed") + self.physicalVampirism = ability:GetSpecialValueFor("physical_vampirism") + self.stackPhysicalVampirism = 0 + local parent = self:GetParent() + if parent:IsRealHero() then + addPhysicalVampirism(nil, parent, self.physicalVampirism) + end + self:StartIntervalThink(0.25) + self:OnIntervalThink() +end +function modifier_bloodrage_buff.prototype.OnRefresh(self) + local ability = self:GetAbility() + if not ability then + return + end + self.bonusDamage = ability:GetSpecialValueFor("bonus_damage") + self.bonusAttackSpeed = ability:GetSpecialValueFor("bonus_attack_speed") + self.stackDamage = ability:GetSpecialValueFor("stack_damage") + self.stackAttackspeed = ability:GetSpecialValueFor("stack_attackspeed") + self.physicalVampirism = ability:GetSpecialValueFor("physical_vampirism") + self.stackPhysicalVampirism = 0 + self:StartIntervalThink(0.25) + self:OnIntervalThink() +end +function modifier_bloodrage_buff.prototype.OnIntervalThink(self) + if not IsServer() then + return + end + local parent = self:GetParent() + local currentHealth = parent:GetHealth() + local damage = 100 * 0.1 + parent:SetHealth(math.max(currentHealth - damage, 1)) +end +function modifier_bloodrage_buff.prototype.GetModifierProcAttack_Feedback(self, event) + if not IsServer() then + return 0 + end + local parent = self:GetParent() + if parent:HasScepter() then + self:AddStack(40, 5) + else + self:AddStack(20, 2) + end + parent:CalculateStatBonus(true) + return 0 +end +function modifier_bloodrage_buff.prototype.GetModifierPreAttack_BonusDamage(self) + return self.bonusDamage + self:GetStackCount() * self.stackDamage +end +function modifier_bloodrage_buff.prototype.GetModifierAttackSpeedBonus_Constant(self) + return self.bonusAttackSpeed + self:GetStackCount() * self.stackAttackspeed +end +function modifier_bloodrage_buff.prototype.AddStack(self, duration, count) + if not IsServer() then + return + end + local parent = self:GetParent() + local ability = self:GetAbility() + if not ability then + return + end + local mod = parent:AddNewModifier(parent, ability, ____exports.modifier_bloodrage_count.name, {duration = duration}) + if mod then + mod.modifier = self + mod.bonus = count + end + self:SetStackCount(self:GetStackCount() + count) + if parent:IsRealHero() then + addPhysicalVampirism(nil, parent, self.stackPhysicalVampirism) + end +end +function modifier_bloodrage_buff.prototype.RemoveStack(self, value) + if not IsServer() then + return + end + self:SetStackCount(self:GetStackCount() - value) + local parent = self:GetParent() + if parent:IsRealHero() then + reducePhysicalVampirism(nil, parent, self.stackPhysicalVampirism) + end +end +function modifier_bloodrage_buff.prototype.OnDestroy(self) + if not IsServer() then + return + end + local parent = self:GetParent() + if parent:IsRealHero() then + local totalPhysical = self.physicalVampirism + self:GetStackCount() * self.stackPhysicalVampirism + reducePhysicalVampirism(nil, parent, totalPhysical) + end +end +modifier_bloodrage_buff = __TS__Decorate( + modifier_bloodrage_buff, + modifier_bloodrage_buff, + {registerModifier(nil)}, + {kind = "class", name = "modifier_bloodrage_buff"} +) +____exports.modifier_bloodrage_buff = modifier_bloodrage_buff +____exports.modifier_bloodrage_count = __TS__Class() +local modifier_bloodrage_count = ____exports.modifier_bloodrage_count +modifier_bloodrage_count.name = "modifier_bloodrage_count" +modifier_bloodrage_count.____file_path = "scripts/vscripts/abilities/heroes/bloodhunter/ability_bloodrage.lua" +__TS__ClassExtends(modifier_bloodrage_count, BaseModifier) +function modifier_bloodrage_count.prototype.IsHidden(self) + return true +end +function modifier_bloodrage_count.prototype.IsPurgable(self) + return false +end +function modifier_bloodrage_count.prototype.IsDebuff(self) + return false +end +function modifier_bloodrage_count.prototype.IsBuff(self) + return true +end +function modifier_bloodrage_count.prototype.RemoveOnDeath(self) + return false +end +function modifier_bloodrage_count.prototype.GetAttributes(self) + return MODIFIER_ATTRIBUTE_MULTIPLE +end +function modifier_bloodrage_count.prototype.OnRemoved(self) + if not IsServer() then + return + end + if self.modifier and self.bonus ~= nil then + self.modifier:RemoveStack(self.bonus) + end +end +modifier_bloodrage_count = __TS__Decorate( + modifier_bloodrage_count, + modifier_bloodrage_count, + {registerModifier(nil)}, + {kind = "class", name = "modifier_bloodrage_count"} +) +____exports.modifier_bloodrage_count = modifier_bloodrage_count +return ____exports diff --git a/scripts/vscripts/abilities/heroes/bloodhunter/ability_bloodstained_memory.lua b/scripts/vscripts/abilities/heroes/bloodhunter/ability_bloodstained_memory.lua new file mode 100644 index 0000000..095990b --- /dev/null +++ b/scripts/vscripts/abilities/heroes/bloodhunter/ability_bloodstained_memory.lua @@ -0,0 +1,106 @@ +local ____lualib = require("lualib_bundle") +local __TS__Class = ____lualib.__TS__Class +local __TS__ClassExtends = ____lualib.__TS__ClassExtends +local __TS__Decorate = ____lualib.__TS__Decorate +local ____exports = {} +local ____dota_ts_adapter = require("lib.dota_ts_adapter") +local BaseAbility = ____dota_ts_adapter.BaseAbility +local BaseModifier = ____dota_ts_adapter.BaseModifier +local registerAbility = ____dota_ts_adapter.registerAbility +local registerModifier = ____dota_ts_adapter.registerModifier +local ____ability_bloodrage = require("abilities.heroes.bloodhunter.ability_bloodrage") +local modifier_bloodrage_buff = ____ability_bloodrage.modifier_bloodrage_buff +____exports.ability_bloodstained_memory = __TS__Class() +local ability_bloodstained_memory = ____exports.ability_bloodstained_memory +ability_bloodstained_memory.name = "ability_bloodstained_memory" +ability_bloodstained_memory.____file_path = "scripts/vscripts/abilities/heroes/bloodhunter/ability_bloodstained_memory.lua" +__TS__ClassExtends(ability_bloodstained_memory, BaseAbility) +function ability_bloodstained_memory.prototype.GetIntrinsicModifierName(self) + return ____exports.modifier_bloodstained_memory.name +end +ability_bloodstained_memory = __TS__Decorate( + ability_bloodstained_memory, + ability_bloodstained_memory, + {registerAbility(nil)}, + {kind = "class", name = "ability_bloodstained_memory"} +) +____exports.ability_bloodstained_memory = ability_bloodstained_memory +____exports.modifier_bloodstained_memory = __TS__Class() +local modifier_bloodstained_memory = ____exports.modifier_bloodstained_memory +modifier_bloodstained_memory.name = "modifier_bloodstained_memory" +modifier_bloodstained_memory.____file_path = "scripts/vscripts/abilities/heroes/bloodhunter/ability_bloodstained_memory.lua" +__TS__ClassExtends(modifier_bloodstained_memory, BaseModifier) +function modifier_bloodstained_memory.prototype.____constructor(self, ...) + BaseModifier.prototype.____constructor(self, ...) + self.healthbonus = 0 + self.movespeed = 0 +end +function modifier_bloodstained_memory.prototype.IsHidden(self) + return false +end +function modifier_bloodstained_memory.prototype.IsPurgable(self) + return false +end +function modifier_bloodstained_memory.prototype.IsDebuff(self) + return false +end +function modifier_bloodstained_memory.prototype.RemoveOnDeath(self) + return true +end +function modifier_bloodstained_memory.prototype.DeclareFunctions(self) + return {MODIFIER_PROPERTY_HEALTH_BONUS, MODIFIER_PROPERTY_MOVESPEED_BONUS_CONSTANT, MODIFIER_PROPERTY_IGNORE_MOVESPEED_LIMIT} +end +function modifier_bloodstained_memory.prototype.OnCreated(self) + if not IsServer() then + return + end + local ability = self:GetAbility() + if not ability then + return + end + self.healthbonus = ability:GetSpecialValueFor("healthbonus") + self.movespeed = ability:GetSpecialValueFor("movespeed") + self:StartIntervalThink(0.1) +end +function modifier_bloodstained_memory.prototype.OnIntervalThink(self) + self:OnCreated() +end +function modifier_bloodstained_memory.prototype.OnRefresh(self) + self:OnCreated() +end +function modifier_bloodstained_memory.prototype.GetModifierIgnoreMovespeedLimit(self) + if self:GetParent():PassivesDisabled() then + return 0 + end + return 1 +end +function modifier_bloodstained_memory.prototype.GetModifierHealthBonus(self) + local parent = self:GetParent() + if parent:PassivesDisabled() then + return 0 + end + local bloodrageMod = parent:FindModifierByName(modifier_bloodrage_buff.name) + if not bloodrageMod then + return 0 + end + return self.healthbonus * bloodrageMod:GetStackCount() +end +function modifier_bloodstained_memory.prototype.GetModifierMoveSpeedBonus_Constant(self) + local parent = self:GetParent() + if parent:PassivesDisabled() then + return 0 + end + local bloodrageMod = parent:FindModifierByName(modifier_bloodrage_buff.name) + if not bloodrageMod then + return 0 + end + return self.movespeed * bloodrageMod:GetStackCount() +end +modifier_bloodstained_memory = __TS__Decorate( + modifier_bloodstained_memory, + modifier_bloodstained_memory, + {registerModifier(nil)}, + {kind = "class", name = "modifier_bloodstained_memory"} +) +____exports.modifier_bloodstained_memory = modifier_bloodstained_memory +return ____exports diff --git a/scripts/vscripts/abilities/heroes/bloodhunter/ability_illusion_of_blood.lua b/scripts/vscripts/abilities/heroes/bloodhunter/ability_illusion_of_blood.lua new file mode 100644 index 0000000..0c0d294 --- /dev/null +++ b/scripts/vscripts/abilities/heroes/bloodhunter/ability_illusion_of_blood.lua @@ -0,0 +1,291 @@ +local ____lualib = require("lualib_bundle") +local __TS__Class = ____lualib.__TS__Class +local __TS__ClassExtends = ____lualib.__TS__ClassExtends +local __TS__Decorate = ____lualib.__TS__Decorate +local ____exports = {} +local ____dota_ts_adapter = require("lib.dota_ts_adapter") +local BaseAbility = ____dota_ts_adapter.BaseAbility +local BaseModifier = ____dota_ts_adapter.BaseModifier +local registerAbility = ____dota_ts_adapter.registerAbility +local registerModifier = ____dota_ts_adapter.registerModifier +local ____ability_bloodrage = require("abilities.heroes.bloodhunter.ability_bloodrage") +local modifier_bloodrage_buff = ____ability_bloodrage.modifier_bloodrage_buff +local modifier_bloodrage_count = ____ability_bloodrage.modifier_bloodrage_count +____exports.ability_illusion_of_blood = __TS__Class() +local ability_illusion_of_blood = ____exports.ability_illusion_of_blood +ability_illusion_of_blood.name = "ability_illusion_of_blood" +ability_illusion_of_blood.____file_path = "scripts/vscripts/abilities/heroes/bloodhunter/ability_illusion_of_blood.lua" +__TS__ClassExtends(ability_illusion_of_blood, BaseAbility) +function ability_illusion_of_blood.prototype.____constructor(self, ...) + BaseAbility.prototype.____constructor(self, ...) + self.illusions = {} +end +function ability_illusion_of_blood.prototype.GetHealthCost(self, level) + return 0 +end +function ability_illusion_of_blood.prototype.CastFilterResult(self) + local caster = self:GetCaster() + if not caster:HasModifier(modifier_bloodrage_buff.name) then + return UF_FAIL_CUSTOM + end + local modif = caster:FindModifierByName(modifier_bloodrage_buff.name) + if not modif then + return UF_FAIL_CUSTOM + end + local healthCost = self:GetHealthCost(self:GetLevel()) + if modif:GetStackCount() < healthCost then + return UF_FAIL_CUSTOM + end + return UF_SUCCESS +end +function ability_illusion_of_blood.prototype.GetCustomCastError(self) + local caster = self:GetCaster() + if not caster:HasModifier(modifier_bloodrage_buff.name) then + return "#dota_hud_error_havent_charges" + end + local modif = caster:FindModifierByName(modifier_bloodrage_buff.name) + if not modif then + return "#dota_hud_error_havent_charges" + end + local healthCost = self:GetHealthCost(self:GetLevel()) + if modif:GetStackCount() < healthCost then + return "#dota_hud_error_havent_charges" + end + return "" +end +function ability_illusion_of_blood.prototype.OnSpellStart(self) + if not IsServer() then + return + end + local caster = self:GetCaster() + local healthCost = self:GetHealthCost(self:GetLevel()) + local modif = caster:FindModifierByName(modifier_bloodrage_buff.name) + if modif then + modif:SetStackCount(modif:GetStackCount() - healthCost) + end + local invDuration = self:GetSpecialValueFor("invuln_duration") + caster:AddNewModifier(caster, self, ____exports.modifier_illusion_of_blood.name, {duration = 0.5}) +end +ability_illusion_of_blood = __TS__Decorate( + ability_illusion_of_blood, + ability_illusion_of_blood, + {registerAbility(nil)}, + {kind = "class", name = "ability_illusion_of_blood"} +) +____exports.ability_illusion_of_blood = ability_illusion_of_blood +____exports.modifier_illusion_of_blood = __TS__Class() +local modifier_illusion_of_blood = ____exports.modifier_illusion_of_blood +modifier_illusion_of_blood.name = "modifier_illusion_of_blood" +modifier_illusion_of_blood.____file_path = "scripts/vscripts/abilities/heroes/bloodhunter/ability_illusion_of_blood.lua" +__TS__ClassExtends(modifier_illusion_of_blood, BaseModifier) +function modifier_illusion_of_blood.prototype.____constructor(self, ...) + BaseModifier.prototype.____constructor(self, ...) + self.imageCount = 0 + self.imageDuration = 0 + self.incomingDamage = 0 + self.outgoingDamage = 0 + self.magicResistance = 0 + self.illusions = {} + self.spawnLoc = {} + self.spawnedUnits = 0 +end +function modifier_illusion_of_blood.prototype.IsHidden(self) + return true +end +function modifier_illusion_of_blood.prototype.IsPurgable(self) + return true +end +function modifier_illusion_of_blood.prototype.IsDebuff(self) + return false +end +function modifier_illusion_of_blood.prototype.IsBuff(self) + return true +end +function modifier_illusion_of_blood.prototype.RemoveOnDeath(self) + return true +end +function modifier_illusion_of_blood.prototype.AllowIllusionDuplicate(self) + return false +end +function modifier_illusion_of_blood.prototype.CheckState(self) + return { + [MODIFIER_STATE_INVULNERABLE] = true, + [MODIFIER_STATE_NO_HEALTH_BAR] = true, + [MODIFIER_STATE_UNSELECTABLE] = true, + [MODIFIER_STATE_OUT_OF_GAME] = true, + [MODIFIER_STATE_ROOTED] = true, + [MODIFIER_STATE_UNTARGETABLE] = true + } +end +function modifier_illusion_of_blood.prototype.OnCreated(self) + if not IsServer() then + return + end + local ability = self:GetAbility() + if not ability then + return + end + self.imageCount = ability:GetSpecialValueFor("images_count") + self.imageDuration = ability:GetSpecialValueFor("illusion_duration") + self.incomingDamage = ability:GetSpecialValueFor("incoming_damage") + self.outgoingDamage = ability:GetSpecialValueFor("outgoing_damage") + self.magicResistance = ability:GetSpecialValueFor("magic_resistance") + local caster = self:GetParent() + self.illusions = {} + caster:Purge( + false, + true, + false, + false, + false + ) + caster:Stop() + local abilityInstance = ability + if abilityInstance ~= nil and abilityInstance ~= nil and abilityInstance.illusions ~= nil and abilityInstance.illusions ~= nil then + for ____, illusion in ipairs(abilityInstance.illusions) do + if IsValidEntity(illusion) and illusion:IsAlive() then + illusion:ForceKill(false) + end + end + abilityInstance.illusions = {} + end + local distance = 135 + local spawn = {} + spawn[1] = caster:GetAbsOrigin() + local rightVector = caster:GetRightVector():Normalized() + local forwardVector = caster:GetForwardVector():Normalized() + spawn[2] = spawn[1] + rightVector * distance + spawn[3] = spawn[1] + rightVector * -distance + spawn[4] = spawn[1] + forwardVector * -distance + spawn[5] = spawn[1] + forwardVector * distance + local realPos = RandomInt(0, #spawn - 1) + self.spawnSelf = spawn[realPos + 1] + self.spawnLoc = {} + do + local i = 0 + while i < self.imageCount do + local sp = i + if sp == realPos then + sp = self.imageCount + 1 + end + self.spawnLoc[i + 1] = spawn[sp + 1] + i = i + 1 + end + end + self.spawnedUnits = 0 + self.effectCast = ParticleManager:CreateParticle("particles/units/heroes/hero_chaos_knight/chaos_knight_phantasm.vpcf", PATTACH_ABSORIGIN, caster) + ParticleManager:SetParticleControl(self.effectCast, 0, spawn[1]) + self:AddParticle( + self.effectCast, + false, + false, + -1, + false, + false + ) + EmitSoundOn("Hero_ChaosKnight.Phantasm", caster) +end +function modifier_illusion_of_blood.prototype.CreateIllusionAndAdd(self, caster, location) + if not IsServer() then + return + end + local ability = self:GetAbility() + if not ability then + return + end + local function modifyIllusion(____, illusion) + if not illusion:IsRealHero() then + return + end + local heroIllusion = illusion + local heroCaster = caster + heroIllusion:SetForwardVector(heroCaster:GetForwardVector()) + while heroIllusion:GetLevel() < heroCaster:GetLevel() do + heroIllusion:HeroLevelUp(false) + end + heroIllusion:SetAbilityPoints(0) + do + local i = 0 + while i < 24 do + local casterAbility = heroCaster:GetAbilityByIndex(i) + local illusionAbility = heroIllusion:GetAbilityByIndex(i) + if casterAbility and illusionAbility then + illusionAbility:SetLevel(casterAbility:GetLevel()) + end + i = i + 1 + end + end + do + local slot = 0 + while slot <= 5 do + local item = heroCaster:GetItemInSlot(slot) + if item then + heroIllusion:AddItemByName(item:GetName()) + end + slot = slot + 1 + end + end + heroIllusion:MakeIllusion() + heroIllusion:SetControllableByPlayer( + heroCaster:GetPlayerID(), + false + ) + heroIllusion:SetOwner(heroCaster) + illusion:AddNewModifier(caster, ability, "modifier_illusion", {duration = self.imageDuration, outgoing_damage = self.outgoingDamage, incoming_damage = self.incomingDamage}) + illusion:AddNewModifier(caster, ability, modifier_bloodrage_buff.name, {duration = 100}) + illusion:AddNewModifier(caster, ability, modifier_bloodrage_count.name, {duration = 100}) + illusion:SetBaseMagicalResistanceValue(self.magicResistance) + local abilityInstance = ability + if abilityInstance ~= nil and abilityInstance ~= nil then + if abilityInstance.illusions == nil or abilityInstance.illusions == nil then + abilityInstance.illusions = {} + end + local ____abilityInstance_illusions_0 = abilityInstance.illusions + ____abilityInstance_illusions_0[#____abilityInstance_illusions_0 + 1] = illusion + end + end + CreateUnitByNameAsync( + caster:GetUnitName(), + location, + false, + caster, + caster, + caster:GetTeamNumber(), + function(illusion) return modifyIllusion(nil, illusion) end + ) +end +function modifier_illusion_of_blood.prototype.OnDestroy(self) + if not IsServer() then + return + end + if self.spawnSelf then + FindClearSpaceForUnit( + self:GetParent(), + self.spawnSelf, + true + ) + end + local loop = true + while loop do + if self.spawnedUnits < #self.spawnLoc then + self:CreateIllusionAndAdd( + self:GetParent(), + self.spawnLoc[self.spawnedUnits + 1] + ) + self.spawnedUnits = self.spawnedUnits + 1 + if self.spawnedUnits >= self.imageCount then + loop = false + end + else + loop = false + end + end +end +modifier_illusion_of_blood = __TS__Decorate( + modifier_illusion_of_blood, + modifier_illusion_of_blood, + {registerModifier(nil)}, + {kind = "class", name = "modifier_illusion_of_blood"} +) +____exports.modifier_illusion_of_blood = modifier_illusion_of_blood +return ____exports diff --git a/scripts/vscripts/abilities/heroes/bloodhunter/ability_ring_of_corrosive.lua b/scripts/vscripts/abilities/heroes/bloodhunter/ability_ring_of_corrosive.lua new file mode 100644 index 0000000..c330e7a --- /dev/null +++ b/scripts/vscripts/abilities/heroes/bloodhunter/ability_ring_of_corrosive.lua @@ -0,0 +1,227 @@ +local ____lualib = require("lualib_bundle") +local __TS__Class = ____lualib.__TS__Class +local __TS__ClassExtends = ____lualib.__TS__ClassExtends +local __TS__Decorate = ____lualib.__TS__Decorate +local ____exports = {} +local ____dota_ts_adapter = require("lib.dota_ts_adapter") +local BaseAbility = ____dota_ts_adapter.BaseAbility +local BaseModifier = ____dota_ts_adapter.BaseModifier +local registerAbility = ____dota_ts_adapter.registerAbility +local registerModifier = ____dota_ts_adapter.registerModifier +____exports.ability_ring_of_corrosive = __TS__Class() +local ability_ring_of_corrosive = ____exports.ability_ring_of_corrosive +ability_ring_of_corrosive.name = "ability_ring_of_corrosive" +ability_ring_of_corrosive.____file_path = "scripts/vscripts/abilities/heroes/bloodhunter/ability_ring_of_corrosive.lua" +__TS__ClassExtends(ability_ring_of_corrosive, BaseAbility) +function ability_ring_of_corrosive.prototype.GetAOERadius(self) + return self:GetSpecialValueFor("radius") +end +function ability_ring_of_corrosive.prototype.OnSpellStart(self) + if not IsServer() then + return + end + local caster = self:GetCaster() + local point = self:GetCursorPosition() + local duration = self:GetSpecialValueFor("duration") + CreateModifierThinker( + caster, + self, + ____exports.modifier_ring_of_corrosive.name, + {duration = duration}, + point, + caster:GetTeamNumber(), + false + ) +end +ability_ring_of_corrosive = __TS__Decorate( + ability_ring_of_corrosive, + ability_ring_of_corrosive, + {registerAbility(nil)}, + {kind = "class", name = "ability_ring_of_corrosive"} +) +____exports.ability_ring_of_corrosive = ability_ring_of_corrosive +____exports.modifier_ring_of_corrosive = __TS__Class() +local modifier_ring_of_corrosive = ____exports.modifier_ring_of_corrosive +modifier_ring_of_corrosive.name = "modifier_ring_of_corrosive" +modifier_ring_of_corrosive.____file_path = "scripts/vscripts/abilities/heroes/bloodhunter/ability_ring_of_corrosive.lua" +__TS__ClassExtends(modifier_ring_of_corrosive, BaseModifier) +function modifier_ring_of_corrosive.prototype.____constructor(self, ...) + BaseModifier.prototype.____constructor(self, ...) + self.interval = 0 + self.damage = 0 + self.radius = 0 + self.thinker = false +end +function modifier_ring_of_corrosive.prototype.IsHidden(self) + return false +end +function modifier_ring_of_corrosive.prototype.IsDebuff(self) + return true +end +function modifier_ring_of_corrosive.prototype.IsStunDebuff(self) + return false +end +function modifier_ring_of_corrosive.prototype.IsPurgable(self) + return false +end +function modifier_ring_of_corrosive.prototype.OnCreated(self, params) + if not IsServer() then + return + end + local ability = self:GetAbility() + if not ability then + return + end + self.interval = ability:GetSpecialValueFor("tick_rate") + self.damage = ability:GetSpecialValueFor("damage") + self.radius = ability:GetSpecialValueFor("radius") + self.thinker = params.isProvidedByAura ~= 1 + if not self.thinker then + return + end + self.soundCast = "Hero_Warlock.Upheaval" + EmitSoundOn( + self.soundCast, + self:GetParent() + ) + self:StartIntervalThink(self.interval) + self:OnIntervalThink() + self:PlayEffects() +end +function modifier_ring_of_corrosive.prototype.OnRefresh(self) +end +function modifier_ring_of_corrosive.prototype.OnRemoved(self) +end +function modifier_ring_of_corrosive.prototype.OnDestroy(self) + if not IsServer() then + return + end + if not self.thinker then + return + end + if self.soundCast then + StopSoundOn( + self.soundCast, + self:GetParent() + ) + end + local parent = self:GetParent() + if IsValidEntity(parent) then + UTIL_Remove(parent) + end +end +function modifier_ring_of_corrosive.prototype.OnIntervalThink(self) + if not IsServer() then + return + end + local caster = self:GetCaster() + local ability = self:GetAbility() + if not caster or not ability then + return + end + local enemies = FindUnitsInRadius( + caster:GetTeamNumber(), + self:GetParent():GetAbsOrigin(), + nil, + self.radius, + DOTA_UNIT_TARGET_TEAM_ENEMY, + bit.bor(DOTA_UNIT_TARGET_HERO, DOTA_UNIT_TARGET_BASIC), + DOTA_UNIT_TARGET_FLAG_NONE, + FIND_ANY_ORDER, + false + ) + for ____, enemy in ipairs(enemies) do + do + if not enemy or not enemy:IsAlive() then + goto __continue23 + end + local baseDamage = self:GetParent():GetBaseDamageMax() * 3 + local damageTick = self.damage * baseDamage * self.interval + ApplyDamage({ + victim = enemy, + attacker = caster, + damage = damageTick, + damage_type = ability:GetAbilityDamageType(), + ability = ability + }) + end + ::__continue23:: + end +end +function modifier_ring_of_corrosive.prototype.DeclareFunctions(self) + return {} +end +function modifier_ring_of_corrosive.prototype.IsAura(self) + return self.thinker +end +function modifier_ring_of_corrosive.prototype.GetModifierAura(self) + return ____exports.modifier_ring_of_corrosive.name +end +function modifier_ring_of_corrosive.prototype.GetAuraRadius(self) + return self.radius +end +function modifier_ring_of_corrosive.prototype.GetAuraDuration(self) + return 0.5 +end +function modifier_ring_of_corrosive.prototype.GetAuraSearchTeam(self) + return DOTA_UNIT_TARGET_TEAM_ENEMY +end +function modifier_ring_of_corrosive.prototype.GetAuraSearchType(self) + return bit.bor(DOTA_UNIT_TARGET_HERO, DOTA_UNIT_TARGET_BASIC) +end +function modifier_ring_of_corrosive.prototype.GetAuraSearchFlags(self) + return DOTA_UNIT_TARGET_FLAG_NONE +end +function modifier_ring_of_corrosive.prototype.PlayEffects(self) + if not IsServer() then + return + end + local particleCast = "particles/units/heroes/hero_bloodseeker/bloodseeker_bloodritual_ring_lv.vpcf" + local effectCast = ParticleManager:CreateParticle( + particleCast, + PATTACH_ABSORIGIN_FOLLOW, + self:GetParent() + ) + ParticleManager:SetParticleControl( + effectCast, + 0, + self:GetParent():GetAbsOrigin() + ) + ParticleManager:SetParticleControl( + effectCast, + 1, + Vector(self.radius, 1, 1) + ) + local particleCast2 = "particles/units/heroes/hero_bloodseeker/bloodseeker_spell_bloodbath_bubbles_.vpcf" + local effectCast2 = ParticleManager:CreateParticle( + particleCast2, + PATTACH_ABSORIGIN_FOLLOW, + self:GetParent() + ) + ParticleManager:SetParticleControl( + effectCast2, + 0, + self:GetParent():GetAbsOrigin() + ) + ParticleManager:SetParticleControl( + effectCast2, + 1, + Vector(self.radius, 1, 1) + ) + self:AddParticle( + effectCast2, + false, + false, + -1, + false, + false + ) +end +modifier_ring_of_corrosive = __TS__Decorate( + modifier_ring_of_corrosive, + modifier_ring_of_corrosive, + {registerModifier(nil)}, + {kind = "class", name = "modifier_ring_of_corrosive"} +) +____exports.modifier_ring_of_corrosive = modifier_ring_of_corrosive +return ____exports diff --git a/scripts/vscripts/abilities/heroes/bloodhunter/ability_sacrifice_revenge.lua b/scripts/vscripts/abilities/heroes/bloodhunter/ability_sacrifice_revenge.lua new file mode 100644 index 0000000..bcf5c3c --- /dev/null +++ b/scripts/vscripts/abilities/heroes/bloodhunter/ability_sacrifice_revenge.lua @@ -0,0 +1,205 @@ +local ____lualib = require("lualib_bundle") +local __TS__Class = ____lualib.__TS__Class +local __TS__ClassExtends = ____lualib.__TS__ClassExtends +local __TS__Decorate = ____lualib.__TS__Decorate +local ____exports = {} +local ____dota_ts_adapter = require("lib.dota_ts_adapter") +local BaseAbility = ____dota_ts_adapter.BaseAbility +local BaseModifier = ____dota_ts_adapter.BaseModifier +local registerAbility = ____dota_ts_adapter.registerAbility +local registerModifier = ____dota_ts_adapter.registerModifier +local ____ability_bloodrage = require("abilities.heroes.bloodhunter.ability_bloodrage") +local modifier_bloodrage_buff = ____ability_bloodrage.modifier_bloodrage_buff +____exports.ability_sacrifice_revenge = __TS__Class() +local ability_sacrifice_revenge = ____exports.ability_sacrifice_revenge +ability_sacrifice_revenge.name = "ability_sacrifice_revenge" +ability_sacrifice_revenge.____file_path = "scripts/vscripts/abilities/heroes/bloodhunter/ability_sacrifice_revenge.lua" +__TS__ClassExtends(ability_sacrifice_revenge, BaseAbility) +function ability_sacrifice_revenge.prototype.GetHealthCost(self, level) + return 0 +end +function ability_sacrifice_revenge.prototype.CastFilterResult(self) + local caster = self:GetCaster() + if not caster:HasModifier(modifier_bloodrage_buff.name) then + return UF_FAIL_CUSTOM + end + local modif = caster:FindModifierByName(modifier_bloodrage_buff.name) + if not modif then + return UF_FAIL_CUSTOM + end + local healthCost = self:GetHealthCost(self:GetLevel()) + if modif:GetStackCount() < healthCost then + return UF_FAIL_CUSTOM + end + return UF_SUCCESS +end +function ability_sacrifice_revenge.prototype.GetCustomCastError(self) + local caster = self:GetCaster() + if not caster:HasModifier(modifier_bloodrage_buff.name) then + return "#dota_hud_error_havent_charges" + end + local modif = caster:FindModifierByName(modifier_bloodrage_buff.name) + if not modif then + return "#dota_hud_error_havent_charges" + end + local healthCost = self:GetHealthCost(self:GetLevel()) + if modif:GetStackCount() < healthCost then + return "#dota_hud_error_havent_charges" + end + return "" +end +function ability_sacrifice_revenge.prototype.OnSpellStart(self) + if not IsServer() then + return + end + local caster = self:GetCaster() + local healthCost = self:GetHealthCost(self:GetLevel()) + local modif = caster:FindModifierByName(modifier_bloodrage_buff.name) + if modif then + modif:SetStackCount(modif:GetStackCount() - healthCost) + end + caster:Purge( + false, + true, + false, + false, + false + ) + caster:AddNewModifier( + caster, + self, + ____exports.modifier_sacrifice_revenge.name, + {duration = self:GetSpecialValueFor("transformation_time")} + ) + EmitSoundOn("hero_bloodseeker.rupture", caster) +end +ability_sacrifice_revenge = __TS__Decorate( + ability_sacrifice_revenge, + ability_sacrifice_revenge, + {registerAbility(nil)}, + {kind = "class", name = "ability_sacrifice_revenge"} +) +____exports.ability_sacrifice_revenge = ability_sacrifice_revenge +____exports.modifier_sacrifice_revenge = __TS__Class() +local modifier_sacrifice_revenge = ____exports.modifier_sacrifice_revenge +modifier_sacrifice_revenge.name = "modifier_sacrifice_revenge" +modifier_sacrifice_revenge.____file_path = "scripts/vscripts/abilities/heroes/bloodhunter/ability_sacrifice_revenge.lua" +__TS__ClassExtends(modifier_sacrifice_revenge, BaseModifier) +function modifier_sacrifice_revenge.prototype.IsHidden(self) + return true +end +function modifier_sacrifice_revenge.prototype.GetEffectName(self) + return "particles/econ/items/lifestealer/ls_ti10_immortal/ls_ti10_immortal_infest_gold.vpcf" +end +function modifier_sacrifice_revenge.prototype.GetEffectAttachType(self) + return PATTACH_ABSORIGIN +end +function modifier_sacrifice_revenge.prototype.OnCreated(self) + if not IsServer() then + return + end + local parent = self:GetParent() + if parent:GetUnitName() == "npc_dota_hero_bloodseeker" then + parent:StartGesture(ACT_DOTA_ALCHEMIST_CHEMICAL_RAGE_START) + end +end +function modifier_sacrifice_revenge.prototype.OnDestroy(self) + if not IsServer() then + return + end + local caster = self:GetCaster() + local ability = self:GetAbility() + if not caster or not ability then + return + end + caster:AddNewModifier( + caster, + ability, + ____exports.modifier_sacrifice_revenge_buff.name, + {duration = ability:GetSpecialValueFor("duration")} + ) +end +modifier_sacrifice_revenge = __TS__Decorate( + modifier_sacrifice_revenge, + modifier_sacrifice_revenge, + {registerModifier(nil)}, + {kind = "class", name = "modifier_sacrifice_revenge"} +) +____exports.modifier_sacrifice_revenge = modifier_sacrifice_revenge +____exports.modifier_sacrifice_revenge_buff = __TS__Class() +local modifier_sacrifice_revenge_buff = ____exports.modifier_sacrifice_revenge_buff +modifier_sacrifice_revenge_buff.name = "modifier_sacrifice_revenge_buff" +modifier_sacrifice_revenge_buff.____file_path = "scripts/vscripts/abilities/heroes/bloodhunter/ability_sacrifice_revenge.lua" +__TS__ClassExtends(modifier_sacrifice_revenge_buff, BaseModifier) +function modifier_sacrifice_revenge_buff.prototype.____constructor(self, ...) + BaseModifier.prototype.____constructor(self, ...) + self.bonusArmor = 0 + self.baseAttackTime = 0 + self.pureDamage = 0 +end +function modifier_sacrifice_revenge_buff.prototype.IsHidden(self) + return false +end +function modifier_sacrifice_revenge_buff.prototype.IsPurgable(self) + return false +end +function modifier_sacrifice_revenge_buff.prototype.IsDebuff(self) + return false +end +function modifier_sacrifice_revenge_buff.prototype.IsBuff(self) + return true +end +function modifier_sacrifice_revenge_buff.prototype.AllowIllusionDuplicate(self) + return true +end +function modifier_sacrifice_revenge_buff.prototype.DeclareFunctions(self) + return {MODIFIER_PROPERTY_PHYSICAL_ARMOR_BONUS, MODIFIER_PROPERTY_BASE_ATTACK_TIME_CONSTANT, MODIFIER_PROPERTY_PROCATTACK_BONUS_DAMAGE_PURE} +end +function modifier_sacrifice_revenge_buff.prototype.GetEffectName(self) + return "particles/units/heroes/hero_ursa/ursa_enrage_buff_2.vpcf" +end +function modifier_sacrifice_revenge_buff.prototype.GetEffectAttachType(self) + return PATTACH_ABSORIGIN_FOLLOW +end +function modifier_sacrifice_revenge_buff.prototype.StatusEffectPriority(self) + return 10 +end +function modifier_sacrifice_revenge_buff.prototype.HeroEffectPriority(self) + return 10 +end +function modifier_sacrifice_revenge_buff.prototype.OnCreated(self) + local ability = self:GetAbility() + if not ability then + return + end + self.bonusArmor = ability:GetSpecialValueFor("bonus_armor") + self.baseAttackTime = ability:GetSpecialValueFor("base_attack_time") + self.pureDamage = ability:GetSpecialValueFor("pure_damage") +end +function modifier_sacrifice_revenge_buff.prototype.OnDestroy(self) + if not IsServer() then + return + end + local parent = self:GetParent() + if parent:GetUnitName() == "npc_dota_hero_bloodseeker" then + parent:StartGesture(ACT_DOTA_ALCHEMIST_CHEMICAL_RAGE_END) + end +end +function modifier_sacrifice_revenge_buff.prototype.GetModifierPhysicalArmorBonus(self) + return self:GetParent():GetPhysicalArmorBaseValue() * 2 +end +function modifier_sacrifice_revenge_buff.prototype.GetModifierProcAttack_BonusDamage_Pure(self, event) + local damage = self:GetParent():GetAverageTrueAttackDamage(event.target) * (self.pureDamage / 100) + return damage +end +function modifier_sacrifice_revenge_buff.prototype.GetModifierBaseAttackTimeConstant(self) + return self.baseAttackTime +end +modifier_sacrifice_revenge_buff = __TS__Decorate( + modifier_sacrifice_revenge_buff, + modifier_sacrifice_revenge_buff, + {registerModifier(nil)}, + {kind = "class", name = "modifier_sacrifice_revenge_buff"} +) +____exports.modifier_sacrifice_revenge_buff = modifier_sacrifice_revenge_buff +return ____exports diff --git a/scripts/vscripts/abilities/heroes/bristleback/bristleback_bristleback_custom.lua b/scripts/vscripts/abilities/heroes/bristleback/bristleback_bristleback_custom.lua new file mode 100644 index 0000000..c0b71b9 --- /dev/null +++ b/scripts/vscripts/abilities/heroes/bristleback/bristleback_bristleback_custom.lua @@ -0,0 +1,303 @@ +local ____lualib = require("lualib_bundle") +local __TS__Class = ____lualib.__TS__Class +local __TS__ClassExtends = ____lualib.__TS__ClassExtends +local __TS__Decorate = ____lualib.__TS__Decorate +local ____exports = {} +local ____dota_ts_adapter = require("lib.dota_ts_adapter") +local BaseAbility = ____dota_ts_adapter.BaseAbility +local BaseModifier = ____dota_ts_adapter.BaseModifier +local registerAbility = ____dota_ts_adapter.registerAbility +local registerModifier = ____dota_ts_adapter.registerModifier +local ____incoming_damage_reduction_combine = require("utils.incoming_damage_reduction_combine") +local removeIncomingDamageReductionSource = ____incoming_damage_reduction_combine.removeIncomingDamageReductionSource +local setIncomingDamageReductionEventSource = ____incoming_damage_reduction_combine.setIncomingDamageReductionEventSource +local BRISTLEBACK_INCOMING_SOURCE = "modifier_bristleback_bristleback_custom" +____exports.bristleback_bristleback_custom = __TS__Class() +local bristleback_bristleback_custom = ____exports.bristleback_bristleback_custom +bristleback_bristleback_custom.name = "bristleback_bristleback_custom" +bristleback_bristleback_custom.____file_path = "scripts/vscripts/abilities/heroes/bristleback/bristleback_bristleback_custom.lua" +__TS__ClassExtends(bristleback_bristleback_custom, BaseAbility) +function bristleback_bristleback_custom.prototype.____constructor(self, ...) + BaseAbility.prototype.____constructor(self, ...) + self.caster = self:GetCaster() +end +function bristleback_bristleback_custom.prototype.GetIntrinsicModifierName(self) + return ____exports.modifier_bristleback_bristleback_custom.name +end +function bristleback_bristleback_custom.prototype.IsFacingBack(self, attacker) + local convert_attacker = EntIndexToHScript(attacker) + local forwardVector = self.caster:GetForwardVector() + local forwardAngle = math.deg(math.atan2(forwardVector.x, forwardVector.y)) + local reverseEnemyVector = (self.caster:GetAbsOrigin() - convert_attacker:GetAbsOrigin()):Normalized() + local reverseEnemyAngle = math.deg(math.atan2(reverseEnemyVector.x, reverseEnemyVector.y)) + local back_angle = self:GetSpecialValueFor("back_angle") + local difference = math.abs(forwardAngle - reverseEnemyAngle) + if difference <= back_angle / 1 or difference >= 360 - back_angle / 1 then + return true + end + return false +end +function bristleback_bristleback_custom.prototype.IncStacks(self, add_stack) + local stack = add_stack + local mod = self.caster:FindModifierByName(____exports.modifier_bristleback_bristleback_custom.name) + local quill_release_threshold = self:GetSpecialValueFor("quill_release_threshold") + if mod == nil then + return + end + local all_stack = mod:GetStackCount() + stack + if self.caster:HasScepter() then + local scepter_thresholds = { + self:GetSpecialValueFor("quill_release_threshold"), + self:GetSpecialValueFor("quill_release_threshold_scepter1"), + self:GetSpecialValueFor("quill_release_threshold_scepter2"), + self:GetSpecialValueFor("quill_release_threshold_scepter3"), + self:GetSpecialValueFor("quill_release_threshold_scepter4") + } + local spray_count = 0 + if all_stack >= scepter_thresholds[5] then + spray_count = 5 + elseif all_stack >= scepter_thresholds[4] and all_stack < scepter_thresholds[5] then + spray_count = 4 + elseif all_stack >= scepter_thresholds[3] and all_stack < scepter_thresholds[4] then + spray_count = 3 + elseif all_stack >= scepter_thresholds[2] and all_stack < scepter_thresholds[3] then + spray_count = 2 + elseif all_stack >= scepter_thresholds[1] and all_stack < scepter_thresholds[2] then + spray_count = 1 + end + if spray_count >= 1 then + do + local i = 0 + while i < spray_count do + ____exports.modifier_bristleback_bristleback_custom_make_spray:apply(self.caster, self.caster, self, {}) + i = i + 1 + end + end + mod:SetStackCount(all_stack - spray_count * quill_release_threshold) + return + end + end + if all_stack >= quill_release_threshold then + local count = math.floor(all_stack / quill_release_threshold) + do + local i = 0 + while i < count do + ____exports.modifier_bristleback_bristleback_custom_make_spray:apply(self.caster, self.caster, self, {}) + i = i + 1 + end + end + mod:SetStackCount(all_stack - count * quill_release_threshold) + else + mod:SetStackCount(all_stack) + end +end +bristleback_bristleback_custom = __TS__Decorate( + bristleback_bristleback_custom, + bristleback_bristleback_custom, + {registerAbility(nil)}, + {kind = "class", name = "bristleback_bristleback_custom"} +) +____exports.bristleback_bristleback_custom = bristleback_bristleback_custom +____exports.modifier_bristleback_bristleback_custom = __TS__Class() +local modifier_bristleback_bristleback_custom = ____exports.modifier_bristleback_bristleback_custom +modifier_bristleback_bristleback_custom.name = "modifier_bristleback_bristleback_custom" +modifier_bristleback_bristleback_custom.____file_path = "scripts/vscripts/abilities/heroes/bristleback/bristleback_bristleback_custom.lua" +__TS__ClassExtends(modifier_bristleback_bristleback_custom, BaseModifier) +function modifier_bristleback_bristleback_custom.prototype.____constructor(self, ...) + BaseModifier.prototype.____constructor(self, ...) + self.ability = self:GetAbility() + self.parent = self:GetParent() + self.side_angle = 0 + self.back_angle = 0 +end +function modifier_bristleback_bristleback_custom.prototype.IsPurgable(self) + return false +end +function modifier_bristleback_bristleback_custom.prototype.IsPurgeException(self) + return false +end +function modifier_bristleback_bristleback_custom.prototype.IsHidden(self) + return true +end +function modifier_bristleback_bristleback_custom.prototype.DeclareFunctions(self) + return {MODIFIER_EVENT_ON_TAKEDAMAGE} +end +function modifier_bristleback_bristleback_custom.prototype.OnCreated(self) + self.side_angle = self.ability:GetSpecialValueFor("side_angle") + self.back_angle = self.ability:GetSpecialValueFor("back_angle") + if not IsServer() then + return + end + setIncomingDamageReductionEventSource( + nil, + self.parent, + BRISTLEBACK_INCOMING_SOURCE, + function(____, event) + if self.parent:PassivesDisabled() then + return 0 + end + if bit.band(event.damage_flags, DOTA_DAMAGE_FLAG_REFLECTION) == DOTA_DAMAGE_FLAG_REFLECTION then + return 0 + end + if bit.band(event.damage_flags, DOTA_DAMAGE_FLAG_HPLOSS) == DOTA_DAMAGE_FLAG_HPLOSS then + return 0 + end + local forwardVector = self.parent:GetForwardVector() + local forwardAngle = math.deg(math.atan2(forwardVector.x, forwardVector.y)) + local reverseEnemyVector = (self.parent:GetAbsOrigin() - event.attacker:GetAbsOrigin()):Normalized() + local reverseEnemyAngle = math.deg(math.atan2(reverseEnemyVector.x, reverseEnemyVector.y)) + local difference = math.abs(forwardAngle - reverseEnemyAngle) + local sideDamageReduction = self.ability:GetSpecialValueFor("side_damage_reduction") + local backDamageReduction = self.ability:GetSpecialValueFor("back_damage_reduction") + if difference <= self.back_angle / 1 or difference >= 360 - self.back_angle / 1 then + local particle = ParticleManager:CreateParticle("particles/units/heroes/hero_bristleback/bristleback_back_dmg.vpcf", PATTACH_ABSORIGIN_FOLLOW, self.parent) + ParticleManager:SetParticleControl( + particle, + 1, + self.parent:GetAbsOrigin() + ) + ParticleManager:SetParticleControlEnt( + particle, + 1, + self.parent, + PATTACH_POINT_FOLLOW, + "attach_hitloc", + self.parent:GetAbsOrigin(), + true + ) + ParticleManager:ReleaseParticleIndex(particle) + local particle2 = ParticleManager:CreateParticle("particles/units/heroes/hero_bristleback/bristleback_back_lrg_dmg.vpcf", PATTACH_ABSORIGIN_FOLLOW, self.parent) + ParticleManager:SetParticleControlEnt( + particle2, + 1, + self.parent, + PATTACH_POINT_FOLLOW, + "attach_hitloc", + self.parent:GetAbsOrigin(), + true + ) + ParticleManager:ReleaseParticleIndex(particle2) + EmitSoundOn("Hero_Bristleback.Bristleback", self.parent) + return math.max(0, backDamageReduction) + end + if difference <= self.side_angle or difference >= 360 - self.side_angle then + local particle = ParticleManager:CreateParticle("particles/units/heroes/hero_bristleback/bristleback_back_dmg.vpcf", PATTACH_ABSORIGIN_FOLLOW, self.parent) + ParticleManager:SetParticleControl( + particle, + 1, + self.parent:GetAbsOrigin() + ) + ParticleManager:SetParticleControlEnt( + particle, + 1, + self.parent, + PATTACH_POINT_FOLLOW, + "attach_hitloc", + self.parent:GetAbsOrigin(), + true + ) + ParticleManager:ReleaseParticleIndex(particle) + return math.max(0, sideDamageReduction) + end + return 0 + end + ) +end +function modifier_bristleback_bristleback_custom.prototype.OnRefresh(self) + self:OnCreated() +end +function modifier_bristleback_bristleback_custom.prototype.OnDestroy(self) + if not IsServer() then + return + end + removeIncomingDamageReductionSource(nil, self.parent, BRISTLEBACK_INCOMING_SOURCE) +end +function modifier_bristleback_bristleback_custom.prototype.OnTakeDamage(self, event) + if event.attacker == nil then + return + end + if event.unit ~= self.parent then + return + end + if self.parent:PassivesDisabled() then + return + end + if bit.band(event.damage_flags, DOTA_DAMAGE_FLAG_REFLECTION) == DOTA_DAMAGE_FLAG_REFLECTION then + return + end + if bit.band(event.damage_flags, DOTA_DAMAGE_FLAG_HPLOSS) == DOTA_DAMAGE_FLAG_HPLOSS then + return + end + if not self.parent:HasAbility("bristleback_quill_spray_custom") then + return + end + if not self.parent:FindAbilityByName("bristleback_quill_spray_custom"):IsTrained() then + return + end + if self.ability:IsFacingBack(event.attacker:GetEntityIndex()) then + self.ability:IncStacks(event.damage) + end +end +modifier_bristleback_bristleback_custom = __TS__Decorate( + modifier_bristleback_bristleback_custom, + modifier_bristleback_bristleback_custom, + {registerModifier(nil)}, + {kind = "class", name = "modifier_bristleback_bristleback_custom"} +) +____exports.modifier_bristleback_bristleback_custom = modifier_bristleback_bristleback_custom +____exports.modifier_bristleback_bristleback_custom_make_spray = __TS__Class() +local modifier_bristleback_bristleback_custom_make_spray = ____exports.modifier_bristleback_bristleback_custom_make_spray +modifier_bristleback_bristleback_custom_make_spray.name = "modifier_bristleback_bristleback_custom_make_spray" +modifier_bristleback_bristleback_custom_make_spray.____file_path = "scripts/vscripts/abilities/heroes/bristleback/bristleback_bristleback_custom.lua" +__TS__ClassExtends(modifier_bristleback_bristleback_custom_make_spray, BaseModifier) +function modifier_bristleback_bristleback_custom_make_spray.prototype.IsHidden(self) + return true +end +function modifier_bristleback_bristleback_custom_make_spray.prototype.OnCreated(self, params) + if not IsServer() then + return + end + self:SetStackCount(1) + self:Active() + self:StartIntervalThink(self:GetAbility():GetSpecialValueFor("quill_release_interval")) +end +function modifier_bristleback_bristleback_custom_make_spray.prototype.OnRefresh(self, params) + if not IsServer() then + return + end + self:IncrementStackCount() +end +function modifier_bristleback_bristleback_custom_make_spray.prototype.Active(self) + if not IsServer() then + return + end + if self:GetStackCount() <= 0 then + return + end + self:DecrementStackCount() + local ability_quill = self:GetParent():FindAbilityByName("bristleback_quill_spray_custom") + if not ability_quill then + return + end + if ability_quill:GetLevel() <= 0 then + return + end + ability_quill:MakeSpray(nil, true) +end +function modifier_bristleback_bristleback_custom_make_spray.prototype.OnIntervalThink(self) + if not IsServer() then + return + end + self:Active() + if self:GetStackCount() <= 0 then + self:Destroy() + end +end +modifier_bristleback_bristleback_custom_make_spray = __TS__Decorate( + modifier_bristleback_bristleback_custom_make_spray, + modifier_bristleback_bristleback_custom_make_spray, + {registerModifier(nil)}, + {kind = "class", name = "modifier_bristleback_bristleback_custom_make_spray"} +) +____exports.modifier_bristleback_bristleback_custom_make_spray = modifier_bristleback_bristleback_custom_make_spray +return ____exports diff --git a/scripts/vscripts/abilities/heroes/bristleback/bristleback_hairball_custom.lua b/scripts/vscripts/abilities/heroes/bristleback/bristleback_hairball_custom.lua new file mode 100644 index 0000000..416d0b5 --- /dev/null +++ b/scripts/vscripts/abilities/heroes/bristleback/bristleback_hairball_custom.lua @@ -0,0 +1,87 @@ +local ____lualib = require("lualib_bundle") +local __TS__Class = ____lualib.__TS__Class +local __TS__ClassExtends = ____lualib.__TS__ClassExtends +local __TS__Decorate = ____lualib.__TS__Decorate +local ____exports = {} +local ____dota_ts_adapter = require("lib.dota_ts_adapter") +local BaseAbility = ____dota_ts_adapter.BaseAbility +local registerAbility = ____dota_ts_adapter.registerAbility +____exports.bristleback_hairball_custom = __TS__Class() +local bristleback_hairball_custom = ____exports.bristleback_hairball_custom +bristleback_hairball_custom.name = "bristleback_hairball_custom" +bristleback_hairball_custom.____file_path = "scripts/vscripts/abilities/heroes/bristleback/bristleback_hairball_custom.lua" +__TS__ClassExtends(bristleback_hairball_custom, BaseAbility) +function bristleback_hairball_custom.prototype.Precache(self, context) + PrecacheResource("particle", "particles/units/heroes/hero_bristleback/bristleback_hairball.vpcf", context) + PrecacheResource("model", "models/heroes/lanaya/lanaya_trap_crystal_invis.vmdl", context) +end +function bristleback_hairball_custom.prototype.OnSpellStart(self) + local caster = self:GetCaster() + EmitSoundOn("Hero_Bristleback.Hairball.Cast", caster) + local projectile = { + Ability = self, + EffectName = "particles/units/heroes/hero_bristleback/bristleback_hairball.vpcf", + vSpawnOrigin = caster:GetAttachmentOrigin(caster:ScriptLookupAttachment("attach_hitloc")), + fDistance = (self:GetCursorPosition() - caster:GetAbsOrigin()):Length2D(), + fStartRadius = 0, + fEndRadius = 0, + Source = caster, + bHasFrontalCone = false, + bReplaceExisting = false, + iUnitTargetTeam = DOTA_UNIT_TARGET_TEAM_NONE, + iUnitTargetFlags = DOTA_UNIT_TARGET_FLAG_NONE, + iUnitTargetType = DOTA_UNIT_TARGET_NONE, + fExpireTime = GameRules:GetGameTime() + 5, + bDeleteOnHit = false, + vVelocity = (self:GetCursorPosition() - self:GetCaster():GetAbsOrigin()):Normalized() * self:GetSpecialValueFor("projectile_speed") * Vector(1, 1, 0), + bProvidesVision = false + } + ProjectileManager:CreateLinearProjectile(projectile) +end +function bristleback_hairball_custom.prototype.OnProjectileHit(self, target, vLocation) + if not IsServer() then + return + end + local caster = self:GetCaster() + local spray = caster:FindAbilityByName("bristleback_quill_spray_custom") + AddFOWViewer( + caster:GetTeamNumber(), + vLocation, + self:GetSpecialValueFor("radius"), + 2, + false + ) + self:AddGoo(vLocation, caster) + Timers:CreateTimer( + 0.15, + function() + self:AddGoo(vLocation, caster) + end + ) + if spray and spray:GetLevel() > 0 then + do + local i = 0 + while i < self:GetSpecialValueFor("quill_stacks") do + spray:MakeSpray( + GetGroundPosition(vLocation, nil), + nil + ) + i = i + 1 + end + end + end +end +function bristleback_hairball_custom.prototype.AddGoo(self, vLocation, caster) + local goo = caster:FindAbilityByName("bristleback_viscous_nasal_goo_custom") + if goo and goo:GetLevel() > 0 then + goo:OnCustomSpellStart(nil, vLocation) + end +end +bristleback_hairball_custom = __TS__Decorate( + bristleback_hairball_custom, + bristleback_hairball_custom, + {registerAbility(nil)}, + {kind = "class", name = "bristleback_hairball_custom"} +) +____exports.bristleback_hairball_custom = bristleback_hairball_custom +return ____exports diff --git a/scripts/vscripts/abilities/heroes/bristleback/bristleback_quill_spray_custom.lua b/scripts/vscripts/abilities/heroes/bristleback/bristleback_quill_spray_custom.lua new file mode 100644 index 0000000..2d678ee --- /dev/null +++ b/scripts/vscripts/abilities/heroes/bristleback/bristleback_quill_spray_custom.lua @@ -0,0 +1,330 @@ +local ____lualib = require("lualib_bundle") +local __TS__Class = ____lualib.__TS__Class +local __TS__ClassExtends = ____lualib.__TS__ClassExtends +local __TS__Decorate = ____lualib.__TS__Decorate +local __TS__ArrayForEach = ____lualib.__TS__ArrayForEach +local ____exports = {} +local ____dota_ts_adapter = require("lib.dota_ts_adapter") +local BaseAbility = ____dota_ts_adapter.BaseAbility +local BaseModifier = ____dota_ts_adapter.BaseModifier +local registerAbility = ____dota_ts_adapter.registerAbility +local registerModifier = ____dota_ts_adapter.registerModifier +____exports.bristleback_quill_spray_custom = __TS__Class() +local bristleback_quill_spray_custom = ____exports.bristleback_quill_spray_custom +bristleback_quill_spray_custom.name = "bristleback_quill_spray_custom" +bristleback_quill_spray_custom.____file_path = "scripts/vscripts/abilities/heroes/bristleback/bristleback_quill_spray_custom.lua" +__TS__ClassExtends(bristleback_quill_spray_custom, BaseAbility) +function bristleback_quill_spray_custom.prototype.Precache(self, context) + PrecacheResource("particle", "particles/units/heroes/hero_bristleback/bristleback_quill_spray.vpcf", context) + PrecacheResource("particle", "particles/units/heroes/hero_bristleback/bristleback_quill_spray_impact.vpcf", context) + PrecacheResource("particle", "particles/units/heroes/hero_bristleback/bristleback_quill_spray_hit.vpcf", context) + PrecacheResource("particle", "particles/units/heroes/hero_bristleback/bristleback_quill_spray_hit_creep.vpcf", context) +end +function bristleback_quill_spray_custom.prototype.GetCastRange(self, location, target) + return self:GetSpecialValueFor("radius") +end +function bristleback_quill_spray_custom.prototype.OnSpellStart(self) + self:MakeSpray(nil, nil) +end +function bristleback_quill_spray_custom.prototype.GetIntrinsicModifierName(self) + return ____exports.modifier_bristleback_quill_spray_autocast_custom.name +end +function bristleback_quill_spray_custom.prototype.MakeSpray(self, location, isPassive) + if not IsServer() then + return + end + local caster = self:GetCaster() + local projectile_speed = self:GetSpecialValueFor("projectile_speed") + local radius = self:GetSpecialValueFor("radius") + local duration = radius / projectile_speed + if location == nil then + caster:FadeGesture(ACT_DOTA_CAST_ABILITY_2) + caster:StartGesture(ACT_DOTA_CAST_ABILITY_2) + end + local x = nil + local y = nil + if location ~= nil then + x = location.x + y = location.y + end + ____exports.modifier_bristleback_quillspray_custom_thinker:apply(caster, caster, self, {x = x, y = y, duration = duration, passive = isPassive}) + EmitSoundOn("Hero_Bristleback.QuillSpray.Cast", caster) +end +bristleback_quill_spray_custom = __TS__Decorate( + bristleback_quill_spray_custom, + bristleback_quill_spray_custom, + {registerAbility(nil)}, + {kind = "class", name = "bristleback_quill_spray_custom"} +) +____exports.bristleback_quill_spray_custom = bristleback_quill_spray_custom +____exports.modifier_bristleback_quill_spray_autocast_custom = __TS__Class() +local modifier_bristleback_quill_spray_autocast_custom = ____exports.modifier_bristleback_quill_spray_autocast_custom +modifier_bristleback_quill_spray_autocast_custom.name = "modifier_bristleback_quill_spray_autocast_custom" +modifier_bristleback_quill_spray_autocast_custom.____file_path = "scripts/vscripts/abilities/heroes/bristleback/bristleback_quill_spray_custom.lua" +__TS__ClassExtends(modifier_bristleback_quill_spray_autocast_custom, BaseModifier) +function modifier_bristleback_quill_spray_autocast_custom.prototype.____constructor(self, ...) + BaseModifier.prototype.____constructor(self, ...) + self.ability = self:GetAbility() +end +function modifier_bristleback_quill_spray_autocast_custom.prototype.IsPurgable(self) + return false +end +function modifier_bristleback_quill_spray_autocast_custom.prototype.IsPurgeException(self) + return false +end +function modifier_bristleback_quill_spray_autocast_custom.prototype.IsHidden(self) + return true +end +function modifier_bristleback_quill_spray_autocast_custom.prototype.OnCreated(self, params) + self:StartIntervalThink(0.1) +end +function modifier_bristleback_quill_spray_autocast_custom.prototype.OnIntervalThink(self) + if not IsServer() then + return + end + local parent = self:GetParent() + if self.ability:GetAutoCastState() == true and self.ability:IsFullyCastable() and not parent:IsChanneling() and not self.ability:IsHidden() then + parent:CastAbilityNoTarget( + self.ability, + parent:GetPlayerID() + ) + end +end +modifier_bristleback_quill_spray_autocast_custom = __TS__Decorate( + modifier_bristleback_quill_spray_autocast_custom, + modifier_bristleback_quill_spray_autocast_custom, + {registerModifier(nil)}, + {kind = "class", name = "modifier_bristleback_quill_spray_autocast_custom"} +) +____exports.modifier_bristleback_quill_spray_autocast_custom = modifier_bristleback_quill_spray_autocast_custom +____exports.modifier_bristleback_quillspray_custom_thinker = __TS__Class() +local modifier_bristleback_quillspray_custom_thinker = ____exports.modifier_bristleback_quillspray_custom_thinker +modifier_bristleback_quillspray_custom_thinker.name = "modifier_bristleback_quillspray_custom_thinker" +modifier_bristleback_quillspray_custom_thinker.____file_path = "scripts/vscripts/abilities/heroes/bristleback/bristleback_quill_spray_custom.lua" +__TS__ClassExtends(modifier_bristleback_quillspray_custom_thinker, BaseModifier) +function modifier_bristleback_quillspray_custom_thinker.prototype.____constructor(self, ...) + BaseModifier.prototype.____constructor(self, ...) + self.caster = self:GetCaster() + self.parent = self:GetParent() + self.ability = self:GetAbility() + self.radius = 0 + self.quill_base_damage = 0 + self.quill_stack_damage = 0 + self.quill_stack_duration = 0 + self.max_damage = 0 + self.attack_damage_bonus_pct = 0 + self.hit_enemies = {} + self.cast_point = Vector(0, 0, 0) + self.passive = false +end +function modifier_bristleback_quillspray_custom_thinker.prototype.IsHidden(self) + return true +end +function modifier_bristleback_quillspray_custom_thinker.prototype.IsPurgable(self) + return false +end +function modifier_bristleback_quillspray_custom_thinker.prototype.RemoveOnDeath(self) + return false +end +function modifier_bristleback_quillspray_custom_thinker.prototype.GetAttributes(self) + return MODIFIER_ATTRIBUTE_MULTIPLE +end +function modifier_bristleback_quillspray_custom_thinker.prototype.OnCreated(self, params) + self.radius = self.ability:GetSpecialValueFor("radius") + self.quill_base_damage = self.ability:GetSpecialValueFor("quill_base_damage") + self.quill_stack_damage = self.ability:GetSpecialValueFor("quill_stack_damage") + self.quill_stack_duration = self.ability:GetSpecialValueFor("quill_stack_duration") + self.max_damage = self.ability:GetSpecialValueFor("max_damage") + self.attack_damage_bonus_pct = self.ability:GetSpecialValueFor("attack_damage_bonus_pct") + self.passive = params.passive + if not IsServer() then + return + end + self.cast_point = self.parent:GetAbsOrigin() + if params.x ~= nil and params.y ~= nil then + self.cast_point = GetGroundPosition( + Vector(params.x, params.y, 0), + nil + ) + end + local particle = ParticleManager:CreateParticle("particles/units/heroes/hero_bristleback/bristleback_quill_spray.vpcf", PATTACH_WORLDORIGIN, nil) + ParticleManager:SetParticleControl(particle, 0, self.cast_point) + self:AddParticle( + particle, + false, + false, + -1, + false, + false + ) + self.hit_enemies = {} + self:StartIntervalThink(FrameTime()) +end +function modifier_bristleback_quillspray_custom_thinker.prototype.OnIntervalThink(self) + if not IsServer() then + return + end + local radius_pct = math.min( + (self:GetDuration() - self:GetRemainingTime()) / self:GetDuration(), + 1 + ) + local origin = self.cast_point + local enemies = FindUnitsInRadius( + self.parent:GetTeamNumber(), + origin, + nil, + self.radius * radius_pct, + DOTA_UNIT_TARGET_TEAM_ENEMY, + DOTA_UNIT_TARGET_HERO + DOTA_UNIT_TARGET_CREEP, + DOTA_UNIT_TARGET_FLAG_MAGIC_IMMUNE_ENEMIES, + FIND_ANY_ORDER, + false + ) + __TS__ArrayForEach( + enemies, + function(____, enemy) + if not (self.hit_enemies[enemy:GetEntityIndex()] ~= nil) then + self.hit_enemies[enemy:GetEntityIndex()] = true + local quill_spray_stacks = 0 + local quill_spray_modifier = enemy:FindModifierByName("modifier_bristleback_quill_spray_custom") + if quill_spray_modifier ~= nil then + quill_spray_stacks = quill_spray_modifier:GetStackCount() + end + local attackBonus = self.caster:GetAverageTrueAttackDamage(enemy) * self.attack_damage_bonus_pct / 100 + local damage_flags = DOTA_DAMAGE_FLAG_BYPASSES_BLOCK + ApplyDamage({ + victim = enemy, + damage = math.min(self.quill_base_damage + self.quill_stack_damage * quill_spray_stacks + attackBonus, self.max_damage), + damage_type = DAMAGE_TYPE_PHYSICAL, + damage_flags = damage_flags, + attacker = self.caster, + ability = self.ability + }) + local particle = ParticleManager:CreateParticle("particles/units/heroes/hero_bristleback/bristleback_quill_spray_impact.vpcf", PATTACH_ABSORIGIN_FOLLOW, enemy) + ParticleManager:SetParticleControlEnt( + particle, + 1, + enemy, + PATTACH_ABSORIGIN_FOLLOW, + "attach_hitloc", + enemy:GetAbsOrigin(), + true + ) + ParticleManager:ReleaseParticleIndex(particle) + EmitSoundOn("Hero_Bristleback.QuillSpray.Target", enemy) + ____exports.modifier_bristleback_quill_spray_custom:apply( + enemy, + self.caster, + self.ability, + {duration = self.quill_stack_duration * (1 - enemy:GetStatusResistance())} + ) + ____exports.modifier_bristleback_quill_spray_custom_count:apply( + enemy, + self.caster, + self.ability, + {duration = self.quill_stack_duration * (1 - enemy:GetStatusResistance())} + ) + end + end + ) +end +modifier_bristleback_quillspray_custom_thinker = __TS__Decorate( + modifier_bristleback_quillspray_custom_thinker, + modifier_bristleback_quillspray_custom_thinker, + {registerModifier(nil)}, + {kind = "class", name = "modifier_bristleback_quillspray_custom_thinker"} +) +____exports.modifier_bristleback_quillspray_custom_thinker = modifier_bristleback_quillspray_custom_thinker +____exports.modifier_bristleback_quill_spray_custom = __TS__Class() +local modifier_bristleback_quill_spray_custom = ____exports.modifier_bristleback_quill_spray_custom +modifier_bristleback_quill_spray_custom.name = "modifier_bristleback_quill_spray_custom" +modifier_bristleback_quill_spray_custom.____file_path = "scripts/vscripts/abilities/heroes/bristleback/bristleback_quill_spray_custom.lua" +__TS__ClassExtends(modifier_bristleback_quill_spray_custom, BaseModifier) +function modifier_bristleback_quill_spray_custom.prototype.____constructor(self, ...) + BaseModifier.prototype.____constructor(self, ...) + self.parent = self:GetParent() +end +function modifier_bristleback_quill_spray_custom.prototype.IsPurgable(self) + return false +end +function modifier_bristleback_quill_spray_custom.prototype.OnCreated(self, params) + if not IsServer() then + return + end + self:IncrementStackCount() + local particle_name = "particles/units/heroes/hero_bristleback/bristleback_quill_spray_hit.vpcf" + if self.parent:IsCreep() then + particle_name = "particles/units/heroes/hero_bristleback/bristleback_quill_spray_hit_creep.vpcf" + end + local particle = ParticleManager:CreateParticle(particle_name, PATTACH_ABSORIGIN_FOLLOW, self.parent) + ParticleManager:SetParticleControlEnt( + particle, + 0, + self.parent, + PATTACH_ABSORIGIN_FOLLOW, + "attach_hitloc", + self.parent:GetAbsOrigin(), + true + ) + ParticleManager:SetParticleControlEnt( + particle, + 1, + self.parent, + PATTACH_ABSORIGIN_FOLLOW, + "attach_hitloc", + self.parent:GetAbsOrigin(), + true + ) + self:AddParticle( + particle, + false, + false, + -1, + false, + false + ) +end +function modifier_bristleback_quill_spray_custom.prototype.OnRefresh(self, params) + if not IsServer() then + return + end + self:IncrementStackCount() +end +modifier_bristleback_quill_spray_custom = __TS__Decorate( + modifier_bristleback_quill_spray_custom, + modifier_bristleback_quill_spray_custom, + {registerModifier(nil)}, + {kind = "class", name = "modifier_bristleback_quill_spray_custom"} +) +____exports.modifier_bristleback_quill_spray_custom = modifier_bristleback_quill_spray_custom +____exports.modifier_bristleback_quill_spray_custom_count = __TS__Class() +local modifier_bristleback_quill_spray_custom_count = ____exports.modifier_bristleback_quill_spray_custom_count +modifier_bristleback_quill_spray_custom_count.name = "modifier_bristleback_quill_spray_custom_count" +modifier_bristleback_quill_spray_custom_count.____file_path = "scripts/vscripts/abilities/heroes/bristleback/bristleback_quill_spray_custom.lua" +__TS__ClassExtends(modifier_bristleback_quill_spray_custom_count, BaseModifier) +function modifier_bristleback_quill_spray_custom_count.prototype.IsHidden(self) + return true +end +function modifier_bristleback_quill_spray_custom_count.prototype.IsPurgable(self) + return false +end +function modifier_bristleback_quill_spray_custom_count.prototype.GetAttributes(self) + return MODIFIER_ATTRIBUTE_MULTIPLE +end +function modifier_bristleback_quill_spray_custom_count.prototype.OnDestroy(self) + if not IsServer() then + return + end + local modifier = self:GetParent():FindModifierByName("modifier_bristleback_quill_spray_custom") + if modifier ~= nil then + modifier:DecrementStackCount() + end +end +modifier_bristleback_quill_spray_custom_count = __TS__Decorate( + modifier_bristleback_quill_spray_custom_count, + modifier_bristleback_quill_spray_custom_count, + {registerModifier(nil)}, + {kind = "class", name = "modifier_bristleback_quill_spray_custom_count"} +) +____exports.modifier_bristleback_quill_spray_custom_count = modifier_bristleback_quill_spray_custom_count +return ____exports diff --git a/scripts/vscripts/abilities/heroes/bristleback/bristleback_rage_fortitude_custom.lua b/scripts/vscripts/abilities/heroes/bristleback/bristleback_rage_fortitude_custom.lua new file mode 100644 index 0000000..01069a1 --- /dev/null +++ b/scripts/vscripts/abilities/heroes/bristleback/bristleback_rage_fortitude_custom.lua @@ -0,0 +1,127 @@ +local ____lualib = require("lualib_bundle") +local __TS__Class = ____lualib.__TS__Class +local __TS__ClassExtends = ____lualib.__TS__ClassExtends +local __TS__Decorate = ____lualib.__TS__Decorate +local ____exports = {} +local ____dota_ts_adapter = require("lib.dota_ts_adapter") +local BaseAbility = ____dota_ts_adapter.BaseAbility +local BaseModifier = ____dota_ts_adapter.BaseModifier +local registerAbility = ____dota_ts_adapter.registerAbility +local registerModifier = ____dota_ts_adapter.registerModifier +local ____hero_rage = require("abilities.system.hero_rage") +local heroRageGetCurrent = ____hero_rage.heroRageGetCurrent +local WARPATH_ACTIVE_PARTICLE = "particles/units/heroes/hero_bristleback/bristleback_warpath_active.vpcf" +____exports.bristleback_rage_fortitude_custom = __TS__Class() +local bristleback_rage_fortitude_custom = ____exports.bristleback_rage_fortitude_custom +bristleback_rage_fortitude_custom.name = "bristleback_rage_fortitude_custom" +bristleback_rage_fortitude_custom.____file_path = "scripts/vscripts/abilities/heroes/bristleback/bristleback_rage_fortitude_custom.lua" +__TS__ClassExtends(bristleback_rage_fortitude_custom, BaseAbility) +function bristleback_rage_fortitude_custom.prototype.Precache(self, context) + PrecacheResource("particle", WARPATH_ACTIVE_PARTICLE, context) +end +function bristleback_rage_fortitude_custom.prototype.GetIntrinsicModifierName(self) + return ____exports.modifier_bristleback_rage_fortitude_custom.name +end +bristleback_rage_fortitude_custom = __TS__Decorate( + bristleback_rage_fortitude_custom, + bristleback_rage_fortitude_custom, + {registerAbility(nil)}, + {kind = "class", name = "bristleback_rage_fortitude_custom"} +) +____exports.bristleback_rage_fortitude_custom = bristleback_rage_fortitude_custom +--- Следит за яростью и включает защитный бафф выше порога. +____exports.modifier_bristleback_rage_fortitude_custom = __TS__Class() +local modifier_bristleback_rage_fortitude_custom = ____exports.modifier_bristleback_rage_fortitude_custom +modifier_bristleback_rage_fortitude_custom.name = "modifier_bristleback_rage_fortitude_custom" +modifier_bristleback_rage_fortitude_custom.____file_path = "scripts/vscripts/abilities/heroes/bristleback/bristleback_rage_fortitude_custom.lua" +__TS__ClassExtends(modifier_bristleback_rage_fortitude_custom, BaseModifier) +function modifier_bristleback_rage_fortitude_custom.prototype.IsHidden(self) + return true +end +function modifier_bristleback_rage_fortitude_custom.prototype.IsPurgable(self) + return false +end +function modifier_bristleback_rage_fortitude_custom.prototype.OnCreated(self) + if not IsServer() then + return + end + self:StartIntervalThink(0.1) + self:OnIntervalThink() +end +function modifier_bristleback_rage_fortitude_custom.prototype.OnIntervalThink(self) + if not IsServer() then + return + end + local parent = self:GetParent() + local ability = self:GetAbility() + if not ability or parent:PassivesDisabled() then + parent:RemoveModifierByName(____exports.modifier_bristleback_rage_fortitude_buff.name) + return + end + local threshold = ability:GetSpecialValueFor("rage_threshold") + local rage = heroRageGetCurrent(nil, parent) + local buffName = ____exports.modifier_bristleback_rage_fortitude_buff.name + if rage > threshold then + if not parent:HasModifier(buffName) then + parent:AddNewModifier(parent, ability, buffName, {}) + end + else + parent:RemoveModifierByName(buffName) + end +end +function modifier_bristleback_rage_fortitude_custom.prototype.OnDestroy(self) + if not IsServer() then + return + end + self:GetParent():RemoveModifierByName(____exports.modifier_bristleback_rage_fortitude_buff.name) +end +modifier_bristleback_rage_fortitude_custom = __TS__Decorate( + modifier_bristleback_rage_fortitude_custom, + modifier_bristleback_rage_fortitude_custom, + {registerModifier(nil)}, + {kind = "class", name = "modifier_bristleback_rage_fortitude_custom"} +) +____exports.modifier_bristleback_rage_fortitude_custom = modifier_bristleback_rage_fortitude_custom +____exports.modifier_bristleback_rage_fortitude_buff = __TS__Class() +local modifier_bristleback_rage_fortitude_buff = ____exports.modifier_bristleback_rage_fortitude_buff +modifier_bristleback_rage_fortitude_buff.name = "modifier_bristleback_rage_fortitude_buff" +modifier_bristleback_rage_fortitude_buff.____file_path = "scripts/vscripts/abilities/heroes/bristleback/bristleback_rage_fortitude_custom.lua" +__TS__ClassExtends(modifier_bristleback_rage_fortitude_buff, BaseModifier) +function modifier_bristleback_rage_fortitude_buff.prototype.IsHidden(self) + return false +end +function modifier_bristleback_rage_fortitude_buff.prototype.IsPurgable(self) + return false +end +function modifier_bristleback_rage_fortitude_buff.prototype.IsDebuff(self) + return false +end +function modifier_bristleback_rage_fortitude_buff.prototype.GetTexture(self) + return "bristleback_warpath" +end +function modifier_bristleback_rage_fortitude_buff.prototype.CheckState(self) + return {[MODIFIER_STATE_DEBUFF_IMMUNE] = true} +end +function modifier_bristleback_rage_fortitude_buff.prototype.DeclareFunctions(self) + return {MODIFIER_PROPERTY_MAGICAL_RESISTANCE_BONUS, MODIFIER_PROPERTY_PHYSICAL_ARMOR_BONUS} +end +function modifier_bristleback_rage_fortitude_buff.prototype.GetModifierMagicalResistanceBonus(self) + return self:GetAbility():GetSpecialValueFor("bonus_magic_resist") +end +function modifier_bristleback_rage_fortitude_buff.prototype.GetModifierPhysicalArmorBonus(self) + return self:GetAbility():GetSpecialValueFor("bonus_armor") +end +function modifier_bristleback_rage_fortitude_buff.prototype.GetEffectName(self) + return WARPATH_ACTIVE_PARTICLE +end +function modifier_bristleback_rage_fortitude_buff.prototype.GetEffectAttachType(self) + return PATTACH_ABSORIGIN_FOLLOW +end +modifier_bristleback_rage_fortitude_buff = __TS__Decorate( + modifier_bristleback_rage_fortitude_buff, + modifier_bristleback_rage_fortitude_buff, + {registerModifier(nil)}, + {kind = "class", name = "modifier_bristleback_rage_fortitude_buff"} +) +____exports.modifier_bristleback_rage_fortitude_buff = modifier_bristleback_rage_fortitude_buff +return ____exports diff --git a/scripts/vscripts/abilities/heroes/bristleback/bristleback_viscous_nasal_goo_custom.lua b/scripts/vscripts/abilities/heroes/bristleback/bristleback_viscous_nasal_goo_custom.lua new file mode 100644 index 0000000..ff58765 --- /dev/null +++ b/scripts/vscripts/abilities/heroes/bristleback/bristleback_viscous_nasal_goo_custom.lua @@ -0,0 +1,289 @@ +local ____lualib = require("lualib_bundle") +local __TS__Class = ____lualib.__TS__Class +local __TS__ClassExtends = ____lualib.__TS__ClassExtends +local __TS__ObjectAssign = ____lualib.__TS__ObjectAssign +local __TS__Decorate = ____lualib.__TS__Decorate +local ____exports = {} +local ____dota_ts_adapter = require("lib.dota_ts_adapter") +local BaseAbility = ____dota_ts_adapter.BaseAbility +local BaseModifier = ____dota_ts_adapter.BaseModifier +local registerAbility = ____dota_ts_adapter.registerAbility +local registerModifier = ____dota_ts_adapter.registerModifier +____exports.bristleback_viscous_nasal_goo_custom = __TS__Class() +local bristleback_viscous_nasal_goo_custom = ____exports.bristleback_viscous_nasal_goo_custom +bristleback_viscous_nasal_goo_custom.name = "bristleback_viscous_nasal_goo_custom" +bristleback_viscous_nasal_goo_custom.____file_path = "scripts/vscripts/abilities/heroes/bristleback/bristleback_viscous_nasal_goo_custom.lua" +__TS__ClassExtends(bristleback_viscous_nasal_goo_custom, BaseAbility) +function bristleback_viscous_nasal_goo_custom.prototype.Precache(self, context) + PrecacheResource("particle", "particles/units/heroes/hero_bristleback/bristleback_viscous_nasal_goo.vpcf", context) + PrecacheResource("particle", "particles/units/heroes/hero_bristleback/bristleback_viscous_nasal_goo_debuff.vpcf", context) + PrecacheResource("particle", "particles/status_fx/status_effect_goo.vpcf", context) + PrecacheResource("particle", "particles/units/heroes/hero_bristleback/bristleback_viscous_nasal_stack.vpcf", context) +end +function bristleback_viscous_nasal_goo_custom.prototype.GetCastRange(self, location, target) + return self:GetSpecialValueFor("AbilityCastRange") +end +function bristleback_viscous_nasal_goo_custom.prototype.GetAOERadius(self) + return self:GetSpecialValueFor("radius") +end +function bristleback_viscous_nasal_goo_custom.prototype.OnSpellStart(self) + local target = self:GetCursorTarget() + if not target then + return + end + self:OnCustomSpellStart( + target:entindex(), + nil + ) +end +function bristleback_viscous_nasal_goo_custom.prototype.OnCustomSpellStart(self, target, location) + local caster = self:GetCaster() + local sourceCaster = caster + local sourceLocation = caster:GetAbsOrigin() + local shardAbility = caster:FindAbilityByName("bristleback_hairball_custom") + local shardRadius = shardAbility and shardAbility:GetSpecialValueFor("radius") or 0 + local isShard = 0 + if location ~= nil then + local shardUnit = CreateUnitByName( + "npc_dota_templar_assassin_psionic_trap", + location, + false, + caster, + caster, + caster:GetTeamNumber() + ) + ____exports.modifier_bristleback_viscous_nasal_goo_shard_unit:apply(shardUnit, nil, self, {duration = 1}) + sourceLocation = shardUnit:GetAbsOrigin() + sourceCaster = shardUnit + isShard = 1 + EmitSoundOn("Hero_Bristleback.ViscousGoo.Cast", shardUnit) + self:LaunchGooAtRadius( + caster, + sourceCaster, + sourceLocation, + shardRadius, + isShard + ) + return + end + if target == nil then + return + end + local cursorTarget = EntIndexToHScript(target) + if not cursorTarget or cursorTarget:IsNull() then + return + end + EmitSoundOn("Hero_Bristleback.ViscousGoo.Cast", caster) + local radius = self:GetSpecialValueFor("radius") + self:LaunchGooAtRadius( + caster, + caster, + caster:GetAbsOrigin(), + radius, + isShard, + cursorTarget:GetAbsOrigin() + ) +end +function bristleback_viscous_nasal_goo_custom.prototype.LaunchGooAtRadius(self, caster, sourceCaster, sourceLocation, radius, isShard, impactLocation) + local origin = impactLocation or sourceLocation + local enemies = FindUnitsInRadius( + caster:GetTeamNumber(), + origin, + nil, + radius, + DOTA_UNIT_TARGET_TEAM_ENEMY, + DOTA_UNIT_TARGET_HERO + DOTA_UNIT_TARGET_BASIC, + DOTA_UNIT_TARGET_FLAG_NONE, + FIND_ANY_ORDER, + false + ) + if #enemies == 0 then + return + end + local projectile = { + Source = sourceCaster, + Ability = self, + iSourceAttachment = DOTA_PROJECTILE_ATTACHMENT_HITLOCATION, + EffectName = "particles/units/heroes/hero_bristleback/bristleback_viscous_nasal_goo.vpcf", + iMoveSpeed = self:GetSpecialValueFor("goo_speed"), + vSourceLoc = sourceLocation, + bDrawsOnMinimap = false, + bDodgeable = true, + bIsAttack = false, + bVisibleToEnemies = true, + bReplaceExisting = false, + flExpireTime = GameRules:GetGameTime() + 10, + bProvidesVision = false, + ExtraData = {is_shard = isShard} + } + for ____, enemy in ipairs(enemies) do + ProjectileManager:CreateTrackingProjectile(__TS__ObjectAssign({}, projectile, {Target = enemy})) + end + if caster:GetUnitName() == "npc_dota_hero_bristleback" then + EmitSoundOn( + "bristleback_bristle_nasal_goo_0" .. tostring(math.random(1, 7)), + caster + ) + end +end +function bristleback_viscous_nasal_goo_custom.prototype.OnProjectileHit_ExtraData(self, target, location, ExtraData) + if target == nil or not target:IsAlive() or target:IsMagicImmune() then + return + end + if ExtraData.is_shard and ExtraData.is_shard == 0 then + if target:TriggerSpellAbsorb(self) then + return + end + end + local duration = self:GetSpecialValueFor("goo_duration") + local modifier = target:FindModifierByName(____exports.modifier_bristleback_viscous_nasal_goo_custom.name) + ____exports.modifier_bristleback_viscous_nasal_goo_custom:apply( + target, + self:GetCaster(), + self, + {duration = duration * (1 - target:GetStatusResistance())} + ):SetStackCount((modifier and modifier:GetStackCount() or 0) + 1) + EmitSoundOn("Hero_Bristleback.ViscousGoo.Target", target) +end +bristleback_viscous_nasal_goo_custom = __TS__Decorate( + bristleback_viscous_nasal_goo_custom, + bristleback_viscous_nasal_goo_custom, + {registerAbility(nil)}, + {kind = "class", name = "bristleback_viscous_nasal_goo_custom"} +) +____exports.bristleback_viscous_nasal_goo_custom = bristleback_viscous_nasal_goo_custom +____exports.modifier_bristleback_viscous_nasal_goo_custom = __TS__Class() +local modifier_bristleback_viscous_nasal_goo_custom = ____exports.modifier_bristleback_viscous_nasal_goo_custom +modifier_bristleback_viscous_nasal_goo_custom.name = "modifier_bristleback_viscous_nasal_goo_custom" +modifier_bristleback_viscous_nasal_goo_custom.____file_path = "scripts/vscripts/abilities/heroes/bristleback/bristleback_viscous_nasal_goo_custom.lua" +__TS__ClassExtends(modifier_bristleback_viscous_nasal_goo_custom, BaseModifier) +function modifier_bristleback_viscous_nasal_goo_custom.prototype.____constructor(self, ...) + BaseModifier.prototype.____constructor(self, ...) + self.parent = self:GetParent() + self.movespeed_slow = 0 + self.armor_reduce_pct = 0 + self.armor_reduce_pct_per_stack = 0 + self.movespeed_slow_per_stack = 0 + self.ability = self:GetAbility() +end +function modifier_bristleback_viscous_nasal_goo_custom.prototype.IsHidden(self) + return false +end +function modifier_bristleback_viscous_nasal_goo_custom.prototype.IsPurgable(self) + return true +end +function modifier_bristleback_viscous_nasal_goo_custom.prototype.GetEffectName(self) + return "particles/units/heroes/hero_bristleback/bristleback_viscous_nasal_goo_debuff.vpcf" +end +function modifier_bristleback_viscous_nasal_goo_custom.prototype.GetStatusEffectName(self) + return "particles/status_fx/status_effect_goo.vpcf" +end +function modifier_bristleback_viscous_nasal_goo_custom.prototype.StatusEffectPriority(self) + return MODIFIER_PRIORITY_NORMAL +end +function modifier_bristleback_viscous_nasal_goo_custom.prototype.OnCreated(self, params) + self.movespeed_slow = self.ability:GetSpecialValueFor("base_move_slow") + self.armor_reduce_pct = self.ability:GetSpecialValueFor("base_armor_pct") + self.armor_reduce_pct_per_stack = self.ability:GetSpecialValueFor("armor_per_stack_pct") + self.movespeed_slow_per_stack = self.ability:GetSpecialValueFor("move_slow_per_stack") + self.particle = ParticleManager:CreateParticle("particles/units/heroes/hero_bristleback/bristleback_viscous_nasal_stack.vpcf", PATTACH_OVERHEAD_FOLLOW, self.parent) + ParticleManager:SetParticleControl( + self.particle, + 1, + Vector( + 0, + self:GetStackCount(), + 0 + ) + ) + self:AddParticle( + self.particle, + false, + false, + -1, + false, + false + ) + self.isBoss = self.parent:IsBossCreature() + self:SetHasCustomTransmitterData(true) +end +function modifier_bristleback_viscous_nasal_goo_custom.prototype.AddCustomTransmitterData(self) + return {isBoss = self.isBoss, movespeed_total = self.movespeed_total} +end +function modifier_bristleback_viscous_nasal_goo_custom.prototype.HandleCustomTransmitterData(self, data) + self.isBoss = data.isBoss + self.movespeed_total = data.movespeed_total +end +function modifier_bristleback_viscous_nasal_goo_custom.prototype.OnStackCountChanged(self, stackCount) + if self.isBoss then + self.movespeed_total = 0 + else + self.movespeed_total = (self.movespeed_slow + self.movespeed_slow_per_stack * self:GetStackCount()) * -1 + end + if self.particle ~= nil then + ParticleManager:SetParticleControl( + self.particle, + 1, + Vector( + 0, + self:GetStackCount(), + 0 + ) + ) + end +end +function modifier_bristleback_viscous_nasal_goo_custom.prototype.DeclareFunctions(self) + return {MODIFIER_PROPERTY_MOVESPEED_BONUS_PERCENTAGE, MODIFIER_PROPERTY_PHYSICAL_ARMOR_BONUS} +end +function modifier_bristleback_viscous_nasal_goo_custom.prototype.GetModifierMoveSpeedBonus_Percentage(self) + return self.movespeed_total +end +function modifier_bristleback_viscous_nasal_goo_custom.prototype.GetModifierPhysicalArmorBonus(self, event) + local baseArmor = self.parent:GetPhysicalArmorBaseValue() + if baseArmor <= 0 then + return 0 + end + local totalPct = (self.armor_reduce_pct + self.armor_reduce_pct_per_stack * self:GetStackCount()) / 100 + return -baseArmor * totalPct +end +modifier_bristleback_viscous_nasal_goo_custom = __TS__Decorate( + modifier_bristleback_viscous_nasal_goo_custom, + modifier_bristleback_viscous_nasal_goo_custom, + {registerModifier(nil)}, + {kind = "class", name = "modifier_bristleback_viscous_nasal_goo_custom"} +) +____exports.modifier_bristleback_viscous_nasal_goo_custom = modifier_bristleback_viscous_nasal_goo_custom +____exports.modifier_bristleback_viscous_nasal_goo_shard_unit = __TS__Class() +local modifier_bristleback_viscous_nasal_goo_shard_unit = ____exports.modifier_bristleback_viscous_nasal_goo_shard_unit +modifier_bristleback_viscous_nasal_goo_shard_unit.name = "modifier_bristleback_viscous_nasal_goo_shard_unit" +modifier_bristleback_viscous_nasal_goo_shard_unit.____file_path = "scripts/vscripts/abilities/heroes/bristleback/bristleback_viscous_nasal_goo_custom.lua" +__TS__ClassExtends(modifier_bristleback_viscous_nasal_goo_shard_unit, BaseModifier) +function modifier_bristleback_viscous_nasal_goo_shard_unit.prototype.IsHidden(self) + return true +end +function modifier_bristleback_viscous_nasal_goo_shard_unit.prototype.IsPurgable(self) + return false +end +function modifier_bristleback_viscous_nasal_goo_shard_unit.prototype.CheckState(self) + return { + [MODIFIER_STATE_INVULNERABLE] = true, + [MODIFIER_STATE_OUT_OF_GAME] = true, + [MODIFIER_STATE_STUNNED] = true, + [MODIFIER_STATE_NO_HEALTH_BAR] = true, + [MODIFIER_STATE_NO_UNIT_COLLISION] = true + } +end +function modifier_bristleback_viscous_nasal_goo_shard_unit.prototype.OnCreated(self, params) + self:GetParent():AddNoDraw() +end +function modifier_bristleback_viscous_nasal_goo_shard_unit.prototype.OnDestroy(self) + UTIL_Remove(self:GetParent()) +end +modifier_bristleback_viscous_nasal_goo_shard_unit = __TS__Decorate( + modifier_bristleback_viscous_nasal_goo_shard_unit, + modifier_bristleback_viscous_nasal_goo_shard_unit, + {registerModifier(nil)}, + {kind = "class", name = "modifier_bristleback_viscous_nasal_goo_shard_unit"} +) +____exports.modifier_bristleback_viscous_nasal_goo_shard_unit = modifier_bristleback_viscous_nasal_goo_shard_unit +return ____exports diff --git a/scripts/vscripts/abilities/heroes/bristleback/bristleback_warpath_custom.lua b/scripts/vscripts/abilities/heroes/bristleback/bristleback_warpath_custom.lua new file mode 100644 index 0000000..a1194e8 --- /dev/null +++ b/scripts/vscripts/abilities/heroes/bristleback/bristleback_warpath_custom.lua @@ -0,0 +1,181 @@ +local ____lualib = require("lualib_bundle") +local __TS__Class = ____lualib.__TS__Class +local __TS__ClassExtends = ____lualib.__TS__ClassExtends +local __TS__Decorate = ____lualib.__TS__Decorate +local ____exports = {} +local ____dota_ts_adapter = require("lib.dota_ts_adapter") +local BaseAbility = ____dota_ts_adapter.BaseAbility +local BaseModifier = ____dota_ts_adapter.BaseModifier +local registerAbility = ____dota_ts_adapter.registerAbility +local registerModifier = ____dota_ts_adapter.registerModifier +--- Значения Warpath от уровня героя (не от уровня способности). +local function getWarpathLevel(self, hero) + return math.max( + 1, + hero:GetLevel() + ) +end +local function getWarpathDamagePerStack(self, ability, hero) + local level = getWarpathLevel(nil, hero) + return ability:GetSpecialValueFor("damage_per_stack_base") + level * ability:GetSpecialValueFor("damage_per_stack_per_hero_level") + ability:GetSpecialValueFor("damage_per_stack") +end +local function getWarpathMoveSpeedPerStack(self, ability, hero) + local level = getWarpathLevel(nil, hero) + return ability:GetSpecialValueFor("move_speed_per_stack_base") + level * ability:GetSpecialValueFor("move_speed_per_stack_per_hero_level") +end +local function getWarpathMaxStacks(self, ability, hero) + local level = getWarpathLevel(nil, hero) + return math.floor(ability:GetSpecialValueFor("max_stacks_base") + level * ability:GetSpecialValueFor("max_stacks_per_hero_level")) +end +local function getWarpathStackDuration(self, ability, hero) + local level = getWarpathLevel(nil, hero) + return ability:GetSpecialValueFor("stack_duration_base") + level * ability:GetSpecialValueFor("stack_duration_per_hero_level") +end +____exports.bristleback_warpath = __TS__Class() +local bristleback_warpath = ____exports.bristleback_warpath +bristleback_warpath.name = "bristleback_warpath" +bristleback_warpath.____file_path = "scripts/vscripts/abilities/heroes/bristleback/bristleback_warpath_custom.lua" +__TS__ClassExtends(bristleback_warpath, BaseAbility) +function bristleback_warpath.prototype.GetIntrinsicModifierName(self) + return ____exports.modifier_bristleback_warpath_custom.name +end +bristleback_warpath = __TS__Decorate( + bristleback_warpath, + bristleback_warpath, + {registerAbility(nil)}, + {kind = "class", name = "bristleback_warpath"} +) +____exports.bristleback_warpath = bristleback_warpath +____exports.modifier_bristleback_warpath_custom = __TS__Class() +local modifier_bristleback_warpath_custom = ____exports.modifier_bristleback_warpath_custom +modifier_bristleback_warpath_custom.name = "modifier_bristleback_warpath_custom" +modifier_bristleback_warpath_custom.____file_path = "scripts/vscripts/abilities/heroes/bristleback/bristleback_warpath_custom.lua" +__TS__ClassExtends(modifier_bristleback_warpath_custom, BaseModifier) +function modifier_bristleback_warpath_custom.prototype.IsHidden(self) + return true +end +function modifier_bristleback_warpath_custom.prototype.IsPurgable(self) + return false +end +function modifier_bristleback_warpath_custom.prototype.RemoveOnDeath(self) + return false +end +function modifier_bristleback_warpath_custom.prototype.DeclareFunctions(self) + return {MODIFIER_EVENT_ON_TAKEDAMAGE} +end +function modifier_bristleback_warpath_custom.prototype.OnCreated(self) + if not IsServer() then + return + end + self.parentHero = self:GetParent() +end +function modifier_bristleback_warpath_custom.prototype.OnTakeDamage(self, event) + if not IsServer() then + return + end + if event.unit ~= self.parentHero then + return + end + if event.damage <= 0 then + return + end + if self.parentHero:PassivesDisabled() then + return + end + local ability = self:GetAbility() + if not ability or ability:IsNull() then + return + end + local stacksMod = self.parentHero:FindModifierByName(____exports.modifier_bristleback_warpath_stacks.name) + local maxStacks = getWarpathMaxStacks(nil, ability, self.parentHero) + local duration = getWarpathStackDuration(nil, ability, self.parentHero) + if not stacksMod then + self.parentHero:AddNewModifier(self.parentHero, ability, ____exports.modifier_bristleback_warpath_stacks.name, {duration = duration}) + return + end + local nextStacks = math.min( + maxStacks, + stacksMod:GetStackCount() + 1 + ) + stacksMod:SetStackCount(nextStacks) + stacksMod:SetDuration(duration, true) + stacksMod:ForceRefresh() +end +modifier_bristleback_warpath_custom = __TS__Decorate( + modifier_bristleback_warpath_custom, + modifier_bristleback_warpath_custom, + {registerModifier(nil)}, + {kind = "class", name = "modifier_bristleback_warpath_custom"} +) +____exports.modifier_bristleback_warpath_custom = modifier_bristleback_warpath_custom +____exports.modifier_bristleback_warpath_stacks = __TS__Class() +local modifier_bristleback_warpath_stacks = ____exports.modifier_bristleback_warpath_stacks +modifier_bristleback_warpath_stacks.name = "modifier_bristleback_warpath_stacks" +modifier_bristleback_warpath_stacks.____file_path = "scripts/vscripts/abilities/heroes/bristleback/bristleback_warpath_custom.lua" +__TS__ClassExtends(modifier_bristleback_warpath_stacks, BaseModifier) +function modifier_bristleback_warpath_stacks.prototype.IsHidden(self) + return false +end +function modifier_bristleback_warpath_stacks.prototype.IsPurgable(self) + return false +end +function modifier_bristleback_warpath_stacks.prototype.IsDebuff(self) + return false +end +function modifier_bristleback_warpath_stacks.prototype.GetTexture(self) + return "bristleback_warpath" +end +function modifier_bristleback_warpath_stacks.prototype.OnCreated(self) + if not IsServer() then + return + end + self:SetStackCount(1) +end +function modifier_bristleback_warpath_stacks.prototype.OnRefresh(self) + if not IsServer() then + return + end + self:SetStackCount(math.max( + 1, + self:GetStackCount() + )) +end +function modifier_bristleback_warpath_stacks.prototype.DeclareFunctions(self) + return {MODIFIER_PROPERTY_PREATTACK_BONUS_DAMAGE, MODIFIER_PROPERTY_MOVESPEED_BONUS_CONSTANT} +end +function modifier_bristleback_warpath_stacks.prototype.GetModifierPreAttack_BonusDamage(self) + if self:GetParent():PassivesDisabled() then + return 0 + end + local ability = self:GetAbility() + if not ability then + return 0 + end + return self:GetStackCount() * getWarpathDamagePerStack( + nil, + ability, + self:GetParent() + ) +end +function modifier_bristleback_warpath_stacks.prototype.GetModifierMoveSpeedBonus_Constant(self) + if self:GetParent():PassivesDisabled() then + return 0 + end + local ability = self:GetAbility() + if not ability then + return 0 + end + return self:GetStackCount() * getWarpathMoveSpeedPerStack( + nil, + ability, + self:GetParent() + ) +end +modifier_bristleback_warpath_stacks = __TS__Decorate( + modifier_bristleback_warpath_stacks, + modifier_bristleback_warpath_stacks, + {registerModifier(nil)}, + {kind = "class", name = "modifier_bristleback_warpath_stacks"} +) +____exports.modifier_bristleback_warpath_stacks = modifier_bristleback_warpath_stacks +return ____exports diff --git a/scripts/vscripts/abilities/heroes/crystal_maiden/ability_crystal_maiden_brilliance_aura_custom.lua b/scripts/vscripts/abilities/heroes/crystal_maiden/ability_crystal_maiden_brilliance_aura_custom.lua new file mode 100644 index 0000000..4cc1bf2 --- /dev/null +++ b/scripts/vscripts/abilities/heroes/crystal_maiden/ability_crystal_maiden_brilliance_aura_custom.lua @@ -0,0 +1,687 @@ +local ____lualib = require("lualib_bundle") +local __TS__Class = ____lualib.__TS__Class +local __TS__ClassExtends = ____lualib.__TS__ClassExtends +local __TS__Decorate = ____lualib.__TS__Decorate +local __TS__ArrayForEach = ____lualib.__TS__ArrayForEach +local Set = ____lualib.Set +local __TS__New = ____lualib.__TS__New +local __TS__ArraySplice = ____lualib.__TS__ArraySplice +local ____exports = {} +local ____dota_ts_adapter = require("lib.dota_ts_adapter") +local BaseAbility = ____dota_ts_adapter.BaseAbility +local BaseModifier = ____dota_ts_adapter.BaseModifier +local registerAbility = ____dota_ts_adapter.registerAbility +local registerModifier = ____dota_ts_adapter.registerModifier +local ____modifier_general_froze = require("abilities.modifiers.modifier_general_froze") +local modifier_general_froze = ____modifier_general_froze.modifier_general_froze +____exports.ability_crystal_maiden_brilliance_aura_custom = __TS__Class() +local ability_crystal_maiden_brilliance_aura_custom = ____exports.ability_crystal_maiden_brilliance_aura_custom +ability_crystal_maiden_brilliance_aura_custom.name = "ability_crystal_maiden_brilliance_aura_custom" +ability_crystal_maiden_brilliance_aura_custom.____file_path = "scripts/vscripts/abilities/heroes/crystal_maiden/ability_crystal_maiden_brilliance_aura_custom.lua" +__TS__ClassExtends(ability_crystal_maiden_brilliance_aura_custom, BaseAbility) +function ability_crystal_maiden_brilliance_aura_custom.prototype.GetIntrinsicModifierName(self) + return "modifier_crystal_maiden_brilliance_aura_custom" +end +function ability_crystal_maiden_brilliance_aura_custom.prototype.GetAOERadius(self) + return self:GetSpecialValueFor("active_radius") +end +function ability_crystal_maiden_brilliance_aura_custom.prototype.GetManaCost(self, level) + local ____opt_0 = self:GetCaster() + return (____opt_0 and ____opt_0:GetMana()) * (0.01 * self:GetSpecialValueFor("mana_cost_pct")) +end +function ability_crystal_maiden_brilliance_aura_custom.prototype.GetCastRange(self, location, target) + if self:IsAltCastAbility() then + return self:GetSpecialValueFor("teleport_range") + end + return 0 +end +function ability_crystal_maiden_brilliance_aura_custom.prototype.OnSpellStart(self) + if not IsServer() then + return + end + local caster = self:GetCaster() + local point = self:GetCursorPosition() + local isAltCast = self:IsAltCastAbility() + if isAltCast then + local teleportRange = self:GetSpecialValueFor("teleport_range") + local casterPos = caster:GetAbsOrigin() + local dx = point.x - casterPos.x + local dy = point.y - casterPos.y + local distance = math.sqrt(dx * dx + dy * dy) + local targetPos + if distance > teleportRange then + local normalizedX = dx / distance + local normalizedY = dy / distance + targetPos = Vector(casterPos.x + normalizedX * teleportRange, casterPos.y + normalizedY * teleportRange, casterPos.z) + else + targetPos = point + end + caster:Stop() + caster:StartGesture(ACT_DOTA_CAST_ABILITY_5) + FindClearSpaceForUnit(caster, targetPos, false) + local activeModifier = caster:FindModifierByName("modifier_crystal_maiden_brilliance_active") + if activeModifier then + activeModifier:SetDuration( + self:GetSpecialValueFor("duration"), + true + ) + else + caster:AddNewModifier( + caster, + self, + "modifier_crystal_maiden_brilliance_active", + {duration = self:GetSpecialValueFor("duration")} + ) + end + else + local activeModifier = caster:FindModifierByName("modifier_crystal_maiden_brilliance_active") + if activeModifier then + activeModifier:SetDuration( + self:GetSpecialValueFor("duration"), + true + ) + else + caster:AddNewModifier( + caster, + self, + "modifier_crystal_maiden_brilliance_active", + {duration = self:GetSpecialValueFor("duration")} + ) + end + end +end +ability_crystal_maiden_brilliance_aura_custom = __TS__Decorate( + ability_crystal_maiden_brilliance_aura_custom, + ability_crystal_maiden_brilliance_aura_custom, + {registerAbility(nil)}, + {kind = "class", name = "ability_crystal_maiden_brilliance_aura_custom"} +) +____exports.ability_crystal_maiden_brilliance_aura_custom = ability_crystal_maiden_brilliance_aura_custom +____exports.modifier_crystal_maiden_brilliance_active = __TS__Class() +local modifier_crystal_maiden_brilliance_active = ____exports.modifier_crystal_maiden_brilliance_active +modifier_crystal_maiden_brilliance_active.name = "modifier_crystal_maiden_brilliance_active" +modifier_crystal_maiden_brilliance_active.____file_path = "scripts/vscripts/abilities/heroes/crystal_maiden/ability_crystal_maiden_brilliance_aura_custom.lua" +__TS__ClassExtends(modifier_crystal_maiden_brilliance_active, BaseModifier) +function modifier_crystal_maiden_brilliance_active.prototype.IsHidden(self) + return false +end +function modifier_crystal_maiden_brilliance_active.prototype.DeclareFunctions(self) + return {MODIFIER_EVENT_ON_ABILITY_FULLY_CAST} +end +function modifier_crystal_maiden_brilliance_active.prototype.OnAbilityFullyCast(self, event) + if not IsServer() then + return + end + if event.unit ~= self:GetParent() then + return + end + if event.ability:IsItem() then + return + end + local caster = self:GetParent() + local ability = self:GetAbility() + if not ability then + return + end + local hasCrystalAspect = ability:GetSpecialValueFor("crystal_aspect_enabled") > 0 + if hasCrystalAspect then + local crystalDuration = ability:GetSpecialValueFor("crystal_duration") + local crystalModifier = caster:FindModifierByName("modifier_crystal_maiden_brilliance_crystals") + if crystalModifier then + local currentStacks = crystalModifier:GetStackCount() + local maxStacks = 5 + if currentStacks < maxStacks then + crystalModifier:IncrementStackCount() + crystalModifier:SyncCrystalsWithStacks() + end + crystalModifier:SetDuration(crystalDuration, true) + else + crystalModifier = caster:AddNewModifier(caster, ability, "modifier_crystal_maiden_brilliance_crystals", {duration = crystalDuration}) + if crystalModifier then + crystalModifier:IncrementStackCount() + crystalModifier:SyncCrystalsWithStacks() + end + end + end + local radius = ability:GetSpecialValueFor("active_radius") + local damage = ability:GetSpecialValueFor("damage") + local frost_stacks = ability:GetSpecialValueFor("frost_stacks_per_level") + local intellect_per_damage = ability:GetSpecialValueFor("intellect_per_damage") + EmitSoundOn("Hero_Crystal.CrystalNova", caster) + local particleEffect = ParticleManager:CreateParticle("particles/econ/items/queen_of_pain/qop_2022_immortal/queen_2022_scream_of_pain_owner_blue.vpcf", PATTACH_WORLDORIGIN, nil) + ParticleManager:SetParticleControl( + particleEffect, + 0, + caster:GetAbsOrigin() + ) + ParticleManager:SetParticleControl( + particleEffect, + 1, + Vector(radius, 1, 1) + ) + ParticleManager:SetParticleControl( + particleEffect, + 2, + caster:GetAbsOrigin() + ) + ParticleManager:ReleaseParticleIndex(particleEffect) + local enemies = FindUnitsInRadius( + caster:GetTeamNumber(), + caster:GetAbsOrigin(), + nil, + radius, + DOTA_UNIT_TARGET_TEAM_ENEMY, + bit.bor(DOTA_UNIT_TARGET_HERO, DOTA_UNIT_TARGET_BASIC), + DOTA_UNIT_TARGET_FLAG_NONE, + FIND_ANY_ORDER, + false + ) + __TS__ArrayForEach( + enemies, + function(____, enemy) + if not enemy or enemy:IsNull() or not enemy:IsAlive() then + return + end + local ____ApplyDamage_8 = ApplyDamage + local ____enemy_7 = enemy + local ____opt_2 = self:GetCaster() + local ____temp_6 = ____opt_2 and ____opt_2:GetMaxMana() + local ____opt_4 = self:GetCaster() + ____ApplyDamage_8({ + victim = ____enemy_7, + attacker = caster, + damage = damage + (____temp_6 - (____opt_4 and ____opt_4:GetMana())), + damage_type = DAMAGE_TYPE_MAGICAL, + ability = ability + }) + local frostModifier = enemy:AddNewModifier(caster, ability, modifier_general_froze.name, {}) + if frostModifier then + frostModifier:SetDuration( + ability:GetSpecialValueFor("slow_duration"), + true + ) + local stacksToAdd = frost_stacks + getLuck(nil, caster) * 0.1 + do + local i = 0 + while i < stacksToAdd do + frostModifier:IncrementStackCount() + i = i + 1 + end + end + end + end + ) +end +modifier_crystal_maiden_brilliance_active = __TS__Decorate( + modifier_crystal_maiden_brilliance_active, + modifier_crystal_maiden_brilliance_active, + {registerModifier(nil)}, + {kind = "class", name = "modifier_crystal_maiden_brilliance_active"} +) +____exports.modifier_crystal_maiden_brilliance_active = modifier_crystal_maiden_brilliance_active +____exports.modifier_crystal_maiden_brilliance_crystals = __TS__Class() +local modifier_crystal_maiden_brilliance_crystals = ____exports.modifier_crystal_maiden_brilliance_crystals +modifier_crystal_maiden_brilliance_crystals.name = "modifier_crystal_maiden_brilliance_crystals" +modifier_crystal_maiden_brilliance_crystals.____file_path = "scripts/vscripts/abilities/heroes/crystal_maiden/ability_crystal_maiden_brilliance_aura_custom.lua" +__TS__ClassExtends(modifier_crystal_maiden_brilliance_crystals, BaseModifier) +function modifier_crystal_maiden_brilliance_crystals.prototype.____constructor(self, ...) + BaseModifier.prototype.____constructor(self, ...) + self.crystals = {} + self.maxCrystals = self:GetAbility():GetSpecialValueFor("max_crystals") + self.rotationRadius = 150 + self.rotationSpeed = 12000 +end +function modifier_crystal_maiden_brilliance_crystals.prototype.IsHidden(self) + return false +end +function modifier_crystal_maiden_brilliance_crystals.prototype.IsPurgable(self) + return false +end +function modifier_crystal_maiden_brilliance_crystals.prototype.OnCreated(self) + if not IsServer() then + return + end + self:StartRotation() + local initialStacks = self:GetStackCount() + if initialStacks > 0 then + do + local i = 0 + while i < initialStacks do + self:AddCrystal() + i = i + 1 + end + end + end +end +function modifier_crystal_maiden_brilliance_crystals.prototype.SyncCrystalsWithStacks(self) + if not IsServer() then + return + end + local currentStacks = self:GetStackCount() + while #self.crystals < currentStacks and #self.crystals < self.maxCrystals do + self:AddCrystal() + end + while #self.crystals > currentStacks do + self:RemoveCrystal(#self.crystals - 1) + end + self:RecalculateCrystalAngles() +end +function modifier_crystal_maiden_brilliance_crystals.prototype.OnRefresh(self) + if not IsServer() then + return + end + self:SyncCrystalsWithStacks() +end +function modifier_crystal_maiden_brilliance_crystals.prototype.OnDestroy(self) + if IsServer() then + self:StopRotation() + __TS__ArrayForEach( + self.crystals, + function(____, crystal) + if crystal.particle ~= nil then + ParticleManager:DestroyParticle(crystal.particle, false) + ParticleManager:ReleaseParticleIndex(crystal.particle) + end + if crystal.radiusParticle ~= nil then + ParticleManager:DestroyParticle(crystal.radiusParticle, false) + ParticleManager:ReleaseParticleIndex(crystal.radiusParticle) + end + end + ) + self.crystals = {} + if self.lastDamageTime then + self.lastDamageTime:clear() + end + end +end +function modifier_crystal_maiden_brilliance_crystals.prototype.RecalculateCrystalAngles(self) + if not IsServer() then + return + end + local parent = self:GetParent() + if not parent or parent:IsNull() then + return + end + local parentPos = parent:GetAbsOrigin() + local totalCrystals = #self.crystals + if totalCrystals == 0 then + return + end + local angleStep = 360 / totalCrystals + __TS__ArrayForEach( + self.crystals, + function(____, crystal, index) + local newAngle = index * angleStep + crystal.angle = newAngle + local radians = newAngle * math.pi / 180 + local newPos = Vector( + parentPos.x + math.cos(radians) * self.rotationRadius, + parentPos.y + math.sin(radians) * self.rotationRadius, + parentPos.z + 50 + ) + crystal.position = newPos + if crystal.particle ~= nil then + ParticleManager:SetParticleControl(crystal.particle, 0, newPos) + end + if crystal.radiusParticle ~= nil then + local radiusPos = Vector(newPos.x, newPos.y, newPos.z - 10) + ParticleManager:SetParticleControl(crystal.radiusParticle, 0, radiusPos) + end + end + ) +end +function modifier_crystal_maiden_brilliance_crystals.prototype.AddCrystal(self) + if not IsServer() then + return + end + if #self.crystals >= self.maxCrystals then + return + end + local parent = self:GetParent() + if not parent or parent:IsNull() then + return + end + local parentPos = parent:GetAbsOrigin() + local totalCrystals = #self.crystals + 1 + local angleStep = 360 / totalCrystals + __TS__ArrayForEach( + self.crystals, + function(____, crystal, index) + local newAngle = index * angleStep + crystal.angle = newAngle + local radians = newAngle * math.pi / 180 + local newPos = Vector( + parentPos.x + math.cos(radians) * self.rotationRadius, + parentPos.y + math.sin(radians) * self.rotationRadius, + parentPos.z + 50 + ) + crystal.position = newPos + if crystal.particle ~= nil then + ParticleManager:SetParticleControl(crystal.particle, 0, newPos) + end + end + ) + local newCrystalIndex = #self.crystals + local startAngle = newCrystalIndex * angleStep + local radians = startAngle * math.pi / 180 + local initialPos = Vector( + parentPos.x + math.cos(radians) * self.rotationRadius, + parentPos.y + math.sin(radians) * self.rotationRadius, + parentPos.z + 50 + ) + local particle = ParticleManager:CreateParticle("particles/crystal_maiden_aspect_3.vpcf", PATTACH_CUSTOMORIGIN, nil) + if not particle then + return + end + ParticleManager:SetParticleControl(particle, 0, initialPos) + ParticleManager:SetParticleControl( + particle, + 3, + Vector(100, 0, 0) + ) + self:AddParticle( + particle, + false, + false, + -1, + false, + false + ) + local ability = self:GetAbility() + local damageRadius = ability and (ability:GetSpecialValueFor("crystal_explosion_radius") or 100) or 100 + local radiusParticle = ParticleManager:CreateParticle("particles/units/heroes/hero_crystalmaiden/maiden_crystal_nova.vpcf", PATTACH_CUSTOMORIGIN, nil) + if radiusParticle ~= nil then + local radiusPos = Vector(initialPos.x, initialPos.y, initialPos.z - 10) + ParticleManager:SetParticleControl(radiusParticle, 0, radiusPos) + ParticleManager:SetParticleControl( + radiusParticle, + 1, + Vector(damageRadius, 1, damageRadius) + ) + self:AddParticle( + radiusParticle, + false, + false, + -1, + false, + false + ) + end + local ____self_crystals_9 = self.crystals + ____self_crystals_9[#____self_crystals_9 + 1] = { + particle = particle, + radiusParticle = radiusParticle, + angle = startAngle, + position = initialPos, + hitEnemies = __TS__New(Set) + } + EmitSoundOn("Hero_Crystal.FreezingField.Explosion", parent) +end +function modifier_crystal_maiden_brilliance_crystals.prototype.StartRotation(self) + if not IsServer() then + return + end + if self.rotationTimer then + return + end + local modifier = self + local parent = self:GetParent() + local updateRotation + updateRotation = function() + if not modifier or not parent or parent:IsNull() then + modifier.rotationTimer = nil + return + end + if not modifier.crystals or #modifier.crystals == 0 then + modifier.rotationTimer = nil + return + end + local parentPos = parent:GetAbsOrigin() + local deltaTime = 0.00033 + local angleDelta = modifier.rotationSpeed * deltaTime + local ability = modifier:GetAbility() + local caster = modifier:GetCaster() + if not ability or not caster then + modifier.rotationTimer = nil + return + end + local damageRadius = ability:GetSpecialValueFor("crystal_explosion_radius") + local crystalDamage = ability:GetSpecialValueFor("crystal_damage") + local crystalFrostStacks = ability:GetSpecialValueFor("crystal_frost_stacks") + local touchRadius = 90 + __TS__ArrayForEach( + modifier.crystals, + function(____, crystal, index) + crystal.angle = crystal.angle + angleDelta + if crystal.angle >= 360 then + crystal.angle = crystal.angle - 360 + end + local radians = crystal.angle * math.pi / 180 + local newPos = Vector( + parentPos.x + math.cos(radians) * modifier.rotationRadius, + parentPos.y + math.sin(radians) * modifier.rotationRadius, + parentPos.z + 50 + ) + crystal.position = newPos + if crystal.particle ~= nil then + ParticleManager:SetParticleControl(crystal.particle, 0, newPos) + else + return + end + if crystal.radiusParticle ~= nil then + local radiusPos = Vector(newPos.x, newPos.y, newPos.z - 10) + ParticleManager:SetParticleControl(crystal.radiusParticle, 0, radiusPos) + end + local enemiesInRadius = FindUnitsInRadius( + caster:GetTeamNumber(), + newPos, + nil, + touchRadius, + DOTA_UNIT_TARGET_TEAM_ENEMY, + bit.bor(DOTA_UNIT_TARGET_HERO, DOTA_UNIT_TARGET_BASIC), + DOTA_UNIT_TARGET_FLAG_NONE, + FIND_ANY_ORDER, + false + ) + local currentEnemiesInRadius = __TS__New(Set) + __TS__ArrayForEach( + enemiesInRadius, + function(____, enemy) + if not enemy or enemy:IsNull() or not enemy:IsAlive() then + return + end + local enemyPos = enemy:GetAbsOrigin() + local distance = (newPos - enemyPos):Length2D() + if distance > touchRadius then + return + end + local enemyIndex = enemy:GetEntityIndex() + currentEnemiesInRadius:add(enemyIndex) + if not crystal.hitEnemies:has(enemyIndex) then + local ____ApplyDamage_17 = ApplyDamage + local ____enemy_16 = enemy + local ____temp_15 = modifier:GetAbility():GetSpecialValueFor("pct_25_mana_damage") * 0.01 + local ____opt_10 = modifier:GetParent() + local ____temp_14 = ____opt_10 and ____opt_10:GetMaxMana() + local ____opt_12 = modifier:GetParent() + ____ApplyDamage_17({ + victim = ____enemy_16, + attacker = caster, + damage = crystalDamage + ____temp_15 * (____temp_14 - (____opt_12 and ____opt_12:GetMana())), + damage_type = DAMAGE_TYPE_MAGICAL, + ability = ability + }) + local frostModifier = enemy:FindModifierByName(modifier_general_froze.name) + if not frostModifier then + frostModifier = enemy:AddNewModifier(caster, ability, modifier_general_froze.name, {}) + end + if frostModifier then + frostModifier:SetDuration(1, true) + do + local i = 0 + while i < crystalFrostStacks do + frostModifier:IncrementStackCount() + i = i + 1 + end + end + end + crystal.hitEnemies:add(enemyIndex) + end + end + ) + crystal.hitEnemies:forEach(function(____, enemyIndex) + if not currentEnemiesInRadius:has(enemyIndex) then + crystal.hitEnemies:delete(enemyIndex) + end + end) + end + ) + Timers:CreateTimer(deltaTime, updateRotation) + end + modifier.rotationTimer = Timers:CreateTimer(0.00033, updateRotation) +end +function modifier_crystal_maiden_brilliance_crystals.prototype.StopRotation(self) + if not IsServer() then + return + end + if self.rotationTimer ~= nil and self.rotationTimer ~= nil then + do + pcall(function() + Timers:RemoveTimer(self.rotationTimer) + end) + end + self.rotationTimer = nil + end +end +function modifier_crystal_maiden_brilliance_crystals.prototype.RemoveCrystal(self, index) + if not IsServer() then + return + end + if index < 0 or index >= #self.crystals then + return + end + local crystal = self.crystals[index + 1] + if crystal.particle ~= nil then + ParticleManager:DestroyParticle(crystal.particle, false) + ParticleManager:ReleaseParticleIndex(crystal.particle) + end + if crystal.radiusParticle ~= nil then + ParticleManager:DestroyParticle(crystal.radiusParticle, false) + ParticleManager:ReleaseParticleIndex(crystal.radiusParticle) + end + __TS__ArraySplice(self.crystals, index, 1) + local parent = self:GetParent() + if parent and not parent:IsNull() and #self.crystals > 0 then + local parentPos = parent:GetAbsOrigin() + local totalCrystals = #self.crystals + local angleStep = 360 / totalCrystals + __TS__ArrayForEach( + self.crystals, + function(____, crystal, newIndex) + local newAngle = newIndex * angleStep + crystal.angle = newAngle + local radians = newAngle * math.pi / 180 + local newPos = Vector( + parentPos.x + math.cos(radians) * self.rotationRadius, + parentPos.y + math.sin(radians) * self.rotationRadius, + parentPos.z + 50 + ) + crystal.position = newPos + if crystal.particle ~= nil then + ParticleManager:SetParticleControl(crystal.particle, 0, newPos) + end + if crystal.radiusParticle ~= nil then + local radiusPos = Vector(newPos.x, newPos.y, newPos.z - 10) + ParticleManager:SetParticleControl(crystal.radiusParticle, 0, radiusPos) + end + end + ) + end +end +function modifier_crystal_maiden_brilliance_crystals.prototype.GetCrystalCount(self) + return #self.crystals +end +modifier_crystal_maiden_brilliance_crystals = __TS__Decorate( + modifier_crystal_maiden_brilliance_crystals, + modifier_crystal_maiden_brilliance_crystals, + {registerModifier(nil)}, + {kind = "class", name = "modifier_crystal_maiden_brilliance_crystals"} +) +____exports.modifier_crystal_maiden_brilliance_crystals = modifier_crystal_maiden_brilliance_crystals +____exports.modifier_crystal_maiden_brilliance_aura_custom = __TS__Class() +local modifier_crystal_maiden_brilliance_aura_custom = ____exports.modifier_crystal_maiden_brilliance_aura_custom +modifier_crystal_maiden_brilliance_aura_custom.name = "modifier_crystal_maiden_brilliance_aura_custom" +modifier_crystal_maiden_brilliance_aura_custom.____file_path = "scripts/vscripts/abilities/heroes/crystal_maiden/ability_crystal_maiden_brilliance_aura_custom.lua" +__TS__ClassExtends(modifier_crystal_maiden_brilliance_aura_custom, BaseModifier) +function modifier_crystal_maiden_brilliance_aura_custom.prototype.IsHidden(self) + return true +end +function modifier_crystal_maiden_brilliance_aura_custom.prototype.IsPurgable(self) + return false +end +function modifier_crystal_maiden_brilliance_aura_custom.prototype.IsAura(self) + return true +end +function modifier_crystal_maiden_brilliance_aura_custom.prototype.GetAuraRadius(self) + return self:GetAbility():GetSpecialValueFor("aura_radius") +end +function modifier_crystal_maiden_brilliance_aura_custom.prototype.GetAuraSearchTeam(self) + return DOTA_UNIT_TARGET_TEAM_FRIENDLY +end +function modifier_crystal_maiden_brilliance_aura_custom.prototype.GetAuraSearchType(self) + return bit.bor(DOTA_UNIT_TARGET_HERO, DOTA_UNIT_TARGET_BASIC) +end +function modifier_crystal_maiden_brilliance_aura_custom.prototype.GetAuraSearchFlags(self) + return DOTA_UNIT_TARGET_FLAG_NONE +end +function modifier_crystal_maiden_brilliance_aura_custom.prototype.GetModifierAura(self) + return "modifier_crystal_maiden_brilliance_aura_buff" +end +function modifier_crystal_maiden_brilliance_aura_custom.prototype.GetAuraEntityReject(self, _hTarget) + return self:GetParent():PassivesDisabled() +end +modifier_crystal_maiden_brilliance_aura_custom = __TS__Decorate( + modifier_crystal_maiden_brilliance_aura_custom, + modifier_crystal_maiden_brilliance_aura_custom, + {registerModifier(nil)}, + {kind = "class", name = "modifier_crystal_maiden_brilliance_aura_custom"} +) +____exports.modifier_crystal_maiden_brilliance_aura_custom = modifier_crystal_maiden_brilliance_aura_custom +____exports.modifier_crystal_maiden_brilliance_aura_buff = __TS__Class() +local modifier_crystal_maiden_brilliance_aura_buff = ____exports.modifier_crystal_maiden_brilliance_aura_buff +modifier_crystal_maiden_brilliance_aura_buff.name = "modifier_crystal_maiden_brilliance_aura_buff" +modifier_crystal_maiden_brilliance_aura_buff.____file_path = "scripts/vscripts/abilities/heroes/crystal_maiden/ability_crystal_maiden_brilliance_aura_custom.lua" +__TS__ClassExtends(modifier_crystal_maiden_brilliance_aura_buff, BaseModifier) +function modifier_crystal_maiden_brilliance_aura_buff.prototype.IsHidden(self) + return false +end +function modifier_crystal_maiden_brilliance_aura_buff.prototype.IsDebuff(self) + return false +end +function modifier_crystal_maiden_brilliance_aura_buff.prototype.IsPurgable(self) + return false +end +function modifier_crystal_maiden_brilliance_aura_buff.prototype.DeclareFunctions(self) + return {MODIFIER_PROPERTY_MANA_REGEN_TOTAL_PERCENTAGE} +end +function modifier_crystal_maiden_brilliance_aura_buff.prototype.GetModifierTotalPercentageManaRegen(self) + local caster = self:GetCaster() + if caster and caster:PassivesDisabled() then + return 0 + end + return self:GetAbility():GetSpecialValueFor("mana_regen_pct") +end +function modifier_crystal_maiden_brilliance_aura_buff.prototype.GetEffectName(self) + return "particles/units/heroes/hero_crystalmaiden/maiden_brilliance_aura.vpcf" +end +function modifier_crystal_maiden_brilliance_aura_buff.prototype.GetEffectAttachType(self) + return PATTACH_ABSORIGIN_FOLLOW +end +modifier_crystal_maiden_brilliance_aura_buff = __TS__Decorate( + modifier_crystal_maiden_brilliance_aura_buff, + modifier_crystal_maiden_brilliance_aura_buff, + {registerModifier(nil)}, + {kind = "class", name = "modifier_crystal_maiden_brilliance_aura_buff"} +) +____exports.modifier_crystal_maiden_brilliance_aura_buff = modifier_crystal_maiden_brilliance_aura_buff +return ____exports diff --git a/scripts/vscripts/abilities/heroes/crystal_maiden/ability_crystal_maiden_crystal_nova_custom.lua b/scripts/vscripts/abilities/heroes/crystal_maiden/ability_crystal_maiden_crystal_nova_custom.lua new file mode 100644 index 0000000..9d05b16 --- /dev/null +++ b/scripts/vscripts/abilities/heroes/crystal_maiden/ability_crystal_maiden_crystal_nova_custom.lua @@ -0,0 +1,211 @@ +local ____lualib = require("lualib_bundle") +local __TS__Class = ____lualib.__TS__Class +local __TS__ClassExtends = ____lualib.__TS__ClassExtends +local __TS__ArrayForEach = ____lualib.__TS__ArrayForEach +local __TS__Decorate = ____lualib.__TS__Decorate +local ____exports = {} +local ____dota_ts_adapter = require("lib.dota_ts_adapter") +local BaseAbility = ____dota_ts_adapter.BaseAbility +local BaseModifier = ____dota_ts_adapter.BaseModifier +local registerAbility = ____dota_ts_adapter.registerAbility +local registerModifier = ____dota_ts_adapter.registerModifier +local ____modifier_general_froze = require("abilities.modifiers.modifier_general_froze") +local modifier_general_froze = ____modifier_general_froze.modifier_general_froze +local ____heal_tracker = require("utils.heal_tracker") +local HealWithBattlePass = ____heal_tracker.HealWithBattlePass +____exports.ability_crystal_maiden_crystal_nova_custom = __TS__Class() +local ability_crystal_maiden_crystal_nova_custom = ____exports.ability_crystal_maiden_crystal_nova_custom +ability_crystal_maiden_crystal_nova_custom.name = "ability_crystal_maiden_crystal_nova_custom" +ability_crystal_maiden_crystal_nova_custom.____file_path = "scripts/vscripts/abilities/heroes/crystal_maiden/ability_crystal_maiden_crystal_nova_custom.lua" +__TS__ClassExtends(ability_crystal_maiden_crystal_nova_custom, BaseAbility) +function ability_crystal_maiden_crystal_nova_custom.prototype.GetAOERadius(self) + return self:GetSpecialValueFor("radius") +end +function ability_crystal_maiden_crystal_nova_custom.prototype.OnSpellStart(self) + if not IsServer() then + return + end + local caster = self:GetCaster() + local point = self:GetCursorPosition() + local radius = self:GetSpecialValueFor("radius") + local damage = self:GetSpecialValueFor("damage") + local slow_duration = self:GetSpecialValueFor("slow_duration") + local frost_stacks = self:GetSpecialValueFor("frost_stacks_per_level") + local intellect_per_damage = self:GetSpecialValueFor("intellect_per_damage") + EmitSoundOnLocationWithCaster(point, "Hero_Crystal.CrystalNova", caster) + local particleEffect = ParticleManager:CreateParticle("particles/units/heroes/hero_crystalmaiden/maiden_crystal_nova.vpcf", PATTACH_WORLDORIGIN, nil) + ParticleManager:SetParticleControl(particleEffect, 0, point) + ParticleManager:SetParticleControl( + particleEffect, + 1, + Vector(radius, 1, radius) + ) + ParticleManager:SetParticleControl( + particleEffect, + 2, + self:GetCursorPosition() + ) + ParticleManager:ReleaseParticleIndex(particleEffect) + local units = FindUnitsInRadius( + caster:GetTeamNumber(), + point, + nil, + radius, + DOTA_UNIT_TARGET_TEAM_BOTH, + bit.bor(DOTA_UNIT_TARGET_HERO, DOTA_UNIT_TARGET_BASIC), + DOTA_UNIT_TARGET_FLAG_NONE, + FIND_ANY_ORDER, + false + ) + local healAmount = self:GetSpecialValueFor("heal") or damage + __TS__ArrayForEach( + units, + function(____, unit) + if not unit or unit:IsNull() or not unit:IsAlive() then + return + end + if unit == caster then + return + end + local isAlly = unit:GetTeamNumber() == caster:GetTeamNumber() + if isAlly then + local healValue = healAmount + self:GetCaster():GetIntellect(true) * intellect_per_damage + local maxShieldPctValue = self:GetSpecialValueFor("max_shield_pct") + local hasShieldFromOverheal = maxShieldPctValue > 0 + if hasShieldFromOverheal then + local healthBefore = unit:GetHealth() + local maxHealth = unit:GetMaxHealth() + local potentialHealthAfter = healthBefore + healValue + local potentialOverheal = potentialHealthAfter - maxHealth + HealWithBattlePass( + nil, + unit, + healValue, + self, + caster + ) + local healthAfter = unit:GetHealth() + local actualOverheal = healthAfter - maxHealth + local overheal = actualOverheal > 0 and actualOverheal or (potentialOverheal > 0 and potentialOverheal or 0) + if overheal > 0 then + local shieldModifier = unit:FindModifierByName("modifier_crystal_maiden_crystal_nova_shield") + local maxShieldPct = self:GetSpecialValueFor("max_shield_pct") + local maxShield = unit:GetMaxHealth() * (maxShieldPct / 100) + local shieldDuration = self:GetSpecialValueFor("shield_duration") + if shieldModifier then + local currentShield = shieldModifier:GetStackCount() + local newShield = math.min(currentShield + overheal, maxShield) + shieldModifier:SetDuration(shieldDuration, true) + shieldModifier:SetStackCount(newShield) + else + local newShieldModifier = unit:AddNewModifier(caster, self, "modifier_crystal_maiden_crystal_nova_shield", {duration = shieldDuration}) + if newShieldModifier ~= nil and newShieldModifier ~= nil then + newShieldModifier:SetStackCount(overheal) + end + end + end + else + HealWithBattlePass( + nil, + unit, + healValue, + self, + caster + ) + end + else + local damageValue = damage + self:GetCaster():GetIntellect(true) * intellect_per_damage + ApplyDamage({ + victim = unit, + attacker = caster, + damage = damageValue, + damage_type = DAMAGE_TYPE_MAGICAL, + ability = self + }) + local modifier = unit:AddNewModifier(caster, self, modifier_general_froze.name, {}) + if modifier then + modifier:SetDuration(slow_duration, true) + do + local i = 0 + while i < frost_stacks + getLuck( + nil, + self:GetCaster() + ) * 0.1 do + modifier:IncrementStackCount() + i = i + 1 + end + end + end + end + end + ) +end +ability_crystal_maiden_crystal_nova_custom = __TS__Decorate( + ability_crystal_maiden_crystal_nova_custom, + ability_crystal_maiden_crystal_nova_custom, + {registerAbility(nil)}, + {kind = "class", name = "ability_crystal_maiden_crystal_nova_custom"} +) +____exports.ability_crystal_maiden_crystal_nova_custom = ability_crystal_maiden_crystal_nova_custom +____exports.modifier_crystal_maiden_crystal_nova_shield = __TS__Class() +local modifier_crystal_maiden_crystal_nova_shield = ____exports.modifier_crystal_maiden_crystal_nova_shield +modifier_crystal_maiden_crystal_nova_shield.name = "modifier_crystal_maiden_crystal_nova_shield" +modifier_crystal_maiden_crystal_nova_shield.____file_path = "scripts/vscripts/abilities/heroes/crystal_maiden/ability_crystal_maiden_crystal_nova_custom.lua" +__TS__ClassExtends(modifier_crystal_maiden_crystal_nova_shield, BaseModifier) +function modifier_crystal_maiden_crystal_nova_shield.prototype.IsHidden(self) + return false +end +function modifier_crystal_maiden_crystal_nova_shield.prototype.IsPurgable(self) + return true +end +function modifier_crystal_maiden_crystal_nova_shield.prototype.GetEffectName(self) + return "particles/crystal_scepter_shield.vpcf" +end +function modifier_crystal_maiden_crystal_nova_shield.prototype.GetEffectAttachType(self) + return PATTACH_ABSORIGIN_FOLLOW +end +function modifier_crystal_maiden_crystal_nova_shield.prototype.OnCreated(self) + if not IsServer() then + return + end +end +function modifier_crystal_maiden_crystal_nova_shield.prototype.OnRefresh(self) + if not IsServer() then + return + end +end +function modifier_crystal_maiden_crystal_nova_shield.prototype.DeclareFunctions(self) + return {MODIFIER_PROPERTY_INCOMING_DAMAGE_CONSTANT} +end +function modifier_crystal_maiden_crystal_nova_shield.prototype.GetModifierIncomingDamageConstant(self, event) + if IsClient() then + return self:GetStackCount() + end + if not IsServer() then + return 0 + end + if event.inflictor and event.inflictor == self:GetAbility() then + return 0 + end + local currentShield = self:GetStackCount() + if currentShield > event.damage then + self:SetStackCount(currentShield - event.damage) + return -event.damage + else + local absorbedDamage = currentShield + self:SetStackCount(0) + self:Destroy() + return -absorbedDamage + end +end +function modifier_crystal_maiden_crystal_nova_shield.prototype.GetTexture(self) + return "crystal_maiden_crystal_nova" +end +modifier_crystal_maiden_crystal_nova_shield = __TS__Decorate( + modifier_crystal_maiden_crystal_nova_shield, + modifier_crystal_maiden_crystal_nova_shield, + {registerModifier(nil)}, + {kind = "class", name = "modifier_crystal_maiden_crystal_nova_shield"} +) +____exports.modifier_crystal_maiden_crystal_nova_shield = modifier_crystal_maiden_crystal_nova_shield +return ____exports diff --git a/scripts/vscripts/abilities/heroes/crystal_maiden/ability_crystal_maiden_freezing_field_custom.lua b/scripts/vscripts/abilities/heroes/crystal_maiden/ability_crystal_maiden_freezing_field_custom.lua new file mode 100644 index 0000000..bd5b23c --- /dev/null +++ b/scripts/vscripts/abilities/heroes/crystal_maiden/ability_crystal_maiden_freezing_field_custom.lua @@ -0,0 +1,432 @@ +local ____lualib = require("lualib_bundle") +local __TS__Class = ____lualib.__TS__Class +local __TS__ClassExtends = ____lualib.__TS__ClassExtends +local __TS__Decorate = ____lualib.__TS__Decorate +local __TS__NumberToFixed = ____lualib.__TS__NumberToFixed +local __TS__ArrayForEach = ____lualib.__TS__ArrayForEach +local ____exports = {} +local ____dota_ts_adapter = require("lib.dota_ts_adapter") +local BaseAbility = ____dota_ts_adapter.BaseAbility +local BaseModifier = ____dota_ts_adapter.BaseModifier +local registerAbility = ____dota_ts_adapter.registerAbility +local registerModifier = ____dota_ts_adapter.registerModifier +local ____modifier_general_froze = require("abilities.modifiers.modifier_general_froze") +local modifier_general_froze = ____modifier_general_froze.modifier_general_froze +local ____ability_alt_cast_manager = require("ability_alt_cast_manager") +local AbilityAltCastManager = ____ability_alt_cast_manager.AbilityAltCastManager +local ____heal_tracker = require("utils.heal_tracker") +local HealWithBattlePass = ____heal_tracker.HealWithBattlePass +____exports.ability_crystal_maiden_freezing_field_custom = __TS__Class() +local ability_crystal_maiden_freezing_field_custom = ____exports.ability_crystal_maiden_freezing_field_custom +ability_crystal_maiden_freezing_field_custom.name = "ability_crystal_maiden_freezing_field_custom" +ability_crystal_maiden_freezing_field_custom.____file_path = "scripts/vscripts/abilities/heroes/crystal_maiden/ability_crystal_maiden_freezing_field_custom.lua" +__TS__ClassExtends(ability_crystal_maiden_freezing_field_custom, BaseAbility) +function ability_crystal_maiden_freezing_field_custom.prototype.____constructor(self, ...) + BaseAbility.prototype.____constructor(self, ...) + self.modifier = nil +end +function ability_crystal_maiden_freezing_field_custom.prototype.GetBehavior(self) + if self:GetCaster():HasScepter() then + return bit.bor( + bit.bor(DOTA_ABILITY_BEHAVIOR_NO_TARGET, DOTA_ABILITY_BEHAVIOR_CHANNELLED), + DOTA_ABILITY_BEHAVIOR_IMMEDIATE + ) + end + return bit.bor(DOTA_ABILITY_BEHAVIOR_NO_TARGET, DOTA_ABILITY_BEHAVIOR_CHANNELLED) +end +function ability_crystal_maiden_freezing_field_custom.prototype.OnSpellStart(self) + if not IsServer() then + return + end + local caster = self:GetCaster() + local duration = self:GetChannelTime() + print("[FreezingField] OnSpellStart called, duration: " .. tostring(duration)) + self.modifier = caster:AddNewModifier(caster, self, ____exports.modifier_crystal_maiden_freezing_field_custom.name, {duration = duration}) + if not self.modifier or self.modifier:IsNull() then + print("[FreezingField] ERROR: Failed to apply modifier!") + else + print("[FreezingField] Modifier applied successfully") + end +end +function ability_crystal_maiden_freezing_field_custom.prototype.OnChannelFinish(self, interrupted) + if not IsServer() then + return + end + local caster = self:GetCaster() + if self.modifier and not self.modifier:IsNull() then + self.modifier:Destroy() + end + StopSoundOn("hero_Crystal.freezingField.wind", caster) +end +ability_crystal_maiden_freezing_field_custom = __TS__Decorate( + ability_crystal_maiden_freezing_field_custom, + ability_crystal_maiden_freezing_field_custom, + {registerAbility(nil)}, + {kind = "class", name = "ability_crystal_maiden_freezing_field_custom"} +) +____exports.ability_crystal_maiden_freezing_field_custom = ability_crystal_maiden_freezing_field_custom +____exports.modifier_crystal_maiden_freezing_field_custom = __TS__Class() +local modifier_crystal_maiden_freezing_field_custom = ____exports.modifier_crystal_maiden_freezing_field_custom +modifier_crystal_maiden_freezing_field_custom.name = "modifier_crystal_maiden_freezing_field_custom" +modifier_crystal_maiden_freezing_field_custom.____file_path = "scripts/vscripts/abilities/heroes/crystal_maiden/ability_crystal_maiden_freezing_field_custom.lua" +__TS__ClassExtends(modifier_crystal_maiden_freezing_field_custom, BaseModifier) +function modifier_crystal_maiden_freezing_field_custom.prototype.____constructor(self, ...) + BaseModifier.prototype.____constructor(self, ...) + self.explosionInterval = 0.1 + self.radius = 0 + self.explosionRadius = 0 + self.explosionDamage = 0 + self.frostStacksPerExplosion = 0 + self.slowDuration = 0 + self.intellectPerDamage = 0 + self.lastCrystalNovaTime = 0 + self.crystalNovaChance = 0.5 + self.crystalNovaCheckInterval = 0.5 +end +function modifier_crystal_maiden_freezing_field_custom.prototype.IsHidden(self) + return false +end +function modifier_crystal_maiden_freezing_field_custom.prototype.IsDebuff(self) + return false +end +function modifier_crystal_maiden_freezing_field_custom.prototype.GetAttributes(self) + return MODIFIER_ATTRIBUTE_MULTIPLE +end +function modifier_crystal_maiden_freezing_field_custom.prototype.IsPurgable(self) + return false +end +function modifier_crystal_maiden_freezing_field_custom.prototype.OnCreated(self) + if not IsServer() then + return + end + print("[FreezingField] Modifier OnCreated called") + local ability = self:GetAbility() + if not ability then + print("[FreezingField] ERROR: Ability is null!") + self:Destroy() + return + end + self.radius = ability:GetSpecialValueFor("radius") + self.explosionRadius = ability:GetSpecialValueFor("explosion_radius") + self.explosionDamage = ability:GetSpecialValueFor("explosion_damage") + self.frostStacksPerExplosion = ability:GetSpecialValueFor("frost_stacks_per_explosion") + self.slowDuration = ability:GetSpecialValueFor("slow_duration") + self.explosionInterval = ability:GetSpecialValueFor("explosion_interval") + self.intellectPerDamage = ability:GetSpecialValueFor("intellect_per_damage") + print((((("[FreezingField] Values loaded - radius: " .. tostring(self.radius)) .. ", interval: ") .. tostring(self.explosionInterval)) .. ", damage: ") .. tostring(self.explosionDamage)) + local parent = self:GetParent() + self.mainParticleId = ParticleManager:CreateParticle("particles/units/heroes/hero_crystalmaiden/maiden_freezing_field_snow.vpcf", PATTACH_ABSORIGIN_FOLLOW, parent) + ParticleManager:SetParticleControl( + self.mainParticleId, + 0, + parent:GetAbsOrigin() + ) + ParticleManager:SetParticleControl( + self.mainParticleId, + 1, + Vector(self.radius, 0, 0) + ) + self:AddParticle( + self.mainParticleId, + false, + false, + -1, + false, + false + ) + local caster = self:GetCaster() + if caster then + EmitSoundOn("hero_Crystal.freezingField.wind", caster) + end + self.lastCrystalNovaTime = GameRules:GetGameTime() + self:StartIntervalThink(self.explosionInterval) +end +function modifier_crystal_maiden_freezing_field_custom.prototype.OnIntervalThink(self) + if not IsServer() then + return + end + local caster = self:GetCaster() + local parent = self:GetParent() + local ability = self:GetAbility() + if caster and (caster:IsStunned() or caster:IsHexed() or caster:IsSilenced()) then + return + end + if not caster or caster:IsNull() or not parent or parent:IsNull() or not ability then + return + end + local roll = math.random() + print((("[FreezingField] Checking for mini Crystal Nova - roll: " .. __TS__NumberToFixed(roll, 3)) .. ", chance: ") .. tostring(self.crystalNovaChance)) + print("[FreezingField] Casting mini Crystal Nova!") + if self:GetCaster():HasScepter() then + self:CastMiniCrystalNova(caster, parent) + end + local casterPos = parent:GetAbsOrigin() + local angle = RandomFloat(0, 360) + local minDistance = 120 + local distance = RandomFloat(minDistance, self.radius) + local radians = angle * math.pi / 180 + local explosionPos = Vector( + casterPos.x + math.cos(radians) * distance, + casterPos.y + math.sin(radians) * distance, + casterPos.z + ) + local particleEffect = ParticleManager:CreateParticle("particles/units/heroes/hero_crystalmaiden/maiden_freezing_field_explosion.vpcf", PATTACH_WORLDORIGIN, nil) + ParticleManager:SetParticleControl(particleEffect, 0, explosionPos) + ParticleManager:SetParticleControl( + particleEffect, + 1, + Vector(self.explosionRadius, 0, 0) + ) + ParticleManager:ReleaseParticleIndex(particleEffect) + EmitSoundOnLocationWithCaster(explosionPos, "Hero_Crystal.FreezingField.Explosion", caster) + local enemies = FindUnitsInRadius( + caster:GetTeamNumber(), + explosionPos, + nil, + self.explosionRadius, + DOTA_UNIT_TARGET_TEAM_ENEMY, + bit.bor(DOTA_UNIT_TARGET_HERO, DOTA_UNIT_TARGET_BASIC), + DOTA_UNIT_TARGET_FLAG_NONE, + FIND_ANY_ORDER, + false + ) + print(((((("[FreezingField] Explosion at (" .. __TS__NumberToFixed(explosionPos.x, 1)) .. ", ") .. __TS__NumberToFixed(explosionPos.y, 1)) .. ") - found ") .. tostring(#enemies)) .. " enemies") + __TS__ArrayForEach( + enemies, + function(____, enemy) + if not enemy or enemy:IsNull() or not enemy:IsAlive() then + return + end + local damageValue = self.explosionDamage + caster:GetIntellect(true) * self.intellectPerDamage + ApplyDamage({ + victim = enemy, + attacker = caster, + damage = damageValue, + damage_type = DAMAGE_TYPE_MAGICAL, + ability = ability + }) + print(((("[FreezingField] Explosion hit enemy " .. enemy:GetUnitName()) .. " for ") .. __TS__NumberToFixed(damageValue, 1)) .. " damage") + local frostModifier = enemy:FindModifierByName(modifier_general_froze.name) + if not frostModifier then + frostModifier = enemy:AddNewModifier(caster, ability, modifier_general_froze.name, {}) + end + if frostModifier then + frostModifier:SetDuration(self.slowDuration, true) + local stacksToAdd = self.frostStacksPerExplosion + getLuck(nil, caster) * 0.1 + do + local i = 0 + while i < stacksToAdd do + frostModifier:IncrementStackCount() + i = i + 1 + end + end + print((("[FreezingField] Applied " .. __TS__NumberToFixed(stacksToAdd, 1)) .. " frost stacks to ") .. enemy:GetUnitName()) + end + end + ) +end +function modifier_crystal_maiden_freezing_field_custom.prototype.CastMiniCrystalNova(self, caster, parent) + if not IsServer() then + return + end + local ability = self:GetAbility() + if not ability then + print("[FreezingField] CastMiniCrystalNova - ability is null!") + return + end + local crystalNovaAbility = parent:FindAbilityByName("ability_crystal_maiden_crystal_nova_custom") + if not crystalNovaAbility or crystalNovaAbility:GetLevel() == 0 then + print("[FreezingField] CastMiniCrystalNova - Crystal Nova not learned or level 0") + return + end + print("[FreezingField] CastMiniCrystalNova - Crystal Nova level: " .. tostring(crystalNovaAbility:GetLevel())) + local casterPos = parent:GetAbsOrigin() + local angle = RandomFloat(0, 360) + local minDistance = 200 + local maxDistance = self.radius + local distance = RandomFloat(minDistance, maxDistance) + local radians = angle * math.pi / 180 + local novaPos = Vector( + casterPos.x + math.cos(radians) * distance, + casterPos.y + math.sin(radians) * distance, + casterPos.z + ) + local novaRadius = crystalNovaAbility:GetSpecialValueFor("radius") * (self:GetAbility():GetSpecialValueFor("mini_nova_pct") * 0.01) + local novaDamage = crystalNovaAbility:GetSpecialValueFor("damage") * (self:GetAbility():GetSpecialValueFor("mini_nova_pct") * 0.01) + local novaHeal = crystalNovaAbility:GetSpecialValueFor("heal") * (self:GetAbility():GetSpecialValueFor("mini_nova_pct") * 0.01) + local novaSlowDuration = crystalNovaAbility:GetSpecialValueFor("slow_duration") + local novaFrostStacks = math.floor(crystalNovaAbility:GetSpecialValueFor("frost_stacks_per_level") * (self:GetAbility():GetSpecialValueFor("mini_nova_pct") * 0.01)) + local novaIntellectPerDamage = crystalNovaAbility:GetSpecialValueFor("intellect_per_damage") + EmitSoundOnLocationWithCaster(novaPos, "Hero_Crystal.CrystalNova", caster) + local particleEffect = ParticleManager:CreateParticle("particles/units/heroes/hero_crystalmaiden/maiden_crystal_nova.vpcf", PATTACH_WORLDORIGIN, nil) + ParticleManager:SetParticleControl(particleEffect, 0, novaPos) + ParticleManager:SetParticleControl( + particleEffect, + 1, + Vector(novaRadius, 1, novaRadius) + ) + ParticleManager:ReleaseParticleIndex(particleEffect) + local units = FindUnitsInRadius( + caster:GetTeamNumber(), + novaPos, + nil, + novaRadius, + DOTA_UNIT_TARGET_TEAM_BOTH, + bit.bor(DOTA_UNIT_TARGET_HERO, DOTA_UNIT_TARGET_BASIC), + DOTA_UNIT_TARGET_FLAG_NONE, + FIND_ANY_ORDER, + false + ) + local frostbiteAbility = parent:FindAbilityByName("ability_crystal_maiden_frostbite_custom") + local brillianceAbility = parent:FindAbilityByName("ability_crystal_maiden_brilliance_aura_custom") + print(((((((("[FreezingField] CastMiniCrystalNova - Nova at (" .. __TS__NumberToFixed(novaPos.x, 1)) .. ", ") .. __TS__NumberToFixed(novaPos.y, 1)) .. "), radius: ") .. tostring(novaRadius)) .. ", found ") .. tostring(#units)) .. " units") + print((("[FreezingField] CastMiniCrystalNova - Frostbite level: " .. tostring(frostbiteAbility and frostbiteAbility:GetLevel() or 0)) .. ", Brilliance level: ") .. tostring(brillianceAbility and brillianceAbility:GetLevel() or 0)) + __TS__ArrayForEach( + units, + function(____, unit) + if not unit or unit:IsNull() or not unit:IsAlive() then + return + end + if unit == parent then + return + end + local isAlly = unit:GetTeamNumber() == caster:GetTeamNumber() + if isAlly then + local healValue = novaHeal + caster:GetIntellect(true) * novaIntellectPerDamage + HealWithBattlePass( + nil, + unit, + healValue, + ability, + caster + ) + print((("[FreezingField] CastMiniCrystalNova - Healed ally " .. unit:GetUnitName()) .. " for ") .. __TS__NumberToFixed(healValue, 1)) + if frostbiteAbility and frostbiteAbility:GetLevel() > 0 then + local playerId = caster:GetPlayerOwnerID() + if playerId == -1 or playerId == nil then + playerId = caster:GetPlayerID() + end + local isFrostbiteAltCast = false + if playerId ~= -1 and playerId ~= nil then + local altCastManager = AbilityAltCastManager:getInstance() + isFrostbiteAltCast = altCastManager:getAltCastState(playerId, "ability_crystal_maiden_frostbite_custom") + end + if not isFrostbiteAltCast then + local frostbiteDuration = frostbiteAbility:GetSpecialValueFor("duration") * 0.5 + unit:AddNewModifier(caster, ability, "modifier_crystal_maiden_frostbite_ally", {duration = frostbiteDuration}) + print(((("[FreezingField] CastMiniCrystalNova - Applied Frostbite (ally) to " .. unit:GetUnitName()) .. " for ") .. tostring(frostbiteDuration)) .. "s") + else + print(("[FreezingField] CastMiniCrystalNova - Skipped Frostbite (ally) for " .. unit:GetUnitName()) .. " because alt cast is enabled") + end + end + if brillianceAbility and brillianceAbility:GetLevel() > 0 then + unit:AddNewModifier(caster, ability, "modifier_crystal_maiden_brilliance_aura_buff", {duration = 3}) + print("[FreezingField] CastMiniCrystalNova - Applied Brilliance Aura buff to " .. unit:GetUnitName()) + end + else + local damageValue = novaDamage + caster:GetIntellect(true) * novaIntellectPerDamage + ApplyDamage({ + victim = unit, + attacker = caster, + damage = damageValue, + damage_type = DAMAGE_TYPE_MAGICAL, + ability = ability + }) + print((("[FreezingField] CastMiniCrystalNova - Damaged enemy " .. unit:GetUnitName()) .. " for ") .. __TS__NumberToFixed(damageValue, 1)) + local modifier = unit:AddNewModifier(caster, ability, modifier_general_froze.name, {}) + if modifier then + modifier:SetDuration(novaSlowDuration, true) + do + local i = 0 + while i < novaFrostStacks do + modifier:IncrementStackCount() + i = i + 1 + end + end + print((("[FreezingField] CastMiniCrystalNova - Applied " .. tostring(novaFrostStacks)) .. " frost stacks to ") .. unit:GetUnitName()) + end + if frostbiteAbility and frostbiteAbility:GetLevel() > 0 then + local frostbiteDuration = frostbiteAbility:GetSpecialValueFor("duration") * 0.5 + local frostbiteDamage = frostbiteAbility:GetSpecialValueFor("damage") * 0.5 + unit:AddNewModifier(caster, ability, "modifier_crystal_maiden_frostbite_enemy", {duration = frostbiteDuration, isAltCast = 0}) + ApplyDamage({ + victim = unit, + attacker = caster, + damage = frostbiteDamage, + damage_type = DAMAGE_TYPE_MAGICAL, + ability = ability + }) + print((((("[FreezingField] CastMiniCrystalNova - Applied Frostbite (enemy) to " .. unit:GetUnitName()) .. " for ") .. tostring(frostbiteDuration)) .. "s, damage: ") .. __TS__NumberToFixed(frostbiteDamage, 1)) + end + if brillianceAbility and brillianceAbility:GetLevel() > 0 then + local brillianceRadius = brillianceAbility:GetSpecialValueFor("active_radius") * 0.5 + local brillianceDamage = brillianceAbility:GetSpecialValueFor("damage") * 0.5 + local brillianceFrostStacks = math.floor(brillianceAbility:GetSpecialValueFor("frost_stacks_per_level") * 0.5) + local nearbyEnemies = FindUnitsInRadius( + caster:GetTeamNumber(), + unit:GetAbsOrigin(), + nil, + brillianceRadius, + DOTA_UNIT_TARGET_TEAM_ENEMY, + bit.bor(DOTA_UNIT_TARGET_HERO, DOTA_UNIT_TARGET_BASIC), + DOTA_UNIT_TARGET_FLAG_NONE, + FIND_ANY_ORDER, + false + ) + print(((("[FreezingField] CastMiniCrystalNova - Brilliance Aura effect from " .. unit:GetUnitName()) .. ", found ") .. tostring(#nearbyEnemies)) .. " nearby enemies") + __TS__ArrayForEach( + nearbyEnemies, + function(____, enemy) + if not enemy or enemy:IsNull() or not enemy:IsAlive() or enemy == unit then + return + end + ApplyDamage({ + victim = enemy, + attacker = caster, + damage = brillianceDamage, + damage_type = DAMAGE_TYPE_MAGICAL, + ability = ability + }) + local frostMod = enemy:FindModifierByName(modifier_general_froze.name) + if not frostMod then + frostMod = enemy:AddNewModifier(caster, ability, modifier_general_froze.name, {}) + end + if frostMod then + frostMod:SetDuration(1, true) + do + local i = 0 + while i < brillianceFrostStacks do + frostMod:IncrementStackCount() + i = i + 1 + end + end + end + end + ) + end + end + end + ) +end +function modifier_crystal_maiden_freezing_field_custom.prototype.OnDestroy(self) + if IsClient() then + return + end + print("[FreezingField] Modifier OnDestroy called") + if self.mainParticleId then + ParticleManager:DestroyParticle(self.mainParticleId, false) + ParticleManager:ReleaseParticleIndex(self.mainParticleId) + end + StopSoundOn( + "hero_Crystal.freezingField.wind", + self:GetParent() + ) +end +modifier_crystal_maiden_freezing_field_custom = __TS__Decorate( + modifier_crystal_maiden_freezing_field_custom, + modifier_crystal_maiden_freezing_field_custom, + {registerModifier(nil)}, + {kind = "class", name = "modifier_crystal_maiden_freezing_field_custom"} +) +____exports.modifier_crystal_maiden_freezing_field_custom = modifier_crystal_maiden_freezing_field_custom +return ____exports diff --git a/scripts/vscripts/abilities/heroes/crystal_maiden/ability_crystal_maiden_frostbite_custom.lua b/scripts/vscripts/abilities/heroes/crystal_maiden/ability_crystal_maiden_frostbite_custom.lua new file mode 100644 index 0000000..239d14f --- /dev/null +++ b/scripts/vscripts/abilities/heroes/crystal_maiden/ability_crystal_maiden_frostbite_custom.lua @@ -0,0 +1,910 @@ +local ____lualib = require("lualib_bundle") +local __TS__Class = ____lualib.__TS__Class +local __TS__ClassExtends = ____lualib.__TS__ClassExtends +local __TS__ArrayForEach = ____lualib.__TS__ArrayForEach +local __TS__Decorate = ____lualib.__TS__Decorate +local __TS__StringIncludes = ____lualib.__TS__StringIncludes +local __TS__ArrayFind = ____lualib.__TS__ArrayFind +local __TS__ArraySome = ____lualib.__TS__ArraySome +local ____exports = {} +local ____dota_ts_adapter = require("lib.dota_ts_adapter") +local BaseAbility = ____dota_ts_adapter.BaseAbility +local BaseModifier = ____dota_ts_adapter.BaseModifier +local registerAbility = ____dota_ts_adapter.registerAbility +local registerModifier = ____dota_ts_adapter.registerModifier +local ____modifier_general_froze = require("abilities.modifiers.modifier_general_froze") +local modifier_general_froze = ____modifier_general_froze.modifier_general_froze +local ____modifier_general_knockback = require("abilities.modifiers.modifier_general_knockback") +local modifier_general_knockback = ____modifier_general_knockback.modifier_general_knockback +local ____heal_tracker = require("utils.heal_tracker") +local HealWithBattlePass = ____heal_tracker.HealWithBattlePass +local ____incoming_damage_reduction_combine = require("utils.incoming_damage_reduction_combine") +local removeIncomingDamageReductionSource = ____incoming_damage_reduction_combine.removeIncomingDamageReductionSource +local setIncomingDamageReductionSource = ____incoming_damage_reduction_combine.setIncomingDamageReductionSource +local FROSTBITE_ALLY_INCOMING_SOURCE = "modifier_crystal_maiden_frostbite_ally" +____exports.ability_crystal_maiden_frostbite_custom = __TS__Class() +local ability_crystal_maiden_frostbite_custom = ____exports.ability_crystal_maiden_frostbite_custom +ability_crystal_maiden_frostbite_custom.name = "ability_crystal_maiden_frostbite_custom" +ability_crystal_maiden_frostbite_custom.____file_path = "scripts/vscripts/abilities/heroes/crystal_maiden/ability_crystal_maiden_frostbite_custom.lua" +__TS__ClassExtends(ability_crystal_maiden_frostbite_custom, BaseAbility) +function ability_crystal_maiden_frostbite_custom.prototype.GetAOERadius(self) + return self:GetSpecialValueFor("radius") +end +function ability_crystal_maiden_frostbite_custom.prototype.OnSpellStart(self) + if not IsServer() then + return + end + local caster = self:GetCaster() + local point = self:GetCursorPosition() + local radius = self:GetSpecialValueFor("radius") + local duration = self:GetSpecialValueFor("duration") + local damage = self:GetSpecialValueFor("damage") + local heal_per_second = self:GetSpecialValueFor("heal_per_second") + local isAltCast = self:IsAltCastAbility() + print(isAltCast) + EmitSoundOnLocationWithCaster(point, "Hero_Crystal.Frostbite", caster) + local ____isAltCast_0 + if isAltCast then + ____isAltCast_0 = DOTA_UNIT_TARGET_TEAM_ENEMY + else + ____isAltCast_0 = DOTA_UNIT_TARGET_TEAM_BOTH + end + local targetTeam = ____isAltCast_0 + local units = FindUnitsInRadius( + caster:GetTeamNumber(), + point, + nil, + radius, + targetTeam, + bit.bor(DOTA_UNIT_TARGET_HERO, DOTA_UNIT_TARGET_BASIC), + DOTA_UNIT_TARGET_FLAG_NONE, + FIND_ANY_ORDER, + false + ) + __TS__ArrayForEach( + units, + function(____, unit) + if not unit or unit:IsNull() or not unit:IsAlive() then + return + end + local isAlly = unit:GetTeamNumber() == caster:GetTeamNumber() + if isAltCast and isAlly then + return + end + if isAlly then + unit:AddNewModifier(caster, self, "modifier_crystal_maiden_frostbite_ally", {duration = duration}) + else + unit:AddNewModifier(caster, self, "modifier_crystal_maiden_frostbite_enemy", {duration = duration, isAltCast = isAltCast and 1 or 0}) + local initialDamage = isAltCast and damage * 2 or damage + ApplyDamage({ + victim = unit, + attacker = caster, + damage = initialDamage, + damage_type = DAMAGE_TYPE_MAGICAL, + ability = self + }) + end + end + ) +end +function ability_crystal_maiden_frostbite_custom.prototype.GetCastRange(self, location, target) + return self:GetSpecialValueFor("cast_range") +end +function ability_crystal_maiden_frostbite_custom.prototype.OnProjectileHit(self, target, location) + if not IsServer() then + return false + end + if not target then + return false + end + local caster = self:GetCaster() + if not caster then + return false + end + local damage = 100 + ApplyDamage({ + victim = target, + attacker = caster, + damage = damage, + damage_type = DAMAGE_TYPE_MAGICAL, + ability = self + }) + local frostModifier = target:AddNewModifier(caster, self, modifier_general_froze.name, {}) + if frostModifier then + do + local i = 0 + while i < 10 do + frostModifier:IncrementStackCount() + i = i + 1 + end + end + frostModifier:SetDuration(2, true) + end + local particle = ParticleManager:CreateParticle("particles/units/heroes/hero_crystalmaiden/crystal_maiden_frostbite.vpcf", PATTACH_ABSORIGIN_FOLLOW, target) + ParticleManager:ReleaseParticleIndex(particle) + EmitSoundOn("Hero_Crystal.Frostbite", target) + return true +end +ability_crystal_maiden_frostbite_custom = __TS__Decorate( + ability_crystal_maiden_frostbite_custom, + ability_crystal_maiden_frostbite_custom, + {registerAbility(nil)}, + {kind = "class", name = "ability_crystal_maiden_frostbite_custom"} +) +____exports.ability_crystal_maiden_frostbite_custom = ability_crystal_maiden_frostbite_custom +____exports.modifier_crystal_maiden_frostbite_ally = __TS__Class() +local modifier_crystal_maiden_frostbite_ally = ____exports.modifier_crystal_maiden_frostbite_ally +modifier_crystal_maiden_frostbite_ally.name = "modifier_crystal_maiden_frostbite_ally" +modifier_crystal_maiden_frostbite_ally.____file_path = "scripts/vscripts/abilities/heroes/crystal_maiden/ability_crystal_maiden_frostbite_custom.lua" +__TS__ClassExtends(modifier_crystal_maiden_frostbite_ally, BaseModifier) +function modifier_crystal_maiden_frostbite_ally.prototype.____constructor(self, ...) + BaseModifier.prototype.____constructor(self, ...) + self.healPerSecond = 0 +end +function modifier_crystal_maiden_frostbite_ally.prototype.IsHidden(self) + return false +end +function modifier_crystal_maiden_frostbite_ally.prototype.IsDebuff(self) + return false +end +function modifier_crystal_maiden_frostbite_ally.prototype.IsPurgable(self) + return true +end +function modifier_crystal_maiden_frostbite_ally.prototype.GetAttributes(self) + return MODIFIER_ATTRIBUTE_MULTIPLE +end +function modifier_crystal_maiden_frostbite_ally.prototype.OnCreated(self) + if IsServer() then + local ability = self:GetAbility() + if ability then + self.healPerSecond = ability:GetSpecialValueFor("heal_per_second") + end + setIncomingDamageReductionSource( + nil, + self:GetParent(), + FROSTBITE_ALLY_INCOMING_SOURCE, + function() + local frostAbility = self:GetAbility() + if not frostAbility then + return 0 + end + return math.max( + 0, + frostAbility:GetSpecialValueFor("incoming_damage_pct") + ) + end + ) + self:StartIntervalThink(0.5) + self:OnIntervalThink() + end +end +function modifier_crystal_maiden_frostbite_ally.prototype.OnIntervalThink(self) + if not IsServer() then + return + end + local parent = self:GetParent() + if not parent or parent:IsNull() or not parent:IsAlive() then + return + end + HealWithBattlePass( + nil, + parent, + self.healPerSecond * 0.5, + self:GetAbility(), + self:GetCaster() + ) +end +function modifier_crystal_maiden_frostbite_ally.prototype.CheckState(self) + return {[MODIFIER_STATE_ROOTED] = true, [MODIFIER_STATE_DISARMED] = false} +end +function modifier_crystal_maiden_frostbite_ally.prototype.GetEffectName(self) + return "particles/econ/items/crystal_maiden/ti7_immortal_shoulder/cm_ti7_immortal_frostbite.vpcf" +end +function modifier_crystal_maiden_frostbite_ally.prototype.GetEffectAttachType(self) + return PATTACH_ABSORIGIN_FOLLOW +end +function modifier_crystal_maiden_frostbite_ally.prototype.OnDestroy(self) + if IsClient() then + return + end + removeIncomingDamageReductionSource( + nil, + self:GetParent(), + FROSTBITE_ALLY_INCOMING_SOURCE + ) + StopSoundOn( + "Hero_Crystal.Frostbite", + self:GetParent() + ) +end +modifier_crystal_maiden_frostbite_ally = __TS__Decorate( + modifier_crystal_maiden_frostbite_ally, + modifier_crystal_maiden_frostbite_ally, + {registerModifier(nil)}, + {kind = "class", name = "modifier_crystal_maiden_frostbite_ally"} +) +____exports.modifier_crystal_maiden_frostbite_ally = modifier_crystal_maiden_frostbite_ally +____exports.modifier_crystal_maiden_frostbite_enemy = __TS__Class() +local modifier_crystal_maiden_frostbite_enemy = ____exports.modifier_crystal_maiden_frostbite_enemy +modifier_crystal_maiden_frostbite_enemy.name = "modifier_crystal_maiden_frostbite_enemy" +modifier_crystal_maiden_frostbite_enemy.____file_path = "scripts/vscripts/abilities/heroes/crystal_maiden/ability_crystal_maiden_frostbite_custom.lua" +__TS__ClassExtends(modifier_crystal_maiden_frostbite_enemy, BaseModifier) +function modifier_crystal_maiden_frostbite_enemy.prototype.____constructor(self, ...) + BaseModifier.prototype.____constructor(self, ...) + self.damage = 0 + self.frostStacksPerLevel = 0 + self.isAltCast = false + self.executeThresholdPct = 0 + self.executeAllowed = false + self.hasCreatedCopy = false +end +function modifier_crystal_maiden_frostbite_enemy.prototype.IsHidden(self) + return false +end +function modifier_crystal_maiden_frostbite_enemy.prototype.IsDebuff(self) + return true +end +function modifier_crystal_maiden_frostbite_enemy.prototype.IsPurgable(self) + return true +end +function modifier_crystal_maiden_frostbite_enemy.prototype.GetAttributes(self) + return MODIFIER_ATTRIBUTE_MULTIPLE +end +function modifier_crystal_maiden_frostbite_enemy.prototype.DeclareFunctions(self) + return {MODIFIER_EVENT_ON_DEATH} +end +function modifier_crystal_maiden_frostbite_enemy.prototype.OnCreated(self, params) + if IsServer() then + local ability = self:GetAbility() + if ability then + self.damage = ability:GetSpecialValueFor("damage") + self.frostStacksPerLevel = ability:GetSpecialValueFor("frost_stacks_per_level") + self.executeThresholdPct = ability:GetSpecialValueFor("execute_threshold_pct") or 0 + end + self.executeAllowed = self:canUseExecuteOnTarget(self:GetParent()) + self.isAltCast = params.isAltCast == 1 + self:StartIntervalThink(0.5) + end +end +function modifier_crystal_maiden_frostbite_enemy.prototype.canUseExecuteOnTarget(self, target) + local unitName = target:GetUnitName() + if not unitName then + return true + end + local lowerCaseName = string.lower(unitName) + local denyNames = { + "boss", + "npc_fish_1", + "npc_fish_2", + "npc_bomb", + "npc_wisps" + } + for ____, deny in ipairs(denyNames) do + if __TS__StringIncludes(lowerCaseName, deny) then + return false + end + end + return true +end +function modifier_crystal_maiden_frostbite_enemy.prototype.OnIntervalThink(self) + if not IsServer() then + return + end + local parent = self:GetParent() + local caster = self:GetCaster() + local ability = self:GetAbility() + if not parent or parent:IsNull() or not parent:IsAlive() or not caster or not ability then + return + end + if self.executeThresholdPct > 0 and self.executeAllowed then + local currentHealth = parent:GetHealth() + local maxHealth = parent:GetMaxHealth() + local healthPct = currentHealth / maxHealth * 100 + if healthPct < self.executeThresholdPct and not self.hasCreatedCopy then + local killPosition = parent:GetAbsOrigin() + local killForward = parent:GetForwardVector() + local unitName = parent:GetUnitName() + self.hasCreatedCopy = true + parent:Kill( + self:GetAbility(), + self:GetCaster() + ) + parent:RemoveSelf() + Timers:CreateTimer( + 0.1, + function() + if not parent or parent:IsNull() or not parent:IsAlive() then + local frozenCopy = CreateUnitByName( + unitName, + killPosition, + true, + caster, + caster, + DOTA_TEAM_NEUTRALS + ) + if IsServer() then + frozenCopy:SetRenderColor(60, 120, 255) + end + if frozenCopy and IsValidEntity(frozenCopy) then + frozenCopy:SetForwardVector(killForward) + FindClearSpaceForUnit(frozenCopy, killPosition, true) + frozenCopy:SetMinimumGoldBounty(0) + frozenCopy:SetMaximumGoldBounty(0) + frozenCopy:SetDeathXP(0) + frozenCopy:SetMoveCapability(DOTA_UNIT_CAP_MOVE_NONE) + frozenCopy:SetAttackCapability(DOTA_UNIT_CAP_NO_ATTACK) + local frozenModifier = frozenCopy:AddNewModifier(caster, ability, modifier_general_froze.name, {}) + if frozenModifier then + do + local i = 0 + while i < 150 do + frozenModifier:IncrementStackCount() + i = i + 1 + end + end + frozenModifier:SetDuration(-1, true) + end + frozenCopy:AddNewModifier(caster, ability, "modifier_crystal_maiden_frostbite_execute", {duration = 6}) + EmitSoundOn("Hero_Ancient_Apparition.ColdFeetCast", frozenCopy) + end + end + end + ) + return + end + end + local damageToDeal = self.isAltCast and self.damage * 2 or self.damage + ApplyDamage({ + victim = parent, + attacker = caster, + damage = damageToDeal * 0.5, + damage_type = DAMAGE_TYPE_MAGICAL, + ability = ability + }) + local frostModifier = parent:FindModifierByName(modifier_general_froze.name) + if not frostModifier then + frostModifier = parent:AddNewModifier(caster, ability, modifier_general_froze.name, {}) + end + if frostModifier then + do + local i = 0 + while i < self.frostStacksPerLevel * 0.5 do + frostModifier:IncrementStackCount() + i = i + 1 + end + end + frostModifier:SetDuration(1.01, true) + end +end +function modifier_crystal_maiden_frostbite_enemy.prototype.CheckState(self) + return {[MODIFIER_STATE_ROOTED] = true} +end +function modifier_crystal_maiden_frostbite_enemy.prototype.GetEffectName(self) + return "particles/units/heroes/hero_crystalmaiden/maiden_frostbite_buff.vpcf" +end +function modifier_crystal_maiden_frostbite_enemy.prototype.GetEffectAttachType(self) + return PATTACH_ABSORIGIN_FOLLOW +end +function modifier_crystal_maiden_frostbite_enemy.prototype.OnDeath(self, event) + if not IsServer() then + return + end + if event.unit ~= self:GetParent() then + return + end + local parent = self:GetParent() + local caster = self:GetCaster() + local ability = self:GetAbility() + if not parent or parent:IsNull() or not caster or not ability then + return + end + if self.executeThresholdPct > 0 and self.executeAllowed and not self.hasCreatedCopy then + self.hasCreatedCopy = true + local deathPosition = parent:GetAbsOrigin() + local deathForward = parent:GetForwardVector() + local unitName = parent:GetUnitName() + Timers:CreateTimer( + 0.1, + function() + local frozenCopy = CreateUnitByName( + unitName, + deathPosition, + true, + caster, + caster, + DOTA_TEAM_NEUTRALS + ) + if IsServer() then + frozenCopy:SetRenderColor(60, 120, 255) + end + if frozenCopy and IsValidEntity(frozenCopy) then + frozenCopy:SetForwardVector(deathForward) + FindClearSpaceForUnit(frozenCopy, deathPosition, true) + frozenCopy:SetMinimumGoldBounty(0) + frozenCopy:SetMaximumGoldBounty(0) + frozenCopy:SetDeathXP(0) + frozenCopy:SetMoveCapability(DOTA_UNIT_CAP_MOVE_NONE) + frozenCopy:SetAttackCapability(DOTA_UNIT_CAP_NO_ATTACK) + local frozenModifier = frozenCopy:AddNewModifier(caster, ability, modifier_general_froze.name, {}) + if frozenModifier then + do + local i = 0 + while i < 150 do + frozenModifier:IncrementStackCount() + i = i + 1 + end + end + frozenModifier:SetDuration(-1, true) + end + frozenCopy:AddNewModifier(caster, ability, "modifier_crystal_maiden_frostbite_execute", {duration = 6}) + EmitSoundOn("Hero_Ancient_Apparition.ColdFeetCast", frozenCopy) + end + end + ) + end +end +function modifier_crystal_maiden_frostbite_enemy.prototype.OnDestroy(self) + if IsClient() then + return + end + StopSoundOn( + "Hero_Crystal.Frostbite", + self:GetParent() + ) +end +modifier_crystal_maiden_frostbite_enemy = __TS__Decorate( + modifier_crystal_maiden_frostbite_enemy, + modifier_crystal_maiden_frostbite_enemy, + {registerModifier(nil)}, + {kind = "class", name = "modifier_crystal_maiden_frostbite_enemy"} +) +____exports.modifier_crystal_maiden_frostbite_enemy = modifier_crystal_maiden_frostbite_enemy +____exports.modifier_crystal_maiden_frostbite_execute = __TS__Class() +local modifier_crystal_maiden_frostbite_execute = ____exports.modifier_crystal_maiden_frostbite_execute +modifier_crystal_maiden_frostbite_execute.name = "modifier_crystal_maiden_frostbite_execute" +modifier_crystal_maiden_frostbite_execute.____file_path = "scripts/vscripts/abilities/heroes/crystal_maiden/ability_crystal_maiden_frostbite_custom.lua" +__TS__ClassExtends(modifier_crystal_maiden_frostbite_execute, BaseModifier) +function modifier_crystal_maiden_frostbite_execute.prototype.____constructor(self, ...) + BaseModifier.prototype.____constructor(self, ...) + self.isFlying = false + self.lastPosition = Vector(0, 0, 0) +end +function modifier_crystal_maiden_frostbite_execute.prototype.IsHidden(self) + return false +end +function modifier_crystal_maiden_frostbite_execute.prototype.IsDebuff(self) + return true +end +function modifier_crystal_maiden_frostbite_execute.prototype.IsPurgable(self) + return false +end +function modifier_crystal_maiden_frostbite_execute.prototype.GetEffectName(self) + return "particles/events/crownfall/survivors/abilities/crystal_maiden/crystal_maiden_frostbite.vpcf" +end +function modifier_crystal_maiden_frostbite_execute.prototype.GetEffectAttachType(self) + return PATTACH_ABSORIGIN_FOLLOW +end +function modifier_crystal_maiden_frostbite_execute.prototype.OnCreated(self) + if IsServer() then + self.lastPosition = self:GetParent():GetAbsOrigin() + self:GetParent():RemoveAbility("ability_unit_less_laggy") + end +end +function modifier_crystal_maiden_frostbite_execute.prototype.DeclareFunctions(self) + return {MODIFIER_EVENT_ON_ATTACKED, MODIFIER_PROPERTY_MIN_HEALTH} +end +function modifier_crystal_maiden_frostbite_execute.prototype.GetMinHealth(self) + return 1 +end +function modifier_crystal_maiden_frostbite_execute.prototype.CheckState(self) + return { + [MODIFIER_STATE_FROZEN] = true, + [MODIFIER_STATE_ROOTED] = true, + [MODIFIER_STATE_DISARMED] = true, + [MODIFIER_STATE_NO_HEALTH_BAR] = true, + [MODIFIER_STATE_SILENCED] = true + } +end +function modifier_crystal_maiden_frostbite_execute.prototype.OnAttacked(self, event) + if not IsServer() then + return + end + if event.target ~= self:GetParent() then + return + end + if self.isFlying then + return + end + local parent = self:GetParent() + local attacker = event.attacker + if not parent or parent:IsNull() or not attacker or attacker:IsNull() then + return + end + local direction = parent:GetAbsOrigin() - attacker:GetAbsOrigin() + direction.z = 0 + local normalizedDirection = direction:Normalized() + parent:SetMoveCapability(DOTA_UNIT_CAP_MOVE_GROUND) + local capturedModifier = self + local capturedParent = parent + self.explosionTimer = Timers:CreateTimer( + 0.5, + function() + if not IsServer() then + return + end + if not capturedModifier or capturedModifier:IsNull() then + return + end + if not capturedParent or capturedParent:IsNull() or not IsValidEntity(capturedParent) then + return + end + capturedModifier:Explode() + capturedParent:RemoveSelf() + end + ) + self.isFlying = true + self.lastPosition = parent:GetAbsOrigin() + modifier_general_knockback:apply( + parent, + attacker, + self:GetAbility(), + { + duration = 0.5, + distance = 600, + height = 0, + direction_x = normalizedDirection.x, + direction_y = normalizedDirection.y + } + ) + self:StartCollisionCheck() + self:StartIntervalThink(0.1) + self.knockbackEndTimer = Timers:CreateTimer( + 0.6, + function() + if not IsServer() then + return + end + if not capturedModifier or capturedModifier:IsNull() then + return + end + if not capturedParent or capturedParent:IsNull() or not IsValidEntity(capturedParent) then + return + end + if capturedModifier.isFlying then + capturedModifier.isFlying = false + capturedModifier:Explode() + capturedParent:RemoveSelf() + end + end + ) +end +function modifier_crystal_maiden_frostbite_execute.prototype.OnDestroy(self) + if IsServer() then + if self.collisionCheckTimer then + Timers:RemoveTimer(self.collisionCheckTimer) + self.collisionCheckTimer = nil + end + if self.explosionTimer then + Timers:RemoveTimer(self.explosionTimer) + self.explosionTimer = nil + end + if self.knockbackEndTimer then + Timers:RemoveTimer(self.knockbackEndTimer) + self.knockbackEndTimer = nil + end + self:Explode() + local parent = self:GetParent() + if parent and not parent:IsNull() and IsValidEntity(parent) then + parent:RemoveSelf() + end + end +end +function modifier_crystal_maiden_frostbite_execute.prototype.StartCollisionCheck(self) + if not IsServer() then + return + end + local checkInterval = 0.05 + local checkCount = 0 + local checkCollision + checkCollision = function() + if not self or self:IsNull() then + self.collisionCheckTimer = nil + return + end + local parent = self:GetParent() + if not parent or parent:IsNull() or not IsValidEntity(parent) or not self.isFlying then + if self and not self:IsNull() then + self.collisionCheckTimer = nil + end + return + end + local currentPos = parent:GetAbsOrigin() + local lastPos = self.lastPosition + checkCount = checkCount + 1 + local caster = self:GetCaster() + local searchTeam = caster and caster:GetTeamNumber() or parent:GetTeamNumber() + local nearbyUnits = FindUnitsInRadius( + searchTeam, + currentPos, + nil, + 100, + DOTA_UNIT_TARGET_TEAM_ENEMY, + bit.bor(DOTA_UNIT_TARGET_HERO, DOTA_UNIT_TARGET_BASIC), + DOTA_UNIT_TARGET_FLAG_NONE, + FIND_ANY_ORDER, + false + ) + local allNearbyUnits = FindUnitsInRadius( + DOTA_TEAM_NEUTRALS, + currentPos, + nil, + 100, + DOTA_UNIT_TARGET_TEAM_BOTH, + bit.bor(DOTA_UNIT_TARGET_HERO, DOTA_UNIT_TARGET_BASIC), + DOTA_UNIT_TARGET_FLAG_NONE, + FIND_ANY_ORDER, + false + ) + local otherFrozenCopy = __TS__ArrayFind( + allNearbyUnits, + function(____, unit) return unit ~= parent and unit:IsAlive() and unit:HasModifier("modifier_crystal_maiden_frostbite_execute") end + ) + if otherFrozenCopy then + local pushDirection = otherFrozenCopy:GetAbsOrigin() - currentPos + pushDirection.z = 0 + local normalizedPushDirection = pushDirection:Normalized() + local otherModifier = otherFrozenCopy:FindModifierByName("modifier_crystal_maiden_frostbite_execute") + if otherModifier and not otherModifier.isFlying then + otherFrozenCopy:SetMoveCapability(DOTA_UNIT_CAP_MOVE_GROUND) + modifier_general_knockback:apply( + otherFrozenCopy, + parent, + self:GetAbility(), + { + duration = 0.5, + distance = 400, + height = 0, + direction_x = normalizedPushDirection.x, + direction_y = normalizedPushDirection.y + } + ) + otherModifier.isFlying = true + otherModifier.lastPosition = otherFrozenCopy:GetAbsOrigin() + otherModifier:StartCollisionCheck() + otherModifier:StartIntervalThink(0.1) + local capturedModifier = otherModifier + local capturedUnit = otherFrozenCopy + otherModifier.knockbackEndTimer = Timers:CreateTimer( + 0.6, + function() + if not IsServer() then + return + end + if not capturedModifier or capturedModifier:IsNull() then + return + end + if not capturedUnit or capturedUnit:IsNull() or not IsValidEntity(capturedUnit) then + return + end + if capturedModifier.isFlying then + capturedModifier.isFlying = false + capturedModifier:Explode() + capturedUnit:RemoveSelf() + end + end + ) + otherModifier.explosionTimer = Timers:CreateTimer( + 0.5, + function() + if not IsServer() then + return + end + if not capturedModifier or capturedModifier:IsNull() then + return + end + if not capturedUnit or capturedUnit:IsNull() or not IsValidEntity(capturedUnit) then + return + end + capturedModifier:Explode() + capturedUnit:RemoveSelf() + end + ) + end + end + local hasUnitCollision = __TS__ArraySome( + nearbyUnits, + function(____, unit) return unit ~= parent and unit:IsAlive() end + ) + local isBlocked = not GridNav:CanFindPath(currentPos, currentPos) + local isStuck = false + if checkCount > 5 then + local distanceMoved = (currentPos - lastPos):Length2D() + isStuck = distanceMoved < 10 and self.isFlying + end + if hasUnitCollision or isBlocked or isStuck then + self:Explode() + return + end + self.lastPosition = currentPos + self.collisionCheckTimer = Timers:CreateTimer(checkInterval, checkCollision) + end + self.collisionCheckTimer = Timers:CreateTimer(0.1, checkCollision) +end +function modifier_crystal_maiden_frostbite_execute.prototype.OnIntervalThink(self) + if not IsServer() then + return + end + if not self or self:IsNull() then + return + end + if not self.isFlying then + return + end + local parent = self:GetParent() + if not parent or parent:IsNull() or not IsValidEntity(parent) then + self:Destroy() + return + end + local knockbackModifier = parent:FindModifierByName("modifier_general_knockback") + if not knockbackModifier and self.isFlying then + self.isFlying = false + Timers:CreateTimer( + 0.1, + function() + if self and not self:IsNull() then + local checkParent = self:GetParent() + if checkParent and not checkParent:IsNull() and IsValidEntity(checkParent) then + self:CheckFinalCollision() + end + end + end + ) + end +end +function modifier_crystal_maiden_frostbite_execute.prototype.CheckFinalCollision(self) + if not IsServer() then + return + end + if self:IsNull() then + return + end + local parent = self:GetParent() + if not parent or parent:IsNull() or not IsValidEntity(parent) then + return + end + if not self.isFlying then + return + end + local currentPos = parent:GetAbsOrigin() + local caster = self:GetCaster() + local searchTeam = caster and caster:GetTeamNumber() or parent:GetTeamNumber() + local nearbyUnits = FindUnitsInRadius( + searchTeam, + currentPos, + nil, + 100, + DOTA_UNIT_TARGET_TEAM_ENEMY, + bit.bor(DOTA_UNIT_TARGET_HERO, DOTA_UNIT_TARGET_BASIC), + DOTA_UNIT_TARGET_FLAG_NONE, + FIND_ANY_ORDER, + false + ) + local hasCollision = __TS__ArraySome( + nearbyUnits, + function(____, unit) return unit ~= parent and unit:IsAlive() end + ) + if hasCollision then + self:Explode() + local parent = self:GetParent() + if parent and not parent:IsNull() and IsValidEntity(parent) then + parent:RemoveSelf() + end + else + self:Explode() + local parent = self:GetParent() + if parent and not parent:IsNull() and IsValidEntity(parent) then + parent:RemoveSelf() + end + end +end +function modifier_crystal_maiden_frostbite_execute.prototype.Explode(self) + if not IsServer() then + return + end + if self.collisionCheckTimer then + Timers:RemoveTimer(self.collisionCheckTimer) + self.collisionCheckTimer = nil + end + if self.explosionTimer then + Timers:RemoveTimer(self.explosionTimer) + self.explosionTimer = nil + end + if self.knockbackEndTimer then + Timers:RemoveTimer(self.knockbackEndTimer) + self.knockbackEndTimer = nil + end + local parent = self:GetParent() + if not parent or parent:IsNull() then + return + end + local explosionPos = parent:GetAbsOrigin() + local caster = self:GetCaster() + local ability = self:GetAbility() + if not caster or not ability then + return + end + local knockbackModifier = parent:FindModifierByName("modifier_general_knockback") + if knockbackModifier then + knockbackModifier:Destroy() + end + local explosionParticle = ParticleManager:CreateParticle("particles/econ/items/effigies/status_fx_effigies/frosty_base_statue_destruction_dire.vpcf", PATTACH_CUSTOMORIGIN, nil) + ParticleManager:SetParticleControl(explosionParticle, 0, explosionPos) + ParticleManager:ReleaseParticleIndex(explosionParticle) + EmitSoundOnLocationWithCaster(explosionPos, "Hero_Ancient_Apparition.IceBlast.Target", caster) + local modifierAbility = self:GetAbility() + self:ExplodeInRadius(explosionPos, caster, modifierAbility or ability) + Timers:CreateTimer( + 0.1, + function() + if IsServer() and parent and not parent:IsNull() and IsValidEntity(parent) then + parent:RemoveSelf() + end + end + ) + self:Destroy() +end +function modifier_crystal_maiden_frostbite_execute.prototype.ExplodeInRadius(self, position, caster, ability) + if not IsServer() then + return + end + local explosionRadius = ability:GetSpecialValueFor("explosion_radius") + local damage = ability:GetCaster():GetMana() * (0.01 * ability:GetSpecialValueFor("damage_per_mana")) + local enemies = FindUnitsInRadius( + caster:GetTeamNumber(), + position, + nil, + explosionRadius, + DOTA_UNIT_TARGET_TEAM_ENEMY, + bit.bor(DOTA_UNIT_TARGET_HERO, DOTA_UNIT_TARGET_BASIC), + DOTA_UNIT_TARGET_FLAG_NONE, + FIND_ANY_ORDER, + false + ) + __TS__ArrayForEach( + enemies, + function(____, enemy) + if not enemy or enemy:IsNull() or not enemy:IsAlive() then + return + end + ApplyDamage({ + victim = enemy, + attacker = caster, + damage = damage, + damage_type = DAMAGE_TYPE_MAGICAL, + ability = ability + }) + local frostModifier = enemy:AddNewModifier(caster, ability, modifier_general_froze.name, {}) + if frostModifier then + do + local i = 0 + while i < 15 do + frostModifier:IncrementStackCount() + i = i + 1 + end + end + frostModifier:SetDuration(3, true) + end + local particle = ParticleManager:CreateParticle("particles/econ/items/crystal_maiden/crystal_maiden_maiden_of_icewrack/maiden_freezing_field_explosion_c_arcana1.vpcf", PATTACH_ABSORIGIN_FOLLOW, enemy) + ParticleManager:ReleaseParticleIndex(particle) + EmitSoundOn("Hero_Crystal.Frostbite", enemy) + end + ) +end +modifier_crystal_maiden_frostbite_execute = __TS__Decorate( + modifier_crystal_maiden_frostbite_execute, + modifier_crystal_maiden_frostbite_execute, + {registerModifier(nil)}, + {kind = "class", name = "modifier_crystal_maiden_frostbite_execute"} +) +____exports.modifier_crystal_maiden_frostbite_execute = modifier_crystal_maiden_frostbite_execute +return ____exports diff --git a/scripts/vscripts/abilities/heroes/drow_ranger/ability_drow_ranger_frost_arrows_custom.lua b/scripts/vscripts/abilities/heroes/drow_ranger/ability_drow_ranger_frost_arrows_custom.lua new file mode 100644 index 0000000..84fa4ca --- /dev/null +++ b/scripts/vscripts/abilities/heroes/drow_ranger/ability_drow_ranger_frost_arrows_custom.lua @@ -0,0 +1,293 @@ +local ____lualib = require("lualib_bundle") +local __TS__Class = ____lualib.__TS__Class +local __TS__ClassExtends = ____lualib.__TS__ClassExtends +local __TS__ArrayFind = ____lualib.__TS__ArrayFind +local __TS__Decorate = ____lualib.__TS__Decorate +local __TS__Delete = ____lualib.__TS__Delete +local ____exports = {} +local modifier_ability_drow_ranger_frost_arrows +local ____dota_ts_adapter = require("lib.dota_ts_adapter") +local BaseAbility = ____dota_ts_adapter.BaseAbility +local BaseModifier = ____dota_ts_adapter.BaseModifier +local registerAbility = ____dota_ts_adapter.registerAbility +local registerModifier = ____dota_ts_adapter.registerModifier +local ____modifier_general_froze = require("abilities.modifiers.modifier_general_froze") +local modifier_general_froze = ____modifier_general_froze.modifier_general_froze +____exports.ability_drow_ranger_frost_arrows_custom = __TS__Class() +local ability_drow_ranger_frost_arrows_custom = ____exports.ability_drow_ranger_frost_arrows_custom +ability_drow_ranger_frost_arrows_custom.name = "ability_drow_ranger_frost_arrows_custom" +ability_drow_ranger_frost_arrows_custom.____file_path = "scripts/vscripts/abilities/heroes/drow_ranger/ability_drow_ranger_frost_arrows_custom.lua" +__TS__ClassExtends(ability_drow_ranger_frost_arrows_custom, BaseAbility) +function ability_drow_ranger_frost_arrows_custom.prototype.GetIntrinsicModifierName(self) + return modifier_ability_drow_ranger_frost_arrows.name +end +function ability_drow_ranger_frost_arrows_custom.prototype.OnOrbFire(self, params) + local sound_cast = "Hero_DrowRanger.FrostArrows" + EmitSoundOn( + sound_cast, + self:GetCaster() + ) +end +function ability_drow_ranger_frost_arrows_custom.prototype.OnOrbImpact(self, params) + local target = params.target + if not target then + return + end + local modifier = target:AddNewModifier( + self:GetCaster(), + self, + modifier_general_froze.name, + {} + ) + if modifier then + local stacksPerLevel = self:GetSpecialValueFor("frost_stacks_per_level") + do + local i = 0 + while i < stacksPerLevel do + modifier:IncrementStackCount() + i = i + 1 + end + end + end + if not self:GetCaster():HasScepter() then + return + end + local ricochetChance = self:GetSpecialValueFor("ricochet_chance") + if RandomInt(1, 100) > ricochetChance then + return + end + local ricochetRadius = 500 + local enemies = FindUnitsInRadius( + self:GetCaster():GetTeamNumber(), + target:GetAbsOrigin(), + nil, + ricochetRadius, + DOTA_UNIT_TARGET_TEAM_ENEMY, + DOTA_UNIT_TARGET_HERO + DOTA_UNIT_TARGET_BASIC, + DOTA_UNIT_TARGET_FLAG_NONE, + FIND_CLOSEST, + false + ) + local nearestEnemy = __TS__ArrayFind( + enemies, + function(____, enemy) return enemy ~= target end + ) + if nearestEnemy then + local particlePath = "particles/units/heroes/hero_drow/drow_frost_arrow.vpcf" + ProjectileManager:CreateTrackingProjectile({ + Source = target, + Target = nearestEnemy, + Ability = self, + EffectName = particlePath, + iMoveSpeed = 1250, + bDodgeable = true, + bVisibleToEnemies = true, + bReplaceExisting = false, + bProvidesVision = false + }) + Timers:CreateTimer( + 0.2, + function() + if nearestEnemy and nearestEnemy:IsAlive() then + local ricochetModifier = nearestEnemy:AddNewModifier( + self:GetCaster(), + self, + modifier_general_froze.name, + {} + ) + if ricochetModifier ~= nil then + local stacksPerLevel = self:GetSpecialValueFor("frost_stacks_per_level") + do + local i = 0 + while i < stacksPerLevel do + ricochetModifier:IncrementStackCount() + i = i + 1 + end + end + end + self:GetCaster():PerformAttack( + nearestEnemy, + true, + false, + true, + false, + false, + false, + false + ) + EmitSoundOn("Hero_DrowRanger.FrostArrows.Impact", nearestEnemy) + end + end + ) + end +end +ability_drow_ranger_frost_arrows_custom = __TS__Decorate( + ability_drow_ranger_frost_arrows_custom, + ability_drow_ranger_frost_arrows_custom, + {registerAbility(nil)}, + {kind = "class", name = "ability_drow_ranger_frost_arrows_custom"} +) +____exports.ability_drow_ranger_frost_arrows_custom = ability_drow_ranger_frost_arrows_custom +modifier_ability_drow_ranger_frost_arrows = __TS__Class() +modifier_ability_drow_ranger_frost_arrows.name = "modifier_ability_drow_ranger_frost_arrows" +modifier_ability_drow_ranger_frost_arrows.____file_path = "scripts/vscripts/abilities/heroes/drow_ranger/ability_drow_ranger_frost_arrows_custom.lua" +__TS__ClassExtends(modifier_ability_drow_ranger_frost_arrows, BaseModifier) +function modifier_ability_drow_ranger_frost_arrows.prototype.____constructor(self, ...) + BaseModifier.prototype.____constructor(self, ...) + self.cast = false + self.records = {} +end +function modifier_ability_drow_ranger_frost_arrows.prototype.IsHidden(self) + return false +end +function modifier_ability_drow_ranger_frost_arrows.prototype.IsPurgable(self) + return false +end +function modifier_ability_drow_ranger_frost_arrows.prototype.IsPurgeException(self) + return false +end +function modifier_ability_drow_ranger_frost_arrows.prototype.IsDebuff(self) + return false +end +function modifier_ability_drow_ranger_frost_arrows.prototype.IsBuff(self) + return true +end +function modifier_ability_drow_ranger_frost_arrows.prototype.RemoveOnDeath(self) + return false +end +function modifier_ability_drow_ranger_frost_arrows.prototype.GetAttributes(self) + return MODIFIER_ATTRIBUTE_PERMANENT +end +function modifier_ability_drow_ranger_frost_arrows.prototype.DeclareFunctions(self) + return { + MODIFIER_EVENT_ON_ATTACK, + MODIFIER_EVENT_ON_ATTACK_FAIL, + MODIFIER_EVENT_ON_ATTACK_LANDED, + MODIFIER_PROPERTY_PROCATTACK_FEEDBACK, + MODIFIER_EVENT_ON_ATTACK_RECORD_DESTROY, + MODIFIER_EVENT_ON_ORDER, + MODIFIER_PROPERTY_PROJECTILE_NAME, + MODIFIER_PROPERTY_PREATTACK_BONUS_DAMAGE + } +end +function modifier_ability_drow_ranger_frost_arrows.prototype.OnCreated(self) + self.ability = self:GetAbility() + self.cast = false + self.records = {} +end +function modifier_ability_drow_ranger_frost_arrows.prototype.GetModifierPreAttack_BonusDamage(self) + if self:GetParent():PassivesDisabled() then + return 0 + end + if self:GetParent():IsIllusion() then + return 0 + end + if IsServer() then + local target = self:GetParent():GetAggroTarget() + if self:ShouldLaunch(target) then + return self:GetParent():GetAttackDamage() * (self:GetAbility():GetSpecialValueFor("damage_pct") * 0.01) + end + end + return 0 +end +function modifier_ability_drow_ranger_frost_arrows.prototype.OnAttack(self, params) + if params.attacker ~= self:GetParent() then + return + end + if self:GetParent():PassivesDisabled() then + return + end + if self:GetParent():IsIllusion() then + return + end + if self:ShouldLaunch(params.target) then + self.ability:UseResources(true, false, true, true) + self.records[params.record] = true + if self.ability.OnOrbFire ~= nil then + self.ability:OnOrbFire(params) + end + end + self.cast = false +end +function modifier_ability_drow_ranger_frost_arrows.prototype.OnAttackLanded(self, event) + if event.attacker ~= self:GetParent() then + return + end + if self:GetParent():PassivesDisabled() then + return + end + if self:GetParent():IsIllusion() then + return + end + if self:GetAbility():GetSpecialValueFor("crit_multiplier") > 0 then + self:IncrementStackCount() + if self:GetStackCount() > 3 then + if not IsServer() then + return + end + local mult = self:GetAbility():GetSpecialValueFor("crit_multiplier") + local stackingCritMod = self:GetCaster():FindModifierByName("modifier_stacking_crit") + if stackingCritMod then + local ability = self + stackingCritMod:AddCustomCrit(100, mult, "drow_ranger_frost_arrows") + end + if self:GetStackCount() > 4 then + self:SetStackCount(1) + if stackingCritMod then + stackingCritMod:RemoveCrit("drow_ranger_frost_arrows") + end + end + end + end +end +function modifier_ability_drow_ranger_frost_arrows.prototype.GetModifierProcAttack_Feedback(self, params) + if self:GetParent():PassivesDisabled() then + return 0 + end + if self:GetParent():IsIllusion() then + return 0 + end + if self.records[params.record] then + if self.ability.OnOrbImpact ~= nil then + self.ability:OnOrbImpact(params) + end + end + return 0 +end +function modifier_ability_drow_ranger_frost_arrows.prototype.OnAttackFail(self, params) + if self.records[params.record] then + __TS__Delete(self.records, params.record) + end +end +function modifier_ability_drow_ranger_frost_arrows.prototype.OnAttackRecordDestroy(self, params) + self.records[params.record] = false +end +function modifier_ability_drow_ranger_frost_arrows.prototype.OnOrder(self, params) + return +end +function modifier_ability_drow_ranger_frost_arrows.prototype.ShouldLaunch(self, target) + if not target then + return false + end + return self.ability:GetCooldownTimeRemaining() == 0 +end +function modifier_ability_drow_ranger_frost_arrows.prototype.GetModifierProjectileName(self) + if self:GetParent():PassivesDisabled() then + return "" + end + local target = self:GetParent():GetAggroTarget() + if not target then + return "" + end + if self:ShouldLaunch(target) then + return "particles/units/heroes/hero_drow/drow_frost_arrow.vpcf" + end + return "" +end +modifier_ability_drow_ranger_frost_arrows = __TS__Decorate( + modifier_ability_drow_ranger_frost_arrows, + modifier_ability_drow_ranger_frost_arrows, + {registerModifier(nil)}, + {kind = "class", name = "modifier_ability_drow_ranger_frost_arrows"} +) +return ____exports diff --git a/scripts/vscripts/abilities/heroes/drow_ranger/ability_drow_ranger_gust_custom.lua b/scripts/vscripts/abilities/heroes/drow_ranger/ability_drow_ranger_gust_custom.lua new file mode 100644 index 0000000..d5b0adf --- /dev/null +++ b/scripts/vscripts/abilities/heroes/drow_ranger/ability_drow_ranger_gust_custom.lua @@ -0,0 +1,151 @@ +local ____lualib = require("lualib_bundle") +local __TS__Class = ____lualib.__TS__Class +local __TS__ClassExtends = ____lualib.__TS__ClassExtends +local __TS__Decorate = ____lualib.__TS__Decorate +local ____exports = {} +local ____dota_ts_adapter = require("lib.dota_ts_adapter") +local BaseAbility = ____dota_ts_adapter.BaseAbility +local BaseModifier = ____dota_ts_adapter.BaseModifier +local registerAbility = ____dota_ts_adapter.registerAbility +local registerModifier = ____dota_ts_adapter.registerModifier +local ____modifier_general_froze = require("abilities.modifiers.modifier_general_froze") +local modifier_general_froze = ____modifier_general_froze.modifier_general_froze +local ____modifier_general_knockback = require("abilities.modifiers.modifier_general_knockback") +local modifier_general_knockback = ____modifier_general_knockback.modifier_general_knockback +____exports.ability_drow_ranger_gust_custom = __TS__Class() +local ability_drow_ranger_gust_custom = ____exports.ability_drow_ranger_gust_custom +ability_drow_ranger_gust_custom.name = "ability_drow_ranger_gust_custom" +ability_drow_ranger_gust_custom.____file_path = "scripts/vscripts/abilities/heroes/drow_ranger/ability_drow_ranger_gust_custom.lua" +__TS__ClassExtends(ability_drow_ranger_gust_custom, BaseAbility) +function ability_drow_ranger_gust_custom.prototype.OnSpellStart(self) + local caster = self:GetCaster() + local point = self:GetCursorPosition() + local casterPos = caster:GetAbsOrigin() + local speed = self:GetSpecialValueFor("gust_speed") + local width = self:GetSpecialValueFor("gust_width") + local distance = self:GetSpecialValueFor("gust_distance") + local direction = point - casterPos + direction.z = 0 + direction = direction:Normalized() + local projectileInfo = { + Source = caster, + Ability = self, + vSpawnOrigin = casterPos, + bDeleteOnHit = false, + iUnitTargetTeam = DOTA_UNIT_TARGET_TEAM_ENEMY, + iUnitTargetFlags = DOTA_UNIT_TARGET_FLAG_NONE, + iUnitTargetType = bit.bor(DOTA_UNIT_TARGET_HERO, DOTA_UNIT_TARGET_BASIC), + EffectName = "particles/units/heroes/hero_drow/drow_silence_wave.vpcf", + fDistance = distance, + fStartRadius = width, + fEndRadius = width, + vVelocity = direction * speed, + ExtraData = {x = casterPos.x, y = casterPos.y} + } + ProjectileManager:CreateLinearProjectile(projectileInfo) + EmitSoundOn("Hero_DrowRanger.Silence", caster) +end +function ability_drow_ranger_gust_custom.prototype.OnProjectileHit_ExtraData(self, target, location, extraData) + if not target then + return false + end + if target:GetTeamNumber() == self:GetCaster():GetTeamNumber() then + return false + end + local caster = self:GetCaster() + local knockback_duration = self:GetSpecialValueFor("knockback_duration") + local knockback_distance = self:GetSpecialValueFor("knockback_distance") + local damage = self:GetSpecialValueFor("damage") + local modifier = target:AddNewModifier( + self:GetCaster(), + self, + modifier_general_froze.name, + {} + ) + local stacksPerLevel = self:GetSpecialValueFor("frost_stacks_per_level") + do + local i = 0 + while i < stacksPerLevel do + modifier:IncrementStackCount() + i = i + 1 + end + end + if self:GetSpecialValueFor("frozen_son_duration") > 0 then + target:AddNewModifier( + self:GetCaster(), + self, + ____exports.modifier_general_frozen_son.name, + {duration = self:GetSpecialValueFor("frozen_son_duration")} + ) + end + ApplyDamage({ + victim = target, + attacker = caster, + damage = damage, + damage_type = DAMAGE_TYPE_MAGICAL, + ability = self + }) + local knockbackDirection = target:GetAbsOrigin() - Vector(extraData.x, extraData.y, 0) + knockbackDirection.z = 0 + knockbackDirection = knockbackDirection:Normalized() + modifier_general_knockback:apply(target, caster, self, {duration = knockback_duration, distance = knockback_distance, direction_x = knockbackDirection.x, direction_y = knockbackDirection.y}) + local effect_cast = ParticleManager:CreateParticle("particles/units/heroes/hero_drow/drow_silence.vpcf", PATTACH_ABSORIGIN_FOLLOW, target) + ParticleManager:ReleaseParticleIndex(effect_cast) + return false +end +ability_drow_ranger_gust_custom = __TS__Decorate( + ability_drow_ranger_gust_custom, + ability_drow_ranger_gust_custom, + {registerAbility(nil)}, + {kind = "class", name = "ability_drow_ranger_gust_custom"} +) +____exports.ability_drow_ranger_gust_custom = ability_drow_ranger_gust_custom +____exports.modifier_general_frozen_son = __TS__Class() +local modifier_general_frozen_son = ____exports.modifier_general_frozen_son +modifier_general_frozen_son.name = "modifier_general_frozen_son" +modifier_general_frozen_son.____file_path = "scripts/vscripts/abilities/heroes/drow_ranger/ability_drow_ranger_gust_custom.lua" +__TS__ClassExtends(modifier_general_frozen_son, BaseModifier) +function modifier_general_frozen_son.prototype.IsHidden(self) + return false +end +function modifier_general_frozen_son.prototype.IsDebuff(self) + return true +end +function modifier_general_frozen_son.prototype.IsPurgable(self) + return true +end +function modifier_general_frozen_son.prototype.GetEffectName(self) + return "particles/units/heroes/hero_crystalmaiden/maiden_frostbite_buff.vpcf" +end +function modifier_general_frozen_son.prototype.GetEffectAttachType(self) + return PATTACH_ABSORIGIN_FOLLOW +end +function modifier_general_frozen_son.prototype.CheckState(self) + return {[MODIFIER_STATE_ROOTED] = true, [MODIFIER_STATE_DISARMED] = true} +end +function modifier_general_frozen_son.prototype.OnCreated(self, params) + if IsClient() then + return + end + EmitSoundOn( + "Hero_Crystal.Frostbite", + self:GetParent() + ) +end +function modifier_general_frozen_son.prototype.OnDestroy(self) + if IsClient() then + return + end + StopSoundOn( + "Hero_Crystal.Frostbite", + self:GetParent() + ) +end +modifier_general_frozen_son = __TS__Decorate( + modifier_general_frozen_son, + modifier_general_frozen_son, + {registerModifier(nil)}, + {kind = "class", name = "modifier_general_frozen_son"} +) +____exports.modifier_general_frozen_son = modifier_general_frozen_son +return ____exports diff --git a/scripts/vscripts/abilities/heroes/drow_ranger/ability_drow_ranger_marksmanship_custom.lua b/scripts/vscripts/abilities/heroes/drow_ranger/ability_drow_ranger_marksmanship_custom.lua new file mode 100644 index 0000000..ea3895f --- /dev/null +++ b/scripts/vscripts/abilities/heroes/drow_ranger/ability_drow_ranger_marksmanship_custom.lua @@ -0,0 +1,194 @@ +local ____lualib = require("lualib_bundle") +local __TS__Class = ____lualib.__TS__Class +local __TS__ClassExtends = ____lualib.__TS__ClassExtends +local __TS__Decorate = ____lualib.__TS__Decorate +local ____exports = {} +local ____dota_ts_adapter = require("lib.dota_ts_adapter") +local BaseAbility = ____dota_ts_adapter.BaseAbility +local BaseModifier = ____dota_ts_adapter.BaseModifier +local registerAbility = ____dota_ts_adapter.registerAbility +local registerModifier = ____dota_ts_adapter.registerModifier +____exports.ability_drow_ranger_marksmanship_custom = __TS__Class() +local ability_drow_ranger_marksmanship_custom = ____exports.ability_drow_ranger_marksmanship_custom +ability_drow_ranger_marksmanship_custom.name = "ability_drow_ranger_marksmanship_custom" +ability_drow_ranger_marksmanship_custom.____file_path = "scripts/vscripts/abilities/heroes/drow_ranger/ability_drow_ranger_marksmanship_custom.lua" +__TS__ClassExtends(ability_drow_ranger_marksmanship_custom, BaseAbility) +function ability_drow_ranger_marksmanship_custom.prototype.____constructor(self, ...) + BaseAbility.prototype.____constructor(self, ...) + self.modifier = "modifier_drow_ranger_marksmanship_custom" +end +function ability_drow_ranger_marksmanship_custom.prototype.GetIntrinsicModifierName(self) + return self.modifier +end +ability_drow_ranger_marksmanship_custom = __TS__Decorate( + ability_drow_ranger_marksmanship_custom, + ability_drow_ranger_marksmanship_custom, + {registerAbility(nil)}, + {kind = "class", name = "ability_drow_ranger_marksmanship_custom"} +) +____exports.ability_drow_ranger_marksmanship_custom = ability_drow_ranger_marksmanship_custom +____exports.modifier_drow_ranger_marksmanship_custom = __TS__Class() +local modifier_drow_ranger_marksmanship_custom = ____exports.modifier_drow_ranger_marksmanship_custom +modifier_drow_ranger_marksmanship_custom.name = "modifier_drow_ranger_marksmanship_custom" +modifier_drow_ranger_marksmanship_custom.____file_path = "scripts/vscripts/abilities/heroes/drow_ranger/ability_drow_ranger_marksmanship_custom.lua" +__TS__ClassExtends(modifier_drow_ranger_marksmanship_custom, BaseModifier) +function modifier_drow_ranger_marksmanship_custom.prototype.____constructor(self, ...) + BaseModifier.prototype.____constructor(self, ...) + self.hits = 0 + self.bonusDamage = 0 + self.isActive = true + self.checkRadius = self:GetAbility():GetSpecialValueFor("disable_range") +end +function modifier_drow_ranger_marksmanship_custom.prototype.IsHidden(self) + return false +end +function modifier_drow_ranger_marksmanship_custom.prototype.IsDebuff(self) + return false +end +function modifier_drow_ranger_marksmanship_custom.prototype.IsPurgable(self) + return false +end +function modifier_drow_ranger_marksmanship_custom.prototype.OnCreated(self) + if not IsServer() then + return + end + local ability = self:GetAbility() + self.hits = ability:GetSpecialValueFor("hits") + self.bonusDamage = ability:GetSpecialValueFor("bonus_damage") + self.checkRadius = ability:GetSpecialValueFor("disable_range") + self:SetStackCount(self.hits) + self:StartIntervalThink(0.1) +end +function modifier_drow_ranger_marksmanship_custom.prototype.OnIntervalThink(self) + if not IsServer() then + return + end + if self:GetParent():PassivesDisabled() then + self.isActive = false + return + end + local parent = self:GetParent() + local enemies = FindUnitsInRadius( + parent:GetTeamNumber(), + parent:GetAbsOrigin(), + nil, + self.checkRadius, + DOTA_UNIT_TARGET_TEAM_ENEMY, + bit.bor(DOTA_UNIT_TARGET_HERO, DOTA_UNIT_TARGET_BASIC), + DOTA_UNIT_TARGET_FLAG_NONE, + FIND_ANY_ORDER, + false + ) + self.isActive = #enemies == 0 +end +function modifier_drow_ranger_marksmanship_custom.prototype.OnDestroy(self) + if IsClient() then + return + end +end +function modifier_drow_ranger_marksmanship_custom.prototype.DeclareFunctions(self) + return {MODIFIER_PROPERTY_PROJECTILE_NAME, MODIFIER_EVENT_ON_ATTACK, MODIFIER_PROPERTY_BASEATTACK_BONUSDAMAGE} +end +function modifier_drow_ranger_marksmanship_custom.prototype.OnAttack(self, event) + if not IsServer() then + return + end + if self:GetParent():PassivesDisabled() then + return + end + if not self.isActive then + return + end + if self:GetParent():IsIllusion() then + return + end + local parent = self:GetParent() + if event.attacker ~= parent then + return + end + if self:GetStackCount() == 1 then + self:SetStackCount(self.hits) + EmitSoundOn("Hero_DrowRanger.Marksmanship.Target", event.target) + if self:GetAbility():GetSpecialValueFor("marksmanship_master") > 0 then + event.target:AddNewModifier( + parent, + self:GetAbility(), + "modifier_drow_ranger_marksmanship_custom_armor", + {duration = 0.5} + ) + end + return + end + self:DecrementStackCount() +end +function modifier_drow_ranger_marksmanship_custom.prototype.GetPriority(self) + local ____temp_0 + if self:GetStackCount() == 1 then + ____temp_0 = MODIFIER_PRIORITY_ULTRA + else + ____temp_0 = MODIFIER_PRIORITY_NORMAL + end + return ____temp_0 +end +function modifier_drow_ranger_marksmanship_custom.prototype.GetModifierProjectileName(self) + if self:GetParent():PassivesDisabled() then + return "" + end + if self:GetParent():IsIllusion() then + return "" + end + if not self.isActive then + return "" + end + return self:GetStackCount() == 1 and "particles/units/heroes/hero_drow/drow_marksmanship_attack.vpcf" or "" +end +function modifier_drow_ranger_marksmanship_custom.prototype.GetModifierBaseAttack_BonusDamage(self) + if self:GetParent():PassivesDisabled() then + return 0 + end + if self:GetParent():IsIllusion() then + return 0 + end + if not self.isActive then + return 0 + end + return self:GetStackCount() == 1 and self.bonusDamage or 0 +end +modifier_drow_ranger_marksmanship_custom = __TS__Decorate( + modifier_drow_ranger_marksmanship_custom, + modifier_drow_ranger_marksmanship_custom, + {registerModifier(nil)}, + {kind = "class", name = "modifier_drow_ranger_marksmanship_custom"} +) +____exports.modifier_drow_ranger_marksmanship_custom = modifier_drow_ranger_marksmanship_custom +____exports.modifier_drow_ranger_marksmanship_custom_armor = __TS__Class() +local modifier_drow_ranger_marksmanship_custom_armor = ____exports.modifier_drow_ranger_marksmanship_custom_armor +modifier_drow_ranger_marksmanship_custom_armor.name = "modifier_drow_ranger_marksmanship_custom_armor" +modifier_drow_ranger_marksmanship_custom_armor.____file_path = "scripts/vscripts/abilities/heroes/drow_ranger/ability_drow_ranger_marksmanship_custom.lua" +__TS__ClassExtends(modifier_drow_ranger_marksmanship_custom_armor, BaseModifier) +function modifier_drow_ranger_marksmanship_custom_armor.prototype.IsHidden(self) + return true +end +function modifier_drow_ranger_marksmanship_custom_armor.prototype.IsDebuff(self) + return true +end +function modifier_drow_ranger_marksmanship_custom_armor.prototype.IsPurgable(self) + return false +end +function modifier_drow_ranger_marksmanship_custom_armor.prototype.DeclareFunctions(self) + return {MODIFIER_PROPERTY_IGNORE_PHYSICAL_ARMOR} +end +function modifier_drow_ranger_marksmanship_custom_armor.prototype.GetModifierIgnorePhysicalArmor(self) + return 1 +end +function modifier_drow_ranger_marksmanship_custom_armor.prototype.GetAttributes(self) + return MODIFIER_ATTRIBUTE_IGNORE_INVULNERABLE +end +modifier_drow_ranger_marksmanship_custom_armor = __TS__Decorate( + modifier_drow_ranger_marksmanship_custom_armor, + modifier_drow_ranger_marksmanship_custom_armor, + {registerModifier(nil)}, + {kind = "class", name = "modifier_drow_ranger_marksmanship_custom_armor"} +) +____exports.modifier_drow_ranger_marksmanship_custom_armor = modifier_drow_ranger_marksmanship_custom_armor +return ____exports diff --git a/scripts/vscripts/abilities/heroes/drow_ranger/ability_drow_ranger_multishot_custom.lua b/scripts/vscripts/abilities/heroes/drow_ranger/ability_drow_ranger_multishot_custom.lua new file mode 100644 index 0000000..6ae291b --- /dev/null +++ b/scripts/vscripts/abilities/heroes/drow_ranger/ability_drow_ranger_multishot_custom.lua @@ -0,0 +1,201 @@ +local ____lualib = require("lualib_bundle") +local __TS__Class = ____lualib.__TS__Class +local __TS__ClassExtends = ____lualib.__TS__ClassExtends +local __TS__Decorate = ____lualib.__TS__Decorate +local ____exports = {} +local ____dota_ts_adapter = require("lib.dota_ts_adapter") +local BaseAbility = ____dota_ts_adapter.BaseAbility +local BaseModifier = ____dota_ts_adapter.BaseModifier +local registerAbility = ____dota_ts_adapter.registerAbility +local registerModifier = ____dota_ts_adapter.registerModifier +local ____modifier_general_froze = require("abilities.modifiers.modifier_general_froze") +local modifier_general_froze = ____modifier_general_froze.modifier_general_froze +____exports.ability_drow_ranger_multishot_custom = __TS__Class() +local ability_drow_ranger_multishot_custom = ____exports.ability_drow_ranger_multishot_custom +ability_drow_ranger_multishot_custom.name = "ability_drow_ranger_multishot_custom" +ability_drow_ranger_multishot_custom.____file_path = "scripts/vscripts/abilities/heroes/drow_ranger/ability_drow_ranger_multishot_custom.lua" +__TS__ClassExtends(ability_drow_ranger_multishot_custom, BaseAbility) +function ability_drow_ranger_multishot_custom.prototype.____constructor(self, ...) + BaseAbility.prototype.____constructor(self, ...) + self.modifier = nil +end +function ability_drow_ranger_multishot_custom.prototype.OnSpellStart(self) + local caster = self:GetCaster() + local point = self:GetCursorPosition() + local duration = self:GetChannelTime() + caster:AddNewModifier(caster, self, ____exports.modifier_drow_ranger_multishot_damage_custom.name, {duration = 1.75}) + self.modifier = ____exports.modifier_drow_ranger_multishot_custom:apply(caster, caster, self, {duration = duration, x = point.x, y = point.y, z = point.z}) + EmitSoundOn("Hero_DrowRanger.Multishot.Channel", caster) +end +function ability_drow_ranger_multishot_custom.prototype.OnChannelFinish(self) + if self.modifier ~= nil then + self.modifier:Destroy() + end + self:GetCaster():RemoveModifierByName(____exports.modifier_drow_ranger_multishot_damage_custom.name) +end +function ability_drow_ranger_multishot_custom.prototype.OnProjectileHit_ExtraData(self, target) + if not target then + return + end + local caster = self:GetCaster() + local modifier = target:AddNewModifier( + self:GetCaster(), + self, + modifier_general_froze.name, + {} + ) + local stacksPerLevel = self:GetSpecialValueFor("frost_stacks_per_level") + do + local i = 0 + while i < stacksPerLevel do + modifier:IncrementStackCount() + i = i + 1 + end + end + caster:PerformAttack( + target, + true, + true, + true, + false, + false, + false, + false + ) + EmitSoundOn("Hero_DrowRanger.ProjectileImpact", target) + if self:GetSpecialValueFor("piercing_arrows") > 0 then + return false + end + return true +end +ability_drow_ranger_multishot_custom = __TS__Decorate( + ability_drow_ranger_multishot_custom, + ability_drow_ranger_multishot_custom, + {registerAbility(nil)}, + {kind = "class", name = "ability_drow_ranger_multishot_custom"} +) +____exports.ability_drow_ranger_multishot_custom = ability_drow_ranger_multishot_custom +____exports.modifier_drow_ranger_multishot_custom = __TS__Class() +local modifier_drow_ranger_multishot_custom = ____exports.modifier_drow_ranger_multishot_custom +modifier_drow_ranger_multishot_custom.name = "modifier_drow_ranger_multishot_custom" +modifier_drow_ranger_multishot_custom.____file_path = "scripts/vscripts/abilities/heroes/drow_ranger/ability_drow_ranger_multishot_custom.lua" +__TS__ClassExtends(modifier_drow_ranger_multishot_custom, BaseModifier) +function modifier_drow_ranger_multishot_custom.prototype.____constructor(self, ...) + BaseModifier.prototype.____constructor(self, ...) + self.arrowDelay = 0.01 + self.direction = Vector(0, 0, 0) + self.currentArrows = 0 + self.currentWave = 0 + self.arrowsPerWave = 0 + self.waveCount = 0 + self.waveDelay = 0 + self.speed = 0 + self.angle = 0 +end +function modifier_drow_ranger_multishot_custom.prototype.OnCreated(self, params) + local caster = self:GetParent() + local ability = self:GetAbility() + local range = ability:GetSpecialValueFor("arrow_range_multiplier") + local width = ability:GetSpecialValueFor("arrow_width") + self.speed = ability:GetSpecialValueFor("arrow_speed") + if IsClient() then + return + end + local vision = 100 + local delay = 0.1 + local wave = ability:GetSpecialValueFor("wave_count") + local waveInterval = 0.4375 + self.arrowsPerWave = ability:GetSpecialValueFor("arrow_count_per_wave") + self.waveCount = wave + self.waveDelay = waveInterval - self.arrowDelay * (self.arrowsPerWave - 1) + self.angle = 30 + local point = Vector(params.x, params.y, params.z) + self.direction = point - caster:GetOrigin() + self.direction.z = 0 + self.direction = self.direction:Normalized() + self.info = { + Source = caster, + Ability = ability, + vSpawnOrigin = caster:GetAttachmentOrigin(caster:ScriptLookupAttachment("attach_attack1")), + iUnitTargetTeam = DOTA_UNIT_TARGET_TEAM_ENEMY, + iUnitTargetType = DOTA_UNIT_TARGET_HERO + DOTA_UNIT_TARGET_BASIC, + iUnitTargetFlags = DOTA_UNIT_TARGET_FLAG_MAGIC_IMMUNE_ENEMIES, + EffectName = "particles/units/heroes/hero_drow/drow_multishot_proj_linear_proj.vpcf", + fDistance = caster:Script_GetAttackRange() * range, + fStartRadius = width, + fEndRadius = width, + bProvidesVision = true, + iVisionRadius = vision, + iVisionTeamNumber = caster:GetTeamNumber() + } + self:StartIntervalThink(delay) +end +function modifier_drow_ranger_multishot_custom.prototype.OnDestroy(self) + if IsClient() then + return + end + StopSoundOn( + "Hero_DrowRanger.Multishot.Channel", + self:GetParent() + ) +end +function modifier_drow_ranger_multishot_custom.prototype.OnIntervalThink(self) + if self.currentWave >= 3 then + return + end + if self.currentArrows < self.arrowsPerWave then + self:StartIntervalThink(self.arrowDelay) + else + self.currentArrows = 0 + self.currentWave = self.currentWave + 1 + if self.currentWave < 3 then + self:StartIntervalThink(self.waveDelay) + end + return + end + local step = self.angle / (self.arrowsPerWave - 1) + local angle = -self.angle / 2 + self.currentArrows * step + local projectileDirection = rotatePositionYaw( + nil, + Vector(0, 0, 0), + angle, + self.direction + ) + self.info.vVelocity = projectileDirection * self.speed + ProjectileManager:CreateLinearProjectile(self.info) + EmitSoundOn( + "Hero_DrowRanger.Multishot.Attack", + self:GetParent() + ) + self.currentArrows = self.currentArrows + 1 +end +modifier_drow_ranger_multishot_custom = __TS__Decorate( + modifier_drow_ranger_multishot_custom, + modifier_drow_ranger_multishot_custom, + {registerModifier(nil)}, + {kind = "class", name = "modifier_drow_ranger_multishot_custom"} +) +____exports.modifier_drow_ranger_multishot_custom = modifier_drow_ranger_multishot_custom +____exports.modifier_drow_ranger_multishot_damage_custom = __TS__Class() +local modifier_drow_ranger_multishot_damage_custom = ____exports.modifier_drow_ranger_multishot_damage_custom +modifier_drow_ranger_multishot_damage_custom.name = "modifier_drow_ranger_multishot_damage_custom" +modifier_drow_ranger_multishot_damage_custom.____file_path = "scripts/vscripts/abilities/heroes/drow_ranger/ability_drow_ranger_multishot_custom.lua" +__TS__ClassExtends(modifier_drow_ranger_multishot_damage_custom, BaseModifier) +function modifier_drow_ranger_multishot_damage_custom.prototype.IsHidden(self) + return true +end +function modifier_drow_ranger_multishot_damage_custom.prototype.DeclareFunctions(self) + return {MODIFIER_PROPERTY_DAMAGEOUTGOING_PERCENTAGE} +end +function modifier_drow_ranger_multishot_damage_custom.prototype.GetModifierDamageOutgoing_Percentage(self, event) + local damage = self:GetAbility():GetSpecialValueFor("arrow_damage_pct") - 100 + return damage +end +modifier_drow_ranger_multishot_damage_custom = __TS__Decorate( + modifier_drow_ranger_multishot_damage_custom, + modifier_drow_ranger_multishot_damage_custom, + {registerModifier(nil)}, + {kind = "class", name = "modifier_drow_ranger_multishot_damage_custom"} +) +____exports.modifier_drow_ranger_multishot_damage_custom = modifier_drow_ranger_multishot_damage_custom +return ____exports diff --git a/scripts/vscripts/abilities/heroes/grimstroke/grimstroke_dark_portrait_custom.lua b/scripts/vscripts/abilities/heroes/grimstroke/grimstroke_dark_portrait_custom.lua new file mode 100644 index 0000000..c8e8f8c --- /dev/null +++ b/scripts/vscripts/abilities/heroes/grimstroke/grimstroke_dark_portrait_custom.lua @@ -0,0 +1,95 @@ +local ____lualib = require("lualib_bundle") +local __TS__Class = ____lualib.__TS__Class +local __TS__ClassExtends = ____lualib.__TS__ClassExtends +local __TS__ArrayForEach = ____lualib.__TS__ArrayForEach +local __TS__Decorate = ____lualib.__TS__Decorate +local ____exports = {} +local ____dota_ts_adapter = require("lib.dota_ts_adapter") +local BaseAbility = ____dota_ts_adapter.BaseAbility +local BaseModifier = ____dota_ts_adapter.BaseModifier +local registerAbility = ____dota_ts_adapter.registerAbility +local registerModifier = ____dota_ts_adapter.registerModifier +____exports.grimstroke_dark_portrait_custom = __TS__Class() +local grimstroke_dark_portrait_custom = ____exports.grimstroke_dark_portrait_custom +grimstroke_dark_portrait_custom.name = "grimstroke_dark_portrait_custom" +grimstroke_dark_portrait_custom.____file_path = "scripts/vscripts/abilities/heroes/grimstroke/grimstroke_dark_portrait_custom.lua" +__TS__ClassExtends(grimstroke_dark_portrait_custom, BaseAbility) +function grimstroke_dark_portrait_custom.prototype.Precache(self, context) + PrecacheResource("soundfile", "soundevents/game_sounds_heroes/game_sounds_terrorblade.vsndevts", context) + PrecacheResource("particle", "particles/units/heroes/hero_terrorblade/terrorblade_mirror_image.vpcf", context) +end +function grimstroke_dark_portrait_custom.prototype.OnSpellStart(self) + local caster = self:GetCaster() + local target = self:GetCursorTarget() + if not target or not target:IsInstance(CDOTA_BaseNPC_Hero) then + return + end + local duration = self:GetSpecialValueFor("illusion_duration") + local outgoing = self:GetSpecialValueFor("illusion_outgoing_damage") + local incoming = self:GetSpecialValueFor("illusion_incoming_damage") + local illusion = CreateIllusions( + caster, + target, + {incoming_damage = incoming, outgoing_damage = outgoing, duration = duration}, + 1, + caster:GetHullRadius(), + false, + true + ) + __TS__ArrayForEach( + target:FindAllModifiers(), + function(____, modifier) + illusion[1]:AddNewModifier( + caster, + self, + modifier:GetName(), + {} + ) + end + ) + EmitSoundOn("Hero_Terrorblade.ConjureImage", illusion[1]) +end +grimstroke_dark_portrait_custom = __TS__Decorate( + grimstroke_dark_portrait_custom, + grimstroke_dark_portrait_custom, + {registerAbility(nil)}, + {kind = "class", name = "grimstroke_dark_portrait_custom"} +) +____exports.grimstroke_dark_portrait_custom = grimstroke_dark_portrait_custom +____exports.modifier_grimstroke_dark_portrait_custom = __TS__Class() +local modifier_grimstroke_dark_portrait_custom = ____exports.modifier_grimstroke_dark_portrait_custom +modifier_grimstroke_dark_portrait_custom.name = "modifier_grimstroke_dark_portrait_custom" +modifier_grimstroke_dark_portrait_custom.____file_path = "scripts/vscripts/abilities/heroes/grimstroke/grimstroke_dark_portrait_custom.lua" +__TS__ClassExtends(modifier_grimstroke_dark_portrait_custom, BaseModifier) +function modifier_grimstroke_dark_portrait_custom.prototype.IsHidden(self) + return true +end +function modifier_grimstroke_dark_portrait_custom.prototype.IsDebuff(self) + return false +end +function modifier_grimstroke_dark_portrait_custom.prototype.IsStunDebuff(self) + return false +end +function modifier_grimstroke_dark_portrait_custom.prototype.IsPurgable(self) + return false +end +function modifier_grimstroke_dark_portrait_custom.prototype.GetEffectName(self) + return "particles/units/heroes/hero_terrorblade/terrorblade_mirror_image.vpcf" +end +function modifier_grimstroke_dark_portrait_custom.prototype.GetEffectAttachType(self) + return PATTACH_ABSORIGIN_FOLLOW +end +function modifier_grimstroke_dark_portrait_custom.prototype.GetStatusEffectName(self) + return "particles/status_fx/status_effect_terrorblade_reflection.vpcf" +end +function modifier_grimstroke_dark_portrait_custom.prototype.StatusEffectPriority(self) + return MODIFIER_PRIORITY_SUPER_ULTRA +end +modifier_grimstroke_dark_portrait_custom = __TS__Decorate( + modifier_grimstroke_dark_portrait_custom, + modifier_grimstroke_dark_portrait_custom, + {registerModifier(nil)}, + {kind = "class", name = "modifier_grimstroke_dark_portrait_custom"} +) +____exports.modifier_grimstroke_dark_portrait_custom = modifier_grimstroke_dark_portrait_custom +return ____exports diff --git a/scripts/vscripts/abilities/heroes/grimstroke/grimstroke_ink_swell_custom.lua b/scripts/vscripts/abilities/heroes/grimstroke/grimstroke_ink_swell_custom.lua new file mode 100644 index 0000000..2b468ee --- /dev/null +++ b/scripts/vscripts/abilities/heroes/grimstroke/grimstroke_ink_swell_custom.lua @@ -0,0 +1,231 @@ +local ____lualib = require("lualib_bundle") +local __TS__Class = ____lualib.__TS__Class +local __TS__ClassExtends = ____lualib.__TS__ClassExtends +local __TS__Decorate = ____lualib.__TS__Decorate +local __TS__ArrayForEach = ____lualib.__TS__ArrayForEach +local ____exports = {} +local ____dota_ts_adapter = require("lib.dota_ts_adapter") +local BaseAbility = ____dota_ts_adapter.BaseAbility +local BaseModifier = ____dota_ts_adapter.BaseModifier +local registerAbility = ____dota_ts_adapter.registerAbility +local registerModifier = ____dota_ts_adapter.registerModifier +____exports.grimstroke_ink_swell_custom = __TS__Class() +local grimstroke_ink_swell_custom = ____exports.grimstroke_ink_swell_custom +grimstroke_ink_swell_custom.name = "grimstroke_ink_swell_custom" +grimstroke_ink_swell_custom.____file_path = "scripts/vscripts/abilities/heroes/grimstroke/grimstroke_ink_swell_custom.lua" +__TS__ClassExtends(grimstroke_ink_swell_custom, BaseAbility) +function grimstroke_ink_swell_custom.prototype.OnSpellStart(self) + local caster = self:GetCaster() + local target = self:GetCursorTarget() + if not target then + return + end + local duration = self:GetSpecialValueFor("buff_duration") + target:AddNewModifier(caster, self, ____exports.modifier_grimstroke_ink_swell.name, {duration = duration}) + local effectCast = ParticleManager:CreateParticle("particles/units/heroes/hero_grimstroke/grimstroke_cast_ink_swell.vpcf", PATTACH_ABSORIGIN_FOLLOW, caster) + ParticleManager:ReleaseParticleIndex(effectCast) + EmitSoundOn("Hero_Grimstroke.InkSwell.Cast", caster) +end +grimstroke_ink_swell_custom = __TS__Decorate( + grimstroke_ink_swell_custom, + grimstroke_ink_swell_custom, + {registerAbility(nil)}, + {kind = "class", name = "grimstroke_ink_swell_custom"} +) +____exports.grimstroke_ink_swell_custom = grimstroke_ink_swell_custom +____exports.modifier_grimstroke_ink_swell = __TS__Class() +local modifier_grimstroke_ink_swell = ____exports.modifier_grimstroke_ink_swell +modifier_grimstroke_ink_swell.name = "modifier_grimstroke_ink_swell" +modifier_grimstroke_ink_swell.____file_path = "scripts/vscripts/abilities/heroes/grimstroke/grimstroke_ink_swell_custom.lua" +__TS__ClassExtends(modifier_grimstroke_ink_swell, BaseModifier) +function modifier_grimstroke_ink_swell.prototype.____constructor(self, ...) + BaseModifier.prototype.____constructor(self, ...) + self.counter = 0 +end +function modifier_grimstroke_ink_swell.prototype.IsHidden(self) + return false +end +function modifier_grimstroke_ink_swell.prototype.IsDebuff(self) + return false +end +function modifier_grimstroke_ink_swell.prototype.IsPurgable(self) + return true +end +function modifier_grimstroke_ink_swell.prototype.OnCreated(self) + if IsClient() then + return + end + local interval = self:GetAbility():GetSpecialValueFor("tick_rate") + local radius = self:GetAbility():GetSpecialValueFor("radius") + self:StartIntervalThink(interval) + local effectCast = ParticleManager:CreateParticle( + "particles/units/heroes/hero_grimstroke/grimstroke_ink_swell_buff.vpcf", + PATTACH_OVERHEAD_FOLLOW, + self:GetParent() + ) + ParticleManager:SetParticleControl( + effectCast, + 2, + Vector(radius, radius, radius) + ) + ParticleManager:SetParticleControlEnt( + effectCast, + 3, + self:GetParent(), + PATTACH_ABSORIGIN_FOLLOW, + "attach_attack1", + self:GetParent():GetOrigin(), + true + ) + self:AddParticle( + effectCast, + false, + false, + -1, + false, + true + ) + EmitSoundOn( + "Hero_Grimstroke.InkSwell.Target", + self:GetParent() + ) +end +function modifier_grimstroke_ink_swell.prototype.OnDestroy(self) + if IsClient() then + return + end + local radius = self:GetAbility():GetSpecialValueFor("radius") + local enemies = FindUnitsInRadius( + self:GetParent():GetTeamNumber(), + self:GetParent():GetOrigin(), + nil, + radius, + DOTA_UNIT_TARGET_TEAM_ENEMY, + bit.bor(DOTA_UNIT_TARGET_BASIC, DOTA_UNIT_TARGET_HERO), + DOTA_UNIT_TARGET_FLAG_NONE, + FIND_ANY_ORDER, + false + ) + local baseStun = self:GetAbility():GetSpecialValueFor("debuff_duration") + local maxMultiplier = self:GetAbility():GetSpecialValueFor("max_bonus_multiplier") + local baseDamage = self:GetAbility():GetSpecialValueFor("damage") + local maxCounter = self:GetAbility():GetSpecialValueFor("max_counter") + local multiplier = self.counter / maxCounter * maxMultiplier + local finalMultiplier = math.min(maxMultiplier, multiplier + 1) + local stun = baseStun * finalMultiplier + local damage = baseDamage * finalMultiplier + __TS__ArrayForEach( + enemies, + function(____, enemy) + ApplyDamage({ + victim = enemy, + attacker = self:GetParent(), + damage = damage, + damage_type = DAMAGE_TYPE_MAGICAL, + ability = self:GetAbility() + }) + enemy:AddNewModifier( + self:GetParent(), + self:GetAbility(), + "modifier_stunned", + {duration = stun} + ) + end + ) + StopSoundOn( + "Hero_Grimstroke.InkSwell.Target", + self:GetParent() + ) + local effectCast = ParticleManager:CreateParticle( + "particles/units/heroes/hero_grimstroke/grimstroke_ink_swell_aoe.vpcf", + PATTACH_ABSORIGIN_FOLLOW, + self:GetParent() + ) + ParticleManager:SetParticleControl( + effectCast, + 2, + Vector(radius, radius, radius) + ) + ParticleManager:ReleaseParticleIndex(effectCast) + EmitSoundOn( + "Hero_Grimstroke.InkSwell.Stun", + self:GetParent() + ) +end +function modifier_grimstroke_ink_swell.prototype.DeclareFunctions(self) + return {MODIFIER_PROPERTY_MOVESPEED_BONUS_PERCENTAGE} +end +function modifier_grimstroke_ink_swell.prototype.GetModifierMoveSpeedBonus_Percentage(self) + return self:GetAbility():GetSpecialValueFor("movespeed_bonus_pct") +end +function modifier_grimstroke_ink_swell.prototype.CheckState(self) + return {[MODIFIER_STATE_ATTACK_IMMUNE] = true, [MODIFIER_STATE_DISARMED] = true, [MODIFIER_STATE_SILENCED] = true} +end +function modifier_grimstroke_ink_swell.prototype.OnIntervalThink(self) + if IsClient() then + return + end + local maxCounter = self:GetAbility():GetSpecialValueFor("max_counter") + local radius = self:GetAbility():GetSpecialValueFor("radius") + local maxMultiplier = self:GetAbility():GetSpecialValueFor("max_bonus_multiplier") + local multiplier = self.counter / maxCounter * maxMultiplier + local damagePerTick = self:GetAbility():GetSpecialValueFor("tick_dps_tooltip") * self:GetAbility():GetSpecialValueFor("tick_rate") + local damage = damagePerTick * math.min(maxMultiplier, multiplier + 1) + local enemies = FindUnitsInRadius( + self:GetParent():GetTeamNumber(), + self:GetParent():GetOrigin(), + nil, + radius, + DOTA_UNIT_TARGET_TEAM_ENEMY, + DOTA_UNIT_TARGET_ALL, + DOTA_UNIT_TARGET_FLAG_NONE, + FIND_ANY_ORDER, + false + ) + __TS__ArrayForEach( + enemies, + function(____, enemy) + ApplyDamage({ + victim = enemy, + attacker = self:GetParent(), + damage = damage, + damage_type = DAMAGE_TYPE_MAGICAL, + ability = self:GetAbility() + }) + self.counter = self.counter + 1 + local effectCast = ParticleManager:CreateParticle( + "particles/units/heroes/hero_grimstroke/grimstroke_ink_swell_tick_damage.vpcf", + PATTACH_ABSORIGIN_FOLLOW, + self:GetParent() + ) + ParticleManager:SetParticleControlEnt( + effectCast, + 0, + self:GetParent(), + PATTACH_ABSORIGIN_FOLLOW, + "attach_hitloc", + Vector(0, 0, 0), + true + ) + ParticleManager:SetParticleControlEnt( + effectCast, + 1, + enemy, + PATTACH_POINT_FOLLOW, + "attach_hitloc", + Vector(0, 0, 0), + true + ) + ParticleManager:ReleaseParticleIndex(effectCast) + EmitSoundOn("Hero_Grimstroke.InkSwell.Damage", enemy) + end + ) +end +modifier_grimstroke_ink_swell = __TS__Decorate( + modifier_grimstroke_ink_swell, + modifier_grimstroke_ink_swell, + {registerModifier(nil)}, + {kind = "class", name = "modifier_grimstroke_ink_swell"} +) +____exports.modifier_grimstroke_ink_swell = modifier_grimstroke_ink_swell +return ____exports diff --git a/scripts/vscripts/abilities/heroes/grimstroke/grimstroke_phantoms_embrace_custom.lua b/scripts/vscripts/abilities/heroes/grimstroke/grimstroke_phantoms_embrace_custom.lua new file mode 100644 index 0000000..17f2447 --- /dev/null +++ b/scripts/vscripts/abilities/heroes/grimstroke/grimstroke_phantoms_embrace_custom.lua @@ -0,0 +1,316 @@ +local ____lualib = require("lualib_bundle") +local __TS__Class = ____lualib.__TS__Class +local __TS__ClassExtends = ____lualib.__TS__ClassExtends +local __TS__Decorate = ____lualib.__TS__Decorate +local ____exports = {} +local ____dota_ts_adapter = require("lib.dota_ts_adapter") +local BaseAbility = ____dota_ts_adapter.BaseAbility +local BaseModifier = ____dota_ts_adapter.BaseModifier +local BaseModifierMotionHorizontal = ____dota_ts_adapter.BaseModifierMotionHorizontal +local registerAbility = ____dota_ts_adapter.registerAbility +local registerModifier = ____dota_ts_adapter.registerModifier +____exports.grimstroke_phantoms_embrace_custom = __TS__Class() +local grimstroke_phantoms_embrace_custom = ____exports.grimstroke_phantoms_embrace_custom +grimstroke_phantoms_embrace_custom.name = "grimstroke_phantoms_embrace_custom" +grimstroke_phantoms_embrace_custom.____file_path = "scripts/vscripts/abilities/heroes/grimstroke/grimstroke_phantoms_embrace_custom.lua" +__TS__ClassExtends(grimstroke_phantoms_embrace_custom, BaseAbility) +function grimstroke_phantoms_embrace_custom.prototype.____constructor(self, ...) + BaseAbility.prototype.____constructor(self, ...) + self.returned = false +end +function grimstroke_phantoms_embrace_custom.prototype.OnSpellStart(self) + local target = self:GetCursorTarget() + if not target then + return + end + local caster = self:GetCaster() + local spawnPos = caster:GetOrigin() + caster:GetForwardVector() * 150 + self.returned = false + local phantom = CreateUnitByName( + "npc_dota_grimstroke_ink_creature", + spawnPos, + true, + nil, + nil, + caster:GetTeamNumber() + ) + phantom:AddNewModifier( + caster, + self, + ____exports.modifier_grimstroke_phantoms_embrace_custom_thinker.name, + {target = target:entindex()} + ) + local effectCast = ParticleManager:CreateParticle("particles/units/heroes/hero_grimstroke/grimstroke_cast_phantom.vpcf", PATTACH_ABSORIGIN_FOLLOW, caster) + ParticleManager:ReleaseParticleIndex(effectCast) + self:SetActivated(false) + EmitSoundOn("Hero_Grimstroke.InkCreature.Cast", caster) +end +function grimstroke_phantoms_embrace_custom.prototype.OnProjectileHit(self, _target) + local caster = self:GetCaster() + self:EndCooldown() + self:SetActivated(true) + self.returned = true + Timers:CreateTimer( + self:GetSpecialValueFor("return_time_free"), + function() + self.returned = false + return nil + end + ) + EmitSoundOn("Hero_Grimstroke.InkCreature.Returned", caster) +end +grimstroke_phantoms_embrace_custom = __TS__Decorate( + grimstroke_phantoms_embrace_custom, + grimstroke_phantoms_embrace_custom, + {registerAbility(nil)}, + {kind = "class", name = "grimstroke_phantoms_embrace_custom"} +) +____exports.grimstroke_phantoms_embrace_custom = grimstroke_phantoms_embrace_custom +____exports.modifier_grimstroke_phantoms_embrace_custom_thinker = __TS__Class() +local modifier_grimstroke_phantoms_embrace_custom_thinker = ____exports.modifier_grimstroke_phantoms_embrace_custom_thinker +modifier_grimstroke_phantoms_embrace_custom_thinker.name = "modifier_grimstroke_phantoms_embrace_custom_thinker" +modifier_grimstroke_phantoms_embrace_custom_thinker.____file_path = "scripts/vscripts/abilities/heroes/grimstroke/grimstroke_phantoms_embrace_custom.lua" +__TS__ClassExtends(modifier_grimstroke_phantoms_embrace_custom_thinker, BaseModifierMotionHorizontal) +function modifier_grimstroke_phantoms_embrace_custom_thinker.prototype.____constructor(self, ...) + BaseModifierMotionHorizontal.prototype.____constructor(self, ...) + self.latchOffset = 80 + self.latchDuration = 0 + self.speed = 0 + self.tick = 0 + self.latching = false +end +function modifier_grimstroke_phantoms_embrace_custom_thinker.prototype.OnCreated(self, params) + if IsClient() then + return + end + local ability = self:GetAbility() + self.target = EntIndexToHScript(params.target) + self.speed = ability:GetSpecialValueFor("speed") + self.latchDuration = ability:GetSpecialValueFor("latch_duration") + self.tick = ability:GetSpecialValueFor("tick") + if not self:ApplyHorizontalMotionController() then + self:Destroy() + end +end +function modifier_grimstroke_phantoms_embrace_custom_thinker.prototype.OnDestroy(self) + if IsClient() then + return + end + local parent = self:GetParent() + parent:InterruptMotionControllers(true) + self.target:RemoveModifierByName(____exports.modifier_grimstroke_phantoms_embrace_custom_debuff.name) + local effectCast = ParticleManager:CreateParticle("particles/units/heroes/hero_grimstroke/grimstroke_phantom_death.vpcf", PATTACH_ABSORIGIN_FOLLOW, parent) + ParticleManager:ReleaseParticleIndex(effectCast) + EmitSoundOn("Hero_Grimstroke.InkCreature.Death", parent) + if not self.latching then + local ____opt_0 = self:GetAbility() + if ____opt_0 ~= nil then + ____opt_0:SetActivated(true) + end + end + parent:ForceKill(false) +end +function modifier_grimstroke_phantoms_embrace_custom_thinker.prototype.CheckState(self) + return {[MODIFIER_STATE_INVULNERABLE] = true, [MODIFIER_STATE_NO_HEALTH_BAR] = true} +end +function modifier_grimstroke_phantoms_embrace_custom_thinker.prototype.DeclareFunctions(self) + return {MODIFIER_PROPERTY_OVERRIDE_ANIMATION} +end +function modifier_grimstroke_phantoms_embrace_custom_thinker.prototype.GetOverrideAnimation(self) + local ____table_latching_2 + if self.latching then + ____table_latching_2 = ACT_DOTA_CAPTURE + else + ____table_latching_2 = ACT_DOTA_RUN + end + return ____table_latching_2 +end +function modifier_grimstroke_phantoms_embrace_custom_thinker.prototype.UpdateHorizontalMotion(self, me, dt) + if self.latching then + self:latch(me, dt) + return + end + if (self.target:GetOrigin() - self:GetParent():GetOrigin()):Length2D() < self.latchOffset then + self:setLatching() + return + end + self:charge(me, dt) +end +function modifier_grimstroke_phantoms_embrace_custom_thinker.prototype.OnHorizontalMotionInterrupted(self) + if IsServer() then + self:Destroy() + end +end +function modifier_grimstroke_phantoms_embrace_custom_thinker.prototype.charge(self, _me, dt) + local parent = self:GetParent() + local point = parent:GetOrigin() + local targetPoint = self.target:GetOrigin() + local direction = targetPoint - point + direction.z = 0 + local target = point + direction:Normalized() * (self.speed * dt) + parent:SetOrigin(target) + parent:FaceTowards(targetPoint) +end +function modifier_grimstroke_phantoms_embrace_custom_thinker.prototype.latch(self, _me, _dt) + local parent = self:GetParent() + local target = self.target:GetOrigin() + self.target:GetForwardVector() * self.latchOffset + parent:SetOrigin(target) + parent:FaceTowards(self.target:GetOrigin()) +end +function modifier_grimstroke_phantoms_embrace_custom_thinker.prototype.setLatching(self) + if not self.target:IsAlive() then + self:Destroy() + return + end + self.latching = true + self:SetStackCount(1) + self:SetDuration(self.latchDuration, false) + ____exports.modifier_grimstroke_phantoms_embrace_custom_debuff:apply( + self.target, + self:GetCaster(), + self:GetAbility(), + {phantom = self:GetParent():entindex()} + ) + EmitSoundOn( + "Hero_Grimstroke.InkCreature.Attach", + self:GetParent() + ) +end +modifier_grimstroke_phantoms_embrace_custom_thinker = __TS__Decorate( + modifier_grimstroke_phantoms_embrace_custom_thinker, + modifier_grimstroke_phantoms_embrace_custom_thinker, + {registerModifier(nil)}, + {kind = "class", name = "modifier_grimstroke_phantoms_embrace_custom_thinker"} +) +____exports.modifier_grimstroke_phantoms_embrace_custom_thinker = modifier_grimstroke_phantoms_embrace_custom_thinker +____exports.modifier_grimstroke_phantoms_embrace_custom_debuff = __TS__Class() +local modifier_grimstroke_phantoms_embrace_custom_debuff = ____exports.modifier_grimstroke_phantoms_embrace_custom_debuff +modifier_grimstroke_phantoms_embrace_custom_debuff.name = "modifier_grimstroke_phantoms_embrace_custom_debuff" +modifier_grimstroke_phantoms_embrace_custom_debuff.____file_path = "scripts/vscripts/abilities/heroes/grimstroke/grimstroke_phantoms_embrace_custom.lua" +__TS__ClassExtends(modifier_grimstroke_phantoms_embrace_custom_debuff, BaseModifier) +function modifier_grimstroke_phantoms_embrace_custom_debuff.prototype.____constructor(self, ...) + BaseModifier.prototype.____constructor(self, ...) + self.tick = 0 + self.damage = 0 + self.radiusFind = 0 + self.damageIncoming = 0 +end +function modifier_grimstroke_phantoms_embrace_custom_debuff.prototype.IsPurgable(self) + return false +end +function modifier_grimstroke_phantoms_embrace_custom_debuff.prototype.RemoveOnDeath(self) + return true +end +function modifier_grimstroke_phantoms_embrace_custom_debuff.prototype.DeclareFunctions(self) + return {MODIFIER_PROPERTY_INCOMING_DAMAGE_PERCENTAGE} +end +function modifier_grimstroke_phantoms_embrace_custom_debuff.prototype.GetModifierIncomingDamage_Percentage(self, event) + if event.target == self:GetParent() and event.attacker == self:GetCaster() then + return self.damageIncoming + end + return 0 +end +function modifier_grimstroke_phantoms_embrace_custom_debuff.prototype.OnCreated(self, params) + local ability = self:GetAbility() + self.tick = ability:GetSpecialValueFor("tick") + self.damage = ability:GetSpecialValueFor("damage") + self.radiusFind = ability:GetSpecialValueFor("radius_find") + self.damageIncoming = ability:GetSpecialValueFor("damage_incoming") + if IsClient() then + return + end + self.phantom = EntIndexToHScript(params.phantom) + local parent = self:GetParent() + local effectCast = ParticleManager:CreateParticle("particles/units/heroes/hero_grimstroke/grimstroke_phantom_ambient.vpcf", PATTACH_ABSORIGIN_FOLLOW, parent) + ParticleManager:SetParticleControlEnt( + effectCast, + 0, + parent, + PATTACH_POINT_FOLLOW, + "attach_hitloc", + Vector(0, 0, 0), + true + ) + self:AddParticle( + effectCast, + false, + false, + -1, + false, + false + ) + EmitSoundOn("Hero_Grimstroke.InkCreature.Spawn", parent) + self:StartIntervalThink(self.tick) +end +function modifier_grimstroke_phantoms_embrace_custom_debuff.prototype.OnDestroy(self) + if IsClient() then + return + end + local parent = self:GetParent() + local ability = self:GetAbility() + self.phantom:ForceKill(false) + if parent:IsAlive() then + ability:SetActivated(true) + return + end + local caster = self:GetCaster() + local enemy = FindUnitsInRadius( + caster:GetTeamNumber(), + parent:GetOrigin(), + nil, + self.radiusFind, + DOTA_UNIT_TARGET_TEAM_ENEMY, + bit.bor(DOTA_UNIT_TARGET_HERO, DOTA_UNIT_TARGET_BASIC), + DOTA_UNIT_TARGET_FLAG_NONE, + FIND_CLOSEST, + false + )[1] + if enemy then + UTIL_Remove(self.phantom) + local spawnPos = parent:GetOrigin() + parent:GetForwardVector() * 150 + local phantom = CreateUnitByName( + "npc_dota_grimstroke_ink_creature", + spawnPos, + true, + nil, + nil, + caster:GetTeamNumber() + ) + phantom:AddNewModifier( + caster, + ability, + ____exports.modifier_grimstroke_phantoms_embrace_custom_thinker.name, + {target = enemy:entindex()} + ) + return + end + ProjectileManager:CreateTrackingProjectile({ + Target = caster, + Source = parent, + Ability = ability, + EffectName = "particles/units/heroes/hero_grimstroke/grimstroke_phantom_return.vpcf", + iMoveSpeed = ability:GetSpecialValueFor("speed"), + bDodgeable = true + }) +end +function modifier_grimstroke_phantoms_embrace_custom_debuff.prototype.OnIntervalThink(self) + if IsClient() then + return + end + local ability = self:GetAbility() + ApplyDamage({ + victim = self:GetParent(), + attacker = self:GetCaster(), + damage = self.damage * self.tick, + damage_type = ability:GetAbilityDamageType(), + ability = ability + }) +end +modifier_grimstroke_phantoms_embrace_custom_debuff = __TS__Decorate( + modifier_grimstroke_phantoms_embrace_custom_debuff, + modifier_grimstroke_phantoms_embrace_custom_debuff, + {registerModifier(nil)}, + {kind = "class", name = "modifier_grimstroke_phantoms_embrace_custom_debuff"} +) +____exports.modifier_grimstroke_phantoms_embrace_custom_debuff = modifier_grimstroke_phantoms_embrace_custom_debuff +return ____exports diff --git a/scripts/vscripts/abilities/heroes/grimstroke/grimstroke_stroke_of_fate_custom.lua b/scripts/vscripts/abilities/heroes/grimstroke/grimstroke_stroke_of_fate_custom.lua new file mode 100644 index 0000000..681b795 --- /dev/null +++ b/scripts/vscripts/abilities/heroes/grimstroke/grimstroke_stroke_of_fate_custom.lua @@ -0,0 +1,315 @@ +local ____lualib = require("lualib_bundle") +local __TS__Class = ____lualib.__TS__Class +local __TS__ClassExtends = ____lualib.__TS__ClassExtends +local __TS__Decorate = ____lualib.__TS__Decorate +local ____exports = {} +local ____dota_ts_adapter = require("lib.dota_ts_adapter") +local BaseAbility = ____dota_ts_adapter.BaseAbility +local BaseModifier = ____dota_ts_adapter.BaseModifier +local registerAbility = ____dota_ts_adapter.registerAbility +local registerModifier = ____dota_ts_adapter.registerModifier +____exports.grimstroke_stroke_of_fate_custom = __TS__Class() +local grimstroke_stroke_of_fate_custom = ____exports.grimstroke_stroke_of_fate_custom +grimstroke_stroke_of_fate_custom.name = "grimstroke_stroke_of_fate_custom" +grimstroke_stroke_of_fate_custom.____file_path = "scripts/vscripts/abilities/heroes/grimstroke/grimstroke_stroke_of_fate_custom.lua" +__TS__ClassExtends(grimstroke_stroke_of_fate_custom, BaseAbility) +function grimstroke_stroke_of_fate_custom.prototype.____constructor(self, ...) + BaseAbility.prototype.____constructor(self, ...) + self.activeProj = {} + self.bentWaveId = 0 + self.projectileWaveId = {} + self.waveState = {} +end +function grimstroke_stroke_of_fate_custom.prototype.GetVectorTargetRange(self) + return self:GetCastRange( + self:GetCaster():GetAbsOrigin(), + nil + ) +end +function grimstroke_stroke_of_fate_custom.prototype.GetVectorTargetStartRadius(self) + return self:GetSpecialValueFor("start_radius") +end +function grimstroke_stroke_of_fate_custom.prototype.GetVectorTargetEndRadius(self) + return self:GetSpecialValueFor("end_radius") +end +function grimstroke_stroke_of_fate_custom.prototype.GetVectorPosition(self) + local ____self_vectorTargetPosition_0 = self.vectorTargetPosition + if ____self_vectorTargetPosition_0 == nil then + ____self_vectorTargetPosition_0 = self:GetCursorPosition() + end + return ____self_vectorTargetPosition_0 +end +function grimstroke_stroke_of_fate_custom.prototype.GetVector2Position(self) + local ____self_vectorTargetPosition2_1 = self.vectorTargetPosition2 + if ____self_vectorTargetPosition2_1 == nil then + ____self_vectorTargetPosition2_1 = self:GetCursorPosition() + end + return ____self_vectorTargetPosition2_1 +end +function grimstroke_stroke_of_fate_custom.prototype.GetVectorDirection(self) + local ____self_vectorTargetDirection_2 = self.vectorTargetDirection + if ____self_vectorTargetDirection_2 == nil then + ____self_vectorTargetDirection_2 = self:GetCaster():GetForwardVector() + end + return ____self_vectorTargetDirection_2 +end +function grimstroke_stroke_of_fate_custom.prototype.UpdateVectorValues(self) + CustomNetTables:SetTableValue( + "vector_targeting", + tostring(self:entindex()), + { + startWidth = self:GetVectorTargetStartRadius(), + endWidth = self:GetVectorTargetEndRadius(), + castLength = self:GetVectorTargetRange(), + dual = false, + ignoreArrow = false + } + ) +end +function grimstroke_stroke_of_fate_custom.prototype.IsDualVectorDirection(self) + return true +end +function grimstroke_stroke_of_fate_custom.prototype.IgnoreVectorArrowWidth(self) + return true +end +function grimstroke_stroke_of_fate_custom.prototype.GetCastRange(self, _location, _target) + return self:GetSpecialValueFor("range") > 0 and self:GetSpecialValueFor("range") or BaseAbility.prototype.GetCastRange(self, _location, _target) +end +function grimstroke_stroke_of_fate_custom.prototype.OnAbilityPhaseStart(self) + local particleCast = "particles/units/heroes/hero_grimstroke/grimstroke_cast2_ground.vpcf" + local soundPrecast = "Hero_Grimstroke.DarkArtistry.PreCastPoint" + self.effectCast = ParticleManager:CreateParticle( + particleCast, + PATTACH_ABSORIGIN_FOLLOW, + self:GetCaster() + ) + ParticleManager:SetParticleControlEnt( + self.effectCast, + 0, + self:GetCaster(), + PATTACH_POINT_FOLLOW, + "attach_attack2", + Vector(0, 1, 1), + true + ) + ParticleManager:ReleaseParticleIndex(self.effectCast) + EmitSoundOn( + soundPrecast, + self:GetCaster() + ) + return true +end +function grimstroke_stroke_of_fate_custom.prototype.OnAbilityPhaseInterrupted(self) + local soundPrecast = "Hero_Grimstroke.DarkArtistry.PreCastPoint" + if self.effectCast then + ParticleManager:DestroyParticle(self.effectCast, true) + end + StopSoundOn( + soundPrecast, + self:GetCaster() + ) +end +function grimstroke_stroke_of_fate_custom.prototype.OnVectorCastStart(self, vStartLocation, vDirection) + local caster = self:GetCaster() + local castOrigin = caster:GetAbsOrigin() + local startPoint = vStartLocation + local bendDirection = vDirection + local projectileName = "particles/units/heroes/hero_grimstroke/grimstroke_darkartistry_proj.vpcf" + local secondSegmentDistance = self:GetSpecialValueFor("range") + local startRadius = self:GetSpecialValueFor("start_radius") + local endRadius = self:GetSpecialValueFor("end_radius") + local speed = self:GetSpecialValueFor("projectile_speed") + bendDirection.z = 0 + if bendDirection:Length2D() < 1 then + bendDirection = caster:GetForwardVector() + bendDirection.z = 0 + end + bendDirection = bendDirection:Normalized() + local firstSegmentDirection = startPoint - castOrigin + firstSegmentDirection.z = 0 + if firstSegmentDirection:Length2D() < 1 then + firstSegmentDirection = caster:GetForwardVector() + firstSegmentDirection.z = 0 + end + local firstSegmentDistance = firstSegmentDirection:Length2D() + firstSegmentDirection = firstSegmentDirection:Normalized() + self.bentWaveId = self.bentWaveId + 1 + local waveId = self.bentWaveId + self.waveState[waveId] = {hitCounter = 0, hitTargets = {}} + if firstSegmentDistance > 1 then + local firstProjectile = ProjectileManager:CreateLinearProjectile({ + Source = caster, + Ability = self, + vSpawnOrigin = castOrigin, + iUnitTargetTeam = DOTA_UNIT_TARGET_TEAM_ENEMY, + iUnitTargetFlags = DOTA_UNIT_TARGET_FLAG_NONE, + iUnitTargetType = bit.bor(DOTA_UNIT_TARGET_BASIC, DOTA_UNIT_TARGET_HERO), + EffectName = projectileName, + fDistance = firstSegmentDistance, + fStartRadius = startRadius, + fEndRadius = endRadius, + vVelocity = firstSegmentDirection * speed, + bHasFrontalCone = false, + bProvidesVision = false + }) + self.projectileWaveId[firstProjectile] = waveId + end + local secondDelay = firstSegmentDistance > 1 and firstSegmentDistance / speed or 0 + Timers:CreateTimer( + secondDelay, + function() + if self.bentWaveId ~= waveId or not caster or caster:IsNull() then + return nil + end + local secondProjectile = ProjectileManager:CreateLinearProjectile({ + Source = caster, + Ability = self, + vSpawnOrigin = startPoint, + iUnitTargetTeam = DOTA_UNIT_TARGET_TEAM_ENEMY, + iUnitTargetFlags = DOTA_UNIT_TARGET_FLAG_NONE, + iUnitTargetType = bit.bor(DOTA_UNIT_TARGET_BASIC, DOTA_UNIT_TARGET_HERO), + EffectName = projectileName, + fDistance = secondSegmentDistance, + fStartRadius = startRadius, + fEndRadius = endRadius, + vVelocity = bendDirection * speed, + bHasFrontalCone = false, + bProvidesVision = false + }) + self.projectileWaveId[secondProjectile] = waveId + return nil + end + ) + EmitSoundOn("Hero_Grimstroke.DarkArtistry.Cast", caster) + EmitSoundOn("Hero_Grimstroke.DarkArtistry.Cast.Layer", caster) +end +function grimstroke_stroke_of_fate_custom.prototype.OnSpellStart(self) + local caster = self:GetCaster() + local cursorPoint = self:GetCursorPosition() + local fallbackDirection = cursorPoint - caster:GetAbsOrigin() + self:OnVectorCastStart(cursorPoint, fallbackDirection) +end +function grimstroke_stroke_of_fate_custom.prototype.OnProjectileHitHandle(self, target, _location, projectileHandle) + if not IsServer() then + return + end + local waveId = self.projectileWaveId[projectileHandle] + local ____temp_3 + if waveId ~= nil then + ____temp_3 = self.waveState[waveId] + else + ____temp_3 = nil + end + local state = ____temp_3 + if not target then + self.activeProj[projectileHandle] = nil + self.projectileWaveId[projectileHandle] = nil + return true + end + if state then + local targetId = target:entindex() + if state.hitTargets[targetId] then + return + end + state.hitTargets[targetId] = true + elseif not self.activeProj[projectileHandle] then + self.activeProj[projectileHandle] = 0 + end + local multiplier = state and state.hitCounter or (self.activeProj[projectileHandle] or 0) + local baseDamage = self:GetAbilityDamage() + local bonusDamage = self:GetSpecialValueFor("bonus_damage_per_target") + local duration = self:GetSpecialValueFor("duration") + ApplyDamage({ + victim = target, + attacker = self:GetCaster(), + damage = baseDamage + bonusDamage * multiplier, + damage_type = DAMAGE_TYPE_MAGICAL, + ability = self + }) + target:AddNewModifier( + self:GetCaster(), + self, + ____exports.modifier_grimstroke_stroke_of_fate_root.name, + {duration = duration} + ) + local effectCast = ParticleManager:CreateParticle("particles/units/heroes/hero_grimstroke/grimstroke_darkartistry_dmg.vpcf", PATTACH_ABSORIGIN_FOLLOW, target) + ParticleManager:ReleaseParticleIndex(effectCast) + if state then + state.hitCounter = state.hitCounter + 1 + else + local ____self_activeProj_4, ____projectileHandle_5 = self.activeProj, projectileHandle + ____self_activeProj_4[____projectileHandle_5] = ____self_activeProj_4[____projectileHandle_5] + 1 + end + EmitSoundOn( + target:IsCreep() and "Hero_Grimstroke.DarkArtistry.Damage.Creep" or "Hero_Grimstroke.DarkArtistry.Damage", + target + ) +end +grimstroke_stroke_of_fate_custom = __TS__Decorate( + grimstroke_stroke_of_fate_custom, + grimstroke_stroke_of_fate_custom, + {registerAbility(nil)}, + {kind = "class", name = "grimstroke_stroke_of_fate_custom"} +) +____exports.grimstroke_stroke_of_fate_custom = grimstroke_stroke_of_fate_custom +____exports.modifier_grimstroke_stroke_of_fate_root = __TS__Class() +local modifier_grimstroke_stroke_of_fate_root = ____exports.modifier_grimstroke_stroke_of_fate_root +modifier_grimstroke_stroke_of_fate_root.name = "modifier_grimstroke_stroke_of_fate_root" +modifier_grimstroke_stroke_of_fate_root.____file_path = "scripts/vscripts/abilities/heroes/grimstroke/grimstroke_stroke_of_fate_custom.lua" +__TS__ClassExtends(modifier_grimstroke_stroke_of_fate_root, BaseModifier) +function modifier_grimstroke_stroke_of_fate_root.prototype.CheckState(self) + return {[MODIFIER_STATE_ROOTED] = true} +end +function modifier_grimstroke_stroke_of_fate_root.prototype.OnCreated(self) + self:StartIntervalThink(0.5) + if IsClient() then + return + end + self.effectCast = ParticleManager:CreateParticle( + "particles/units/heroes/hero_grimstroke/grimstroke_soulchain_debuff.vpcf", + PATTACH_ABSORIGIN_FOLLOW, + self:GetParent() + ) + ParticleManager:SetParticleControlEnt( + self.effectCast, + 2, + self:GetParent(), + PATTACH_ABSORIGIN_FOLLOW, + "attach_hitloc", + self:GetParent():GetOrigin(), + true + ) + self:AddParticle( + self.effectCast, + false, + false, + -1, + false, + false + ) +end +function modifier_grimstroke_stroke_of_fate_root.prototype.OnDestroy(self) + if self.effectCast ~= nil then + ParticleManager:DestroyParticle(self.effectCast, false) + end +end +function modifier_grimstroke_stroke_of_fate_root.prototype.OnIntervalThink(self) + if IsClient() then + return + end + ApplyDamage({ + victim = self:GetParent(), + attacker = self:GetCaster(), + damage = self:GetAbility():GetSpecialValueFor("interval_damage"), + damage_type = DAMAGE_TYPE_MAGICAL, + ability = self:GetAbility() + }) +end +modifier_grimstroke_stroke_of_fate_root = __TS__Decorate( + modifier_grimstroke_stroke_of_fate_root, + modifier_grimstroke_stroke_of_fate_root, + {registerModifier(nil)}, + {kind = "class", name = "modifier_grimstroke_stroke_of_fate_root"} +) +____exports.modifier_grimstroke_stroke_of_fate_root = modifier_grimstroke_stroke_of_fate_root +return ____exports diff --git a/scripts/vscripts/abilities/heroes/hoodwink/hoodwink_acorn_shot_custom.lua b/scripts/vscripts/abilities/heroes/hoodwink/hoodwink_acorn_shot_custom.lua new file mode 100644 index 0000000..33aab01 --- /dev/null +++ b/scripts/vscripts/abilities/heroes/hoodwink/hoodwink_acorn_shot_custom.lua @@ -0,0 +1,487 @@ +local ____lualib = require("lualib_bundle") +local __TS__Class = ____lualib.__TS__Class +local __TS__ClassExtends = ____lualib.__TS__ClassExtends +local __TS__ArrayFilter = ____lualib.__TS__ArrayFilter +local __TS__Delete = ____lualib.__TS__Delete +local __TS__ObjectAssign = ____lualib.__TS__ObjectAssign +local __TS__Decorate = ____lualib.__TS__Decorate +local ____exports = {} +local ____dota_ts_adapter = require("lib.dota_ts_adapter") +local BaseAbility = ____dota_ts_adapter.BaseAbility +local BaseModifier = ____dota_ts_adapter.BaseModifier +local registerAbility = ____dota_ts_adapter.registerAbility +local registerModifier = ____dota_ts_adapter.registerModifier +local ____hoodwink_sharpshooter_custom = require("abilities.heroes.hoodwink.hoodwink_sharpshooter_custom") +local hoodwink_sharpshooter_custom = ____hoodwink_sharpshooter_custom.hoodwink_sharpshooter_custom +local ____luck = require("utils.luck") +local rollLuckChance = ____luck.rollLuckChance +____exports.hoodwink_acorn_shot_custom = __TS__Class() +local hoodwink_acorn_shot_custom = ____exports.hoodwink_acorn_shot_custom +hoodwink_acorn_shot_custom.name = "hoodwink_acorn_shot_custom" +hoodwink_acorn_shot_custom.____file_path = "scripts/vscripts/abilities/heroes/hoodwink/hoodwink_acorn_shot_custom.lua" +__TS__ClassExtends(hoodwink_acorn_shot_custom, BaseAbility) +function hoodwink_acorn_shot_custom.prototype.getClosestTree(self, location, radius) + local trees = GridNav:GetAllTreesAroundPoint(location, radius, true) + if #trees == 0 then + return nil + end + local bestTree = trees[1] + local bestDist = bestTree:GetAbsOrigin():__sub(location):Length2D() + do + local i = 1 + while i < #trees do + local dist = trees[i + 1]:GetAbsOrigin():__sub(location):Length2D() + if dist < bestDist then + bestDist = dist + bestTree = trees[i + 1] + end + i = i + 1 + end + end + return bestTree +end +function hoodwink_acorn_shot_custom.prototype.Precache(self, context) + PrecacheResource("particle", "particles/tree_fx/tree_simple_explosion.vpcf", context) + PrecacheResource("particle", "particles/units/heroes/hero_hoodwink/hoodwink_acorn_shot_tree.vpcf", context) + PrecacheResource("particle", "particles/units/heroes/hero_hoodwink/hoodwink_acorn_shot_impact.vpcf", context) + PrecacheResource("particle", "particles/units/heroes/hero_hoodwink/hoodwink_acorn_shot_slow.vpcf", context) + PrecacheResource("particle", "particles/units/heroes/hero_hoodwink/hoodwink_acorn_shot_tracking.vpcf", context) +end +function hoodwink_acorn_shot_custom.prototype.GetIntrinsicModifierName(self) + return ____exports.modifier_hoodwink_acorn_shot_custom_scepter.name +end +function hoodwink_acorn_shot_custom.prototype.OnSpellStart(self) + local caster = self:GetCaster() + local target = self:GetCursorTarget() + local point = self:GetCursorPosition() + local bounceCount = self:GetSpecialValueFor("bounce_count") + local info = { + Ability = self, + Source = caster, + iMoveSpeed = self:GetSpecialValueFor("projectile_speed"), + bDodgeable = true, + iSourceAttachment = DOTA_PROJECTILE_ATTACHMENT_HITLOCATION, + bVisibleToEnemies = true, + bProvidesVision = true, + iVisionRadius = 200, + iVisionTeamNumber = caster:GetTeamNumber(), + EffectName = "particles/units/heroes/hero_hoodwink/hoodwink_acorn_shot_tracking.vpcf", + ExtraData = {count = bounceCount, nextTree = 0} + } + caster:EmitSound("Hero_Hoodwink.AcornShot.Cast") + if target then + info.Target = target + ProjectileManager:CreateTrackingProjectile(info) + return + end + point.z = point.z + 40 + info.vTargetLoc = point + ProjectileManager:CreateTrackingProjectile(info) +end +function hoodwink_acorn_shot_custom.prototype.ProjectileFindAndHitEnemy(self, target, location, data) + local caster = self:GetCaster() + local radius = self:GetSpecialValueFor("bounce_range") + local bounceDelay = self:GetSpecialValueFor("bounce_delay") + Timers:CreateTimer( + bounceDelay, + function() + local enemies = FindUnitsInRadius( + caster:GetTeamNumber(), + location, + nil, + radius, + DOTA_UNIT_TARGET_TEAM_ENEMY, + bit.bor(DOTA_UNIT_TARGET_HERO, DOTA_UNIT_TARGET_BASIC), + DOTA_UNIT_TARGET_FLAG_NONE, + FIND_CLOSEST, + false + ) + local canBounceTrees = self:GetSpecialValueFor("can_bounce_off_of_trees") > 0 + local candidates = __TS__ArrayFilter( + enemies, + function(____, unit) return unit:GetEntityIndex() ~= (target and target:GetEntityIndex()) end + ) + local nonRepeated = __TS__ArrayFilter( + candidates, + function(____, unit) return unit:GetEntityIndex() ~= data.lastEnemy end + ) + local enemy = nonRepeated[1] or candidates[1] + if enemy ~= nil then + __TS__Delete(data, "targetTree") + local enemyInfo = { + Ability = self, + Target = enemy, + EffectName = "particles/units/heroes/hero_hoodwink/hoodwink_acorn_shot_tracking.vpcf", + iMoveSpeed = self:GetSpecialValueFor("projectile_speed") / 2, + bDodgeable = true, + iSourceAttachment = DOTA_PROJECTILE_ATTACHMENT_HITLOCATION, + bVisibleToEnemies = true, + bProvidesVision = true, + iVisionRadius = 200, + iVisionTeamNumber = caster:GetTeamNumber(), + ExtraData = __TS__ObjectAssign({}, data, {nextTree = canBounceTrees and 1 or 0}) + } + if target then + enemyInfo.Source = target + target:EmitSound("Hero_Hoodwink.AcornShot.Bounce") + else + enemyInfo.vSourceLoc = location + end + ProjectileManager:CreateTrackingProjectile(enemyInfo) + return + end + local shouldGoTree = canBounceTrees and data.nextTree == 1 + if not shouldGoTree then + return + end + local tree = self:getClosestTree(location, radius) + if tree == nil then + return + end + local treeLoc = tree:GetAbsOrigin() + local treeTarget = CreateModifierThinker( + self:GetCaster(), + self, + "modifier_kill", + {duration = 1}, + treeLoc, + self:GetCaster():GetTeamNumber(), + false + ) + local treeInfo = { + Ability = self, + Target = treeTarget, + EffectName = "particles/units/heroes/hero_hoodwink/hoodwink_acorn_shot_tracking.vpcf", + iMoveSpeed = self:GetSpecialValueFor("projectile_speed") / 2, + bDodgeable = true, + iSourceAttachment = DOTA_PROJECTILE_ATTACHMENT_HITLOCATION, + bVisibleToEnemies = true, + bProvidesVision = true, + iVisionRadius = 200, + iVisionTeamNumber = caster:GetTeamNumber(), + ExtraData = __TS__ObjectAssign({}, data, {targetTree = 1, nextTree = 0}) + } + if target then + treeInfo.Source = target + target:EmitSound("Hero_Hoodwink.AcornShot.Bounce") + else + treeInfo.vSourceLoc = location + end + ProjectileManager:CreateTrackingProjectile(treeInfo) + end + ) +end +function hoodwink_acorn_shot_custom.prototype.OnProjectileHit_ExtraData(self, target, location, data) + local caster = self:GetCaster() + local hitLoc = target and target:GetAbsOrigin() or location + if data.targetTree ~= 1 then + if not target then + CreateModifierThinker( + caster, + self, + ____exports.modifier_hoodwink_acorn_shot_custom_thinker_tree.name, + {duration = self:GetSpecialValueFor("tree_duration")}, + hitLoc, + caster:GetTeamNumber(), + false + ) + else + if not target:TriggerSpellAbsorb(self) then + local modifier = ____exports.modifier_hoodwink_acorn_shot_custom_bonus_damage:apply(caster, caster, self, {}) + caster:PerformAttack( + target, + false, + true, + true, + false, + false, + false, + true + ) + modifier:Destroy() + local reduceCooldownUltimate = self:GetSpecialValueFor("reduce_cooldown_ultimate") + if reduceCooldownUltimate > 0 then + local ultimate = caster:FindAbilityByName(hoodwink_sharpshooter_custom.name) + if ultimate and not ultimate:IsCooldownReady() then + local time = ultimate:GetCooldownTimeRemaining() + ultimate:EndCooldown() + ultimate:StartCooldown(math.max(0, time - reduceCooldownUltimate)) + end + end + end + target:EmitSound("Hero_Hoodwink.AcornShot.Target") + local particle = ParticleManager:CreateParticle("particles/units/heroes/hero_hoodwink/hoodwink_acorn_shot_impact.vpcf", PATTACH_CUSTOMORIGIN, target) + ParticleManager:SetParticleControlEnt( + particle, + 0, + target, + PATTACH_POINT_FOLLOW, + "attach_hitloc", + target:GetAbsOrigin(), + true + ) + ParticleManager:ReleaseParticleIndex(particle) + ____exports.modifier_hoodwink_acorn_shot_custom_debuff:apply( + target, + caster, + self, + {duration = self:GetSpecialValueFor("debuff_duration")} + ) + data.lastEnemy = target:GetEntityIndex() + data.nextTree = self:GetSpecialValueFor("can_bounce_off_of_trees") > 0 and 1 or 0 + end + else + EmitSoundOnLocationWithCaster(hitLoc, "Hero_Hoodwink.AcornShot.Target", caster) + data.nextTree = 0 + end + data.count = data.count - 1 + if data.count <= 0 then + return + end + self:ProjectileFindAndHitEnemy(target, hitLoc, data) +end +hoodwink_acorn_shot_custom = __TS__Decorate( + hoodwink_acorn_shot_custom, + hoodwink_acorn_shot_custom, + {registerAbility(nil)}, + {kind = "class", name = "hoodwink_acorn_shot_custom"} +) +____exports.hoodwink_acorn_shot_custom = hoodwink_acorn_shot_custom +____exports.modifier_hoodwink_acorn_shot_custom_debuff = __TS__Class() +local modifier_hoodwink_acorn_shot_custom_debuff = ____exports.modifier_hoodwink_acorn_shot_custom_debuff +modifier_hoodwink_acorn_shot_custom_debuff.name = "modifier_hoodwink_acorn_shot_custom_debuff" +modifier_hoodwink_acorn_shot_custom_debuff.____file_path = "scripts/vscripts/abilities/heroes/hoodwink/hoodwink_acorn_shot_custom.lua" +__TS__ClassExtends(modifier_hoodwink_acorn_shot_custom_debuff, BaseModifier) +function modifier_hoodwink_acorn_shot_custom_debuff.prototype.____constructor(self, ...) + BaseModifier.prototype.____constructor(self, ...) + self.slow = 0 +end +function modifier_hoodwink_acorn_shot_custom_debuff.prototype.IsHidden(self) + return false +end +function modifier_hoodwink_acorn_shot_custom_debuff.prototype.IsPurgable(self) + return true +end +function modifier_hoodwink_acorn_shot_custom_debuff.prototype.RemoveOnDeath(self) + return true +end +function modifier_hoodwink_acorn_shot_custom_debuff.prototype.OnCreated(self) + local ability = self:GetAbility() + if not ability then + return + end + self.slow = ability:GetSpecialValueFor("slow") +end +function modifier_hoodwink_acorn_shot_custom_debuff.prototype.GetEffectName(self) + return "particles/units/heroes/hero_hoodwink/hoodwink_acorn_shot_slow.vpcf" +end +function modifier_hoodwink_acorn_shot_custom_debuff.prototype.DeclareFunctions(self) + return {MODIFIER_PROPERTY_MOVESPEED_BONUS_PERCENTAGE} +end +function modifier_hoodwink_acorn_shot_custom_debuff.prototype.GetModifierMoveSpeedBonus_Percentage(self) + return -self.slow +end +modifier_hoodwink_acorn_shot_custom_debuff = __TS__Decorate( + modifier_hoodwink_acorn_shot_custom_debuff, + modifier_hoodwink_acorn_shot_custom_debuff, + {registerModifier(nil)}, + {kind = "class", name = "modifier_hoodwink_acorn_shot_custom_debuff"} +) +____exports.modifier_hoodwink_acorn_shot_custom_debuff = modifier_hoodwink_acorn_shot_custom_debuff +____exports.modifier_hoodwink_acorn_shot_custom_bonus_damage = __TS__Class() +local modifier_hoodwink_acorn_shot_custom_bonus_damage = ____exports.modifier_hoodwink_acorn_shot_custom_bonus_damage +modifier_hoodwink_acorn_shot_custom_bonus_damage.name = "modifier_hoodwink_acorn_shot_custom_bonus_damage" +modifier_hoodwink_acorn_shot_custom_bonus_damage.____file_path = "scripts/vscripts/abilities/heroes/hoodwink/hoodwink_acorn_shot_custom.lua" +__TS__ClassExtends(modifier_hoodwink_acorn_shot_custom_bonus_damage, BaseModifier) +function modifier_hoodwink_acorn_shot_custom_bonus_damage.prototype.____constructor(self, ...) + BaseModifier.prototype.____constructor(self, ...) + self.bonusDamage = 0 + self.damagePercentage = 0 +end +function modifier_hoodwink_acorn_shot_custom_bonus_damage.prototype.IsHidden(self) + return true +end +function modifier_hoodwink_acorn_shot_custom_bonus_damage.prototype.IsPurgable(self) + return false +end +function modifier_hoodwink_acorn_shot_custom_bonus_damage.prototype.IsPurgeException(self) + return false +end +function modifier_hoodwink_acorn_shot_custom_bonus_damage.prototype.RemoveOnDeath(self) + return false +end +function modifier_hoodwink_acorn_shot_custom_bonus_damage.prototype.OnCreated(self) + local ability = self:GetAbility() + if not ability then + return + end + self.bonusDamage = ability:GetSpecialValueFor("acorn_shot_damage") + self.damagePercentage = ability:GetSpecialValueFor("base_damage_pct") - 100 +end +function modifier_hoodwink_acorn_shot_custom_bonus_damage.prototype.DeclareFunctions(self) + return {MODIFIER_PROPERTY_PREATTACK_BONUS_DAMAGE, MODIFIER_PROPERTY_DAMAGEOUTGOING_PERCENTAGE} +end +function modifier_hoodwink_acorn_shot_custom_bonus_damage.prototype.GetModifierPreAttack_BonusDamage(self) + return self.bonusDamage +end +function modifier_hoodwink_acorn_shot_custom_bonus_damage.prototype.GetModifierDamageOutgoing_Percentage(self) + return self.damagePercentage +end +modifier_hoodwink_acorn_shot_custom_bonus_damage = __TS__Decorate( + modifier_hoodwink_acorn_shot_custom_bonus_damage, + modifier_hoodwink_acorn_shot_custom_bonus_damage, + {registerModifier(nil)}, + {kind = "class", name = "modifier_hoodwink_acorn_shot_custom_bonus_damage"} +) +____exports.modifier_hoodwink_acorn_shot_custom_bonus_damage = modifier_hoodwink_acorn_shot_custom_bonus_damage +____exports.modifier_hoodwink_acorn_shot_custom_thinker_tree = __TS__Class() +local modifier_hoodwink_acorn_shot_custom_thinker_tree = ____exports.modifier_hoodwink_acorn_shot_custom_thinker_tree +modifier_hoodwink_acorn_shot_custom_thinker_tree.name = "modifier_hoodwink_acorn_shot_custom_thinker_tree" +modifier_hoodwink_acorn_shot_custom_thinker_tree.____file_path = "scripts/vscripts/abilities/heroes/hoodwink/hoodwink_acorn_shot_custom.lua" +__TS__ClassExtends(modifier_hoodwink_acorn_shot_custom_thinker_tree, BaseModifier) +function modifier_hoodwink_acorn_shot_custom_thinker_tree.prototype.____constructor(self, ...) + BaseModifier.prototype.____constructor(self, ...) + self.treeOrigin = Vector(0, 0, 0) +end +function modifier_hoodwink_acorn_shot_custom_thinker_tree.prototype.IsHidden(self) + return true +end +function modifier_hoodwink_acorn_shot_custom_thinker_tree.prototype.OnCreated(self) + if not IsServer() then + return + end + local parent = self:GetParent() + local ability = self:GetAbility() + local caster = self:GetCaster() + if not ability or not caster then + return + end + self.treeOrigin = parent:GetAbsOrigin() + CreateTempTree( + self.treeOrigin, + ability:GetSpecialValueFor("tree_duration") + ) + self.viewer = AddFOWViewer( + caster:GetTeamNumber(), + self.treeOrigin, + ability:GetSpecialValueFor("tree_radius"), + ability:GetSpecialValueFor("tree_duration"), + false + ) + local units = FindUnitsInRadius( + parent:GetTeamNumber(), + self.treeOrigin, + nil, + 100, + DOTA_UNIT_TARGET_TEAM_BOTH, + DOTA_UNIT_TARGET_ALL, + DOTA_UNIT_TARGET_FLAG_MAGIC_IMMUNE_ENEMIES, + FIND_CLOSEST, + false + ) + for ____, unit in ipairs(units) do + FindClearSpaceForUnit( + unit, + unit:GetAbsOrigin(), + true + ) + end + local particle2 = ParticleManager:CreateParticle("particles/tree_fx/tree_simple_explosion.vpcf", PATTACH_WORLDORIGIN, nil) + ParticleManager:SetParticleControl( + particle2, + 0, + self.treeOrigin:__add(Vector(1, 0, 0)) + ) + ParticleManager:ReleaseParticleIndex(particle2) +end +function modifier_hoodwink_acorn_shot_custom_thinker_tree.prototype.OnDestroy(self) + if not IsServer() then + return + end + GridNav:DestroyTreesAroundPoint( + self:GetParent():GetAbsOrigin(), + 10, + true + ) + local caster = self:GetCaster() + if self.viewer and caster then + RemoveFOWViewer( + caster:GetTeamNumber(), + self.viewer + ) + self.viewer = nil + end +end +modifier_hoodwink_acorn_shot_custom_thinker_tree = __TS__Decorate( + modifier_hoodwink_acorn_shot_custom_thinker_tree, + modifier_hoodwink_acorn_shot_custom_thinker_tree, + {registerModifier(nil)}, + {kind = "class", name = "modifier_hoodwink_acorn_shot_custom_thinker_tree"} +) +____exports.modifier_hoodwink_acorn_shot_custom_thinker_tree = modifier_hoodwink_acorn_shot_custom_thinker_tree +____exports.modifier_hoodwink_acorn_shot_custom_scepter = __TS__Class() +local modifier_hoodwink_acorn_shot_custom_scepter = ____exports.modifier_hoodwink_acorn_shot_custom_scepter +modifier_hoodwink_acorn_shot_custom_scepter.name = "modifier_hoodwink_acorn_shot_custom_scepter" +modifier_hoodwink_acorn_shot_custom_scepter.____file_path = "scripts/vscripts/abilities/heroes/hoodwink/hoodwink_acorn_shot_custom.lua" +__TS__ClassExtends(modifier_hoodwink_acorn_shot_custom_scepter, BaseModifier) +function modifier_hoodwink_acorn_shot_custom_scepter.prototype.IsHidden(self) + return true +end +function modifier_hoodwink_acorn_shot_custom_scepter.prototype.IsPurgable(self) + return false +end +function modifier_hoodwink_acorn_shot_custom_scepter.prototype.IsPurgeException(self) + return false +end +function modifier_hoodwink_acorn_shot_custom_scepter.prototype.DeclareFunctions(self) + return {MODIFIER_EVENT_ON_ATTACK_LANDED} +end +function modifier_hoodwink_acorn_shot_custom_scepter.prototype.OnAttackLanded(self, event) + local parent = self:GetParent() + if parent:PassivesDisabled() then + return + end + if event.attacker ~= parent or not parent:HasScepter() or event.no_attack_cooldown then + return + end + local ability = self:GetAbility() + if not ability then + return + end + if not parent:IsHero() then + return + end + local scepterChancePct = ability:GetSpecialValueFor("scepter_chance") + if rollLuckChance(nil, parent, scepterChancePct / 100) then + local target = event.target + if not target then + return + end + local info = { + Ability = ability, + Source = parent, + Target = target, + EffectName = "particles/units/heroes/hero_hoodwink/hoodwink_acorn_shot_tracking.vpcf", + iMoveSpeed = ability:GetSpecialValueFor("projectile_speed"), + bDodgeable = true, + iSourceAttachment = DOTA_PROJECTILE_ATTACHMENT_HITLOCATION, + bVisibleToEnemies = true, + bProvidesVision = true, + iVisionRadius = 200, + iVisionTeamNumber = parent:GetTeamNumber(), + ExtraData = { + count = ability:GetSpecialValueFor("bounce_count_scepter"), + nextTree = 0 + } + } + ProjectileManager:CreateTrackingProjectile(info) + end +end +modifier_hoodwink_acorn_shot_custom_scepter = __TS__Decorate( + modifier_hoodwink_acorn_shot_custom_scepter, + modifier_hoodwink_acorn_shot_custom_scepter, + {registerModifier(nil)}, + {kind = "class", name = "modifier_hoodwink_acorn_shot_custom_scepter"} +) +____exports.modifier_hoodwink_acorn_shot_custom_scepter = modifier_hoodwink_acorn_shot_custom_scepter +return ____exports diff --git a/scripts/vscripts/abilities/heroes/hoodwink/hoodwink_bushwhack_custom.lua b/scripts/vscripts/abilities/heroes/hoodwink/hoodwink_bushwhack_custom.lua new file mode 100644 index 0000000..5450089 --- /dev/null +++ b/scripts/vscripts/abilities/heroes/hoodwink/hoodwink_bushwhack_custom.lua @@ -0,0 +1,378 @@ +local ____lualib = require("lualib_bundle") +local __TS__Class = ____lualib.__TS__Class +local __TS__ClassExtends = ____lualib.__TS__ClassExtends +local __TS__Decorate = ____lualib.__TS__Decorate +local ____exports = {} +local ____dota_ts_adapter = require("lib.dota_ts_adapter") +local BaseAbility = ____dota_ts_adapter.BaseAbility +local BaseModifier = ____dota_ts_adapter.BaseModifier +local BaseModifierMotionBoth = ____dota_ts_adapter.BaseModifierMotionBoth +local registerAbility = ____dota_ts_adapter.registerAbility +local registerModifier = ____dota_ts_adapter.registerModifier +____exports.hoodwink_bushwhack_custom = __TS__Class() +local hoodwink_bushwhack_custom = ____exports.hoodwink_bushwhack_custom +hoodwink_bushwhack_custom.name = "hoodwink_bushwhack_custom" +hoodwink_bushwhack_custom.____file_path = "scripts/vscripts/abilities/heroes/hoodwink/hoodwink_bushwhack_custom.lua" +__TS__ClassExtends(hoodwink_bushwhack_custom, BaseAbility) +function hoodwink_bushwhack_custom.prototype.GetAOERadius(self) + return self:GetSpecialValueFor("radius") +end +function hoodwink_bushwhack_custom.prototype.OnSpellStart(self) + local caster = self:GetCaster() + local point = self:GetCursorPosition() + local thinker = CreateModifierThinker( + caster, + self, + "modifier_kill", + {duration = 2}, + point, + caster:GetTeamNumber(), + false + ) + local projectile = { + Target = thinker, + iMoveSpeed = self:GetSpecialValueFor("projectile_speed"), + bDodgeable = true, + iSourceAttachment = DOTA_PROJECTILE_ATTACHMENT_ATTACK_1, + EffectName = "particles/units/heroes/hero_hoodwink/hoodwink_bushwhack_projectile.vpcf", + Ability = self, + Source = caster + } + ProjectileManager:CreateTrackingProjectile(projectile) + caster:EmitSound("Hero_Hoodwink.Bushwhack.Cast") +end +function hoodwink_bushwhack_custom.prototype.OnProjectileHit(self, target, location) + local impactLocation = target and target:GetAbsOrigin() or location + local caster = self:GetCaster() + local radius = self:GetSpecialValueFor("radius") + local duration = self:GetSpecialValueFor("duration") + AddFOWViewer( + caster:GetTeamNumber(), + impactLocation, + radius, + duration, + true + ) + local enemies = FindUnitsInRadius( + caster:GetTeamNumber(), + impactLocation, + nil, + radius, + DOTA_UNIT_TARGET_TEAM_ENEMY, + DOTA_UNIT_TARGET_HERO + DOTA_UNIT_TARGET_BASIC, + DOTA_UNIT_TARGET_FLAG_NO_INVIS, + FIND_CLOSEST, + false + ) + local trees = GridNav:GetAllTreesAroundPoint(impactLocation, radius, false) + local isSuccess = #trees > 0 and #enemies > 0 + if isSuccess then + local tree = trees[1] + for ____, enemy in ipairs(enemies) do + if enemy:IsBoss() then + enemy:AddNewModifier( + caster, + self, + ____exports.modifier_hoodwink_bushwhack_custom_boss.name, + { + duration = duration, + tree = tree:entindex() + } + ) + else + enemy:AddNewModifier( + caster, + self, + ____exports.modifier_hoodwink_bushwhack_custom.name, + { + duration = duration, + tree = tree:entindex() + } + ) + end + end + end + local effectName = isSuccess and "particles/units/heroes/hero_hoodwink/hoodwink_bushwhack.vpcf" or "particles/units/heroes/hero_hoodwink/hoodwink_bushwhack_fail.vpcf" + local particle = ParticleManager:CreateParticle(effectName, PATTACH_WORLDORIGIN, nil) + ParticleManager:SetParticleControl(particle, 0, impactLocation) + ParticleManager:SetParticleControl( + particle, + 1, + Vector(radius, 0, 0) + ) + ParticleManager:ReleaseParticleIndex(particle) + caster:StopSound("Hero_Hoodwink.Bushwhack.Cast") + EmitSoundOnLocationWithCaster(impactLocation, "Hero_Hoodwink.Bushwhack.Impact", caster) +end +hoodwink_bushwhack_custom = __TS__Decorate( + hoodwink_bushwhack_custom, + hoodwink_bushwhack_custom, + {registerAbility(nil)}, + {kind = "class", name = "hoodwink_bushwhack_custom"} +) +____exports.hoodwink_bushwhack_custom = hoodwink_bushwhack_custom +____exports.modifier_hoodwink_bushwhack_custom = __TS__Class() +local modifier_hoodwink_bushwhack_custom = ____exports.modifier_hoodwink_bushwhack_custom +modifier_hoodwink_bushwhack_custom.name = "modifier_hoodwink_bushwhack_custom" +modifier_hoodwink_bushwhack_custom.____file_path = "scripts/vscripts/abilities/heroes/hoodwink/hoodwink_bushwhack_custom.lua" +__TS__ClassExtends(modifier_hoodwink_bushwhack_custom, BaseModifierMotionBoth) +function modifier_hoodwink_bushwhack_custom.prototype.____constructor(self, ...) + BaseModifierMotionBoth.prototype.____constructor(self, ...) + self.treeOrigin = Vector(0, 0, 0) + self.speed = 900 + self.distance = 150 + self.damage = 0 + self.interval = 0 + self.tickCount = 0 + self.rate = 0.3 + self.height = 50 + self.damageMultiplier = 0 +end +function modifier_hoodwink_bushwhack_custom.prototype.IsHidden(self) + return false +end +function modifier_hoodwink_bushwhack_custom.prototype.IsStunDebuff(self) + return true +end +function modifier_hoodwink_bushwhack_custom.prototype.OnCreated(self, params) + if not IsServer() then + return + end + local tree = EntIndexToHScript(params.tree) + local ability = self:GetAbility() + if not tree or tree:IsNull() or not ability then + self:Destroy() + return + end + self.treeOrigin = tree:GetAbsOrigin() + self.damage = ability:GetSpecialValueFor("total_damage") + self.interval = ability:GetSpecialValueFor("interval") + self.tickCount = ability:GetSpecialValueFor("duration") / self.interval + self.damage = self.damage / self.tickCount + self.damageMultiplier = ability:GetSpecialValueFor("damage_multiplier") + if not self:ApplyHorizontalMotionController() then + self:Destroy() + return + end + self:StartIntervalThink(self.interval) + self:PlayEffects() +end +function modifier_hoodwink_bushwhack_custom.prototype.OnDestroy(self) + if not IsServer() then + return + end + self:GetParent():RemoveHorizontalMotionController(self) +end +function modifier_hoodwink_bushwhack_custom.prototype.OnIntervalThink(self) + if not IsServer() then + return + end + local ability = self:GetAbility() + local caster = self:GetCaster() + if not ability or not caster then + return + end + local damage = self.damage + local hasShard = HasShard(nil, caster) + if hasShard then + damage = damage + caster:GetAverageTrueAttackDamage(self:GetParent()) * self.damageMultiplier / 100 + end + ApplyDamage({ + victim = self:GetParent(), + attacker = caster, + damage = damage, + damage_type = ability:GetAbilityDamageType(), + ability = ability + }) +end +function modifier_hoodwink_bushwhack_custom.prototype.DeclareFunctions(self) + return {MODIFIER_PROPERTY_OVERRIDE_ANIMATION, MODIFIER_PROPERTY_OVERRIDE_ANIMATION_RATE, MODIFIER_PROPERTY_VISUAL_Z_DELTA} +end +function modifier_hoodwink_bushwhack_custom.prototype.GetOverrideAnimation(self) + return ACT_DOTA_FLAIL +end +function modifier_hoodwink_bushwhack_custom.prototype.GetOverrideAnimationRate(self) + return self.rate +end +function modifier_hoodwink_bushwhack_custom.prototype.GetVisualZDelta(self) + return self.height +end +function modifier_hoodwink_bushwhack_custom.prototype.CheckState(self) + return {[MODIFIER_STATE_STUNNED] = true} +end +function modifier_hoodwink_bushwhack_custom.prototype.UpdateHorizontalMotion(self, me, dt) + local origin = me:GetAbsOrigin() + local dir = self.treeOrigin:__sub(origin) + local dist = dir:Length2D() + dir.z = 0 + dir = dir:Normalized() + if dist < self.distance then + self:GetParent():RemoveHorizontalMotionController(self) + return + end + local target = dir:__mul(self.speed * dt) + local newPos = origin:__add(target) + local newDist = self.treeOrigin:__sub(newPos):Length2D() + if newDist < self.distance then + local finalPos = self.treeOrigin:__sub(dir:__mul(self.distance)) + me:SetAbsOrigin(finalPos) + else + me:SetAbsOrigin(newPos) + end +end +function modifier_hoodwink_bushwhack_custom.prototype.OnHorizontalMotionInterrupted(self) + self:GetParent():RemoveHorizontalMotionController(self) +end +function modifier_hoodwink_bushwhack_custom.prototype.PlayEffects(self) + local effect = ParticleManager:CreateParticle( + "particles/units/heroes/hero_hoodwink/hoodwink_bushwhack_target.vpcf", + PATTACH_ABSORIGIN_FOLLOW, + self:GetParent() + ) + ParticleManager:SetParticleControl(effect, 15, self.treeOrigin) + ParticleManager:SetParticleControlEnt( + effect, + 1, + self:GetParent(), + PATTACH_POINT_FOLLOW, + "attach_hitloc", + self:GetParent():GetAbsOrigin(), + true + ) + ParticleManager:SetParticleControlEnt( + effect, + 4, + self:GetParent(), + PATTACH_POINT_FOLLOW, + "attach_hitloc", + self:GetParent():GetAbsOrigin(), + true + ) + self:AddParticle( + effect, + false, + false, + -1, + false, + false + ) + EmitSoundOn( + "Hero_Hoodwink.Bushwhack.Target", + self:GetParent() + ) +end +modifier_hoodwink_bushwhack_custom = __TS__Decorate( + modifier_hoodwink_bushwhack_custom, + modifier_hoodwink_bushwhack_custom, + {registerModifier(nil)}, + {kind = "class", name = "modifier_hoodwink_bushwhack_custom"} +) +____exports.modifier_hoodwink_bushwhack_custom = modifier_hoodwink_bushwhack_custom +____exports.modifier_hoodwink_bushwhack_custom_boss = __TS__Class() +local modifier_hoodwink_bushwhack_custom_boss = ____exports.modifier_hoodwink_bushwhack_custom_boss +modifier_hoodwink_bushwhack_custom_boss.name = "modifier_hoodwink_bushwhack_custom_boss" +modifier_hoodwink_bushwhack_custom_boss.____file_path = "scripts/vscripts/abilities/heroes/hoodwink/hoodwink_bushwhack_custom.lua" +__TS__ClassExtends(modifier_hoodwink_bushwhack_custom_boss, BaseModifier) +function modifier_hoodwink_bushwhack_custom_boss.prototype.____constructor(self, ...) + BaseModifier.prototype.____constructor(self, ...) + self.damage = 0 + self.interval = 0 + self.tickCount = 0 + self.damageMultiplier = 0 +end +function modifier_hoodwink_bushwhack_custom_boss.prototype.IsHidden(self) + return false +end +function modifier_hoodwink_bushwhack_custom_boss.prototype.IsStunDebuff(self) + return true +end +function modifier_hoodwink_bushwhack_custom_boss.prototype.OnCreated(self, params) + if not IsServer() then + return + end + local tree = EntIndexToHScript(params.tree) + local ability = self:GetAbility() + if not tree or tree:IsNull() or not ability then + self:Destroy() + return + end + self.interval = ability:GetSpecialValueFor("interval") + self.damage = ability:GetSpecialValueFor("total_damage") + self.tickCount = ability:GetSpecialValueFor("duration") / self.interval + self.damage = self.damage / self.tickCount + self.damageMultiplier = ability:GetSpecialValueFor("damage_multiplier") + local effect = ParticleManager:CreateParticle( + "particles/units/heroes/hero_hoodwink/hoodwink_bushwhack_target.vpcf", + PATTACH_ABSORIGIN_FOLLOW, + self:GetParent() + ) + ParticleManager:SetParticleControl( + effect, + 15, + tree:GetAbsOrigin() + ) + ParticleManager:SetParticleControlEnt( + effect, + 1, + self:GetParent(), + PATTACH_POINT_FOLLOW, + "attach_hitloc", + self:GetParent():GetAbsOrigin(), + true + ) + ParticleManager:SetParticleControlEnt( + effect, + 4, + self:GetParent(), + PATTACH_POINT_FOLLOW, + "attach_hitloc", + self:GetParent():GetAbsOrigin(), + true + ) + self:AddParticle( + effect, + false, + false, + -1, + false, + false + ) + EmitSoundOn( + "Hero_Hoodwink.Bushwhack.Target", + self:GetParent() + ) + self:StartIntervalThink(self.interval) +end +function modifier_hoodwink_bushwhack_custom_boss.prototype.OnIntervalThink(self) + if not IsServer() then + return + end + local ability = self:GetAbility() + local caster = self:GetCaster() + local hasShard = HasShard(nil, caster) + if not ability or not caster then + return + end + local damage = self.damage + if hasShard then + damage = damage + caster:GetAverageTrueAttackDamage(self:GetParent()) * self.damageMultiplier / 100 + end + ApplyDamage({ + victim = self:GetParent(), + attacker = caster, + damage = damage, + damage_type = ability:GetAbilityDamageType(), + ability = ability + }) +end +function modifier_hoodwink_bushwhack_custom_boss.prototype.CheckState(self) + return {[MODIFIER_STATE_STUNNED] = true} +end +modifier_hoodwink_bushwhack_custom_boss = __TS__Decorate( + modifier_hoodwink_bushwhack_custom_boss, + modifier_hoodwink_bushwhack_custom_boss, + {registerModifier(nil)}, + {kind = "class", name = "modifier_hoodwink_bushwhack_custom_boss"} +) +____exports.modifier_hoodwink_bushwhack_custom_boss = modifier_hoodwink_bushwhack_custom_boss +return ____exports diff --git a/scripts/vscripts/abilities/heroes/hoodwink/hoodwink_lucky_innate_custom.lua b/scripts/vscripts/abilities/heroes/hoodwink/hoodwink_lucky_innate_custom.lua new file mode 100644 index 0000000..61c9235 --- /dev/null +++ b/scripts/vscripts/abilities/heroes/hoodwink/hoodwink_lucky_innate_custom.lua @@ -0,0 +1,108 @@ +local ____lualib = require("lualib_bundle") +local __TS__Class = ____lualib.__TS__Class +local __TS__ClassExtends = ____lualib.__TS__ClassExtends +local __TS__Decorate = ____lualib.__TS__Decorate +local ____exports = {} +local ____dota_ts_adapter = require("lib.dota_ts_adapter") +local BaseAbility = ____dota_ts_adapter.BaseAbility +local BaseModifier = ____dota_ts_adapter.BaseModifier +local registerAbility = ____dota_ts_adapter.registerAbility +local registerModifier = ____dota_ts_adapter.registerModifier +local ____luck = require("utils.luck") +local addLuck = ____luck.addLuck +local reduceLuck = ____luck.reduceLuck +____exports.hoodwink_lucky_innate_custom = __TS__Class() +local hoodwink_lucky_innate_custom = ____exports.hoodwink_lucky_innate_custom +hoodwink_lucky_innate_custom.name = "hoodwink_lucky_innate_custom" +hoodwink_lucky_innate_custom.____file_path = "scripts/vscripts/abilities/heroes/hoodwink/hoodwink_lucky_innate_custom.lua" +__TS__ClassExtends(hoodwink_lucky_innate_custom, BaseAbility) +function hoodwink_lucky_innate_custom.prototype.GetIntrinsicModifierName(self) + return ____exports.modifier_hoodwink_lucky_innate_custom.name +end +hoodwink_lucky_innate_custom = __TS__Decorate( + hoodwink_lucky_innate_custom, + hoodwink_lucky_innate_custom, + {registerAbility(nil)}, + {kind = "class", name = "hoodwink_lucky_innate_custom"} +) +____exports.hoodwink_lucky_innate_custom = hoodwink_lucky_innate_custom +____exports.modifier_hoodwink_lucky_innate_custom = __TS__Class() +local modifier_hoodwink_lucky_innate_custom = ____exports.modifier_hoodwink_lucky_innate_custom +modifier_hoodwink_lucky_innate_custom.name = "modifier_hoodwink_lucky_innate_custom" +modifier_hoodwink_lucky_innate_custom.____file_path = "scripts/vscripts/abilities/heroes/hoodwink/hoodwink_lucky_innate_custom.lua" +__TS__ClassExtends(modifier_hoodwink_lucky_innate_custom, BaseModifier) +function modifier_hoodwink_lucky_innate_custom.prototype.____constructor(self, ...) + BaseModifier.prototype.____constructor(self, ...) + self.appliedLuckBonus = 0 +end +function modifier_hoodwink_lucky_innate_custom.prototype.IsHidden(self) + return true +end +function modifier_hoodwink_lucky_innate_custom.prototype.IsPurgable(self) + return false +end +function modifier_hoodwink_lucky_innate_custom.prototype.OnGameEvent(self) + ListenToGameEvent( + "dota_player_gained_level", + function(event) + local parent = self:GetParent() + if not parent or not parent:IsHero() or parent:IsIllusion() then + return + end + local playerId = parent:GetPlayerOwnerID() + if playerId == nil or playerId == nil or playerId < 0 then + return + end + if event.player ~= playerId then + return + end + self:UpdateLuckFromHeroLevel() + end, + nil + ) +end +function modifier_hoodwink_lucky_innate_custom.prototype.OnCreated(self) + if not IsServer() then + return + end + self:UpdateLuckFromHeroLevel() + self.levelUpListener = ListenToGameEvent( + "dota_player_gained_level", + function() + local parent = self:GetParent() + if not parent or not parent:IsHero() or parent:IsIllusion() then + return + end + self:UpdateLuckFromHeroLevel() + end, + nil + ) +end +function modifier_hoodwink_lucky_innate_custom.prototype.UpdateLuckFromHeroLevel(self) + local parent = self:GetParent() + local ability = self:GetAbility() + if not parent:IsHero() or parent:IsIllusion() or not ability then + return + end + local hero = parent + local luckPerLevel = math.max( + 0, + ability:GetSpecialValueFor("luck_per_level") + ) + local desiredLuckBonus = hero:GetLevel() * luckPerLevel + local delta = desiredLuckBonus - self.appliedLuckBonus + if delta > 0 then + addLuck(nil, hero, delta) + elseif delta < 0 then + reduceLuck(nil, hero, -delta) + end + self.appliedLuckBonus = desiredLuckBonus +end +modifier_hoodwink_lucky_innate_custom = __TS__Decorate( + modifier_hoodwink_lucky_innate_custom, + modifier_hoodwink_lucky_innate_custom, + {registerModifier(nil)}, + {kind = "class", name = "modifier_hoodwink_lucky_innate_custom"} +) +____exports.modifier_hoodwink_lucky_innate_custom = modifier_hoodwink_lucky_innate_custom +return ____exports diff --git a/scripts/vscripts/abilities/heroes/hoodwink/hoodwink_scurry_custom.lua b/scripts/vscripts/abilities/heroes/hoodwink/hoodwink_scurry_custom.lua new file mode 100644 index 0000000..0f5ff72 --- /dev/null +++ b/scripts/vscripts/abilities/heroes/hoodwink/hoodwink_scurry_custom.lua @@ -0,0 +1,237 @@ +local ____lualib = require("lualib_bundle") +local __TS__Class = ____lualib.__TS__Class +local __TS__ClassExtends = ____lualib.__TS__ClassExtends +local __TS__Decorate = ____lualib.__TS__Decorate +local ____exports = {} +local ____dota_ts_adapter = require("lib.dota_ts_adapter") +local BaseAbility = ____dota_ts_adapter.BaseAbility +local BaseModifier = ____dota_ts_adapter.BaseModifier +local registerAbility = ____dota_ts_adapter.registerAbility +local registerModifier = ____dota_ts_adapter.registerModifier +local ____luck = require("utils.luck") +local getLuck = ____luck.getLuck +local ____incoming_damage_reduction_combine = require("utils.incoming_damage_reduction_combine") +local removeIncomingDamageReductionSource = ____incoming_damage_reduction_combine.removeIncomingDamageReductionSource +local setIncomingDamageReductionSource = ____incoming_damage_reduction_combine.setIncomingDamageReductionSource +local HOODWINK_SCURRY_BUFF_INCOMING_SOURCE = "modifier_hoodwink_scurry_custom_buff" +____exports.hoodwink_scurry_custom = __TS__Class() +local hoodwink_scurry_custom = ____exports.hoodwink_scurry_custom +hoodwink_scurry_custom.name = "hoodwink_scurry_custom" +hoodwink_scurry_custom.____file_path = "scripts/vscripts/abilities/heroes/hoodwink/hoodwink_scurry_custom.lua" +__TS__ClassExtends(hoodwink_scurry_custom, BaseAbility) +function hoodwink_scurry_custom.prototype.GetCastRange(self, _location, _target) + return self:GetSpecialValueFor("radius") +end +function hoodwink_scurry_custom.prototype.GetIntrinsicModifierName(self) + return ____exports.modifier_hoodwink_scurry_custom.name +end +function hoodwink_scurry_custom.prototype.OnSpellStart(self) + local caster = self:GetCaster() + caster:AddNewModifier( + caster, + self, + ____exports.modifier_hoodwink_scurry_custom_active.name, + {duration = self:GetSpecialValueFor("duration")} + ) + caster:AddNewModifier(caster, self, ____exports.modifier_hoodwink_scurry_custom_buff.name, {}) + EmitSoundOn("Hero_Hoodwink.Scurry.Cast", caster) +end +hoodwink_scurry_custom = __TS__Decorate( + hoodwink_scurry_custom, + hoodwink_scurry_custom, + {registerAbility(nil)}, + {kind = "class", name = "hoodwink_scurry_custom"} +) +____exports.hoodwink_scurry_custom = hoodwink_scurry_custom +____exports.modifier_hoodwink_scurry_custom = __TS__Class() +local modifier_hoodwink_scurry_custom = ____exports.modifier_hoodwink_scurry_custom +modifier_hoodwink_scurry_custom.name = "modifier_hoodwink_scurry_custom" +modifier_hoodwink_scurry_custom.____file_path = "scripts/vscripts/abilities/heroes/hoodwink/hoodwink_scurry_custom.lua" +__TS__ClassExtends(modifier_hoodwink_scurry_custom, BaseModifier) +function modifier_hoodwink_scurry_custom.prototype.____constructor(self, ...) + BaseModifier.prototype.____constructor(self, ...) + self.radius = 0 +end +function modifier_hoodwink_scurry_custom.prototype.IsHidden(self) + return true +end +function modifier_hoodwink_scurry_custom.prototype.IsPurgable(self) + return false +end +function modifier_hoodwink_scurry_custom.prototype.IsPurgeException(self) + return false +end +function modifier_hoodwink_scurry_custom.prototype.OnCreated(self) + if not IsServer() then + return + end + local ability = self:GetAbility() + if not ability then + return + end + self.radius = ability:GetSpecialValueFor("radius") + self:StartIntervalThink(0.2) +end +function modifier_hoodwink_scurry_custom.prototype.OnRefresh(self) + self:OnCreated() +end +function modifier_hoodwink_scurry_custom.prototype.OnIntervalThink(self) + if not IsServer() then + return + end + local parent = self:GetParent() + if GridNav:IsNearbyTree( + parent:GetAbsOrigin(), + self.radius, + true + ) or parent:HasModifier(____exports.modifier_hoodwink_scurry_custom_active.name) then + parent:AddNewModifier( + parent, + self:GetAbility(), + ____exports.modifier_hoodwink_scurry_custom_buff.name, + {} + ) + else + parent:RemoveModifierByName(____exports.modifier_hoodwink_scurry_custom_buff.name) + end +end +modifier_hoodwink_scurry_custom = __TS__Decorate( + modifier_hoodwink_scurry_custom, + modifier_hoodwink_scurry_custom, + {registerModifier(nil)}, + {kind = "class", name = "modifier_hoodwink_scurry_custom"} +) +____exports.modifier_hoodwink_scurry_custom = modifier_hoodwink_scurry_custom +____exports.modifier_hoodwink_scurry_custom_buff = __TS__Class() +local modifier_hoodwink_scurry_custom_buff = ____exports.modifier_hoodwink_scurry_custom_buff +modifier_hoodwink_scurry_custom_buff.name = "modifier_hoodwink_scurry_custom_buff" +modifier_hoodwink_scurry_custom_buff.____file_path = "scripts/vscripts/abilities/heroes/hoodwink/hoodwink_scurry_custom.lua" +__TS__ClassExtends(modifier_hoodwink_scurry_custom_buff, BaseModifier) +function modifier_hoodwink_scurry_custom_buff.prototype.____constructor(self, ...) + BaseModifier.prototype.____constructor(self, ...) + self.bonusAttackSpeed = 0 + self.bonusDamage = 0 + self.luckEvasionMultiplier = 1 + self.maxLuckEvasion = 75 +end +function modifier_hoodwink_scurry_custom_buff.prototype.IsHidden(self) + return false +end +function modifier_hoodwink_scurry_custom_buff.prototype.DeclareFunctions(self) + return {MODIFIER_PROPERTY_ATTACKSPEED_BONUS_CONSTANT, MODIFIER_PROPERTY_PREATTACK_BONUS_DAMAGE, MODIFIER_PROPERTY_EVASION_CONSTANT} +end +function modifier_hoodwink_scurry_custom_buff.prototype.OnCreated(self) + local ability = self:GetAbility() + if not ability then + return + end + self.bonusAttackSpeed = ability:GetSpecialValueFor("bonus_attack_speed") + self.bonusDamage = ability:GetSpecialValueFor("bonus_damage") + self.luckEvasionMultiplier = ability:GetSpecialValueFor("luck_evasion_multiplier") + self.maxLuckEvasion = ability:GetSpecialValueFor("max_luck_evasion") + if not IsServer() then + return + end + setIncomingDamageReductionSource( + nil, + self:GetParent(), + HOODWINK_SCURRY_BUFF_INCOMING_SOURCE, + function() + if self:GetParent():PassivesDisabled() then + return 0 + end + local buffAbility = self:GetAbility() + if not buffAbility then + return 0 + end + local reductionPct = buffAbility:GetSpecialValueFor("incoming_damage_reduction_pct") + return reductionPct > 0 and reductionPct or 0 + end + ) +end +function modifier_hoodwink_scurry_custom_buff.prototype.OnRefresh(self) + self:OnCreated() +end +function modifier_hoodwink_scurry_custom_buff.prototype.OnDestroy(self) + if not IsServer() then + return + end + removeIncomingDamageReductionSource( + nil, + self:GetParent(), + HOODWINK_SCURRY_BUFF_INCOMING_SOURCE + ) +end +function modifier_hoodwink_scurry_custom_buff.prototype.GetModifierPreAttack_BonusDamage(self) + if self:GetParent():PassivesDisabled() then + return 0 + end + return self.bonusDamage +end +function modifier_hoodwink_scurry_custom_buff.prototype.GetModifierAttackSpeedBonus_Constant(self) + if self:GetParent():PassivesDisabled() then + return 0 + end + return self.bonusAttackSpeed +end +function modifier_hoodwink_scurry_custom_buff.prototype.GetModifierEvasion_Constant(self) + if self:GetParent():PassivesDisabled() then + return 0 + end + local parent = self:GetParent() + if not parent:IsHero() then + return 0 + end + local luck = getLuck(nil, parent) + self:SetStackCount(math.floor(math.max(0, luck))) + return math.min(self.maxLuckEvasion, luck * self.luckEvasionMultiplier) +end +function modifier_hoodwink_scurry_custom_buff.prototype.GetEffectName(self) + return "particles/units/heroes/hero_hoodwink/hoodwink_scurry_passive.vpcf" +end +function modifier_hoodwink_scurry_custom_buff.prototype.GetEffectAttachType(self) + return PATTACH_ABSORIGIN_FOLLOW +end +modifier_hoodwink_scurry_custom_buff = __TS__Decorate( + modifier_hoodwink_scurry_custom_buff, + modifier_hoodwink_scurry_custom_buff, + {registerModifier(nil)}, + {kind = "class", name = "modifier_hoodwink_scurry_custom_buff"} +) +____exports.modifier_hoodwink_scurry_custom_buff = modifier_hoodwink_scurry_custom_buff +____exports.modifier_hoodwink_scurry_custom_active = __TS__Class() +local modifier_hoodwink_scurry_custom_active = ____exports.modifier_hoodwink_scurry_custom_active +modifier_hoodwink_scurry_custom_active.name = "modifier_hoodwink_scurry_custom_active" +modifier_hoodwink_scurry_custom_active.____file_path = "scripts/vscripts/abilities/heroes/hoodwink/hoodwink_scurry_custom.lua" +__TS__ClassExtends(modifier_hoodwink_scurry_custom_active, BaseModifier) +function modifier_hoodwink_scurry_custom_active.prototype.____constructor(self, ...) + BaseModifier.prototype.____constructor(self, ...) + self.bonusMovementSpeed = 0 +end +function modifier_hoodwink_scurry_custom_active.prototype.IsHidden(self) + return false +end +function modifier_hoodwink_scurry_custom_active.prototype.CheckState(self) + return {[MODIFIER_STATE_NO_UNIT_COLLISION] = true} +end +function modifier_hoodwink_scurry_custom_active.prototype.DeclareFunctions(self) + return {MODIFIER_PROPERTY_MOVESPEED_BONUS_PERCENTAGE} +end +function modifier_hoodwink_scurry_custom_active.prototype.OnCreated(self) + local ability = self:GetAbility() + if not ability then + return + end + self.bonusMovementSpeed = ability:GetSpecialValueFor("movement_speed_pct") +end +function modifier_hoodwink_scurry_custom_active.prototype.GetModifierMoveSpeedBonus_Percentage(self) + return self.bonusMovementSpeed +end +modifier_hoodwink_scurry_custom_active = __TS__Decorate( + modifier_hoodwink_scurry_custom_active, + modifier_hoodwink_scurry_custom_active, + {registerModifier(nil)}, + {kind = "class", name = "modifier_hoodwink_scurry_custom_active"} +) +____exports.modifier_hoodwink_scurry_custom_active = modifier_hoodwink_scurry_custom_active +return ____exports diff --git a/scripts/vscripts/abilities/heroes/hoodwink/hoodwink_sharpshooter_custom.lua b/scripts/vscripts/abilities/heroes/hoodwink/hoodwink_sharpshooter_custom.lua new file mode 100644 index 0000000..aa163fe --- /dev/null +++ b/scripts/vscripts/abilities/heroes/hoodwink/hoodwink_sharpshooter_custom.lua @@ -0,0 +1,527 @@ +local ____lualib = require("lualib_bundle") +local __TS__Class = ____lualib.__TS__Class +local __TS__ClassExtends = ____lualib.__TS__ClassExtends +local __TS__Decorate = ____lualib.__TS__Decorate +local ____exports = {} +local modifier_hoodwink_sharpshooter_custom_debuff, modifier_hoodwink_sharpshooter_custom_sound +local ____dota_ts_adapter = require("lib.dota_ts_adapter") +local BaseAbility = ____dota_ts_adapter.BaseAbility +local BaseModifier = ____dota_ts_adapter.BaseModifier +local registerAbility = ____dota_ts_adapter.registerAbility +local registerModifier = ____dota_ts_adapter.registerModifier +____exports.hoodwink_sharpshooter_custom = __TS__Class() +local hoodwink_sharpshooter_custom = ____exports.hoodwink_sharpshooter_custom +hoodwink_sharpshooter_custom.name = "hoodwink_sharpshooter_custom" +hoodwink_sharpshooter_custom.____file_path = "scripts/vscripts/abilities/heroes/hoodwink/hoodwink_sharpshooter_custom.lua" +__TS__ClassExtends(hoodwink_sharpshooter_custom, BaseAbility) +function hoodwink_sharpshooter_custom.prototype.Precache(self, context) + PrecacheResource("particle", "particles/units/heroes/hero_hoodwink/hoodwink_sharpshooter.vpcf", context) + PrecacheResource("particle", "particles/units/heroes/hero_hoodwink/hoodwink_sharpshooter_projectile.vpcf", context) + PrecacheResource("particle", "particles/units/heroes/hero_hoodwink/hoodwink_sharpshooter_impact.vpcf", context) + PrecacheResource("particle", "particles/units/heroes/hero_hoodwink/hoodwink_sharpshooter_debuff.vpcf", context) + PrecacheResource("particle", "particles/units/heroes/hero_hoodwink/hoodwink_sharpshooter_timer.vpcf", context) +end +function hoodwink_sharpshooter_custom.prototype.OnSpellStart(self) + local point = self:GetCursorPosition() + local duration = self:GetSpecialValueFor("misfire_time") + local caster = self:GetCaster() + caster:StartGesture(ACT_DOTA_CHANNEL_ABILITY_6) + caster:AddNewModifier(caster, self, ____exports.modifier_hoodwink_sharpshooter_custom.name, {duration = duration, x = point.x, y = point.y}) +end +function hoodwink_sharpshooter_custom.prototype.OnProjectileThink_ExtraData(self, location, extraData) + local sound = EntIndexToHScript(extraData.sound) + if not sound or sound:IsNull() then + return + end + sound:SetOrigin(location) +end +function hoodwink_sharpshooter_custom.prototype.OnProjectileHit_ExtraData(self, target, _location, extraData) + local sound = EntIndexToHScript(extraData.sound) + if sound and not sound:IsNull() then + sound:StopSound("Hero_Hoodwink.Sharpshooter.Projectile") + UTIL_Remove(sound) + end + if not target then + return false + end + modifier_hoodwink_sharpshooter_custom_debuff:apply( + target, + self:GetCaster(), + self, + {duration = extraData.duration, x = extraData.x, y = extraData.y} + ) + local real = ApplyDamage({ + victim = target, + attacker = self:GetCaster(), + damage = extraData.damage + self:GetCaster():GetAverageTrueAttackDamage(target) * self:GetSpecialValueFor("damage_multiplier") * 0.01, + damage_type = self:GetAbilityDamageType(), + ability = self + }) + SendOverheadEventMessage( + nil, + OVERHEAD_ALERT_BONUS_SPELL_DAMAGE, + target, + real, + self:GetCaster():GetPlayerOwner() + ) + AddFOWViewer( + self:GetCaster():GetTeamNumber(), + target:GetOrigin(), + 300, + 4, + false + ) + local direction = Vector(extraData.x, extraData.y, 0):Normalized() + local effect = ParticleManager:CreateParticle("particles/units/heroes/hero_hoodwink/hoodwink_sharpshooter_impact.vpcf", PATTACH_ABSORIGIN_FOLLOW, target) + ParticleManager:SetParticleControl( + effect, + 0, + target:GetOrigin() + ) + ParticleManager:SetParticleControl( + effect, + 1, + target:GetOrigin() + ) + ParticleManager:SetParticleControlForward(effect, 1, direction) + ParticleManager:ReleaseParticleIndex(effect) + target:EmitSound("Hero_Hoodwink.Sharpshooter.Target") +end +hoodwink_sharpshooter_custom = __TS__Decorate( + hoodwink_sharpshooter_custom, + hoodwink_sharpshooter_custom, + {registerAbility(nil)}, + {kind = "class", name = "hoodwink_sharpshooter_custom"} +) +____exports.hoodwink_sharpshooter_custom = hoodwink_sharpshooter_custom +____exports.hoodwink_sharpshooter_release_custom = __TS__Class() +local hoodwink_sharpshooter_release_custom = ____exports.hoodwink_sharpshooter_release_custom +hoodwink_sharpshooter_release_custom.name = "hoodwink_sharpshooter_release_custom" +hoodwink_sharpshooter_release_custom.____file_path = "scripts/vscripts/abilities/heroes/hoodwink/hoodwink_sharpshooter_custom.lua" +__TS__ClassExtends(hoodwink_sharpshooter_release_custom, BaseAbility) +function hoodwink_sharpshooter_release_custom.prototype.OnSpellStart(self) + local mod = self:GetCaster():FindModifierByName(____exports.modifier_hoodwink_sharpshooter_custom.name) + if not mod then + return + end + mod:Destroy() +end +hoodwink_sharpshooter_release_custom = __TS__Decorate( + hoodwink_sharpshooter_release_custom, + hoodwink_sharpshooter_release_custom, + {registerAbility(nil)}, + {kind = "class", name = "hoodwink_sharpshooter_release_custom"} +) +____exports.hoodwink_sharpshooter_release_custom = hoodwink_sharpshooter_release_custom +____exports.modifier_hoodwink_sharpshooter_custom = __TS__Class() +local modifier_hoodwink_sharpshooter_custom = ____exports.modifier_hoodwink_sharpshooter_custom +modifier_hoodwink_sharpshooter_custom.name = "modifier_hoodwink_sharpshooter_custom" +modifier_hoodwink_sharpshooter_custom.____file_path = "scripts/vscripts/abilities/heroes/hoodwink/hoodwink_sharpshooter_custom.lua" +__TS__ClassExtends(modifier_hoodwink_sharpshooter_custom, BaseModifier) +function modifier_hoodwink_sharpshooter_custom.prototype.____constructor(self, ...) + BaseModifier.prototype.____constructor(self, ...) + self.charge = 0 + self.base = 0 + self.damage = 0 + self.duration = 0 + self.turnRate = 0 + self.recoilDistance = 0 + self.recoilDuration = 0 + self.recoilHeight = 0 + self.interval = 0.03 + self.projectileSpeed = 0 + self.projectileRange = 0 + self.projectileWidth = 0 + self.targetDir = Vector(0, 0, 0) + self.currentDir = Vector(0, 0, 0) + self.faceTarget = false + self.charged = false + self.half = false + self.turnSpeed = 0 + self.info = {} + self.previewEffect = -1 +end +function modifier_hoodwink_sharpshooter_custom.prototype.IsHidden(self) + return false +end +function modifier_hoodwink_sharpshooter_custom.prototype.IsPurgable(self) + return false +end +function modifier_hoodwink_sharpshooter_custom.prototype.OnCreated(self, kv) + local parent = self:GetParent() + local ability = self:GetAbility() + if not ability then + return + end + self.charge = ability:GetSpecialValueFor("max_charge_time") + self.base = ability:GetSpecialValueFor("base_power") + self.damage = ability:GetSpecialValueFor("max_damage") + self.duration = ability:GetSpecialValueFor("max_slow_debuff_duration") + self.turnRate = ability:GetSpecialValueFor("turn_rate") + self.recoilDistance = ability:GetSpecialValueFor("recoil_distance") + self.recoilDuration = ability:GetSpecialValueFor("recoil_duration") + self.recoilHeight = ability:GetSpecialValueFor("recoil_height") + self.projectileSpeed = ability:GetSpecialValueFor("arrow_speed") + self.projectileRange = ability:GetSpecialValueFor("arrow_range") + self.projectileWidth = ability:GetSpecialValueFor("arrow_width") + self:StartIntervalThink(self.interval) + if not IsServer() then + return + end + local projectileVision = ability:GetSpecialValueFor("arrow_vision") + self:SetDirection(Vector(kv.x, kv.y, 0)) + self.currentDir = self.targetDir + self.faceTarget = true + parent:SetForwardVector(self.currentDir) + self.turnSpeed = self.interval * self.turnRate + self.info = { + Source = parent, + Ability = ability, + bDeleteOnHit = false, + iUnitTargetTeam = DOTA_UNIT_TARGET_TEAM_ENEMY, + iUnitTargetFlags = DOTA_UNIT_TARGET_FLAG_NONE, + iUnitTargetType = DOTA_UNIT_TARGET_HERO + DOTA_UNIT_TARGET_BASIC, + EffectName = "particles/units/heroes/hero_hoodwink/hoodwink_sharpshooter_projectile.vpcf", + fDistance = self.projectileRange, + fStartRadius = self.projectileWidth, + fEndRadius = self.projectileWidth, + bHasFrontalCone = false, + bReplaceExisting = false, + bProvidesVision = true, + bVisibleToEnemies = true, + iVisionRadius = projectileVision, + iVisionTeamNumber = parent:GetTeamNumber() + } + parent:SwapAbilities(____exports.hoodwink_sharpshooter_custom.name, ____exports.hoodwink_sharpshooter_release_custom.name, false, true) + local effect = ParticleManager:CreateParticle("particles/units/heroes/hero_hoodwink/hoodwink_sharpshooter.vpcf", PATTACH_ABSORIGIN_FOLLOW, parent) + ParticleManager:SetParticleControlEnt( + effect, + 1, + parent, + PATTACH_POINT_FOLLOW, + "attach_hitloc", + parent:GetOrigin(), + true + ) + self:AddParticle( + effect, + false, + false, + -1, + false, + false + ) + self.previewEffect = effect + EmitSoundOn("Hero_Hoodwink.Sharpshooter.Channel", parent) +end +function modifier_hoodwink_sharpshooter_custom.prototype.Shoot(self, newPct) + if not IsServer() then + return + end + local parent = self:GetParent() + local pct = newPct or math.min( + 1, + math.min( + self:GetElapsedTime(), + self.charge + ) / self.charge + self.base + ) + local realPct = pct == 0 and 1 or pct + local duration = self.duration * realPct + local damage = self.damage * realPct + self.info.vSpawnOrigin = parent:GetOrigin() + self.info.vVelocity = self.currentDir:__mul(self.projectileSpeed) + local sound = CreateModifierThinker( + parent, + self:GetAbility(), + modifier_hoodwink_sharpshooter_custom_sound.name, + {}, + parent:GetOrigin(), + parent:GetTeamNumber(), + false + ) + sound:EmitSound("Hero_Hoodwink.Sharpshooter.Projectile") + self.info.ExtraData = { + damage = damage, + pct = realPct, + duration = duration, + x = self.currentDir.x, + y = self.currentDir.y, + sound = sound:entindex() + } + ProjectileManager:CreateLinearProjectile(self.info) +end +function modifier_hoodwink_sharpshooter_custom.prototype.OnDestroy(self) + if not IsServer() then + return + end + local parent = self:GetParent() + StopSoundOn("Hero_Hoodwink.Sharpshooter.Channel", parent) + if parent:IsIllusion() and not parent:IsAlive() then + return + end + self:Shoot() + parent:RemoveGesture(ACT_DOTA_CHANNEL_ABILITY_6) + local bumpPoint = parent:GetAbsOrigin() + self.currentDir * self.recoilDistance + local mod = parent:AddNewModifier( + parent, + self:GetAbility(), + "modifier_knockback", + { + duration = self.recoilDuration, + knockback_height = self.recoilHeight, + knockback_distance = self.recoilDistance, + knockback_duration = self.recoilDuration, + center_x = bumpPoint.x, + center_y = bumpPoint.y, + center_z = bumpPoint.z + } + ) + parent:SwapAbilities(____exports.hoodwink_sharpshooter_release_custom.name, ____exports.hoodwink_sharpshooter_custom.name, false, true) + if mod ~= nil then + local effect = ParticleManager:CreateParticle("particles/items_fx/force_staff.vpcf", PATTACH_ABSORIGIN_FOLLOW, parent) + mod:AddParticle( + effect, + false, + false, + -1, + false, + false + ) + end + parent:StopSound("Hero_Hoodwink.Sharpshooter.Cast") + parent:EmitSound("Hero_Hoodwink.Sharpshooter.Cast") +end +function modifier_hoodwink_sharpshooter_custom.prototype.DeclareFunctions(self) + return {MODIFIER_EVENT_ON_ORDER, MODIFIER_PROPERTY_DISABLE_TURNING, MODIFIER_PROPERTY_MOVESPEED_LIMIT, MODIFIER_PROPERTY_OVERRIDE_ANIMATION} +end +function modifier_hoodwink_sharpshooter_custom.prototype.GetOverrideAnimation(self) + return ACT_DOTA_CHANNEL_ABILITY_6 +end +function modifier_hoodwink_sharpshooter_custom.prototype.GetPriority(self) + return MODIFIER_PRIORITY_ULTRA +end +function modifier_hoodwink_sharpshooter_custom.prototype.OnOrder(self, params) + if self:GetParent():IsIllusion() then + return + end + if params.unit ~= self:GetParent() then + return + end + if params.order_type == DOTA_UNIT_ORDER_MOVE_TO_POSITION or params.order_type == DOTA_UNIT_ORDER_MOVE_TO_DIRECTION then + self:SetDirection(params.new_pos) + elseif params.order_type == DOTA_UNIT_ORDER_MOVE_TO_TARGET or params.order_type == DOTA_UNIT_ORDER_ATTACK_TARGET then + local target = params.target + if target ~= nil then + self:SetDirection(target:GetOrigin()) + end + end +end +function modifier_hoodwink_sharpshooter_custom.prototype.GetModifierMoveSpeed_Limit(self) + return 0.1 +end +function modifier_hoodwink_sharpshooter_custom.prototype.GetModifierTurnRate_Percentage(self) + return -self.turnRate +end +function modifier_hoodwink_sharpshooter_custom.prototype.GetModifierDisableTurning(self) + return 1 +end +function modifier_hoodwink_sharpshooter_custom.prototype.SetDirection(self, vec) + local parent = self:GetParent() + if not parent or parent:IsNull() then + return + end + if vec.x == parent:GetAbsOrigin().x and vec.y == parent:GetAbsOrigin().y then + vec = parent:GetAbsOrigin() + 100 * parent:GetForwardVector() + end + self.targetDir = ((vec - parent:GetOrigin()) * Vector(1, 1, 0)):Normalized() + self.faceTarget = false +end +function modifier_hoodwink_sharpshooter_custom.prototype.CheckState(self) + return {[MODIFIER_STATE_DISARMED] = true} +end +function modifier_hoodwink_sharpshooter_custom.prototype.TurnLogic(self) + local parent = self:GetParent() + if not parent or parent:IsNull() or self.faceTarget then + return + end + local currentAngle = VectorToAngles(self.currentDir).y + local targetAngle = VectorToAngles(self.targetDir).y + local angleDiff = AngleDiff(currentAngle, targetAngle) + local sign = angleDiff < 0 and 1 or -1 + if math.abs(angleDiff) < 1.1 * self.turnSpeed then + self.currentDir = self.targetDir + self.faceTarget = true + else + self.currentDir = RotatePosition( + Vector(0, 0, 0), + QAngle(0, sign * self.turnSpeed, 0), + self.currentDir + ) + end + local hasMotion = parent:IsCurrentlyHorizontalMotionControlled() or parent:IsCurrentlyVerticalMotionControlled() + if not hasMotion then + parent:SetForwardVector(self.currentDir) + end +end +function modifier_hoodwink_sharpshooter_custom.prototype.OnIntervalThink(self) + if not IsServer() then + return + end + self:UpdateStack() + local parent = self:GetParent() + if not parent or parent:IsNull() then + return + end + self:TurnLogic() + self:UpdateEffect() + local chargeThreshold = self.charge * (1 - self.base) + if not self.charged and self:GetElapsedTime() > chargeThreshold then + self.charged = true + parent:EmitSound("Hero_Hoodwink.Sharpshooter.MaxCharge") + end + local remaining = self:GetRemainingTime() + local seconds = math.ceil(remaining) + local isHalf = seconds - remaining > 0.5 + if isHalf then + seconds = seconds - 1 + end + if self.half ~= isHalf then + self.half = isHalf + local mid = isHalf and 8 or 1 + local len = 2 + if seconds < 1 then + len = 1 + if not isHalf then + return + end + end + local effect = ParticleManager:CreateParticleForTeam( + "particles/units/heroes/hero_hoodwink/hoodwink_sharpshooter_timer.vpcf", + PATTACH_OVERHEAD_FOLLOW, + parent, + parent:GetTeamNumber() + ) + ParticleManager:SetParticleControl( + effect, + 1, + Vector(1, seconds, mid) + ) + ParticleManager:SetParticleControl( + effect, + 2, + Vector(len, 0, 0) + ) + end +end +function modifier_hoodwink_sharpshooter_custom.prototype.UpdateStack(self) + local fullTime = self.charge * (1 - self.base) + local pct = math.min( + 1, + self:GetElapsedTime() / fullTime + ) + pct = math.floor(pct * 100) + self:SetStackCount(pct) +end +function modifier_hoodwink_sharpshooter_custom.prototype.UpdateEffect(self) + if self.previewEffect == -1 then + return + end + local startPos = self:GetParent():GetAbsOrigin() + local endPos = startPos:__add(self.currentDir:__mul(self.projectileRange)) + ParticleManager:SetParticleControl(self.previewEffect, 0, startPos) + ParticleManager:SetParticleControl(self.previewEffect, 1, endPos) +end +modifier_hoodwink_sharpshooter_custom = __TS__Decorate( + modifier_hoodwink_sharpshooter_custom, + modifier_hoodwink_sharpshooter_custom, + {registerModifier(nil)}, + {kind = "class", name = "modifier_hoodwink_sharpshooter_custom"} +) +____exports.modifier_hoodwink_sharpshooter_custom = modifier_hoodwink_sharpshooter_custom +modifier_hoodwink_sharpshooter_custom_debuff = __TS__Class() +modifier_hoodwink_sharpshooter_custom_debuff.name = "modifier_hoodwink_sharpshooter_custom_debuff" +modifier_hoodwink_sharpshooter_custom_debuff.____file_path = "scripts/vscripts/abilities/heroes/hoodwink/hoodwink_sharpshooter_custom.lua" +__TS__ClassExtends(modifier_hoodwink_sharpshooter_custom_debuff, BaseModifier) +function modifier_hoodwink_sharpshooter_custom_debuff.prototype.____constructor(self, ...) + BaseModifier.prototype.____constructor(self, ...) + self.slow = 0 +end +function modifier_hoodwink_sharpshooter_custom_debuff.prototype.IsHidden(self) + return false +end +function modifier_hoodwink_sharpshooter_custom_debuff.prototype.IsDebuff(self) + return true +end +function modifier_hoodwink_sharpshooter_custom_debuff.prototype.IsPurgable(self) + return true +end +function modifier_hoodwink_sharpshooter_custom_debuff.prototype.OnCreated(self, kv) + local parent = self:GetParent() + local ability = self:GetAbility() + if not ability then + return + end + self.slow = -ability:GetSpecialValueFor("slow_move_pct") + if not IsServer() then + return + end + local direction = Vector(kv.x, kv.y, 0):Normalized() + local effect = ParticleManager:CreateParticle("particles/units/heroes/hero_hoodwink/hoodwink_sharpshooter_debuff.vpcf", PATTACH_POINT_FOLLOW, parent) + ParticleManager:SetParticleControlEnt( + effect, + 0, + parent, + PATTACH_POINT_FOLLOW, + "attach_hitloc", + parent:GetOrigin(), + true + ) + ParticleManager:SetParticleControlForward(effect, 1, direction) + self:AddParticle( + effect, + false, + false, + -1, + false, + false + ) +end +function modifier_hoodwink_sharpshooter_custom_debuff.prototype.DeclareFunctions(self) + return {MODIFIER_PROPERTY_MOVESPEED_BONUS_PERCENTAGE} +end +function modifier_hoodwink_sharpshooter_custom_debuff.prototype.GetModifierMoveSpeedBonus_Percentage(self) + return self.slow +end +function modifier_hoodwink_sharpshooter_custom_debuff.prototype.CheckState(self) + return {[MODIFIER_STATE_PASSIVES_DISABLED] = true} +end +function modifier_hoodwink_sharpshooter_custom_debuff.prototype.GetEffectName(self) + return "particles/generic_gameplay/generic_break.vpcf" +end +function modifier_hoodwink_sharpshooter_custom_debuff.prototype.GetEffectAttachType(self) + return PATTACH_OVERHEAD_FOLLOW +end +modifier_hoodwink_sharpshooter_custom_debuff = __TS__Decorate( + modifier_hoodwink_sharpshooter_custom_debuff, + modifier_hoodwink_sharpshooter_custom_debuff, + {registerModifier(nil)}, + {kind = "class", name = "modifier_hoodwink_sharpshooter_custom_debuff"} +) +modifier_hoodwink_sharpshooter_custom_sound = __TS__Class() +modifier_hoodwink_sharpshooter_custom_sound.name = "modifier_hoodwink_sharpshooter_custom_sound" +modifier_hoodwink_sharpshooter_custom_sound.____file_path = "scripts/vscripts/abilities/heroes/hoodwink/hoodwink_sharpshooter_custom.lua" +__TS__ClassExtends(modifier_hoodwink_sharpshooter_custom_sound, BaseModifier) +function modifier_hoodwink_sharpshooter_custom_sound.prototype.IsHidden(self) + return true +end +function modifier_hoodwink_sharpshooter_custom_sound.prototype.IsPurgable(self) + return false +end +modifier_hoodwink_sharpshooter_custom_sound = __TS__Decorate( + modifier_hoodwink_sharpshooter_custom_sound, + modifier_hoodwink_sharpshooter_custom_sound, + {registerModifier(nil)}, + {kind = "class", name = "modifier_hoodwink_sharpshooter_custom_sound"} +) +return ____exports diff --git a/scripts/vscripts/abilities/heroes/juggernaut/ability_juggernaut_blade_dance_custom.lua b/scripts/vscripts/abilities/heroes/juggernaut/ability_juggernaut_blade_dance_custom.lua new file mode 100644 index 0000000..f8075db --- /dev/null +++ b/scripts/vscripts/abilities/heroes/juggernaut/ability_juggernaut_blade_dance_custom.lua @@ -0,0 +1,309 @@ +local ____lualib = require("lualib_bundle") +local __TS__Class = ____lualib.__TS__Class +local __TS__ClassExtends = ____lualib.__TS__ClassExtends +local __TS__Decorate = ____lualib.__TS__Decorate +local ____exports = {} +local ____dota_ts_adapter = require("lib.dota_ts_adapter") +local BaseAbility = ____dota_ts_adapter.BaseAbility +local BaseModifier = ____dota_ts_adapter.BaseModifier +local registerAbility = ____dota_ts_adapter.registerAbility +local registerModifier = ____dota_ts_adapter.registerModifier +local ____vampirism = require("utils.vampirism") +local addPhysicalVampirism = ____vampirism.addPhysicalVampirism +local reducePhysicalVampirism = ____vampirism.reducePhysicalVampirism +____exports.ability_juggernaut_blade_dance_custom = __TS__Class() +local ability_juggernaut_blade_dance_custom = ____exports.ability_juggernaut_blade_dance_custom +ability_juggernaut_blade_dance_custom.name = "ability_juggernaut_blade_dance_custom" +ability_juggernaut_blade_dance_custom.____file_path = "scripts/vscripts/abilities/heroes/juggernaut/ability_juggernaut_blade_dance_custom.lua" +__TS__ClassExtends(ability_juggernaut_blade_dance_custom, BaseAbility) +function ability_juggernaut_blade_dance_custom.prototype.GetIntrinsicModifierName(self) + return "modifier_juggernaut_blade_dance_custom" +end +function ability_juggernaut_blade_dance_custom.prototype.GetCooldown(self, _level) + return self:GetSpecialValueFor("AbilityCooldown") +end +function ability_juggernaut_blade_dance_custom.prototype.GetManaCost(self, _level) + return self:GetSpecialValueFor("AbilityManaCost") +end +function ability_juggernaut_blade_dance_custom.prototype.GetCastRange(self, _location, _target) + return self:GetSpecialValueFor("AbilityCastRange") +end +function ability_juggernaut_blade_dance_custom.prototype.PerformSlash(self, caster, startPos, endPos) + if not IsServer() then + return + end + local radius = self:GetSpecialValueFor("astral_slash_radius") + local debuffDuration = self:GetSpecialValueFor("astral_slash_debuff_duration") + local groundEnd = GetGroundPosition(endPos, nil) + caster:StartGestureWithPlaybackRate(ACT_DOTA_TELEPORT_END, 1) + FindClearSpaceForUnit(caster, groundEnd, true) + local enemies = FindUnitsInLine( + caster:GetTeamNumber(), + startPos, + groundEnd, + nil, + radius, + DOTA_UNIT_TARGET_TEAM_ENEMY, + bit.bor(DOTA_UNIT_TARGET_HERO, DOTA_UNIT_TARGET_BASIC), + DOTA_UNIT_TARGET_FLAG_MAGIC_IMMUNE_ENEMIES + ) + for ____, enemy in ipairs(enemies) do + do + if not enemy or not enemy:IsAlive() then + goto __continue8 + end + local isAltCast = self:IsAltCastAbility() + if self:GetSpecialValueFor("juggernaut_blade_dance_jugg_step") > 0 then + enemy:AddNewModifier(caster, self, ____exports.modifier_juggernaut_blade_dance_astral_slash_debuff.name, {duration = debuffDuration}) + if not isAltCast then + enemy:AddNewModifier(caster, self, ____exports.modifier_juggernaut_blade_dance_astral_slash_debuff.name, {duration = debuffDuration}) + end + end + local count = self:GetSpecialValueFor("astral_slash_attack_count") + if self:IsAltCastAbility() then + count = math.max( + 1, + math.floor(count / 2) + ) + end + do + local i = 0 + while i < count do + caster:PerformAttack( + enemy, + true, + true, + true, + false, + false, + false, + true + ) + i = i + 1 + end + end + end + ::__continue8:: + end + self:PlayEffects(startPos, groundEnd) +end +function ability_juggernaut_blade_dance_custom.prototype.OnSpellStart(self) + if not IsServer() then + return + end + local caster = self:GetCaster() + local point = self:GetCursorPosition() + local origin = caster:GetAbsOrigin() + local isAltCast = self:IsAltCastAbility() + local minDist = self:GetSpecialValueFor("min_travel_distance") + local maxDist = self:GetSpecialValueFor("max_travel_distance") + local direction = point - origin + direction.z = 0 + local dist2D = direction:Length2D() + local dist = math.max( + math.min(maxDist, dist2D), + minDist + ) + local dirNorm = direction:Normalized() + local endPos = Vector(origin.x + dirNorm.x * dist, origin.y + dirNorm.y * dist, origin.z) + local targetPos = GetGroundPosition(endPos, nil) + self:PerformSlash(caster, origin, targetPos) + if isAltCast then + local returnPos = GetGroundPosition(origin, nil) + caster:FaceTowards(returnPos) + caster:AddNewModifier(caster, self, ____exports.modifier_juggernaut_blade_dance_return.name, {duration = 0.25, x = returnPos.x, y = returnPos.y, z = returnPos.z}) + end +end +function ability_juggernaut_blade_dance_custom.prototype.PlayEffects(self, origin, target) + local particleCast = "particles/juggernaut_step.vpcf" + local effectCast = ParticleManager:CreateParticle(particleCast, PATTACH_WORLDORIGIN, nil) + ParticleManager:SetParticleControl(effectCast, 0, origin) + ParticleManager:SetParticleControl(effectCast, 1, target) + ParticleManager:ReleaseParticleIndex(effectCast) + EmitSoundOnLocationWithCaster( + origin, + "Hero_Juggernaut.OmniSlash", + self:GetCaster() + ) + EmitSoundOnLocationWithCaster( + target, + "Hero_Juggernaut.OmniSlash.End", + self:GetCaster() + ) +end +ability_juggernaut_blade_dance_custom = __TS__Decorate( + ability_juggernaut_blade_dance_custom, + ability_juggernaut_blade_dance_custom, + {registerAbility(nil)}, + {kind = "class", name = "ability_juggernaut_blade_dance_custom"} +) +____exports.ability_juggernaut_blade_dance_custom = ability_juggernaut_blade_dance_custom +____exports.modifier_juggernaut_blade_dance_custom = __TS__Class() +local modifier_juggernaut_blade_dance_custom = ____exports.modifier_juggernaut_blade_dance_custom +modifier_juggernaut_blade_dance_custom.name = "modifier_juggernaut_blade_dance_custom" +modifier_juggernaut_blade_dance_custom.____file_path = "scripts/vscripts/abilities/heroes/juggernaut/ability_juggernaut_blade_dance_custom.lua" +__TS__ClassExtends(modifier_juggernaut_blade_dance_custom, BaseModifier) +function modifier_juggernaut_blade_dance_custom.prototype.____constructor(self, ...) + BaseModifier.prototype.____constructor(self, ...) + self.lifestealAppliedAmount = 0 +end +function modifier_juggernaut_blade_dance_custom.prototype.IsHidden(self) + return true +end +function modifier_juggernaut_blade_dance_custom.prototype.IsPurgable(self) + return false +end +function modifier_juggernaut_blade_dance_custom.prototype.RemoveOnDeath(self) + return false +end +function modifier_juggernaut_blade_dance_custom.prototype.OnIntervalThink(self) + if not IsServer() then + return + end + local parent = self:GetParent() + local ability = self:GetAbility() + if not ability or not parent:IsRealHero() then + return + end + local critMod = parent:FindModifierByName("modifier_stacking_crit") + local critChance = ability:GetSpecialValueFor("crit_chance") + local critMult = ability:GetSpecialValueFor("crit_mult") + if parent:PassivesDisabled() then + if critMod ~= nil then + critMod:RemoveCrit("blade_dance") + end + if self.lifestealAppliedAmount > 0 then + reducePhysicalVampirism(nil, parent, self.lifestealAppliedAmount) + self.lifestealAppliedAmount = 0 + end + return + end + if critMod then + critMod:UpdateCrit( + 0, + 0, + critChance, + critMult, + "blade_dance" + ) + end + local lifesteal = ability:GetSpecialValueFor("lifesteal_percent") + if lifesteal > 0 and self.lifestealAppliedAmount == 0 then + addPhysicalVampirism(nil, parent, lifesteal) + self.lifestealAppliedAmount = lifesteal + end +end +function modifier_juggernaut_blade_dance_custom.prototype.OnCreated(self) + if not IsServer() then + return + end + self:StartIntervalThink(0.2) + self:OnIntervalThink() +end +function modifier_juggernaut_blade_dance_custom.prototype.OnRefresh(self) + if not IsServer() then + return + end + self:OnIntervalThink() +end +function modifier_juggernaut_blade_dance_custom.prototype.OnDestroy(self) + if not IsServer() then + return + end + local parent = self:GetParent() + local ____opt_2 = parent and parent:FindModifierByName("modifier_stacking_crit") + if ____opt_2 ~= nil then + ____opt_2:RemoveCrit("blade_dance") + end + if self.lifestealAppliedAmount > 0 and (parent and parent:IsRealHero()) then + reducePhysicalVampirism(nil, parent, self.lifestealAppliedAmount) + end +end +modifier_juggernaut_blade_dance_custom = __TS__Decorate( + modifier_juggernaut_blade_dance_custom, + modifier_juggernaut_blade_dance_custom, + {registerModifier(nil)}, + {kind = "class", name = "modifier_juggernaut_blade_dance_custom"} +) +____exports.modifier_juggernaut_blade_dance_custom = modifier_juggernaut_blade_dance_custom +____exports.modifier_juggernaut_blade_dance_return = __TS__Class() +local modifier_juggernaut_blade_dance_return = ____exports.modifier_juggernaut_blade_dance_return +modifier_juggernaut_blade_dance_return.name = "modifier_juggernaut_blade_dance_return" +modifier_juggernaut_blade_dance_return.____file_path = "scripts/vscripts/abilities/heroes/juggernaut/ability_juggernaut_blade_dance_custom.lua" +__TS__ClassExtends(modifier_juggernaut_blade_dance_return, BaseModifier) +function modifier_juggernaut_blade_dance_return.prototype.IsHidden(self) + return true +end +function modifier_juggernaut_blade_dance_return.prototype.IsPurgable(self) + return false +end +function modifier_juggernaut_blade_dance_return.prototype.OnCreated(self, params) + if not IsServer() then + return + end + local x = params.x or 0 + local y = params.y or 0 + local z = params.z or 0 + self.returnPos = Vector(x, y, z) +end +function modifier_juggernaut_blade_dance_return.prototype.OnDestroy(self) + if not IsServer() then + return + end + local parent = self:GetParent() + local ability = self:GetAbility() + if parent and parent:IsAlive() and ability then + local startPos = parent:GetAbsOrigin() + ability:PerformSlash(parent, startPos, self.returnPos) + end +end +modifier_juggernaut_blade_dance_return = __TS__Decorate( + modifier_juggernaut_blade_dance_return, + modifier_juggernaut_blade_dance_return, + {registerModifier(nil)}, + {kind = "class", name = "modifier_juggernaut_blade_dance_return"} +) +____exports.modifier_juggernaut_blade_dance_return = modifier_juggernaut_blade_dance_return +____exports.modifier_juggernaut_blade_dance_astral_slash_debuff = __TS__Class() +local modifier_juggernaut_blade_dance_astral_slash_debuff = ____exports.modifier_juggernaut_blade_dance_astral_slash_debuff +modifier_juggernaut_blade_dance_astral_slash_debuff.name = "modifier_juggernaut_blade_dance_astral_slash_debuff" +modifier_juggernaut_blade_dance_astral_slash_debuff.____file_path = "scripts/vscripts/abilities/heroes/juggernaut/ability_juggernaut_blade_dance_custom.lua" +__TS__ClassExtends(modifier_juggernaut_blade_dance_astral_slash_debuff, BaseModifier) +function modifier_juggernaut_blade_dance_astral_slash_debuff.prototype.IsHidden(self) + return false +end +function modifier_juggernaut_blade_dance_astral_slash_debuff.prototype.IsPurgable(self) + return true +end +function modifier_juggernaut_blade_dance_astral_slash_debuff.prototype.GetEffectName(self) + return "particles/econ/items/void_spirit/void_spirit_immortal_2021/void_spirit_immortal_2021_astral_step_debuff.vpcf" +end +function modifier_juggernaut_blade_dance_astral_slash_debuff.prototype.GetEffectAttachType(self) + return PATTACH_ABSORIGIN_FOLLOW +end +function modifier_juggernaut_blade_dance_astral_slash_debuff.prototype.DeclareFunctions(self) + return {MODIFIER_PROPERTY_PHYSICAL_ARMOR_BONUS} +end +function modifier_juggernaut_blade_dance_astral_slash_debuff.prototype.GetAttributes(self) + return MODIFIER_ATTRIBUTE_MULTIPLE +end +function modifier_juggernaut_blade_dance_astral_slash_debuff.prototype.GetModifierPhysicalArmorBonus(self, event) + local parent = self:GetParent() + if not parent then + return 0 + end + if not parent:IsRealHero() then + return 0 + end + local hero = parent + local armor = hero:GetPhysicalArmorBaseValue() + return -armor * (self:GetAbility():GetSpecialValueFor("juggernaut_blade_dance_jugg_step_armor_reduce") * 0.01) +end +modifier_juggernaut_blade_dance_astral_slash_debuff = __TS__Decorate( + modifier_juggernaut_blade_dance_astral_slash_debuff, + modifier_juggernaut_blade_dance_astral_slash_debuff, + {registerModifier(nil)}, + {kind = "class", name = "modifier_juggernaut_blade_dance_astral_slash_debuff"} +) +____exports.modifier_juggernaut_blade_dance_astral_slash_debuff = modifier_juggernaut_blade_dance_astral_slash_debuff +return ____exports diff --git a/scripts/vscripts/abilities/heroes/juggernaut/ability_juggernaut_blade_fury_custom.lua b/scripts/vscripts/abilities/heroes/juggernaut/ability_juggernaut_blade_fury_custom.lua new file mode 100644 index 0000000..959618d --- /dev/null +++ b/scripts/vscripts/abilities/heroes/juggernaut/ability_juggernaut_blade_fury_custom.lua @@ -0,0 +1,730 @@ +local ____lualib = require("lualib_bundle") +local __TS__Class = ____lualib.__TS__Class +local __TS__ClassExtends = ____lualib.__TS__ClassExtends +local __TS__Decorate = ____lualib.__TS__Decorate +local ____exports = {} +local ____dota_ts_adapter = require("lib.dota_ts_adapter") +local BaseAbility = ____dota_ts_adapter.BaseAbility +local BaseModifier = ____dota_ts_adapter.BaseModifier +local registerAbility = ____dota_ts_adapter.registerAbility +local registerModifier = ____dota_ts_adapter.registerModifier +local ____luck = require("utils.luck") +local rollLuckChance = ____luck.rollLuckChance +____exports.ability_juggernaut_blade_fury_custom = __TS__Class() +local ability_juggernaut_blade_fury_custom = ____exports.ability_juggernaut_blade_fury_custom +ability_juggernaut_blade_fury_custom.name = "ability_juggernaut_blade_fury_custom" +ability_juggernaut_blade_fury_custom.____file_path = "scripts/vscripts/abilities/heroes/juggernaut/ability_juggernaut_blade_fury_custom.lua" +__TS__ClassExtends(ability_juggernaut_blade_fury_custom, BaseAbility) +function ability_juggernaut_blade_fury_custom.prototype.GetIntrinsicModifierName(self) + return "modifier_juggernaut_blade_fury_custom_passive" +end +function ability_juggernaut_blade_fury_custom.prototype.GetAOERadius(self) + return self:GetSpecialValueFor("radius") +end +function ability_juggernaut_blade_fury_custom.prototype.OnSpellStart(self) + if not IsServer() then + return + end + local caster = self:GetCaster() + local duration = self:GetSpecialValueFor("duration") + caster:Purge( + false, + true, + false, + true, + false + ) + local isAltCast = self:IsAltCastAbility() + if isAltCast then + local point = self:GetCursorPosition() + local casterPos = caster:GetAbsOrigin() + local castRange = self:GetCastRange(casterPos, nil) + local delta = Vector(point.x - casterPos.x, point.y - casterPos.y, 0) + local dist2D = delta:Length2D() + local clampedPoint = dist2D > castRange and dist2D > 0 and Vector(casterPos.x + delta.x / dist2D * castRange, casterPos.y + delta.y / dist2D * castRange, point.z) or point + local groundPoint = GetGroundPosition(clampedPoint, nil) + local thinker = CreateModifierThinker( + caster, + self, + "modifier_juggernaut_blade_fury_thinker", + {x = groundPoint.x, y = groundPoint.y, z = groundPoint.z}, + caster:GetAbsOrigin(), + caster:GetTeamNumber(), + false + ) + local ____opt_0 = self:GetCaster() + if ____opt_0 ~= nil then + ____opt_0:RemoveGesture(ACT_DOTA_ATTACK) + end + if thinker and IsValidEntity(thinker) then + thinker:AddNewModifier(caster, self, "modifier_juggernaut_blade_fury_sword_unit", {}) + thinker:AddNewModifier(caster, self, "modifier_juggernaut_blade_fury_custom_point", {}) + thinker:AddNewModifier(caster, self, "modifier_juggernaut_blade_fury_fly", {x = groundPoint.x, y = groundPoint.y}) + EmitSoundOn("Hero_Juggernaut.BladeFuryStart", thinker) + end + return + else + caster:AddNewModifier(caster, self, "modifier_juggernaut_blade_fury_custom", {duration = duration}) + EmitSoundOn("Hero_Juggernaut.BladeFuryStart", caster) + end +end +ability_juggernaut_blade_fury_custom = __TS__Decorate( + ability_juggernaut_blade_fury_custom, + ability_juggernaut_blade_fury_custom, + {registerAbility(nil)}, + {kind = "class", name = "ability_juggernaut_blade_fury_custom"} +) +____exports.ability_juggernaut_blade_fury_custom = ability_juggernaut_blade_fury_custom +____exports.modifier_juggernaut_blade_fury_custom = __TS__Class() +local modifier_juggernaut_blade_fury_custom = ____exports.modifier_juggernaut_blade_fury_custom +modifier_juggernaut_blade_fury_custom.name = "modifier_juggernaut_blade_fury_custom" +modifier_juggernaut_blade_fury_custom.____file_path = "scripts/vscripts/abilities/heroes/juggernaut/ability_juggernaut_blade_fury_custom.lua" +__TS__ClassExtends(modifier_juggernaut_blade_fury_custom, BaseModifier) +function modifier_juggernaut_blade_fury_custom.prototype.____constructor(self, ...) + BaseModifier.prototype.____constructor(self, ...) + self.damageInterval = 1 / self:GetCaster():GetAttacksPerSecond(false) +end +function modifier_juggernaut_blade_fury_custom.prototype.IsHidden(self) + return false +end +function modifier_juggernaut_blade_fury_custom.prototype.IsPurgable(self) + return false +end +function modifier_juggernaut_blade_fury_custom.prototype.CheckState(self) + return {[MODIFIER_STATE_DEBUFF_IMMUNE] = true} +end +function modifier_juggernaut_blade_fury_custom.prototype.OnCreated(self, params) + if not IsServer() then + return + end + local parent = self:GetParent() + local ability = self:GetAbility() + if not ability then + return + end + local ____opt_2 = self:GetCaster() + if ____opt_2 ~= nil then + ____opt_2:StartGesture(ACT_DOTA_OVERRIDE_ABILITY_1) + end + local particleId = ParticleManager:CreateParticle( + "particles/units/heroes/hero_juggernaut/juggernaut_blade_fury.vpcf", + PATTACH_ABSORIGIN_FOLLOW, + self:GetParent() + ) + ParticleManager:SetParticleControl( + particleId, + 0, + self:GetParent():GetAbsOrigin() + ) + ParticleManager:SetParticleControl( + particleId, + 1, + self:GetParent():GetAbsOrigin() + ) + self.particleId = particleId + self:StartIntervalThink(self.damageInterval) +end +function modifier_juggernaut_blade_fury_custom.prototype.OnRefresh(self, params) + StopSoundOn( + "Hero_Juggernaut.BladeFuryStart", + self:GetParent() + ) +end +function modifier_juggernaut_blade_fury_custom.prototype.OnDestroy(self) + if IsClient() then + return + end + local ____opt_4 = self:GetCaster() + if ____opt_4 ~= nil then + ____opt_4:RemoveGesture(ACT_DOTA_OVERRIDE_ABILITY_1) + end + StopSoundOn( + "Hero_Juggernaut.BladeFuryStart", + self:GetParent() + ) + if self.particleId ~= nil then + ParticleManager:DestroyParticle(self.particleId, false) + ParticleManager:ReleaseParticleIndex(self.particleId) + self.particleId = nil + end + self:GetParent():Purge( + false, + true, + false, + true, + false + ) + EmitSoundOn( + "Hero_Juggernaut.BladeFuryStop", + self:GetParent() + ) +end +function modifier_juggernaut_blade_fury_custom.prototype.OnIntervalThink(self) + if not IsServer() then + return + end + local parent = self:GetParent() + local caster = self:GetCaster() + local ability = self:GetAbility() + if not ability or not caster or not caster:IsAlive() or not parent:IsAlive() then + return + end + local radius = ability:GetSpecialValueFor("radius") + local damagePerTick = ability:GetSpecialValueFor("damage_per_tick") + local enemies = FindUnitsInRadius( + caster:GetTeamNumber(), + parent:GetAbsOrigin(), + nil, + radius, + DOTA_UNIT_TARGET_TEAM_ENEMY, + bit.bor(DOTA_UNIT_TARGET_HERO, DOTA_UNIT_TARGET_BASIC), + DOTA_UNIT_TARGET_FLAG_NONE, + FIND_ANY_ORDER, + false + ) + for ____, enemy in ipairs(enemies) do + if enemy and enemy:IsAlive() and not enemy:IsInvulnerable() then + if self:GetAbility():GetSpecialValueFor("leap_sword") > 0 then + caster:PerformAttack( + enemy, + true, + true, + true, + false, + false, + false, + true + ) + end + ApplyDamage({ + victim = enemy, + attacker = caster, + damage = damagePerTick, + damage_type = ability:GetAbilityDamageType(), + ability = ability + }) + local finaldamage + if self:GetAbility():GetSpecialValueFor("leap_sword") > 0 then + finaldamage = damagePerTick + caster:GetAverageTrueAttackDamage(enemy) + else + finaldamage = damagePerTick + end + SendOverheadEventMessage( + nil, + OVERHEAD_ALERT_DAMAGE, + enemy, + finaldamage, + nil + ) + end + end +end +modifier_juggernaut_blade_fury_custom = __TS__Decorate( + modifier_juggernaut_blade_fury_custom, + modifier_juggernaut_blade_fury_custom, + {registerModifier(nil)}, + {kind = "class", name = "modifier_juggernaut_blade_fury_custom"} +) +____exports.modifier_juggernaut_blade_fury_custom = modifier_juggernaut_blade_fury_custom +____exports.modifier_juggernaut_blade_fury_custom_passive = __TS__Class() +local modifier_juggernaut_blade_fury_custom_passive = ____exports.modifier_juggernaut_blade_fury_custom_passive +modifier_juggernaut_blade_fury_custom_passive.name = "modifier_juggernaut_blade_fury_custom_passive" +modifier_juggernaut_blade_fury_custom_passive.____file_path = "scripts/vscripts/abilities/heroes/juggernaut/ability_juggernaut_blade_fury_custom.lua" +__TS__ClassExtends(modifier_juggernaut_blade_fury_custom_passive, BaseModifier) +function modifier_juggernaut_blade_fury_custom_passive.prototype.IsHidden(self) + return true +end +function modifier_juggernaut_blade_fury_custom_passive.prototype.IsPurgable(self) + return false +end +function modifier_juggernaut_blade_fury_custom_passive.prototype.DeclareFunctions(self) + return {MODIFIER_EVENT_ON_ATTACK_LANDED} +end +function modifier_juggernaut_blade_fury_custom_passive.prototype.OnAttackLanded(self, event) + if not HasShard( + nil, + self:GetCaster() + ) then + return + end + local caster = self:GetCaster() + if not caster then + return + end + if caster:PassivesDisabled() then + return + end + if event.attacker ~= self:GetParent() then + return + end + local parent = self:GetParent() + if not parent or not parent:IsRealHero() then + return + end + local hero = parent + local ability = self:GetAbility() + if not ability then + return + end + local baseChancePercent = ability:GetSpecialValueFor("proc_chance") + local baseChance = baseChancePercent / 100 + if rollLuckChance(nil, hero, baseChance) then + if self:GetCaster():HasModifier("modifier_juggernaut_blade_fury_custom") then + caster:AddNewModifier( + caster, + ability, + "modifier_juggernaut_blade_fury_custom", + {duration = self:GetCaster():FindModifierByName("modifier_juggernaut_blade_fury_custom"):GetRemainingTime() + 1 / self:GetCaster():GetAttacksPerSecond(false)} + ) + else + caster:AddNewModifier( + caster, + ability, + "modifier_juggernaut_blade_fury_custom", + {duration = 1 / self:GetCaster():GetAttacksPerSecond(false)} + ) + end + end +end +modifier_juggernaut_blade_fury_custom_passive = __TS__Decorate( + modifier_juggernaut_blade_fury_custom_passive, + modifier_juggernaut_blade_fury_custom_passive, + {registerModifier(nil)}, + {kind = "class", name = "modifier_juggernaut_blade_fury_custom_passive"} +) +____exports.modifier_juggernaut_blade_fury_custom_passive = modifier_juggernaut_blade_fury_custom_passive +____exports.modifier_juggernaut_blade_fury_sword_unit = __TS__Class() +local modifier_juggernaut_blade_fury_sword_unit = ____exports.modifier_juggernaut_blade_fury_sword_unit +modifier_juggernaut_blade_fury_sword_unit.name = "modifier_juggernaut_blade_fury_sword_unit" +modifier_juggernaut_blade_fury_sword_unit.____file_path = "scripts/vscripts/abilities/heroes/juggernaut/ability_juggernaut_blade_fury_custom.lua" +__TS__ClassExtends(modifier_juggernaut_blade_fury_sword_unit, BaseModifier) +function modifier_juggernaut_blade_fury_sword_unit.prototype.IsHidden(self) + return true +end +function modifier_juggernaut_blade_fury_sword_unit.prototype.IsPurgable(self) + return false +end +function modifier_juggernaut_blade_fury_sword_unit.prototype.CheckState(self) + return { + [MODIFIER_STATE_INVULNERABLE] = true, + [MODIFIER_STATE_UNSELECTABLE] = true, + [MODIFIER_STATE_UNTARGETABLE] = true, + [MODIFIER_STATE_NO_HEALTH_BAR] = true, + [MODIFIER_STATE_NOT_ON_MINIMAP] = true, + [MODIFIER_STATE_NO_UNIT_COLLISION] = true + } +end +modifier_juggernaut_blade_fury_sword_unit = __TS__Decorate( + modifier_juggernaut_blade_fury_sword_unit, + modifier_juggernaut_blade_fury_sword_unit, + {registerModifier(nil)}, + {kind = "class", name = "modifier_juggernaut_blade_fury_sword_unit"} +) +____exports.modifier_juggernaut_blade_fury_sword_unit = modifier_juggernaut_blade_fury_sword_unit +local BLADE_FURY_FLY_SPEED = 800 +local BLADE_FURY_FLY_INTERVAL = 0.03 +local BLADE_FURY_THINKER_MOD = "modifier_juggernaut_blade_fury_thinker" +--- Точечный режим на thinker: при смерти/пропажи кастера убираем юнит, иначе вихрь крутится вечно. +local function cleanupBladeFuryThinkerAfterCasterLost(self, parent, caster) + if not IsServer() then + return + end + if caster ~= nil and caster ~= nil and caster:IsAlive() then + return + end + local thinkerMod = parent:FindModifierByName(BLADE_FURY_THINKER_MOD) + if thinkerMod then + thinkerMod:Destroy() + return + end + StopSoundOn("Hero_Juggernaut.BladeFuryStart", parent) + EmitSoundOn("Hero_Juggernaut.BladeFuryStop", parent) + if IsValidEntity(parent) then + UTIL_Remove(parent) + end +end +____exports.modifier_juggernaut_blade_fury_custom_point = __TS__Class() +local modifier_juggernaut_blade_fury_custom_point = ____exports.modifier_juggernaut_blade_fury_custom_point +modifier_juggernaut_blade_fury_custom_point.name = "modifier_juggernaut_blade_fury_custom_point" +modifier_juggernaut_blade_fury_custom_point.____file_path = "scripts/vscripts/abilities/heroes/juggernaut/ability_juggernaut_blade_fury_custom.lua" +__TS__ClassExtends(modifier_juggernaut_blade_fury_custom_point, BaseModifier) +function modifier_juggernaut_blade_fury_custom_point.prototype.____constructor(self, ...) + BaseModifier.prototype.____constructor(self, ...) + self.radius = 0 + self.damageTickInterval = 0.2 + self.lastDamageTime = 0 +end +function modifier_juggernaut_blade_fury_custom_point.prototype.IsHidden(self) + return false +end +function modifier_juggernaut_blade_fury_custom_point.prototype.IsPurgable(self) + return false +end +function modifier_juggernaut_blade_fury_custom_point.prototype.OnCreated(self, _params) + local ability = self:GetAbility() + if not ability then + return + end + self.radius = ability:GetSpecialValueFor("radius") + local caster = self:GetCaster() + if caster then + self.damageTickInterval = 0.1 + 1 / caster:GetAttacksPerSecond(false) + end + self.lastDamageTime = GameRules:GetGameTime() + if IsServer() then + local particleName = "particles/units/heroes/hero_juggernaut/juggernaut_blade_fury.vpcf" + local fx = ParticleManager:CreateParticle( + particleName, + PATTACH_ABSORIGIN_FOLLOW, + self:GetParent() + ) + ParticleManager:SetParticleControl( + fx, + 5, + Vector(self.radius * 1.2, 0, 0) + ) + self:AddParticle( + fx, + false, + false, + -1, + false, + false + ) + self.particleId = fx + end + self:StartIntervalThink(0.1) +end +function modifier_juggernaut_blade_fury_custom_point.prototype.OnIntervalThink(self) + if not IsServer() then + return + end + local parent = self:GetParent() + local ability = self:GetAbility() + local caster = self:GetCaster() + if not ability or not parent:IsAlive() then + return + end + if not caster or not caster:IsAlive() then + cleanupBladeFuryThinkerAfterCasterLost(nil, parent, caster) + return + end + local pos = parent:GetAbsOrigin() + local currentTime = GameRules:GetGameTime() + if currentTime - self.lastDamageTime >= self.damageTickInterval then + self.lastDamageTime = currentTime + local damagePerTick = ability:GetSpecialValueFor("damage_per_tick") + local enemies = FindUnitsInRadius( + caster:GetTeamNumber(), + pos, + nil, + self.radius, + DOTA_UNIT_TARGET_TEAM_ENEMY, + bit.bor(DOTA_UNIT_TARGET_HERO, DOTA_UNIT_TARGET_BASIC), + DOTA_UNIT_TARGET_FLAG_NONE, + FIND_ANY_ORDER, + false + ) + for ____, enemy in ipairs(enemies) do + if enemy and enemy:IsAlive() and not enemy:IsInvulnerable() then + if self:GetAbility():GetSpecialValueFor("leap_sword") > 0 then + caster:PerformAttack( + enemy, + true, + true, + true, + false, + false, + false, + true + ) + end + ApplyDamage({ + victim = enemy, + attacker = caster, + damage = damagePerTick, + damage_type = ability:GetAbilityDamageType(), + ability = ability + }) + local finaldamage = damagePerTick + caster:GetAverageTrueAttackDamage(enemy) + SendOverheadEventMessage( + nil, + OVERHEAD_ALERT_DAMAGE, + enemy, + finaldamage, + nil + ) + end + end + end +end +function modifier_juggernaut_blade_fury_custom_point.prototype.OnDestroy(self) + if IsServer() and self.particleId ~= nil then + ParticleManager:DestroyParticle(self.particleId, false) + ParticleManager:ReleaseParticleIndex(self.particleId) + self.particleId = nil + end +end +modifier_juggernaut_blade_fury_custom_point = __TS__Decorate( + modifier_juggernaut_blade_fury_custom_point, + modifier_juggernaut_blade_fury_custom_point, + {registerModifier(nil)}, + {kind = "class", name = "modifier_juggernaut_blade_fury_custom_point"} +) +____exports.modifier_juggernaut_blade_fury_custom_point = modifier_juggernaut_blade_fury_custom_point +____exports.modifier_juggernaut_blade_fury_fly = __TS__Class() +local modifier_juggernaut_blade_fury_fly = ____exports.modifier_juggernaut_blade_fury_fly +modifier_juggernaut_blade_fury_fly.name = "modifier_juggernaut_blade_fury_fly" +modifier_juggernaut_blade_fury_fly.____file_path = "scripts/vscripts/abilities/heroes/juggernaut/ability_juggernaut_blade_fury_custom.lua" +__TS__ClassExtends(modifier_juggernaut_blade_fury_fly, BaseModifier) +function modifier_juggernaut_blade_fury_fly.prototype.____constructor(self, ...) + BaseModifier.prototype.____constructor(self, ...) + self.speed = BLADE_FURY_FLY_SPEED + self.interval = BLADE_FURY_FLY_INTERVAL +end +function modifier_juggernaut_blade_fury_fly.prototype.IsHidden(self) + return true +end +function modifier_juggernaut_blade_fury_fly.prototype.IsPurgable(self) + return false +end +function modifier_juggernaut_blade_fury_fly.prototype.RemoveOnDeath(self) + return false +end +function modifier_juggernaut_blade_fury_fly.prototype.OnCreated(self, params) + if not IsServer() then + return + end + local parent = self:GetParent() + local z = GetGroundPosition( + Vector(params.x or 0, params.y or 0, 0), + nil + ).z + self.targetPos = Vector(params.x or 0, params.y or 0, z) + self:StartIntervalThink(self.interval) +end +function modifier_juggernaut_blade_fury_fly.prototype.OnIntervalThink(self) + if not IsServer() then + return + end + local parent = self:GetParent() + local caster = self:GetCaster() + if not caster or not caster:IsAlive() then + cleanupBladeFuryThinkerAfterCasterLost(nil, parent, caster) + return + end + local pos = parent:GetAbsOrigin() + local delta = Vector(self.targetPos.x - pos.x, self.targetPos.y - pos.y, self.targetPos.z - pos.z) + local dist = delta:Length2D() + local dir = delta:Normalized() + local step = self.speed * self.interval + if dist <= step + 5 then + parent:SetAbsOrigin(self.targetPos) + local ability = self:GetAbility() + local duration = ability and ability:GetSpecialValueFor("duration") or 5 + parent:AddNewModifier( + self:GetCaster(), + ability, + "modifier_juggernaut_blade_fury_pause", + {duration = duration} + ) + self:Destroy() + return + end + local newPos = Vector(pos.x + dir.x * step, pos.y + dir.y * step, pos.z + dir.z * step) + parent:SetAbsOrigin(GetGroundPosition(newPos, nil)) +end +modifier_juggernaut_blade_fury_fly = __TS__Decorate( + modifier_juggernaut_blade_fury_fly, + modifier_juggernaut_blade_fury_fly, + {registerModifier(nil)}, + {kind = "class", name = "modifier_juggernaut_blade_fury_fly"} +) +____exports.modifier_juggernaut_blade_fury_fly = modifier_juggernaut_blade_fury_fly +____exports.modifier_juggernaut_blade_fury_pause = __TS__Class() +local modifier_juggernaut_blade_fury_pause = ____exports.modifier_juggernaut_blade_fury_pause +modifier_juggernaut_blade_fury_pause.name = "modifier_juggernaut_blade_fury_pause" +modifier_juggernaut_blade_fury_pause.____file_path = "scripts/vscripts/abilities/heroes/juggernaut/ability_juggernaut_blade_fury_custom.lua" +__TS__ClassExtends(modifier_juggernaut_blade_fury_pause, BaseModifier) +function modifier_juggernaut_blade_fury_pause.prototype.IsHidden(self) + return true +end +function modifier_juggernaut_blade_fury_pause.prototype.RemoveOnDeath(self) + return false +end +function modifier_juggernaut_blade_fury_pause.prototype.IsPurgable(self) + return false +end +function modifier_juggernaut_blade_fury_pause.prototype.OnDestroy(self) + if not IsServer() then + return + end + local caster = self:GetCaster() + if not caster or not caster:IsAlive() then + return + end + self:GetParent():AddNewModifier( + caster, + self:GetAbility(), + "modifier_juggernaut_blade_fury_fly_back", + {} + ) +end +modifier_juggernaut_blade_fury_pause = __TS__Decorate( + modifier_juggernaut_blade_fury_pause, + modifier_juggernaut_blade_fury_pause, + {registerModifier(nil)}, + {kind = "class", name = "modifier_juggernaut_blade_fury_pause"} +) +____exports.modifier_juggernaut_blade_fury_pause = modifier_juggernaut_blade_fury_pause +____exports.modifier_juggernaut_blade_fury_fly_back = __TS__Class() +local modifier_juggernaut_blade_fury_fly_back = ____exports.modifier_juggernaut_blade_fury_fly_back +modifier_juggernaut_blade_fury_fly_back.name = "modifier_juggernaut_blade_fury_fly_back" +modifier_juggernaut_blade_fury_fly_back.____file_path = "scripts/vscripts/abilities/heroes/juggernaut/ability_juggernaut_blade_fury_custom.lua" +__TS__ClassExtends(modifier_juggernaut_blade_fury_fly_back, BaseModifier) +function modifier_juggernaut_blade_fury_fly_back.prototype.____constructor(self, ...) + BaseModifier.prototype.____constructor(self, ...) + self.interval = BLADE_FURY_FLY_INTERVAL + self.speed = BLADE_FURY_FLY_SPEED +end +function modifier_juggernaut_blade_fury_fly_back.prototype.IsHidden(self) + return true +end +function modifier_juggernaut_blade_fury_fly_back.prototype.IsPurgable(self) + return false +end +function modifier_juggernaut_blade_fury_fly_back.prototype.RemoveOnDeath(self) + return false +end +function modifier_juggernaut_blade_fury_fly_back.prototype.OnCreated(self, _params) + if not IsServer() then + return + end + self:StartIntervalThink(self.interval) +end +function modifier_juggernaut_blade_fury_fly_back.prototype.OnIntervalThink(self) + if not IsServer() then + return + end + local parent = self:GetParent() + local caster = self:GetCaster() + if not caster or not caster:IsAlive() then + cleanupBladeFuryThinkerAfterCasterLost(nil, parent, caster) + return + end + local pos = parent:GetAbsOrigin() + local casterPos = caster:GetAbsOrigin() + local delta = Vector(casterPos.x - pos.x, casterPos.y - pos.y, casterPos.z - pos.z) + local dist = delta:Length2D() + local dir = delta:Normalized() + local step = self.speed * self.interval + if dist <= 50 then + local thinkerMod = parent:FindModifierByName("modifier_juggernaut_blade_fury_thinker") + if thinkerMod then + thinkerMod:Destroy() + end + self:Destroy() + return + end + local move = math.min(step, dist) + local newPos = Vector(pos.x + dir.x * move, pos.y + dir.y * move, pos.z + dir.z * move) + parent:SetAbsOrigin(GetGroundPosition(newPos, nil)) +end +modifier_juggernaut_blade_fury_fly_back = __TS__Decorate( + modifier_juggernaut_blade_fury_fly_back, + modifier_juggernaut_blade_fury_fly_back, + {registerModifier(nil)}, + {kind = "class", name = "modifier_juggernaut_blade_fury_fly_back"} +) +____exports.modifier_juggernaut_blade_fury_fly_back = modifier_juggernaut_blade_fury_fly_back +____exports.ability_juggernaut_blade_fury_custom_alt = __TS__Class() +local ability_juggernaut_blade_fury_custom_alt = ____exports.ability_juggernaut_blade_fury_custom_alt +ability_juggernaut_blade_fury_custom_alt.name = "ability_juggernaut_blade_fury_custom_alt" +ability_juggernaut_blade_fury_custom_alt.____file_path = "scripts/vscripts/abilities/heroes/juggernaut/ability_juggernaut_blade_fury_custom.lua" +__TS__ClassExtends(ability_juggernaut_blade_fury_custom_alt, BaseAbility) +function ability_juggernaut_blade_fury_custom_alt.prototype.GetIntrinsicModifierName(self) + return "modifier_juggernaut_blade_fury_custom_passive" +end +function ability_juggernaut_blade_fury_custom_alt.prototype.OnSpellStart(self) + if not IsServer() then + return + end + local caster = self:GetCaster() + local point = self:GetCursorPosition() + local casterPos = caster:GetAbsOrigin() + local castRange = self:GetCastRange(casterPos, nil) + local delta = Vector(point.x - casterPos.x, point.y - casterPos.y, 0) + local dist2D = delta:Length2D() + local clampedPoint = dist2D > castRange and dist2D > 0 and Vector(casterPos.x + delta.x / dist2D * castRange, casterPos.y + delta.y / dist2D * castRange, point.z) or point + local groundPoint = GetGroundPosition(clampedPoint, nil) + local thinker = CreateModifierThinker( + caster, + self, + "modifier_juggernaut_blade_fury_thinker", + {x = groundPoint.x, y = groundPoint.y, z = groundPoint.z}, + caster:GetAbsOrigin(), + caster:GetTeamNumber(), + false + ) + if thinker and IsValidEntity(thinker) then + thinker:AddNewModifier(caster, self, "modifier_juggernaut_blade_fury_sword_unit", {}) + thinker:AddNewModifier(caster, self, "modifier_juggernaut_blade_fury_custom_point", {}) + thinker:AddNewModifier(caster, self, "modifier_juggernaut_blade_fury_fly", {x = groundPoint.x, y = groundPoint.y}) + EmitSoundOn("Hero_Juggernaut.BladeFuryStart", thinker) + end +end +ability_juggernaut_blade_fury_custom_alt = __TS__Decorate( + ability_juggernaut_blade_fury_custom_alt, + ability_juggernaut_blade_fury_custom_alt, + {registerAbility(nil)}, + {kind = "class", name = "ability_juggernaut_blade_fury_custom_alt"} +) +____exports.ability_juggernaut_blade_fury_custom_alt = ability_juggernaut_blade_fury_custom_alt +____exports.modifier_juggernaut_blade_fury_thinker = __TS__Class() +local modifier_juggernaut_blade_fury_thinker = ____exports.modifier_juggernaut_blade_fury_thinker +modifier_juggernaut_blade_fury_thinker.name = "modifier_juggernaut_blade_fury_thinker" +modifier_juggernaut_blade_fury_thinker.____file_path = "scripts/vscripts/abilities/heroes/juggernaut/ability_juggernaut_blade_fury_custom.lua" +__TS__ClassExtends(modifier_juggernaut_blade_fury_thinker, BaseModifier) +function modifier_juggernaut_blade_fury_thinker.prototype.IsHidden(self) + return true +end +function modifier_juggernaut_blade_fury_thinker.prototype.IsPurgable(self) + return false +end +function modifier_juggernaut_blade_fury_thinker.prototype.OnCreated(self, _params) + if not IsServer() then + return + end + local caster = self:GetCaster() + if not caster or not caster:IsAlive() then + self:Destroy() + end +end +function modifier_juggernaut_blade_fury_thinker.prototype.OnIntervalThink(self) + if not IsServer() then + return + end + local caster = self:GetCaster() + if not caster or not caster:IsAlive() then + self:Destroy() + end +end +function modifier_juggernaut_blade_fury_thinker.prototype.OnDestroy(self) + if not IsServer() then + return + end + local parent = self:GetParent() + StopSoundOn("Hero_Juggernaut.BladeFuryStart", parent) + EmitSoundOn("Hero_Juggernaut.BladeFuryStop", parent) + if parent and IsValidEntity(parent) then + UTIL_Remove(parent) + end +end +modifier_juggernaut_blade_fury_thinker = __TS__Decorate( + modifier_juggernaut_blade_fury_thinker, + modifier_juggernaut_blade_fury_thinker, + {registerModifier(nil)}, + {kind = "class", name = "modifier_juggernaut_blade_fury_thinker"} +) +____exports.modifier_juggernaut_blade_fury_thinker = modifier_juggernaut_blade_fury_thinker +return ____exports diff --git a/scripts/vscripts/abilities/heroes/juggernaut/ability_juggernaut_healing_ward_custom.lua b/scripts/vscripts/abilities/heroes/juggernaut/ability_juggernaut_healing_ward_custom.lua new file mode 100644 index 0000000..658ee42 --- /dev/null +++ b/scripts/vscripts/abilities/heroes/juggernaut/ability_juggernaut_healing_ward_custom.lua @@ -0,0 +1,162 @@ +local ____lualib = require("lualib_bundle") +local __TS__Class = ____lualib.__TS__Class +local __TS__ClassExtends = ____lualib.__TS__ClassExtends +local __TS__Decorate = ____lualib.__TS__Decorate +local ____exports = {} +local ____dota_ts_adapter = require("lib.dota_ts_adapter") +local BaseAbility = ____dota_ts_adapter.BaseAbility +local BaseModifier = ____dota_ts_adapter.BaseModifier +local registerAbility = ____dota_ts_adapter.registerAbility +local registerModifier = ____dota_ts_adapter.registerModifier +local ____heal_tracker = require("utils.heal_tracker") +local HealWithBattlePass = ____heal_tracker.HealWithBattlePass +____exports.ability_juggernaut_healing_ward_custom = __TS__Class() +local ability_juggernaut_healing_ward_custom = ____exports.ability_juggernaut_healing_ward_custom +ability_juggernaut_healing_ward_custom.name = "ability_juggernaut_healing_ward_custom" +ability_juggernaut_healing_ward_custom.____file_path = "scripts/vscripts/abilities/heroes/juggernaut/ability_juggernaut_healing_ward_custom.lua" +__TS__ClassExtends(ability_juggernaut_healing_ward_custom, BaseAbility) +function ability_juggernaut_healing_ward_custom.prototype.OnSpellStart(self) + if not IsServer() then + return + end + local caster = self:GetCaster() + local point = self:GetCursorPosition() + local duration = self:GetSpecialValueFor("ward_duration") + local ward = CreateUnitByName( + "npc_dota_juggernaut_healing_ward_custom", + point, + true, + caster, + caster, + caster:GetTeamNumber() + ) + if not ward or not IsValidEntity(ward) then + return + end + Timers:CreateTimer( + 0.2, + function() + ward:MoveToNPC(caster) + return nil + end + ) + ward:SetOwner(caster) + ward:SetControllableByPlayer( + caster:GetPlayerOwnerID(), + false + ) + ward:AddNewModifier(caster, self, "modifier_juggernaut_healing_ward_custom", {duration = duration}) + EmitSoundOn("Hero_Juggernaut.HealingWard.Cast", caster) + EmitSoundOn("Hero_Juggernaut.HealingWard.Loop", ward) +end +ability_juggernaut_healing_ward_custom = __TS__Decorate( + ability_juggernaut_healing_ward_custom, + ability_juggernaut_healing_ward_custom, + {registerAbility(nil)}, + {kind = "class", name = "ability_juggernaut_healing_ward_custom"} +) +____exports.ability_juggernaut_healing_ward_custom = ability_juggernaut_healing_ward_custom +____exports.modifier_juggernaut_healing_ward_custom = __TS__Class() +local modifier_juggernaut_healing_ward_custom = ____exports.modifier_juggernaut_healing_ward_custom +modifier_juggernaut_healing_ward_custom.name = "modifier_juggernaut_healing_ward_custom" +modifier_juggernaut_healing_ward_custom.____file_path = "scripts/vscripts/abilities/heroes/juggernaut/ability_juggernaut_healing_ward_custom.lua" +__TS__ClassExtends(modifier_juggernaut_healing_ward_custom, BaseModifier) +function modifier_juggernaut_healing_ward_custom.prototype.____constructor(self, ...) + BaseModifier.prototype.____constructor(self, ...) + self.healInterval = 1 +end +function modifier_juggernaut_healing_ward_custom.prototype.IsHidden(self) + return false +end +function modifier_juggernaut_healing_ward_custom.prototype.IsPurgable(self) + return false +end +function modifier_juggernaut_healing_ward_custom.prototype.OnCreated(self, params) + if not IsServer() then + return + end + local parent = self:GetParent() + local ability = self:GetAbility() + if not ability then + return + end + self.particleId = ParticleManager:CreateParticle("particles/units/heroes/hero_juggernaut/juggernaut_healing_ward.vpcf", PATTACH_ABSORIGIN_FOLLOW, parent) + self:StartIntervalThink(self.healInterval) +end +function modifier_juggernaut_healing_ward_custom.prototype.OnDestroy(self) + if self.particleId ~= nil then + ParticleManager:DestroyParticle(self.particleId, false) + ParticleManager:ReleaseParticleIndex(self.particleId) + self.particleId = nil + self:GetParent():Kill( + self:GetAbility(), + nil + ) + end + StopSoundOn( + "Hero_Juggernaut.HealingWard.Loop", + self:GetParent() + ) +end +function modifier_juggernaut_healing_ward_custom.prototype.OnIntervalThink(self) + if not IsServer() then + return + end + local parent = self:GetParent() + local caster = self:GetCaster() + local ability = self:GetAbility() + if not ability or not caster or not parent:IsAlive() then + return + end + local radius = ability:GetSpecialValueFor("heal_radius") + local healPerSecond = ability:GetSpecialValueFor("heal_per_second") + local allies = FindUnitsInRadius( + caster:GetTeamNumber(), + parent:GetAbsOrigin(), + nil, + radius, + DOTA_UNIT_TARGET_TEAM_FRIENDLY, + bit.bor(DOTA_UNIT_TARGET_HERO, DOTA_UNIT_TARGET_BASIC), + DOTA_UNIT_TARGET_FLAG_NONE, + FIND_ANY_ORDER, + false + ) + for ____, ally in ipairs(allies) do + if ally and ally:IsAlive() then + local finalheal = healPerSecond * 0.01 * ally:GetMaxHealth() + HealWithBattlePass( + nil, + ally, + finalheal, + ability, + caster + ) + local healParticle = ParticleManager:CreateParticle("particles/generic_gameplay/generic_lifesteal.vpcf", PATTACH_ABSORIGIN_FOLLOW, ally) + ParticleManager:ReleaseParticleIndex(healParticle) + SendOverheadEventMessage( + nil, + OVERHEAD_ALERT_HEAL, + ally, + finalheal, + nil + ) + end + end +end +function modifier_juggernaut_healing_ward_custom.prototype.GetEffectName(self) + return "particles/units/heroes/hero_juggernaut/juggernaut_healing_ward.vpcf" +end +function modifier_juggernaut_healing_ward_custom.prototype.GetEffectAttachType(self) + return PATTACH_ABSORIGIN_FOLLOW +end +function modifier_juggernaut_healing_ward_custom.prototype.CheckState(self) + return {[MODIFIER_STATE_NO_HEALTH_BAR] = true, [MODIFIER_STATE_INVULNERABLE] = true} +end +modifier_juggernaut_healing_ward_custom = __TS__Decorate( + modifier_juggernaut_healing_ward_custom, + modifier_juggernaut_healing_ward_custom, + {registerModifier(nil)}, + {kind = "class", name = "modifier_juggernaut_healing_ward_custom"} +) +____exports.modifier_juggernaut_healing_ward_custom = modifier_juggernaut_healing_ward_custom +return ____exports diff --git a/scripts/vscripts/abilities/heroes/juggernaut/ability_juggernaut_omnislash_custom.lua b/scripts/vscripts/abilities/heroes/juggernaut/ability_juggernaut_omnislash_custom.lua new file mode 100644 index 0000000..d57e988 --- /dev/null +++ b/scripts/vscripts/abilities/heroes/juggernaut/ability_juggernaut_omnislash_custom.lua @@ -0,0 +1,348 @@ +local ____lualib = require("lualib_bundle") +local __TS__Class = ____lualib.__TS__Class +local __TS__ClassExtends = ____lualib.__TS__ClassExtends +local __TS__Decorate = ____lualib.__TS__Decorate +local ____exports = {} +local ____dota_ts_adapter = require("lib.dota_ts_adapter") +local BaseAbility = ____dota_ts_adapter.BaseAbility +local BaseModifier = ____dota_ts_adapter.BaseModifier +local registerAbility = ____dota_ts_adapter.registerAbility +local registerModifier = ____dota_ts_adapter.registerModifier +____exports.ability_juggernaut_omnislash_custom = __TS__Class() +local ability_juggernaut_omnislash_custom = ____exports.ability_juggernaut_omnislash_custom +ability_juggernaut_omnislash_custom.name = "ability_juggernaut_omnislash_custom" +ability_juggernaut_omnislash_custom.____file_path = "scripts/vscripts/abilities/heroes/juggernaut/ability_juggernaut_omnislash_custom.lua" +__TS__ClassExtends(ability_juggernaut_omnislash_custom, BaseAbility) +function ability_juggernaut_omnislash_custom.prototype.OnSpellStart(self) + if not IsServer() then + return + end + local caster = self:GetCaster() + local target = self:GetCursorTarget() + if not target or not target:IsAlive() then + return + end + local duration = self:GetSpecialValueFor("duration") + caster:AddNewModifier( + caster, + self, + "modifier_juggernaut_omnislash_custom", + { + target = target:entindex(), + duration = duration + } + ) + EmitSoundOn("Hero_Juggernaut.Omnislash", caster) +end +ability_juggernaut_omnislash_custom = __TS__Decorate( + ability_juggernaut_omnislash_custom, + ability_juggernaut_omnislash_custom, + {registerAbility(nil)}, + {kind = "class", name = "ability_juggernaut_omnislash_custom"} +) +____exports.ability_juggernaut_omnislash_custom = ability_juggernaut_omnislash_custom +____exports.ability_juggernaut_miniomnislash_custom = __TS__Class() +local ability_juggernaut_miniomnislash_custom = ____exports.ability_juggernaut_miniomnislash_custom +ability_juggernaut_miniomnislash_custom.name = "ability_juggernaut_miniomnislash_custom" +ability_juggernaut_miniomnislash_custom.____file_path = "scripts/vscripts/abilities/heroes/juggernaut/ability_juggernaut_omnislash_custom.lua" +__TS__ClassExtends(ability_juggernaut_miniomnislash_custom, BaseAbility) +function ability_juggernaut_miniomnislash_custom.prototype.OnSpellStart(self) + if not IsServer() then + return + end + local caster = self:GetCaster() + local target = self:GetCursorTarget() + if not target or not target:IsAlive() then + return + end + local duration = self:GetSpecialValueFor("duration") + caster:AddNewModifier( + caster, + self, + "modifier_juggernaut_omnislash_custom", + { + target = target:entindex(), + duration = duration + } + ) + EmitSoundOn("Hero_Juggernaut.Omnislash", caster) +end +ability_juggernaut_miniomnislash_custom = __TS__Decorate( + ability_juggernaut_miniomnislash_custom, + ability_juggernaut_miniomnislash_custom, + {registerAbility(nil)}, + {kind = "class", name = "ability_juggernaut_miniomnislash_custom"} +) +____exports.ability_juggernaut_miniomnislash_custom = ability_juggernaut_miniomnislash_custom +____exports.modifier_juggernaut_omnislash_custom = __TS__Class() +local modifier_juggernaut_omnislash_custom = ____exports.modifier_juggernaut_omnislash_custom +modifier_juggernaut_omnislash_custom.name = "modifier_juggernaut_omnislash_custom" +modifier_juggernaut_omnislash_custom.____file_path = "scripts/vscripts/abilities/heroes/juggernaut/ability_juggernaut_omnislash_custom.lua" +__TS__ClassExtends(modifier_juggernaut_omnislash_custom, BaseModifier) +function modifier_juggernaut_omnislash_custom.prototype.____constructor(self, ...) + BaseModifier.prototype.____constructor(self, ...) + self.attackCount = 0 + self.interval = 0 + self.radius = 0 + self.endInterval = 0.15 +end +function modifier_juggernaut_omnislash_custom.prototype.IsHidden(self) + return false +end +function modifier_juggernaut_omnislash_custom.prototype.IsPurgable(self) + return false +end +function modifier_juggernaut_omnislash_custom.prototype.CheckState(self) + return { + [MODIFIER_STATE_NO_UNIT_COLLISION] = true, + [MODIFIER_STATE_INVULNERABLE] = true, + [MODIFIER_STATE_NO_HEALTH_BAR] = true, + [MODIFIER_STATE_DISARMED] = true, + [MODIFIER_STATE_ROOTED] = true, + [MODIFIER_STATE_SILENCED] = true + } +end +function modifier_juggernaut_omnislash_custom.prototype.DeclareFunctions(self) + return {MODIFIER_PROPERTY_IGNORE_CAST_ANGLE} +end +function modifier_juggernaut_omnislash_custom.prototype.GetModifierIgnoreCastAngle(self) + return 1 +end +function modifier_juggernaut_omnislash_custom.prototype.OnCreated(self, params) + if not IsServer() then + return + end + local caster = self:GetParent() + local ability = self:GetAbility() + if not caster or not ability then + return + end + self.interval = 1 / self:GetCaster():GetAttacksPerSecond(false) / ability:GetSpecialValueFor("slash_interval_mult") + self.radius = ability:GetSpecialValueFor("bounce_radius") + if params.target ~= nil then + self.currentTarget = EntIndexToHScript(params.target) + end + caster:StartGesture(ACT_DOTA_OVERRIDE_ABILITY_4) + self:OnIntervalThink() + self:StartIntervalThink(self.interval) +end +function modifier_juggernaut_omnislash_custom.prototype.OnIntervalThink(self) + if not IsServer() then + return + end + self.attackCount = self.attackCount + 1 + local caster = self:GetParent() + local ability = self:GetAbility() + if not caster or not caster:IsAlive() or not ability then + self:Destroy() + return + end + if self:GetRemainingTime() <= 0 then + self:Destroy() + return + end + local enemy = nil + local final = self.currentTarget == nil + if self.currentTarget and IsValidEntity(self.currentTarget) and self.currentTarget:IsAlive() and not self.currentTarget:IsOutOfGame() and (not self.currentTarget:IsInvisible() or caster:CanEntityBeSeenByMyTeam(self.currentTarget)) then + local distance = (self.currentTarget:GetAbsOrigin() - caster:GetAbsOrigin()):Length2D() + if distance <= self.radius or self.attackCount == 1 then + enemy = self.currentTarget + end + end + if not enemy then + local targets = FindUnitsInRadius( + caster:GetTeamNumber(), + caster:GetAbsOrigin(), + nil, + self.radius, + DOTA_UNIT_TARGET_TEAM_ENEMY, + bit.bor(DOTA_UNIT_TARGET_HERO, DOTA_UNIT_TARGET_BASIC), + bit.bor( + bit.bor(DOTA_UNIT_TARGET_FLAG_MAGIC_IMMUNE_ENEMIES, DOTA_UNIT_TARGET_FLAG_NO_INVIS), + DOTA_UNIT_TARGET_FLAG_INVULNERABLE + ), + FIND_ANY_ORDER, + false + ) + if targets and #targets > 0 then + enemy = targets[1] + end + end + if not enemy then + if final then + self:Destroy() + return + end + self.currentTarget = nil + self:StartIntervalThink(self.endInterval) + return + end + self.currentTarget = enemy + self:PlayEffect(enemy) + self:DealAttack(enemy) + if self:GetRemainingTime() > self.interval then + self:StartIntervalThink(self.interval) + end +end +function modifier_juggernaut_omnislash_custom.prototype.PlayEffect(self, enemy) + if not IsServer() then + return + end + local caster = self:GetParent() + caster:RemoveGesture(ACT_DOTA_OVERRIDE_ABILITY_4) + caster:StartGesture(ACT_DOTA_OVERRIDE_ABILITY_4) + local position1 = caster:GetAbsOrigin() + local turn = (enemy:GetAbsOrigin() - position1):Normalized() + turn.z = 0 + local linePos = enemy:GetAbsOrigin() + turn * 90 + local angle = -45 + 90 * RandomInt(0, 1) + local finalPos = rotatePositionYaw( + nil, + enemy:GetAbsOrigin(), + angle, + linePos + ) + caster:SetAbsOrigin(finalPos) + local position2 = caster:GetAbsOrigin() + local direction = (enemy:GetAbsOrigin() - caster:GetAbsOrigin()):Normalized() + direction.z = 0 + caster:SetForwardVector(direction) + caster:FaceTowards(enemy:GetAbsOrigin()) + local trailParticle = ParticleManager:CreateParticle("particles/units/heroes/hero_juggernaut/juggernaut_omni_slash_trail.vpcf", PATTACH_ABSORIGIN, caster) + ParticleManager:SetParticleControl(trailParticle, 0, position1) + ParticleManager:SetParticleControl(trailParticle, 1, position2) + ParticleManager:ReleaseParticleIndex(trailParticle) + if self.attackCount == 1 then + local dashParticle = ParticleManager:CreateParticle("particles/units/heroes/hero_juggernaut/juggernaut_omni_dash.vpcf", PATTACH_CUSTOMORIGIN, caster) + local vDirection = (position2 - position1):Normalized() + ParticleManager:SetParticleControl(dashParticle, 0, position1) + ParticleManager:SetParticleControlForward(dashParticle, 0, vDirection * -1) + ParticleManager:SetParticleControlEnt( + dashParticle, + 1, + enemy, + PATTACH_ABSORIGIN_FOLLOW, + "attach_hitloc", + enemy:GetAbsOrigin(), + true + ) + ParticleManager:SetParticleControlEnt( + dashParticle, + 2, + enemy, + PATTACH_ABSORIGIN_FOLLOW, + "attach_hitloc", + enemy:GetAbsOrigin(), + true + ) + ParticleManager:ReleaseParticleIndex(dashParticle) + end +end +function modifier_juggernaut_omnislash_custom.prototype.DealAttack(self, target) + if not IsServer() then + return + end + local caster = self:GetParent() + local ability = self:GetAbility() + if not caster or not ability then + return + end + local attackModifier = caster:AddNewModifier(caster, ability, "modifier_juggernaut_omnislash_attack", {}) + caster:PerformAttack( + target, + true, + true, + true, + false, + false, + false, + false + ) + if attackModifier ~= nil then + attackModifier:Destroy() + end + local tgtParticle = ParticleManager:CreateParticle("particles/units/heroes/hero_juggernaut/juggernaut_omni_slash_tgt.vpcf", PATTACH_ABSORIGIN_FOLLOW, target) + ParticleManager:SetParticleControlEnt( + tgtParticle, + 0, + target, + PATTACH_ABSORIGIN_FOLLOW, + "attach_hitloc", + target:GetAbsOrigin(), + true + ) + ParticleManager:SetParticleControlEnt( + tgtParticle, + 1, + target, + PATTACH_ABSORIGIN_FOLLOW, + "attach_hitloc", + target:GetAbsOrigin(), + true + ) + ParticleManager:ReleaseParticleIndex(tgtParticle) + EmitSoundOn("Hero_Juggernaut.OmniSlash", target) +end +function modifier_juggernaut_omnislash_custom.prototype.OnDestroy(self) + if not IsServer() then + return + end + local caster = self:GetParent() + if caster and caster:IsAlive() then + caster:FadeGesture(ACT_DOTA_OVERRIDE_ABILITY_4) + caster:Stop() + end + EmitSoundOn("Hero_Juggernaut.Omnislash.End", caster) +end +function modifier_juggernaut_omnislash_custom.prototype.GetStatusEffectName(self) + return "particles/status_fx/status_effect_omnislash.vpcf" +end +function modifier_juggernaut_omnislash_custom.prototype.StatusEffectPriority(self) + return 10 +end +modifier_juggernaut_omnislash_custom = __TS__Decorate( + modifier_juggernaut_omnislash_custom, + modifier_juggernaut_omnislash_custom, + {registerModifier(nil)}, + {kind = "class", name = "modifier_juggernaut_omnislash_custom"} +) +____exports.modifier_juggernaut_omnislash_custom = modifier_juggernaut_omnislash_custom +____exports.modifier_juggernaut_omnislash_attack = __TS__Class() +local modifier_juggernaut_omnislash_attack = ____exports.modifier_juggernaut_omnislash_attack +modifier_juggernaut_omnislash_attack.name = "modifier_juggernaut_omnislash_attack" +modifier_juggernaut_omnislash_attack.____file_path = "scripts/vscripts/abilities/heroes/juggernaut/ability_juggernaut_omnislash_custom.lua" +__TS__ClassExtends(modifier_juggernaut_omnislash_attack, BaseModifier) +function modifier_juggernaut_omnislash_attack.prototype.____constructor(self, ...) + BaseModifier.prototype.____constructor(self, ...) + self.damagePercent = 0 +end +function modifier_juggernaut_omnislash_attack.prototype.IsHidden(self) + return true +end +function modifier_juggernaut_omnislash_attack.prototype.OnCreated(self) + if not IsServer() then + return + end + local ability = self:GetAbility() + if not ability then + return + end + self.damagePercent = ability:GetSpecialValueFor("damage") - 100 +end +function modifier_juggernaut_omnislash_attack.prototype.DeclareFunctions(self) + return {MODIFIER_PROPERTY_DAMAGEOUTGOING_PERCENTAGE} +end +function modifier_juggernaut_omnislash_attack.prototype.GetModifierDamageOutgoing_Percentage(self, event) + if event.inflictor then + return 0 + end + return self.damagePercent +end +modifier_juggernaut_omnislash_attack = __TS__Decorate( + modifier_juggernaut_omnislash_attack, + modifier_juggernaut_omnislash_attack, + {registerModifier(nil)}, + {kind = "class", name = "modifier_juggernaut_omnislash_attack"} +) +____exports.modifier_juggernaut_omnislash_attack = modifier_juggernaut_omnislash_attack +return ____exports diff --git a/scripts/vscripts/abilities/heroes/juggernaut/ability_juggernaut_samurai_soul.lua b/scripts/vscripts/abilities/heroes/juggernaut/ability_juggernaut_samurai_soul.lua new file mode 100644 index 0000000..47a01e9 --- /dev/null +++ b/scripts/vscripts/abilities/heroes/juggernaut/ability_juggernaut_samurai_soul.lua @@ -0,0 +1,193 @@ +local ____lualib = require("lualib_bundle") +local __TS__Class = ____lualib.__TS__Class +local __TS__ClassExtends = ____lualib.__TS__ClassExtends +local __TS__Decorate = ____lualib.__TS__Decorate +local ____exports = {} +local ____dota_ts_adapter = require("lib.dota_ts_adapter") +local BaseAbility = ____dota_ts_adapter.BaseAbility +local BaseModifier = ____dota_ts_adapter.BaseModifier +local registerAbility = ____dota_ts_adapter.registerAbility +local registerModifier = ____dota_ts_adapter.registerModifier +____exports.ability_juggernaut_samurai_soul = __TS__Class() +local ability_juggernaut_samurai_soul = ____exports.ability_juggernaut_samurai_soul +ability_juggernaut_samurai_soul.name = "ability_juggernaut_samurai_soul" +ability_juggernaut_samurai_soul.____file_path = "scripts/vscripts/abilities/heroes/juggernaut/ability_juggernaut_samurai_soul.lua" +__TS__ClassExtends(ability_juggernaut_samurai_soul, BaseAbility) +function ability_juggernaut_samurai_soul.prototype.GetIntrinsicModifierName(self) + return "modifier_juggernaut_samurai_soul" +end +ability_juggernaut_samurai_soul = __TS__Decorate( + ability_juggernaut_samurai_soul, + ability_juggernaut_samurai_soul, + {registerAbility(nil)}, + {kind = "class", name = "ability_juggernaut_samurai_soul"} +) +____exports.ability_juggernaut_samurai_soul = ability_juggernaut_samurai_soul +____exports.modifier_juggernaut_samurai_soul = __TS__Class() +local modifier_juggernaut_samurai_soul = ____exports.modifier_juggernaut_samurai_soul +modifier_juggernaut_samurai_soul.name = "modifier_juggernaut_samurai_soul" +modifier_juggernaut_samurai_soul.____file_path = "scripts/vscripts/abilities/heroes/juggernaut/ability_juggernaut_samurai_soul.lua" +__TS__ClassExtends(modifier_juggernaut_samurai_soul, BaseModifier) +function modifier_juggernaut_samurai_soul.prototype.____constructor(self, ...) + BaseModifier.prototype.____constructor(self, ...) + self.lastAttackTime = 0 + self.RESET_TIMEOUT = self:GetAbility():GetSpecialValueFor("cooldown") + self.maxRestoreCount = 2 +end +function modifier_juggernaut_samurai_soul.prototype.IsHidden(self) + return false +end +function modifier_juggernaut_samurai_soul.prototype.IsPurgable(self) + return false +end +function modifier_juggernaut_samurai_soul.prototype.IsDebuff(self) + if self:GetStackCount() > 0 then + return true + end + return false +end +function modifier_juggernaut_samurai_soul.prototype.RemoveOnDeath(self) + return false +end +function modifier_juggernaut_samurai_soul.prototype.OnCreated(self) + if not IsServer() then + return + end + local ability = self:GetAbility() + if ability then + local configuredLimit = ability:GetSpecialValueFor("max_restore_count") + if configuredLimit > 0 then + self.maxRestoreCount = configuredLimit + end + end + self:StartIntervalThink(0.1) +end +function modifier_juggernaut_samurai_soul.prototype.OnIntervalThink(self) + if not IsServer() then + return + end + local currentTime = GameRules:GetGameTime() + local timeSinceLastAttack = currentTime - self.lastAttackTime + if timeSinceLastAttack >= self.RESET_TIMEOUT and self:GetStackCount() > 0 then + local parent = self:GetParent() + if parent and parent:IsRealHero() then + local hero = parent + self:SetStackCount(0) + hero:CalculateStatBonus(true) + end + end +end +function modifier_juggernaut_samurai_soul.prototype.DeclareFunctions(self) + return { + MODIFIER_PROPERTY_MIN_HEALTH, + MODIFIER_PROPERTY_STATS_AGILITY_BONUS, + MODIFIER_PROPERTY_STATS_INTELLECT_BONUS, + MODIFIER_PROPERTY_STATS_STRENGTH_BONUS, + MODIFIER_EVENT_ON_DEATH + } +end +function modifier_juggernaut_samurai_soul.prototype.OnDeath(self, event) + if event.unit == self:GetParent() then + self:GetCaster():RemoveModifierByName("modifier_juggernaut_samurai_soul") + end +end +function modifier_juggernaut_samurai_soul.prototype.GetModifierBonusStats_Strength(self) + if not IsServer() then + return 0 + end + local parent = self:GetParent() + if not parent then + return 0 + end + if parent:PassivesDisabled() then + return 0 + end + if not parent:IsRealHero() then + return 0 + end + local hero = parent + local baseStr = hero:GetBaseStrength() + return -baseStr * (self:GetStackCount() * (0.01 * self:GetAbility():GetSpecialValueFor("debuff_pct"))) +end +function modifier_juggernaut_samurai_soul.prototype.GetModifierBonusStats_Agility(self) + if not IsServer() then + return 0 + end + local parent = self:GetParent() + if not parent then + return 0 + end + if parent:PassivesDisabled() then + return 0 + end + if not parent:IsRealHero() then + return 0 + end + local hero = parent + local baseAgi = hero:GetBaseAgility() + return -baseAgi * (self:GetStackCount() * (0.01 * self:GetAbility():GetSpecialValueFor("debuff_pct"))) +end +function modifier_juggernaut_samurai_soul.prototype.GetModifierBonusStats_Intellect(self) + if not IsServer() then + return 0 + end + local parent = self:GetParent() + if not parent then + return 0 + end + if parent:PassivesDisabled() then + return 0 + end + if not parent:IsRealHero() then + return 0 + end + local hero = parent + local baseInt = hero:GetBaseIntellect() + return -baseInt * (self:GetStackCount() * (0.01 * self:GetAbility():GetSpecialValueFor("debuff_pct"))) +end +function modifier_juggernaut_samurai_soul.prototype.GetMinHealth(self) + if self:GetParent():PassivesDisabled() then + return 0 + end + self:SetDuration(11, true) + self.lastAttackTime = GameRules:GetGameTime() + if self:GetStackCount() >= self.maxRestoreCount then + return 0 + end + if self:GetCaster():GetHealth() < 10 then + self:GetCaster():SetHealth(self:GetCaster():GetMaxHealth()) + self:IncrementStackCount() + self:GetCaster():CalculateStatBonus(true) + end + return 1 +end +function modifier_juggernaut_samurai_soul.prototype.OnDestroy(self) + if not IsServer() then + return + end + local caster = self:GetCaster() + local ability = self:GetAbility() + if caster and not caster:IsNull() and caster:IsAlive() and ability and not ability:IsNull() then + caster:AddNewModifier( + self:GetParent(), + ability, + "modifier_juggernaut_samurai_soul", + {} + ) + end +end +function modifier_juggernaut_samurai_soul.prototype.GetTexture(self) + if self:GetStackCount() > 0 then + return "new_heroes/juggernaut_death" + else + return "new_heroes/juggernaut_miracle" + end +end +modifier_juggernaut_samurai_soul = __TS__Decorate( + modifier_juggernaut_samurai_soul, + modifier_juggernaut_samurai_soul, + {registerModifier(nil)}, + {kind = "class", name = "modifier_juggernaut_samurai_soul"} +) +____exports.modifier_juggernaut_samurai_soul = modifier_juggernaut_samurai_soul +return ____exports diff --git a/scripts/vscripts/abilities/heroes/keeper_of_the_light/keeper_of_the_light_blinding_light_custom.lua b/scripts/vscripts/abilities/heroes/keeper_of_the_light/keeper_of_the_light_blinding_light_custom.lua new file mode 100644 index 0000000..e7448ef --- /dev/null +++ b/scripts/vscripts/abilities/heroes/keeper_of_the_light/keeper_of_the_light_blinding_light_custom.lua @@ -0,0 +1,134 @@ +local ____lualib = require("lualib_bundle") +local __TS__Class = ____lualib.__TS__Class +local __TS__ClassExtends = ____lualib.__TS__ClassExtends +local __TS__Decorate = ____lualib.__TS__Decorate +local ____exports = {} +local ____dota_ts_adapter = require("lib.dota_ts_adapter") +local BaseAbility = ____dota_ts_adapter.BaseAbility +local BaseModifier = ____dota_ts_adapter.BaseModifier +local registerAbility = ____dota_ts_adapter.registerAbility +local registerModifier = ____dota_ts_adapter.registerModifier +____exports.keeper_of_the_light_blinding_light_custom = __TS__Class() +local keeper_of_the_light_blinding_light_custom = ____exports.keeper_of_the_light_blinding_light_custom +keeper_of_the_light_blinding_light_custom.name = "keeper_of_the_light_blinding_light_custom" +keeper_of_the_light_blinding_light_custom.____file_path = "scripts/vscripts/abilities/heroes/keeper_of_the_light/keeper_of_the_light_blinding_light_custom.lua" +__TS__ClassExtends(keeper_of_the_light_blinding_light_custom, BaseAbility) +function keeper_of_the_light_blinding_light_custom.prototype.GetAOERadius(self) + return self:GetSpecialValueFor("radius") +end +function keeper_of_the_light_blinding_light_custom.prototype.Precache(self, context) + PrecacheResource("particle", "particles/econ/items/keeper_of_the_light/kotl_ti10_immortal/kotl_ti10_blinding_light.vpcf", context) +end +function keeper_of_the_light_blinding_light_custom.prototype.OnSpellStart(self) + if not IsServer() then + return + end + local caster = self:GetCaster() + local point = self:GetCursorPosition() + local particle = ParticleManager:CreateParticle("particles/econ/items/keeper_of_the_light/kotl_ti10_immortal/kotl_ti10_blinding_light.vpcf", PATTACH_CUSTOMORIGIN, caster) + ParticleManager:SetParticleControl(particle, 1, point) + ParticleManager:ReleaseParticleIndex(particle) + local enemies = FindUnitsInRadius( + caster:GetTeamNumber(), + point, + nil, + self:GetSpecialValueFor("radius"), + DOTA_UNIT_TARGET_TEAM_ENEMY, + bit.bor(DOTA_UNIT_TARGET_HERO, DOTA_UNIT_TARGET_BASIC), + DOTA_UNIT_TARGET_FLAG_NONE, + FIND_ANY_ORDER, + false + ) + EmitSoundOnLocationWithCaster(point, "Hero_KeeperOfTheLight.BlindingLight", caster) + for ____, enemy in ipairs(enemies) do + do + ApplyDamage({ + victim = enemy, + attacker = caster, + damage = self:GetSpecialValueFor("damage"), + damage_type = self:GetAbilityDamageType(), + ability = self + }) + if not enemy:IsAlive() then + goto __continue6 + end + if not enemy:IsBossCreature() then + enemy:AddNewModifier( + caster, + self, + "modifier_knockback", + { + center_x = point.x, + center_y = point.y, + center_z = point.z, + duration = self:GetSpecialValueFor("knockback_duration"), + knockback_duration = self:GetSpecialValueFor("knockback_duration"), + knockback_distance = self:GetSpecialValueFor("knockback_distance"), + knockback_height = 0 + } + ) + end + local debuff = enemy:AddNewModifier( + caster, + self, + ____exports.modifier_keeper_of_the_light_blinding_light_custom.name, + {duration = self:GetSpecialValueFor("duration_stack")} + ) + if not debuff then + goto __continue6 + end + local stacks = enemy:IsBossCreature() and self:GetSpecialValueFor("miss_stack_boss") or self:GetSpecialValueFor("miss_stack") + debuff:SetStackCount(debuff:GetStackCount() + stacks) + end + ::__continue6:: + end +end +keeper_of_the_light_blinding_light_custom = __TS__Decorate( + keeper_of_the_light_blinding_light_custom, + keeper_of_the_light_blinding_light_custom, + {registerAbility(nil)}, + {kind = "class", name = "keeper_of_the_light_blinding_light_custom"} +) +____exports.keeper_of_the_light_blinding_light_custom = keeper_of_the_light_blinding_light_custom +____exports.modifier_keeper_of_the_light_blinding_light_custom = __TS__Class() +local modifier_keeper_of_the_light_blinding_light_custom = ____exports.modifier_keeper_of_the_light_blinding_light_custom +modifier_keeper_of_the_light_blinding_light_custom.name = "modifier_keeper_of_the_light_blinding_light_custom" +modifier_keeper_of_the_light_blinding_light_custom.____file_path = "scripts/vscripts/abilities/heroes/keeper_of_the_light/keeper_of_the_light_blinding_light_custom.lua" +__TS__ClassExtends(modifier_keeper_of_the_light_blinding_light_custom, BaseModifier) +function modifier_keeper_of_the_light_blinding_light_custom.prototype.DeclareFunctions(self) + return {MODIFIER_PROPERTY_MISS_PERCENTAGE, MODIFIER_EVENT_ON_ATTACK_FAIL} +end +function modifier_keeper_of_the_light_blinding_light_custom.prototype.GetModifierMiss_Percentage(self) + return self:GetStackCount() > 0 and self:GetAbility():GetSpecialValueFor("miss_pct") or 0 +end +function modifier_keeper_of_the_light_blinding_light_custom.prototype.OnAttackFail(self, event) + if not IsServer() then + return + end + if event.attacker ~= self:GetParent() then + return + end + if self:GetStackCount() > 0 then + self:DecrementStackCount() + end + if self:GetStackCount() <= 0 then + self:Destroy() + end +end +function modifier_keeper_of_the_light_blinding_light_custom.prototype.IsPurgable(self) + return true +end +function modifier_keeper_of_the_light_blinding_light_custom.prototype.IsPurgeException(self) + return true +end +function modifier_keeper_of_the_light_blinding_light_custom.prototype.GetEffectName(self) + return "particles/units/heroes/hero_keeper_of_the_light/keeper_of_the_light_blinding_light_debuff.vpcf" +end +modifier_keeper_of_the_light_blinding_light_custom = __TS__Decorate( + modifier_keeper_of_the_light_blinding_light_custom, + modifier_keeper_of_the_light_blinding_light_custom, + {registerModifier(nil)}, + {kind = "class", name = "modifier_keeper_of_the_light_blinding_light_custom"} +) +____exports.modifier_keeper_of_the_light_blinding_light_custom = modifier_keeper_of_the_light_blinding_light_custom +return ____exports diff --git a/scripts/vscripts/abilities/heroes/keeper_of_the_light/keeper_of_the_light_chakra_magic_custom.lua b/scripts/vscripts/abilities/heroes/keeper_of_the_light/keeper_of_the_light_chakra_magic_custom.lua new file mode 100644 index 0000000..3108434 --- /dev/null +++ b/scripts/vscripts/abilities/heroes/keeper_of_the_light/keeper_of_the_light_chakra_magic_custom.lua @@ -0,0 +1,171 @@ +local ____lualib = require("lualib_bundle") +local __TS__Class = ____lualib.__TS__Class +local __TS__ClassExtends = ____lualib.__TS__ClassExtends +local __TS__Decorate = ____lualib.__TS__Decorate +local ____exports = {} +local ____dota_ts_adapter = require("lib.dota_ts_adapter") +local BaseAbility = ____dota_ts_adapter.BaseAbility +local BaseModifier = ____dota_ts_adapter.BaseModifier +local registerAbility = ____dota_ts_adapter.registerAbility +local registerModifier = ____dota_ts_adapter.registerModifier +local ____incoming_damage_reduction_combine = require("utils.incoming_damage_reduction_combine") +local removeIncomingDamageReductionSource = ____incoming_damage_reduction_combine.removeIncomingDamageReductionSource +local setIncomingDamageReductionSource = ____incoming_damage_reduction_combine.setIncomingDamageReductionSource +local CHAKRA_MAGIC_INCOMING_SOURCE = "modifier_keeper_of_the_light_chakra_magic_buff" +____exports.keeper_of_the_light_chakra_magic_custom = __TS__Class() +local keeper_of_the_light_chakra_magic_custom = ____exports.keeper_of_the_light_chakra_magic_custom +keeper_of_the_light_chakra_magic_custom.name = "keeper_of_the_light_chakra_magic_custom" +keeper_of_the_light_chakra_magic_custom.____file_path = "scripts/vscripts/abilities/heroes/keeper_of_the_light/keeper_of_the_light_chakra_magic_custom.lua" +__TS__ClassExtends(keeper_of_the_light_chakra_magic_custom, BaseAbility) +function keeper_of_the_light_chakra_magic_custom.prototype.OnSpellStart(self) + if not IsServer() then + return + end + local target = self:GetCursorTarget() + if not target then + return + end + self:OnCustomSpellStart(target) +end +function keeper_of_the_light_chakra_magic_custom.prototype.OnCustomSpellStart(self, target) + if not IsServer() then + return + end + local caster = self:GetCaster() + target:GiveMana(self:GetSpecialValueFor("mana_restore")) + do + local i = 0 + while i < target:GetAbilityCount() - 1 do + do + local ability = target:GetAbilityByIndex(i) + if not ability or ability:IsItem() or ability:GetName() == self:GetName() then + goto __continue7 + end + local cooldownReduction = self:GetSpecialValueFor("cooldown_reduction") + local cooldownRemaining = ability:GetCooldownTimeRemaining() + if cooldownRemaining > 0 then + ability:EndCooldown() + ability:StartCooldown(math.max(cooldownRemaining - cooldownReduction, 0)) + end + local maxCharges = ability:GetMaxAbilityCharges(ability:GetLevel()) + if self:GetSpecialValueFor("refresh_charges") > 0 and maxCharges > 0 then + ability:SetCurrentAbilityCharges(math.min( + ability:GetCurrentAbilityCharges() + 1, + maxCharges + )) + end + end + ::__continue7:: + i = i + 1 + end + end + if self:GetSpecialValueFor("strong_dispel") > 0 then + target:Purge( + false, + true, + false, + true, + true + ) + end + local buffDuration = self:GetSpecialValueFor("damage_reduction_duration") + if buffDuration > 0 then + ____exports.modifier_keeper_of_the_light_chakra_magic_buff:apply(target, caster, self, {duration = buffDuration}) + end + local chakraCast = ParticleManager:CreateParticle("particles/units/heroes/hero_keeper_of_the_light/keeper_chakra_magic.vpcf", PATTACH_POINT_FOLLOW, caster) + ParticleManager:SetParticleControlEnt( + chakraCast, + 0, + caster, + PATTACH_POINT_FOLLOW, + "attach_attack1", + caster:GetAbsOrigin(), + true + ) + ParticleManager:SetParticleControlEnt( + chakraCast, + 1, + target, + PATTACH_POINT_FOLLOW, + "attach_hitloc", + target:GetAbsOrigin(), + true + ) + ParticleManager:ReleaseParticleIndex(chakraCast) + EmitSoundOn("Hero_KeeperOfTheLight.ChakraMagic.Target", target) +end +keeper_of_the_light_chakra_magic_custom = __TS__Decorate( + keeper_of_the_light_chakra_magic_custom, + keeper_of_the_light_chakra_magic_custom, + {registerAbility(nil)}, + {kind = "class", name = "keeper_of_the_light_chakra_magic_custom"} +) +____exports.keeper_of_the_light_chakra_magic_custom = keeper_of_the_light_chakra_magic_custom +____exports.modifier_keeper_of_the_light_chakra_magic_buff = __TS__Class() +local modifier_keeper_of_the_light_chakra_magic_buff = ____exports.modifier_keeper_of_the_light_chakra_magic_buff +modifier_keeper_of_the_light_chakra_magic_buff.name = "modifier_keeper_of_the_light_chakra_magic_buff" +modifier_keeper_of_the_light_chakra_magic_buff.____file_path = "scripts/vscripts/abilities/heroes/keeper_of_the_light/keeper_of_the_light_chakra_magic_custom.lua" +__TS__ClassExtends(modifier_keeper_of_the_light_chakra_magic_buff, BaseModifier) +function modifier_keeper_of_the_light_chakra_magic_buff.prototype.____constructor(self, ...) + BaseModifier.prototype.____constructor(self, ...) + self.damageReductionPct = 0 +end +function modifier_keeper_of_the_light_chakra_magic_buff.prototype.IsHidden(self) + return false +end +function modifier_keeper_of_the_light_chakra_magic_buff.prototype.IsDebuff(self) + return false +end +function modifier_keeper_of_the_light_chakra_magic_buff.prototype.IsPurgable(self) + return true +end +function modifier_keeper_of_the_light_chakra_magic_buff.prototype.IsPurgeException(self) + return false +end +function modifier_keeper_of_the_light_chakra_magic_buff.prototype.GetAttributes(self) + return MODIFIER_ATTRIBUTE_MULTIPLE +end +function modifier_keeper_of_the_light_chakra_magic_buff.prototype.OnCreated(self) + local ability = self:GetAbility() + if not ability then + return + end + self.damageReductionPct = ability:GetSpecialValueFor("incoming_damage_reduction_pct") + self:SetStackCount(self.damageReductionPct) + if not IsServer() then + return + end + setIncomingDamageReductionSource( + nil, + self:GetParent(), + CHAKRA_MAGIC_INCOMING_SOURCE, + function() return math.max(0, self.damageReductionPct) end + ) +end +function modifier_keeper_of_the_light_chakra_magic_buff.prototype.OnRefresh(self) + self:OnCreated() +end +function modifier_keeper_of_the_light_chakra_magic_buff.prototype.OnDestroy(self) + if not IsServer() then + return + end + removeIncomingDamageReductionSource( + nil, + self:GetParent(), + CHAKRA_MAGIC_INCOMING_SOURCE + ) +end +function modifier_keeper_of_the_light_chakra_magic_buff.prototype.GetEffectName(self) + return "particles/units/heroes/hero_keeper_of_the_light/keeper_dazzling.vpcf" +end +function modifier_keeper_of_the_light_chakra_magic_buff.prototype.GetEffectAttachType(self) + return PATTACH_ABSORIGIN_FOLLOW +end +modifier_keeper_of_the_light_chakra_magic_buff = __TS__Decorate( + modifier_keeper_of_the_light_chakra_magic_buff, + modifier_keeper_of_the_light_chakra_magic_buff, + {registerModifier(nil)}, + {kind = "class", name = "modifier_keeper_of_the_light_chakra_magic_buff"} +) +____exports.modifier_keeper_of_the_light_chakra_magic_buff = modifier_keeper_of_the_light_chakra_magic_buff +return ____exports diff --git a/scripts/vscripts/abilities/heroes/keeper_of_the_light/keeper_of_the_light_illuminate_custom.lua b/scripts/vscripts/abilities/heroes/keeper_of_the_light/keeper_of_the_light_illuminate_custom.lua new file mode 100644 index 0000000..5fc8fe8 --- /dev/null +++ b/scripts/vscripts/abilities/heroes/keeper_of_the_light/keeper_of_the_light_illuminate_custom.lua @@ -0,0 +1,116 @@ +local ____lualib = require("lualib_bundle") +local __TS__Class = ____lualib.__TS__Class +local __TS__ClassExtends = ____lualib.__TS__ClassExtends +local __TS__Decorate = ____lualib.__TS__Decorate +local ____exports = {} +local ____dota_ts_adapter = require("lib.dota_ts_adapter") +local BaseAbility = ____dota_ts_adapter.BaseAbility +local registerAbility = ____dota_ts_adapter.registerAbility +local ____heal_tracker = require("utils.heal_tracker") +local HealWithBattlePass = ____heal_tracker.HealWithBattlePass +____exports.keeper_of_the_light_illuminate_custom = __TS__Class() +local keeper_of_the_light_illuminate_custom = ____exports.keeper_of_the_light_illuminate_custom +keeper_of_the_light_illuminate_custom.name = "keeper_of_the_light_illuminate_custom" +keeper_of_the_light_illuminate_custom.____file_path = "scripts/vscripts/abilities/heroes/keeper_of_the_light/keeper_of_the_light_illuminate_custom.lua" +__TS__ClassExtends(keeper_of_the_light_illuminate_custom, BaseAbility) +function keeper_of_the_light_illuminate_custom.prototype.OnSpellStart(self) + local caster = self:GetCaster() + caster:EmitSound("Hero_KeeperOfTheLight.Illuminate.Charge") + caster:SwapAbilities( + self:GetAbilityName(), + "keeper_of_the_light_illuminate_end", + false, + true + ) +end +function keeper_of_the_light_illuminate_custom.prototype.OnChannelFinish(self, _interrupted) + local caster = self:GetCaster() + local casterPoint = caster:GetOrigin() + local distance = self:GetCastRange(casterPoint, nil) + local speed = self:GetSpecialValueFor("speed") + local radius = self:GetSpecialValueFor("radius") + local multiple = (GameRules:GetGameTime() - self:GetChannelStartTime()) / self:GetChannelTime() + caster:EmitSound("Hero_KeeperOfTheLight.Illuminate.Discharge") + local direction = self:GetCursorPosition() - casterPoint + direction.z = 0 + direction = direction:Normalized() * distance + local particle = ParticleManager:CreateParticle("particles/units/heroes/hero_keeper_of_the_light/kotl_illuminate.vpcf", PATTACH_ABSORIGIN_FOLLOW, caster) + ParticleManager:SetParticleControl( + particle, + 1, + direction:Normalized() * speed + ) + ParticleManager:SetParticleControl(particle, 3, casterPoint) + ProjectileManager:CreateLinearProjectile({ + Ability = self, + vSpawnOrigin = casterPoint, + vVelocity = direction:Normalized() * speed, + fDistance = distance, + fStartRadius = radius, + fEndRadius = radius, + Source = caster, + iUnitTargetTeam = DOTA_UNIT_TARGET_TEAM_BOTH, + iUnitTargetFlags = DOTA_UNIT_TARGET_FLAG_NONE, + iUnitTargetType = bit.bor(DOTA_UNIT_TARGET_HERO, DOTA_UNIT_TARGET_BASIC), + bProvidesVision = true, + iVisionRadius = self:GetSpecialValueFor("vision_radius"), + iVisionTeamNumber = caster:GetTeamNumber(), + ExtraData = {particleId = particle, multiple = multiple} + }) + caster:SwapAbilities( + self:GetAbilityName(), + "keeper_of_the_light_illuminate_end", + true, + false + ) + caster:EmitSound("keeper_of_the_light_keep_illuminate_05") +end +function keeper_of_the_light_illuminate_custom.prototype.OnProjectileHit_ExtraData(self, target, _location, extraData) + if not target then + ParticleManager:DestroyParticle(extraData.particleId, false) + return + end + local caster = self:GetCaster() + local minDamage = self:GetSpecialValueFor("min_damage") + local scaledDamage = (self:GetSpecialValueFor("max_damage") - minDamage) * extraData.multiple + local totalValue = scaledDamage + minDamage + target:EmitSound("Hero_KeeperOfTheLight.Illuminate.Target") + local impactFx = ParticleManager:CreateParticle("particles/units/heroes/hero_keeper_of_the_light/keeper_of_the_light_illuminate_impact.vpcf", PATTACH_CUSTOMORIGIN, target) + ParticleManager:SetParticleControlEnt( + impactFx, + 0, + target, + PATTACH_ABSORIGIN_FOLLOW, + "attach_hitloc", + target:GetOrigin(), + true + ) + ParticleManager:ReleaseParticleIndex(impactFx) + if target:GetTeamNumber() == caster:GetTeamNumber() then + local healPct = self:GetSpecialValueFor("heal_percent") + local heal = totalValue * (caster:GetSpellAmplification(false) + 1) * healPct / 100 + HealWithBattlePass( + nil, + target, + heal, + self, + caster + ) + return + end + ApplyDamage({ + victim = target, + attacker = caster, + damage = totalValue + self:GetCaster():GetMaxMana() * 0.5, + damage_type = self:GetAbilityDamageType(), + ability = self + }) +end +keeper_of_the_light_illuminate_custom = __TS__Decorate( + keeper_of_the_light_illuminate_custom, + keeper_of_the_light_illuminate_custom, + {registerAbility(nil)}, + {kind = "class", name = "keeper_of_the_light_illuminate_custom"} +) +____exports.keeper_of_the_light_illuminate_custom = keeper_of_the_light_illuminate_custom +return ____exports diff --git a/scripts/vscripts/abilities/heroes/keeper_of_the_light/keeper_of_the_light_radiant_bind_custom.lua b/scripts/vscripts/abilities/heroes/keeper_of_the_light/keeper_of_the_light_radiant_bind_custom.lua new file mode 100644 index 0000000..e262100 --- /dev/null +++ b/scripts/vscripts/abilities/heroes/keeper_of_the_light/keeper_of_the_light_radiant_bind_custom.lua @@ -0,0 +1,170 @@ +local ____lualib = require("lualib_bundle") +local __TS__Class = ____lualib.__TS__Class +local __TS__ClassExtends = ____lualib.__TS__ClassExtends +local __TS__Decorate = ____lualib.__TS__Decorate +local ____exports = {} +local ____dota_ts_adapter = require("lib.dota_ts_adapter") +local BaseAbility = ____dota_ts_adapter.BaseAbility +local BaseModifier = ____dota_ts_adapter.BaseModifier +local registerAbility = ____dota_ts_adapter.registerAbility +local registerModifier = ____dota_ts_adapter.registerModifier +____exports.keeper_of_the_light_radiant_bind_custom = __TS__Class() +local keeper_of_the_light_radiant_bind_custom = ____exports.keeper_of_the_light_radiant_bind_custom +keeper_of_the_light_radiant_bind_custom.name = "keeper_of_the_light_radiant_bind_custom" +keeper_of_the_light_radiant_bind_custom.____file_path = "scripts/vscripts/abilities/heroes/keeper_of_the_light/keeper_of_the_light_radiant_bind_custom.lua" +__TS__ClassExtends(keeper_of_the_light_radiant_bind_custom, BaseAbility) +function keeper_of_the_light_radiant_bind_custom.prototype.OnSpellStart(self) + if not IsServer() then + return + end + local target = self:GetCursorTarget() + local caster = self:GetCaster() + if not target then + return + end + EmitSoundOn("Hero_KeeperOfTheLight.ManaLeak.Cast", caster) + local particle = ParticleManager:CreateParticle("particles/units/heroes/hero_keeper_of_the_light/keeper_radiant_bind_cast.vpcf", PATTACH_CUSTOMORIGIN, caster) + ParticleManager:SetParticleControlEnt( + particle, + 0, + caster, + PATTACH_POINT_FOLLOW, + "attach_attack1", + caster:GetAbsOrigin(), + true + ) + ParticleManager:SetParticleControl( + particle, + 1, + target:GetAbsOrigin() + ) + ParticleManager:ReleaseParticleIndex(particle) + ____exports.modifier_keeper_of_the_light_radiant_bind_custom:apply( + target, + caster, + self, + {duration = self:GetSpecialValueFor("duration")} + ) +end +keeper_of_the_light_radiant_bind_custom = __TS__Decorate( + keeper_of_the_light_radiant_bind_custom, + keeper_of_the_light_radiant_bind_custom, + {registerAbility(nil)}, + {kind = "class", name = "keeper_of_the_light_radiant_bind_custom"} +) +____exports.keeper_of_the_light_radiant_bind_custom = keeper_of_the_light_radiant_bind_custom +____exports.modifier_keeper_of_the_light_radiant_bind_custom = __TS__Class() +local modifier_keeper_of_the_light_radiant_bind_custom = ____exports.modifier_keeper_of_the_light_radiant_bind_custom +modifier_keeper_of_the_light_radiant_bind_custom.name = "modifier_keeper_of_the_light_radiant_bind_custom" +modifier_keeper_of_the_light_radiant_bind_custom.____file_path = "scripts/vscripts/abilities/heroes/keeper_of_the_light/keeper_of_the_light_radiant_bind_custom.lua" +__TS__ClassExtends(modifier_keeper_of_the_light_radiant_bind_custom, BaseModifier) +function modifier_keeper_of_the_light_radiant_bind_custom.prototype.____constructor(self, ...) + BaseModifier.prototype.____constructor(self, ...) + self.totalDistanceMoved = 0 + self.movespeedPct = 0 +end +function modifier_keeper_of_the_light_radiant_bind_custom.prototype.DeclareFunctions(self) + return {MODIFIER_PROPERTY_MOVESPEED_BONUS_PERCENTAGE, MODIFIER_PROPERTY_MOVESPEED_ABSOLUTE_MAX, MODIFIER_PROPERTY_IGNORE_MOVESPEED_LIMIT, MODIFIER_PROPERTY_MAGICAL_RESISTANCE_BONUS} +end +function modifier_keeper_of_the_light_radiant_bind_custom.prototype.IsDebuff(self) + local caster = self:GetCaster() + local parent = self:GetParent() + if not caster then + return true + end + return caster:GetTeamNumber() ~= parent:GetTeamNumber() +end +function modifier_keeper_of_the_light_radiant_bind_custom.prototype.IsPurgable(self) + return true +end +function modifier_keeper_of_the_light_radiant_bind_custom.prototype.IsPurgeException(self) + return true +end +function modifier_keeper_of_the_light_radiant_bind_custom.prototype.OnCreated(self) + if not IsServer() then + return + end + local parent = self:GetParent() + self.particle = ParticleManager:CreateParticle("particles/units/heroes/hero_keeper_of_the_light/keeper_of_the_light_radiant_bind_debuff.vpcf", PATTACH_CUSTOMORIGIN_FOLLOW, parent) + self:AddParticle( + self.particle, + false, + false, + 0, + false, + false + ) + self.lastPosition = parent:GetAbsOrigin() + self:StartIntervalThink(0.1) +end +function modifier_keeper_of_the_light_radiant_bind_custom.prototype.OnIntervalThink(self) + if not IsServer() then + return + end + local ability = self:GetAbility() + local parent = self:GetParent() + if not ability then + return + end + local distanceForSlow = ability:GetSpecialValueFor("distance_for_slow") + local slowPctPerDistance = ability:GetSpecialValueFor("slow_pct_per_distance") + local newPos = parent:GetAbsOrigin() + if self.lastPosition then + self.totalDistanceMoved = self.totalDistanceMoved + (newPos - self.lastPosition):Length2D() + end + self.lastPosition = newPos + local slowStacks = math.floor(self.totalDistanceMoved / distanceForSlow) + if slowStacks < 1 then + self.movespeedPct = 0 + return + end + if self.particle then + ParticleManager:SetParticleControl( + self.particle, + 0, + parent:GetAbsOrigin() + ) + ParticleManager:SetParticleControl( + self.particle, + 1, + Vector( + math.max( + math.floor(slowStacks / 1.5), + 1 + ), + 0, + 0 + ) + ) + end + self.movespeedPct = slowStacks * slowPctPerDistance +end +function modifier_keeper_of_the_light_radiant_bind_custom.prototype.GetModifierMoveSpeedBonus_Percentage(self) + return self:IsDebuff() and -self.movespeedPct or self.movespeedPct +end +function modifier_keeper_of_the_light_radiant_bind_custom.prototype.GetModifierIgnoreMovespeedLimit(self) + return 1 +end +function modifier_keeper_of_the_light_radiant_bind_custom.prototype.GetModifierMoveSpeed_AbsoluteMax(self) + local ability = self:GetAbility() + if not ability or self:IsDebuff() then + return 0 + end + return ability:GetSpecialValueFor("movespeed_limit") +end +function modifier_keeper_of_the_light_radiant_bind_custom.prototype.GetModifierMagicalResistanceBonus(self) + local ability = self:GetAbility() + if not ability then + return 0 + end + local value = ability:GetSpecialValueFor("magres_pct") + return self:IsDebuff() and -value or value +end +modifier_keeper_of_the_light_radiant_bind_custom = __TS__Decorate( + modifier_keeper_of_the_light_radiant_bind_custom, + modifier_keeper_of_the_light_radiant_bind_custom, + {registerModifier(nil)}, + {kind = "class", name = "modifier_keeper_of_the_light_radiant_bind_custom"} +) +____exports.modifier_keeper_of_the_light_radiant_bind_custom = modifier_keeper_of_the_light_radiant_bind_custom +return ____exports diff --git a/scripts/vscripts/abilities/heroes/keeper_of_the_light/keeper_of_the_light_recall_custom.lua b/scripts/vscripts/abilities/heroes/keeper_of_the_light/keeper_of_the_light_recall_custom.lua new file mode 100644 index 0000000..1aacd2c --- /dev/null +++ b/scripts/vscripts/abilities/heroes/keeper_of_the_light/keeper_of_the_light_recall_custom.lua @@ -0,0 +1,185 @@ +local ____lualib = require("lualib_bundle") +local __TS__Class = ____lualib.__TS__Class +local __TS__ClassExtends = ____lualib.__TS__ClassExtends +local __TS__Decorate = ____lualib.__TS__Decorate +local ____exports = {} +local ____dota_ts_adapter = require("lib.dota_ts_adapter") +local BaseAbility = ____dota_ts_adapter.BaseAbility +local BaseModifier = ____dota_ts_adapter.BaseModifier +local registerAbility = ____dota_ts_adapter.registerAbility +local registerModifier = ____dota_ts_adapter.registerModifier +____exports.keeper_of_the_light_recall_custom = __TS__Class() +local keeper_of_the_light_recall_custom = ____exports.keeper_of_the_light_recall_custom +keeper_of_the_light_recall_custom.name = "keeper_of_the_light_recall_custom" +keeper_of_the_light_recall_custom.____file_path = "scripts/vscripts/abilities/heroes/keeper_of_the_light/keeper_of_the_light_recall_custom.lua" +__TS__ClassExtends(keeper_of_the_light_recall_custom, BaseAbility) +function keeper_of_the_light_recall_custom.prototype.OnSpellStart(self) + if not IsServer() then + return + end + local caster = self:GetCaster() + local cursorTarget = self:GetCursorTarget() + if not cursorTarget then + return + end + EmitSoundOn("Hero_KeeperOfTheLight.Recall.Cast", caster) + local recallCast = ParticleManager:CreateParticle("particles/units/heroes/hero_keeper_of_the_light/keeper_of_the_light_recall_cast.vpcf", PATTACH_CUSTOMORIGIN, caster) + ParticleManager:SetParticleControlEnt( + recallCast, + 0, + caster, + PATTACH_POINT_FOLLOW, + "attach_origin", + caster:GetAbsOrigin(), + true + ) + ParticleManager:SetParticleControlEnt( + recallCast, + 1, + caster, + PATTACH_POINT_FOLLOW, + "attach_origin", + caster:GetAbsOrigin(), + true + ) + ParticleManager:ReleaseParticleIndex(recallCast) + local duration = self:GetSpecialValueFor("teleport_delay") + if self:GetAutoCastState() then + EmitSoundOn("Hero_KeeperOfTheLight.Recall.Target", caster) + caster:AddNewModifier( + caster, + self, + ____exports.modifier_keeper_of_the_light_recall_custom.name, + { + duration = duration, + target = cursorTarget:GetEntityIndex() + } + ) + return + end + EmitSoundOn("Hero_KeeperOfTheLight.Recall.Target", cursorTarget) + cursorTarget:AddNewModifier( + caster, + self, + ____exports.modifier_keeper_of_the_light_recall_custom.name, + { + duration = duration, + target = caster:GetEntityIndex() + } + ) +end +function keeper_of_the_light_recall_custom.prototype.CastFilterResultTarget(self, target) + if self:GetCaster() == target then + return UF_FAIL_OTHER + end + return UnitFilter( + target, + DOTA_UNIT_TARGET_TEAM_FRIENDLY, + DOTA_UNIT_TARGET_HERO, + DOTA_UNIT_TARGET_FLAG_NOT_CREEP_HERO, + self:GetCaster():GetTeamNumber() + ) +end +keeper_of_the_light_recall_custom = __TS__Decorate( + keeper_of_the_light_recall_custom, + keeper_of_the_light_recall_custom, + {registerAbility(nil)}, + {kind = "class", name = "keeper_of_the_light_recall_custom"} +) +____exports.keeper_of_the_light_recall_custom = keeper_of_the_light_recall_custom +____exports.modifier_keeper_of_the_light_recall_custom = __TS__Class() +local modifier_keeper_of_the_light_recall_custom = ____exports.modifier_keeper_of_the_light_recall_custom +modifier_keeper_of_the_light_recall_custom.name = "modifier_keeper_of_the_light_recall_custom" +modifier_keeper_of_the_light_recall_custom.____file_path = "scripts/vscripts/abilities/heroes/keeper_of_the_light/keeper_of_the_light_recall_custom.lua" +__TS__ClassExtends(modifier_keeper_of_the_light_recall_custom, BaseModifier) +function modifier_keeper_of_the_light_recall_custom.prototype.OnCreated(self, params) + if not IsServer() then + return + end + self.target = EntIndexToHScript(params.target) + local parent = self:GetParent() + local caster = self:GetCaster() + local ability = self:GetAbility() + if not ability then + return + end + parent:AddNewModifier( + caster, + ability, + ____exports.modifier_keeper_of_the_light_recall_custom_speed.name, + {duration = ability:GetSpecialValueFor("movespeed_bonus_duration")} + ) +end +function modifier_keeper_of_the_light_recall_custom.prototype.OnDestroy(self) + if not IsServer() then + return + end + if not self.target or self.target:IsNull() then + return + end + local ability = self:GetAbility() + local parent = self:GetParent() + local caster = self:GetCaster() + if not ability or not caster then + return + end + StopSoundOn("Hero_KeeperOfTheLight.Recall.Target", parent) + EmitSoundOn("Hero_KeeperOfTheLight.Recall.End", parent) + local poof = ParticleManager:CreateParticle("particles/units/heroes/hero_keeper_of_the_light/keeper_of_the_light_recall_poof.vpcf", PATTACH_ABSORIGIN_FOLLOW, parent) + ParticleManager:ReleaseParticleIndex(poof) + FindClearSpaceForUnit( + parent, + self.target:GetAbsOrigin(), + false + ) + local speedDuration = ability:GetSpecialValueFor("movespeed_bonus_duration") + parent:AddNewModifier(caster, ability, ____exports.modifier_keeper_of_the_light_recall_custom_speed.name, {duration = speedDuration}) + self.target:AddNewModifier(caster, ability, ____exports.modifier_keeper_of_the_light_recall_custom_speed.name, {duration = speedDuration}) + local chakraAbility = caster:FindAbilityByName("keeper_of_the_light_chakra_magic_custom") + if chakraAbility and chakraAbility:GetLevel() > 0 then + chakraAbility:OnCustomSpellStart(parent) + end +end +function modifier_keeper_of_the_light_recall_custom.prototype.IsPurgable(self) + return false +end +function modifier_keeper_of_the_light_recall_custom.prototype.GetEffectName(self) + return "particles/units/heroes/hero_keeper_of_the_light/keeper_of_the_light_recall.vpcf" +end +function modifier_keeper_of_the_light_recall_custom.prototype.GetEffectAttachType(self) + return PATTACH_ABSORIGIN_FOLLOW +end +modifier_keeper_of_the_light_recall_custom = __TS__Decorate( + modifier_keeper_of_the_light_recall_custom, + modifier_keeper_of_the_light_recall_custom, + {registerModifier(nil)}, + {kind = "class", name = "modifier_keeper_of_the_light_recall_custom"} +) +____exports.modifier_keeper_of_the_light_recall_custom = modifier_keeper_of_the_light_recall_custom +____exports.modifier_keeper_of_the_light_recall_custom_speed = __TS__Class() +local modifier_keeper_of_the_light_recall_custom_speed = ____exports.modifier_keeper_of_the_light_recall_custom_speed +modifier_keeper_of_the_light_recall_custom_speed.name = "modifier_keeper_of_the_light_recall_custom_speed" +modifier_keeper_of_the_light_recall_custom_speed.____file_path = "scripts/vscripts/abilities/heroes/keeper_of_the_light/keeper_of_the_light_recall_custom.lua" +__TS__ClassExtends(modifier_keeper_of_the_light_recall_custom_speed, BaseModifier) +function modifier_keeper_of_the_light_recall_custom_speed.prototype.____constructor(self, ...) + BaseModifier.prototype.____constructor(self, ...) + self.allyMoveSpeed = 0 +end +function modifier_keeper_of_the_light_recall_custom_speed.prototype.DeclareFunctions(self) + return {MODIFIER_PROPERTY_MOVESPEED_BONUS_PERCENTAGE} +end +function modifier_keeper_of_the_light_recall_custom_speed.prototype.OnCreated(self) + local ____opt_0 = self:GetAbility() + self.allyMoveSpeed = ____opt_0 and ____opt_0:GetSpecialValueFor("ally_movespeed_pct") or 0 +end +function modifier_keeper_of_the_light_recall_custom_speed.prototype.GetModifierMoveSpeedBonus_Percentage(self) + return self.allyMoveSpeed +end +modifier_keeper_of_the_light_recall_custom_speed = __TS__Decorate( + modifier_keeper_of_the_light_recall_custom_speed, + modifier_keeper_of_the_light_recall_custom_speed, + {registerModifier(nil)}, + {kind = "class", name = "modifier_keeper_of_the_light_recall_custom_speed"} +) +____exports.modifier_keeper_of_the_light_recall_custom_speed = modifier_keeper_of_the_light_recall_custom_speed +return ____exports diff --git a/scripts/vscripts/abilities/heroes/keeper_of_the_light/keeper_of_the_light_will_o_wisp_custom.lua b/scripts/vscripts/abilities/heroes/keeper_of_the_light/keeper_of_the_light_will_o_wisp_custom.lua new file mode 100644 index 0000000..3725f07 --- /dev/null +++ b/scripts/vscripts/abilities/heroes/keeper_of_the_light/keeper_of_the_light_will_o_wisp_custom.lua @@ -0,0 +1,205 @@ +local ____lualib = require("lualib_bundle") +local __TS__Class = ____lualib.__TS__Class +local __TS__ClassExtends = ____lualib.__TS__ClassExtends +local __TS__Decorate = ____lualib.__TS__Decorate +local __TS__NumberToFixed = ____lualib.__TS__NumberToFixed +local ____exports = {} +local ____dota_ts_adapter = require("lib.dota_ts_adapter") +local BaseAbility = ____dota_ts_adapter.BaseAbility +local BaseModifier = ____dota_ts_adapter.BaseModifier +local registerAbility = ____dota_ts_adapter.registerAbility +local registerModifier = ____dota_ts_adapter.registerModifier +____exports.keeper_of_the_light_will_o_wisp_custom = __TS__Class() +local keeper_of_the_light_will_o_wisp_custom = ____exports.keeper_of_the_light_will_o_wisp_custom +keeper_of_the_light_will_o_wisp_custom.name = "keeper_of_the_light_will_o_wisp_custom" +keeper_of_the_light_will_o_wisp_custom.____file_path = "scripts/vscripts/abilities/heroes/keeper_of_the_light/keeper_of_the_light_will_o_wisp_custom.lua" +__TS__ClassExtends(keeper_of_the_light_will_o_wisp_custom, BaseAbility) +function keeper_of_the_light_will_o_wisp_custom.prototype.GetAOERadius(self) + return self:GetSpecialValueFor("radius") +end +function keeper_of_the_light_will_o_wisp_custom.prototype.Precache(self, context) + PrecacheResource("model", "models/heroes/keeper_of_the_light/kotl_wisp.vmdl", context) +end +function keeper_of_the_light_will_o_wisp_custom.prototype.OnSpellStart(self) + if not IsServer() then + return + end + local caster = self:GetCaster() + local point = self:GetCursorPosition() + local unit = CreateUnitByName( + "npc_kotl_wisp", + point, + true, + caster, + caster, + caster:GetTeamNumber() + ) + ____exports.modifier_keeper_of_the_light_will_o_wisp_custom:apply(unit, caster, self, {}) +end +keeper_of_the_light_will_o_wisp_custom = __TS__Decorate( + keeper_of_the_light_will_o_wisp_custom, + keeper_of_the_light_will_o_wisp_custom, + {registerAbility(nil)}, + {kind = "class", name = "keeper_of_the_light_will_o_wisp_custom"} +) +____exports.keeper_of_the_light_will_o_wisp_custom = keeper_of_the_light_will_o_wisp_custom +____exports.modifier_keeper_of_the_light_will_o_wisp_custom = __TS__Class() +local modifier_keeper_of_the_light_will_o_wisp_custom = ____exports.modifier_keeper_of_the_light_will_o_wisp_custom +modifier_keeper_of_the_light_will_o_wisp_custom.name = "modifier_keeper_of_the_light_will_o_wisp_custom" +modifier_keeper_of_the_light_will_o_wisp_custom.____file_path = "scripts/vscripts/abilities/heroes/keeper_of_the_light/keeper_of_the_light_will_o_wisp_custom.lua" +__TS__ClassExtends(modifier_keeper_of_the_light_will_o_wisp_custom, BaseModifier) +function modifier_keeper_of_the_light_will_o_wisp_custom.prototype.____constructor(self, ...) + BaseModifier.prototype.____constructor(self, ...) + self.radius = 0 + self.delay = 0 + self.activeDuration = 0 + self.activeTick = 0 + self.activeChannel = 0 + self.damage = 0 + self.hitCount = 0 +end +function modifier_keeper_of_the_light_will_o_wisp_custom.prototype.CheckState(self) + return {[MODIFIER_STATE_INVULNERABLE] = true, [MODIFIER_STATE_NO_HEALTH_BAR] = true, [MODIFIER_STATE_UNSELECTABLE] = true} +end +function modifier_keeper_of_the_light_will_o_wisp_custom.prototype.GetEffectName(self) + return "particles/units/heroes/hero_keeper_of_the_light/keeper_dazzling.vpcf" +end +function modifier_keeper_of_the_light_will_o_wisp_custom.prototype.OnCreated(self) + if not IsServer() then + return + end + local parent = self:GetParent() + local ability = self:GetAbility() + if not ability then + return + end + self.radius = ability:GetSpecialValueFor("radius") + self.delay = ability:GetSpecialValueFor("delay") + self.activeDuration = tonumber(__TS__NumberToFixed( + ability:GetSpecialValueFor("active_duration"), + 1 + )) or 0 + self.activeTick = ability:GetSpecialValueFor("active_tick") + self.damage = ability:GetSpecialValueFor("damage") + self.particleAura = ParticleManager:CreateParticle("particles/units/heroes/hero_keeper_of_the_light/keeper_dazzling_aoe_ring.vpcf", PATTACH_CUSTOMORIGIN, parent) + ParticleManager:SetParticleControl( + self.particleAura, + 0, + parent:GetAbsOrigin() + ) + ParticleManager:SetParticleControl( + self.particleAura, + 1, + Vector(self.radius, self.radius, self.radius) + ) + EmitSoundOn("Hero_KeeperOfTheLight.Wisp.Spawn", parent) + self:StartIntervalThink(self.delay) +end +function modifier_keeper_of_the_light_will_o_wisp_custom.prototype.OnDestroy(self) + if not IsServer() then + return + end + if self.particleAura ~= nil then + ParticleManager:DestroyParticle(self.particleAura, false) + ParticleManager:ReleaseParticleIndex(self.particleAura) + self.particleAura = nil + end + if self.particleActive ~= nil then + ParticleManager:DestroyParticle(self.particleActive, false) + ParticleManager:ReleaseParticleIndex(self.particleActive) + self.particleActive = nil + end +end +function modifier_keeper_of_the_light_will_o_wisp_custom.prototype.OnIntervalThink(self) + if not IsServer() then + return + end + local parent = self:GetParent() + local ability = self:GetAbility() + if not ability then + return + end + if self.activeChannel == 0 then + EmitSoundOn("Hero_KeeperOfTheLight.Wisp.Active", parent) + end + self:StartIntervalThink(self.activeTick) + self.activeChannel = self.activeChannel + self.activeTick + if not self.particleActive then + self.particleActive = ParticleManager:CreateParticle("particles/units/heroes/hero_keeper_of_the_light/keeper_dazzling_on.vpcf", PATTACH_ABSORIGIN_FOLLOW, parent) + end + local enemies = FindUnitsInRadius( + parent:GetTeamNumber(), + parent:GetOrigin(), + nil, + self.radius, + DOTA_UNIT_TARGET_TEAM_ENEMY, + bit.bor(DOTA_UNIT_TARGET_HERO, DOTA_UNIT_TARGET_BASIC), + DOTA_UNIT_TARGET_FLAG_NONE, + FIND_ANY_ORDER, + false + ) + for ____, enemy in ipairs(enemies) do + local manaDamagePerSecond = self:GetCaster():GetMana() * 0.25 + ApplyDamage({ + victim = enemy, + attacker = parent, + damage = (self.damage + manaDamagePerSecond) * self.activeTick, + damage_type = ability:GetAbilityDamageType(), + ability = ability + }) + ____exports.modifier_keeper_will_o_wisp_debuff:apply( + enemy, + self:GetCaster(), + ability, + {duration = self.activeTick + 0.1} + ) + end + if self.activeChannel < self.activeDuration then + return + end + self:StartIntervalThink(self.delay) + if self.particleActive ~= nil then + ParticleManager:DestroyParticle(self.particleActive, false) + ParticleManager:ReleaseParticleIndex(self.particleActive) + self.particleActive = nil + end + self.activeChannel = 0 + self.hitCount = self.hitCount + 1 + if ability:GetSpecialValueFor("hit_count") <= self.hitCount then + parent:ForceKill(true) + EmitSoundOn("Hero_KeeperOfTheLight.Wisp.Destroy", parent) + end +end +modifier_keeper_of_the_light_will_o_wisp_custom = __TS__Decorate( + modifier_keeper_of_the_light_will_o_wisp_custom, + modifier_keeper_of_the_light_will_o_wisp_custom, + {registerModifier(nil)}, + {kind = "class", name = "modifier_keeper_of_the_light_will_o_wisp_custom"} +) +____exports.modifier_keeper_of_the_light_will_o_wisp_custom = modifier_keeper_of_the_light_will_o_wisp_custom +____exports.modifier_keeper_will_o_wisp_debuff = __TS__Class() +local modifier_keeper_will_o_wisp_debuff = ____exports.modifier_keeper_will_o_wisp_debuff +modifier_keeper_will_o_wisp_debuff.name = "modifier_keeper_will_o_wisp_debuff" +modifier_keeper_will_o_wisp_debuff.____file_path = "scripts/vscripts/abilities/heroes/keeper_of_the_light/keeper_of_the_light_will_o_wisp_custom.lua" +__TS__ClassExtends(modifier_keeper_will_o_wisp_debuff, BaseModifier) +function modifier_keeper_will_o_wisp_debuff.prototype.DeclareFunctions(self) + return {MODIFIER_PROPERTY_MOVESPEED_BONUS_PERCENTAGE} +end +function modifier_keeper_will_o_wisp_debuff.prototype.GetModifierMoveSpeedBonus_Percentage(self) + local ____opt_0 = self:GetAbility() + return ____opt_0 and ____opt_0:GetSpecialValueFor("slow_movespeed") or 0 +end +function modifier_keeper_will_o_wisp_debuff.prototype.GetStatusEffectName(self) + return "particles/status_fx/status_effect_keeper_dazzle.vpcf" +end +function modifier_keeper_will_o_wisp_debuff.prototype.StatusEffectPriority(self) + return MODIFIER_PRIORITY_HIGH +end +modifier_keeper_will_o_wisp_debuff = __TS__Decorate( + modifier_keeper_will_o_wisp_debuff, + modifier_keeper_will_o_wisp_debuff, + {registerModifier(nil)}, + {kind = "class", name = "modifier_keeper_will_o_wisp_debuff"} +) +____exports.modifier_keeper_will_o_wisp_debuff = modifier_keeper_will_o_wisp_debuff +return ____exports diff --git a/scripts/vscripts/abilities/heroes/legion_commander/ability_legion_commander_duel_custom.lua b/scripts/vscripts/abilities/heroes/legion_commander/ability_legion_commander_duel_custom.lua new file mode 100644 index 0000000..91e708e --- /dev/null +++ b/scripts/vscripts/abilities/heroes/legion_commander/ability_legion_commander_duel_custom.lua @@ -0,0 +1,601 @@ +local ____lualib = require("lualib_bundle") +local __TS__Class = ____lualib.__TS__Class +local __TS__ClassExtends = ____lualib.__TS__ClassExtends +local __TS__Decorate = ____lualib.__TS__Decorate +local ____exports = {} +local ____dota_ts_adapter = require("lib.dota_ts_adapter") +local BaseAbility = ____dota_ts_adapter.BaseAbility +local BaseModifier = ____dota_ts_adapter.BaseModifier +local registerAbility = ____dota_ts_adapter.registerAbility +local registerModifier = ____dota_ts_adapter.registerModifier +local DUEL_DEBUG_ENABLED = true +local DUEL_DEBUG_PREFIX = "[LC_DUEL_DEBUG]" +local function duelDebug(self, message) + if not DUEL_DEBUG_ENABLED then + return + end + print((DUEL_DEBUG_PREFIX .. " ") .. message) +end +local function removeDuelModifiersFrom(self, unit) + duelDebug( + nil, + (("removeDuelModifiersFrom unit=" .. unit:GetUnitName()) .. "#") .. tostring(unit:entindex()) + ) + while unit:HasModifier(____exports.modifier_legion_commander_duel_custom.name) do + unit:RemoveModifierByName(____exports.modifier_legion_commander_duel_custom.name) + end + local ____this_1 + ____this_1 = unit + local ____opt_0 = ____this_1.SetForceAttackTarget + if ____opt_0 ~= nil then + ____opt_0(____this_1, nil) + end +end +local DUEL_RESOLUTION_LOCK_DURATION = 0.3 +____exports.ability_legion_commander_duel_custom = __TS__Class() +local ability_legion_commander_duel_custom = ____exports.ability_legion_commander_duel_custom +ability_legion_commander_duel_custom.name = "ability_legion_commander_duel_custom" +ability_legion_commander_duel_custom.____file_path = "scripts/vscripts/abilities/heroes/legion_commander/ability_legion_commander_duel_custom.lua" +__TS__ClassExtends(ability_legion_commander_duel_custom, BaseAbility) +function ability_legion_commander_duel_custom.prototype.Precache(self, context) + PrecacheResource("particle", "particles/units/heroes/hero_legion_commander/legion_commander_duel_victory.vpcf", context) + PrecacheResource("particle", "particles/units/heroes/hero_legion_commander/legion_commander_press_a.vpcf", context) + PrecacheResource("particle", "particles/status_fx/status_effect_legion_commander_duel.vpcf", context) + PrecacheResource("particle", "particles/econ/items/legion/legion_weapon_voth_domosh/legion_duel_ring_arcana.vpcf", context) + PrecacheResource("soundfile", "soundevents/game_sounds_heroes/game_sounds_legion_commander.vsndevts", context) +end +function ability_legion_commander_duel_custom.prototype.GetCastAnimation(self) + return ACT_DOTA_CAST_ABILITY_4 +end +function ability_legion_commander_duel_custom.prototype.OnSpellStart(self) + if not IsServer() then + return + end + local caster = self:GetCaster() + local target = self:GetCursorTarget() + duelDebug( + nil, + (((((("OnSpellStart caster=" .. (caster and caster:GetUnitName())) .. "#") .. tostring(caster and caster:entindex())) .. " target=") .. tostring(target and target:GetUnitName())) .. "#") .. tostring(target and target:entindex()) + ) + if not caster or not caster:IsAlive() or not target or not target:IsAlive() or target:IsCourier() then + return + end + if target:GetTeamNumber() == caster:GetTeamNumber() then + return + end + if target:TriggerSpellAbsorb(self) then + return + end + self:startDuel(caster, target) +end +function ability_legion_commander_duel_custom.prototype.startDuel(self, caster, target) + local duration = self:GetSpecialValueFor("duration") + if caster:IsIllusion() then + duration = 1.5 + end + local durTarget = duration * (1 - target:GetStatusResistance()) + duelDebug( + nil, + (((((("startDuel caster=" .. tostring(caster:entindex())) .. " target=") .. tostring(target:entindex())) .. " duration=") .. tostring(duration)) .. " durTarget=") .. tostring(durTarget) + ) + EmitSoundOn("Hero_LegionCommander.Duel.Cast", caster) + caster:AddNewModifier( + caster, + self, + ____exports.modifier_legion_commander_duel_custom.name, + { + duration = duration, + counterpart_entindex = target:entindex() + } + ) + target:AddNewModifier( + caster, + self, + ____exports.modifier_legion_commander_duel_custom.name, + { + duration = durTarget, + counterpart_entindex = caster:entindex() + } + ) + ParticleManager:ReleaseParticleIndex(ParticleManager:CreateParticle("particles/units/heroes/hero_legion_commander/legion_commander_press_a.vpcf", PATTACH_ABSORIGIN_FOLLOW, caster)) + ParticleManager:ReleaseParticleIndex(ParticleManager:CreateParticle("particles/units/heroes/hero_legion_commander/legion_commander_press_a.vpcf", PATTACH_ABSORIGIN_FOLLOW, target)) + EmitSoundOn("Hero_LegionCommander.Duel", caster) + ExecuteOrderFromTable({ + UnitIndex = caster:entindex(), + OrderType = DOTA_UNIT_ORDER_ATTACK_TARGET, + TargetIndex = target:entindex(), + Queue = false + }) + ExecuteOrderFromTable({ + UnitIndex = target:entindex(), + OrderType = DOTA_UNIT_ORDER_ATTACK_TARGET, + TargetIndex = caster:entindex(), + Queue = false + }) +end +function ability_legion_commander_duel_custom.prototype.completeDuelWithWinner(self, winner, loser) + if not IsServer() then + return + end + duelDebug( + nil, + (("completeDuelWithWinner winner=" .. tostring(winner:entindex())) .. " loser=") .. tostring(loser:entindex()) + ) + local hasResolutionLock = winner:HasModifier(____exports.modifier_legion_commander_duel_resolution_lock_custom.name) + removeDuelModifiersFrom(nil, winner) + removeDuelModifiersFrom(nil, loser) + if hasResolutionLock then + return + end + winner:AddNewModifier(winner, self, ____exports.modifier_legion_commander_duel_resolution_lock_custom.name, {duration = DUEL_RESOLUTION_LOCK_DURATION}) + self:grantDuelVictoryStacks(winner, loser) +end +function ability_legion_commander_duel_custom.prototype.grantDuelVictoryStacks(self, winner, loser) + duelDebug( + nil, + (("grantDuelVictoryStacks winner=" .. tostring(winner and winner:entindex())) .. " loser=") .. tostring(loser and loser:entindex()) + ) + if not winner or not winner:IsAlive() then + return + end + if not loser or loser:IsIllusion() then + return + end + if loser:IsHero() and loser:IsTempestDouble() then + return + end + local isCreepLike = loser:IsCreep() and not loser:IsHero() + local add = math.floor(isCreepLike and self:GetSpecialValueFor("reward_damage_creep") or self:GetSpecialValueFor("reward_damage")) + if add <= 0 then + return + end + local cap = self:GetSpecialValueFor("max_stack_damage") + local stackMod = winner:FindModifierByName(____exports.modifier_legion_commander_duel_damage_stack_custom.name) + if not stackMod then + winner:AddNewModifier(winner, self, ____exports.modifier_legion_commander_duel_damage_stack_custom.name, {}) + stackMod = winner:FindModifierByName(____exports.modifier_legion_commander_duel_damage_stack_custom.name) + end + if stackMod then + local next = stackMod:GetStackCount() + add + stackMod:SetStackCount(cap > 0 and math.min(cap, next) or next) + end + local randomStatPerWin = math.max( + 0, + math.floor(self:GetSpecialValueFor("reward_random_stat")) + ) + if randomStatPerWin > 0 and winner:IsRealHero() then + self:grantRandomStat(winner, randomStatPerWin) + end + local pWin = ParticleManager:CreateParticle("particles/units/heroes/hero_legion_commander/legion_commander_duel_victory.vpcf", PATTACH_OVERHEAD_FOLLOW, winner) + ParticleManager:ReleaseParticleIndex(pWin) + EmitSoundOn("Hero_LegionCommander.Duel.Victory", winner) +end +function ability_legion_commander_duel_custom.prototype.grantRandomStat(self, winner, amount) + local roll = RandomInt(1, 3) + local modName = ____exports.modifier_legion_commander_duel_random_stat_str_custom.name + if roll == 2 then + modName = ____exports.modifier_legion_commander_duel_random_stat_agi_custom.name + end + if roll == 3 then + modName = ____exports.modifier_legion_commander_duel_random_stat_int_custom.name + end + local mod = winner:FindModifierByName(modName) + if not mod then + winner:AddNewModifier(winner, self, modName, {}) + mod = winner:FindModifierByName(modName) + end + if mod then + mod:SetStackCount(mod:GetStackCount() + amount) + end +end +function ability_legion_commander_duel_custom.prototype.terminateDuelNoReward(self, attackerSide, defenderSide) + if not IsServer() then + return + end + removeDuelModifiersFrom(nil, attackerSide) + removeDuelModifiersFrom(nil, defenderSide) +end +ability_legion_commander_duel_custom = __TS__Decorate( + ability_legion_commander_duel_custom, + ability_legion_commander_duel_custom, + {registerAbility(nil)}, + {kind = "class", name = "ability_legion_commander_duel_custom"} +) +____exports.ability_legion_commander_duel_custom = ability_legion_commander_duel_custom +____exports.modifier_legion_commander_duel_damage_stack_custom = __TS__Class() +local modifier_legion_commander_duel_damage_stack_custom = ____exports.modifier_legion_commander_duel_damage_stack_custom +modifier_legion_commander_duel_damage_stack_custom.name = "modifier_legion_commander_duel_damage_stack_custom" +modifier_legion_commander_duel_damage_stack_custom.____file_path = "scripts/vscripts/abilities/heroes/legion_commander/ability_legion_commander_duel_custom.lua" +__TS__ClassExtends(modifier_legion_commander_duel_damage_stack_custom, BaseModifier) +function modifier_legion_commander_duel_damage_stack_custom.prototype.IsHidden(self) + return false +end +function modifier_legion_commander_duel_damage_stack_custom.prototype.IsPurgable(self) + return false +end +function modifier_legion_commander_duel_damage_stack_custom.prototype.IsDebuff(self) + return false +end +function modifier_legion_commander_duel_damage_stack_custom.prototype.RemoveOnDeath(self) + return false +end +function modifier_legion_commander_duel_damage_stack_custom.prototype.DeclareFunctions(self) + return {MODIFIER_PROPERTY_PREATTACK_BONUS_DAMAGE} +end +function modifier_legion_commander_duel_damage_stack_custom.prototype.GetModifierPreAttack_BonusDamage(self) + return self:GetStackCount() +end +function modifier_legion_commander_duel_damage_stack_custom.prototype.GetTexture(self) + return "legion_commander_duel" +end +modifier_legion_commander_duel_damage_stack_custom = __TS__Decorate( + modifier_legion_commander_duel_damage_stack_custom, + modifier_legion_commander_duel_damage_stack_custom, + {registerModifier(nil)}, + {kind = "class", name = "modifier_legion_commander_duel_damage_stack_custom"} +) +____exports.modifier_legion_commander_duel_damage_stack_custom = modifier_legion_commander_duel_damage_stack_custom +____exports.modifier_legion_commander_duel_resolution_lock_custom = __TS__Class() +local modifier_legion_commander_duel_resolution_lock_custom = ____exports.modifier_legion_commander_duel_resolution_lock_custom +modifier_legion_commander_duel_resolution_lock_custom.name = "modifier_legion_commander_duel_resolution_lock_custom" +modifier_legion_commander_duel_resolution_lock_custom.____file_path = "scripts/vscripts/abilities/heroes/legion_commander/ability_legion_commander_duel_custom.lua" +__TS__ClassExtends(modifier_legion_commander_duel_resolution_lock_custom, BaseModifier) +function modifier_legion_commander_duel_resolution_lock_custom.prototype.IsHidden(self) + return true +end +function modifier_legion_commander_duel_resolution_lock_custom.prototype.IsPurgable(self) + return false +end +modifier_legion_commander_duel_resolution_lock_custom = __TS__Decorate( + modifier_legion_commander_duel_resolution_lock_custom, + modifier_legion_commander_duel_resolution_lock_custom, + {registerModifier(nil)}, + {kind = "class", name = "modifier_legion_commander_duel_resolution_lock_custom"} +) +____exports.modifier_legion_commander_duel_resolution_lock_custom = modifier_legion_commander_duel_resolution_lock_custom +____exports.modifier_legion_commander_duel_random_stat_str_custom = __TS__Class() +local modifier_legion_commander_duel_random_stat_str_custom = ____exports.modifier_legion_commander_duel_random_stat_str_custom +modifier_legion_commander_duel_random_stat_str_custom.name = "modifier_legion_commander_duel_random_stat_str_custom" +modifier_legion_commander_duel_random_stat_str_custom.____file_path = "scripts/vscripts/abilities/heroes/legion_commander/ability_legion_commander_duel_custom.lua" +__TS__ClassExtends(modifier_legion_commander_duel_random_stat_str_custom, BaseModifier) +function modifier_legion_commander_duel_random_stat_str_custom.prototype.IsHidden(self) + return false +end +function modifier_legion_commander_duel_random_stat_str_custom.prototype.IsPurgable(self) + return false +end +function modifier_legion_commander_duel_random_stat_str_custom.prototype.RemoveOnDeath(self) + return false +end +function modifier_legion_commander_duel_random_stat_str_custom.prototype.DeclareFunctions(self) + return {MODIFIER_PROPERTY_STATS_STRENGTH_BONUS} +end +function modifier_legion_commander_duel_random_stat_str_custom.prototype.GetModifierBonusStats_Strength(self) + return self:GetStackCount() +end +function modifier_legion_commander_duel_random_stat_str_custom.prototype.GetTexture(self) + return "legion_commander_duel" +end +modifier_legion_commander_duel_random_stat_str_custom = __TS__Decorate( + modifier_legion_commander_duel_random_stat_str_custom, + modifier_legion_commander_duel_random_stat_str_custom, + {registerModifier(nil)}, + {kind = "class", name = "modifier_legion_commander_duel_random_stat_str_custom"} +) +____exports.modifier_legion_commander_duel_random_stat_str_custom = modifier_legion_commander_duel_random_stat_str_custom +____exports.modifier_legion_commander_duel_random_stat_agi_custom = __TS__Class() +local modifier_legion_commander_duel_random_stat_agi_custom = ____exports.modifier_legion_commander_duel_random_stat_agi_custom +modifier_legion_commander_duel_random_stat_agi_custom.name = "modifier_legion_commander_duel_random_stat_agi_custom" +modifier_legion_commander_duel_random_stat_agi_custom.____file_path = "scripts/vscripts/abilities/heroes/legion_commander/ability_legion_commander_duel_custom.lua" +__TS__ClassExtends(modifier_legion_commander_duel_random_stat_agi_custom, BaseModifier) +function modifier_legion_commander_duel_random_stat_agi_custom.prototype.IsHidden(self) + return false +end +function modifier_legion_commander_duel_random_stat_agi_custom.prototype.IsPurgable(self) + return false +end +function modifier_legion_commander_duel_random_stat_agi_custom.prototype.RemoveOnDeath(self) + return false +end +function modifier_legion_commander_duel_random_stat_agi_custom.prototype.DeclareFunctions(self) + return {MODIFIER_PROPERTY_STATS_AGILITY_BONUS} +end +function modifier_legion_commander_duel_random_stat_agi_custom.prototype.GetModifierBonusStats_Agility(self) + return self:GetStackCount() +end +function modifier_legion_commander_duel_random_stat_agi_custom.prototype.GetTexture(self) + return "legion_commander_duel" +end +modifier_legion_commander_duel_random_stat_agi_custom = __TS__Decorate( + modifier_legion_commander_duel_random_stat_agi_custom, + modifier_legion_commander_duel_random_stat_agi_custom, + {registerModifier(nil)}, + {kind = "class", name = "modifier_legion_commander_duel_random_stat_agi_custom"} +) +____exports.modifier_legion_commander_duel_random_stat_agi_custom = modifier_legion_commander_duel_random_stat_agi_custom +____exports.modifier_legion_commander_duel_random_stat_int_custom = __TS__Class() +local modifier_legion_commander_duel_random_stat_int_custom = ____exports.modifier_legion_commander_duel_random_stat_int_custom +modifier_legion_commander_duel_random_stat_int_custom.name = "modifier_legion_commander_duel_random_stat_int_custom" +modifier_legion_commander_duel_random_stat_int_custom.____file_path = "scripts/vscripts/abilities/heroes/legion_commander/ability_legion_commander_duel_custom.lua" +__TS__ClassExtends(modifier_legion_commander_duel_random_stat_int_custom, BaseModifier) +function modifier_legion_commander_duel_random_stat_int_custom.prototype.IsHidden(self) + return false +end +function modifier_legion_commander_duel_random_stat_int_custom.prototype.IsPurgable(self) + return false +end +function modifier_legion_commander_duel_random_stat_int_custom.prototype.RemoveOnDeath(self) + return false +end +function modifier_legion_commander_duel_random_stat_int_custom.prototype.DeclareFunctions(self) + return {MODIFIER_PROPERTY_STATS_INTELLECT_BONUS} +end +function modifier_legion_commander_duel_random_stat_int_custom.prototype.GetModifierBonusStats_Intellect(self) + return self:GetStackCount() +end +function modifier_legion_commander_duel_random_stat_int_custom.prototype.GetTexture(self) + return "legion_commander_duel" +end +modifier_legion_commander_duel_random_stat_int_custom = __TS__Decorate( + modifier_legion_commander_duel_random_stat_int_custom, + modifier_legion_commander_duel_random_stat_int_custom, + {registerModifier(nil)}, + {kind = "class", name = "modifier_legion_commander_duel_random_stat_int_custom"} +) +____exports.modifier_legion_commander_duel_random_stat_int_custom = modifier_legion_commander_duel_random_stat_int_custom +____exports.modifier_legion_commander_duel_custom = __TS__Class() +local modifier_legion_commander_duel_custom = ____exports.modifier_legion_commander_duel_custom +modifier_legion_commander_duel_custom.name = "modifier_legion_commander_duel_custom" +modifier_legion_commander_duel_custom.____file_path = "scripts/vscripts/abilities/heroes/legion_commander/ability_legion_commander_duel_custom.lua" +__TS__ClassExtends(modifier_legion_commander_duel_custom, BaseModifier) +function modifier_legion_commander_duel_custom.prototype.____constructor(self, ...) + BaseModifier.prototype.____constructor(self, ...) + self.thinkInterval = 0.1 + self.nextDebugAt = 0 + self.nextShardAutoOddsAt = 0 + self.finished = false +end +function modifier_legion_commander_duel_custom.prototype.getShardOverwhelmingInterval(self) + local ab = self:GetAbility() + if not ab then + return 2 + end + return math.max( + 0.1, + ab:GetSpecialValueFor("shard_overwhelming_interval") + ) +end +function modifier_legion_commander_duel_custom.prototype.getCounterpart(self) + if self.counterpartEi == nil or self.counterpartEi == -1 then + return nil + end + local unit = EntIndexToHScript(self.counterpartEi) + if not unit or unit:IsNull() then + return nil + end + return unit +end +function modifier_legion_commander_duel_custom.prototype.IsHidden(self) + return false +end +function modifier_legion_commander_duel_custom.prototype.IsPurgable(self) + return false +end +function modifier_legion_commander_duel_custom.prototype.IsDebuff(self) + return true +end +function modifier_legion_commander_duel_custom.prototype.RemoveOnDeath(self) + return false +end +function modifier_legion_commander_duel_custom.prototype.GetTexture(self) + return "legion_commander_duel" +end +function modifier_legion_commander_duel_custom.prototype.GetStatusEffectName(self) + return "particles/status_fx/status_effect_legion_commander_duel.vpcf" +end +function modifier_legion_commander_duel_custom.prototype.StatusEffectPriority(self) + return MODIFIER_PRIORITY_ULTRA +end +function modifier_legion_commander_duel_custom.prototype.CheckState(self) + return {[MODIFIER_STATE_COMMAND_RESTRICTED] = true, [MODIFIER_STATE_TAUNTED] = true, [MODIFIER_STATE_SILENCED] = true} +end +function modifier_legion_commander_duel_custom.prototype.DeclareFunctions(self) + return {MODIFIER_PROPERTY_ATTACKSPEED_BONUS_CONSTANT, MODIFIER_PROPERTY_MAGICAL_RESISTANCE_BONUS, MODIFIER_EVENT_ON_DEATH} +end +function modifier_legion_commander_duel_custom.prototype.GetModifierAttackSpeedBonus_Constant(self) + local ab = self:GetAbility() + if not ab then + return 0 + end + return ab:GetSpecialValueFor("bonus_attack_speed") +end +function modifier_legion_commander_duel_custom.prototype.GetModifierMagicalResistanceBonus(self) + local parent = self:GetParent() + local caster = self:GetCaster() + local ab = self:GetAbility() + if not ab or not caster or not parent or parent ~= caster then + return 0 + end + if not caster:HasScepter() then + return 0 + end + return ab:GetSpecialValueFor("scepter_magic_resist") +end +function modifier_legion_commander_duel_custom.prototype.OnCreated(self, params) + if not IsServer() then + return + end + self.counterpartEi = params.counterpart_entindex or -1 + duelDebug( + nil, + (("modifier_duel.OnCreated parent=" .. tostring(self:GetParent():entindex())) .. " counterpartEi=") .. tostring(self.counterpartEi) + ) + if self.counterpartEi == -1 then + self:breakDuelNoReward() + return + end + local ab = self:GetAbility() + self.victoryRange = ab and ab:GetSpecialValueFor("victory_range") or 1500 + local parent = self:GetParent() + local counterpart = self:getCounterpart() + if counterpart and not parent:IsCreep() then + local ____opt_14 = parent.SetForceAttackTarget + if ____opt_14 ~= nil then + ____opt_14(parent, counterpart) + end + parent:MoveToTargetToAttack(counterpart) + end + if self:GetCaster() == parent then + self.nextShardAutoOddsAt = GameRules:GetGameTime() + self:getShardOverwhelmingInterval() + end + if counterpart and self:GetCaster() == parent then + local center = GetGroundPosition( + parent:GetAbsOrigin(), + parent + ) + local ringFx = ParticleManager:CreateParticle("particles/econ/items/legion/legion_weapon_voth_domosh/legion_duel_ring_arcana.vpcf", PATTACH_WORLDORIGIN, parent) + ParticleManager:SetParticleControl(ringFx, 0, center) + ParticleManager:SetParticleControl(ringFx, 7, center) + self:AddParticle( + ringFx, + false, + false, + -1, + true, + false + ) + end + self:StartIntervalThink(self.thinkInterval) +end +function modifier_legion_commander_duel_custom.prototype.OnIntervalThink(self) + if not IsServer() or self.finished then + return + end + local parent = self:GetParent() + local counterpart = self:getCounterpart() + local now = GameRules:GetGameTime() + if now >= self.nextDebugAt then + self.nextDebugAt = now + 1 + duelDebug( + nil, + (((((("modifier_duel.Tick parent=" .. tostring(parent:entindex())) .. " alive=") .. tostring(parent:IsAlive())) .. " counterpart=") .. tostring(counterpart and counterpart:entindex())) .. " counterpartAlive=") .. tostring(counterpart and counterpart:IsAlive()) + ) + end + if not parent or not parent:IsAlive() then + return + end + if not counterpart then + self:breakDuelNoReward() + return + end + local ability = self:GetAbility() + if not counterpart:IsAlive() then + if ability then + ability:completeDuelWithWinner(parent, counterpart) + else + self:breakDuelNoReward() + end + return + end + if not counterpart:HasModifier(____exports.modifier_legion_commander_duel_custom.name) then + self:breakDuelNoReward() + return + end + local d = parent:GetAbsOrigin():__sub(counterpart:GetAbsOrigin()):Length2D() + if d > self.victoryRange then + self:breakDuelNoReward() + return + end + if self:GetCaster() == parent and HasShard(nil, parent) then + local gameTime = GameRules:GetGameTime() + if gameTime >= self.nextShardAutoOddsAt then + self.nextShardAutoOddsAt = gameTime + self:getShardOverwhelmingInterval() + local odds = parent:FindAbilityByName("ability_legion_commander_overwhelming_odds_custom") + if odds and odds:GetLevel() > 0 then + odds:ExecuteBurstAt(GetGroundPosition( + parent:GetAbsOrigin(), + parent + )) + end + end + end + if not parent:IsCreep() then + local ____opt_20 = parent.SetForceAttackTarget + if ____opt_20 ~= nil then + ____opt_20(parent, counterpart) + end + parent:MoveToTargetToAttack(counterpart) + end +end +function modifier_legion_commander_duel_custom.prototype.OnDeath(self, event) + if not IsServer() or self.finished then + return + end + local deadUnit = event.unit + if not deadUnit or deadUnit:entindex() ~= self:GetParent():entindex() then + return + end + local ____duelDebug_25 = duelDebug + local ____temp_24 = deadUnit:entindex() + local ____opt_22 = self:getCounterpart() + ____duelDebug_25( + nil, + (("modifier_duel.OnDeath dead=" .. tostring(____temp_24)) .. " counterpart=") .. tostring(____opt_22 and ____opt_22:entindex()) + ) + local ability = self:GetAbility() + local loser = self:GetParent() + local winner = self:getCounterpart() + if not ability or not winner then + removeDuelModifiersFrom(nil, loser) + return + end + self.finished = true + if winner:IsAlive() then + ability:completeDuelWithWinner(winner, loser) + else + ability:terminateDuelNoReward(loser, winner) + end +end +function modifier_legion_commander_duel_custom.prototype.breakDuelNoReward(self) + if not IsServer() or self.finished then + return + end + self.finished = true + local ____duelDebug_29 = duelDebug + local ____temp_28 = self:GetParent():entindex() + local ____opt_26 = self:getCounterpart() + ____duelDebug_29( + nil, + (("modifier_duel.breakDuelNoReward parent=" .. tostring(____temp_28)) .. " counterpart=") .. tostring(____opt_26 and ____opt_26:entindex()) + ) + local parent = self:GetParent() + local counterpart = self:getCounterpart() + local ability = self:GetAbility() + if not ability or not counterpart then + removeDuelModifiersFrom(nil, parent) + return + end + ability:terminateDuelNoReward(parent, counterpart) +end +function modifier_legion_commander_duel_custom.prototype.OnDestroy(self) + if not IsServer() then + return + end + local parent = self:GetParent() + duelDebug( + nil, + "modifier_duel.OnDestroy parent=" .. tostring(parent:entindex()) + ) + local ____opt_30 = parent.SetForceAttackTarget + if ____opt_30 ~= nil then + ____opt_30(parent, nil) + end +end +modifier_legion_commander_duel_custom = __TS__Decorate( + modifier_legion_commander_duel_custom, + modifier_legion_commander_duel_custom, + {registerModifier(nil)}, + {kind = "class", name = "modifier_legion_commander_duel_custom"} +) +____exports.modifier_legion_commander_duel_custom = modifier_legion_commander_duel_custom +return ____exports diff --git a/scripts/vscripts/abilities/heroes/legion_commander/ability_legion_commander_moment_of_courage_custom.lua b/scripts/vscripts/abilities/heroes/legion_commander/ability_legion_commander_moment_of_courage_custom.lua new file mode 100644 index 0000000..281c146 --- /dev/null +++ b/scripts/vscripts/abilities/heroes/legion_commander/ability_legion_commander_moment_of_courage_custom.lua @@ -0,0 +1,180 @@ +local ____lualib = require("lualib_bundle") +local __TS__Class = ____lualib.__TS__Class +local __TS__ClassExtends = ____lualib.__TS__ClassExtends +local __TS__Decorate = ____lualib.__TS__Decorate +local ____exports = {} +local ____dota_ts_adapter = require("lib.dota_ts_adapter") +local BaseAbility = ____dota_ts_adapter.BaseAbility +local BaseModifier = ____dota_ts_adapter.BaseModifier +local registerAbility = ____dota_ts_adapter.registerAbility +local registerModifier = ____dota_ts_adapter.registerModifier +local ____luck = require("utils.luck") +local calculateLuckChance = ____luck.calculateLuckChance +____exports.ability_legion_commander_moment_of_courage_custom = __TS__Class() +local ability_legion_commander_moment_of_courage_custom = ____exports.ability_legion_commander_moment_of_courage_custom +ability_legion_commander_moment_of_courage_custom.name = "ability_legion_commander_moment_of_courage_custom" +ability_legion_commander_moment_of_courage_custom.____file_path = "scripts/vscripts/abilities/heroes/legion_commander/ability_legion_commander_moment_of_courage_custom.lua" +__TS__ClassExtends(ability_legion_commander_moment_of_courage_custom, BaseAbility) +function ability_legion_commander_moment_of_courage_custom.prototype.Precache(self, context) + PrecacheResource("particle", "particles/units/heroes/hero_legion_commander/legion_commander_courage_cnt.vpcf", context) + PrecacheResource("soundfile", "soundevents/game_sounds_heroes/game_sounds_legion_commander.vsndevts", context) +end +function ability_legion_commander_moment_of_courage_custom.prototype.GetIntrinsicModifierName(self) + return ____exports.modifier_legion_commander_moment_of_courage_intrinsic.name +end +ability_legion_commander_moment_of_courage_custom = __TS__Decorate( + ability_legion_commander_moment_of_courage_custom, + ability_legion_commander_moment_of_courage_custom, + {registerAbility(nil)}, + {kind = "class", name = "ability_legion_commander_moment_of_courage_custom"} +) +____exports.ability_legion_commander_moment_of_courage_custom = ability_legion_commander_moment_of_courage_custom +____exports.modifier_legion_commander_moment_of_courage_intrinsic = __TS__Class() +local modifier_legion_commander_moment_of_courage_intrinsic = ____exports.modifier_legion_commander_moment_of_courage_intrinsic +modifier_legion_commander_moment_of_courage_intrinsic.name = "modifier_legion_commander_moment_of_courage_intrinsic" +modifier_legion_commander_moment_of_courage_intrinsic.____file_path = "scripts/vscripts/abilities/heroes/legion_commander/ability_legion_commander_moment_of_courage_custom.lua" +__TS__ClassExtends(modifier_legion_commander_moment_of_courage_intrinsic, BaseModifier) +function modifier_legion_commander_moment_of_courage_intrinsic.prototype.____constructor(self, ...) + BaseModifier.prototype.____constructor(self, ...) + self.lastProcGameTime = -999 +end +function modifier_legion_commander_moment_of_courage_intrinsic.prototype.IsHidden(self) + return true +end +function modifier_legion_commander_moment_of_courage_intrinsic.prototype.IsPurgable(self) + return false +end +function modifier_legion_commander_moment_of_courage_intrinsic.prototype.DeclareFunctions(self) + return {MODIFIER_EVENT_ON_ATTACK_LANDED} +end +function modifier_legion_commander_moment_of_courage_intrinsic.prototype.OnAttackLanded(self, event) + if not IsServer() then + return + end + local parent = self:GetParent() + local ability = self:GetAbility() + if parent:PassivesDisabled() or not ability then + return + end + if event.target ~= parent then + return + end + local attacker = event.attacker + if not attacker or attacker == parent or not attacker:IsAlive() then + return + end + if attacker:GetTeamNumber() == parent:GetTeamNumber() then + return + end + local icd = ability:GetSpecialValueFor("proc_cooldown") + local now = GameRules:GetGameTime() + if now < self.lastProcGameTime + icd then + return + end + local basePct = ability:GetSpecialValueFor("trigger_chance_pct") + --- Слот псевдослучайности движка (не выносим в KV — не показывать игроку в тултипе). + local prdSlot = 27 + local pid = parent:GetPlayerOwnerID() + local isPlayerHero = pid >= 0 and parent:IsRealHero() + local rolled = false + if isPlayerHero then + local luckPct = math.floor(calculateLuckChance(nil, parent, basePct / 100) * 100) + local clamped = math.min( + 95, + math.max(5, luckPct) + ) + rolled = RollPseudoRandomPercentage(clamped, prdSlot, parent) + else + rolled = RollPercentage(basePct) + end + if not rolled then + return + end + self.lastProcGameTime = now + self:procCounter(ability, attacker) +end +function modifier_legion_commander_moment_of_courage_intrinsic.prototype.procCounter(self, ability, mainTarget) + local parent = self:GetParent() + local lsPct = ability:GetSpecialValueFor("counter_lifesteal_pct") + local swings = math.max( + 1, + math.floor(ability:GetSpecialValueFor("attack_count")) + ) + local buffDur = ability:GetSpecialValueFor("buff_duration") + local buffMs = ability:GetSpecialValueFor("buff_bonus_movespeed") + local pfx = ParticleManager:CreateParticle("particles/units/heroes/hero_legion_commander/legion_commander_courage_cnt.vpcf", PATTACH_ABSORIGIN_FOLLOW, parent) + ParticleManager:ReleaseParticleIndex(pfx) + EmitSoundOn("Hero_LegionCommander.MomentOfCourage", parent) + EmitSoundOn("Hero_LegionCommander.Courage", parent) + parent:StartGestureWithPlaybackRate(ACT_DOTA_ATTACK, 2) + parent:AddNewModifier(parent, ability, ____exports.modifier_legion_commander_moment_proc_haste.name, {duration = buffDur, ms = buffMs}) + do + local i = 0 + while i < swings do + parent:PerformAttack( + mainTarget, + true, + true, + true, + false, + false, + false, + false + ) + i = i + 1 + end + end + local estDamage = math.max( + 1, + parent:GetAverageTrueAttackDamage(mainTarget) + ) + local heal = estDamage * swings * lsPct / 100 + if heal > 0 and parent:IsAlive() then + parent:Heal(heal, ability) + end +end +modifier_legion_commander_moment_of_courage_intrinsic = __TS__Decorate( + modifier_legion_commander_moment_of_courage_intrinsic, + modifier_legion_commander_moment_of_courage_intrinsic, + {registerModifier(nil)}, + {kind = "class", name = "modifier_legion_commander_moment_of_courage_intrinsic"} +) +____exports.modifier_legion_commander_moment_of_courage_intrinsic = modifier_legion_commander_moment_of_courage_intrinsic +____exports.modifier_legion_commander_moment_proc_haste = __TS__Class() +local modifier_legion_commander_moment_proc_haste = ____exports.modifier_legion_commander_moment_proc_haste +modifier_legion_commander_moment_proc_haste.name = "modifier_legion_commander_moment_proc_haste" +modifier_legion_commander_moment_proc_haste.____file_path = "scripts/vscripts/abilities/heroes/legion_commander/ability_legion_commander_moment_of_courage_custom.lua" +__TS__ClassExtends(modifier_legion_commander_moment_proc_haste, BaseModifier) +function modifier_legion_commander_moment_proc_haste.prototype.____constructor(self, ...) + BaseModifier.prototype.____constructor(self, ...) + self.ms = 0 +end +function modifier_legion_commander_moment_proc_haste.prototype.IsHidden(self) + return false +end +function modifier_legion_commander_moment_proc_haste.prototype.IsPurgable(self) + return true +end +function modifier_legion_commander_moment_proc_haste.prototype.IsDebuff(self) + return false +end +function modifier_legion_commander_moment_proc_haste.prototype.OnCreated(self, params) + self.ms = params.ms or 0 +end +function modifier_legion_commander_moment_proc_haste.prototype.DeclareFunctions(self) + return {MODIFIER_PROPERTY_MOVESPEED_BONUS_CONSTANT} +end +function modifier_legion_commander_moment_proc_haste.prototype.GetModifierMoveSpeedBonus_Constant(self) + return self.ms +end +function modifier_legion_commander_moment_proc_haste.prototype.GetTexture(self) + return "legion_commander_moment_of_courage" +end +modifier_legion_commander_moment_proc_haste = __TS__Decorate( + modifier_legion_commander_moment_proc_haste, + modifier_legion_commander_moment_proc_haste, + {registerModifier(nil)}, + {kind = "class", name = "modifier_legion_commander_moment_proc_haste"} +) +____exports.modifier_legion_commander_moment_proc_haste = modifier_legion_commander_moment_proc_haste +return ____exports diff --git a/scripts/vscripts/abilities/heroes/legion_commander/ability_legion_commander_overwhelming_odds_custom.lua b/scripts/vscripts/abilities/heroes/legion_commander/ability_legion_commander_overwhelming_odds_custom.lua new file mode 100644 index 0000000..e4ca5b2 --- /dev/null +++ b/scripts/vscripts/abilities/heroes/legion_commander/ability_legion_commander_overwhelming_odds_custom.lua @@ -0,0 +1,307 @@ +local ____lualib = require("lualib_bundle") +local __TS__Class = ____lualib.__TS__Class +local __TS__ClassExtends = ____lualib.__TS__ClassExtends +local __TS__Decorate = ____lualib.__TS__Decorate +local ____exports = {} +local ____dota_ts_adapter = require("lib.dota_ts_adapter") +local BaseAbility = ____dota_ts_adapter.BaseAbility +local BaseModifier = ____dota_ts_adapter.BaseModifier +local registerAbility = ____dota_ts_adapter.registerAbility +local registerModifier = ____dota_ts_adapter.registerModifier +____exports.ability_legion_commander_overwhelming_odds_custom = __TS__Class() +local ability_legion_commander_overwhelming_odds_custom = ____exports.ability_legion_commander_overwhelming_odds_custom +ability_legion_commander_overwhelming_odds_custom.name = "ability_legion_commander_overwhelming_odds_custom" +ability_legion_commander_overwhelming_odds_custom.____file_path = "scripts/vscripts/abilities/heroes/legion_commander/ability_legion_commander_overwhelming_odds_custom.lua" +__TS__ClassExtends(ability_legion_commander_overwhelming_odds_custom, BaseAbility) +function ability_legion_commander_overwhelming_odds_custom.prototype.GetAOERadius(self) + return self:GetSpecialValueFor("radius") +end +function ability_legion_commander_overwhelming_odds_custom.prototype.Precache(self, context) + PrecacheResource("particle", "particles/units/heroes/hero_legion_commander/legion_commander_odds_cast.vpcf", context) + PrecacheResource("particle", "particles/units/heroes/hero_legion_commander/legion_commander_odds.vpcf", context) + PrecacheResource("particle", "particles/units/heroes/hero_legion_commander/legion_commander_odds_dmga.vpcf", context) + PrecacheResource("particle", "particles/units/heroes/hero_legion_commander/legion_commander_odds_buff.vpcf", context) + PrecacheResource("soundfile", "soundevents/game_sounds_heroes/game_sounds_legion_commander.vsndevts", context) +end +function ability_legion_commander_overwhelming_odds_custom.prototype.OnAbilityPhaseStart(self) + if not IsServer() then + return true + end + local caster = self:GetCaster() + if not caster then + return true + end + local p = ParticleManager:CreateParticle("particles/units/heroes/hero_legion_commander/legion_commander_odds_cast.vpcf", PATTACH_CUSTOMORIGIN, caster) + ParticleManager:SetParticleControlEnt( + p, + 1, + caster, + PATTACH_POINT_FOLLOW, + "attach_attack1", + caster:GetAbsOrigin(), + true + ) + ParticleManager:ReleaseParticleIndex(p) + EmitSoundOn("Hero_LegionCommander.Overwhelming.Cast", caster) + return true +end +function ability_legion_commander_overwhelming_odds_custom.prototype.OnSpellStart(self) + if not IsServer() then + return + end + self:ExecuteBurstAt(self:GetCursorPosition()) +end +function ability_legion_commander_overwhelming_odds_custom.prototype.ExecuteBurstAt(self, groundPoint) + if not IsServer() then + return + end + local caster = self:GetCaster() + if not caster or not caster:IsAlive() then + return + end + local point = GetGroundPosition(groundPoint, nil) + local radius = self:GetSpecialValueFor("radius") + local dmgBase = self:GetSpecialValueFor("damage_base") + local dmgPerEnemy = self:GetSpecialValueFor("damage_per_enemy") + local armorDur = self:GetSpecialValueFor("armor_buff_duration") + local debuffDurBase = self:GetSpecialValueFor("debuff_duration") + local slowPct = self:GetSpecialValueFor("enemy_movespeed_slow") + local selfDur = self:GetSpecialValueFor("self_buff_duration") + local selfAs = self:GetSpecialValueFor("self_bonus_attack_speed") + local selfMs = self:GetSpecialValueFor("self_bonus_movespeed") + EmitSoundOnLocationWithCaster(point, "Hero_LegionCommander.Overwhelming.Location", caster) + local pFx = ParticleManager:CreateParticle("particles/units/heroes/hero_legion_commander/legion_commander_odds.vpcf", PATTACH_WORLDORIGIN, nil) + ParticleManager:SetParticleControl(pFx, 0, point) + ParticleManager:SetParticleControl( + pFx, + 1, + caster:GetAbsOrigin() + ) + ParticleManager:SetParticleControl(pFx, 2, point) + ParticleManager:SetParticleControl(pFx, 3, point) + ParticleManager:SetParticleControl( + pFx, + 4, + Vector(radius, radius, radius) + ) + ParticleManager:SetParticleControl(pFx, 6, point) + ParticleManager:ReleaseParticleIndex(pFx) + local enemies = FindUnitsInRadius( + caster:GetTeamNumber(), + point, + nil, + radius, + DOTA_UNIT_TARGET_TEAM_ENEMY, + bit.bor(DOTA_UNIT_TARGET_HERO, DOTA_UNIT_TARGET_BASIC), + DOTA_UNIT_TARGET_FLAG_NONE, + FIND_ANY_ORDER, + false + ) + local counted = 0 + for ____, u in ipairs(enemies) do + do + if not u or not u:IsAlive() or u:IsCourier() then + goto __continue12 + end + counted = counted + 1 + end + ::__continue12:: + end + local totalDamage = dmgBase + dmgPerEnemy * counted + for ____, enemy in ipairs(enemies) do + do + if not enemy or not enemy:IsAlive() or enemy:IsCourier() then + goto __continue15 + end + local pHit = ParticleManager:CreateParticle("particles/units/heroes/hero_legion_commander/legion_commander_odds_dmga.vpcf", PATTACH_ABSORIGIN_FOLLOW, enemy) + ParticleManager:SetParticleControlEnt( + pHit, + 0, + enemy, + PATTACH_POINT_FOLLOW, + "attach_hitloc", + enemy:GetAbsOrigin(), + true + ) + ParticleManager:SetParticleControlEnt( + pHit, + 1, + enemy, + PATTACH_POINT_FOLLOW, + "attach_hitloc", + enemy:GetAbsOrigin(), + true + ) + ParticleManager:SetParticleControlEnt( + pHit, + 3, + caster, + PATTACH_POINT_FOLLOW, + "attach_hitloc", + caster:GetAbsOrigin(), + true + ) + ParticleManager:ReleaseParticleIndex(pHit) + EmitSoundOn("Hero_LegionCommander.Overwhelming.Creep", enemy) + ApplyDamage({ + victim = enemy, + attacker = caster, + damage = totalDamage, + damage_type = DAMAGE_TYPE_MAGICAL, + ability = self + }) + local durSlow = debuffDurBase * (1 - enemy:GetStatusResistance()) + enemy:AddNewModifier(caster, self, ____exports.modifier_legion_commander_overwhelming_slow.name, {duration = durSlow, slow = slowPct}) + end + ::__continue15:: + end + if counted > 0 then + caster:RemoveModifierByName(____exports.modifier_legion_commander_overwhelming_armor.name) + local mod = caster:AddNewModifier(caster, self, ____exports.modifier_legion_commander_overwhelming_armor.name, {duration = armorDur}) + if mod ~= nil and mod ~= nil then + mod:SetStackCount(math.min(counted, 20)) + end + end + caster:AddNewModifier(caster, self, ____exports.modifier_legion_commander_overwhelming_self.name, {duration = selfDur, as = selfAs, ms = selfMs}) +end +function ability_legion_commander_overwhelming_odds_custom.tryAutoCastAfterDuelCast(self, caster) + if not caster or not caster:IsRealHero() or not caster:IsAlive() then + return + end + local odds = caster:FindAbilityByName("ability_legion_commander_overwhelming_odds_custom") + if not odds or odds:GetLevel() <= 0 then + return + end + if not odds:IsCooldownReady() then + return + end + local idx = odds:GetLevel() - 1 + local manaNeed = odds:GetManaCost(math.max(0, idx)) + if caster:GetMana() < manaNeed then + return + end + local at = caster:GetAbsOrigin() + odds:ExecuteBurstAt(GetGroundPosition(at, caster)) + odds:UseResources(true, false, false, true) +end +ability_legion_commander_overwhelming_odds_custom = __TS__Decorate( + ability_legion_commander_overwhelming_odds_custom, + ability_legion_commander_overwhelming_odds_custom, + {registerAbility(nil)}, + {kind = "class", name = "ability_legion_commander_overwhelming_odds_custom"} +) +____exports.ability_legion_commander_overwhelming_odds_custom = ability_legion_commander_overwhelming_odds_custom +____exports.modifier_legion_commander_overwhelming_slow = __TS__Class() +local modifier_legion_commander_overwhelming_slow = ____exports.modifier_legion_commander_overwhelming_slow +modifier_legion_commander_overwhelming_slow.name = "modifier_legion_commander_overwhelming_slow" +modifier_legion_commander_overwhelming_slow.____file_path = "scripts/vscripts/abilities/heroes/legion_commander/ability_legion_commander_overwhelming_odds_custom.lua" +__TS__ClassExtends(modifier_legion_commander_overwhelming_slow, BaseModifier) +function modifier_legion_commander_overwhelming_slow.prototype.IsHidden(self) + return false +end +function modifier_legion_commander_overwhelming_slow.prototype.IsDebuff(self) + return true +end +function modifier_legion_commander_overwhelming_slow.prototype.IsPurgable(self) + return true +end +function modifier_legion_commander_overwhelming_slow.prototype.OnCreated(self, params) + self.slowPct = params.slow or 40 +end +function modifier_legion_commander_overwhelming_slow.prototype.DeclareFunctions(self) + return {MODIFIER_PROPERTY_MOVESPEED_BONUS_PERCENTAGE} +end +function modifier_legion_commander_overwhelming_slow.prototype.GetModifierMoveSpeedBonus_Percentage(self) + return -math.abs(self.slowPct) +end +function modifier_legion_commander_overwhelming_slow.prototype.GetStatusEffectName(self) + return "particles/status_fx/status_effect_snapfire_slow.vpcf" +end +function modifier_legion_commander_overwhelming_slow.prototype.GetTexture(self) + return "legion_commander_overwhelming_odds" +end +modifier_legion_commander_overwhelming_slow = __TS__Decorate( + modifier_legion_commander_overwhelming_slow, + modifier_legion_commander_overwhelming_slow, + {registerModifier(nil)}, + {kind = "class", name = "modifier_legion_commander_overwhelming_slow"} +) +____exports.modifier_legion_commander_overwhelming_slow = modifier_legion_commander_overwhelming_slow +____exports.modifier_legion_commander_overwhelming_self = __TS__Class() +local modifier_legion_commander_overwhelming_self = ____exports.modifier_legion_commander_overwhelming_self +modifier_legion_commander_overwhelming_self.name = "modifier_legion_commander_overwhelming_self" +modifier_legion_commander_overwhelming_self.____file_path = "scripts/vscripts/abilities/heroes/legion_commander/ability_legion_commander_overwhelming_odds_custom.lua" +__TS__ClassExtends(modifier_legion_commander_overwhelming_self, BaseModifier) +function modifier_legion_commander_overwhelming_self.prototype.____constructor(self, ...) + BaseModifier.prototype.____constructor(self, ...) + self.as = 0 + self.ms = 0 +end +function modifier_legion_commander_overwhelming_self.prototype.IsHidden(self) + return false +end +function modifier_legion_commander_overwhelming_self.prototype.IsPurgable(self) + return true +end +function modifier_legion_commander_overwhelming_self.prototype.IsDebuff(self) + return false +end +function modifier_legion_commander_overwhelming_self.prototype.OnCreated(self, params) + self.as = params.as or 0 + self.ms = params.ms or 0 + if not IsServer() then + return + end + local caster = self:GetParent() + local pBuff = ParticleManager:CreateParticle("particles/units/heroes/hero_legion_commander/legion_commander_odds_buff.vpcf", PATTACH_ABSORIGIN_FOLLOW, caster) + ParticleManager:ReleaseParticleIndex(pBuff) +end +function modifier_legion_commander_overwhelming_self.prototype.DeclareFunctions(self) + return {MODIFIER_PROPERTY_ATTACKSPEED_BONUS_CONSTANT, MODIFIER_PROPERTY_MOVESPEED_BONUS_CONSTANT} +end +function modifier_legion_commander_overwhelming_self.prototype.GetModifierAttackSpeedBonus_Constant(self) + return self.as +end +function modifier_legion_commander_overwhelming_self.prototype.GetModifierMoveSpeedBonus_Constant(self) + return self.ms +end +function modifier_legion_commander_overwhelming_self.prototype.GetTexture(self) + return "legion_commander_overwhelming_odds" +end +modifier_legion_commander_overwhelming_self = __TS__Decorate( + modifier_legion_commander_overwhelming_self, + modifier_legion_commander_overwhelming_self, + {registerModifier(nil)}, + {kind = "class", name = "modifier_legion_commander_overwhelming_self"} +) +____exports.modifier_legion_commander_overwhelming_self = modifier_legion_commander_overwhelming_self +____exports.modifier_legion_commander_overwhelming_armor = __TS__Class() +local modifier_legion_commander_overwhelming_armor = ____exports.modifier_legion_commander_overwhelming_armor +modifier_legion_commander_overwhelming_armor.name = "modifier_legion_commander_overwhelming_armor" +modifier_legion_commander_overwhelming_armor.____file_path = "scripts/vscripts/abilities/heroes/legion_commander/ability_legion_commander_overwhelming_odds_custom.lua" +__TS__ClassExtends(modifier_legion_commander_overwhelming_armor, BaseModifier) +function modifier_legion_commander_overwhelming_armor.prototype.IsHidden(self) + return false +end +function modifier_legion_commander_overwhelming_armor.prototype.IsPurgable(self) + return true +end +function modifier_legion_commander_overwhelming_armor.prototype.DeclareFunctions(self) + return {MODIFIER_PROPERTY_PHYSICAL_ARMOR_BONUS} +end +function modifier_legion_commander_overwhelming_armor.prototype.GetModifierPhysicalArmorBonus(self) + local ab = self:GetAbility() + local per = ab and ab:GetSpecialValueFor("armor_per_enemy") or 0 + return per * self:GetStackCount() +end +function modifier_legion_commander_overwhelming_armor.prototype.GetTexture(self) + return "legion_commander_overwhelming_odds" +end +modifier_legion_commander_overwhelming_armor = __TS__Decorate( + modifier_legion_commander_overwhelming_armor, + modifier_legion_commander_overwhelming_armor, + {registerModifier(nil)}, + {kind = "class", name = "modifier_legion_commander_overwhelming_armor"} +) +____exports.modifier_legion_commander_overwhelming_armor = modifier_legion_commander_overwhelming_armor +return ____exports diff --git a/scripts/vscripts/abilities/heroes/legion_commander/ability_legion_commander_press_the_attack_custom.lua b/scripts/vscripts/abilities/heroes/legion_commander/ability_legion_commander_press_the_attack_custom.lua new file mode 100644 index 0000000..0edb69b --- /dev/null +++ b/scripts/vscripts/abilities/heroes/legion_commander/ability_legion_commander_press_the_attack_custom.lua @@ -0,0 +1,105 @@ +local ____lualib = require("lualib_bundle") +local __TS__Class = ____lualib.__TS__Class +local __TS__ClassExtends = ____lualib.__TS__ClassExtends +local __TS__Decorate = ____lualib.__TS__Decorate +local ____exports = {} +local ____dota_ts_adapter = require("lib.dota_ts_adapter") +local BaseAbility = ____dota_ts_adapter.BaseAbility +local BaseModifier = ____dota_ts_adapter.BaseModifier +local registerAbility = ____dota_ts_adapter.registerAbility +local registerModifier = ____dota_ts_adapter.registerModifier +____exports.ability_legion_commander_press_the_attack_custom = __TS__Class() +local ability_legion_commander_press_the_attack_custom = ____exports.ability_legion_commander_press_the_attack_custom +ability_legion_commander_press_the_attack_custom.name = "ability_legion_commander_press_the_attack_custom" +ability_legion_commander_press_the_attack_custom.____file_path = "scripts/vscripts/abilities/heroes/legion_commander/ability_legion_commander_press_the_attack_custom.lua" +__TS__ClassExtends(ability_legion_commander_press_the_attack_custom, BaseAbility) +function ability_legion_commander_press_the_attack_custom.prototype.Precache(self, context) + PrecacheResource("particle", "particles/units/heroes/hero_legion_commander/legion_commander_press_halo.vpcf", context) + PrecacheResource("particle", "particles/units/heroes/hero_legion_commander/legion_commander_press.vpcf", context) + PrecacheResource("particle", "particles/units/heroes/hero_legion_commander/legion_commander_press_a.vpcf", context) + PrecacheResource("soundfile", "soundevents/game_sounds_heroes/game_sounds_legion_commander.vsndevts", context) +end +function ability_legion_commander_press_the_attack_custom.prototype.OnSpellStart(self) + if not IsServer() then + return + end + local caster = self:GetCaster() + local target = self:GetCursorTarget() + if not caster or not caster:IsAlive() then + return + end + if not target or not target:IsAlive() then + target = caster + end + if not target:IsHero() then + return + end + if target:GetTeamNumber() ~= caster:GetTeamNumber() then + return + end + target:Purge( + false, + true, + false, + true, + false + ) + local duration = self:GetSpecialValueFor("duration") + target:AddNewModifier(caster, self, ____exports.modifier_legion_commander_press_the_attack_buff.name, {duration = duration}) + local halo = ParticleManager:CreateParticle("particles/units/heroes/hero_legion_commander/legion_commander_press_halo.vpcf", PATTACH_ABSORIGIN_FOLLOW, caster) + ParticleManager:ReleaseParticleIndex(halo) + EmitSoundOn("Hero_LegionCommander.PressTheAttack", target) +end +ability_legion_commander_press_the_attack_custom = __TS__Decorate( + ability_legion_commander_press_the_attack_custom, + ability_legion_commander_press_the_attack_custom, + {registerAbility(nil)}, + {kind = "class", name = "ability_legion_commander_press_the_attack_custom"} +) +____exports.ability_legion_commander_press_the_attack_custom = ability_legion_commander_press_the_attack_custom +____exports.modifier_legion_commander_press_the_attack_buff = __TS__Class() +local modifier_legion_commander_press_the_attack_buff = ____exports.modifier_legion_commander_press_the_attack_buff +modifier_legion_commander_press_the_attack_buff.name = "modifier_legion_commander_press_the_attack_buff" +modifier_legion_commander_press_the_attack_buff.____file_path = "scripts/vscripts/abilities/heroes/legion_commander/ability_legion_commander_press_the_attack_custom.lua" +__TS__ClassExtends(modifier_legion_commander_press_the_attack_buff, BaseModifier) +function modifier_legion_commander_press_the_attack_buff.prototype.IsHidden(self) + return false +end +function modifier_legion_commander_press_the_attack_buff.prototype.IsDebuff(self) + return false +end +function modifier_legion_commander_press_the_attack_buff.prototype.IsPurgable(self) + return true +end +function modifier_legion_commander_press_the_attack_buff.prototype.DeclareFunctions(self) + return {MODIFIER_PROPERTY_ATTACKSPEED_BONUS_CONSTANT, MODIFIER_PROPERTY_HEALTH_REGEN_CONSTANT, MODIFIER_PROPERTY_MOVESPEED_BONUS_CONSTANT} +end +function modifier_legion_commander_press_the_attack_buff.prototype.GetModifierAttackSpeedBonus_Constant(self) + local ab = self:GetAbility() + return ab and ab:GetSpecialValueFor("attack_speed_bonus") or 0 +end +function modifier_legion_commander_press_the_attack_buff.prototype.GetModifierConstantHealthRegen(self) + local ab = self:GetAbility() + return ab and ab:GetSpecialValueFor("hp_regen") or 0 +end +function modifier_legion_commander_press_the_attack_buff.prototype.GetModifierMoveSpeedBonus_Constant(self) + local ab = self:GetAbility() + return ab and ab:GetSpecialValueFor("movespeed_bonus") or 0 +end +function modifier_legion_commander_press_the_attack_buff.prototype.GetEffectName(self) + return "particles/units/heroes/hero_legion_commander/legion_commander_press.vpcf" +end +function modifier_legion_commander_press_the_attack_buff.prototype.GetEffectAttachType(self) + return PATTACH_ABSORIGIN_FOLLOW +end +function modifier_legion_commander_press_the_attack_buff.prototype.GetTexture(self) + return "legion_commander_press_the_attack" +end +modifier_legion_commander_press_the_attack_buff = __TS__Decorate( + modifier_legion_commander_press_the_attack_buff, + modifier_legion_commander_press_the_attack_buff, + {registerModifier(nil)}, + {kind = "class", name = "modifier_legion_commander_press_the_attack_buff"} +) +____exports.modifier_legion_commander_press_the_attack_buff = modifier_legion_commander_press_the_attack_buff +return ____exports diff --git a/scripts/vscripts/abilities/heroes/lina/ability_lina_dragon_slave_custom.lua b/scripts/vscripts/abilities/heroes/lina/ability_lina_dragon_slave_custom.lua new file mode 100644 index 0000000..6b42a1a --- /dev/null +++ b/scripts/vscripts/abilities/heroes/lina/ability_lina_dragon_slave_custom.lua @@ -0,0 +1,134 @@ +local ____lualib = require("lualib_bundle") +local __TS__Class = ____lualib.__TS__Class +local __TS__ClassExtends = ____lualib.__TS__ClassExtends +local __TS__Decorate = ____lualib.__TS__Decorate +local ____exports = {} +local ____dota_ts_adapter = require("lib.dota_ts_adapter") +local BaseAbility = ____dota_ts_adapter.BaseAbility +local BaseModifier = ____dota_ts_adapter.BaseModifier +local registerAbility = ____dota_ts_adapter.registerAbility +local registerModifier = ____dota_ts_adapter.registerModifier +local ____modifier_general_fired = require("abilities.modifiers.modifier_general_fired") +local modifier_general_fired = ____modifier_general_fired.modifier_general_fired +local ____lina_mana_bonus = require("abilities.heroes.lina.lina_mana_bonus") +local getLinaManaFlatDamage = ____lina_mana_bonus.getLinaManaFlatDamage +____exports.ability_lina_dragon_slave_custom = __TS__Class() +local ability_lina_dragon_slave_custom = ____exports.ability_lina_dragon_slave_custom +ability_lina_dragon_slave_custom.name = "ability_lina_dragon_slave_custom" +ability_lina_dragon_slave_custom.____file_path = "scripts/vscripts/abilities/heroes/lina/ability_lina_dragon_slave_custom.lua" +__TS__ClassExtends(ability_lina_dragon_slave_custom, BaseAbility) +function ability_lina_dragon_slave_custom.prototype.GetIntrinsicModifierName(self) + return "modifier_lina_dragon_slave_custom_passive" +end +function ability_lina_dragon_slave_custom.prototype.OnSpellStart(self) + local caster = self:GetCaster() + local point = self:GetCursorPosition() + local toPoint = point - caster:GetAbsOrigin() + local direction = toPoint:Length2D() < 1 and caster:GetForwardVector() or toPoint:Normalized() + local speed = self:GetSpecialValueFor("dragon_slave_speed") + local width_initial = self:GetSpecialValueFor("dragon_slave_width_initial") + local width_end = self:GetSpecialValueFor("dragon_slave_width_end") + local distance = self:GetSpecialValueFor("dragon_slave_distance") + ProjectileManager:CreateLinearProjectile({ + Ability = self, + EffectName = "particles/units/heroes/hero_lina/lina_spell_dragon_slave.vpcf", + vSpawnOrigin = caster:GetAbsOrigin(), + fDistance = distance, + fStartRadius = width_initial, + fEndRadius = width_end, + Source = caster, + bHasFrontalCone = true, + iUnitTargetTeam = DOTA_UNIT_TARGET_TEAM_ENEMY, + iUnitTargetType = DOTA_UNIT_TARGET_HERO + DOTA_UNIT_TARGET_BASIC, + vVelocity = direction * speed, + bProvidesVision = true, + iVisionRadius = 150, + iVisionTeamNumber = caster:GetTeamNumber() + }) + EmitSoundOn("Hero_Lina.DragonSlave", caster) +end +function ability_lina_dragon_slave_custom.prototype.OnProjectileHit(self, target, location) + if not target then + return false + end + if IsServer() then + local caster = self:GetCaster() + local damage = self:GetSpecialValueFor("dragon_slave_damage") + caster:GetAttackDamage() + getLinaManaFlatDamage(nil, self, caster) + ApplyDamage({ + victim = target, + attacker = self:GetCaster(), + damage = damage, + damage_type = self:GetAbilityDamageType(), + ability = self + }) + local modifier = target:AddNewModifier( + self:GetCaster(), + self, + modifier_general_fired.name, + {} + ) + if modifier then + local stacksPerLevel = self:GetSpecialValueFor("fire_stacks_per_level") + do + local i = 0 + while i < stacksPerLevel do + modifier:IncrementStackCount() + i = i + 1 + end + end + end + local particleHit = ParticleManager:CreateParticle("particles/units/heroes/hero_lina/lina_spell_dragon_slave_impact.vpcf", PATTACH_ABSORIGIN, target) + ParticleManager:ReleaseParticleIndex(particleHit) + end + return false +end +ability_lina_dragon_slave_custom = __TS__Decorate( + ability_lina_dragon_slave_custom, + ability_lina_dragon_slave_custom, + {registerAbility(nil)}, + {kind = "class", name = "ability_lina_dragon_slave_custom"} +) +____exports.ability_lina_dragon_slave_custom = ability_lina_dragon_slave_custom +____exports.modifier_lina_dragon_slave_custom_passive = __TS__Class() +local modifier_lina_dragon_slave_custom_passive = ____exports.modifier_lina_dragon_slave_custom_passive +modifier_lina_dragon_slave_custom_passive.name = "modifier_lina_dragon_slave_custom_passive" +modifier_lina_dragon_slave_custom_passive.____file_path = "scripts/vscripts/abilities/heroes/lina/ability_lina_dragon_slave_custom.lua" +__TS__ClassExtends(modifier_lina_dragon_slave_custom_passive, BaseModifier) +function modifier_lina_dragon_slave_custom_passive.prototype.DeclareFunctions(self) + return {MODIFIER_EVENT_ON_ATTACK_LANDED} +end +function modifier_lina_dragon_slave_custom_passive.prototype.IsHidden(self) + return true +end +function modifier_lina_dragon_slave_custom_passive.prototype.IsPurgable(self) + return false +end +function modifier_lina_dragon_slave_custom_passive.prototype.OnAttackLanded(self, event) + if IsServer() then + if self:GetParent():PassivesDisabled() then + return + end + local ____temp_3 = event.attacker == self:GetParent() + if ____temp_3 then + local ____RandomInt_result_2 = RandomInt(1, 100) + local ____opt_0 = self:GetAbility() + ____temp_3 = ____RandomInt_result_2 <= (____opt_0 and ____opt_0:GetSpecialValueFor("proc_chance")) + end + if ____temp_3 then + local ability = self:GetParent():FindAbilityByName("ability_lina_dragon_slave_custom") + if ability and event.target:IsAlive() and not event.attacker:IsIllusion() then + local targetPosition = event.target:GetAbsOrigin() + ability:GetCaster():SetCursorPosition(targetPosition) + ability:OnSpellStart() + end + end + end +end +modifier_lina_dragon_slave_custom_passive = __TS__Decorate( + modifier_lina_dragon_slave_custom_passive, + modifier_lina_dragon_slave_custom_passive, + {registerModifier(nil)}, + {kind = "class", name = "modifier_lina_dragon_slave_custom_passive"} +) +____exports.modifier_lina_dragon_slave_custom_passive = modifier_lina_dragon_slave_custom_passive +return ____exports diff --git a/scripts/vscripts/abilities/heroes/lina/ability_lina_flame_cloak_custom.lua b/scripts/vscripts/abilities/heroes/lina/ability_lina_flame_cloak_custom.lua new file mode 100644 index 0000000..cff7f55 --- /dev/null +++ b/scripts/vscripts/abilities/heroes/lina/ability_lina_flame_cloak_custom.lua @@ -0,0 +1,290 @@ +local ____lualib = require("lualib_bundle") +local __TS__Class = ____lualib.__TS__Class +local __TS__ClassExtends = ____lualib.__TS__ClassExtends +local __TS__Decorate = ____lualib.__TS__Decorate +local __TS__ArrayForEach = ____lualib.__TS__ArrayForEach +local ____exports = {} +local ____dota_ts_adapter = require("lib.dota_ts_adapter") +local BaseAbility = ____dota_ts_adapter.BaseAbility +local BaseModifier = ____dota_ts_adapter.BaseModifier +local registerAbility = ____dota_ts_adapter.registerAbility +local registerModifier = ____dota_ts_adapter.registerModifier +local ____modifier_general_fired = require("abilities.modifiers.modifier_general_fired") +local modifier_general_fired = ____modifier_general_fired.modifier_general_fired +local ____lina_mana_bonus = require("abilities.heroes.lina.lina_mana_bonus") +local getLinaManaFlatDamage = ____lina_mana_bonus.getLinaManaFlatDamage +____exports.ability_lina_flame_cloak_custom = __TS__Class() +local ability_lina_flame_cloak_custom = ____exports.ability_lina_flame_cloak_custom +ability_lina_flame_cloak_custom.name = "ability_lina_flame_cloak_custom" +ability_lina_flame_cloak_custom.____file_path = "scripts/vscripts/abilities/heroes/lina/ability_lina_flame_cloak_custom.lua" +__TS__ClassExtends(ability_lina_flame_cloak_custom, BaseAbility) +function ability_lina_flame_cloak_custom.prototype.GetAOERadius(self) + return self:GetSpecialValueFor("radius") +end +function ability_lina_flame_cloak_custom.prototype.OnSpellStart(self) + local caster = self:GetCaster() + self.duration = self:GetSpecialValueFor("duration") + caster:AddNewModifier(caster, self, "modifier_lina_flame_cloak_custom", {duration = self.duration}) + EmitSoundOn("Hero_Lina.FlameCloak.Cast", caster) +end +function ability_lina_flame_cloak_custom.prototype.GetIntrinsicModifierName(self) + return "modifier_lina_flame_cloak_custom_passive" +end +ability_lina_flame_cloak_custom = __TS__Decorate( + ability_lina_flame_cloak_custom, + ability_lina_flame_cloak_custom, + {registerAbility(nil)}, + {kind = "class", name = "ability_lina_flame_cloak_custom"} +) +____exports.ability_lina_flame_cloak_custom = ability_lina_flame_cloak_custom +____exports.modifier_lina_flame_cloak_custom = __TS__Class() +local modifier_lina_flame_cloak_custom = ____exports.modifier_lina_flame_cloak_custom +modifier_lina_flame_cloak_custom.name = "modifier_lina_flame_cloak_custom" +modifier_lina_flame_cloak_custom.____file_path = "scripts/vscripts/abilities/heroes/lina/ability_lina_flame_cloak_custom.lua" +__TS__ClassExtends(modifier_lina_flame_cloak_custom, BaseModifier) +function modifier_lina_flame_cloak_custom.prototype.____constructor(self, ...) + BaseModifier.prototype.____constructor(self, ...) + self.thinkInterval = 0.33 +end +function modifier_lina_flame_cloak_custom.prototype.OnCreated(self) + if IsServer() then + self:StartIntervalThink(self.thinkInterval) + local parent = self:GetParent() + local flameCloakParticle = ParticleManager:CreateParticle("particles/units/heroes/hero_lina/lina_flame_cloak.vpcf", PATTACH_ABSORIGIN_FOLLOW, parent) + ParticleManager:SetParticleControl( + flameCloakParticle, + 0, + parent:GetAbsOrigin() + ) + ParticleManager:SetParticleControlEnt( + flameCloakParticle, + 1, + parent, + PATTACH_ABSORIGIN_FOLLOW, + "attach_hitloc", + parent:GetAbsOrigin(), + true + ) + ParticleManager:SetParticleControl( + flameCloakParticle, + 3, + parent:GetAbsOrigin() + ) + ParticleManager:SetParticleControl( + flameCloakParticle, + 4, + parent:GetAbsOrigin() + ) + ParticleManager:SetParticleControl( + flameCloakParticle, + 10, + parent:GetAbsOrigin() + ) + self:AddParticle( + flameCloakParticle, + false, + false, + -1, + false, + false + ) + end +end +function modifier_lina_flame_cloak_custom.prototype.OnDestroy(self) + if IsClient() then + return + end + local parent = self:GetParent() + local currentPos = parent:GetAbsOrigin() + local groundPos = GetGroundPosition(currentPos, parent) + parent:SetAbsOrigin(groundPos) +end +function modifier_lina_flame_cloak_custom.prototype.CheckState(self) + return {[MODIFIER_STATE_FLYING] = true, [MODIFIER_STATE_NO_UNIT_COLLISION] = true} +end +function modifier_lina_flame_cloak_custom.prototype.DeclareFunctions(self) + return {MODIFIER_PROPERTY_MOVESPEED_BONUS_PERCENTAGE, MODIFIER_PROPERTY_VISUAL_Z_DELTA} +end +function modifier_lina_flame_cloak_custom.prototype.GetVisualZDelta(self) + local ____opt_0 = self:GetAbility() + return ____opt_0 and ____opt_0:GetSpecialValueFor("visualzdelta") or 0 +end +function modifier_lina_flame_cloak_custom.prototype.GetModifierMoveSpeedBonus_Percentage(self) + local ____opt_2 = self:GetAbility() + return ____opt_2 and ____opt_2:GetSpecialValueFor("movespeed_bonus") or 0 +end +function modifier_lina_flame_cloak_custom.prototype.GetEffectName(self) + return "particles/econ/items/ember_spirit/ember_ti9/ember_ti9_flameguard.vpcf" +end +function modifier_lina_flame_cloak_custom.prototype.GetEffectAttachType(self) + return PATTACH_ABSORIGIN_FOLLOW +end +function modifier_lina_flame_cloak_custom.prototype.OnIntervalThink(self) + if IsServer() then + local ability = self:GetAbility() + if not ability then + return + end + local caster = self:GetCaster() + if not caster then + return + end + local damage = ability:GetSpecialValueFor("damage_per_second") * self.thinkInterval + getLinaManaFlatDamage(nil, ability, caster) + local ____FindUnitsInRadius_8 = FindUnitsInRadius + local ____temp_6 = caster:GetTeamNumber() + local ____temp_7 = caster:GetAbsOrigin() + local ____opt_4 = self:GetAbility() + local units = ____FindUnitsInRadius_8( + ____temp_6, + ____temp_7, + nil, + ____opt_4 and ____opt_4:GetSpecialValueFor("radius"), + DOTA_UNIT_TARGET_TEAM_ENEMY, + DOTA_UNIT_TARGET_HERO + DOTA_UNIT_TARGET_BASIC, + DOTA_UNIT_TARGET_FLAG_NONE, + FIND_ANY_ORDER, + false + ) + __TS__ArrayForEach( + units, + function(____, unit) + ApplyDamage({ + victim = unit, + attacker = caster, + damage = damage, + damage_type = DAMAGE_TYPE_MAGICAL, + ability = ability + }) + local modifier = unit:AddNewModifier(caster, ability, modifier_general_fired.name, {}) + if modifier then + modifier:IncrementStackCount() + end + end + ) + end +end +modifier_lina_flame_cloak_custom = __TS__Decorate( + modifier_lina_flame_cloak_custom, + modifier_lina_flame_cloak_custom, + {registerModifier(nil)}, + {kind = "class", name = "modifier_lina_flame_cloak_custom"} +) +____exports.modifier_lina_flame_cloak_custom = modifier_lina_flame_cloak_custom +____exports.modifier_lina_flame_cloak_custom_passive = __TS__Class() +local modifier_lina_flame_cloak_custom_passive = ____exports.modifier_lina_flame_cloak_custom_passive +modifier_lina_flame_cloak_custom_passive.name = "modifier_lina_flame_cloak_custom_passive" +modifier_lina_flame_cloak_custom_passive.____file_path = "scripts/vscripts/abilities/heroes/lina/ability_lina_flame_cloak_custom.lua" +__TS__ClassExtends(modifier_lina_flame_cloak_custom_passive, BaseModifier) +function modifier_lina_flame_cloak_custom_passive.prototype.IsHidden(self) + return true +end +function modifier_lina_flame_cloak_custom_passive.prototype.IsPurgable(self) + return false +end +function modifier_lina_flame_cloak_custom_passive.prototype.DeclareFunctions(self) + return {MODIFIER_EVENT_ON_TAKEDAMAGE} +end +function modifier_lina_flame_cloak_custom_passive.prototype.OnTakeDamage(self, event) + if not IsServer() then + return + end + if self:GetParent():PassivesDisabled() then + return + end + local ability = self:GetAbility() + if not ability then + return + end + if event.inflictor == ability then + return + end + local unit = event.unit + if unit == self:GetParent() or unit:GetTeamNumber() == self:GetParent():GetTeamNumber() or self:GetParent() ~= event.attacker then + return + end + local modifier = self:GetParent():FindModifierByName("modifier_lina_flame_cloak_custom_passive_buff") + local ____modifier_12 = modifier + if ____modifier_12 then + local ____temp_11 = modifier:GetStackCount() + local ____opt_9 = self:GetAbility() + ____modifier_12 = ____temp_11 == (____opt_9 and ____opt_9:GetSpecialValueFor("max_stacks")) + end + if ____modifier_12 then + local ____self_15 = self:GetParent():AddNewModifier( + self:GetParent(), + self:GetAbility(), + "modifier_lina_flame_cloak_custom_passive_buff", + {duration = 5} + ) + local ____self_15_SetStackCount_16 = ____self_15.SetStackCount + local ____opt_13 = self:GetAbility() + ____self_15_SetStackCount_16( + ____self_15, + ____opt_13 and ____opt_13:GetSpecialValueFor("max_stacks") + ) + else + self:GetParent():AddNewModifier( + self:GetParent(), + self:GetAbility(), + "modifier_lina_flame_cloak_custom_passive_buff", + {duration = 5} + ):IncrementStackCount() + end +end +modifier_lina_flame_cloak_custom_passive = __TS__Decorate( + modifier_lina_flame_cloak_custom_passive, + modifier_lina_flame_cloak_custom_passive, + {registerModifier(nil)}, + {kind = "class", name = "modifier_lina_flame_cloak_custom_passive"} +) +____exports.modifier_lina_flame_cloak_custom_passive = modifier_lina_flame_cloak_custom_passive +____exports.modifier_lina_flame_cloak_custom_passive_buff = __TS__Class() +local modifier_lina_flame_cloak_custom_passive_buff = ____exports.modifier_lina_flame_cloak_custom_passive_buff +modifier_lina_flame_cloak_custom_passive_buff.name = "modifier_lina_flame_cloak_custom_passive_buff" +modifier_lina_flame_cloak_custom_passive_buff.____file_path = "scripts/vscripts/abilities/heroes/lina/ability_lina_flame_cloak_custom.lua" +__TS__ClassExtends(modifier_lina_flame_cloak_custom_passive_buff, BaseModifier) +function modifier_lina_flame_cloak_custom_passive_buff.prototype.IsHidden(self) + return false +end +function modifier_lina_flame_cloak_custom_passive_buff.prototype.IsPurgable(self) + return false +end +function modifier_lina_flame_cloak_custom_passive_buff.prototype.DeclareFunctions(self) + return {MODIFIER_PROPERTY_MOVESPEED_BONUS_PERCENTAGE, MODIFIER_PROPERTY_ATTACKSPEED_PERCENTAGE, MODIFIER_PROPERTY_SPELL_AMPLIFY_PERCENTAGE, MODIFIER_PROPERTY_MAGICAL_RESISTANCE_BONUS} +end +function modifier_lina_flame_cloak_custom_passive_buff.prototype.GetModifierMoveSpeedBonus_Percentage(self) + if self:GetParent():PassivesDisabled() then + return 0 + end + local ____opt_17 = self:GetAbility() + return (____opt_17 and ____opt_17:GetSpecialValueFor("movespeed_bonus")) * self:GetStackCount() +end +function modifier_lina_flame_cloak_custom_passive_buff.prototype.GetModifierAttackSpeedPercentage(self) + if self:GetParent():PassivesDisabled() then + return 0 + end + local ____opt_19 = self:GetAbility() + return (____opt_19 and ____opt_19:GetSpecialValueFor("attackspeed_bonus")) * self:GetStackCount() +end +function modifier_lina_flame_cloak_custom_passive_buff.prototype.GetModifierSpellAmplify_Percentage(self) + if self:GetParent():PassivesDisabled() then + return 0 + end + local ____opt_21 = self:GetAbility() + return (____opt_21 and ____opt_21:GetSpecialValueFor("spell_amplify")) * self:GetStackCount() +end +function modifier_lina_flame_cloak_custom_passive_buff.prototype.GetModifierMagicalResistanceBonus(self) + if self:GetParent():PassivesDisabled() then + return 0 + end + local ____opt_23 = self:GetAbility() + return (____opt_23 and ____opt_23:GetSpecialValueFor("magical_resistance")) * self:GetStackCount() +end +modifier_lina_flame_cloak_custom_passive_buff = __TS__Decorate( + modifier_lina_flame_cloak_custom_passive_buff, + modifier_lina_flame_cloak_custom_passive_buff, + {registerModifier(nil)}, + {kind = "class", name = "modifier_lina_flame_cloak_custom_passive_buff"} +) +____exports.modifier_lina_flame_cloak_custom_passive_buff = modifier_lina_flame_cloak_custom_passive_buff +return ____exports diff --git a/scripts/vscripts/abilities/heroes/lina/ability_lina_laguna_blade_custom.lua b/scripts/vscripts/abilities/heroes/lina/ability_lina_laguna_blade_custom.lua new file mode 100644 index 0000000..5575fe3 --- /dev/null +++ b/scripts/vscripts/abilities/heroes/lina/ability_lina_laguna_blade_custom.lua @@ -0,0 +1,150 @@ +local ____lualib = require("lualib_bundle") +local __TS__Class = ____lualib.__TS__Class +local __TS__ClassExtends = ____lualib.__TS__ClassExtends +local __TS__Decorate = ____lualib.__TS__Decorate +local ____exports = {} +local ____dota_ts_adapter = require("lib.dota_ts_adapter") +local BaseAbility = ____dota_ts_adapter.BaseAbility +local BaseModifier = ____dota_ts_adapter.BaseModifier +local registerAbility = ____dota_ts_adapter.registerAbility +local registerModifier = ____dota_ts_adapter.registerModifier +local ____lina_mana_bonus = require("abilities.heroes.lina.lina_mana_bonus") +local getLinaManaFlatDamage = ____lina_mana_bonus.getLinaManaFlatDamage +____exports.ability_lina_laguna_blade_custom = __TS__Class() +local ability_lina_laguna_blade_custom = ____exports.ability_lina_laguna_blade_custom +ability_lina_laguna_blade_custom.name = "ability_lina_laguna_blade_custom" +ability_lina_laguna_blade_custom.____file_path = "scripts/vscripts/abilities/heroes/lina/ability_lina_laguna_blade_custom.lua" +__TS__ClassExtends(ability_lina_laguna_blade_custom, BaseAbility) +function ability_lina_laguna_blade_custom.prototype.OnSpellStart(self) + if not IsServer() then + return + end + local caster = self:GetCaster() + local target = self:GetCursorTarget() + if not caster or not target then + return + end + if caster:HasScepter() and self:GetSpecialValueFor("mana_cost_facet") > 0 then + caster:AddNewModifier( + caster, + self, + "modifier_lina_laguna_blade_custom_buff", + {duration = self:GetSpecialValueFor("duration")} + ) + end + EmitSoundOn("Ability.LagunaBlade", caster) + EmitSoundOn("Ability.LagunaBladeImpact", target) + local particleId = ParticleManager:CreateParticle("particles/units/heroes/hero_lina/lina_spell_laguna_blade.vpcf", PATTACH_CUSTOMORIGIN, caster) + ParticleManager:SetParticleControlEnt( + particleId, + 0, + caster, + PATTACH_POINT_FOLLOW, + "attach_attack1", + caster:GetAbsOrigin(), + true + ) + ParticleManager:SetParticleControlEnt( + particleId, + 1, + target, + PATTACH_POINT_FOLLOW, + "attach_hitloc", + target:GetAbsOrigin(), + true + ) + Timers:CreateTimer( + 0.5, + function() + ParticleManager:DestroyParticle(particleId, false) + ParticleManager:ReleaseParticleIndex(particleId) + return nil + end + ) + Timers:CreateTimer( + self:GetSpecialValueFor("damage_delay"), + function() + if target and not target:IsNull() and target:IsAlive() then + local ____caster_HasScepter_result_0 + if caster:HasScepter() then + ____caster_HasScepter_result_0 = DAMAGE_TYPE_PURE + else + ____caster_HasScepter_result_0 = DAMAGE_TYPE_MAGICAL + end + local damageType = ____caster_HasScepter_result_0 + local damage = self:GetSpecialValueFor("damage") + getLinaManaFlatDamage(nil, self, caster) + if caster:HasScepter() then + damage = damage + caster:GetIntellect(true) * self:GetSpecialValueFor("damage_mult_agh") + end + ApplyDamage({ + victim = target, + attacker = caster, + damage = damage, + damage_type = damageType, + ability = self + }) + end + return nil + end + ) +end +function ability_lina_laguna_blade_custom.prototype.GetManaCost(self, level) + local caster = self:GetCaster() + if caster and caster:HasScepter() and self:GetSpecialValueFor("mana_cost_facet") > 0 then + return caster:GetMana() * (1 - self:GetSpecialValueFor("mana_cost_facet_tooltip") * 0.01) + self:GetSpecialValueFor("mana_cost") + end + return self:GetSpecialValueFor("mana_cost") +end +ability_lina_laguna_blade_custom = __TS__Decorate( + ability_lina_laguna_blade_custom, + ability_lina_laguna_blade_custom, + {registerAbility(nil)}, + {kind = "class", name = "ability_lina_laguna_blade_custom"} +) +____exports.ability_lina_laguna_blade_custom = ability_lina_laguna_blade_custom +____exports.modifier_lina_laguna_blade_custom_buff = __TS__Class() +local modifier_lina_laguna_blade_custom_buff = ____exports.modifier_lina_laguna_blade_custom_buff +modifier_lina_laguna_blade_custom_buff.name = "modifier_lina_laguna_blade_custom_buff" +modifier_lina_laguna_blade_custom_buff.____file_path = "scripts/vscripts/abilities/heroes/lina/ability_lina_laguna_blade_custom.lua" +__TS__ClassExtends(modifier_lina_laguna_blade_custom_buff, BaseModifier) +function modifier_lina_laguna_blade_custom_buff.prototype.IsDebuff(self) + return false +end +function modifier_lina_laguna_blade_custom_buff.prototype.IsHidden(self) + return false +end +function modifier_lina_laguna_blade_custom_buff.prototype.IsPurgable(self) + return false +end +function modifier_lina_laguna_blade_custom_buff.prototype.RemoveOnDeath(self) + return true +end +function modifier_lina_laguna_blade_custom_buff.prototype.OnCreated(self, params) + self:StartIntervalThink(0.1) +end +function modifier_lina_laguna_blade_custom_buff.prototype.OnIntervalThink(self) + local ____self_SetStackCount_9 = self.SetStackCount + local ____opt_1 = self:GetCaster() + local ____temp_5 = ____opt_1 and ____opt_1:GetMaxMana() + local ____opt_3 = self:GetCaster() + local ____temp_8 = ____temp_5 - (____opt_3 and ____opt_3:GetMana()) + local ____opt_6 = self:GetAbility() + ____self_SetStackCount_9( + self, + ____temp_8 * (____opt_6 and ____opt_6:GetSpecialValueFor("spell_amplify")) * 0.01 + ) +end +function modifier_lina_laguna_blade_custom_buff.prototype.DeclareFunctions(self) + return {MODIFIER_PROPERTY_SPELL_AMPLIFY_PERCENTAGE} +end +function modifier_lina_laguna_blade_custom_buff.prototype.GetModifierSpellAmplify_Percentage(self, event) + return self:GetStackCount() +end +modifier_lina_laguna_blade_custom_buff = __TS__Decorate( + modifier_lina_laguna_blade_custom_buff, + modifier_lina_laguna_blade_custom_buff, + {registerModifier(nil)}, + {kind = "class", name = "modifier_lina_laguna_blade_custom_buff"} +) +____exports.modifier_lina_laguna_blade_custom_buff = modifier_lina_laguna_blade_custom_buff +return ____exports diff --git a/scripts/vscripts/abilities/heroes/lina/ability_lina_light_strike_array_custom.lua b/scripts/vscripts/abilities/heroes/lina/ability_lina_light_strike_array_custom.lua new file mode 100644 index 0000000..351601f --- /dev/null +++ b/scripts/vscripts/abilities/heroes/lina/ability_lina_light_strike_array_custom.lua @@ -0,0 +1,158 @@ +local ____lualib = require("lualib_bundle") +local __TS__Class = ____lualib.__TS__Class +local __TS__ClassExtends = ____lualib.__TS__ClassExtends +local __TS__ArrayForEach = ____lualib.__TS__ArrayForEach +local __TS__Decorate = ____lualib.__TS__Decorate +local ____exports = {} +local ____dota_ts_adapter = require("lib.dota_ts_adapter") +local BaseAbility = ____dota_ts_adapter.BaseAbility +local registerAbility = ____dota_ts_adapter.registerAbility +local ____modifier_general_fired = require("abilities.modifiers.modifier_general_fired") +local modifier_general_fired = ____modifier_general_fired.modifier_general_fired +local ____lina_mana_bonus = require("abilities.heroes.lina.lina_mana_bonus") +local getLinaManaFlatDamage = ____lina_mana_bonus.getLinaManaFlatDamage +____exports.ability_lina_light_strike_array_custom = __TS__Class() +local ability_lina_light_strike_array_custom = ____exports.ability_lina_light_strike_array_custom +ability_lina_light_strike_array_custom.name = "ability_lina_light_strike_array_custom" +ability_lina_light_strike_array_custom.____file_path = "scripts/vscripts/abilities/heroes/lina/ability_lina_light_strike_array_custom.lua" +__TS__ClassExtends(ability_lina_light_strike_array_custom, BaseAbility) +function ability_lina_light_strike_array_custom.prototype.GetAOERadius(self) + return 250 +end +function ability_lina_light_strike_array_custom.prototype.OnSpellStart(self) + local caster = self:GetCaster() + local point = self:GetCursorPosition() + local delay = self:GetSpecialValueFor("light_strike_array_delay_time") + local radius = self:GetSpecialValueFor("light_strike_array_aoe") + local stun_duration = self:GetSpecialValueFor("light_strike_array_stun_duration") + local multicast_max = self:GetLevel() + local multicast_chance = self:GetSpecialValueFor("light_strike_chance") + self:CastLSA(point, delay, radius, stun_duration) + local additional_casts = 0 + do + local i = 1 + while i < multicast_max do + local roll = RandomInt(1, 100) + if roll <= multicast_chance then + additional_casts = additional_casts + 1 + end + i = i + 1 + end + end + do + local i = 0 + while i < additional_casts do + Timers:CreateTimer( + delay * (i + 1), + function() + self:CastLSA(point, delay, radius, stun_duration) + end + ) + i = i + 1 + end + end +end +function ability_lina_light_strike_array_custom.prototype.CastLSA(self, point, delay, radius, stun_duration) + local caster = self:GetCaster() + local particleWarning = ParticleManager:CreateParticle("particles/units/heroes/hero_lina/lina_spell_light_strike_array_ray_team.vpcf", PATTACH_WORLDORIGIN, nil) + ParticleManager:SetParticleControl(particleWarning, 0, point) + ParticleManager:SetParticleControl( + particleWarning, + 1, + Vector(radius, 0, 0) + ) + local particleRay = ParticleManager:CreateParticle("particles/units/heroes/hero_lina/lina_spell_light_strike_array_ray.vpcf", PATTACH_WORLDORIGIN, nil) + ParticleManager:SetParticleControl(particleRay, 0, point) + ParticleManager:SetParticleControl( + particleRay, + 1, + Vector(radius, 0, 0) + ) + EmitSoundOnLocationWithCaster(point, "Ability.PreLightStrikeArray", caster) + Timers:CreateTimer( + delay, + function() + self:CreateExplosion(point, radius, stun_duration, caster) + if not HasTalent( + nil, + self:GetCaster(), + "special_bonus_unique_lina_light_strike_array_five" + ) then + return + end + local sideDistance = radius / 2 + local leftPoint = Vector(point.x - sideDistance, point.y, point.z) + self:CreateExplosion(leftPoint, radius, stun_duration, caster) + local rightPoint = Vector(point.x + sideDistance, point.y, point.z) + self:CreateExplosion(rightPoint, radius, stun_duration, caster) + local topPoint = Vector(point.x, point.y + sideDistance, point.z) + self:CreateExplosion(topPoint, radius, stun_duration, caster) + local bottomPoint = Vector(point.x, point.y - sideDistance, point.z) + self:CreateExplosion(bottomPoint, radius, stun_duration, caster) + ParticleManager:DestroyParticle(particleWarning, false) + ParticleManager:ReleaseParticleIndex(particleWarning) + ParticleManager:DestroyParticle(particleRay, false) + ParticleManager:ReleaseParticleIndex(particleRay) + end + ) +end +function ability_lina_light_strike_array_custom.prototype.CreateExplosion(self, point, radius, stun_duration, caster) + local damage = self:GetSpecialValueFor("light_strike_array_damage") + getLinaManaFlatDamage(nil, self, caster) + local units = FindUnitsInRadius( + caster:GetTeamNumber(), + point, + nil, + radius, + DOTA_UNIT_TARGET_TEAM_ENEMY, + DOTA_UNIT_TARGET_HERO + DOTA_UNIT_TARGET_BASIC, + DOTA_UNIT_TARGET_FLAG_NONE, + FIND_ANY_ORDER, + false + ) + __TS__ArrayForEach( + units, + function(____, unit) + ApplyDamage({ + victim = unit, + attacker = caster, + damage = damage, + damage_type = self:GetAbilityDamageType(), + ability = self + }) + local modifier = unit:AddNewModifier( + self:GetCaster(), + self, + modifier_general_fired.name, + {} + ) + if modifier then + local stacksPerLevel = self:GetSpecialValueFor("fire_stacks_per_level") + do + local i = 0 + while i < stacksPerLevel do + modifier:IncrementStackCount() + i = i + 1 + end + end + end + unit:AddNewModifier(caster, self, "modifier_stunned", {duration = stun_duration}) + end + ) + local particleEffect = ParticleManager:CreateParticle("particles/units/heroes/hero_lina/lina_spell_light_strike_array.vpcf", PATTACH_WORLDORIGIN, nil) + ParticleManager:SetParticleControl(particleEffect, 0, point) + ParticleManager:SetParticleControl( + particleEffect, + 1, + Vector(radius, 0, 0) + ) + ParticleManager:ReleaseParticleIndex(particleEffect) + EmitSoundOnLocationWithCaster(point, "Ability.LightStrikeArray", caster) +end +ability_lina_light_strike_array_custom = __TS__Decorate( + ability_lina_light_strike_array_custom, + ability_lina_light_strike_array_custom, + {registerAbility(nil)}, + {kind = "class", name = "ability_lina_light_strike_array_custom"} +) +____exports.ability_lina_light_strike_array_custom = ability_lina_light_strike_array_custom +return ____exports diff --git a/scripts/vscripts/abilities/heroes/lina/ability_lina_scorch_affinity_innate.lua b/scripts/vscripts/abilities/heroes/lina/ability_lina_scorch_affinity_innate.lua new file mode 100644 index 0000000..31ab061 --- /dev/null +++ b/scripts/vscripts/abilities/heroes/lina/ability_lina_scorch_affinity_innate.lua @@ -0,0 +1,158 @@ +local ____lualib = require("lualib_bundle") +local __TS__Class = ____lualib.__TS__Class +local __TS__ClassExtends = ____lualib.__TS__ClassExtends +local __TS__Decorate = ____lualib.__TS__Decorate +local ____exports = {} +local ____dota_ts_adapter = require("lib.dota_ts_adapter") +local BaseAbility = ____dota_ts_adapter.BaseAbility +local BaseModifier = ____dota_ts_adapter.BaseModifier +local registerAbility = ____dota_ts_adapter.registerAbility +local registerModifier = ____dota_ts_adapter.registerModifier +local ____modifier_general_fired = require("abilities.modifiers.modifier_general_fired") +local modifier_general_fired = ____modifier_general_fired.modifier_general_fired +local ____incoming_damage_reduction_combine = require("utils.incoming_damage_reduction_combine") +local removeIncomingDamageReductionSource = ____incoming_damage_reduction_combine.removeIncomingDamageReductionSource +local setIncomingDamageReductionSource = ____incoming_damage_reduction_combine.setIncomingDamageReductionSource +local LINA_SCORCH_AFFINITY_INCOMING_SOURCE = "modifier_lina_scorch_affinity_innate" +____exports.ability_lina_scorch_affinity_innate = __TS__Class() +local ability_lina_scorch_affinity_innate = ____exports.ability_lina_scorch_affinity_innate +ability_lina_scorch_affinity_innate.name = "ability_lina_scorch_affinity_innate" +ability_lina_scorch_affinity_innate.____file_path = "scripts/vscripts/abilities/heroes/lina/ability_lina_scorch_affinity_innate.lua" +__TS__ClassExtends(ability_lina_scorch_affinity_innate, BaseAbility) +function ability_lina_scorch_affinity_innate.prototype.GetIntrinsicModifierName(self) + return ____exports.modifier_lina_scorch_affinity_innate.name +end +function ability_lina_scorch_affinity_innate.prototype.GetAOERadius(self) + return self:GetSpecialValueFor("radius") +end +ability_lina_scorch_affinity_innate = __TS__Decorate( + ability_lina_scorch_affinity_innate, + ability_lina_scorch_affinity_innate, + {registerAbility(nil)}, + {kind = "class", name = "ability_lina_scorch_affinity_innate"} +) +____exports.ability_lina_scorch_affinity_innate = ability_lina_scorch_affinity_innate +____exports.modifier_lina_scorch_affinity_innate = __TS__Class() +local modifier_lina_scorch_affinity_innate = ____exports.modifier_lina_scorch_affinity_innate +modifier_lina_scorch_affinity_innate.name = "modifier_lina_scorch_affinity_innate" +modifier_lina_scorch_affinity_innate.____file_path = "scripts/vscripts/abilities/heroes/lina/ability_lina_scorch_affinity_innate.lua" +__TS__ClassExtends(modifier_lina_scorch_affinity_innate, BaseModifier) +function modifier_lina_scorch_affinity_innate.prototype.____constructor(self, ...) + BaseModifier.prototype.____constructor(self, ...) + self.burningUnitCount = 0 + self.totalBonusPct = 0 +end +function modifier_lina_scorch_affinity_innate.prototype.IsHidden(self) + return false +end +function modifier_lina_scorch_affinity_innate.prototype.IsDebuff(self) + return false +end +function modifier_lina_scorch_affinity_innate.prototype.IsPurgable(self) + return false +end +function modifier_lina_scorch_affinity_innate.prototype.RemoveOnDeath(self) + return false +end +function modifier_lina_scorch_affinity_innate.prototype.OnCreated(self) + self:refreshBurningUnits() + self:StartIntervalThink(0.25) + if not IsServer() then + return + end + setIncomingDamageReductionSource( + nil, + self:GetParent(), + LINA_SCORCH_AFFINITY_INCOMING_SOURCE, + function() + if self:GetParent():PassivesDisabled() then + return 0 + end + return math.max(0, self.totalBonusPct) + end + ) +end +function modifier_lina_scorch_affinity_innate.prototype.OnRefresh(self) + self:refreshBurningUnits() +end +function modifier_lina_scorch_affinity_innate.prototype.OnDestroy(self) + if not IsServer() then + return + end + removeIncomingDamageReductionSource( + nil, + self:GetParent(), + LINA_SCORCH_AFFINITY_INCOMING_SOURCE + ) +end +function modifier_lina_scorch_affinity_innate.prototype.OnIntervalThink(self) + self:refreshBurningUnits() +end +function modifier_lina_scorch_affinity_innate.prototype.DeclareFunctions(self) + return {MODIFIER_PROPERTY_DAMAGEOUTGOING_PERCENTAGE, MODIFIER_PROPERTY_SPELL_AMPLIFY_PERCENTAGE} +end +function modifier_lina_scorch_affinity_innate.prototype.refreshBurningUnits(self) + local parent = self:GetParent() + local ability = self:GetAbility() + if not ability or not parent or parent:IsNull() then + self.burningUnitCount = 0 + self.totalBonusPct = 0 + self:SetStackCount(0) + return + end + local radius = ability:GetSpecialValueFor("radius") + local basePct = ability:GetSpecialValueFor("bonus_pct_base") + local pctPerHeroLevel = ability:GetSpecialValueFor("bonus_pct_per_hero_level") + local heroLevel = parent:IsHero() and parent:GetLevel() or 1 + local pctPerUnit = basePct + heroLevel * pctPerHeroLevel + local count = 0 + local units = FindUnitsInRadius( + parent:GetTeamNumber(), + parent:GetAbsOrigin(), + nil, + radius, + DOTA_UNIT_TARGET_TEAM_BOTH, + bit.bor(DOTA_UNIT_TARGET_HERO, DOTA_UNIT_TARGET_BASIC), + DOTA_UNIT_TARGET_FLAG_NONE, + FIND_ANY_ORDER, + false + ) + for ____, unit in ipairs(units) do + do + if not unit or unit:IsNull() or not unit:IsAlive() then + goto __continue19 + end + if unit == parent then + goto __continue19 + end + local fired = unit:FindModifierByName(modifier_general_fired.name) + if fired and not fired:IsNull() then + count = count + 1 + end + end + ::__continue19:: + end + self.burningUnitCount = count + self.totalBonusPct = count * pctPerUnit + self:SetStackCount(count) +end +function modifier_lina_scorch_affinity_innate.prototype.GetModifierDamageOutgoing_Percentage(self) + if self:GetParent():PassivesDisabled() then + return 0 + end + return self.totalBonusPct +end +function modifier_lina_scorch_affinity_innate.prototype.GetModifierSpellAmplify_Percentage(self, event) + if self:GetParent():PassivesDisabled() then + return 0 + end + return self.totalBonusPct +end +modifier_lina_scorch_affinity_innate = __TS__Decorate( + modifier_lina_scorch_affinity_innate, + modifier_lina_scorch_affinity_innate, + {registerModifier(nil)}, + {kind = "class", name = "modifier_lina_scorch_affinity_innate"} +) +____exports.modifier_lina_scorch_affinity_innate = modifier_lina_scorch_affinity_innate +return ____exports diff --git a/scripts/vscripts/abilities/heroes/lina/lina_mana_bonus.lua b/scripts/vscripts/abilities/heroes/lina/lina_mana_bonus.lua new file mode 100644 index 0000000..ffff1f2 --- /dev/null +++ b/scripts/vscripts/abilities/heroes/lina/lina_mana_bonus.lua @@ -0,0 +1,14 @@ +--[[ Generated with https://github.com/TypeScriptToLua/TypeScriptToLua ]] +local ____exports = {} +--- Доп. урон кастомок Лины: текущая мана × (`mana_damage_from_current_pct` / 100) из AbilityValues. +function ____exports.getLinaManaFlatDamage(self, ability, caster) + if caster == nil or not caster:IsAlive() or not caster:IsHero() then + return 0 + end + local pct = ability:GetSpecialValueFor("mana_damage_from_current_pct") + if pct <= 0 then + return 0 + end + return caster:GetMana() * (pct * 0.01) +end +return ____exports diff --git a/scripts/vscripts/abilities/heroes/luna/ability_luna_eclipse_custom.lua b/scripts/vscripts/abilities/heroes/luna/ability_luna_eclipse_custom.lua new file mode 100644 index 0000000..ba87e73 --- /dev/null +++ b/scripts/vscripts/abilities/heroes/luna/ability_luna_eclipse_custom.lua @@ -0,0 +1,182 @@ +local ____lualib = require("lualib_bundle") +local __TS__Class = ____lualib.__TS__Class +local __TS__ClassExtends = ____lualib.__TS__ClassExtends +local __TS__Decorate = ____lualib.__TS__Decorate +local ____exports = {} +local ____dota_ts_adapter = require("lib.dota_ts_adapter") +local BaseAbility = ____dota_ts_adapter.BaseAbility +local BaseModifier = ____dota_ts_adapter.BaseModifier +local registerAbility = ____dota_ts_adapter.registerAbility +local registerModifier = ____dota_ts_adapter.registerModifier +local ____luna_lucent_beam_shared = require("abilities.heroes.luna.luna_lucent_beam_shared") +local applyLucentBeamHit = ____luna_lucent_beam_shared.applyLucentBeamHit +local getLunaLucentBeamAbility = ____luna_lucent_beam_shared.getLunaLucentBeamAbility +____exports.ability_luna_eclipse_custom = __TS__Class() +local ability_luna_eclipse_custom = ____exports.ability_luna_eclipse_custom +ability_luna_eclipse_custom.name = "ability_luna_eclipse_custom" +ability_luna_eclipse_custom.____file_path = "scripts/vscripts/abilities/heroes/luna/ability_luna_eclipse_custom.lua" +__TS__ClassExtends(ability_luna_eclipse_custom, BaseAbility) +function ability_luna_eclipse_custom.prototype.Precache(self, context) + PrecacheResource("particle", "particles/units/heroes/hero_luna/luna_eclipse.vpcf", context) + PrecacheResource("particle", "particles/units/heroes/hero_luna/luna_lucent_beam.vpcf", context) +end +function ability_luna_eclipse_custom.prototype.GetBehavior(self) + return DOTA_ABILITY_BEHAVIOR_NO_TARGET +end +function ability_luna_eclipse_custom.prototype.OnUpgrade(self) + if not IsServer() then + return + end + local caster = self:GetCaster() + local lucentBeam = getLunaLucentBeamAbility(nil, caster) + if not lucentBeam or lucentBeam:GetLevel() > 0 then + return + end + if self:GetLevel() > 0 then + self:SetLevel(0) + if caster:IsRealHero() then + local heroCaster = caster + heroCaster:SetAbilityPoints(heroCaster:GetAbilityPoints() + 1) + end + end +end +function ability_luna_eclipse_custom.prototype.OnSpellStart(self) + if not IsServer() then + return + end + local caster = self:GetCaster() + local duration = self:GetSpecialValueFor("eclipse_duration") + local radius = self:GetSpecialValueFor("eclipse_radius") + local eclipsePfx = ParticleManager:CreateParticle("particles/units/heroes/hero_luna/luna_eclipse.vpcf", PATTACH_ABSORIGIN_FOLLOW, caster) + ParticleManager:SetParticleControl( + eclipsePfx, + 0, + caster:GetAbsOrigin() + ) + ParticleManager:SetParticleControl( + eclipsePfx, + 1, + Vector(radius, 0, 0) + ) + Timers:CreateTimer( + duration + 0.5, + function() + ParticleManager:DestroyParticle(eclipsePfx, false) + ParticleManager:ReleaseParticleIndex(eclipsePfx) + return nil + end + ) + caster:AddNewModifier(caster, self, ____exports.modifier_luna_eclipse_active.name, {duration = duration}) + EmitSoundOn("Hero_Luna.Eclipse", caster) +end +ability_luna_eclipse_custom = __TS__Decorate( + ability_luna_eclipse_custom, + ability_luna_eclipse_custom, + {registerAbility(nil)}, + {kind = "class", name = "ability_luna_eclipse_custom"} +) +____exports.ability_luna_eclipse_custom = ability_luna_eclipse_custom +____exports.modifier_luna_eclipse_active = __TS__Class() +local modifier_luna_eclipse_active = ____exports.modifier_luna_eclipse_active +modifier_luna_eclipse_active.name = "modifier_luna_eclipse_active" +modifier_luna_eclipse_active.____file_path = "scripts/vscripts/abilities/heroes/luna/ability_luna_eclipse_custom.lua" +__TS__ClassExtends(modifier_luna_eclipse_active, BaseModifier) +function modifier_luna_eclipse_active.prototype.____constructor(self, ...) + BaseModifier.prototype.____constructor(self, ...) + self.beamsDone = 0 +end +function modifier_luna_eclipse_active.prototype.IsHidden(self) + return false +end +function modifier_luna_eclipse_active.prototype.IsPurgable(self) + return false +end +function modifier_luna_eclipse_active.prototype.GetAttributes(self) + return MODIFIER_ATTRIBUTE_MULTIPLE +end +function modifier_luna_eclipse_active.prototype.OnCreated(self) + if not IsServer() then + return + end + local interval = self:GetAbility():GetSpecialValueFor("beam_interval") + self:StartIntervalThink(interval) + self:OnIntervalThink() +end +function modifier_luna_eclipse_active.prototype.OnIntervalThink(self) + if not IsServer() then + return + end + local ability = self:GetAbility() + local parent = self:GetParent() + if not ability or not parent then + self:Destroy() + return + end + local facetWaves = parent:HasScepter() and math.floor(ability:GetSpecialValueFor("eclipse_facet_wave_count")) or 0 + local maxBeams = facetWaves > 0 and facetWaves or math.floor(ability:GetSpecialValueFor("eclipse_beam_count")) + if self.beamsDone >= maxBeams then + self:Destroy() + return + end + local radius = ability:GetSpecialValueFor("eclipse_radius") + local lucentBeam = getLunaLucentBeamAbility(nil, parent) + local hasLeveledLucentBeam = not not lucentBeam and lucentBeam:GetLevel() > 0 + local damage = hasLeveledLucentBeam and lucentBeam:GetSpecialValueFor("lucent_beam_damage") or 0 + local stun = hasLeveledLucentBeam and lucentBeam:GetSpecialValueFor("stun_duration") or 0 + local enemies = FindUnitsInRadius( + parent:GetTeamNumber(), + parent:GetAbsOrigin(), + nil, + radius, + DOTA_UNIT_TARGET_TEAM_ENEMY, + DOTA_UNIT_TARGET_HERO + DOTA_UNIT_TARGET_BASIC, + DOTA_UNIT_TARGET_FLAG_NONE, + FIND_ANY_ORDER, + false + ) + if #enemies == 0 then + self.beamsDone = self.beamsDone + 1 + return + end + if facetWaves > 0 then + for ____, victim in ipairs(enemies) do + do + if not victim or not victim:IsAlive() then + goto __continue23 + end + applyLucentBeamHit( + nil, + parent, + ability, + victim, + damage, + stun + ) + end + ::__continue23:: + end + else + local victim = enemies[RandomInt(0, #enemies - 1) + 1] + if not victim or not victim:IsAlive() then + self.beamsDone = self.beamsDone + 1 + return + end + applyLucentBeamHit( + nil, + parent, + ability, + victim, + damage, + stun + ) + end + self.beamsDone = self.beamsDone + 1 +end +modifier_luna_eclipse_active = __TS__Decorate( + modifier_luna_eclipse_active, + modifier_luna_eclipse_active, + {registerModifier(nil)}, + {kind = "class", name = "modifier_luna_eclipse_active"} +) +____exports.modifier_luna_eclipse_active = modifier_luna_eclipse_active +return ____exports diff --git a/scripts/vscripts/abilities/heroes/luna/ability_luna_lucent_beam_custom.lua b/scripts/vscripts/abilities/heroes/luna/ability_luna_lucent_beam_custom.lua new file mode 100644 index 0000000..0a98714 --- /dev/null +++ b/scripts/vscripts/abilities/heroes/luna/ability_luna_lucent_beam_custom.lua @@ -0,0 +1,78 @@ +local ____lualib = require("lualib_bundle") +local __TS__Class = ____lualib.__TS__Class +local __TS__ClassExtends = ____lualib.__TS__ClassExtends +local __TS__Decorate = ____lualib.__TS__Decorate +local ____exports = {} +local ____dota_ts_adapter = require("lib.dota_ts_adapter") +local BaseAbility = ____dota_ts_adapter.BaseAbility +local registerAbility = ____dota_ts_adapter.registerAbility +local ____luna_lucent_beam_shared = require("abilities.heroes.luna.luna_lucent_beam_shared") +local applyLucentBeamHit = ____luna_lucent_beam_shared.applyLucentBeamHit +____exports.ability_luna_lucent_beam_custom = __TS__Class() +local ability_luna_lucent_beam_custom = ____exports.ability_luna_lucent_beam_custom +ability_luna_lucent_beam_custom.name = "ability_luna_lucent_beam_custom" +ability_luna_lucent_beam_custom.____file_path = "scripts/vscripts/abilities/heroes/luna/ability_luna_lucent_beam_custom.lua" +__TS__ClassExtends(ability_luna_lucent_beam_custom, BaseAbility) +function ability_luna_lucent_beam_custom.prototype.Precache(self, context) + PrecacheResource("particle", "particles/units/heroes/hero_luna/luna_lucent_beam.vpcf", context) + PrecacheResource("particle", "particles/units/heroes/hero_luna/luna_lucent_beam_precast.vpcf", context) + PrecacheResource("particle", "particles/units/heroes/hero_luna/luna_lucent_beam_cast.vpcf", context) +end +function ability_luna_lucent_beam_custom.prototype.GetBehavior(self) + return DOTA_ABILITY_BEHAVIOR_UNIT_TARGET +end +function ability_luna_lucent_beam_custom.prototype.GetAOERadius(self) + local r = self:GetSpecialValueFor("lucent_beam_aoe_radius") + return r > 0 and r or 0 +end +function ability_luna_lucent_beam_custom.prototype.OnAbilityPhaseStart(self) + if not IsServer() then + return true + end + local caster = self:GetCaster() + self.precastPfx = ParticleManager:CreateParticle("particles/units/heroes/hero_luna/luna_lucent_beam_precast.vpcf", PATTACH_ABSORIGIN_FOLLOW, caster) + return true +end +function ability_luna_lucent_beam_custom.prototype.OnAbilityPhaseInterrupted(self) + self:clearPrecastPfx() +end +function ability_luna_lucent_beam_custom.prototype.clearPrecastPfx(self) + if self.precastPfx == nil then + return + end + ParticleManager:DestroyParticle(self.precastPfx, false) + ParticleManager:ReleaseParticleIndex(self.precastPfx) + self.precastPfx = nil +end +function ability_luna_lucent_beam_custom.prototype.OnSpellStart(self) + if not IsServer() then + return + end + self:clearPrecastPfx() + local caster = self:GetCaster() + local target = self:GetCursorTarget() + if not target or target:IsNull() or not target:IsAlive() then + return + end + local castPfx = ParticleManager:CreateParticle("particles/units/heroes/hero_luna/luna_lucent_beam_cast.vpcf", PATTACH_ABSORIGIN_FOLLOW, caster) + ParticleManager:ReleaseParticleIndex(castPfx) + EmitSoundOn("Hero_Luna.LucentBeam.Cast", caster) + local damage = self:GetSpecialValueFor("lucent_beam_damage") + caster:GetMaxMana() * (self:GetSpecialValueFor("mana_damage_pct") / 100) + local stun = self:GetSpecialValueFor("stun_duration") + applyLucentBeamHit( + nil, + caster, + self, + target, + damage, + stun + ) +end +ability_luna_lucent_beam_custom = __TS__Decorate( + ability_luna_lucent_beam_custom, + ability_luna_lucent_beam_custom, + {registerAbility(nil)}, + {kind = "class", name = "ability_luna_lucent_beam_custom"} +) +____exports.ability_luna_lucent_beam_custom = ability_luna_lucent_beam_custom +return ____exports diff --git a/scripts/vscripts/abilities/heroes/luna/ability_luna_lunar_blessing_custom.lua b/scripts/vscripts/abilities/heroes/luna/ability_luna_lunar_blessing_custom.lua new file mode 100644 index 0000000..6d3ab0a --- /dev/null +++ b/scripts/vscripts/abilities/heroes/luna/ability_luna_lunar_blessing_custom.lua @@ -0,0 +1,134 @@ +local ____lualib = require("lualib_bundle") +local __TS__Class = ____lualib.__TS__Class +local __TS__ClassExtends = ____lualib.__TS__ClassExtends +local __TS__Decorate = ____lualib.__TS__Decorate +local ____exports = {} +local ____dota_ts_adapter = require("lib.dota_ts_adapter") +local BaseAbility = ____dota_ts_adapter.BaseAbility +local BaseModifier = ____dota_ts_adapter.BaseModifier +local registerAbility = ____dota_ts_adapter.registerAbility +local registerModifier = ____dota_ts_adapter.registerModifier +local ____moon_sisters_synergy = require("abilities.heroes.mirana_luna.moon_sisters_synergy") +local ensureMoonSistersInnateModifier = ____moon_sisters_synergy.ensureMoonSistersInnateModifier +local removeMoonSistersInnateModifier = ____moon_sisters_synergy.removeMoonSistersInnateModifier +____exports.ability_luna_lunar_blessing_custom = __TS__Class() +local ability_luna_lunar_blessing_custom = ____exports.ability_luna_lunar_blessing_custom +ability_luna_lunar_blessing_custom.name = "ability_luna_lunar_blessing_custom" +ability_luna_lunar_blessing_custom.____file_path = "scripts/vscripts/abilities/heroes/luna/ability_luna_lunar_blessing_custom.lua" +__TS__ClassExtends(ability_luna_lunar_blessing_custom, BaseAbility) +function ability_luna_lunar_blessing_custom.prototype.GetIntrinsicModifierName(self) + return ____exports.modifier_luna_lunar_blessing_aura_custom.name +end +function ability_luna_lunar_blessing_custom.prototype.GetAOERadius(self) + return self:GetSpecialValueFor("blessing_radius") +end +ability_luna_lunar_blessing_custom = __TS__Decorate( + ability_luna_lunar_blessing_custom, + ability_luna_lunar_blessing_custom, + {registerAbility(nil)}, + {kind = "class", name = "ability_luna_lunar_blessing_custom"} +) +____exports.ability_luna_lunar_blessing_custom = ability_luna_lunar_blessing_custom +____exports.modifier_luna_lunar_blessing_aura_custom = __TS__Class() +local modifier_luna_lunar_blessing_aura_custom = ____exports.modifier_luna_lunar_blessing_aura_custom +modifier_luna_lunar_blessing_aura_custom.name = "modifier_luna_lunar_blessing_aura_custom" +modifier_luna_lunar_blessing_aura_custom.____file_path = "scripts/vscripts/abilities/heroes/luna/ability_luna_lunar_blessing_custom.lua" +__TS__ClassExtends(modifier_luna_lunar_blessing_aura_custom, BaseModifier) +function modifier_luna_lunar_blessing_aura_custom.prototype.IsHidden(self) + return true +end +function modifier_luna_lunar_blessing_aura_custom.prototype.IsPurgable(self) + return false +end +function modifier_luna_lunar_blessing_aura_custom.prototype.IsDebuff(self) + return false +end +function modifier_luna_lunar_blessing_aura_custom.prototype.IsAura(self) + return true +end +function modifier_luna_lunar_blessing_aura_custom.prototype.GetModifierAura(self) + return ____exports.modifier_luna_lunar_blessing_buff_custom.name +end +function modifier_luna_lunar_blessing_aura_custom.prototype.GetAuraRadius(self) + return self:GetAbility():GetSpecialValueFor("blessing_radius") +end +function modifier_luna_lunar_blessing_aura_custom.prototype.GetAuraDuration(self) + return 0.5 +end +function modifier_luna_lunar_blessing_aura_custom.prototype.GetAuraSearchTeam(self) + return DOTA_UNIT_TARGET_TEAM_FRIENDLY +end +function modifier_luna_lunar_blessing_aura_custom.prototype.GetAuraSearchType(self) + return DOTA_UNIT_TARGET_HERO + DOTA_UNIT_TARGET_BASIC +end +function modifier_luna_lunar_blessing_aura_custom.prototype.GetAuraSearchFlags(self) + return DOTA_UNIT_TARGET_FLAG_NONE +end +function modifier_luna_lunar_blessing_aura_custom.prototype.OnCreated(self) + local parent = self:GetParent() + local ability = self:GetAbility() + if ability then + ensureMoonSistersInnateModifier(nil, parent, ability) + end +end +function modifier_luna_lunar_blessing_aura_custom.prototype.OnDestroy(self) + removeMoonSistersInnateModifier( + nil, + self:GetParent() + ) +end +function modifier_luna_lunar_blessing_aura_custom.prototype.GetAuraEntityReject(self, _hTarget) + return self:GetParent():PassivesDisabled() +end +modifier_luna_lunar_blessing_aura_custom = __TS__Decorate( + modifier_luna_lunar_blessing_aura_custom, + modifier_luna_lunar_blessing_aura_custom, + {registerModifier(nil)}, + {kind = "class", name = "modifier_luna_lunar_blessing_aura_custom"} +) +____exports.modifier_luna_lunar_blessing_aura_custom = modifier_luna_lunar_blessing_aura_custom +____exports.modifier_luna_lunar_blessing_buff_custom = __TS__Class() +local modifier_luna_lunar_blessing_buff_custom = ____exports.modifier_luna_lunar_blessing_buff_custom +modifier_luna_lunar_blessing_buff_custom.name = "modifier_luna_lunar_blessing_buff_custom" +modifier_luna_lunar_blessing_buff_custom.____file_path = "scripts/vscripts/abilities/heroes/luna/ability_luna_lunar_blessing_custom.lua" +__TS__ClassExtends(modifier_luna_lunar_blessing_buff_custom, BaseModifier) +function modifier_luna_lunar_blessing_buff_custom.prototype.IsHidden(self) + return false +end +function modifier_luna_lunar_blessing_buff_custom.prototype.IsPurgable(self) + return false +end +function modifier_luna_lunar_blessing_buff_custom.prototype.IsDebuff(self) + return false +end +function modifier_luna_lunar_blessing_buff_custom.prototype.DeclareFunctions(self) + return {MODIFIER_PROPERTY_PREATTACK_BONUS_DAMAGE, MODIFIER_PROPERTY_SPELL_AMPLIFY_PERCENTAGE} +end +function modifier_luna_lunar_blessing_buff_custom.prototype.GetModifierPreAttack_BonusDamage(self) + local caster = self:GetCaster() + if caster and caster:PassivesDisabled() then + return 0 + end + local ability = self:GetAbility() + if not ability then + return 0 + end + return ability:GetSpecialValueFor("blessing_bonus_damage") * self:GetParent():GetLevel() +end +function modifier_luna_lunar_blessing_buff_custom.prototype.GetModifierSpellAmplify_Percentage(self) + local caster = self:GetCaster() + if caster and caster:PassivesDisabled() then + return 0 + end + local ability = self:GetAbility() + local perLevel = ability:GetSpecialValueFor("spell_amp_per_level") + return perLevel * self:GetParent():GetLevel() +end +modifier_luna_lunar_blessing_buff_custom = __TS__Decorate( + modifier_luna_lunar_blessing_buff_custom, + modifier_luna_lunar_blessing_buff_custom, + {registerModifier(nil)}, + {kind = "class", name = "modifier_luna_lunar_blessing_buff_custom"} +) +____exports.modifier_luna_lunar_blessing_buff_custom = modifier_luna_lunar_blessing_buff_custom +return ____exports diff --git a/scripts/vscripts/abilities/heroes/luna/ability_luna_moon_glaive_custom.lua b/scripts/vscripts/abilities/heroes/luna/ability_luna_moon_glaive_custom.lua new file mode 100644 index 0000000..efdcb36 --- /dev/null +++ b/scripts/vscripts/abilities/heroes/luna/ability_luna_moon_glaive_custom.lua @@ -0,0 +1,192 @@ +local ____lualib = require("lualib_bundle") +local __TS__Class = ____lualib.__TS__Class +local __TS__ClassExtends = ____lualib.__TS__ClassExtends +local __TS__Decorate = ____lualib.__TS__Decorate +local ____exports = {} +local ____dota_ts_adapter = require("lib.dota_ts_adapter") +local BaseAbility = ____dota_ts_adapter.BaseAbility +local BaseModifier = ____dota_ts_adapter.BaseModifier +local registerAbility = ____dota_ts_adapter.registerAbility +local registerModifier = ____dota_ts_adapter.registerModifier +____exports.ability_luna_moon_glaive_custom = __TS__Class() +local ability_luna_moon_glaive_custom = ____exports.ability_luna_moon_glaive_custom +ability_luna_moon_glaive_custom.name = "ability_luna_moon_glaive_custom" +ability_luna_moon_glaive_custom.____file_path = "scripts/vscripts/abilities/heroes/luna/ability_luna_moon_glaive_custom.lua" +__TS__ClassExtends(ability_luna_moon_glaive_custom, BaseAbility) +function ability_luna_moon_glaive_custom.prototype.Precache(self, context) + PrecacheResource("particle", "particles/units/heroes/hero_luna/luna_moon_glaive.vpcf", context) + PrecacheResource("particle", "particles/units/heroes/hero_luna/luna_base_attack.vpcf", context) +end +function ability_luna_moon_glaive_custom.prototype.GetIntrinsicModifierName(self) + return ____exports.modifier_luna_moon_glaive_passive.name +end +function ability_luna_moon_glaive_custom.prototype.LaunchGlaive(self, from, to, state) + if not IsServer() then + return + end + self.glaiveChain = state + local caster = self:GetCaster() + local speed = math.max( + 600, + caster:GetProjectileSpeed() + ) + ProjectileManager:CreateTrackingProjectile({ + Ability = self, + EffectName = "particles/units/heroes/hero_luna/luna_moon_glaive.vpcf", + Source = from, + Target = to, + iMoveSpeed = speed, + bDodgeable = true, + bVisibleToEnemies = true + }) +end +function ability_luna_moon_glaive_custom.prototype.OnProjectileHit(self, target, location) + if not IsServer() then + return + end + if not target or target:IsNull() or not target:IsAlive() then + self.glaiveChain = nil + return + end + local state = self.glaiveChain + local ability = self + local caster = ability:GetCaster() + if not state or not caster then + self.glaiveChain = nil + return + end + local atk = state.attacker:GetAverageTrueAttackDamage(target) + local dmg = atk * state.damageMult + ApplyDamage({ + victim = target, + attacker = state.attacker, + damage = dmg, + damage_type = DAMAGE_TYPE_PHYSICAL, + damage_flags = bit.bor(DOTA_DAMAGE_FLAG_NO_SPELL_AMPLIFICATION, DOTA_DAMAGE_FLAG_ATTACK_MODIFIER), + ability = ability + }) + EmitSoundOn("Hero_Luna.MoonGlaive", target) + local bounceRange = ability:GetSpecialValueFor("bounce_range") + local falloff = state.falloffPct / 100 + local nextMult = state.damageMult * falloff + if state.bouncesRemaining <= 0 then + self.glaiveChain = nil + return + end + local enemies = FindUnitsInRadius( + caster:GetTeamNumber(), + target:GetAbsOrigin(), + nil, + bounceRange, + DOTA_UNIT_TARGET_TEAM_ENEMY, + DOTA_UNIT_TARGET_HERO + DOTA_UNIT_TARGET_BASIC, + DOTA_UNIT_TARGET_FLAG_NONE, + FIND_CLOSEST, + false + ) + local next + local targetIdx = target:entindex() + for ____, e in ipairs(enemies) do + do + if not e or not e:IsAlive() then + goto __continue11 + end + if e:entindex() == targetIdx then + goto __continue11 + end + next = e + break + end + ::__continue11:: + end + if not next then + self.glaiveChain = nil + return + end + local nextState = {attacker = state.attacker, bouncesRemaining = state.bouncesRemaining - 1, damageMult = nextMult, falloffPct = state.falloffPct} + self:LaunchGlaive(target, next, nextState) +end +ability_luna_moon_glaive_custom = __TS__Decorate( + ability_luna_moon_glaive_custom, + ability_luna_moon_glaive_custom, + {registerAbility(nil)}, + {kind = "class", name = "ability_luna_moon_glaive_custom"} +) +____exports.ability_luna_moon_glaive_custom = ability_luna_moon_glaive_custom +____exports.modifier_luna_moon_glaive_passive = __TS__Class() +local modifier_luna_moon_glaive_passive = ____exports.modifier_luna_moon_glaive_passive +modifier_luna_moon_glaive_passive.name = "modifier_luna_moon_glaive_passive" +modifier_luna_moon_glaive_passive.____file_path = "scripts/vscripts/abilities/heroes/luna/ability_luna_moon_glaive_custom.lua" +__TS__ClassExtends(modifier_luna_moon_glaive_passive, BaseModifier) +function modifier_luna_moon_glaive_passive.prototype.IsHidden(self) + return true +end +function modifier_luna_moon_glaive_passive.prototype.IsPurgable(self) + return false +end +function modifier_luna_moon_glaive_passive.prototype.DeclareFunctions(self) + return {MODIFIER_EVENT_ON_ATTACK_LANDED} +end +function modifier_luna_moon_glaive_passive.prototype.OnAttackLanded(self, event) + if not IsServer() then + return + end + local parent = self:GetParent() + if parent:PassivesDisabled() then + return + end + if event.attacker ~= parent then + return + end + local primary = event.target + if not primary or not primary:IsAlive() then + return + end + local ability = self:GetAbility() + if not ability then + return + end + local bounceRange = ability:GetSpecialValueFor("bounce_range") + local totalBounces = math.floor(ability:GetSpecialValueFor("moon_glaive_bounce_count")) + local basePct = ability:GetSpecialValueFor("bounce_damage_pct") / 100 + local falloff = ability:GetSpecialValueFor("bounce_falloff_pct") + local enemies = FindUnitsInRadius( + parent:GetTeamNumber(), + primary:GetAbsOrigin(), + nil, + bounceRange, + DOTA_UNIT_TARGET_TEAM_ENEMY, + DOTA_UNIT_TARGET_HERO + DOTA_UNIT_TARGET_BASIC, + DOTA_UNIT_TARGET_FLAG_NONE, + FIND_CLOSEST, + false + ) + local first + local primaryIdx = primary:entindex() + for ____, e in ipairs(enemies) do + do + if not e or not e:IsAlive() then + goto __continue25 + end + if e:entindex() == primaryIdx then + goto __continue25 + end + first = e + break + end + ::__continue25:: + end + if not first or totalBounces <= 0 then + return + end + local state = {attacker = parent, bouncesRemaining = totalBounces - 1, damageMult = basePct, falloffPct = falloff} + ability:LaunchGlaive(primary, first, state) +end +modifier_luna_moon_glaive_passive = __TS__Decorate( + modifier_luna_moon_glaive_passive, + modifier_luna_moon_glaive_passive, + {registerModifier(nil)}, + {kind = "class", name = "modifier_luna_moon_glaive_passive"} +) +____exports.modifier_luna_moon_glaive_passive = modifier_luna_moon_glaive_passive +return ____exports diff --git a/scripts/vscripts/abilities/heroes/luna/ability_luna_twin_glaives_custom.lua b/scripts/vscripts/abilities/heroes/luna/ability_luna_twin_glaives_custom.lua new file mode 100644 index 0000000..f470cb6 --- /dev/null +++ b/scripts/vscripts/abilities/heroes/luna/ability_luna_twin_glaives_custom.lua @@ -0,0 +1,509 @@ +local ____lualib = require("lualib_bundle") +local __TS__Class = ____lualib.__TS__Class +local __TS__ClassExtends = ____lualib.__TS__ClassExtends +local __TS__Decorate = ____lualib.__TS__Decorate +local Set = ____lualib.Set +local __TS__New = ____lualib.__TS__New +local ____exports = {} +local ____dota_ts_adapter = require("lib.dota_ts_adapter") +local BaseAbility = ____dota_ts_adapter.BaseAbility +local BaseModifier = ____dota_ts_adapter.BaseModifier +local registerAbility = ____dota_ts_adapter.registerAbility +local registerModifier = ____dota_ts_adapter.registerModifier +local function applyTwinGlaiveFacetVuln(self, caster, victim, ability) + if not IsServer() then + return + end + if not victim or victim:IsNull() or not victim:IsAlive() then + return + end + local dur = ability:GetSpecialValueFor("twin_facet_vuln_duration") + if dur <= 0 then + return + end + victim:AddNewModifier(caster, ability, ____exports.modifier_luna_twin_glaives_facet_vuln.name, {duration = dur}) +end +--- Как в ванили Wild Axes / примере temple_guardian: vpcf цепляется к юниту оси, а не к linear projectile. +local WILD_AXE_PARTICLE = "particles/twin_glaves.vpcf" +local BEASTMASTER_AXE_UNIT = "npc_dota_beastmaster_axe" +local THINK_INTERVAL = 0.03 +--- Близко к владельцу — считаем, что ось вернулась +local RETURN_ARRIVE_DIST = 96 +--- Минимальный радиус «круга» урона вокруг оси (шире узкой линии) +local MIN_DAMAGE_RADIUS = 140 +--- Множитель к projectile_width для зоны урона +local DAMAGE_RADIUS_WIDTH_MULT = 2.25 +--- Слот W: слева/справа → дугой к цели (не прямиком) → перекрёстом назад; широкий круг урона; главная цель не промахивается. +____exports.ability_luna_twin_glaives_custom = __TS__Class() +local ability_luna_twin_glaives_custom = ____exports.ability_luna_twin_glaives_custom +ability_luna_twin_glaives_custom.name = "ability_luna_twin_glaives_custom" +ability_luna_twin_glaives_custom.____file_path = "scripts/vscripts/abilities/heroes/luna/ability_luna_twin_glaives_custom.lua" +__TS__ClassExtends(ability_luna_twin_glaives_custom, BaseAbility) +function ability_luna_twin_glaives_custom.prototype.Precache(self, context) + PrecacheResource("particle", WILD_AXE_PARTICLE, context) +end +function ability_luna_twin_glaives_custom.prototype.GetCastRange(self, location, target) + return self:GetSpecialValueFor("cast_range") +end +function ability_luna_twin_glaives_custom.prototype.OnSpellStart(self) + if not IsServer() then + return + end + local caster = self:GetCaster() + local cursor = self:GetCursorPosition() + local unitUnderCursor = self:GetCursorTarget() + local primaryIdx = unitUnderCursor and not unitUnderCursor:IsNull() and unitUnderCursor:IsAlive() and unitUnderCursor:GetTeamNumber() ~= caster:GetTeamNumber() and unitUnderCursor:entindex() or -1 + local origin = caster:GetAbsOrigin() + local speed = self:GetSpecialValueFor("projectile_speed") + local maxRange = self:GetSpecialValueFor("projectile_range") + local width = self:GetSpecialValueFor("projectile_width") + local spread = self:GetSpecialValueFor("axe_spread") + local damage = self:GetSpecialValueFor("glaive_damage") + local toTarget = cursor - origin + local flat = Vector(toTarget.x, toTarget.y, 0) + local len2d = flat:Length2D() + local fwd = len2d < 16 and Vector( + caster:GetForwardVector().x, + caster:GetForwardVector().y, + 0 + ):Normalized() or flat:Normalized() + local perp = Vector(-fwd.y, fwd.x, 0) + local lift = 72 + local left = Vector(origin.x + perp.x * spread, origin.y + perp.y * spread, origin.z + lift) + local right = Vector(origin.x - perp.x * spread, origin.y - perp.y * spread, origin.z + lift) + local mid = Vector((left.x + right.x) / 2, (left.y + right.y) / 2, (left.z + right.z) / 2) + local toEnemy = cursor - mid + local flatEn = Vector(toEnemy.x, toEnemy.y, 0) + local distEn = flatEn:Length2D() + local dirToEnemy = distEn < 8 and fwd or flatEn:Normalized() + local travel = math.min(distEn, maxRange) + local endPoint = Vector( + mid.x + dirToEnemy.x * travel, + mid.y + dirToEnemy.y * travel, + mid.z + toEnemy.z / math.max(distEn, 1) * travel * 0.35 + ) + caster:AddNewModifier(caster, self, ____exports.modifier_luna_twin_glaives_flight.name, { + s = speed, + w = width, + sp = spread, + dmg = damage, + lx = left.x, + ly = left.y, + lz = left.z, + rx = right.x, + ry = right.y, + rz = right.z, + ex = endPoint.x, + ey = endPoint.y, + ez = endPoint.z, + te = primaryIdx + }) + EmitSoundOn("Hero_Beastmaster.WildAxes", caster) +end +ability_luna_twin_glaives_custom = __TS__Decorate( + ability_luna_twin_glaives_custom, + ability_luna_twin_glaives_custom, + {registerAbility(nil)}, + {kind = "class", name = "ability_luna_twin_glaives_custom"} +) +____exports.ability_luna_twin_glaives_custom = ability_luna_twin_glaives_custom +____exports.modifier_luna_twin_glaives_flight = __TS__Class() +local modifier_luna_twin_glaives_flight = ____exports.modifier_luna_twin_glaives_flight +modifier_luna_twin_glaives_flight.name = "modifier_luna_twin_glaives_flight" +modifier_luna_twin_glaives_flight.____file_path = "scripts/vscripts/abilities/heroes/luna/ability_luna_twin_glaives_custom.lua" +__TS__ClassExtends(modifier_luna_twin_glaives_flight, BaseModifier) +function modifier_luna_twin_glaives_flight.prototype.____constructor(self, ...) + BaseModifier.prototype.____constructor(self, ...) + self.outSegmentLen = 1 + self.outT = 0 + self.returning = false + self.hitForward = __TS__New(Set) + self.hitBackward = __TS__New(Set) + self.primaryEntIndex = -1 + self.primaryOutboundEnsured = false + self.finished = false +end +function modifier_luna_twin_glaives_flight.prototype.setupAxeDummy(self, unit, caster, ability) + unit:AddNewModifier(caster, ability, "modifier_invulnerable", {}) + unit:AddNewModifier(caster, ability, "modifier_attack_immune", {}) + unit:AddNewModifier(caster, ability, "modifier_no_healthbar", {}) + unit:AddNewModifier(caster, ability, "modifier_phased", {}) + unit:SetDayTimeVisionRange(0) + unit:SetNightTimeVisionRange(0) + unit:SetAcquisitionRange(0) +end +function modifier_luna_twin_glaives_flight.prototype.IsHidden(self) + return true +end +function modifier_luna_twin_glaives_flight.prototype.IsDebuff(self) + return false +end +function modifier_luna_twin_glaives_flight.prototype.RemoveOnDeath(self) + return true +end +function modifier_luna_twin_glaives_flight.prototype.OnCreated(self, params) + if not IsServer() then + return + end + local parent = self:GetParent() + local ability = self:GetAbility() + if not ability or not parent or not parent:IsAlive() then + self:Destroy() + return + end + self.speed = params.s + self.damageRadius = math.max(MIN_DAMAGE_RADIUS, params.w * DAMAGE_RADIUS_WIDTH_MULT) + self.spread = params.sp + self.damage = params.dmg + self.startL = Vector(params.lx, params.ly, params.lz) + self.startR = Vector(params.rx, params.ry, params.rz) + self.posL = self.startL + self.posR = self.startR + self.endPoint = Vector(params.ex, params.ey, params.ez) + self.primaryEntIndex = params.te + self.controlL = self:bezierControlOutward(self.startL, self.endPoint, 1) + self.controlR = self:bezierControlOutward(self.startR, self.endPoint, -1) + local lenL = (self.endPoint - self.startL):Length() + local lenR = (self.endPoint - self.startR):Length() + self.outSegmentLen = math.max(48, (lenL + lenR) / 2 * 1.22) + local team = parent:GetTeamNumber() + self.hammerL = CreateUnitByName( + BEASTMASTER_AXE_UNIT, + self.startL, + false, + nil, + nil, + team + ) + self.hammerR = CreateUnitByName( + BEASTMASTER_AXE_UNIT, + self.startR, + false, + nil, + nil, + team + ) + if not self.hammerL or not self.hammerR then + self:cleanup() + self:Destroy() + return + end + self.hammerL:AddEffects(EF_NODRAW) + self.hammerR:AddEffects(EF_NODRAW) + self:setupAxeDummy(self.hammerL, parent, ability) + self:setupAxeDummy(self.hammerR, parent, ability) + self.pfxL = ParticleManager:CreateParticle(WILD_AXE_PARTICLE, PATTACH_CUSTOMORIGIN, nil) + ParticleManager:SetParticleControlEnt( + self.pfxL, + 0, + self.hammerL, + PATTACH_ABSORIGIN_FOLLOW, + nil, + self.hammerL:GetAbsOrigin(), + true + ) + self.pfxR = ParticleManager:CreateParticle(WILD_AXE_PARTICLE, PATTACH_CUSTOMORIGIN, nil) + ParticleManager:SetParticleControlEnt( + self.pfxR, + 0, + self.hammerR, + PATTACH_ABSORIGIN_FOLLOW, + nil, + self.hammerR:GetAbsOrigin(), + true + ) + self:StartIntervalThink(THINK_INTERVAL) +end +function modifier_luna_twin_glaives_flight.prototype.bezierControlOutward(self, start, ____end, sideSign) + local dx = ____end.x - start.x + local dy = ____end.y - start.y + local len2d = math.sqrt(dx * dx + dy * dy) + local midZ = (start.z + ____end.z) / 2 + local mid = Vector((start.x + ____end.x) / 2, (start.y + ____end.y) / 2, midZ) + if len2d < 12 then + return mid + end + local nx = -dy / len2d + local ny = dx / len2d + local bulge = math.min(len2d * 0.44, self.spread * 2.6) + return Vector(mid.x + nx * bulge * sideSign, mid.y + ny * bulge * sideSign, mid.z) +end +function modifier_luna_twin_glaives_flight.prototype.bezierQuadratic(self, p0, p1, p2, t) + local u = math.min( + 1, + math.max(0, 1 - t) + ) + local s = math.min( + 1, + math.max(0, t) + ) + return Vector(u * u * p0.x + 2 * u * s * p1.x + s * s * p2.x, u * u * p0.y + 2 * u * s * p1.y + s * s * p2.y, u * u * p0.z + 2 * u * s * p1.z + s * s * p2.z) +end +function modifier_luna_twin_glaives_flight.prototype.OnIntervalThink(self) + if not IsServer() or self.finished then + return + end + local parent = self:GetParent() + local ability = self:GetAbility() + if not parent or not parent:IsAlive() or not ability or not self.hammerL or not self.hammerR then + self:finish() + return + end + local dt = THINK_INTERVAL + if not self.returning then + self.outT = self.outT + self.speed * dt / self.outSegmentLen + if self.outT >= 1 then + self.outT = 1 + self.posL = self.endPoint + self.posR = self.endPoint + self.returning = true + else + self.posL = self:bezierQuadratic(self.startL, self.controlL, self.endPoint, self.outT) + self.posR = self:bezierQuadratic(self.startR, self.controlR, self.endPoint, self.outT) + end + self.hammerL:SetAbsOrigin(self.posL) + self.hammerR:SetAbsOrigin(self.posR) + self:damageAlongAxe( + self.hammerL:GetAbsOrigin(), + self.hitForward, + parent, + ability + ) + self:damageAlongAxe( + self.hammerR:GetAbsOrigin(), + self.hitForward, + parent, + ability + ) + if self.returning then + self:ensurePrimaryOutboundHit(parent, ability) + end + return + end + local lift = 72 + local fwd2 = Vector( + parent:GetForwardVector().x, + parent:GetForwardVector().y, + 0 + ) + local len = fwd2:Length2D() + local fwd = len > 0.01 and fwd2:Normalized() or Vector(1, 0, 0) + local perp = Vector(-fwd.y, fwd.x, 0) + local o = parent:GetAbsOrigin() + local shoulderL = Vector(o.x + perp.x * self.spread, o.y + perp.y * self.spread, o.z + lift) + local shoulderR = Vector(o.x - perp.x * self.spread, o.y - perp.y * self.spread, o.z + lift) + self.posL = self:moveTowards(self.posL, shoulderR, self.speed, dt) + self.posR = self:moveTowards(self.posR, shoulderL, self.speed, dt) + self.hammerL:SetAbsOrigin(self.posL) + self.hammerR:SetAbsOrigin(self.posR) + self:damageAlongAxe( + self.hammerL:GetAbsOrigin(), + self.hitBackward, + parent, + ability + ) + self:damageAlongAxe( + self.hammerR:GetAbsOrigin(), + self.hitBackward, + parent, + ability + ) + local dL = (shoulderR - self.posL):Length2D() + local dR = (shoulderL - self.posR):Length2D() + if dL < RETURN_ARRIVE_DIST and dR < RETURN_ARRIVE_DIST then + self:finish() + end +end +function modifier_luna_twin_glaives_flight.prototype.ensurePrimaryOutboundHit(self, caster, ability) + if self.primaryOutboundEnsured then + return + end + local pe = self.primaryEntIndex + if pe < 0 then + return + end + local ent = EntIndexToHScript(self.primaryEntIndex) + if not ent or ent:IsNull() or not ent:IsAlive() then + self.primaryOutboundEnsured = true + return + end + if ent:GetTeamNumber() == caster:GetTeamNumber() then + self.primaryOutboundEnsured = true + return + end + if self.hitForward:has(self.primaryEntIndex) then + self.primaryOutboundEnsured = true + return + end + ApplyDamage({ + victim = ent, + attacker = caster, + damage = self.damage + self:GetCaster():GetAverageTrueAttackDamage(ent) * 0.5, + damage_type = DAMAGE_TYPE_PHYSICAL, + damage_flags = DOTA_DAMAGE_FLAG_NO_SPELL_AMPLIFICATION, + ability = ability + }) + ApplyDamage({ + victim = ent, + attacker = caster, + damage = self.damage + self:GetCaster():GetAverageTrueAttackDamage(ent) * 0.5, + damage_type = DAMAGE_TYPE_MAGICAL, + damage_flags = DOTA_DAMAGE_FLAG_ATTACK_MODIFIER, + ability = ability + }) + applyTwinGlaiveFacetVuln(nil, caster, ent, ability) + self.hitForward:add(self.primaryEntIndex) + self.primaryOutboundEnsured = true +end +function modifier_luna_twin_glaives_flight.prototype.moveTowards(self, from, to, speed, dt) + local delta = to - from + local dist = delta:Length() + if dist < 1 then + return to + end + local step = math.min(speed * dt, dist) + local dir = delta:Normalized() + return from + dir * step +end +function modifier_luna_twin_glaives_flight.prototype.damageAlongAxe(self, pos, hitSet, caster, ability) + local enemies = FindUnitsInRadius( + caster:GetTeamNumber(), + pos, + nil, + self.damageRadius, + DOTA_UNIT_TARGET_TEAM_ENEMY, + DOTA_UNIT_TARGET_HERO + DOTA_UNIT_TARGET_BASIC, + DOTA_UNIT_TARGET_FLAG_NONE, + FIND_ANY_ORDER, + false + ) + for ____, enemy in ipairs(enemies) do + do + if not enemy or enemy:IsNull() or not enemy:IsAlive() then + goto __continue38 + end + local idx = enemy:entindex() + if hitSet:has(idx) then + goto __continue38 + end + hitSet:add(idx) + ApplyDamage({ + victim = enemy, + attacker = caster, + damage = self.damage + self:GetCaster():GetAverageTrueAttackDamage(enemy) * 0.5, + damage_type = DAMAGE_TYPE_PHYSICAL, + damage_flags = DOTA_DAMAGE_FLAG_NO_SPELL_AMPLIFICATION, + ability = ability + }) + ApplyDamage({ + victim = enemy, + attacker = caster, + damage = self.damage + self:GetCaster():GetAverageTrueAttackDamage(enemy) * 0.5, + damage_type = DAMAGE_TYPE_MAGICAL, + damage_flags = DOTA_DAMAGE_FLAG_ATTACK_MODIFIER, + ability = ability + }) + applyTwinGlaiveFacetVuln(nil, caster, enemy, ability) + end + ::__continue38:: + end +end +function modifier_luna_twin_glaives_flight.prototype.OnDestroy(self) + if not IsServer() then + return + end + self:cleanup() +end +function modifier_luna_twin_glaives_flight.prototype.finish(self) + if self.finished then + return + end + self.finished = true + self:StartIntervalThink(-1) + self:Destroy() +end +function modifier_luna_twin_glaives_flight.prototype.cleanup(self) + if self.pfxL ~= nil then + ParticleManager:DestroyParticle(self.pfxL, false) + ParticleManager:ReleaseParticleIndex(self.pfxL) + self.pfxL = nil + end + if self.pfxR ~= nil then + ParticleManager:DestroyParticle(self.pfxR, false) + ParticleManager:ReleaseParticleIndex(self.pfxR) + self.pfxR = nil + end + if self.hammerL then + UTIL_Remove(self.hammerL) + self.hammerL = nil + end + if self.hammerR then + UTIL_Remove(self.hammerR) + self.hammerR = nil + end +end +modifier_luna_twin_glaives_flight = __TS__Decorate( + modifier_luna_twin_glaives_flight, + modifier_luna_twin_glaives_flight, + {registerModifier(nil)}, + {kind = "class", name = "modifier_luna_twin_glaives_flight"} +) +____exports.modifier_luna_twin_glaives_flight = modifier_luna_twin_glaives_flight +____exports.modifier_luna_twin_glaives_facet_vuln = __TS__Class() +local modifier_luna_twin_glaives_facet_vuln = ____exports.modifier_luna_twin_glaives_facet_vuln +modifier_luna_twin_glaives_facet_vuln.name = "modifier_luna_twin_glaives_facet_vuln" +modifier_luna_twin_glaives_facet_vuln.____file_path = "scripts/vscripts/abilities/heroes/luna/ability_luna_twin_glaives_custom.lua" +__TS__ClassExtends(modifier_luna_twin_glaives_facet_vuln, BaseModifier) +function modifier_luna_twin_glaives_facet_vuln.prototype.IsHidden(self) + return false +end +function modifier_luna_twin_glaives_facet_vuln.prototype.IsDebuff(self) + return true +end +function modifier_luna_twin_glaives_facet_vuln.prototype.IsPurgable(self) + return true +end +function modifier_luna_twin_glaives_facet_vuln.prototype.OnRefresh(self, params) + local ____self_SetDuration_2 = self.SetDuration + local ____opt_0 = self:GetAbility() + ____self_SetDuration_2( + self, + ____opt_0 and ____opt_0:GetSpecialValueFor("twin_facet_vuln_duration"), + true + ) + self:IncrementStackCount() +end +function modifier_luna_twin_glaives_facet_vuln.prototype.DeclareFunctions(self) + return {MODIFIER_PROPERTY_INCOMING_DAMAGE_PERCENTAGE, MODIFIER_PROPERTY_INCOMING_PHYSICAL_DAMAGE_PERCENTAGE} +end +function modifier_luna_twin_glaives_facet_vuln.prototype.GetModifierIncomingDamage_Percentage(self, event) + local ab = self:GetAbility() + if not ab then + return 0 + end + local pct = ab:GetSpecialValueFor("twin_facet_pct") + local value = self:GetStackCount() * (pct / 100) + print(value) + return self:GetStackCount() * pct +end +function modifier_luna_twin_glaives_facet_vuln.prototype.GetModifierIncomingPhysicalDamage_Percentage(self, event) + local ab = self:GetAbility() + if not ab then + return 0 + end + local pct = ab:GetSpecialValueFor("twin_facet_pct") + local value = self:GetStackCount() * (pct / 100) + print(value) + return self:GetStackCount() * pct +end +function modifier_luna_twin_glaives_facet_vuln.prototype.GetTexture(self) + return "luna_lunar_orbit" +end +modifier_luna_twin_glaives_facet_vuln = __TS__Decorate( + modifier_luna_twin_glaives_facet_vuln, + modifier_luna_twin_glaives_facet_vuln, + {registerModifier(nil)}, + {kind = "class", name = "modifier_luna_twin_glaives_facet_vuln"} +) +____exports.modifier_luna_twin_glaives_facet_vuln = modifier_luna_twin_glaives_facet_vuln +return ____exports diff --git a/scripts/vscripts/abilities/heroes/luna/luna_lucent_beam_shared.lua b/scripts/vscripts/abilities/heroes/luna/luna_lucent_beam_shared.lua new file mode 100644 index 0000000..51274d1 --- /dev/null +++ b/scripts/vscripts/abilities/heroes/luna/luna_lucent_beam_shared.lua @@ -0,0 +1,161 @@ +--[[ Generated with https://github.com/TypeScriptToLua/TypeScriptToLua ]] +local ____exports = {} +function ____exports.getLunaMoonGlaiveAbility(self, caster) + return caster:FindAbilityByName(____exports.LUNA_MOON_GLAIVE_ABILITY) or nil +end +--- Фасет «лунное благословение» на пассиве Moon Glaives: 2× PerformAttack по цели удара Q/ульты. +function ____exports.tryLunarBlessingFacetPerformAttack(self, caster, victim) + if not IsServer() then + return + end + if not victim or victim:IsNull() or not victim:IsAlive() then + return + end + local moon = ____exports.getLunaMoonGlaiveAbility(nil, caster) + if not moon then + return + end + if moon:GetSpecialValueFor("moon_facet_perform_attack") <= 0 then + return + end + caster:PerformAttack( + victim, + true, + true, + true, + false, + false, + false, + true + ) + caster:PerformAttack( + victim, + true, + true, + true, + false, + false, + false, + true + ) +end +--- Имя Q — параметры AoE фасета читаются только отсюда. +____exports.LUNA_LUCENT_BEAM_ABILITY = "ability_luna_lucent_beam_custom" +____exports.LUNA_MOON_GLAIVE_ABILITY = "ability_luna_moon_glaive_custom" +function ____exports.getLunaLucentBeamAbility(self, caster) + return caster:FindAbilityByName(____exports.LUNA_LUCENT_BEAM_ABILITY) or nil +end +--- Фасет Lucent Beam: AoE вокруг точки удара. +function ____exports.isLucentBeamFacetAoeActive(self, caster) + local q = ____exports.getLunaLucentBeamAbility(nil, caster) + if not q then + return false + end + return q:GetSpecialValueFor("lucent_beam_aoe_radius") > 0 +end +--- Луч Lucent на врага — CP на attach_hitloc. +function ____exports.playLucentBeamVfxOnTarget(self, target) + local pos = target:GetAbsOrigin() + local pfx = ParticleManager:CreateParticle("particles/units/heroes/hero_luna/luna_lucent_beam.vpcf", PATTACH_ABSORIGIN, target) + ParticleManager:SetParticleControl(pfx, 1, pos) + ParticleManager:SetParticleControlEnt( + pfx, + 2, + target, + PATTACH_POINT_FOLLOW, + "attach_hitloc", + target:GetAbsOrigin(), + true + ) + ParticleManager:SetParticleControlEnt( + pfx, + 5, + target, + PATTACH_POINT_FOLLOW, + "attach_hitloc", + target:GetAbsOrigin(), + true + ) + ParticleManager:SetParticleControlEnt( + pfx, + 6, + target, + PATTACH_POINT_FOLLOW, + "attach_hitloc", + target:GetAbsOrigin(), + true + ) + ParticleManager:ReleaseParticleIndex(pfx) +end +--- Урон + мини-стан Lucent по главной цели; при фасете Q — AoE вокруг неё. +-- После удара — фасет Lunar Blessing: 2× PerformAttack по цели (через Moon Glaives KV). +function ____exports.applyLucentBeamHit(self, caster, damageAbility, primary, damage, stunDuration) + if not IsServer() then + return + end + if not primary or primary:IsNull() or not primary:IsAlive() then + return + end + local lunaQ = ____exports.getLunaLucentBeamAbility(nil, caster) + ____exports.playLucentBeamVfxOnTarget(nil, primary) + EmitSoundOnLocationWithCaster( + primary:GetAbsOrigin(), + "Hero_Luna.LucentBeam.Target", + caster + ) + ApplyDamage({ + victim = primary, + attacker = caster, + damage = damage, + damage_type = DAMAGE_TYPE_MAGICAL, + ability = damageAbility + }) + primary:AddNewModifier(caster, damageAbility, "modifier_stunned", {duration = stunDuration}) + if lunaQ and ____exports.isLucentBeamFacetAoeActive(nil, caster) then + local radius = lunaQ:GetSpecialValueFor("lucent_beam_aoe_radius") + local pct = lunaQ:GetSpecialValueFor("lucent_beam_aoe_damage_pct") / 100 + local secondaryDmg = damage * pct + local secondaryStun = stunDuration * pct + local others = FindUnitsInRadius( + caster:GetTeamNumber(), + primary:GetAbsOrigin(), + nil, + radius, + DOTA_UNIT_TARGET_TEAM_ENEMY, + DOTA_UNIT_TARGET_HERO + DOTA_UNIT_TARGET_BASIC, + DOTA_UNIT_TARGET_FLAG_NONE, + FIND_ANY_ORDER, + false + ) + local primaryIdx = primary:entindex() + for ____, u in ipairs(others) do + do + if not u or u:IsNull() or not u:IsAlive() then + goto __continue11 + end + if u:entindex() == primaryIdx then + goto __continue11 + end + ____exports.playLucentBeamVfxOnTarget(nil, u) + EmitSoundOnLocationWithCaster( + u:GetAbsOrigin(), + "Hero_Luna.LucentBeam.Target", + caster + ) + ApplyDamage({ + victim = u, + attacker = caster, + damage = secondaryDmg, + damage_type = DAMAGE_TYPE_MAGICAL, + ability = damageAbility + }) + if secondaryStun > 0 then + u:AddNewModifier(caster, damageAbility, "modifier_stunned", {duration = secondaryStun}) + end + end + ::__continue11:: + end + end + ____exports.tryLunarBlessingFacetPerformAttack(nil, caster, primary) +end +return ____exports diff --git a/scripts/vscripts/abilities/heroes/mirana/ability_mirana_innate_custom.lua b/scripts/vscripts/abilities/heroes/mirana/ability_mirana_innate_custom.lua new file mode 100644 index 0000000..3ef6e60 --- /dev/null +++ b/scripts/vscripts/abilities/heroes/mirana/ability_mirana_innate_custom.lua @@ -0,0 +1,98 @@ +local ____lualib = require("lualib_bundle") +local __TS__Class = ____lualib.__TS__Class +local __TS__ClassExtends = ____lualib.__TS__ClassExtends +local __TS__Decorate = ____lualib.__TS__Decorate +local ____exports = {} +local ____dota_ts_adapter = require("lib.dota_ts_adapter") +local BaseAbility = ____dota_ts_adapter.BaseAbility +local BaseModifier = ____dota_ts_adapter.BaseModifier +local registerAbility = ____dota_ts_adapter.registerAbility +local registerModifier = ____dota_ts_adapter.registerModifier +local ____moon_sisters_synergy = require("abilities.heroes.mirana_luna.moon_sisters_synergy") +local ensureMoonSistersInnateModifier = ____moon_sisters_synergy.ensureMoonSistersInnateModifier +local removeMoonSistersInnateModifier = ____moon_sisters_synergy.removeMoonSistersInnateModifier +local ____ability_mirana_leap_custom = require("abilities.heroes.mirana.ability_mirana_leap_custom") +local modifier_mirana_leap_custom_buff = ____ability_mirana_leap_custom.modifier_mirana_leap_custom_buff +____exports.ability_mirana_innate_custom = __TS__Class() +local ability_mirana_innate_custom = ____exports.ability_mirana_innate_custom +ability_mirana_innate_custom.name = "ability_mirana_innate_custom" +ability_mirana_innate_custom.____file_path = "scripts/vscripts/abilities/heroes/mirana/ability_mirana_innate_custom.lua" +__TS__ClassExtends(ability_mirana_innate_custom, BaseAbility) +function ability_mirana_innate_custom.prototype.GetIntrinsicModifierName(self) + return ____exports.modifier_mirana_celestial_quiver_custom.name +end +function ability_mirana_innate_custom.prototype.GetAOERadius(self) + return self:GetSpecialValueFor("moon_sisters_radius") +end +ability_mirana_innate_custom = __TS__Decorate( + ability_mirana_innate_custom, + ability_mirana_innate_custom, + {registerAbility(nil)}, + {kind = "class", name = "ability_mirana_innate_custom"} +) +____exports.ability_mirana_innate_custom = ability_mirana_innate_custom +____exports.modifier_mirana_celestial_quiver_custom = __TS__Class() +local modifier_mirana_celestial_quiver_custom = ____exports.modifier_mirana_celestial_quiver_custom +modifier_mirana_celestial_quiver_custom.name = "modifier_mirana_celestial_quiver_custom" +modifier_mirana_celestial_quiver_custom.____file_path = "scripts/vscripts/abilities/heroes/mirana/ability_mirana_innate_custom.lua" +__TS__ClassExtends(modifier_mirana_celestial_quiver_custom, BaseModifier) +function modifier_mirana_celestial_quiver_custom.prototype.IsHidden(self) + return false +end +function modifier_mirana_celestial_quiver_custom.prototype.IsDebuff(self) + return false +end +function modifier_mirana_celestial_quiver_custom.prototype.IsPurgable(self) + return false +end +function modifier_mirana_celestial_quiver_custom.prototype.OnCreated(self) + local ability = self:GetAbility() + if ability then + ensureMoonSistersInnateModifier( + nil, + self:GetParent(), + ability + ) + end +end +function modifier_mirana_celestial_quiver_custom.prototype.OnDestroy(self) + removeMoonSistersInnateModifier( + nil, + self:GetParent() + ) +end +function modifier_mirana_celestial_quiver_custom.prototype.DeclareFunctions(self) + return {MODIFIER_PROPERTY_PROCATTACK_BONUS_DAMAGE_MAGICAL} +end +function modifier_mirana_celestial_quiver_custom.prototype.GetModifierProcAttack_BonusDamage_Magical(self, event) + local parent = self:GetParent() + if event.attacker ~= parent then + return 0 + end + if parent:IsIllusion() or not parent:IsRangedAttacker() then + return 0 + end + if parent:PassivesDisabled() then + return 0 + end + local ability = self:GetAbility() + if not ability then + return 0 + end + local manaPct = ability:GetSpecialValueFor("mana_damage_pct") + local damage = parent:GetMaxMana() * (manaPct / 100) + if parent:HasModifier(modifier_mirana_leap_custom_buff.name) then + local leap = parent:FindAbilityByName("ability_mirana_leap_custom") + local multiplier = leap and leap:GetSpecialValueFor("innate_damage_multiplier") or 4 + damage = damage * multiplier + end + return damage +end +modifier_mirana_celestial_quiver_custom = __TS__Decorate( + modifier_mirana_celestial_quiver_custom, + modifier_mirana_celestial_quiver_custom, + {registerModifier(nil)}, + {kind = "class", name = "modifier_mirana_celestial_quiver_custom"} +) +____exports.modifier_mirana_celestial_quiver_custom = modifier_mirana_celestial_quiver_custom +return ____exports diff --git a/scripts/vscripts/abilities/heroes/mirana/ability_mirana_leap_custom.lua b/scripts/vscripts/abilities/heroes/mirana/ability_mirana_leap_custom.lua new file mode 100644 index 0000000..21aa361 --- /dev/null +++ b/scripts/vscripts/abilities/heroes/mirana/ability_mirana_leap_custom.lua @@ -0,0 +1,511 @@ +local ____lualib = require("lualib_bundle") +local __TS__Class = ____lualib.__TS__Class +local __TS__ClassExtends = ____lualib.__TS__ClassExtends +local __TS__Decorate = ____lualib.__TS__Decorate +local ____exports = {} +local modifier_mirana_leap_charges, modifier_mirana_leap_arc +local ____dota_ts_adapter = require("lib.dota_ts_adapter") +local BaseAbility = ____dota_ts_adapter.BaseAbility +local BaseModifier = ____dota_ts_adapter.BaseModifier +local BaseModifierMotionBoth = ____dota_ts_adapter.BaseModifierMotionBoth +local registerAbility = ____dota_ts_adapter.registerAbility +local registerModifier = ____dota_ts_adapter.registerModifier +local ____mirana_mana_damage = require("abilities.heroes.mirana.mirana_mana_damage") +local getMiranaManaBonusDamage = ____mirana_mana_damage.getMiranaManaBonusDamage +--- Партиклы Leap (ванильные пути Dota 2). +local LEAP_PFX_START = "particles/econ/items/mirana/mirana_ti8_immortal_mount/mirana_ti8_immortal_leap_start_embers.vpcf" +local LEAP_PFX_TRAIL = "particles/econ/items/mirana/mirana_ti8_immortal_mount/mirana_ti8_immortal_leap_trail.vpcf" +local LEAP_PFX_END = "particles/neutral_fx/ogre_bruiser_smash.vpcf" +local TALENT_LEAP_AIR_STRIKE = "special_bonus_unique_mirana_leap_air_strike" +local LEAP_AIR_STRIKE_PFX = "particles/units/heroes/hero_mirana/mirana_base_attack.vpcf" +____exports.ability_mirana_leap_custom = __TS__Class() +local ability_mirana_leap_custom = ____exports.ability_mirana_leap_custom +ability_mirana_leap_custom.name = "ability_mirana_leap_custom" +ability_mirana_leap_custom.____file_path = "scripts/vscripts/abilities/heroes/mirana/ability_mirana_leap_custom.lua" +__TS__ClassExtends(ability_mirana_leap_custom, BaseAbility) +function ability_mirana_leap_custom.prototype.GetIntrinsicModifierName(self) + return modifier_mirana_leap_charges.name +end +function ability_mirana_leap_custom.prototype.GetMaxCharges(self) + return self:GetSpecialValueFor("max_charges") +end +function ability_mirana_leap_custom.prototype.Precache(self, context) + PrecacheResource("particle", LEAP_PFX_START, context) + PrecacheResource("particle", LEAP_PFX_TRAIL, context) + PrecacheResource("particle", LEAP_PFX_END, context) + PrecacheResource("particle", LEAP_AIR_STRIKE_PFX, context) + PrecacheResource("soundfile", "soundevents/game_sounds_heroes/game_sounds_mirana.vsndevts", context) +end +function ability_mirana_leap_custom.prototype.GetBehavior(self) + return bit.bor( + bit.bor(DOTA_ABILITY_BEHAVIOR_NO_TARGET, DOTA_ABILITY_BEHAVIOR_ROOT_DISABLES), + DOTA_ABILITY_BEHAVIOR_IGNORE_BACKSWING + ) +end +function ability_mirana_leap_custom.prototype.GetCastRange(self, _location, _target) + return self:GetSpecialValueFor("leap_distance") +end +function ability_mirana_leap_custom.prototype.GetManaCost(self, _level) + local caster = self:GetCaster() + if not caster then + return 0 + end + local pct = self:GetSpecialValueFor("mana_cost_pct") + return math.max( + 0, + math.floor(caster:GetMaxMana() * pct * 0.01) + ) +end +function ability_mirana_leap_custom.prototype.CastFilterResult(self) + if self:GetCaster():HasModifier(modifier_mirana_leap_arc.name) then + return UF_FAIL_CUSTOM + end + return UF_SUCCESS +end +function ability_mirana_leap_custom.prototype.GetCustomCastError(self) + if self:GetCaster():HasModifier(modifier_mirana_leap_arc.name) then + return "#dota_hud_error_mirana_leap_in_air" + end + return "" +end +function ability_mirana_leap_custom.prototype.OnSpellStart(self) + if not IsServer() then + return + end + local caster = self:GetCaster() + if caster:HasModifier(modifier_mirana_leap_arc.name) then + return + end + local distance = self:GetSpecialValueFor("leap_distance") + caster:GetCastRangeBonus() + local speed = self:GetSpecialValueFor("leap_speed") + local height = self:GetSpecialValueFor("leap_height") + local buffDuration = self:GetSpecialValueFor("leap_bonus_duration") + ProjectileManager:ProjectileDodge(caster) + caster:AddNewModifier(caster, self, ____exports.modifier_mirana_leap_custom_buff.name, {duration = buffDuration}) + caster:AddNewModifier(caster, self, modifier_mirana_leap_arc.name, {distance = distance, speed = speed, height = height}) + local origin = caster:GetAbsOrigin() + local startPfx = ParticleManager:CreateParticle(LEAP_PFX_START, PATTACH_WORLDORIGIN, nil) + ParticleManager:SetParticleControl(startPfx, 0, origin) + ParticleManager:ReleaseParticleIndex(startPfx) + EmitSoundOn("Ability.Leap", caster) +end +function ability_mirana_leap_custom.prototype.getAirStrikeRadius(self, caster) + return math.max( + 0, + caster:Script_GetAttackRange() + ) +end +function ability_mirana_leap_custom.prototype.applyLandingDamage(self, caster, landPos) + if not IsServer() then + return + end + local radius = self:GetSpecialValueFor("leap_landing_radius") + local damagePct = self:GetSpecialValueFor("leap_landing_damage_pct") + local manaDamage = getMiranaManaBonusDamage(nil, caster, self) + if radius <= 0 or damagePct <= 0 and manaDamage <= 0 then + return + end + local enemies = FindUnitsInRadius( + caster:GetTeamNumber(), + landPos, + nil, + radius, + DOTA_UNIT_TARGET_TEAM_ENEMY, + bit.bor(DOTA_UNIT_TARGET_HERO, DOTA_UNIT_TARGET_BASIC), + DOTA_UNIT_TARGET_FLAG_NONE, + FIND_ANY_ORDER, + false + ) + for ____, enemy in ipairs(enemies) do + do + if not enemy or enemy:IsNull() or not enemy:IsAlive() then + goto __continue20 + end + local attackDamage = caster:GetAverageTrueAttackDamage(enemy) * (damagePct / 100) + if attackDamage > 0 then + ApplyDamage({ + victim = enemy, + attacker = caster, + damage = attackDamage, + damage_type = DAMAGE_TYPE_PHYSICAL, + ability = self + }) + end + if manaDamage > 0 then + ApplyDamage({ + victim = enemy, + attacker = caster, + damage = manaDamage, + damage_type = DAMAGE_TYPE_MAGICAL, + ability = self + }) + end + end + ::__continue20:: + end +end +function ability_mirana_leap_custom.prototype.getAirStrikeProjectileSpeed(self, caster) + local fromKv = self:GetSpecialValueFor("talent_air_strike_projectile_speed") + if fromKv > 0 then + return fromKv + end + return math.max( + 900, + caster:GetProjectileSpeed() + ) +end +function ability_mirana_leap_custom.prototype.applyAirStrike(self, caster, center, struckVictims) + if not IsServer() then + return + end + if not HasTalent(nil, caster, TALENT_LEAP_AIR_STRIKE) then + return + end + local radius = self:getAirStrikeRadius(caster) + if radius <= 0 then + return + end + local enemies = FindUnitsInRadius( + caster:GetTeamNumber(), + center, + nil, + radius, + DOTA_UNIT_TARGET_TEAM_ENEMY, + bit.bor(DOTA_UNIT_TARGET_HERO, DOTA_UNIT_TARGET_BASIC), + DOTA_UNIT_TARGET_FLAG_NONE, + FIND_ANY_ORDER, + false + ) + local projectileSpeed = self:getAirStrikeProjectileSpeed(caster) + for ____, enemy in ipairs(enemies) do + do + if not enemy or enemy:IsNull() or not enemy:IsAlive() then + goto __continue31 + end + local entIndex = enemy:entindex() + if struckVictims[entIndex] then + goto __continue31 + end + struckVictims[entIndex] = true + ProjectileManager:CreateTrackingProjectile({ + Ability = self, + EffectName = LEAP_AIR_STRIKE_PFX, + Source = caster, + Target = enemy, + iMoveSpeed = projectileSpeed, + bDodgeable = true, + bVisibleToEnemies = true, + bReplaceExisting = false, + bProvidesVision = false, + ExtraData = {target = entIndex} + }) + end + ::__continue31:: + end +end +function ability_mirana_leap_custom.prototype.OnProjectileHit_ExtraData(self, target, _location, extraData) + if not IsServer() then + return + end + if not target or target:IsNull() or not target:IsAlive() then + return + end + if extraData.target ~= target:entindex() then + return + end + local caster = self:GetCaster() + if not caster or caster:IsNull() or not caster:IsAlive() then + return + end + caster:PerformAttack( + target, + true, + true, + true, + false, + false, + false, + true + ) + EmitSoundOn("Hero_Mirana.ArrowImpact", target) + return true +end +ability_mirana_leap_custom = __TS__Decorate( + ability_mirana_leap_custom, + ability_mirana_leap_custom, + {registerAbility(nil)}, + {kind = "class", name = "ability_mirana_leap_custom"} +) +____exports.ability_mirana_leap_custom = ability_mirana_leap_custom +modifier_mirana_leap_charges = __TS__Class() +modifier_mirana_leap_charges.name = "modifier_mirana_leap_charges" +modifier_mirana_leap_charges.____file_path = "scripts/vscripts/abilities/heroes/mirana/ability_mirana_leap_custom.lua" +__TS__ClassExtends(modifier_mirana_leap_charges, BaseModifier) +function modifier_mirana_leap_charges.prototype.____constructor(self, ...) + BaseModifier.prototype.____constructor(self, ...) + self.syncedMaxCharges = 0 +end +function modifier_mirana_leap_charges.prototype.IsHidden(self) + return true +end +function modifier_mirana_leap_charges.prototype.IsPurgable(self) + return false +end +function modifier_mirana_leap_charges.prototype.OnCreated(self) + if not IsServer() then + return + end + self:syncCharges() + self.levelUpListener = ListenToGameEvent( + "dota_player_gained_level", + function(event) + local parent = self:GetParent() + if not parent or not parent:IsHero() or parent:IsIllusion() then + return + end + local playerId = parent:GetPlayerOwnerID() + if playerId == nil or playerId < 0 or event.player ~= playerId then + return + end + self:syncCharges() + end, + nil + ) +end +function modifier_mirana_leap_charges.prototype.OnDestroy(self) + if self.levelUpListener ~= nil then + StopListeningToGameEvent(self.levelUpListener) + self.levelUpListener = nil + end +end +function modifier_mirana_leap_charges.prototype.syncCharges(self) + local ability = self:GetAbility() + if not ability or ability:IsNull() then + return + end + local max = ability:GetMaxCharges() + if max <= 0 then + return + end + local current = ability:GetCurrentAbilityCharges() + if self.syncedMaxCharges <= 0 then + ability:SetCurrentAbilityCharges(max) + elseif max > self.syncedMaxCharges then + ability:SetCurrentAbilityCharges(math.min(current + (max - self.syncedMaxCharges), max)) + else + ability:SetCurrentAbilityCharges(math.min(current, max)) + end + self.syncedMaxCharges = max +end +modifier_mirana_leap_charges = __TS__Decorate( + modifier_mirana_leap_charges, + modifier_mirana_leap_charges, + {registerModifier(nil)}, + {kind = "class", name = "modifier_mirana_leap_charges"} +) +modifier_mirana_leap_arc = __TS__Class() +modifier_mirana_leap_arc.name = "modifier_mirana_leap_arc" +modifier_mirana_leap_arc.____file_path = "scripts/vscripts/abilities/heroes/mirana/ability_mirana_leap_custom.lua" +__TS__ClassExtends(modifier_mirana_leap_arc, BaseModifierMotionBoth) +function modifier_mirana_leap_arc.prototype.____constructor(self, ...) + BaseModifierMotionBoth.prototype.____constructor(self, ...) + self.direction = Vector(0, 0, 0) + self.duration = 0 + self.distance = 0 + self.speed = 0 + self.height = 0 + self.const1 = 0 + self.const2 = 0 + self.targetPos = Vector(0, 0, 0) + self.airStrikeVictims = {} +end +function modifier_mirana_leap_arc.prototype.IsHidden(self) + return true +end +function modifier_mirana_leap_arc.prototype.IsPurgable(self) + return false +end +function modifier_mirana_leap_arc.prototype.OnCreated(self, params) + if not IsServer() then + return + end + local parent = self:GetParent() + local origin = parent:GetAbsOrigin() + self.distance = math.max(1, params.distance or 575) + self.speed = math.max(1, params.speed or 1300) + self.height = params.height or 150 + self.duration = self.distance / self.speed + self.direction = parent:GetForwardVector() + self.direction.z = 0 + self.direction = self.direction:Normalized() + self.targetPos = GetGroundPosition(origin + self.direction * self.distance, parent) + if not self:ApplyHorizontalMotionController() or not self:ApplyVerticalMotionController() then + self:Destroy() + return + end + local heightStart = GetGroundHeight(origin, parent) + local heightEnd = GetGroundHeight(self.targetPos, parent) + local tempMin = heightStart + local tempMax = heightEnd + if tempMin > tempMax then + local swap = tempMin + tempMin = tempMax + tempMax = swap + end + local deltaH = (tempMax - tempMin) * 2 / 3 + local heightMax = tempMin + deltaH + self.height + heightEnd = heightEnd - heightStart + heightMax = heightMax - heightStart + if heightMax < heightEnd then + heightMax = heightEnd + 0.01 + end + if heightMax <= 0 then + heightMax = 0.01 + end + local durationEnd = (1 + math.sqrt(1 - heightEnd / heightMax)) / 2 + self.const1 = 4 * heightMax * durationEnd / self.duration + self.const2 = 4 * heightMax * durationEnd * durationEnd / (self.duration * self.duration) + self.trailPfx = ParticleManager:CreateParticle(LEAP_PFX_TRAIL, PATTACH_ABSORIGIN_FOLLOW, parent) + self.airStrikeVictims = {} + if HasTalent(nil, parent, TALENT_LEAP_AIR_STRIKE) then + local ability = self:GetAbility() + if ability ~= nil then + ability:applyAirStrike( + parent, + parent:GetAbsOrigin(), + self.airStrikeVictims + ) + end + self:StartIntervalThink(0.12) + end +end +function modifier_mirana_leap_arc.prototype.OnIntervalThink(self) + if not IsServer() then + return + end + if self:GetElapsedTime() >= self.duration then + self:StartIntervalThink(-1) + return + end + local parent = self:GetParent() + local ability = self:GetAbility() + if not ability or not HasTalent(nil, parent, TALENT_LEAP_AIR_STRIKE) then + self:StartIntervalThink(-1) + return + end + ability:applyAirStrike( + parent, + parent:GetAbsOrigin(), + self.airStrikeVictims + ) +end +function modifier_mirana_leap_arc.prototype.OnDestroy(self) + if not IsServer() then + return + end + local parent = self:GetParent() + parent:RemoveHorizontalMotionController(self) + parent:RemoveVerticalMotionController(self) + if self.trailPfx ~= nil then + ParticleManager:DestroyParticle(self.trailPfx, false) + ParticleManager:ReleaseParticleIndex(self.trailPfx) + self.trailPfx = nil + end + FindClearSpaceForUnit(parent, self.targetPos, true) + local landPos = parent:GetAbsOrigin() + local ability = self:GetAbility() + if ability and not ability:IsNull() then + ability:applyLandingDamage(parent, landPos) + end + local landingRadius = ability and ability:GetSpecialValueFor("leap_landing_radius") or 175 + local endPfx = ParticleManager:CreateParticle(LEAP_PFX_END, PATTACH_WORLDORIGIN, nil) + ParticleManager:SetParticleControl(endPfx, 0, landPos) + ParticleManager:SetParticleControl( + endPfx, + 1, + Vector(landingRadius, 0, 0) + ) + ParticleManager:ReleaseParticleIndex(endPfx) + parent:StartGesture(ACT_DOTA_CAST_ABILITY_3_END) +end +function modifier_mirana_leap_arc.prototype.GetVerticalSpeed(self, time) + return self.const1 - 2 * self.const2 * time +end +function modifier_mirana_leap_arc.prototype.UpdateHorizontalMotion(self, me, dt) + if self:GetElapsedTime() >= self.duration then + return + end + local pos = me:GetOrigin() + self.direction * self.speed * dt + me:SetOrigin(pos) +end +function modifier_mirana_leap_arc.prototype.UpdateVerticalMotion(self, me, dt) + if self:GetElapsedTime() >= self.duration then + self:Destroy() + return + end + local pos = me:GetOrigin() + pos.z = pos.z + self:GetVerticalSpeed(self:GetElapsedTime()) * dt + me:SetOrigin(pos) +end +function modifier_mirana_leap_arc.prototype.OnHorizontalMotionInterrupted(self) + self:Destroy() +end +function modifier_mirana_leap_arc.prototype.OnVerticalMotionInterrupted(self) + self:Destroy() +end +function modifier_mirana_leap_arc.prototype.CheckState(self) + return {[MODIFIER_STATE_NO_UNIT_COLLISION] = true} +end +function modifier_mirana_leap_arc.prototype.DeclareFunctions(self) + return {MODIFIER_PROPERTY_DISABLE_TURNING, MODIFIER_PROPERTY_OVERRIDE_ANIMATION, MODIFIER_PROPERTY_INCOMING_DAMAGE_PERCENTAGE} +end +function modifier_mirana_leap_arc.prototype.GetModifierIncomingDamage_Percentage(self) + local ____opt_4 = self:GetAbility() + local reduction = ____opt_4 and ____opt_4:GetSpecialValueFor("leap_damage_reduction_pct") or 75 + return -reduction +end +function modifier_mirana_leap_arc.prototype.GetModifierDisableTurning(self) + return 1 +end +function modifier_mirana_leap_arc.prototype.GetOverrideAnimation(self) + return ACT_DOTA_CAST_ABILITY_3 +end +modifier_mirana_leap_arc = __TS__Decorate( + modifier_mirana_leap_arc, + modifier_mirana_leap_arc, + {registerModifier(nil)}, + {kind = "class", name = "modifier_mirana_leap_arc"} +) +____exports.modifier_mirana_leap_custom_buff = __TS__Class() +local modifier_mirana_leap_custom_buff = ____exports.modifier_mirana_leap_custom_buff +modifier_mirana_leap_custom_buff.name = "modifier_mirana_leap_custom_buff" +modifier_mirana_leap_custom_buff.____file_path = "scripts/vscripts/abilities/heroes/mirana/ability_mirana_leap_custom.lua" +__TS__ClassExtends(modifier_mirana_leap_custom_buff, BaseModifier) +function modifier_mirana_leap_custom_buff.prototype.IsHidden(self) + return false +end +function modifier_mirana_leap_custom_buff.prototype.IsDebuff(self) + return false +end +function modifier_mirana_leap_custom_buff.prototype.IsPurgable(self) + return true +end +function modifier_mirana_leap_custom_buff.prototype.DeclareFunctions(self) + return {MODIFIER_PROPERTY_ATTACKSPEED_BONUS_CONSTANT, MODIFIER_PROPERTY_MOVESPEED_BONUS_PERCENTAGE} +end +function modifier_mirana_leap_custom_buff.prototype.GetModifierAttackSpeedBonus_Constant(self) + local ____opt_6 = self:GetAbility() + return ____opt_6 and ____opt_6:GetSpecialValueFor("leap_speedbonus_as") or 0 +end +function modifier_mirana_leap_custom_buff.prototype.GetModifierMoveSpeedBonus_Percentage(self) + local ____opt_8 = self:GetAbility() + return ____opt_8 and ____opt_8:GetSpecialValueFor("leap_speedbonus") or 0 +end +modifier_mirana_leap_custom_buff = __TS__Decorate( + modifier_mirana_leap_custom_buff, + modifier_mirana_leap_custom_buff, + {registerModifier(nil)}, + {kind = "class", name = "modifier_mirana_leap_custom_buff"} +) +____exports.modifier_mirana_leap_custom_buff = modifier_mirana_leap_custom_buff +return ____exports diff --git a/scripts/vscripts/abilities/heroes/mirana/ability_mirana_moonlight_shadow_custom.lua b/scripts/vscripts/abilities/heroes/mirana/ability_mirana_moonlight_shadow_custom.lua new file mode 100644 index 0000000..2cf00c2 --- /dev/null +++ b/scripts/vscripts/abilities/heroes/mirana/ability_mirana_moonlight_shadow_custom.lua @@ -0,0 +1,239 @@ +local ____lualib = require("lualib_bundle") +local __TS__Class = ____lualib.__TS__Class +local __TS__ClassExtends = ____lualib.__TS__ClassExtends +local __TS__Decorate = ____lualib.__TS__Decorate +local ____exports = {} +local ____dota_ts_adapter = require("lib.dota_ts_adapter") +local BaseAbility = ____dota_ts_adapter.BaseAbility +local BaseModifier = ____dota_ts_adapter.BaseModifier +local registerAbility = ____dota_ts_adapter.registerAbility +local registerModifier = ____dota_ts_adapter.registerModifier +require("utils.utils") +local PFX_OVERHEAD_OWNER = "particles/units/heroes/hero_mirana/mirana_moonlight_owner.vpcf" +local PFX_RECIPIENT = "particles/econ/items/mirana/mirana_2021_immortal/mirana_2021_immortal_moonlight_recipient.vpcf" +local MOONLIGHT_ABILITY_NAME = "ability_mirana_moonlight_shadow_custom" +local function isHeroUnit(self, unit) + return not not unit and not unit:IsNull() and unit:IsHero() +end +____exports.ability_mirana_moonlight_shadow_custom = __TS__Class() +local ability_mirana_moonlight_shadow_custom = ____exports.ability_mirana_moonlight_shadow_custom +ability_mirana_moonlight_shadow_custom.name = "ability_mirana_moonlight_shadow_custom" +ability_mirana_moonlight_shadow_custom.____file_path = "scripts/vscripts/abilities/heroes/mirana/ability_mirana_moonlight_shadow_custom.lua" +__TS__ClassExtends(ability_mirana_moonlight_shadow_custom, BaseAbility) +function ability_mirana_moonlight_shadow_custom.prototype.Precache(self, context) + PrecacheResource("particle", PFX_OVERHEAD_OWNER, context) + PrecacheResource("particle", PFX_RECIPIENT, context) +end +function ability_mirana_moonlight_shadow_custom.prototype.GetBehavior(self) + return DOTA_ABILITY_BEHAVIOR_NO_TARGET +end +function ability_mirana_moonlight_shadow_custom.prototype.OnSpellStart(self) + if not IsServer() then + return + end + local caster = self:GetCaster() + local duration = self:GetSpecialValueFor("duration") + local radius = self:GetSpecialValueFor("radius") + local hasScepter = HasScepter(nil, caster) + EmitSoundOn("Hero_Mirana.MoonlightShadow", caster) + caster:StartGesture(ACT_DOTA_CAST_ABILITY_4) + local allies = FindUnitsInRadius( + caster:GetTeamNumber(), + caster:GetAbsOrigin(), + nil, + radius, + DOTA_UNIT_TARGET_TEAM_FRIENDLY, + DOTA_UNIT_TARGET_HERO, + DOTA_UNIT_TARGET_FLAG_NONE, + FIND_ANY_ORDER, + false + ) + local modifierKv = {duration = duration} + local applied = false + for ____, ally in ipairs(allies) do + do + if not ally or ally:IsNull() or not ally:IsAlive() then + goto __continue7 + end + local mod = ally:AddNewModifier(caster, self, ____exports.modifier_mirana_moonlight_shadow_custom.name, modifierKv) + if mod ~= nil then + mod:bindCastContext(caster, hasScepter) + end + applied = true + end + ::__continue7:: + end + if not applied and caster:IsAlive() then + local mod = caster:AddNewModifier(caster, self, ____exports.modifier_mirana_moonlight_shadow_custom.name, modifierKv) + if mod ~= nil then + mod:bindCastContext(caster, hasScepter) + end + end +end +ability_mirana_moonlight_shadow_custom = __TS__Decorate( + ability_mirana_moonlight_shadow_custom, + ability_mirana_moonlight_shadow_custom, + {registerAbility(nil)}, + {kind = "class", name = "ability_mirana_moonlight_shadow_custom"} +) +____exports.ability_mirana_moonlight_shadow_custom = ability_mirana_moonlight_shadow_custom +____exports.modifier_mirana_moonlight_shadow_custom = __TS__Class() +local modifier_mirana_moonlight_shadow_custom = ____exports.modifier_mirana_moonlight_shadow_custom +modifier_mirana_moonlight_shadow_custom.name = "modifier_mirana_moonlight_shadow_custom" +modifier_mirana_moonlight_shadow_custom.____file_path = "scripts/vscripts/abilities/heroes/mirana/ability_mirana_moonlight_shadow_custom.lua" +__TS__ClassExtends(modifier_mirana_moonlight_shadow_custom, BaseModifier) +function modifier_mirana_moonlight_shadow_custom.prototype.____constructor(self, ...) + BaseModifier.prototype.____constructor(self, ...) + self.scepterShareAtCast = false +end +function modifier_mirana_moonlight_shadow_custom.prototype.bindCastContext(self, mirana, scepterShare) + if not IsServer() then + return + end + if not isHeroUnit(nil, mirana) then + return + end + self.miranaEntIndex = mirana:entindex() + self.scepterShareAtCast = scepterShare +end +function modifier_mirana_moonlight_shadow_custom.prototype.IsHidden(self) + return false +end +function modifier_mirana_moonlight_shadow_custom.prototype.IsDebuff(self) + return false +end +function modifier_mirana_moonlight_shadow_custom.prototype.IsPurgable(self) + return true +end +function modifier_mirana_moonlight_shadow_custom.prototype.OnCreated(self, _params) + local parent = self:GetParent() + if not parent or parent:IsNull() then + return + end + if IsServer() and self.miranaEntIndex == nil then + local ability = self:GetAbility() + local mirana = ability and ability:GetCaster() + if isHeroUnit(nil, mirana) then + self:bindCastContext( + mirana, + HasScepter(nil, mirana) + ) + end + end + local ownerPfx = ParticleManager:CreateParticle(PFX_OVERHEAD_OWNER, PATTACH_OVERHEAD_FOLLOW, parent) + self:AddParticle( + ownerPfx, + false, + false, + -1, + false, + true + ) + local recipientPfx = ParticleManager:CreateParticle(PFX_RECIPIENT, PATTACH_ABSORIGIN_FOLLOW, parent) + self:AddParticle( + recipientPfx, + false, + false, + -1, + false, + false + ) +end +function modifier_mirana_moonlight_shadow_custom.prototype.getMoonlightAbility(self) + local ability = self:GetAbility() + if ability and not ability:IsNull() then + return ability + end + local mirana = self:getUltCasterUnit() + return mirana and mirana:FindAbilityByName(MOONLIGHT_ABILITY_NAME) or nil +end +function modifier_mirana_moonlight_shadow_custom.prototype.getUltCasterUnit(self) + if self.miranaEntIndex ~= nil then + local fromIndex = EntIndexToHScript(self.miranaEntIndex) + if isHeroUnit(nil, fromIndex) then + return fromIndex + end + end + local ability = self:getMoonlightAbility() + local caster = ability and ability:GetCaster() + if isHeroUnit(nil, caster) then + return caster + end + return nil +end +function modifier_mirana_moonlight_shadow_custom.prototype.hasAllyScepterDamageShare(self) + if self.scepterShareAtCast then + return true + end + local mirana = self:getUltCasterUnit() + return not not mirana and HasScepter(nil, mirana) +end +function modifier_mirana_moonlight_shadow_custom.prototype.isUltOwner(self) + local parent = self:GetParent() + local mirana = self:getUltCasterUnit() + if not parent or not mirana then + return false + end + return parent:entindex() == mirana:entindex() +end +function modifier_mirana_moonlight_shadow_custom.prototype.getMiranaDamageRampSeconds(self) + return math.floor(self:GetElapsedTime()) +end +function modifier_mirana_moonlight_shadow_custom.prototype.getMiranaFullDamageBonusPct(self) + local ability = self:getMoonlightAbility() + if not ability then + return 0 + end + local base = ability:GetSpecialValueFor("damage_bonus_base") + local perSecond = ability:GetSpecialValueFor("damage_bonus_per_second") + return base + perSecond * self:getMiranaDamageRampSeconds() +end +function modifier_mirana_moonlight_shadow_custom.prototype.getDamageBonusPct(self) + local ability = self:getMoonlightAbility() + local mirana = self:getUltCasterUnit() + if not ability or not mirana then + return 0 + end + local fullBonus = self:getMiranaFullDamageBonusPct() + if self:isUltOwner() then + return fullBonus + end + if not self:hasAllyScepterDamageShare() then + return 0 + end + local sharePct = ability:GetSpecialValueFor("scepter_ally_damage_share_pct") + return math.floor(fullBonus * (sharePct / 100)) +end +function modifier_mirana_moonlight_shadow_custom.prototype.DeclareFunctions(self) + return { + MODIFIER_PROPERTY_MIN_HEALTH, + MODIFIER_PROPERTY_MOVESPEED_BONUS_PERCENTAGE, + MODIFIER_PROPERTY_DAMAGEOUTGOING_PERCENTAGE, + MODIFIER_PROPERTY_SPELL_AMPLIFY_PERCENTAGE, + MODIFIER_PROPERTY_TOOLTIP + } +end +function modifier_mirana_moonlight_shadow_custom.prototype.GetMinHealth(self) + return 1 +end +function modifier_mirana_moonlight_shadow_custom.prototype.GetModifierMoveSpeedBonus_Percentage(self) + local ____opt_10 = self:getMoonlightAbility() + return ____opt_10 and ____opt_10:GetSpecialValueFor("bonus_movement_speed") or 0 +end +function modifier_mirana_moonlight_shadow_custom.prototype.GetModifierDamageOutgoing_Percentage(self, _event) + return self:getDamageBonusPct() +end +function modifier_mirana_moonlight_shadow_custom.prototype.GetModifierSpellAmplify_Percentage(self, _event) + return self:getDamageBonusPct() +end +function modifier_mirana_moonlight_shadow_custom.prototype.OnTooltip(self) + return self:getDamageBonusPct() +end +modifier_mirana_moonlight_shadow_custom = __TS__Decorate( + modifier_mirana_moonlight_shadow_custom, + modifier_mirana_moonlight_shadow_custom, + {registerModifier(nil)}, + {kind = "class", name = "modifier_mirana_moonlight_shadow_custom"} +) +____exports.modifier_mirana_moonlight_shadow_custom = modifier_mirana_moonlight_shadow_custom +return ____exports diff --git a/scripts/vscripts/abilities/heroes/mirana/ability_mirana_sacred_arrow_custom.lua b/scripts/vscripts/abilities/heroes/mirana/ability_mirana_sacred_arrow_custom.lua new file mode 100644 index 0000000..e967564 --- /dev/null +++ b/scripts/vscripts/abilities/heroes/mirana/ability_mirana_sacred_arrow_custom.lua @@ -0,0 +1,235 @@ +local ____lualib = require("lualib_bundle") +local __TS__StringStartsWith = ____lualib.__TS__StringStartsWith +local __TS__Class = ____lualib.__TS__Class +local __TS__ClassExtends = ____lualib.__TS__ClassExtends +local __TS__Decorate = ____lualib.__TS__Decorate +local ____exports = {} +local ____dota_ts_adapter = require("lib.dota_ts_adapter") +local BaseAbility = ____dota_ts_adapter.BaseAbility +local registerAbility = ____dota_ts_adapter.registerAbility +local ____mirana_mana_damage = require("abilities.heroes.mirana.mirana_mana_damage") +local getMiranaSpellTotalDamage = ____mirana_mana_damage.getMiranaSpellTotalDamage +local PFX_METEOR = "particles/units/heroes/hero_mirana/mirana_starfall_attack.vpcf" +local TALENT_SIDE_ARROWS = "special_bonus_unique_mirana_sacred_arrow_side_arrows" +--- Босс — стрела останавливается; остальные враги получают урон и прокол насквозь. +local function isSacredArrowBossTarget(self, unit) + local unitName = unit:GetUnitName() + if __TS__StringStartsWith(unitName, "npc_boss_") or __TS__StringStartsWith(unitName, "npc_wave_boss") then + return true + end + return unit:IsBossCreature() or unit:IsBoss() +end +____exports.ability_mirana_sacred_arrow_custom = __TS__Class() +local ability_mirana_sacred_arrow_custom = ____exports.ability_mirana_sacred_arrow_custom +ability_mirana_sacred_arrow_custom.name = "ability_mirana_sacred_arrow_custom" +ability_mirana_sacred_arrow_custom.____file_path = "scripts/vscripts/abilities/heroes/mirana/ability_mirana_sacred_arrow_custom.lua" +__TS__ClassExtends(ability_mirana_sacred_arrow_custom, BaseAbility) +function ability_mirana_sacred_arrow_custom.prototype.____constructor(self, ...) + BaseAbility.prototype.____constructor(self, ...) + self.shardStarfallVictims = {} +end +function ability_mirana_sacred_arrow_custom.prototype.Precache(self, context) + PrecacheResource("particle", "particles/units/heroes/hero_mirana/mirana_spell_arrow.vpcf", context) + PrecacheResource("particle", PFX_METEOR, context) + PrecacheResource("soundfile", "sounds/units/heroes/mirana/arrow.vsnd", context) + PrecacheResource("soundfile", "sounds/units/heroes/mirana/arrow_target.vsnd", context) +end +function ability_mirana_sacred_arrow_custom.prototype.GetCastRange(self, _location, _target) + return self:GetSpecialValueFor("arrow_range") +end +function ability_mirana_sacred_arrow_custom.prototype.OnSpellStart(self) + if not IsServer() then + return + end + local caster = self:GetCaster() + local origin = caster:GetAbsOrigin() + local point = self:GetCursorPosition() + self.shardStarfallVictims = {} + local direction = Vector(point.x - origin.x, point.y - origin.y, 0):Normalized() + self:launchArrow(direction, origin) + if HasTalent(nil, caster, TALENT_SIDE_ARROWS) then + local coneAngle = self:GetSpecialValueFor("talent_side_arrow_angle") + local leftDir = RotatePosition( + Vector(0, 0, 0), + QAngle(0, coneAngle, 0), + direction + ) + local rightDir = RotatePosition( + Vector(0, 0, 0), + QAngle(0, -coneAngle, 0), + direction + ) + self:launchArrow(leftDir, origin) + self:launchArrow(rightDir, origin) + end + EmitSoundOn("Hero_Mirana.ArrowCast", caster) +end +function ability_mirana_sacred_arrow_custom.prototype.launchArrow(self, direction, spawnOrigin) + local caster = self:GetCaster() + local projectileSpeed = self:GetSpecialValueFor("arrow_speed") + local projectileDistance = self:GetSpecialValueFor("arrow_range") + local projectileRadius = self:GetSpecialValueFor("arrow_width") + ProjectileManager:CreateLinearProjectile({ + Ability = self, + Source = caster, + vSpawnOrigin = spawnOrigin, + vVelocity = direction * projectileSpeed, + fDistance = projectileDistance, + fStartRadius = projectileRadius, + fEndRadius = projectileRadius, + iUnitTargetTeam = DOTA_UNIT_TARGET_TEAM_ENEMY, + iUnitTargetType = bit.bor(DOTA_UNIT_TARGET_HERO, DOTA_UNIT_TARGET_BASIC), + iUnitTargetFlags = DOTA_UNIT_TARGET_FLAG_NONE, + EffectName = "particles/units/heroes/hero_mirana/mirana_spell_arrow.vpcf", + bHasFrontalCone = false, + bProvidesVision = true, + iVisionRadius = 300, + iVisionTeamNumber = caster:GetTeamNumber(), + fExpireTime = GameRules:GetGameTime() + 10, + ExtraData = {sx = spawnOrigin.x, sy = spawnOrigin.y} + }) +end +function ability_mirana_sacred_arrow_custom.prototype.OnProjectileThink(self, location) + if not IsServer() then + return + end + local caster = self:GetCaster() + if not caster or caster:IsNull() or not caster:IsAlive() or not HasShard(nil, caster) then + return + end + local radius = self:GetSpecialValueFor("shard_starfall_radius") + local enemies = FindUnitsInRadius( + caster:GetTeamNumber(), + location, + nil, + radius, + DOTA_UNIT_TARGET_TEAM_ENEMY, + bit.bor(DOTA_UNIT_TARGET_HERO, DOTA_UNIT_TARGET_BASIC), + DOTA_UNIT_TARGET_FLAG_NONE, + FIND_ANY_ORDER, + false + ) + for ____, enemy in ipairs(enemies) do + do + if not enemy or enemy:IsNull() or not enemy:IsAlive() then + goto __continue13 + end + local entIndex = enemy:entindex() + if self.shardStarfallVictims[entIndex] then + goto __continue13 + end + self.shardStarfallVictims[entIndex] = true + self:applyShardStarfallOnTarget(caster, enemy) + end + ::__continue13:: + end +end +function ability_mirana_sacred_arrow_custom.prototype.applyShardStarfallOnTarget(self, caster, target) + local baseDamage = self:GetSpecialValueFor("arrow_damage") + local fullDamage = getMiranaSpellTotalDamage(nil, caster, self, baseDamage) + local pctFirst = self:GetSpecialValueFor("shard_starfall_damage_pct_first") + local pctSecond = self:GetSpecialValueFor("shard_starfall_damage_pct_second") + local impactDelay = self:GetSpecialValueFor("shard_meteor_impact_delay") + local secondMeteorDelay = self:GetSpecialValueFor("shard_second_meteor_delay") + self:spawnShardMeteorVfx(target) + Timers:CreateTimer( + impactDelay, + function() + if not caster or caster:IsNull() or not caster:IsAlive() then + return nil + end + if not target or target:IsNull() or not target:IsAlive() then + return nil + end + self:applyShardMeteorDamage(caster, target, fullDamage * (pctFirst / 100), true) + return nil + end + ) + Timers:CreateTimer( + secondMeteorDelay, + function() + if not caster or caster:IsNull() or not caster:IsAlive() then + return nil + end + if not target or target:IsNull() or not target:IsAlive() then + return nil + end + self:spawnShardMeteorVfx(target) + Timers:CreateTimer( + impactDelay, + function() + if not caster or caster:IsNull() or not caster:IsAlive() then + return nil + end + if not target or target:IsNull() or not target:IsAlive() then + return nil + end + self:applyShardMeteorDamage(caster, target, fullDamage * (pctSecond / 100), false) + return nil + end + ) + return nil + end + ) +end +function ability_mirana_sacred_arrow_custom.prototype.spawnShardMeteorVfx(self, target) + local pfx = ParticleManager:CreateParticle(PFX_METEOR, PATTACH_ABSORIGIN_FOLLOW, target) + ParticleManager:ReleaseParticleIndex(pfx) +end +function ability_mirana_sacred_arrow_custom.prototype.applyShardMeteorDamage(self, caster, target, damage, playImpactSound) + if damage <= 0 then + return + end + if playImpactSound then + EmitSoundOn("Hero_Mirana.Starstorm.Impact", target) + end + ApplyDamage({ + victim = target, + attacker = caster, + damage = damage, + damage_type = DAMAGE_TYPE_MAGICAL, + ability = self + }) +end +function ability_mirana_sacred_arrow_custom.prototype.OnProjectileHit_ExtraData(self, target, location, extraData) + if not IsServer() then + return + end + if not target or target:IsNull() or not target:IsAlive() then + return + end + if target:GetTeamNumber() == self:GetCaster():GetTeamNumber() then + return + end + local caster = self:GetCaster() + local baseDamage = self:GetSpecialValueFor("arrow_damage") + local totalDamage = getMiranaSpellTotalDamage(nil, caster, self, baseDamage) + ApplyDamage({ + victim = target, + attacker = caster, + damage = totalDamage, + damage_type = DAMAGE_TYPE_MAGICAL, + ability = self + }) + EmitSoundOn("Hero_Mirana.ArrowImpact", target) + if not isSacredArrowBossTarget(nil, target) then + return false + end + local spawn = Vector(extraData.sx, extraData.sy, 0) + local traveled = (location - spawn):Length2D() + local stunPer100 = self:GetSpecialValueFor("stun_per_100_distance") + local maxStun = self:GetSpecialValueFor("max_stun_duration") + local stun = math.min(maxStun, traveled * (stunPer100 / 100)) + if stun > 0 then + target:AddNewModifier(caster, self, "modifier_stunned", {duration = stun}) + end + return true +end +ability_mirana_sacred_arrow_custom = __TS__Decorate( + ability_mirana_sacred_arrow_custom, + ability_mirana_sacred_arrow_custom, + {registerAbility(nil)}, + {kind = "class", name = "ability_mirana_sacred_arrow_custom"} +) +____exports.ability_mirana_sacred_arrow_custom = ability_mirana_sacred_arrow_custom +return ____exports diff --git a/scripts/vscripts/abilities/heroes/mirana/ability_mirana_starstorm_custom.lua b/scripts/vscripts/abilities/heroes/mirana/ability_mirana_starstorm_custom.lua new file mode 100644 index 0000000..2c1c8c5 --- /dev/null +++ b/scripts/vscripts/abilities/heroes/mirana/ability_mirana_starstorm_custom.lua @@ -0,0 +1,277 @@ +local ____lualib = require("lualib_bundle") +local __TS__Class = ____lualib.__TS__Class +local __TS__ClassExtends = ____lualib.__TS__ClassExtends +local __TS__Decorate = ____lualib.__TS__Decorate +local ____exports = {} +local ____dota_ts_adapter = require("lib.dota_ts_adapter") +local BaseAbility = ____dota_ts_adapter.BaseAbility +local BaseModifier = ____dota_ts_adapter.BaseModifier +local registerAbility = ____dota_ts_adapter.registerAbility +local registerModifier = ____dota_ts_adapter.registerModifier +local ____luck = require("utils.luck") +local rollLuckChance = ____luck.rollLuckChance +local ____mirana_mana_damage = require("abilities.heroes.mirana.mirana_mana_damage") +local getMiranaSpellTotalDamage = ____mirana_mana_damage.getMiranaSpellTotalDamage +local PFX_CIRCLE = "particles/units/heroes/hero_mirana/mirana_starfall_circle.vpcf" +local PFX_METEOR = "particles/units/heroes/hero_mirana/mirana_starfall_attack.vpcf" +local TALENT_ATTACK_PROC = "special_bonus_unique_mirana_starstorm_attack_proc" +____exports.ability_mirana_starstorm_custom = __TS__Class() +local ability_mirana_starstorm_custom = ____exports.ability_mirana_starstorm_custom +ability_mirana_starstorm_custom.name = "ability_mirana_starstorm_custom" +ability_mirana_starstorm_custom.____file_path = "scripts/vscripts/abilities/heroes/mirana/ability_mirana_starstorm_custom.lua" +__TS__ClassExtends(ability_mirana_starstorm_custom, BaseAbility) +function ability_mirana_starstorm_custom.prototype.Precache(self, context) + PrecacheResource("particle", PFX_METEOR, context) + PrecacheResource("particle", PFX_CIRCLE, context) +end +function ability_mirana_starstorm_custom.prototype.GetIntrinsicModifierName(self) + return ____exports.modifier_mirana_starstorm_attack_proc.name +end +function ability_mirana_starstorm_custom.prototype.GetBehavior(self) + return DOTA_ABILITY_BEHAVIOR_NO_TARGET +end +function ability_mirana_starstorm_custom.prototype.GetAOERadius(self) + return self:GetSpecialValueFor("radius") +end +function ability_mirana_starstorm_custom.prototype.OnSpellStart(self) + if not IsServer() then + return + end + local caster = self:GetCaster() + local castOrigin = caster:GetAbsOrigin() + local radius = self:GetSpecialValueFor("radius") + local impactDelay = self:GetSpecialValueFor("meteor_impact_delay") + local secondarySpawnDelay = self:GetSpecialValueFor("secondary_delay") + local damage = self:GetSpecialValueFor("damage") + local secondaryPct = self:GetSpecialValueFor("secondary_damage_pct") + local team = caster:GetTeamNumber() + local castPfx = ParticleManager:CreateParticle(PFX_CIRCLE, PATTACH_ABSORIGIN_FOLLOW, caster) + ParticleManager:ReleaseParticleIndex(castPfx) + EmitSoundOn("Hero_Mirana.Starstorm", caster) + caster:StartGesture(ACT_DOTA_CAST_ABILITY_1) + local primaryTargets = FindUnitsInRadius( + team, + castOrigin, + nil, + radius, + DOTA_UNIT_TARGET_TEAM_ENEMY, + bit.bor(DOTA_UNIT_TARGET_HERO, DOTA_UNIT_TARGET_BASIC), + DOTA_UNIT_TARGET_FLAG_NONE, + FIND_ANY_ORDER, + false + ) + for ____, enemy in ipairs(primaryTargets) do + self:spawnMeteorVfx(enemy) + end + Timers:CreateTimer( + impactDelay, + function() + if not caster or caster:IsNull() or not caster:IsAlive() then + return nil + end + for ____, enemy in ipairs(primaryTargets) do + do + if not enemy or enemy:IsNull() or not enemy:IsAlive() then + goto __continue12 + end + self:applyMeteorDamage(caster, enemy, damage, false) + end + ::__continue12:: + end + return nil + end + ) + if secondarySpawnDelay <= 0 then + return + end + Timers:CreateTimer( + secondarySpawnDelay, + function() + if not caster or caster:IsNull() or not caster:IsAlive() then + return nil + end + local secondaryTargets = FindUnitsInRadius( + team, + castOrigin, + nil, + radius, + DOTA_UNIT_TARGET_TEAM_ENEMY, + bit.bor(DOTA_UNIT_TARGET_HERO, DOTA_UNIT_TARGET_BASIC), + DOTA_UNIT_TARGET_FLAG_NONE, + FIND_CLOSEST, + false + ) + if #secondaryTargets == 0 then + return nil + end + local closest = secondaryTargets[1] + self:spawnMeteorVfx(closest) + Timers:CreateTimer( + impactDelay, + function() + if not caster or caster:IsNull() or not caster:IsAlive() then + return nil + end + if not closest or closest:IsNull() or not closest:IsAlive() then + return nil + end + local secondaryDamage = damage * (secondaryPct / 100) + self:applyMeteorDamage(caster, closest, secondaryDamage, true) + return nil + end + ) + return nil + end + ) +end +function ability_mirana_starstorm_custom.prototype.castOnTarget(self, caster, target) + if not IsServer() then + return + end + if not caster or caster:IsNull() or not caster:IsAlive() then + return + end + if not target or target:IsNull() or not target:IsAlive() then + return + end + if target:GetTeamNumber() == caster:GetTeamNumber() then + return + end + local damage = self:GetSpecialValueFor("damage") + local impactDelay = self:GetSpecialValueFor("meteor_impact_delay") + local secondarySpawnDelay = self:GetSpecialValueFor("secondary_delay") + local secondaryPct = self:GetSpecialValueFor("secondary_damage_pct") + self:spawnMeteorVfx(target) + Timers:CreateTimer( + impactDelay, + function() + if not caster or caster:IsNull() or not caster:IsAlive() then + return nil + end + if not target or target:IsNull() or not target:IsAlive() then + return nil + end + self:applyMeteorDamage(caster, target, damage, false) + return nil + end + ) + if secondarySpawnDelay <= 0 then + return + end + Timers:CreateTimer( + secondarySpawnDelay, + function() + if not caster or caster:IsNull() or not caster:IsAlive() then + return nil + end + if not target or target:IsNull() or not target:IsAlive() then + return nil + end + self:spawnMeteorVfx(target) + Timers:CreateTimer( + impactDelay, + function() + if not caster or caster:IsNull() or not caster:IsAlive() then + return nil + end + if not target or target:IsNull() or not target:IsAlive() then + return nil + end + self:applyMeteorDamage(caster, target, damage * (secondaryPct / 100), true) + return nil + end + ) + return nil + end + ) +end +function ability_mirana_starstorm_custom.prototype.spawnMeteorVfx(self, target) + if not target or target:IsNull() or not target:IsAlive() then + return + end + local pfx = ParticleManager:CreateParticle(PFX_METEOR, PATTACH_ABSORIGIN_FOLLOW, target) + ParticleManager:ReleaseParticleIndex(pfx) +end +function ability_mirana_starstorm_custom.prototype.applyMeteorDamage(self, caster, target, damage, secondary) + if not target or target:IsNull() or not target:IsAlive() then + return + end + if not secondary then + EmitSoundOn("Hero_Mirana.Starstorm.Impact", target) + end + local totalDamage = getMiranaSpellTotalDamage(nil, caster, self, damage) + ApplyDamage({ + victim = target, + attacker = caster, + damage = totalDamage, + damage_type = DAMAGE_TYPE_MAGICAL, + ability = self + }) +end +ability_mirana_starstorm_custom = __TS__Decorate( + ability_mirana_starstorm_custom, + ability_mirana_starstorm_custom, + {registerAbility(nil)}, + {kind = "class", name = "ability_mirana_starstorm_custom"} +) +____exports.ability_mirana_starstorm_custom = ability_mirana_starstorm_custom +____exports.modifier_mirana_starstorm_attack_proc = __TS__Class() +local modifier_mirana_starstorm_attack_proc = ____exports.modifier_mirana_starstorm_attack_proc +modifier_mirana_starstorm_attack_proc.name = "modifier_mirana_starstorm_attack_proc" +modifier_mirana_starstorm_attack_proc.____file_path = "scripts/vscripts/abilities/heroes/mirana/ability_mirana_starstorm_custom.lua" +__TS__ClassExtends(modifier_mirana_starstorm_attack_proc, BaseModifier) +function modifier_mirana_starstorm_attack_proc.prototype.IsHidden(self) + return true +end +function modifier_mirana_starstorm_attack_proc.prototype.IsPurgable(self) + return false +end +function modifier_mirana_starstorm_attack_proc.prototype.DeclareFunctions(self) + return {MODIFIER_EVENT_ON_ATTACK_LANDED} +end +function modifier_mirana_starstorm_attack_proc.prototype.OnAttackLanded(self, event) + if not IsServer() then + return + end + local parent = self:GetParent() + if event.attacker ~= parent then + return + end + if not parent:IsRealHero() or parent:IsIllusion() then + return + end + if parent:PassivesDisabled() then + return + end + if not HasTalent(nil, parent, TALENT_ATTACK_PROC) then + return + end + local target = event.target + if not target or target:IsNull() or not target:IsAlive() then + return + end + if target:GetTeamNumber() == parent:GetTeamNumber() then + return + end + local ability = self:GetAbility() + if not ability or ability:IsNull() then + return + end + local chancePct = ability:GetSpecialValueFor("attack_proc_chance") + if chancePct <= 0 then + return + end + local hero = parent + if not rollLuckChance(nil, hero, chancePct / 100) then + return + end + ability:castOnTarget(parent, target) +end +modifier_mirana_starstorm_attack_proc = __TS__Decorate( + modifier_mirana_starstorm_attack_proc, + modifier_mirana_starstorm_attack_proc, + {registerModifier(nil)}, + {kind = "class", name = "modifier_mirana_starstorm_attack_proc"} +) +____exports.modifier_mirana_starstorm_attack_proc = modifier_mirana_starstorm_attack_proc +return ____exports diff --git a/scripts/vscripts/abilities/heroes/mirana/mirana_damage_shared.lua b/scripts/vscripts/abilities/heroes/mirana/mirana_damage_shared.lua new file mode 100644 index 0000000..41bc498 --- /dev/null +++ b/scripts/vscripts/abilities/heroes/mirana/mirana_damage_shared.lua @@ -0,0 +1,26 @@ +--[[ Generated with https://github.com/TypeScriptToLua/TypeScriptToLua ]] +local ____exports = {} +--- Доп. урон кастомок Мираны: недостающая мана × (`missing_mana_damage_pct` / 100) из врождёнки. +function ____exports.getMiranaMissingManaBonusDamage(self, caster) + if not caster or caster:IsNull() or not caster:IsAlive() or not caster:IsHero() then + return 0 + end + local innate = caster:FindAbilityByName("ability_mirana_innate_custom") + if not innate or innate:IsNull() then + return 0 + end + local level = math.max( + 1, + innate:GetLevel() + ) + local pct = innate:GetLevelSpecialValueFor("missing_mana_damage_pct", level) + if pct <= 0 then + return 0 + end + local missing = math.max( + 0, + caster:GetMaxMana() - caster:GetMana() + ) + return missing * (pct * 0.01) +end +return ____exports diff --git a/scripts/vscripts/abilities/heroes/mirana/mirana_mana_damage.lua b/scripts/vscripts/abilities/heroes/mirana/mirana_mana_damage.lua new file mode 100644 index 0000000..d657078 --- /dev/null +++ b/scripts/vscripts/abilities/heroes/mirana/mirana_mana_damage.lua @@ -0,0 +1,14 @@ +--[[ Generated with https://github.com/TypeScriptToLua/TypeScriptToLua ]] +local ____exports = {} +--- Доп. магический урон от маны: % из AbilityValues `mana_damage_pct` (с hero_levelup в KV). +function ____exports.getMiranaManaBonusDamage(self, caster, ability) + local pct = ability:GetSpecialValueFor("mana_damage_pct") + if pct <= 0 then + return 0 + end + return caster:GetMaxMana() * (pct / 100) +end +function ____exports.getMiranaSpellTotalDamage(self, caster, ability, baseDamage) + return baseDamage + ____exports.getMiranaManaBonusDamage(nil, caster, ability) +end +return ____exports diff --git a/scripts/vscripts/abilities/heroes/mirana_luna/ability_moon_sisters_synergy.lua b/scripts/vscripts/abilities/heroes/mirana_luna/ability_moon_sisters_synergy.lua new file mode 100644 index 0000000..55f471e --- /dev/null +++ b/scripts/vscripts/abilities/heroes/mirana_luna/ability_moon_sisters_synergy.lua @@ -0,0 +1,182 @@ +local ____lualib = require("lualib_bundle") +local __TS__Class = ____lualib.__TS__Class +local __TS__ClassExtends = ____lualib.__TS__ClassExtends +local __TS__Decorate = ____lualib.__TS__Decorate +local ____exports = {} +local ____dota_ts_adapter = require("lib.dota_ts_adapter") +local BaseAbility = ____dota_ts_adapter.BaseAbility +local BaseModifier = ____dota_ts_adapter.BaseModifier +local registerAbility = ____dota_ts_adapter.registerAbility +local registerModifier = ____dota_ts_adapter.registerModifier +local ____incoming_damage_reduction_combine = require("utils.incoming_damage_reduction_combine") +local removeIncomingDamageReductionSource = ____incoming_damage_reduction_combine.removeIncomingDamageReductionSource +local setIncomingDamageReductionSource = ____incoming_damage_reduction_combine.setIncomingDamageReductionSource +local HERO_MIRANA = "npc_dota_hero_mirana" +local HERO_LUNA = "npc_dota_hero_luna" +local MOON_SISTERS_INCOMING_SOURCE = "modifier_moon_sisters_synergy" +local function getMoonSisterHeroName(self, unitName) + if unitName == HERO_MIRANA then + return HERO_LUNA + end + if unitName == HERO_LUNA then + return HERO_MIRANA + end + return nil +end +local function isValidMoonSisterAlly(self, unit) + if not unit or unit:IsNull() or not unit:IsAlive() then + return false + end + if not unit:IsRealHero() or unit:IsIllusion() then + return false + end + if unit.IsTempestDouble and unit:IsTempestDouble() then + return false + end + return true +end +____exports.ability_moon_sisters_synergy = __TS__Class() +local ability_moon_sisters_synergy = ____exports.ability_moon_sisters_synergy +ability_moon_sisters_synergy.name = "ability_moon_sisters_synergy" +ability_moon_sisters_synergy.____file_path = "scripts/vscripts/abilities/heroes/mirana_luna/ability_moon_sisters_synergy.lua" +__TS__ClassExtends(ability_moon_sisters_synergy, BaseAbility) +function ability_moon_sisters_synergy.prototype.GetIntrinsicModifierName(self) + return ____exports.modifier_moon_sisters_synergy.name +end +function ability_moon_sisters_synergy.prototype.GetAOERadius(self) + return self:GetSpecialValueFor("synergy_radius") +end +ability_moon_sisters_synergy = __TS__Decorate( + ability_moon_sisters_synergy, + ability_moon_sisters_synergy, + {registerAbility(nil)}, + {kind = "class", name = "ability_moon_sisters_synergy"} +) +____exports.ability_moon_sisters_synergy = ability_moon_sisters_synergy +____exports.modifier_moon_sisters_synergy = __TS__Class() +local modifier_moon_sisters_synergy = ____exports.modifier_moon_sisters_synergy +modifier_moon_sisters_synergy.name = "modifier_moon_sisters_synergy" +modifier_moon_sisters_synergy.____file_path = "scripts/vscripts/abilities/heroes/mirana_luna/ability_moon_sisters_synergy.lua" +__TS__ClassExtends(modifier_moon_sisters_synergy, BaseModifier) +function modifier_moon_sisters_synergy.prototype.____constructor(self, ...) + BaseModifier.prototype.____constructor(self, ...) + self.sisterNearby = false +end +function modifier_moon_sisters_synergy.prototype.IsHidden(self) + return not self.sisterNearby +end +function modifier_moon_sisters_synergy.prototype.IsDebuff(self) + return false +end +function modifier_moon_sisters_synergy.prototype.IsPurgable(self) + return false +end +function modifier_moon_sisters_synergy.prototype.RemoveOnDeath(self) + return false +end +function modifier_moon_sisters_synergy.prototype.OnCreated(self) + if not IsServer() then + return + end + setIncomingDamageReductionSource( + nil, + self:GetParent(), + MOON_SISTERS_INCOMING_SOURCE, + function() + if self:GetParent():PassivesDisabled() then + return 0 + end + if not self.sisterNearby then + return 0 + end + local ability = self:GetAbility() + if not ability then + return 0 + end + return math.max( + 0, + ability:GetSpecialValueFor("incoming_damage_reduction_pct") + ) + end + ) + self:refreshSisterProximity() + self:StartIntervalThink(0.25) +end +function modifier_moon_sisters_synergy.prototype.OnRefresh(self) + self:refreshSisterProximity() +end +function modifier_moon_sisters_synergy.prototype.OnDestroy(self) + if not IsServer() then + return + end + removeIncomingDamageReductionSource( + nil, + self:GetParent(), + MOON_SISTERS_INCOMING_SOURCE + ) +end +function modifier_moon_sisters_synergy.prototype.OnIntervalThink(self) + self:refreshSisterProximity() +end +function modifier_moon_sisters_synergy.prototype.refreshSisterProximity(self) + local parent = self:GetParent() + local ability = self:GetAbility() + if not parent or parent:IsNull() or not ability then + self:setSisterNearby(false) + return + end + local sisterName = getMoonSisterHeroName( + nil, + parent:GetUnitName() + ) + if not sisterName then + self:setSisterNearby(false) + return + end + local radius = ability:GetSpecialValueFor("synergy_radius") + local allies = FindUnitsInRadius( + parent:GetTeamNumber(), + parent:GetAbsOrigin(), + nil, + radius, + DOTA_UNIT_TARGET_TEAM_FRIENDLY, + DOTA_UNIT_TARGET_HERO, + DOTA_UNIT_TARGET_FLAG_NONE, + FIND_ANY_ORDER, + false + ) + local found = false + for ____, ally in ipairs(allies) do + do + if not isValidMoonSisterAlly(nil, ally) then + goto __continue28 + end + if ally == parent then + goto __continue28 + end + if ally:GetUnitName() ~= sisterName then + goto __continue28 + end + found = true + break + end + ::__continue28:: + end + self:setSisterNearby(found) +end +function modifier_moon_sisters_synergy.prototype.setSisterNearby(self, nearby) + if self.sisterNearby == nearby then + return + end + self.sisterNearby = nearby + self:SetStackCount(nearby and 1 or 0) + self:SendBuffRefreshToClients() +end +modifier_moon_sisters_synergy = __TS__Decorate( + modifier_moon_sisters_synergy, + modifier_moon_sisters_synergy, + {registerModifier(nil)}, + {kind = "class", name = "modifier_moon_sisters_synergy"} +) +____exports.modifier_moon_sisters_synergy = modifier_moon_sisters_synergy +return ____exports diff --git a/scripts/vscripts/abilities/heroes/mirana_luna/moon_sisters_synergy.lua b/scripts/vscripts/abilities/heroes/mirana_luna/moon_sisters_synergy.lua new file mode 100644 index 0000000..f53f243 --- /dev/null +++ b/scripts/vscripts/abilities/heroes/mirana_luna/moon_sisters_synergy.lua @@ -0,0 +1,184 @@ +local ____lualib = require("lualib_bundle") +local __TS__Class = ____lualib.__TS__Class +local __TS__ClassExtends = ____lualib.__TS__ClassExtends +local __TS__Decorate = ____lualib.__TS__Decorate +local ____exports = {} +local ____dota_ts_adapter = require("lib.dota_ts_adapter") +local BaseModifier = ____dota_ts_adapter.BaseModifier +local registerModifier = ____dota_ts_adapter.registerModifier +local ____incoming_damage_reduction_combine = require("utils.incoming_damage_reduction_combine") +local removeIncomingDamageReductionSource = ____incoming_damage_reduction_combine.removeIncomingDamageReductionSource +local setIncomingDamageReductionSource = ____incoming_damage_reduction_combine.setIncomingDamageReductionSource +____exports.HERO_MIRANA = "npc_dota_hero_mirana" +____exports.HERO_LUNA = "npc_dota_hero_luna" +local MOON_SISTERS_INCOMING_SOURCE = "modifier_moon_sisters_innate" +function ____exports.getMoonSisterHeroName(self, unitName) + if unitName == ____exports.HERO_MIRANA then + return ____exports.HERO_LUNA + end + if unitName == ____exports.HERO_LUNA then + return ____exports.HERO_MIRANA + end + return nil +end +local function isValidMoonSisterAlly(self, unit) + if not unit or unit:IsNull() or not unit:IsAlive() then + return false + end + if not unit:IsRealHero() or unit:IsIllusion() then + return false + end + if unit.IsTempestDouble and unit:IsTempestDouble() then + return false + end + return true +end +--- Снижение входящего урона, пока рядом союзная Мирана / Luna (вешается с врождёнки). +____exports.modifier_moon_sisters_innate = __TS__Class() +local modifier_moon_sisters_innate = ____exports.modifier_moon_sisters_innate +modifier_moon_sisters_innate.name = "modifier_moon_sisters_innate" +modifier_moon_sisters_innate.____file_path = "scripts/vscripts/abilities/heroes/mirana_luna/moon_sisters_synergy.lua" +__TS__ClassExtends(modifier_moon_sisters_innate, BaseModifier) +function modifier_moon_sisters_innate.prototype.____constructor(self, ...) + BaseModifier.prototype.____constructor(self, ...) + self.sisterNearby = false +end +function modifier_moon_sisters_innate.prototype.IsHidden(self) + return not self.sisterNearby +end +function modifier_moon_sisters_innate.prototype.IsDebuff(self) + return false +end +function modifier_moon_sisters_innate.prototype.IsPurgable(self) + return false +end +function modifier_moon_sisters_innate.prototype.RemoveOnDeath(self) + return false +end +function modifier_moon_sisters_innate.prototype.OnCreated(self) + if not IsServer() then + return + end + setIncomingDamageReductionSource( + nil, + self:GetParent(), + MOON_SISTERS_INCOMING_SOURCE, + function() + if self:GetParent():PassivesDisabled() then + return 0 + end + if not self.sisterNearby then + return 0 + end + local ability = self:GetAbility() + if not ability then + return 0 + end + return math.max( + 0, + ability:GetSpecialValueFor("moon_sisters_damage_reduction_pct") + ) + end + ) + self:refreshSisterProximity() + self:StartIntervalThink(0.25) +end +function modifier_moon_sisters_innate.prototype.OnRefresh(self) + self:refreshSisterProximity() +end +function modifier_moon_sisters_innate.prototype.OnDestroy(self) + if not IsServer() then + return + end + removeIncomingDamageReductionSource( + nil, + self:GetParent(), + MOON_SISTERS_INCOMING_SOURCE + ) +end +function modifier_moon_sisters_innate.prototype.OnIntervalThink(self) + self:refreshSisterProximity() +end +function modifier_moon_sisters_innate.prototype.refreshSisterProximity(self) + local parent = self:GetParent() + local ability = self:GetAbility() + if not parent or parent:IsNull() or not ability then + self:setSisterNearby(false) + return + end + local sisterName = ____exports.getMoonSisterHeroName( + nil, + parent:GetUnitName() + ) + if not sisterName then + self:setSisterNearby(false) + return + end + local radius = ability:GetSpecialValueFor("moon_sisters_radius") + local allies = FindUnitsInRadius( + parent:GetTeamNumber(), + parent:GetAbsOrigin(), + nil, + radius, + DOTA_UNIT_TARGET_TEAM_FRIENDLY, + DOTA_UNIT_TARGET_HERO, + DOTA_UNIT_TARGET_FLAG_NONE, + FIND_ANY_ORDER, + false + ) + local found = false + for ____, ally in ipairs(allies) do + do + if not isValidMoonSisterAlly(nil, ally) then + goto __continue26 + end + if ally == parent then + goto __continue26 + end + if ally:GetUnitName() ~= sisterName then + goto __continue26 + end + found = true + break + end + ::__continue26:: + end + self:setSisterNearby(found) +end +function modifier_moon_sisters_innate.prototype.setSisterNearby(self, nearby) + if self.sisterNearby == nearby then + return + end + self.sisterNearby = nearby + self:SetStackCount(nearby and 1 or 0) + self:SendBuffRefreshToClients() +end +modifier_moon_sisters_innate = __TS__Decorate( + modifier_moon_sisters_innate, + modifier_moon_sisters_innate, + {registerModifier(nil)}, + {kind = "class", name = "modifier_moon_sisters_innate"} +) +____exports.modifier_moon_sisters_innate = modifier_moon_sisters_innate +function ____exports.ensureMoonSistersInnateModifier(self, hero, innateAbility) + if not IsServer() then + return + end + if not hero or hero:IsNull() then + return + end + if hero:HasModifier(____exports.modifier_moon_sisters_innate.name) then + return + end + hero:AddNewModifier(hero, innateAbility, ____exports.modifier_moon_sisters_innate.name, {}) +end +function ____exports.removeMoonSistersInnateModifier(self, hero) + if not IsServer() then + return + end + if not hero or hero:IsNull() then + return + end + hero:RemoveModifierByName(____exports.modifier_moon_sisters_innate.name) +end +return ____exports diff --git a/scripts/vscripts/abilities/heroes/nagash/dark_friends.lua b/scripts/vscripts/abilities/heroes/nagash/dark_friends.lua new file mode 100644 index 0000000..b9ba352 --- /dev/null +++ b/scripts/vscripts/abilities/heroes/nagash/dark_friends.lua @@ -0,0 +1,335 @@ +local ____lualib = require("lualib_bundle") +local __TS__StringIncludes = ____lualib.__TS__StringIncludes +local __TS__Class = ____lualib.__TS__Class +local __TS__ClassExtends = ____lualib.__TS__ClassExtends +local __TS__Decorate = ____lualib.__TS__Decorate +local ____exports = {} +local ____dota_ts_adapter = require("lib.dota_ts_adapter") +local BaseAbility = ____dota_ts_adapter.BaseAbility +local BaseModifier = ____dota_ts_adapter.BaseModifier +local registerAbility = ____dota_ts_adapter.registerAbility +local registerModifier = ____dota_ts_adapter.registerModifier +local ____sync_owner_modifiers_to_summon = require("utils.sync_owner_modifiers_to_summon") +local syncOwnerModifiersToSummon = ____sync_owner_modifiers_to_summon.syncOwnerModifiersToSummon +--- Подстроки в имени юнита: на таких целей призывы не агрятся. +local DARK_FRIENDS_NO_AGGRO_NAME_PARTS = {"boss", "wisp"} +local function darkFriendsIsNoAggroUnit(self, unitName) + if not unitName then + return false + end + local lowerName = string.lower(unitName) + for ____, part in ipairs(DARK_FRIENDS_NO_AGGRO_NAME_PARTS) do + if __TS__StringIncludes(lowerName, part) then + return true + end + end + return false +end +____exports.dark_friends = __TS__Class() +local dark_friends = ____exports.dark_friends +dark_friends.name = "dark_friends" +dark_friends.____file_path = "scripts/vscripts/abilities/heroes/nagash/dark_friends.lua" +__TS__ClassExtends(dark_friends, BaseAbility) +function dark_friends.prototype.GetIntrinsicModifierName(self) + return "modifier_dark_friends_create" +end +dark_friends = __TS__Decorate( + dark_friends, + dark_friends, + {registerAbility(nil)}, + {kind = "class", name = "dark_friends"} +) +____exports.dark_friends = dark_friends +____exports.modifier_dark_friends_create = __TS__Class() +local modifier_dark_friends_create = ____exports.modifier_dark_friends_create +modifier_dark_friends_create.name = "modifier_dark_friends_create" +modifier_dark_friends_create.____file_path = "scripts/vscripts/abilities/heroes/nagash/dark_friends.lua" +__TS__ClassExtends(modifier_dark_friends_create, BaseModifier) +function modifier_dark_friends_create.prototype.spawnFriendForLevel(self, level, caster, ability) + local unitNames = {"npc_dota_melee_nagash_summon", "npc_dota_ranged_nagash_summon", "npc_dota_mage_nagash_summon", "npc_dota_shield_nagash_summon"} + local unitIndex = math.min(level, #unitNames) - 1 + local unitName = unitNames[unitIndex + 1] or unitNames[1] + local dark_friends = CreateUnitByName( + unitName, + caster:GetAbsOrigin() + RandomVector(100), + true, + caster, + caster, + caster:GetTeamNumber() + ) + dark_friends:SetOwner(caster) + dark_friends:SetBaseDamageMin(caster:GetBaseDamageMin()) + dark_friends:SetBaseDamageMax(caster:GetBaseDamageMax()) + dark_friends:SetBaseMoveSpeed(caster:GetBaseMoveSpeed() + 65) + dark_friends:AddNewModifier(caster, ability, "modifier_dark_friends", {}) + dark_friends:AddAbility("ability_stacking_crit"):SetLevel(1) + EmitSoundOn("Hero_AbyssalUnderlord.DarkRift.Aftershock", caster) +end +function modifier_dark_friends_create.prototype.OnCreated(self, params) + local caster = self:GetCaster() + local ability = self:GetAbility() + if not caster or not ability then + return + end + if caster:PassivesDisabled() then + return + end + local level = math.max( + 1, + ability:GetLevel() + ) + if self:GetStackCount() < level then + self:spawnFriendForLevel(level, caster, ability) + self:SetStackCount(level) + end +end +function modifier_dark_friends_create.prototype.OnRefresh(self, params) + local caster = self:GetCaster() + local ability = self:GetAbility() + if not caster or not ability then + return + end + if caster:PassivesDisabled() then + return + end + local level = math.max( + 1, + ability:GetLevel() + ) + if self:GetStackCount() < level then + self:spawnFriendForLevel(level, caster, ability) + self:SetStackCount(level) + end +end +modifier_dark_friends_create = __TS__Decorate( + modifier_dark_friends_create, + modifier_dark_friends_create, + {registerModifier(nil)}, + {kind = "class", name = "modifier_dark_friends_create"} +) +____exports.modifier_dark_friends_create = modifier_dark_friends_create +____exports.modifier_dark_friends = __TS__Class() +local modifier_dark_friends = ____exports.modifier_dark_friends +modifier_dark_friends.name = "modifier_dark_friends" +modifier_dark_friends.____file_path = "scripts/vscripts/abilities/heroes/nagash/dark_friends.lua" +__TS__ClassExtends(modifier_dark_friends, BaseModifier) +function modifier_dark_friends.prototype.____constructor(self, ...) + BaseModifier.prototype.____constructor(self, ...) + self.ownerIsDead = false +end +function modifier_dark_friends.prototype.IsHidden(self) + return true +end +function modifier_dark_friends.prototype.IsPurgable(self) + return false +end +function modifier_dark_friends.prototype.OnCreated(self) + if not IsServer() then + return + end + self:StartIntervalThink(0.1) + self:OnIntervalThink() +end +function modifier_dark_friends.prototype.GetRealOwner(self) + local parent = self:GetParent() + if not parent then + return nil + end + if parent:IsRealHero() then + return parent + end + local owner = parent:GetOwner() + if not owner then + return nil + end + local ownerUnit = owner + if ownerUnit:IsRealHero() then + return ownerUnit + end + return nil +end +function modifier_dark_friends.prototype.OnIntervalThink(self) + if not IsServer() then + return + end + local parent = self:GetParent() + if not parent then + return + end + local realOwner = self:GetRealOwner() + if not realOwner then + self.ownerIsDead = true + parent:Stop() + return + end + if not realOwner:IsAlive() then + self.ownerIsDead = true + parent:Stop() + local distanceToOwnerDead = (parent:GetAbsOrigin() - realOwner:GetAbsOrigin()):Length2D() + if distanceToOwnerDead > 300 then + parent:MoveToPosition(toVectorWS( + nil, + realOwner:GetAbsOrigin() + RandomVector(150) + )) + end + return + else + self.ownerIsDead = false + end + syncOwnerModifiersToSummon(nil, realOwner, parent) + parent:SetBaseAttackTime(realOwner:GetBaseAttackTime()) + parent:SetBaseDamageMin(realOwner:GetAverageTrueAttackDamage(nil)) + parent:SetBaseDamageMax(realOwner:GetAverageTrueAttackDamage(nil)) + parent:SetBaseMoveSpeed(realOwner:GetBaseMoveSpeed()) + local distanceToOwner = (parent:GetAbsOrigin() - realOwner:GetAbsOrigin()):Length2D() + if distanceToOwner > 250 then + parent:MoveToPosition(toVectorWS( + nil, + realOwner:GetAbsOrigin() + RandomVector(200) + )) + end + if distanceToOwner > 450 then + FindClearSpaceForUnit( + parent, + toVectorWS( + nil, + realOwner:GetAbsOrigin() + RandomVector(200) + ), + true + ) + end + if distanceToOwner <= 425 and realOwner:IsAlive() then + local enemies = FindUnitsInRadius( + parent:GetTeamNumber(), + parent:GetAbsOrigin(), + nil, + 425, + DOTA_UNIT_TARGET_TEAM_ENEMY, + bit.bor(DOTA_UNIT_TARGET_BASIC, DOTA_UNIT_TARGET_HERO), + DOTA_UNIT_TARGET_FLAG_NONE, + FIND_CLOSEST, + false + ) + local enemyTarget + for ____, u in ipairs(enemies) do + if not darkFriendsIsNoAggroUnit( + nil, + u:GetUnitName() + ) then + enemyTarget = u + break + end + end + if enemyTarget then + enemyTarget:MoveToTargetToAttack(realOwner) + parent:MoveToTargetToAttack(enemyTarget) + end + end +end +function modifier_dark_friends.prototype.IsAura(self) + return true +end +function modifier_dark_friends.prototype.GetAuraRadius(self) + local ability = self:GetAbility() + local level = ability and math.max( + 1, + ability:GetLevel() + ) or 1 + return self:GetAbility():GetSpecialValueFor("aura_radius") + level * 50 +end +function modifier_dark_friends.prototype.GetAuraSearchTeam(self) + return DOTA_UNIT_TARGET_TEAM_FRIENDLY +end +function modifier_dark_friends.prototype.GetAuraSearchType(self) + return bit.bor(DOTA_UNIT_TARGET_HERO, DOTA_UNIT_TARGET_BASIC) +end +function modifier_dark_friends.prototype.GetAuraSearchFlags(self) + return DOTA_UNIT_TARGET_FLAG_NONE +end +function modifier_dark_friends.prototype.GetModifierAura(self) + return "modifier_dark_friends_aura_buff" +end +function modifier_dark_friends.prototype.GetAuraEntityReject(self, target) + local caster = self:GetCaster() + if caster and caster:PassivesDisabled() then + return true + end + return false +end +function modifier_dark_friends.prototype.DeclareFunctions(self) + return {MODIFIER_PROPERTY_DAMAGEOUTGOING_PERCENTAGE} +end +function modifier_dark_friends.prototype.CheckState(self) + return { + [MODIFIER_STATE_NO_HEALTH_BAR] = true, + [MODIFIER_STATE_INVULNERABLE] = true, + [MODIFIER_STATE_NO_UNIT_COLLISION] = true, + [MODIFIER_STATE_NO_TEAM_SELECT] = true, + [MODIFIER_STATE_NO_TEAM_MOVE_TO] = true, + [MODIFIER_STATE_DISARMED] = self.ownerIsDead + } +end +function modifier_dark_friends.prototype.GetModifierDamageOutgoing_Percentage(self, event) + return self:GetAbility():GetSpecialValueFor("outgoing_damage_pct") +end +modifier_dark_friends = __TS__Decorate( + modifier_dark_friends, + modifier_dark_friends, + {registerModifier(nil)}, + {kind = "class", name = "modifier_dark_friends"} +) +____exports.modifier_dark_friends = modifier_dark_friends +____exports.modifier_dark_friends_aura_buff = __TS__Class() +local modifier_dark_friends_aura_buff = ____exports.modifier_dark_friends_aura_buff +modifier_dark_friends_aura_buff.name = "modifier_dark_friends_aura_buff" +modifier_dark_friends_aura_buff.____file_path = "scripts/vscripts/abilities/heroes/nagash/dark_friends.lua" +__TS__ClassExtends(modifier_dark_friends_aura_buff, BaseModifier) +function modifier_dark_friends_aura_buff.prototype.IsHidden(self) + return true +end +function modifier_dark_friends_aura_buff.prototype.IsPurgable(self) + return false +end +function modifier_dark_friends_aura_buff.prototype.DeclareFunctions(self) + return {MODIFIER_PROPERTY_PREATTACK_BONUS_DAMAGE, MODIFIER_PROPERTY_ATTACKSPEED_BONUS_CONSTANT, MODIFIER_PROPERTY_MOVESPEED_BONUS_PERCENTAGE, MODIFIER_PROPERTY_PHYSICAL_ARMOR_BONUS} +end +function modifier_dark_friends_aura_buff.prototype.GetModifierPreAttack_BonusDamage(self) + local ability = self:GetAbility() + local level = ability and math.max( + 1, + ability:GetLevel() + ) or 1 + return level * self:GetAbility():GetSpecialValueFor("aura_bonus_damage") +end +function modifier_dark_friends_aura_buff.prototype.GetModifierAttackSpeedBonus_Constant(self) + local ability = self:GetAbility() + local level = ability and math.max( + 1, + ability:GetLevel() + ) or 1 + return level * self:GetAbility():GetSpecialValueFor("aura_bonus_attack_speed") +end +function modifier_dark_friends_aura_buff.prototype.GetModifierMoveSpeedBonus_Percentage(self) + local ability = self:GetAbility() + local level = ability and math.max( + 1, + ability:GetLevel() + ) or 1 + return level * self:GetAbility():GetSpecialValueFor("aura_bonus_movespeed_pct") +end +function modifier_dark_friends_aura_buff.prototype.GetModifierPhysicalArmorBonus(self, event) + local ability = self:GetAbility() + local level = ability and math.max( + 1, + ability:GetLevel() + ) or 1 + return level * self:GetAbility():GetSpecialValueFor("aura_bonus_armor") +end +modifier_dark_friends_aura_buff = __TS__Decorate( + modifier_dark_friends_aura_buff, + modifier_dark_friends_aura_buff, + {registerModifier(nil)}, + {kind = "class", name = "modifier_dark_friends_aura_buff"} +) +____exports.modifier_dark_friends_aura_buff = modifier_dark_friends_aura_buff +return ____exports diff --git a/scripts/vscripts/abilities/heroes/nagash/dark_golem.lua b/scripts/vscripts/abilities/heroes/nagash/dark_golem.lua new file mode 100644 index 0000000..989efc4 --- /dev/null +++ b/scripts/vscripts/abilities/heroes/nagash/dark_golem.lua @@ -0,0 +1,129 @@ +local ____lualib = require("lualib_bundle") +local __TS__Class = ____lualib.__TS__Class +local __TS__ClassExtends = ____lualib.__TS__ClassExtends +local __TS__Decorate = ____lualib.__TS__Decorate +local ____exports = {} +local ____dota_ts_adapter = require("lib.dota_ts_adapter") +local BaseAbility = ____dota_ts_adapter.BaseAbility +local BaseModifier = ____dota_ts_adapter.BaseModifier +local registerAbility = ____dota_ts_adapter.registerAbility +local registerModifier = ____dota_ts_adapter.registerModifier +____exports.dark_golem = __TS__Class() +local dark_golem = ____exports.dark_golem +dark_golem.name = "dark_golem" +dark_golem.____file_path = "scripts/vscripts/abilities/heroes/nagash/dark_golem.lua" +__TS__ClassExtends(dark_golem, BaseAbility) +function dark_golem.prototype.OnSpellStart(self) + local caster = self:GetCaster() + EmitSoundOn("Hero_Warlock.RainOfChaos", caster) + local particleId = ParticleManager:CreateParticle("particles/units/heroes/hero_warlock/warlock_rain_of_chaos_start.vpcf", PATTACH_ABSORIGIN_FOLLOW, caster) + ParticleManager:ReleaseParticleIndex(particleId) + caster:AddNewModifier( + caster, + self, + "modifier_dark_golem_buff", + {duration = self:GetSpecialValueFor("duration")} + ) +end +dark_golem = __TS__Decorate( + dark_golem, + dark_golem, + {registerAbility(nil)}, + {kind = "class", name = "dark_golem"} +) +____exports.dark_golem = dark_golem +____exports.modifier_dark_golem_buff = __TS__Class() +local modifier_dark_golem_buff = ____exports.modifier_dark_golem_buff +modifier_dark_golem_buff.name = "modifier_dark_golem_buff" +modifier_dark_golem_buff.____file_path = "scripts/vscripts/abilities/heroes/nagash/dark_golem.lua" +__TS__ClassExtends(modifier_dark_golem_buff, BaseModifier) +function modifier_dark_golem_buff.prototype.IsHidden(self) + return false +end +function modifier_dark_golem_buff.prototype.DeclareFunctions(self) + return { + MODIFIER_PROPERTY_MODEL_CHANGE, + MODIFIER_PROPERTY_DAMAGEOUTGOING_PERCENTAGE, + MODIFIER_PROPERTY_ATTACKSPEED_PERCENTAGE, + MODIFIER_PROPERTY_MOVESPEED_BONUS_PERCENTAGE, + MODIFIER_EVENT_ON_ATTACK_LANDED + } +end +function modifier_dark_golem_buff.prototype.GetModifierModelChange(self) + return "models/items/warlock/golem/puppet_summoner_golem/puppet_summoner_golem.vmdl" +end +function modifier_dark_golem_buff.prototype.GetModifierDamageOutgoing_Percentage(self) + if IsClient() then + return 0 + end + local ability = self:GetAbility() + if not ability then + return 0 + end + local parent = self:GetParent() + if not parent then + return ability:GetSpecialValueFor("bonus_damage_pct_per_token") + end + local leaderTokenModifier = parent:FindModifierByName("modifier_leader_token") + local stackCount = leaderTokenModifier and leaderTokenModifier:GetStackCount() or 0 + return ability:GetSpecialValueFor("bonus_damage_pct_per_token") + stackCount +end +function modifier_dark_golem_buff.prototype.GetModifierAttackSpeedPercentage(self) + if IsClient() then + return 0 + end + local ability = self:GetAbility() + if not ability then + return 0 + end + local parent = self:GetParent() + if not parent then + return ability:GetSpecialValueFor("bonus_attack_speed_pct_per_token") + end + local leaderTokenModifier = parent:FindModifierByName("modifier_leader_token") + local stackCount = leaderTokenModifier and leaderTokenModifier:GetStackCount() or 0 + return ability:GetSpecialValueFor("bonus_attack_speed_pct_per_token") + stackCount +end +function modifier_dark_golem_buff.prototype.GetModifierMoveSpeedBonus_Percentage(self) + if IsClient() then + return 0 + end + local ability = self:GetAbility() + if not ability then + return 0 + end + local parent = self:GetParent() + if not parent then + return ability:GetSpecialValueFor("bonus_movespeed_pct_per_token") + end + local leaderTokenModifier = parent:FindModifierByName("modifier_leader_token") + local stackCount = leaderTokenModifier and leaderTokenModifier:GetStackCount() or 0 + return ability:GetSpecialValueFor("bonus_movespeed_pct_per_token") + stackCount +end +function modifier_dark_golem_buff.prototype.OnAttackLanded(self, event) + local parent = event.attacker + if self:GetParent() ~= parent then + return + end + local target = event.target + local attackDamage = parent:GetAttackDamage() + local cleaveDamage = attackDamage * (self:GetAbility():GetSpecialValueFor("cleave_damage") / 100) + DoCleaveAttack( + parent, + target, + self:GetAbility(), + cleaveDamage, + self:GetAbility():GetSpecialValueFor("cleave_starting_width"), + self:GetAbility():GetSpecialValueFor("cleave_ending_width"), + self:GetAbility():GetSpecialValueFor("cleave_distance"), + "particles/items_fx/battlefury_cleave.vpcf" + ) +end +modifier_dark_golem_buff = __TS__Decorate( + modifier_dark_golem_buff, + modifier_dark_golem_buff, + {registerModifier(nil)}, + {kind = "class", name = "modifier_dark_golem_buff"} +) +____exports.modifier_dark_golem_buff = modifier_dark_golem_buff +return ____exports diff --git a/scripts/vscripts/abilities/heroes/nagash/fighting_up.lua b/scripts/vscripts/abilities/heroes/nagash/fighting_up.lua new file mode 100644 index 0000000..e9796fb --- /dev/null +++ b/scripts/vscripts/abilities/heroes/nagash/fighting_up.lua @@ -0,0 +1,111 @@ +local ____lualib = require("lualib_bundle") +local __TS__Class = ____lualib.__TS__Class +local __TS__ClassExtends = ____lualib.__TS__ClassExtends +local __TS__Decorate = ____lualib.__TS__Decorate +local ____exports = {} +local ____dota_ts_adapter = require("lib.dota_ts_adapter") +local BaseAbility = ____dota_ts_adapter.BaseAbility +local BaseModifier = ____dota_ts_adapter.BaseModifier +local registerAbility = ____dota_ts_adapter.registerAbility +local registerModifier = ____dota_ts_adapter.registerModifier +____exports.fighting_up = __TS__Class() +local fighting_up = ____exports.fighting_up +fighting_up.name = "fighting_up" +fighting_up.____file_path = "scripts/vscripts/abilities/heroes/nagash/fighting_up.lua" +__TS__ClassExtends(fighting_up, BaseAbility) +function fighting_up.prototype.OnSpellStart(self) + if not IsServer() then + return + end + local caster = self:GetCaster() + local team = caster:GetTeamNumber() + local radius = self:GetSpecialValueFor("radius") + local duration = self:GetSpecialValueFor("duration") + local allies = FindUnitsInRadius( + team, + caster:GetAbsOrigin(), + nil, + radius, + DOTA_UNIT_TARGET_TEAM_FRIENDLY, + DOTA_UNIT_TARGET_HEROES_AND_CREEPS, + DOTA_UNIT_TARGET_FLAG_INVULNERABLE, + FIND_ANY_ORDER, + false + ) + for ____, unit in ipairs(allies) do + do + if not IsValidEntity(unit) or not unit:IsAlive() then + goto __continue4 + end + unit:AddNewModifier(caster, self, "modifier_fighting_up_buff", {duration = duration}) + EmitSoundOn("Hero_Warlock.FatalBonds", unit) + end + ::__continue4:: + end +end +function fighting_up.prototype.GetAOERadius(self) + return self:GetSpecialValueFor("radius") +end +fighting_up = __TS__Decorate( + fighting_up, + fighting_up, + {registerAbility(nil)}, + {kind = "class", name = "fighting_up"} +) +____exports.fighting_up = fighting_up +____exports.modifier_fighting_up_buff = __TS__Class() +local modifier_fighting_up_buff = ____exports.modifier_fighting_up_buff +modifier_fighting_up_buff.name = "modifier_fighting_up_buff" +modifier_fighting_up_buff.____file_path = "scripts/vscripts/abilities/heroes/nagash/fighting_up.lua" +__TS__ClassExtends(modifier_fighting_up_buff, BaseModifier) +function modifier_fighting_up_buff.prototype.____constructor(self, ...) + BaseModifier.prototype.____constructor(self, ...) + self.bonusHealth = 0 +end +function modifier_fighting_up_buff.prototype.IsHidden(self) + return false +end +function modifier_fighting_up_buff.prototype.IsPurgable(self) + return true +end +function modifier_fighting_up_buff.prototype.GetEffectName(self) + return "particles/econ/items/warlock/warlock_ti10_head/warlock_ti_10_fatal_bonds_icon.vpcf" +end +function modifier_fighting_up_buff.prototype.GetEffectAttachType(self) + return PATTACH_OVERHEAD_FOLLOW +end +function modifier_fighting_up_buff.prototype.OnRefresh(self, params) + self:OnCreated(params) +end +function modifier_fighting_up_buff.prototype.DeclareFunctions(self) + return {MODIFIER_PROPERTY_DAMAGEOUTGOING_PERCENTAGE, MODIFIER_PROPERTY_MOVESPEED_BONUS_PERCENTAGE, MODIFIER_PROPERTY_ATTACKSPEED_PERCENTAGE} +end +function modifier_fighting_up_buff.prototype.GetModifierDamageOutgoing_Percentage(self, event) + local ability = self:GetAbility() + if not ability then + return 0 + end + return ability:GetSpecialValueFor("bonus_damage_pct") +end +function modifier_fighting_up_buff.prototype.GetModifierMoveSpeedBonus_Percentage(self) + local ability = self:GetAbility() + if not ability then + return 0 + end + return ability:GetSpecialValueFor("bonus_movespeed_pct") +end +function modifier_fighting_up_buff.prototype.GetModifierAttackSpeedPercentage(self) + local ability = self:GetAbility() + if not ability then + return 0 + end + return ability:GetSpecialValueFor("bonus_attackspeed_pct") +end +modifier_fighting_up_buff = __TS__Decorate( + modifier_fighting_up_buff, + modifier_fighting_up_buff, + {registerModifier(nil)}, + {kind = "class", name = "modifier_fighting_up_buff"} +) +____exports.modifier_fighting_up_buff = modifier_fighting_up_buff +return ____exports diff --git a/scripts/vscripts/abilities/heroes/nagash/leader_call.lua b/scripts/vscripts/abilities/heroes/nagash/leader_call.lua new file mode 100644 index 0000000..7d2425d --- /dev/null +++ b/scripts/vscripts/abilities/heroes/nagash/leader_call.lua @@ -0,0 +1,154 @@ +local ____lualib = require("lualib_bundle") +local __TS__Class = ____lualib.__TS__Class +local __TS__ClassExtends = ____lualib.__TS__ClassExtends +local __TS__ArrayFilter = ____lualib.__TS__ArrayFilter +local __TS__Decorate = ____lualib.__TS__Decorate +local ____exports = {} +local ____dota_ts_adapter = require("lib.dota_ts_adapter") +local BaseAbility = ____dota_ts_adapter.BaseAbility +local BaseModifier = ____dota_ts_adapter.BaseModifier +local registerAbility = ____dota_ts_adapter.registerAbility +local registerModifier = ____dota_ts_adapter.registerModifier +local ____world_destroyer = require("abilities.heroes.nagash.world_destroyer") +local applyNagashSecondSkillShardInvulnerable = ____world_destroyer.applyNagashSecondSkillShardInvulnerable +____exports.leader_call = __TS__Class() +local leader_call = ____exports.leader_call +leader_call.name = "leader_call" +leader_call.____file_path = "scripts/vscripts/abilities/heroes/nagash/leader_call.lua" +__TS__ClassExtends(leader_call, BaseAbility) +function leader_call.prototype.getWorldDestroyerUnits(self, caster) + return __TS__ArrayFilter( + FindUnitsInRadius( + caster:GetTeamNumber(), + caster:GetAbsOrigin(), + nil, + 2500, + 1, + bit.bor(18, 1), + 0, + 0, + false + ), + function(____, u) return IsValidEntity(u) and u:GetOwner() == caster and u:HasModifier("modifier_dominated_bonus") end + ) +end +function leader_call.prototype.CastFilterResult(self) + if not IsServer() then + return UF_SUCCESS + end + local caster = self:GetCaster() + local units = self:getWorldDestroyerUnits(caster) + if #units < 1 then + return UF_FAIL_CUSTOM + end + return UF_SUCCESS +end +function leader_call.prototype.GetCustomCastError(self) + if not IsServer() then + return "" + end + local caster = self:GetCaster() + local units = self:getWorldDestroyerUnits(caster) + if #units < 1 then + return "#dota_hud_error_havent_entities" + end + return "" +end +function leader_call.prototype.OnSpellStart(self) + if not IsServer() then + return + end + local caster = self:GetCaster() + local team = caster:GetTeamNumber() + applyNagashSecondSkillShardInvulnerable( + nil, + caster, + self, + self:GetCastPoint() + 0.3 + ) + local explosionRadius = self:GetSpecialValueFor("explosion_radius") + local explosionDamage = self:GetSpecialValueFor("explosion_damage") + local units = self:getWorldDestroyerUnits(caster) + local sacrificedCount = 0 + for ____, unit in ipairs(units) do + local origin = unit:GetAbsOrigin() + local p = ParticleManager:CreateParticle("particles/econ/items/zeus/arcana_chariot/zeus_arcana_kill_explosion.vpcf", PATTACH_WORLDORIGIN, nil) + ParticleManager:SetParticleControl(p, 1, origin) + ParticleManager:SetParticleControl(p, 2, origin) + ParticleManager:ReleaseParticleIndex(p) + EmitSoundOn("Hero_AbyssalUnderlord.PitOfMalice", unit) + local enemies = FindUnitsInRadius( + team, + origin, + nil, + explosionRadius, + DOTA_UNIT_TARGET_TEAM_ENEMY, + bit.bor(DOTA_UNIT_TARGET_BASIC, DOTA_UNIT_TARGET_HERO), + DOTA_UNIT_TARGET_FLAG_NONE, + FIND_ANY_ORDER, + false + ) + for ____, enemy in ipairs(enemies) do + ApplyDamage({ + victim = enemy, + attacker = caster, + damage = self:GetSpecialValueFor("unit_health_for_damage") > 0 and explosionDamage + unit:GetMaxHealth() * (self:GetSpecialValueFor("unit_health_for_damage") * 0.01) or explosionDamage, + damage_type = DAMAGE_TYPE_PURE, + ability = self + }) + end + if unit:IsAlive() then + unit:Kill(self, unit) + sacrificedCount = sacrificedCount + 1 + end + end + if sacrificedCount > 0 then + local tokenModifier = caster:AddNewModifier(caster, self, "modifier_leader_token", {}) + if tokenModifier ~= nil then + tokenModifier:SetStackCount(tokenModifier:GetStackCount() + sacrificedCount) + end + end +end +leader_call = __TS__Decorate( + leader_call, + leader_call, + {registerAbility(nil)}, + {kind = "class", name = "leader_call"} +) +____exports.leader_call = leader_call +____exports.modifier_leader_token = __TS__Class() +local modifier_leader_token = ____exports.modifier_leader_token +modifier_leader_token.name = "modifier_leader_token" +modifier_leader_token.____file_path = "scripts/vscripts/abilities/heroes/nagash/leader_call.lua" +__TS__ClassExtends(modifier_leader_token, BaseModifier) +function modifier_leader_token.prototype.IsHidden(self) + return false +end +function modifier_leader_token.prototype.IsPurgable(self) + return false +end +function modifier_leader_token.prototype.DeclareFunctions(self) + return {MODIFIER_PROPERTY_STATS_AGILITY_BONUS, MODIFIER_PROPERTY_STATS_INTELLECT_BONUS, MODIFIER_PROPERTY_STATS_STRENGTH_BONUS, MODIFIER_EVENT_ON_DEATH} +end +function modifier_leader_token.prototype.OnDeath(self, event) + if event.unit == self:GetParent() then + self:SetStackCount(self:GetStackCount() * (self:GetAbility():GetSpecialValueFor("lost_souls_pct") * 0.01)) + end +end +function modifier_leader_token.prototype.GetModifierBonusStats_Agility(self) + return self:GetStackCount() * self:GetAbility():GetSpecialValueFor("bonus_stats_per_soul") +end +function modifier_leader_token.prototype.GetModifierBonusStats_Intellect(self) + return self:GetStackCount() * self:GetAbility():GetSpecialValueFor("bonus_stats_per_soul") +end +function modifier_leader_token.prototype.GetModifierBonusStats_Strength(self) + return self:GetStackCount() * self:GetAbility():GetSpecialValueFor("bonus_stats_per_soul") +end +modifier_leader_token = __TS__Decorate( + modifier_leader_token, + modifier_leader_token, + {registerModifier(nil)}, + {kind = "class", name = "modifier_leader_token"} +) +____exports.modifier_leader_token = modifier_leader_token +return ____exports diff --git a/scripts/vscripts/abilities/heroes/nagash/war_for_life.lua b/scripts/vscripts/abilities/heroes/nagash/war_for_life.lua new file mode 100644 index 0000000..5155eee --- /dev/null +++ b/scripts/vscripts/abilities/heroes/nagash/war_for_life.lua @@ -0,0 +1,170 @@ +local ____lualib = require("lualib_bundle") +local __TS__Class = ____lualib.__TS__Class +local __TS__ClassExtends = ____lualib.__TS__ClassExtends +local __TS__Decorate = ____lualib.__TS__Decorate +local ____exports = {} +local ____dota_ts_adapter = require("lib.dota_ts_adapter") +local BaseAbility = ____dota_ts_adapter.BaseAbility +local BaseModifier = ____dota_ts_adapter.BaseModifier +local registerAbility = ____dota_ts_adapter.registerAbility +local registerModifier = ____dota_ts_adapter.registerModifier +local ____incoming_damage_reduction_combine = require("utils.incoming_damage_reduction_combine") +local removeIncomingDamageReductionSource = ____incoming_damage_reduction_combine.removeIncomingDamageReductionSource +local setIncomingDamageReductionSource = ____incoming_damage_reduction_combine.setIncomingDamageReductionSource +local WAR_FOR_LIFE_INCOMING_SOURCE = "modifier_war_for_life_buff" +____exports.war_for_life = __TS__Class() +local war_for_life = ____exports.war_for_life +war_for_life.name = "war_for_life" +war_for_life.____file_path = "scripts/vscripts/abilities/heroes/nagash/war_for_life.lua" +__TS__ClassExtends(war_for_life, BaseAbility) +function war_for_life.prototype.GetAOERadius(self) + return self:GetSpecialValueFor("radius") +end +function war_for_life.prototype.Precache(self, context) + PrecacheResource("particle", "particles/units/heroes/hero_skeletonking/wraith_king_reincarnate.vpcf", context) + PrecacheResource("particle", "particles/econ/items/queen_of_pain/qop_arcana/qop_arcana_wings_ambient.vpcf", context) +end +function war_for_life.prototype.OnSpellStart(self) + if not IsServer() then + return + end + local caster = self:GetCaster() + if not caster then + return + end + local casterTeam = caster:GetTeamNumber() + local casterPos = caster:GetAbsOrigin() + local radius = self:GetSpecialValueFor("radius") + self:GetCaster():AddNewModifier( + caster, + self, + "modifier_war_for_life_buff", + {duration = self:GetSpecialValueFor("duration")} + ) + local heroes = HeroList:GetAllHeroes() + for ____, hero in ipairs(heroes) do + do + if not hero or not IsValidEntity(hero) then + goto __continue7 + end + if not hero:IsRealHero() then + goto __continue7 + end + if hero:GetTeamNumber() ~= casterTeam then + goto __continue7 + end + if hero:IsAlive() then + goto __continue7 + end + if hero.IsReincarnating and hero:IsReincarnating() then + goto __continue7 + end + local heroDeathPos = hero:GetAbsOrigin() + local distance = (heroDeathPos - casterPos):Length2D() + if distance > radius then + goto __continue7 + end + do + pcall(function() + hero:RespawnHero(false, false) + end) + end + local spawnPos = casterPos + RandomVector(150) + FindClearSpaceForUnit(hero, spawnPos, true) + local pfx = ParticleManager:CreateParticle("particles/units/heroes/hero_skeletonking/wraith_king_reincarnate.vpcf", PATTACH_ABSORIGIN_FOLLOW, hero) + ParticleManager:SetParticleControl( + pfx, + 0, + hero:GetAbsOrigin() + ) + ParticleManager:ReleaseParticleIndex(pfx) + EmitSoundOn("Hero_SkeletonKing.Hellfire_BlastImpact", hero) + local maxHealth = hero:GetMaxHealth() + local newHealth = math.max( + 1, + math.floor(maxHealth * self:GetSpecialValueFor("health_res_pct") / 100) + ) + hero:SetHealth(newHealth) + hero:AddNewModifier( + caster, + self, + "modifier_war_for_life_buff", + {duration = self:GetSpecialValueFor("duration")} + ) + hero:RemoveModifierByName("modifier_fountain_invulnerability") + hero:Stop() + hero:Purge( + false, + true, + false, + true, + true + ) + end + ::__continue7:: + end +end +war_for_life = __TS__Decorate( + war_for_life, + war_for_life, + {registerAbility(nil)}, + {kind = "class", name = "war_for_life"} +) +____exports.war_for_life = war_for_life +____exports.modifier_war_for_life_buff = __TS__Class() +local modifier_war_for_life_buff = ____exports.modifier_war_for_life_buff +modifier_war_for_life_buff.name = "modifier_war_for_life_buff" +modifier_war_for_life_buff.____file_path = "scripts/vscripts/abilities/heroes/nagash/war_for_life.lua" +__TS__ClassExtends(modifier_war_for_life_buff, BaseModifier) +function modifier_war_for_life_buff.prototype.IsHidden(self) + return true +end +function modifier_war_for_life_buff.prototype.IsPurgable(self) + return false +end +function modifier_war_for_life_buff.prototype.GetEffectName(self) + return "particles/econ/items/queen_of_pain/qop_arcana/qop_arcana_wings_ambient.vpcf" +end +function modifier_war_for_life_buff.prototype.GetEffectAttachType(self) + return PATTACH_CENTER_FOLLOW +end +function modifier_war_for_life_buff.prototype.OnCreated(self) + if not IsServer() then + return + end + local parent = self:GetParent() + local ability = self:GetAbility() + if not parent or not ability then + return + end + setIncomingDamageReductionSource( + nil, + parent, + WAR_FOR_LIFE_INCOMING_SOURCE, + function() return math.max( + 0, + ability:GetSpecialValueFor("inc_damage_res") + ) end + ) +end +function modifier_war_for_life_buff.prototype.OnRefresh(self) + self:OnCreated() +end +function modifier_war_for_life_buff.prototype.OnDestroy(self) + if not IsServer() then + return + end + removeIncomingDamageReductionSource( + nil, + self:GetParent(), + WAR_FOR_LIFE_INCOMING_SOURCE + ) +end +modifier_war_for_life_buff = __TS__Decorate( + modifier_war_for_life_buff, + modifier_war_for_life_buff, + {registerModifier(nil)}, + {kind = "class", name = "modifier_war_for_life_buff"} +) +____exports.modifier_war_for_life_buff = modifier_war_for_life_buff +return ____exports diff --git a/scripts/vscripts/abilities/heroes/nagash/world_destroyer.lua b/scripts/vscripts/abilities/heroes/nagash/world_destroyer.lua new file mode 100644 index 0000000..fc1d59a --- /dev/null +++ b/scripts/vscripts/abilities/heroes/nagash/world_destroyer.lua @@ -0,0 +1,462 @@ +local ____lualib = require("lualib_bundle") +local Set = ____lualib.Set +local __TS__New = ____lualib.__TS__New +local __TS__Class = ____lualib.__TS__Class +local __TS__ClassExtends = ____lualib.__TS__ClassExtends +local __TS__StringStartsWith = ____lualib.__TS__StringStartsWith +local __TS__ArraySome = ____lualib.__TS__ArraySome +local __TS__Decorate = ____lualib.__TS__Decorate +local ____exports = {} +local ____dota_ts_adapter = require("lib.dota_ts_adapter") +local BaseAbility = ____dota_ts_adapter.BaseAbility +local BaseModifier = ____dota_ts_adapter.BaseModifier +local registerAbility = ____dota_ts_adapter.registerAbility +local registerModifier = ____dota_ts_adapter.registerModifier +require("utils.utils") +local WORLD_DESTROYER_BLOCKED_UNITS = __TS__New(Set, {"npc_wisps", "npc_fish_1", "npc_fish_2", "npc_bomb"}) +local WORLD_DESTROYER_BLOCKED_PREFIXES = {"npc_wave_boss"} +local WORLD_DESTROYER_PREP_DURATION = 3 +local function getWorldDestroyerSoulEaterMaxHealth(self, ability, caster) + local base = ability:GetSpecialValueFor("health_soul_eater") + return base + caster:GetMaxHealth() * 0.5 +end +--- Неуязвимость кастера при шарде на world destroyer / leader call. +function ____exports.applyNagashSecondSkillShardInvulnerable(self, caster, ability, duration) + if not IsServer() then + return + end + if not HasShard(nil, caster) then + return + end + if duration <= 0 then + return + end + caster:AddNewModifier(caster, ability, ____exports.modifier_world_destroyer_shard_invulnerable.name, {duration = duration}) +end +____exports.world_destroyer = __TS__Class() +local world_destroyer = ____exports.world_destroyer +world_destroyer.name = "world_destroyer" +world_destroyer.____file_path = "scripts/vscripts/abilities/heroes/nagash/world_destroyer.lua" +__TS__ClassExtends(world_destroyer, BaseAbility) +function world_destroyer.prototype.Precache(self, context) + PrecacheResource("sound", "soundevents/game_sounds_heroes/game_sounds_arc_warden.vsndevts", context) +end +function world_destroyer.prototype.OnSpellStart(self) + if not IsServer() then + return + end + local caster = self:GetCaster() + local playerId = caster:GetPlayerOwnerID() + local point = self:GetCursorPosition() + local casterLevel = caster:GetLevel() + local shardInvulnerableDuration = self:GetSpecialValueFor("shard_invulnerable_duration") + ____exports.applyNagashSecondSkillShardInvulnerable(nil, caster, self, shardInvulnerableDuration > 0 and shardInvulnerableDuration or WORLD_DESTROYER_PREP_DURATION) + local radius = self:GetSpecialValueFor("radius") + local duration = self:GetSpecialValueFor("dominate_duration") + local soulEater = CreateUnitByName( + "npc_dota_nagash_soul_eater", + point, + true, + caster, + caster, + caster:GetTeamNumber() + ) + if soulEater and IsValidEntity(soulEater) then + local countParticle + FindClearSpaceForUnit(soulEater, point, true) + local soulEaterMaxHealth = getWorldDestroyerSoulEaterMaxHealth(nil, self, caster) + soulEater:AddNewModifier(caster, self, ____exports.modifier_world_destroyer_soul_eater.name, {duration = WORLD_DESTROYER_PREP_DURATION + 0.15, max_health = soulEaterMaxHealth}) + local prepParticle = ParticleManager:CreateParticle("particles/nagash_world_full.vpcf", PATTACH_ABSORIGIN_FOLLOW, soulEater) + ParticleManager:SetParticleControl( + prepParticle, + 0, + soulEater:GetAbsOrigin() + ) + local cancelled = false + local soulEaterIndex = soulEater:GetEntityIndex() + local killListener + killListener = ListenToGameEvent( + "entity_killed", + function(event) + local killed = EntIndexToHScript(event.entindex_killed) + if killed and IsValidEntity(killed) and killed:GetEntityIndex() == soulEaterIndex then + cancelled = true + do + pcall(function() + ParticleManager:DestroyParticle(prepParticle, false) + ParticleManager:ReleaseParticleIndex(prepParticle) + end) + end + do + pcall(function() + if countParticle ~= nil then + ParticleManager:DestroyParticle(countParticle, true) + ParticleManager:ReleaseParticleIndex(countParticle) + end + end) + end + do + pcall(function() + StopListeningToGameEvent(killListener) + end) + end + end + end, + nil + ) + local function showCount(____, n) + do + pcall(function() + if countParticle ~= nil then + ParticleManager:DestroyParticle(countParticle, true) + ParticleManager:ReleaseParticleIndex(countParticle) + end + countParticle = ParticleManager:CreateParticle("particles/units/heroes/hero_centaur/centaur_shard_buff_strength_counter_stack.vpcf", PATTACH_OVERHEAD_FOLLOW, soulEater) + ParticleManager:SetParticleControl( + countParticle, + 2, + Vector(n, 0, 0) + ) + ParticleManager:SetParticleControlEnt( + countParticle, + 3, + soulEater, + PATTACH_OVERHEAD_FOLLOW, + nil, + soulEater:GetAbsOrigin(), + true + ) + EmitSoundOn("General.ButtonClick", soulEater) + EmitSoundOn("Hero_ArcWarden.MagneticField", soulEater) + end) + end + end + showCount(nil, 3) + Timers:CreateTimer( + 1, + function() + if cancelled or not IsValidEntity(soulEater) or not soulEater:IsAlive() then + return + end + showCount(nil, 2) + end + ) + Timers:CreateTimer( + 2, + function() + if cancelled or not IsValidEntity(soulEater) or not soulEater:IsAlive() then + return + end + showCount(nil, 1) + end + ) + Timers:CreateTimer( + 3, + function() + if cancelled or not IsValidEntity(soulEater) or not soulEater:IsAlive() then + return + end + local origin = soulEater:GetAbsOrigin() + local burst = ParticleManager:CreateParticle("particles/units/heroes/hero_warlock/warlock_rain_of_chaos_start.vpcf", PATTACH_WORLDORIGIN, nil) + ParticleManager:SetParticleControl(burst, 0, origin) + ParticleManager:ReleaseParticleIndex(burst) + EmitSoundOnLocationWithCaster(origin, "Hero_Enchantress.EnchantCreep", caster) + local enemies = FindUnitsInRadius( + caster:GetTeamNumber(), + origin, + nil, + radius, + DOTA_UNIT_TARGET_TEAM_ENEMY, + DOTA_UNIT_TARGET_BASIC, + DOTA_UNIT_TARGET_FLAG_NOT_ANCIENTS + DOTA_UNIT_TARGET_FLAG_NOT_SUMMONED + DOTA_UNIT_TARGET_FLAG_NOT_CREEP_HERO, + FIND_ANY_ORDER, + false + ) + local processed = 0 + for ____, unit in ipairs(enemies) do + do + if not unit:IsAlive() then + goto __continue26 + end + if unit:IsConsideredHero() then + goto __continue26 + end + if unit:IsAncient() then + goto __continue26 + end + if __TS__StringStartsWith( + unit:GetUnitName(), + "npc_wave_boss" + ) then + goto __continue26 + end + if unit.GetLevel and unit:GetLevel() > casterLevel then + goto __continue26 + end + local spawnPos = unit:GetAbsOrigin() + local spawnForward = unit:GetForwardVector() + local originalName = unit:GetUnitName() + local isBlockedByName = WORLD_DESTROYER_BLOCKED_UNITS:has(originalName) + local isBlockedByPrefix = __TS__ArraySome( + WORLD_DESTROYER_BLOCKED_PREFIXES, + function(____, prefix) return __TS__StringStartsWith(originalName, prefix) end + ) + if isBlockedByName or isBlockedByPrefix then + goto __continue26 + end + local copy = CreateUnitByName( + originalName, + spawnPos, + true, + caster, + caster, + caster:GetTeamNumber() + ) + if copy and IsValidEntity(copy) then + copy:SetForwardVector(spawnForward) + FindClearSpaceForUnit(copy, spawnPos, true) + copy:SetOwner(caster) + copy:SetControllableByPlayer(playerId, true) + EmitSoundOn("Hero_Warlock.RainOfChaos", copy) + do + pcall(function() + local copyMaxHealth = unit:GetMaxHealth() + local copyHealth = unit:GetHealth() + copy:SetBaseDamageMin(unit:GetBaseDamageMin()) + copy:SetBaseDamageMax(unit:GetBaseDamageMax()) + copy:SetBaseMoveSpeed(unit:GetBaseMoveSpeed()) + copy:SetBaseMaxHealth(copyMaxHealth) + copy:SetMaxHealth(copyMaxHealth) + copy:SetHealth(math.min(copyHealth, copyMaxHealth)) + end) + end + if duration > 0 then + do + pcall(function() + copy:AddNewModifier(caster, self, "modifier_dominated", {duration = duration}) + copy:AddNewModifier(caster, self, "modifier_dominated_bonus", {duration = duration}) + end) + end + end + end + if unit:IsAlive() then + unit:Kill(self, unit) + end + processed = processed + 1 + end + ::__continue26:: + end + do + pcall(function() + ParticleManager:DestroyParticle(prepParticle, false) + ParticleManager:ReleaseParticleIndex(prepParticle) + end) + end + do + pcall(function() + if countParticle ~= nil then + ParticleManager:DestroyParticle(countParticle, true) + ParticleManager:ReleaseParticleIndex(countParticle) + end + end) + end + do + pcall(function() + UTIL_Remove(soulEater) + end) + end + do + pcall(function() + StopListeningToGameEvent(killListener) + end) + end + end + ) + end +end +function world_destroyer.prototype.GetAOERadius(self) + return self:GetSpecialValueFor("radius") +end +world_destroyer = __TS__Decorate( + world_destroyer, + world_destroyer, + {registerAbility(nil)}, + {kind = "class", name = "world_destroyer"} +) +____exports.world_destroyer = world_destroyer +--- Высасыватель душ на фазе 3-2-1: фиксирует целевой пул HP (без отката к ~150 при маг. уроне). +____exports.modifier_world_destroyer_soul_eater = __TS__Class() +local modifier_world_destroyer_soul_eater = ____exports.modifier_world_destroyer_soul_eater +modifier_world_destroyer_soul_eater.name = "modifier_world_destroyer_soul_eater" +modifier_world_destroyer_soul_eater.____file_path = "scripts/vscripts/abilities/heroes/nagash/world_destroyer.lua" +__TS__ClassExtends(modifier_world_destroyer_soul_eater, BaseModifier) +function modifier_world_destroyer_soul_eater.prototype.____constructor(self, ...) + BaseModifier.prototype.____constructor(self, ...) + self.desiredMaxHealth = 0 +end +function modifier_world_destroyer_soul_eater.prototype.IsHidden(self) + return true +end +function modifier_world_destroyer_soul_eater.prototype.IsPurgable(self) + return false +end +function modifier_world_destroyer_soul_eater.prototype.OnCreated(self, kv) + if not IsServer() then + return + end + self.desiredMaxHealth = tonumber(tostring(kv.max_health or 0)) or 0 + if self.desiredMaxHealth <= 0 then + return + end + self:applySoulEaterHealth(true) + Timers:CreateTimer( + 0, + function() + if not IsValidEntity(self:GetParent()) or not self:GetParent():IsAlive() then + return nil + end + self:applySoulEaterHealth(false) + return nil + end + ) +end +function modifier_world_destroyer_soul_eater.prototype.applySoulEaterHealth(self, healToFull) + local parent = self:GetParent() + if not parent or not parent:IsAlive() or self.desiredMaxHealth <= 0 then + return + end + parent:SetBaseMaxHealth(self.desiredMaxHealth) + parent:SetMaxHealth(self.desiredMaxHealth) + if healToFull then + parent:SetHealth(self.desiredMaxHealth) + return + end + local current = parent:GetHealth() + if current > self.desiredMaxHealth then + parent:SetHealth(self.desiredMaxHealth) + end +end +function modifier_world_destroyer_soul_eater.prototype.DeclareFunctions(self) + return {MODIFIER_PROPERTY_EXTRA_HEALTH_BONUS} +end +function modifier_world_destroyer_soul_eater.prototype.GetModifierExtraHealthBonus(self) + if self.desiredMaxHealth <= 0 then + return 0 + end + local base = self:GetParent():GetBaseMaxHealth() + return math.max(0, self.desiredMaxHealth - base) +end +modifier_world_destroyer_soul_eater = __TS__Decorate( + modifier_world_destroyer_soul_eater, + modifier_world_destroyer_soul_eater, + {registerModifier(nil)}, + {kind = "class", name = "modifier_world_destroyer_soul_eater"} +) +____exports.modifier_world_destroyer_soul_eater = modifier_world_destroyer_soul_eater +____exports.modifier_world_destroyer_shard_invulnerable = __TS__Class() +local modifier_world_destroyer_shard_invulnerable = ____exports.modifier_world_destroyer_shard_invulnerable +modifier_world_destroyer_shard_invulnerable.name = "modifier_world_destroyer_shard_invulnerable" +modifier_world_destroyer_shard_invulnerable.____file_path = "scripts/vscripts/abilities/heroes/nagash/world_destroyer.lua" +__TS__ClassExtends(modifier_world_destroyer_shard_invulnerable, BaseModifier) +function modifier_world_destroyer_shard_invulnerable.prototype.IsHidden(self) + return true +end +function modifier_world_destroyer_shard_invulnerable.prototype.IsPurgable(self) + return false +end +function modifier_world_destroyer_shard_invulnerable.prototype.IsDebuff(self) + return false +end +function modifier_world_destroyer_shard_invulnerable.prototype.RemoveOnDeath(self) + return true +end +function modifier_world_destroyer_shard_invulnerable.prototype.CheckState(self) + return {[MODIFIER_STATE_INVULNERABLE] = true} +end +modifier_world_destroyer_shard_invulnerable = __TS__Decorate( + modifier_world_destroyer_shard_invulnerable, + modifier_world_destroyer_shard_invulnerable, + {registerModifier(nil)}, + {kind = "class", name = "modifier_world_destroyer_shard_invulnerable"} +) +____exports.modifier_world_destroyer_shard_invulnerable = modifier_world_destroyer_shard_invulnerable +____exports.modifier_dominated_bonus = __TS__Class() +local modifier_dominated_bonus = ____exports.modifier_dominated_bonus +modifier_dominated_bonus.name = "modifier_dominated_bonus" +modifier_dominated_bonus.____file_path = "scripts/vscripts/abilities/heroes/nagash/world_destroyer.lua" +__TS__ClassExtends(modifier_dominated_bonus, BaseModifier) +function modifier_dominated_bonus.prototype.IsHidden(self) + return true +end +function modifier_dominated_bonus.prototype.IsPurgable(self) + return false +end +function modifier_dominated_bonus.prototype.RemoveOnDeath(self) + return false +end +function modifier_dominated_bonus.prototype.GetStatusEffectName(self) + return "particles/events/crownfall/survivors/status/status_effect_burn.vpcf" +end +function modifier_dominated_bonus.prototype.GetEffectName(self) + return "particles/econ/items/omniknight/omni_crimson_witness_2021/omniknight_crimson_witness_2021_degen_aura_debuff.vpcf" +end +function modifier_dominated_bonus.prototype.StatusEffectPriority(self) + return 100 +end +function modifier_dominated_bonus.prototype.DeclareFunctions(self) + return {MODIFIER_PROPERTY_PREATTACK_BONUS_DAMAGE, MODIFIER_PROPERTY_ATTACKSPEED_BONUS_CONSTANT, MODIFIER_PROPERTY_PHYSICAL_ARMOR_BONUS} +end +function modifier_dominated_bonus.prototype.CheckState(self) + return {[MODIFIER_STATE_NO_HEALTH_BAR] = true, [MODIFIER_STATE_NO_UNIT_COLLISION] = true} +end +function modifier_dominated_bonus.prototype.OnDestroy(self) + if IsClient() then + return + end + local caster = self:GetCaster() + local tokenModifier = caster:AddNewModifier( + caster, + self:GetAbility(), + "modifier_leader_token", + {} + ) + if tokenModifier ~= nil then + tokenModifier:SetStackCount(tokenModifier:GetStackCount() + 1) + end + self:GetParent():Kill( + self:GetAbility(), + self:GetParent() + ) +end +function modifier_dominated_bonus.prototype.GetModifierPreAttack_BonusDamage(self) + local ability = self:GetAbility() + local level = ability and math.max( + 1, + ability:GetLevel() + ) or 1 + return self:GetAbility():GetSpecialValueFor("bonus_damage") +end +function modifier_dominated_bonus.prototype.GetModifierAttackSpeedBonus_Constant(self) + local ability = self:GetAbility() + local level = ability and math.max( + 1, + ability:GetLevel() + ) or 1 + return self:GetAbility():GetSpecialValueFor("bonus_attack_speed") +end +function modifier_dominated_bonus.prototype.GetModifierPhysicalArmorBonus(self, event) + local ability = self:GetAbility() + local level = ability and math.max( + 1, + ability:GetLevel() + ) or 1 + return self:GetAbility():GetSpecialValueFor("bonus_armor") +end +modifier_dominated_bonus = __TS__Decorate( + modifier_dominated_bonus, + modifier_dominated_bonus, + {registerModifier(nil)}, + {kind = "class", name = "modifier_dominated_bonus"} +) +____exports.modifier_dominated_bonus = modifier_dominated_bonus +return ____exports diff --git a/scripts/vscripts/abilities/heroes/nevermore/nevermore_dark_lord_custom.lua b/scripts/vscripts/abilities/heroes/nevermore/nevermore_dark_lord_custom.lua new file mode 100644 index 0000000..e73637d --- /dev/null +++ b/scripts/vscripts/abilities/heroes/nevermore/nevermore_dark_lord_custom.lua @@ -0,0 +1,157 @@ +local ____lualib = require("lualib_bundle") +local __TS__Class = ____lualib.__TS__Class +local __TS__ClassExtends = ____lualib.__TS__ClassExtends +local __TS__Decorate = ____lualib.__TS__Decorate +local ____exports = {} +local ____dota_ts_adapter = require("lib.dota_ts_adapter") +local BaseAbility = ____dota_ts_adapter.BaseAbility +local BaseModifier = ____dota_ts_adapter.BaseModifier +local registerAbility = ____dota_ts_adapter.registerAbility +local registerModifier = ____dota_ts_adapter.registerModifier +local ____nevermore_necromastery_custom = require("abilities.heroes.nevermore.nevermore_necromastery_custom") +local modifier_nevermore_necromastery_custom = ____nevermore_necromastery_custom.modifier_nevermore_necromastery_custom +____exports.nevermore_dark_lord_custom = __TS__Class() +local nevermore_dark_lord_custom = ____exports.nevermore_dark_lord_custom +nevermore_dark_lord_custom.name = "nevermore_dark_lord_custom" +nevermore_dark_lord_custom.____file_path = "scripts/vscripts/abilities/heroes/nevermore/nevermore_dark_lord_custom.lua" +__TS__ClassExtends(nevermore_dark_lord_custom, BaseAbility) +function nevermore_dark_lord_custom.prototype.GetIntrinsicModifierName(self) + return ____exports.modifier_nevermore_dark_lord_custom.name +end +nevermore_dark_lord_custom = __TS__Decorate( + nevermore_dark_lord_custom, + nevermore_dark_lord_custom, + {registerAbility(nil)}, + {kind = "class", name = "nevermore_dark_lord_custom"} +) +____exports.nevermore_dark_lord_custom = nevermore_dark_lord_custom +____exports.modifier_nevermore_dark_lord_custom = __TS__Class() +local modifier_nevermore_dark_lord_custom = ____exports.modifier_nevermore_dark_lord_custom +modifier_nevermore_dark_lord_custom.name = "modifier_nevermore_dark_lord_custom" +modifier_nevermore_dark_lord_custom.____file_path = "scripts/vscripts/abilities/heroes/nevermore/nevermore_dark_lord_custom.lua" +__TS__ClassExtends(modifier_nevermore_dark_lord_custom, BaseModifier) +function modifier_nevermore_dark_lord_custom.prototype.IsHidden(self) + return true +end +function modifier_nevermore_dark_lord_custom.prototype.IsPurgable(self) + return false +end +function modifier_nevermore_dark_lord_custom.prototype.IsPurgeException(self) + return false +end +function modifier_nevermore_dark_lord_custom.prototype.IsAura(self) + return not self:GetParent():PassivesDisabled() +end +function modifier_nevermore_dark_lord_custom.prototype.GetAuraRadius(self) + return self:GetAbility():GetSpecialValueFor("presence_radius") +end +function modifier_nevermore_dark_lord_custom.prototype.GetModifierAura(self) + return ____exports.modifier_nevermore_dark_lord_custom_debuff.name +end +function modifier_nevermore_dark_lord_custom.prototype.GetAuraSearchTeam(self) + if HasShard( + nil, + self:GetParent() + ) then + return DOTA_UNIT_TARGET_TEAM_BOTH + end + return DOTA_UNIT_TARGET_TEAM_ENEMY +end +function modifier_nevermore_dark_lord_custom.prototype.GetAuraDuration(self) + return 0 +end +function modifier_nevermore_dark_lord_custom.prototype.GetAuraSearchFlags(self) + return DOTA_UNIT_TARGET_FLAG_NONE +end +function modifier_nevermore_dark_lord_custom.prototype.GetAuraSearchType(self) + return DOTA_UNIT_TARGET_HERO + DOTA_UNIT_TARGET_BASIC +end +function modifier_nevermore_dark_lord_custom.prototype.AllowIllusionDuplicate(self) + return true +end +modifier_nevermore_dark_lord_custom = __TS__Decorate( + modifier_nevermore_dark_lord_custom, + modifier_nevermore_dark_lord_custom, + {registerModifier(nil)}, + {kind = "class", name = "modifier_nevermore_dark_lord_custom"} +) +____exports.modifier_nevermore_dark_lord_custom = modifier_nevermore_dark_lord_custom +____exports.modifier_nevermore_dark_lord_custom_debuff = __TS__Class() +local modifier_nevermore_dark_lord_custom_debuff = ____exports.modifier_nevermore_dark_lord_custom_debuff +modifier_nevermore_dark_lord_custom_debuff.name = "modifier_nevermore_dark_lord_custom_debuff" +modifier_nevermore_dark_lord_custom_debuff.____file_path = "scripts/vscripts/abilities/heroes/nevermore/nevermore_dark_lord_custom.lua" +__TS__ClassExtends(modifier_nevermore_dark_lord_custom_debuff, BaseModifier) +function modifier_nevermore_dark_lord_custom_debuff.prototype.IsPurgable(self) + return false +end +function modifier_nevermore_dark_lord_custom_debuff.prototype.IsPurgeException(self) + return false +end +function modifier_nevermore_dark_lord_custom_debuff.prototype.IsHidden(self) + return false +end +function modifier_nevermore_dark_lord_custom_debuff.prototype.IsDebuff(self) + local ____temp_2 = self:GetParent():GetTeamNumber() + local ____opt_0 = self:GetCaster() + if ____temp_2 == (____opt_0 and ____opt_0:GetTeamNumber()) then + return false + end + if not IsServer() then + return false + end + if self:GetParent():PassivesDisabled() then + return false + end + return true +end +function modifier_nevermore_dark_lord_custom_debuff.prototype.OnCreated(self) + if not IsServer() then + return + end + self:SetStackCount(0) + self:StartIntervalThink(0.2) + self:OnIntervalThink() +end +function modifier_nevermore_dark_lord_custom_debuff.prototype.OnIntervalThink(self) + if not IsServer() then + return + end + local ____temp_5 = self:GetCaster() + if ____temp_5 == nil then + local ____opt_3 = self:GetAbility() + ____temp_5 = ____opt_3 and ____opt_3:GetCaster() + end + local caster = ____temp_5 + if not caster or caster:IsNull() then + self:SetStackCount(0) + return + end + local soulsModifier = caster:FindModifierByName(modifier_nevermore_necromastery_custom.name) + self:SetStackCount(soulsModifier and soulsModifier:GetStackCount() or 0) +end +function modifier_nevermore_dark_lord_custom_debuff.prototype.DeclareFunctions(self) + return {MODIFIER_PROPERTY_PHYSICAL_ARMOR_BONUS} +end +function modifier_nevermore_dark_lord_custom_debuff.prototype.GetModifierPhysicalArmorBonus(self) + local ____opt_6 = self:GetCaster() + if ____opt_6 and ____opt_6:PassivesDisabled() then + return 0 + end + if self:GetParent() == self:GetAbility():GetCaster() then + return 0 + end + local ____opt_8 = self:GetAbility() + local armorPerSoul = ____opt_8 and ____opt_8:GetSpecialValueFor("armor_reduction_per_soul") or 0 + if self:GetParent():GetTeamNumber() == self:GetCaster():GetTeamNumber() then + return self:GetStackCount() * armorPerSoul + end + return -(self:GetStackCount() * armorPerSoul) +end +modifier_nevermore_dark_lord_custom_debuff = __TS__Decorate( + modifier_nevermore_dark_lord_custom_debuff, + modifier_nevermore_dark_lord_custom_debuff, + {registerModifier(nil)}, + {kind = "class", name = "modifier_nevermore_dark_lord_custom_debuff"} +) +____exports.modifier_nevermore_dark_lord_custom_debuff = modifier_nevermore_dark_lord_custom_debuff +return ____exports diff --git a/scripts/vscripts/abilities/heroes/nevermore/nevermore_deadly_strike_custom.lua b/scripts/vscripts/abilities/heroes/nevermore/nevermore_deadly_strike_custom.lua new file mode 100644 index 0000000..91811f1 --- /dev/null +++ b/scripts/vscripts/abilities/heroes/nevermore/nevermore_deadly_strike_custom.lua @@ -0,0 +1,95 @@ +local ____lualib = require("lualib_bundle") +local __TS__Class = ____lualib.__TS__Class +local __TS__ClassExtends = ____lualib.__TS__ClassExtends +local __TS__Decorate = ____lualib.__TS__Decorate +local ____exports = {} +local ____dota_ts_adapter = require("lib.dota_ts_adapter") +local BaseAbility = ____dota_ts_adapter.BaseAbility +local BaseModifier = ____dota_ts_adapter.BaseModifier +local registerAbility = ____dota_ts_adapter.registerAbility +local registerModifier = ____dota_ts_adapter.registerModifier +local ____ability_stacking_crit = require("abilities.modifiers.ability_stacking_crit") +local modifier_stacking_crit = ____ability_stacking_crit.modifier_stacking_crit +local ____nevermore_necromastery_custom = require("abilities.heroes.nevermore.nevermore_necromastery_custom") +local modifier_nevermore_necromastery_custom = ____nevermore_necromastery_custom.modifier_nevermore_necromastery_custom +local DEADLY_STRIKE_SOURCE = "nevermore_deadly_strike_custom" +____exports.nevermore_deadly_strike_custom = __TS__Class() +local nevermore_deadly_strike_custom = ____exports.nevermore_deadly_strike_custom +nevermore_deadly_strike_custom.name = "nevermore_deadly_strike_custom" +nevermore_deadly_strike_custom.____file_path = "scripts/vscripts/abilities/heroes/nevermore/nevermore_deadly_strike_custom.lua" +__TS__ClassExtends(nevermore_deadly_strike_custom, BaseAbility) +function nevermore_deadly_strike_custom.prototype.GetIntrinsicModifierName(self) + return ____exports.modifier_nevermore_deadly_strike_custom.name +end +nevermore_deadly_strike_custom = __TS__Decorate( + nevermore_deadly_strike_custom, + nevermore_deadly_strike_custom, + {registerAbility(nil)}, + {kind = "class", name = "nevermore_deadly_strike_custom"} +) +____exports.nevermore_deadly_strike_custom = nevermore_deadly_strike_custom +____exports.modifier_nevermore_deadly_strike_custom = __TS__Class() +local modifier_nevermore_deadly_strike_custom = ____exports.modifier_nevermore_deadly_strike_custom +modifier_nevermore_deadly_strike_custom.name = "modifier_nevermore_deadly_strike_custom" +modifier_nevermore_deadly_strike_custom.____file_path = "scripts/vscripts/abilities/heroes/nevermore/nevermore_deadly_strike_custom.lua" +__TS__ClassExtends(modifier_nevermore_deadly_strike_custom, BaseModifier) +function modifier_nevermore_deadly_strike_custom.prototype.____constructor(self, ...) + BaseModifier.prototype.____constructor(self, ...) + self.deadlyStrikeArmed = false +end +function modifier_nevermore_deadly_strike_custom.prototype.IsHidden(self) + return true +end +function modifier_nevermore_deadly_strike_custom.prototype.IsPurgable(self) + return false +end +function modifier_nevermore_deadly_strike_custom.prototype.IsPurgeException(self) + return false +end +function modifier_nevermore_deadly_strike_custom.prototype.DeclareFunctions(self) + return {MODIFIER_EVENT_ON_ATTACK_LANDED} +end +function modifier_nevermore_deadly_strike_custom.prototype.OnAttackLanded(self, event) + if not IsServer() then + return + end + local parent = self:GetParent() + if event.attacker ~= parent then + return + end + if parent:PassivesDisabled() then + return + end + local stackingCrit = modifier_stacking_crit:GetForUnit(parent) + if not stackingCrit then + return + end + if self.deadlyStrikeArmed then + stackingCrit:RemoveCrit(DEADLY_STRIKE_SOURCE) + self.deadlyStrikeArmed = false + end + local ability = self:GetAbility() + if not ability then + return + end + local chance = ability:GetSpecialValueFor("deadly_strike_chance") + local chanceNormalized = chance / 100 + if rollLuckChance(nil, parent, chanceNormalized, 0.01) then + local soulsModifier = parent:FindModifierByName(modifier_nevermore_necromastery_custom.name) + local souls = soulsModifier and soulsModifier:GetStackCount() or 0 + local baseCrit = ability:GetSpecialValueFor("deadly_strike_base_crit") + local critPerSoul = ability:GetSpecialValueFor("deadly_strike_crit_per_soul") + local critMult = baseCrit + souls * critPerSoul + stackingCrit:AddCustomCrit(0, critMult, DEADLY_STRIKE_SOURCE) + stackingCrit:GuaranteeNextCrit(1) + self.deadlyStrikeArmed = true + end +end +modifier_nevermore_deadly_strike_custom = __TS__Decorate( + modifier_nevermore_deadly_strike_custom, + modifier_nevermore_deadly_strike_custom, + {registerModifier(nil)}, + {kind = "class", name = "modifier_nevermore_deadly_strike_custom"} +) +____exports.modifier_nevermore_deadly_strike_custom = modifier_nevermore_deadly_strike_custom +return ____exports diff --git a/scripts/vscripts/abilities/heroes/nevermore/nevermore_necromastery_custom.lua b/scripts/vscripts/abilities/heroes/nevermore/nevermore_necromastery_custom.lua new file mode 100644 index 0000000..833cc6b --- /dev/null +++ b/scripts/vscripts/abilities/heroes/nevermore/nevermore_necromastery_custom.lua @@ -0,0 +1,197 @@ +local ____lualib = require("lualib_bundle") +local __TS__Class = ____lualib.__TS__Class +local __TS__ClassExtends = ____lualib.__TS__ClassExtends +local __TS__Decorate = ____lualib.__TS__Decorate +local ____exports = {} +local ____dota_ts_adapter = require("lib.dota_ts_adapter") +local BaseAbility = ____dota_ts_adapter.BaseAbility +local BaseModifier = ____dota_ts_adapter.BaseModifier +local registerAbility = ____dota_ts_adapter.registerAbility +local registerModifier = ____dota_ts_adapter.registerModifier +local ____nevermore_requiem_custom = require("abilities.heroes.nevermore.nevermore_requiem_custom") +local nevermore_requiem_custom = ____nevermore_requiem_custom.nevermore_requiem_custom +____exports.nevermore_necromastery_custom = __TS__Class() +local nevermore_necromastery_custom = ____exports.nevermore_necromastery_custom +nevermore_necromastery_custom.name = "nevermore_necromastery_custom" +nevermore_necromastery_custom.____file_path = "scripts/vscripts/abilities/heroes/nevermore/nevermore_necromastery_custom.lua" +__TS__ClassExtends(nevermore_necromastery_custom, BaseAbility) +function nevermore_necromastery_custom.prototype.GetIntrinsicModifierName(self) + return ____exports.modifier_nevermore_necromastery_custom.name +end +function nevermore_necromastery_custom.prototype.CastFilterResult(self) + local caster = self:GetCaster() + local soulModifier = caster:FindModifierByName(____exports.modifier_nevermore_necromastery_custom.name) + if not soulModifier then + return UF_FAIL_CUSTOM + end + if not soulModifier:CanActivateSoulOverflow() then + return UF_FAIL_CUSTOM + end + return UF_SUCCESS +end +function nevermore_necromastery_custom.prototype.GetCustomCastError(self) + return "#dota_hud_error_nevermore_need_max_souls" +end +function nevermore_necromastery_custom.prototype.OnSpellStart(self) + if not IsServer() then + return + end + local caster = self:GetCaster() + local duration = self:GetSpecialValueFor("active_duration") + ____exports.modifier_nevermore_necromastery_custom_active:apply(caster, caster, self, {duration = duration}) + EmitSoundOn("Hero_Nevermore.Necromastery", caster) +end +nevermore_necromastery_custom = __TS__Decorate( + nevermore_necromastery_custom, + nevermore_necromastery_custom, + {registerAbility(nil)}, + {kind = "class", name = "nevermore_necromastery_custom"} +) +____exports.nevermore_necromastery_custom = nevermore_necromastery_custom +____exports.modifier_nevermore_necromastery_custom = __TS__Class() +local modifier_nevermore_necromastery_custom = ____exports.modifier_nevermore_necromastery_custom +modifier_nevermore_necromastery_custom.name = "modifier_nevermore_necromastery_custom" +modifier_nevermore_necromastery_custom.____file_path = "scripts/vscripts/abilities/heroes/nevermore/nevermore_necromastery_custom.lua" +__TS__ClassExtends(modifier_nevermore_necromastery_custom, BaseModifier) +function modifier_nevermore_necromastery_custom.prototype.____constructor(self, ...) + BaseModifier.prototype.____constructor(self, ...) + self.damagePerSoul = 0 + self.soulsPerKill = 0 + self.maxSouls = 0 + self.soulRetainRatio = 0 +end +function modifier_nevermore_necromastery_custom.prototype.IsPurgable(self) + return false +end +function modifier_nevermore_necromastery_custom.prototype.IsPurgeException(self) + return false +end +function modifier_nevermore_necromastery_custom.prototype.DeclareFunctions(self) + return {MODIFIER_EVENT_ON_DEATH, MODIFIER_PROPERTY_PREATTACK_BONUS_DAMAGE} +end +function modifier_nevermore_necromastery_custom.prototype.OnCreated(self) + self:refreshNumbers() +end +function modifier_nevermore_necromastery_custom.prototype.OnRefresh(self) + self:refreshNumbers() +end +function modifier_nevermore_necromastery_custom.prototype.refreshNumbers(self) + local ability = self:GetAbility() + if not ability then + return + end + self.damagePerSoul = ability:GetSpecialValueFor("necromastery_damage_per_soul") + self.soulsPerKill = ability:GetSpecialValueFor("souls_per_kill") + self.maxSouls = ability:GetSpecialValueFor("necromastery_max_souls") + self.soulRetainRatio = 1 - ability:GetSpecialValueFor("necromastery_soul_pct_release") / 100 +end +function modifier_nevermore_necromastery_custom.prototype.getCurrentSoulCap(self) + local ability = self:GetAbility() + if not ability then + return self.maxSouls + end + local cap = self.maxSouls + if self:GetParent():HasModifier(____exports.modifier_nevermore_necromastery_custom_active.name) then + cap = cap + ability:GetSpecialValueFor("active_bonus_soul_cap") + end + return cap +end +function modifier_nevermore_necromastery_custom.prototype.CanActivateSoulOverflow(self) + local baseCap = self.maxSouls + return self:GetStackCount() >= baseCap and not self:GetParent():HasModifier(____exports.modifier_nevermore_necromastery_custom_active.name) +end +function modifier_nevermore_necromastery_custom.prototype.OnDeath(self, event) + if not IsServer() then + return + end + local parent = self:GetParent() + local target = event.unit + local attacker = event.attacker + if not target or not attacker then + return + end + local ability = self:GetAbility() + if not ability then + return + end + if attacker == parent and not target:IsReincarnating() then + local current = self:GetStackCount() + local cap = math.max( + self:getCurrentSoulCap(), + current + ) + self:SetStackCount(math.min(cap, current + self.soulsPerKill)) + ProjectileManager:CreateTrackingProjectile({ + Target = parent, + Source = target, + Ability = ability, + EffectName = "particles/units/heroes/hero_nevermore/nevermore_necro_souls.vpcf", + bDodgeable = false, + bProvidesVision = false, + iMoveSpeed = 900, + iSourceAttachment = DOTA_PROJECTILE_ATTACHMENT_HITLOCATION + }) + end + if target == parent and not target:IsReincarnating() then + local current = self:GetStackCount() + local stackAfterDeath = math.max( + 0, + math.floor(current * self.soulRetainRatio) + ) + self:SetStackCount(stackAfterDeath) + local requiem = parent:FindAbilityByName(nevermore_requiem_custom.name) + if requiem and requiem:GetLevel() > 0 and not parent:PassivesDisabled() then + requiem:OnDeathCast() + end + end +end +function modifier_nevermore_necromastery_custom.prototype.GetModifierPreAttack_BonusDamage(self) + local parent = self:GetParent() + if parent:PassivesDisabled() then + return 0 + end + return self.damagePerSoul * self:GetStackCount() +end +function modifier_nevermore_necromastery_custom.prototype.OnStackCountChanged(self) + if not IsServer() then + return + end + self:refreshNumbers() + local hero = self:GetParent() + hero:CalculateStatBonus(true) +end +modifier_nevermore_necromastery_custom = __TS__Decorate( + modifier_nevermore_necromastery_custom, + modifier_nevermore_necromastery_custom, + {registerModifier(nil)}, + {kind = "class", name = "modifier_nevermore_necromastery_custom"} +) +____exports.modifier_nevermore_necromastery_custom = modifier_nevermore_necromastery_custom +____exports.modifier_nevermore_necromastery_custom_active = __TS__Class() +local modifier_nevermore_necromastery_custom_active = ____exports.modifier_nevermore_necromastery_custom_active +modifier_nevermore_necromastery_custom_active.name = "modifier_nevermore_necromastery_custom_active" +modifier_nevermore_necromastery_custom_active.____file_path = "scripts/vscripts/abilities/heroes/nevermore/nevermore_necromastery_custom.lua" +__TS__ClassExtends(modifier_nevermore_necromastery_custom_active, BaseModifier) +function modifier_nevermore_necromastery_custom_active.prototype.IsHidden(self) + return false +end +function modifier_nevermore_necromastery_custom_active.prototype.IsPurgable(self) + return false +end +function modifier_nevermore_necromastery_custom_active.prototype.IsDebuff(self) + return false +end +function modifier_nevermore_necromastery_custom_active.prototype.GetEffectName(self) + return "particles/units/heroes/hero_nevermore/nevermore_necro_souls.vpcf" +end +function modifier_nevermore_necromastery_custom_active.prototype.GetEffectAttachType(self) + return PATTACH_ABSORIGIN_FOLLOW +end +modifier_nevermore_necromastery_custom_active = __TS__Decorate( + modifier_nevermore_necromastery_custom_active, + modifier_nevermore_necromastery_custom_active, + {registerModifier(nil)}, + {kind = "class", name = "modifier_nevermore_necromastery_custom_active"} +) +____exports.modifier_nevermore_necromastery_custom_active = modifier_nevermore_necromastery_custom_active +return ____exports diff --git a/scripts/vscripts/abilities/heroes/nevermore/nevermore_requiem_custom.lua b/scripts/vscripts/abilities/heroes/nevermore/nevermore_requiem_custom.lua new file mode 100644 index 0000000..c11ed7a --- /dev/null +++ b/scripts/vscripts/abilities/heroes/nevermore/nevermore_requiem_custom.lua @@ -0,0 +1,290 @@ +local ____lualib = require("lualib_bundle") +local __TS__Class = ____lualib.__TS__Class +local __TS__ClassExtends = ____lualib.__TS__ClassExtends +local __TS__Decorate = ____lualib.__TS__Decorate +local ____exports = {} +local ____dota_ts_adapter = require("lib.dota_ts_adapter") +local BaseAbility = ____dota_ts_adapter.BaseAbility +local BaseModifier = ____dota_ts_adapter.BaseModifier +local registerAbility = ____dota_ts_adapter.registerAbility +local registerModifier = ____dota_ts_adapter.registerModifier +____exports.nevermore_requiem_custom = __TS__Class() +local nevermore_requiem_custom = ____exports.nevermore_requiem_custom +nevermore_requiem_custom.name = "nevermore_requiem_custom" +nevermore_requiem_custom.____file_path = "scripts/vscripts/abilities/heroes/nevermore/nevermore_requiem_custom.lua" +__TS__ClassExtends(nevermore_requiem_custom, BaseAbility) +function nevermore_requiem_custom.prototype.GetCastRange(self, _location, _target) + return self:GetSpecialValueFor("requiem_radius") +end +function nevermore_requiem_custom.prototype.Precache(self, context) + PrecacheResource("particle", "particles/units/heroes/hero_nevermore/nevermore_wings.vpcf", context) + PrecacheResource("particle", "particles/units/heroes/hero_nevermore/nevermore_requiemofsouls_a.vpcf", context) + PrecacheResource("particle", "particles/units/heroes/hero_nevermore/nevermore_requiemofsouls.vpcf", context) + PrecacheResource("particle", "particles/units/heroes/hero_nevermore/nevermore_requiemofsouls_line.vpcf", context) +end +function nevermore_requiem_custom.prototype.OnAbilityPhaseStart(self) + local caster = self:GetCaster() + EmitSoundOn("Hero_Nevermore.RequiemOfSoulsCast", caster) + self.wingsPfx = ParticleManager:CreateParticle("particles/units/heroes/hero_nevermore/nevermore_wings.vpcf", PATTACH_ABSORIGIN_FOLLOW, caster) + local castTime = math.max( + 0.01, + self:GetCastPoint() * self:GetCastPointModifier() + ) + caster:StartGestureWithPlaybackRate( + ACT_DOTA_CAST_ABILITY_6, + self:GetCastPoint() / castTime + ) + return true +end +function nevermore_requiem_custom.prototype.OnAbilityPhaseInterrupted(self) + local caster = self:GetCaster() + caster:FadeGesture(ACT_DOTA_CAST_ABILITY_6) + caster:StopSound("Hero_Nevermore.RequiemOfSoulsCast") + self:destroyWingsPfx() +end +function nevermore_requiem_custom.prototype.OnSpellStart(self) + self:castRequiem(false) +end +function nevermore_requiem_custom.prototype.OnDeathCast(self) + self:castRequiem(true) +end +function nevermore_requiem_custom.prototype.destroyWingsPfx(self) + if self.wingsPfx == nil then + return + end + ParticleManager:DestroyParticle(self.wingsPfx, true) + ParticleManager:ReleaseParticleIndex(self.wingsPfx) + self.wingsPfx = nil +end +function nevermore_requiem_custom.prototype.castRequiem(self, isDeathCast) + local caster = self:GetCaster() + self:destroyWingsPfx() + local soulModifier = caster:FindModifierByName("modifier_nevermore_necromastery_custom") + local currentSouls = soulModifier and soulModifier:GetStackCount() or 0 + local souls = math.floor(currentSouls * (self:GetSpecialValueFor("requiem_soul_pct_release") / 100)) + local travelDistance = self:GetSpecialValueFor("requiem_radius") + EmitSoundOn("Hero_Nevermore.RequiemOfSouls", caster) + local soulPfx = ParticleManager:CreateParticle("particles/units/heroes/hero_nevermore/nevermore_requiemofsouls_a.vpcf", PATTACH_WORLDORIGIN, nil) + ParticleManager:SetParticleControl( + soulPfx, + 0, + caster:GetAbsOrigin() + ) + ParticleManager:SetParticleControl( + soulPfx, + 1, + Vector(souls, 0, 0) + ) + ParticleManager:SetParticleControl( + soulPfx, + 2, + caster:GetAbsOrigin() + ) + ParticleManager:ReleaseParticleIndex(soulPfx) + local groundPfx = ParticleManager:CreateParticle("particles/units/heroes/hero_nevermore/nevermore_requiemofsouls.vpcf", PATTACH_WORLDORIGIN, nil) + ParticleManager:SetParticleControl( + groundPfx, + 0, + caster:GetAbsOrigin() + ) + ParticleManager:SetParticleControl( + groundPfx, + 1, + Vector(souls, 0, 0) + ) + ParticleManager:ReleaseParticleIndex(groundPfx) + if souls <= 0 then + return + end + local linePosition = caster:GetAbsOrigin() + caster:GetForwardVector() * travelDistance + local stepYaw = 360 / souls + do + local i = 0 + while i < souls do + linePosition = RotatePosition( + caster:GetAbsOrigin(), + QAngle(0, stepYaw, 0), + linePosition + ) + local start = caster:GetAbsOrigin() + (linePosition - caster:GetAbsOrigin()):Normalized() * 105 + self:createRequiemSoulLine(linePosition, start, isDeathCast, false) + i = i + 1 + end + end +end +function nevermore_requiem_custom.prototype.createRequiemSoulLine(self, lineEndPos, lineStartPos, isDeathCast, isScepterLine) + local caster = self:GetCaster() + local travelDistance = self:GetSpecialValueFor("requiem_radius") + local lineStartWidth = self:GetSpecialValueFor("requiem_line_width_start") + local lineEndWidth = self:GetSpecialValueFor("requiem_line_width_end") + local lineSpeed = self:GetSpecialValueFor("requiem_line_speed") + local travelTime = travelDistance / math.max(1, lineSpeed) + local velocity = (lineEndPos - lineStartPos):Normalized() * lineSpeed + ProjectileManager:CreateLinearProjectile({ + Ability = self, + vSpawnOrigin = lineStartPos, + fDistance = travelDistance, + fStartRadius = lineStartWidth, + fEndRadius = lineEndWidth, + Source = caster, + bHasFrontalCone = false, + iUnitTargetTeam = DOTA_UNIT_TARGET_TEAM_ENEMY, + iUnitTargetFlags = DOTA_UNIT_TARGET_FLAG_NONE, + iUnitTargetType = DOTA_UNIT_TARGET_HERO + DOTA_UNIT_TARGET_BASIC, + vVelocity = velocity, + bProvidesVision = false, + ExtraData = {x = lineStartPos.x, y = lineStartPos.y, isScepterLine = isScepterLine and 1 or 0, isDeathCast = isDeathCast and 1 or 0} + }) + local linePfx = ParticleManager:CreateParticle("particles/units/heroes/hero_nevermore/nevermore_requiemofsouls_line.vpcf", PATTACH_WORLDORIGIN, nil) + ParticleManager:SetParticleControl(linePfx, 0, lineStartPos) + ParticleManager:SetParticleControl(linePfx, 1, velocity) + ParticleManager:SetParticleControl( + linePfx, + 2, + Vector(0, travelTime, 0) + ) + ParticleManager:ReleaseParticleIndex(linePfx) +end +function nevermore_requiem_custom.prototype.OnProjectileHit_ExtraData(self, target, location, extraData) + local caster = self:GetCaster() + local isScepterLine = extraData.isScepterLine == 1 + local isDeathCast = extraData.isDeathCast == 1 + if not target then + if caster:HasScepter() and not isScepterLine and not isDeathCast then + local startPos = GetGroundPosition( + Vector(extraData.x, extraData.y, 0), + nil + ) + self:createRequiemSoulLine(startPos, location, false, true) + end + return + end + target:EmitSound("Hero_Nevermore.RequiemOfSouls.Damage") + local damage = self:GetSpecialValueFor("damage") + 0.25 * (caster:GetAverageTrueAttackDamage(target) + caster:GetMana()) + if isScepterLine then + damage = damage * (self:GetSpecialValueFor("requiem_damage_pct_scepter") / 100) + end + local dealtDamage = ApplyDamage({ + victim = target, + damage = damage, + damage_type = self:GetAbilityDamageType(), + attacker = caster, + ability = self + }) + local slowDuration = self:GetSpecialValueFor("requiem_slow_duration") + local slowDurationMax = self:GetSpecialValueFor("requiem_slow_duration_max") + local statusScale = 1 - target:GetStatusResistance() + if not isDeathCast and not target:IsDebuffImmune() then + local fear = target:FindModifierByName("modifier_nevermore_requiem_fear") + if not fear then + fear = target:AddNewModifier(caster, self, "modifier_nevermore_requiem_fear", {duration = slowDuration * statusScale}) + else + fear:SetDuration( + math.min( + fear:GetRemainingTime() + slowDuration, + slowDurationMax + ) * statusScale, + true + ) + end + end + if caster:HasScepter() and isScepterLine and target:IsHero() then + local healModifier = caster:AddNewModifier(caster, self, ____exports.modifier_nevermore_requiem_custom_scepter_heal.name, {}) + if healModifier ~= nil then + healModifier:AddTotalHeal(dealtDamage) + end + end + ____exports.modifier_nevermore_requiem_custom_debuff:apply(target, caster, self, {duration = slowDurationMax}) +end +nevermore_requiem_custom = __TS__Decorate( + nevermore_requiem_custom, + nevermore_requiem_custom, + {registerAbility(nil)}, + {kind = "class", name = "nevermore_requiem_custom"} +) +____exports.nevermore_requiem_custom = nevermore_requiem_custom +____exports.modifier_nevermore_requiem_custom_scepter_heal = __TS__Class() +local modifier_nevermore_requiem_custom_scepter_heal = ____exports.modifier_nevermore_requiem_custom_scepter_heal +modifier_nevermore_requiem_custom_scepter_heal.name = "modifier_nevermore_requiem_custom_scepter_heal" +modifier_nevermore_requiem_custom_scepter_heal.____file_path = "scripts/vscripts/abilities/heroes/nevermore/nevermore_requiem_custom.lua" +__TS__ClassExtends(modifier_nevermore_requiem_custom_scepter_heal, BaseModifier) +function modifier_nevermore_requiem_custom_scepter_heal.prototype.____constructor(self, ...) + BaseModifier.prototype.____constructor(self, ...) + self.heal = 0 +end +function modifier_nevermore_requiem_custom_scepter_heal.prototype.IsHidden(self) + return true +end +function modifier_nevermore_requiem_custom_scepter_heal.prototype.IsPurgable(self) + return false +end +function modifier_nevermore_requiem_custom_scepter_heal.prototype.OnCreated(self) + if not IsServer() then + return + end + self:StartIntervalThink(0.1) +end +function modifier_nevermore_requiem_custom_scepter_heal.prototype.OnIntervalThink(self) + local parent = self:GetParent() + parent:Heal( + self.heal, + self:GetAbility() + ) + if self.heal > 0 then + local pfx = ParticleManager:CreateParticle("particles/items3_fx/octarine_core_lifesteal.vpcf", PATTACH_ABSORIGIN_FOLLOW, parent) + ParticleManager:ReleaseParticleIndex(pfx) + end + self:Destroy() +end +function modifier_nevermore_requiem_custom_scepter_heal.prototype.AddTotalHeal(self, value) + self.heal = self.heal + value +end +modifier_nevermore_requiem_custom_scepter_heal = __TS__Decorate( + modifier_nevermore_requiem_custom_scepter_heal, + modifier_nevermore_requiem_custom_scepter_heal, + {registerModifier(nil)}, + {kind = "class", name = "modifier_nevermore_requiem_custom_scepter_heal"} +) +____exports.modifier_nevermore_requiem_custom_scepter_heal = modifier_nevermore_requiem_custom_scepter_heal +____exports.modifier_nevermore_requiem_custom_debuff = __TS__Class() +local modifier_nevermore_requiem_custom_debuff = ____exports.modifier_nevermore_requiem_custom_debuff +modifier_nevermore_requiem_custom_debuff.name = "modifier_nevermore_requiem_custom_debuff" +modifier_nevermore_requiem_custom_debuff.____file_path = "scripts/vscripts/abilities/heroes/nevermore/nevermore_requiem_custom.lua" +__TS__ClassExtends(modifier_nevermore_requiem_custom_debuff, BaseModifier) +function modifier_nevermore_requiem_custom_debuff.prototype.____constructor(self, ...) + BaseModifier.prototype.____constructor(self, ...) + self.moveSlowPct = 0 + self.magicResReduction = 0 +end +function modifier_nevermore_requiem_custom_debuff.prototype.IsHidden(self) + return true +end +function modifier_nevermore_requiem_custom_debuff.prototype.IsPurgable(self) + return true +end +function modifier_nevermore_requiem_custom_debuff.prototype.OnCreated(self) + local ____opt_0 = self:GetAbility() + self.moveSlowPct = ____opt_0 and ____opt_0:GetSpecialValueFor("requiem_reduction_ms") or 0 + local ____opt_2 = self:GetAbility() + self.magicResReduction = ____opt_2 and ____opt_2:GetSpecialValueFor("requiem_reduction_mres") or 0 +end +function modifier_nevermore_requiem_custom_debuff.prototype.OnRefresh(self) + self:OnCreated() +end +function modifier_nevermore_requiem_custom_debuff.prototype.DeclareFunctions(self) + return {MODIFIER_PROPERTY_MOVESPEED_BONUS_PERCENTAGE, MODIFIER_PROPERTY_MAGICAL_RESISTANCE_BONUS} +end +function modifier_nevermore_requiem_custom_debuff.prototype.GetModifierMoveSpeedBonus_Percentage(self) + return self.moveSlowPct +end +function modifier_nevermore_requiem_custom_debuff.prototype.GetModifierMagicalResistanceBonus(self) + return self.magicResReduction +end +modifier_nevermore_requiem_custom_debuff = __TS__Decorate( + modifier_nevermore_requiem_custom_debuff, + modifier_nevermore_requiem_custom_debuff, + {registerModifier(nil)}, + {kind = "class", name = "modifier_nevermore_requiem_custom_debuff"} +) +____exports.modifier_nevermore_requiem_custom_debuff = modifier_nevermore_requiem_custom_debuff +return ____exports diff --git a/scripts/vscripts/abilities/heroes/nevermore/nevermore_shadowraze_custom.lua b/scripts/vscripts/abilities/heroes/nevermore/nevermore_shadowraze_custom.lua new file mode 100644 index 0000000..c3e05fe --- /dev/null +++ b/scripts/vscripts/abilities/heroes/nevermore/nevermore_shadowraze_custom.lua @@ -0,0 +1,237 @@ +local ____lualib = require("lualib_bundle") +local __TS__Class = ____lualib.__TS__Class +local __TS__ClassExtends = ____lualib.__TS__ClassExtends +local __TS__Decorate = ____lualib.__TS__Decorate +local ____exports = {} +local CastShadowRazeOnPoint +local ____dota_ts_adapter = require("lib.dota_ts_adapter") +local BaseAbility = ____dota_ts_adapter.BaseAbility +local BaseModifier = ____dota_ts_adapter.BaseModifier +local registerAbility = ____dota_ts_adapter.registerAbility +local registerModifier = ____dota_ts_adapter.registerModifier +function CastShadowRazeOnPoint(self, caster, ability, point, radius) + local pfx = ParticleManager:CreateParticle("particles/units/heroes/hero_nevermore/nevermore_shadowraze.vpcf", PATTACH_WORLDORIGIN, nil) + ParticleManager:SetParticleControl(pfx, 0, point) + ParticleManager:SetParticleControl( + pfx, + 1, + Vector(radius, 1, 1) + ) + ParticleManager:ReleaseParticleIndex(pfx) + local enemies = FindUnitsInRadius( + caster:GetTeamNumber(), + point, + nil, + radius, + DOTA_UNIT_TARGET_TEAM_ENEMY, + bit.bor(DOTA_UNIT_TARGET_HERO, DOTA_UNIT_TARGET_BASIC), + DOTA_UNIT_TARGET_FLAG_NONE, + FIND_ANY_ORDER, + false + ) + local duration = ability:GetSpecialValueFor("duration") + local damagePctPerStack = ability:GetSpecialValueFor("stack_bonus_damage") + local slowDuration = ability:GetSpecialValueFor("slow_duration") + local slowDurationForBoss = ability:GetSpecialValueFor("slow_duration_for_boss") + for ____, enemy in ipairs(enemies) do + do + local stackModifier = enemy:FindModifierByName(____exports.nevermore_shadowraze_custom_debuff.name) + local stackCount = stackModifier and stackModifier:GetStackCount() or 0 + local totalDamage = (caster:GetAverageTrueAttackDamage(enemy) + caster:GetMana()) * (1 + stackCount * damagePctPerStack / 100) + local statusScale = 1 - enemy:GetStatusResistance() + local appliedSlow = enemy:IsBossCreature() and slowDurationForBoss * statusScale or slowDuration * statusScale + ApplyDamage({ + victim = enemy, + attacker = caster, + damage = totalDamage, + damage_type = ability:GetAbilityDamageType(), + ability = ability + }) + if not enemy:IsAlive() then + goto __continue9 + end + ____exports.nevermore_shadowraze_custom_debuff:apply(enemy, caster, ability, {duration = duration * statusScale}) + ____exports.nevermore_shadowraze_custom_slow_debuff:apply(enemy, caster, ability, {duration = appliedSlow}) + end + ::__continue9:: + end +end +____exports.nevermore_shadowraze1_custom = __TS__Class() +local nevermore_shadowraze1_custom = ____exports.nevermore_shadowraze1_custom +nevermore_shadowraze1_custom.name = "nevermore_shadowraze1_custom" +nevermore_shadowraze1_custom.____file_path = "scripts/vscripts/abilities/heroes/nevermore/nevermore_shadowraze_custom.lua" +__TS__ClassExtends(nevermore_shadowraze1_custom, BaseAbility) +function nevermore_shadowraze1_custom.prototype.GetCastRange(self, _location, _target) + return self:GetSpecialValueFor("shadowraze_range") +end +function nevermore_shadowraze1_custom.prototype.OnSpellStart(self) + local caster = self:GetCaster() + local radius = self:GetSpecialValueFor("shadowraze_radius") + local distance = self:GetSpecialValueFor("shadowraze_range") + local point = caster:GetAbsOrigin() + caster:GetForwardVector() * distance + EmitSoundOn("Hero_Nevermore.Shadowraze", caster) + CastShadowRazeOnPoint( + nil, + caster, + self, + point, + radius + ) +end +nevermore_shadowraze1_custom = __TS__Decorate( + nevermore_shadowraze1_custom, + nevermore_shadowraze1_custom, + {registerAbility(nil)}, + {kind = "class", name = "nevermore_shadowraze1_custom"} +) +____exports.nevermore_shadowraze1_custom = nevermore_shadowraze1_custom +____exports.nevermore_shadowraze2_custom = __TS__Class() +local nevermore_shadowraze2_custom = ____exports.nevermore_shadowraze2_custom +nevermore_shadowraze2_custom.name = "nevermore_shadowraze2_custom" +nevermore_shadowraze2_custom.____file_path = "scripts/vscripts/abilities/heroes/nevermore/nevermore_shadowraze_custom.lua" +__TS__ClassExtends(nevermore_shadowraze2_custom, BaseAbility) +function nevermore_shadowraze2_custom.prototype.GetCastRange(self, _location, _target) + return self:GetSpecialValueFor("shadowraze_range") +end +function nevermore_shadowraze2_custom.prototype.OnSpellStart(self) + local caster = self:GetCaster() + local radius = self:GetSpecialValueFor("shadowraze_radius") + local distance = self:GetSpecialValueFor("shadowraze_range") + local point = caster:GetAbsOrigin() + caster:GetForwardVector() * distance + EmitSoundOn("Hero_Nevermore.Shadowraze", caster) + CastShadowRazeOnPoint( + nil, + caster, + self, + point, + radius + ) +end +nevermore_shadowraze2_custom = __TS__Decorate( + nevermore_shadowraze2_custom, + nevermore_shadowraze2_custom, + {registerAbility(nil)}, + {kind = "class", name = "nevermore_shadowraze2_custom"} +) +____exports.nevermore_shadowraze2_custom = nevermore_shadowraze2_custom +____exports.nevermore_shadowraze3_custom = __TS__Class() +local nevermore_shadowraze3_custom = ____exports.nevermore_shadowraze3_custom +nevermore_shadowraze3_custom.name = "nevermore_shadowraze3_custom" +nevermore_shadowraze3_custom.____file_path = "scripts/vscripts/abilities/heroes/nevermore/nevermore_shadowraze_custom.lua" +__TS__ClassExtends(nevermore_shadowraze3_custom, BaseAbility) +function nevermore_shadowraze3_custom.prototype.GetCastRange(self, _location, _target) + return self:GetSpecialValueFor("shadowraze_range") +end +function nevermore_shadowraze3_custom.prototype.OnSpellStart(self) + local caster = self:GetCaster() + local radius = self:GetSpecialValueFor("shadowraze_radius") + local distance = self:GetSpecialValueFor("shadowraze_range") + local point = caster:GetAbsOrigin() + caster:GetForwardVector() * distance + EmitSoundOn("Hero_Nevermore.Shadowraze", caster) + CastShadowRazeOnPoint( + nil, + caster, + self, + point, + radius + ) +end +nevermore_shadowraze3_custom = __TS__Decorate( + nevermore_shadowraze3_custom, + nevermore_shadowraze3_custom, + {registerAbility(nil)}, + {kind = "class", name = "nevermore_shadowraze3_custom"} +) +____exports.nevermore_shadowraze3_custom = nevermore_shadowraze3_custom +____exports.nevermore_shadowraze_custom_debuff = __TS__Class() +local nevermore_shadowraze_custom_debuff = ____exports.nevermore_shadowraze_custom_debuff +nevermore_shadowraze_custom_debuff.name = "nevermore_shadowraze_custom_debuff" +nevermore_shadowraze_custom_debuff.____file_path = "scripts/vscripts/abilities/heroes/nevermore/nevermore_shadowraze_custom.lua" +__TS__ClassExtends(nevermore_shadowraze_custom_debuff, BaseModifier) +function nevermore_shadowraze_custom_debuff.prototype.____constructor(self, ...) + BaseModifier.prototype.____constructor(self, ...) + self.damagePctPerStack = 0 +end +function nevermore_shadowraze_custom_debuff.prototype.IsHidden(self) + return false +end +function nevermore_shadowraze_custom_debuff.prototype.IsPurgable(self) + return true +end +function nevermore_shadowraze_custom_debuff.prototype.DeclareFunctions(self) + return {MODIFIER_PROPERTY_TOOLTIP} +end +function nevermore_shadowraze_custom_debuff.prototype.OnTooltip(self) + return self.damagePctPerStack * self:GetStackCount() +end +function nevermore_shadowraze_custom_debuff.prototype.OnCreated(self) + local ____opt_0 = self:GetAbility() + self.damagePctPerStack = ____opt_0 and ____opt_0:GetSpecialValueFor("stack_bonus_damage") or 0 + if not IsServer() then + return + end + self:SetStackCount(1) +end +function nevermore_shadowraze_custom_debuff.prototype.OnRefresh(self) + local ____opt_2 = self:GetAbility() + self.damagePctPerStack = ____opt_2 and ____opt_2:GetSpecialValueFor("stack_bonus_damage") or self.damagePctPerStack + if not IsServer() then + return + end + self:IncrementStackCount() +end +function nevermore_shadowraze_custom_debuff.prototype.GetEffectName(self) + return "particles/units/heroes/hero_nevermore/nevermore_shadowraze_debuff.vpcf" +end +function nevermore_shadowraze_custom_debuff.prototype.GetEffectAttachType(self) + return PATTACH_ABSORIGIN_FOLLOW +end +nevermore_shadowraze_custom_debuff = __TS__Decorate( + nevermore_shadowraze_custom_debuff, + nevermore_shadowraze_custom_debuff, + {registerModifier(nil)}, + {kind = "class", name = "nevermore_shadowraze_custom_debuff"} +) +____exports.nevermore_shadowraze_custom_debuff = nevermore_shadowraze_custom_debuff +____exports.nevermore_shadowraze_custom_slow_debuff = __TS__Class() +local nevermore_shadowraze_custom_slow_debuff = ____exports.nevermore_shadowraze_custom_slow_debuff +nevermore_shadowraze_custom_slow_debuff.name = "nevermore_shadowraze_custom_slow_debuff" +nevermore_shadowraze_custom_slow_debuff.____file_path = "scripts/vscripts/abilities/heroes/nevermore/nevermore_shadowraze_custom.lua" +__TS__ClassExtends(nevermore_shadowraze_custom_slow_debuff, BaseModifier) +function nevermore_shadowraze_custom_slow_debuff.prototype.IsHidden(self) + return false +end +function nevermore_shadowraze_custom_slow_debuff.prototype.IsPurgable(self) + return true +end +function nevermore_shadowraze_custom_slow_debuff.prototype.DeclareFunctions(self) + return {MODIFIER_PROPERTY_MOVESPEED_BONUS_PERCENTAGE} +end +function nevermore_shadowraze_custom_slow_debuff.prototype.OnCreated(self) + self:syncStacksFromRazeDebuff() +end +function nevermore_shadowraze_custom_slow_debuff.prototype.OnRefresh(self) + self:syncStacksFromRazeDebuff() +end +function nevermore_shadowraze_custom_slow_debuff.prototype.syncStacksFromRazeDebuff(self) + if not IsServer() then + return + end + local owner = self:GetParent() + local stackModifier = owner:FindModifierByName(____exports.nevermore_shadowraze_custom_debuff.name) + local stacks = stackModifier and stackModifier:GetStackCount() or 1 + self:SetStackCount(math.max(1, stacks)) +end +function nevermore_shadowraze_custom_slow_debuff.prototype.GetModifierMoveSpeedBonus_Percentage(self) + local ____opt_4 = self:GetAbility() + local slowPerStack = ____opt_4 and ____opt_4:GetSpecialValueFor("movement_speed_debuff") or 0 + return -slowPerStack * self:GetStackCount() +end +nevermore_shadowraze_custom_slow_debuff = __TS__Decorate( + nevermore_shadowraze_custom_slow_debuff, + nevermore_shadowraze_custom_slow_debuff, + {registerModifier(nil)}, + {kind = "class", name = "nevermore_shadowraze_custom_slow_debuff"} +) +____exports.nevermore_shadowraze_custom_slow_debuff = nevermore_shadowraze_custom_slow_debuff +return ____exports diff --git a/scripts/vscripts/abilities/heroes/ogre_magi/ability_ogre_magi_bloodlust_custom.lua b/scripts/vscripts/abilities/heroes/ogre_magi/ability_ogre_magi_bloodlust_custom.lua new file mode 100644 index 0000000..4c41652 --- /dev/null +++ b/scripts/vscripts/abilities/heroes/ogre_magi/ability_ogre_magi_bloodlust_custom.lua @@ -0,0 +1,225 @@ +local ____lualib = require("lualib_bundle") +local __TS__Class = ____lualib.__TS__Class +local __TS__ClassExtends = ____lualib.__TS__ClassExtends +local __TS__Decorate = ____lualib.__TS__Decorate +local ____exports = {} +local ____dota_ts_adapter = require("lib.dota_ts_adapter") +local BaseAbility = ____dota_ts_adapter.BaseAbility +local BaseModifier = ____dota_ts_adapter.BaseModifier +local registerAbility = ____dota_ts_adapter.registerAbility +local registerModifier = ____dota_ts_adapter.registerModifier +local ____vampirism = require("utils.vampirism") +local addPhysicalVampirism = ____vampirism.addPhysicalVampirism +local precacheVampirismParticle = ____vampirism.precacheVampirismParticle +local reducePhysicalVampirism = ____vampirism.reducePhysicalVampirism +____exports.ability_ogre_magi_bloodlust_custom = __TS__Class() +local ability_ogre_magi_bloodlust_custom = ____exports.ability_ogre_magi_bloodlust_custom +ability_ogre_magi_bloodlust_custom.name = "ability_ogre_magi_bloodlust_custom" +ability_ogre_magi_bloodlust_custom.____file_path = "scripts/vscripts/abilities/heroes/ogre_magi/ability_ogre_magi_bloodlust_custom.lua" +__TS__ClassExtends(ability_ogre_magi_bloodlust_custom, BaseAbility) +function ability_ogre_magi_bloodlust_custom.prototype.Precache(self, context) + PrecacheResource("particle", "particles/units/heroes/hero_ogre_magi/ogre_magi_bloodlust_cast.vpcf", context) + PrecacheResource("particle", "particles/units/heroes/hero_ogre_magi/ogre_magi_bloodlust_buff.vpcf", context) + PrecacheResource("soundfile", "soundevents/game_sounds_heroes/game_sounds_ogre_magi.vsndevts", context) + precacheVampirismParticle(nil, context) +end +function ability_ogre_magi_bloodlust_custom.prototype.GetIntrinsicModifierName(self) + return ____exports.modifier_ogre_magi_bloodlust_handler.name +end +function ability_ogre_magi_bloodlust_custom.prototype.OnSpellStart(self) + if not IsServer() then + return + end + local caster = self:GetCaster() + local target = self:GetCursorTarget() + if not target then + return + end + local duration = self:GetSpecialValueFor("duration") + target:AddNewModifier(caster, self, ____exports.modifier_ogre_magi_bloodlust_buff.name, {duration = duration}) + EmitSoundOn("Hero_OgreMagi.Bloodlust.Target", target) +end +ability_ogre_magi_bloodlust_custom = __TS__Decorate( + ability_ogre_magi_bloodlust_custom, + ability_ogre_magi_bloodlust_custom, + {registerAbility(nil)}, + {kind = "class", name = "ability_ogre_magi_bloodlust_custom"} +) +____exports.ability_ogre_magi_bloodlust_custom = ability_ogre_magi_bloodlust_custom +____exports.modifier_ogre_magi_bloodlust_handler = __TS__Class() +local modifier_ogre_magi_bloodlust_handler = ____exports.modifier_ogre_magi_bloodlust_handler +modifier_ogre_magi_bloodlust_handler.name = "modifier_ogre_magi_bloodlust_handler" +modifier_ogre_magi_bloodlust_handler.____file_path = "scripts/vscripts/abilities/heroes/ogre_magi/ability_ogre_magi_bloodlust_custom.lua" +__TS__ClassExtends(modifier_ogre_magi_bloodlust_handler, BaseModifier) +function modifier_ogre_magi_bloodlust_handler.prototype.IsHidden(self) + return true +end +function modifier_ogre_magi_bloodlust_handler.prototype.IsPurgable(self) + return false +end +function modifier_ogre_magi_bloodlust_handler.prototype.RemoveOnDeath(self) + return false +end +function modifier_ogre_magi_bloodlust_handler.prototype.OnCreated(self) + if IsServer() then + self:StartIntervalThink(1) + end +end +function modifier_ogre_magi_bloodlust_handler.prototype.OnIntervalThink(self) + if not IsServer() then + return + end + local ability = self:GetAbility() + if not ability or ability:GetLevel() <= 0 then + return + end + if not ability:GetAutoCastState() then + return + end + if not ability:IsFullyCastable() then + return + end + local parent = self:GetParent() + local castRange = ability:GetCastRange( + parent:GetAbsOrigin(), + nil + ) + local allies = FindUnitsInRadius( + parent:GetTeamNumber(), + parent:GetAbsOrigin(), + nil, + castRange, + DOTA_UNIT_TARGET_TEAM_FRIENDLY, + bit.bor(DOTA_UNIT_TARGET_HERO, DOTA_UNIT_TARGET_BASIC), + DOTA_UNIT_TARGET_FLAG_INVULNERABLE, + FIND_CLOSEST, + false + ) + for ____, ally in ipairs(allies) do + if not ally:HasModifier(____exports.modifier_ogre_magi_bloodlust_buff.name) then + parent:CastAbilityOnTarget( + ally, + ability, + parent:GetPlayerOwnerID() + ) + break + end + end +end +modifier_ogre_magi_bloodlust_handler = __TS__Decorate( + modifier_ogre_magi_bloodlust_handler, + modifier_ogre_magi_bloodlust_handler, + {registerModifier(nil)}, + {kind = "class", name = "modifier_ogre_magi_bloodlust_handler"} +) +____exports.modifier_ogre_magi_bloodlust_handler = modifier_ogre_magi_bloodlust_handler +____exports.modifier_ogre_magi_bloodlust_buff = __TS__Class() +local modifier_ogre_magi_bloodlust_buff = ____exports.modifier_ogre_magi_bloodlust_buff +modifier_ogre_magi_bloodlust_buff.name = "modifier_ogre_magi_bloodlust_buff" +modifier_ogre_magi_bloodlust_buff.____file_path = "scripts/vscripts/abilities/heroes/ogre_magi/ability_ogre_magi_bloodlust_custom.lua" +__TS__ClassExtends(modifier_ogre_magi_bloodlust_buff, BaseModifier) +function modifier_ogre_magi_bloodlust_buff.prototype.____constructor(self, ...) + BaseModifier.prototype.____constructor(self, ...) + self.isSelf = false + self.appliedVampirism = 0 +end +function modifier_ogre_magi_bloodlust_buff.prototype.IsHidden(self) + return false +end +function modifier_ogre_magi_bloodlust_buff.prototype.IsDebuff(self) + return false +end +function modifier_ogre_magi_bloodlust_buff.prototype.IsPurgable(self) + return true +end +function modifier_ogre_magi_bloodlust_buff.prototype.OnCreated(self) + if not IsServer() then + return + end + self.isSelf = self:GetCaster() == self:GetParent() + self:SetHasCustomTransmitterData(true) + self:refreshVampirism() +end +function modifier_ogre_magi_bloodlust_buff.prototype.OnRefresh(self) + if not IsServer() then + return + end + self:refreshVampirism() +end +function modifier_ogre_magi_bloodlust_buff.prototype.OnDestroy(self) + if not IsServer() then + return + end + self:clearVampirism() +end +function modifier_ogre_magi_bloodlust_buff.prototype.refreshVampirism(self) + local parent = self:GetParent() + if not parent or parent:IsNull() or not parent:IsRealHero() then + return + end + local ability = self:GetAbility() + if not ability then + return + end + local desired = ability:GetSpecialValueFor("physical_vampirism") + local hero = parent + if desired > self.appliedVampirism then + addPhysicalVampirism(nil, hero, desired - self.appliedVampirism) + self.appliedVampirism = desired + elseif desired < self.appliedVampirism then + reducePhysicalVampirism(nil, hero, self.appliedVampirism - desired) + self.appliedVampirism = desired + end +end +function modifier_ogre_magi_bloodlust_buff.prototype.clearVampirism(self) + if self.appliedVampirism <= 0 then + return + end + local parent = self:GetParent() + if not parent or parent:IsNull() or not parent:IsRealHero() then + return + end + reducePhysicalVampirism(nil, parent, self.appliedVampirism) + self.appliedVampirism = 0 +end +function modifier_ogre_magi_bloodlust_buff.prototype.AddCustomTransmitterData(self) + return {isSelf = self.isSelf and 1 or 0} +end +function modifier_ogre_magi_bloodlust_buff.prototype.HandleCustomTransmitterData(self, data) + self.isSelf = data.isSelf == 1 +end +function modifier_ogre_magi_bloodlust_buff.prototype.DeclareFunctions(self) + return {MODIFIER_PROPERTY_MOVESPEED_BONUS_PERCENTAGE, MODIFIER_PROPERTY_ATTACKSPEED_BONUS_CONSTANT, MODIFIER_PROPERTY_MODEL_SCALE} +end +function modifier_ogre_magi_bloodlust_buff.prototype.GetModifierMoveSpeedBonus_Percentage(self) + local ____opt_0 = self:GetAbility() + return ____opt_0 and ____opt_0:GetSpecialValueFor("bonus_movement_speed") or 0 +end +function modifier_ogre_magi_bloodlust_buff.prototype.GetModifierAttackSpeedBonus_Constant(self) + local ability = self:GetAbility() + if not ability then + return 0 + end + if self.isSelf then + return ability:GetSpecialValueFor("self_bonus") + end + return ability:GetSpecialValueFor("bonus_attack_speed") +end +function modifier_ogre_magi_bloodlust_buff.prototype.GetModifierModelScale(self) + local ____opt_2 = self:GetAbility() + return ____opt_2 and ____opt_2:GetSpecialValueFor("modelscale") or 0 +end +function modifier_ogre_magi_bloodlust_buff.prototype.GetEffectName(self) + return "particles/units/heroes/hero_ogre_magi/ogre_magi_bloodlust_buff.vpcf" +end +function modifier_ogre_magi_bloodlust_buff.prototype.GetEffectAttachType(self) + return PATTACH_ABSORIGIN_FOLLOW +end +modifier_ogre_magi_bloodlust_buff = __TS__Decorate( + modifier_ogre_magi_bloodlust_buff, + modifier_ogre_magi_bloodlust_buff, + {registerModifier(nil)}, + {kind = "class", name = "modifier_ogre_magi_bloodlust_buff"} +) +____exports.modifier_ogre_magi_bloodlust_buff = modifier_ogre_magi_bloodlust_buff +return ____exports diff --git a/scripts/vscripts/abilities/heroes/ogre_magi/ability_ogre_magi_fireblast_custom.lua b/scripts/vscripts/abilities/heroes/ogre_magi/ability_ogre_magi_fireblast_custom.lua new file mode 100644 index 0000000..80c026f --- /dev/null +++ b/scripts/vscripts/abilities/heroes/ogre_magi/ability_ogre_magi_fireblast_custom.lua @@ -0,0 +1,143 @@ +local ____lualib = require("lualib_bundle") +local __TS__Class = ____lualib.__TS__Class +local __TS__ClassExtends = ____lualib.__TS__ClassExtends +local __TS__Decorate = ____lualib.__TS__Decorate +local ____exports = {} +local ____dota_ts_adapter = require("lib.dota_ts_adapter") +local BaseAbility = ____dota_ts_adapter.BaseAbility +local BaseModifier = ____dota_ts_adapter.BaseModifier +local registerAbility = ____dota_ts_adapter.registerAbility +local registerModifier = ____dota_ts_adapter.registerModifier +local ____luck = require("utils.luck") +local rollLuckChance = ____luck.rollLuckChance +local ____ogre_magi_fireblast_aoe = require("abilities.heroes.ogre_magi.ogre_magi_fireblast_aoe") +local castOgreFireblastAoE = ____ogre_magi_fireblast_aoe.castOgreFireblastAoE +local ____ogre_magi_particles = require("abilities.heroes.ogre_magi.ogre_magi_particles") +local OGRE_FIREBLAST_IMPACT = ____ogre_magi_particles.OGRE_FIREBLAST_IMPACT +____exports.OGRE_FIREBLAST_ATTACK_PROC_TALENT = "special_bonus_unique_ogre_magi_fireblast_attack_proc" +____exports.ability_ogre_magi_fireblast_custom = __TS__Class() +local ability_ogre_magi_fireblast_custom = ____exports.ability_ogre_magi_fireblast_custom +ability_ogre_magi_fireblast_custom.name = "ability_ogre_magi_fireblast_custom" +ability_ogre_magi_fireblast_custom.____file_path = "scripts/vscripts/abilities/heroes/ogre_magi/ability_ogre_magi_fireblast_custom.lua" +__TS__ClassExtends(ability_ogre_magi_fireblast_custom, BaseAbility) +function ability_ogre_magi_fireblast_custom.prototype.Precache(self, context) + PrecacheResource("particle", OGRE_FIREBLAST_IMPACT, context) + PrecacheResource("soundfile", "soundevents/game_sounds_heroes/game_sounds_ogre_magi.vsndevts", context) +end +function ability_ogre_magi_fireblast_custom.prototype.GetIntrinsicModifierName(self) + return ____exports.modifier_ogre_magi_fireblast_attack_proc.name +end +function ability_ogre_magi_fireblast_custom.prototype.GetAOERadius(self) + return self:GetSpecialValueFor("blast_radius") +end +function ability_ogre_magi_fireblast_custom.prototype.OnSpellStart(self) + if not IsServer() then + return + end + local caster = self:GetCaster() + local point = self:GetCursorPosition() + EmitSoundOn("Hero_OgreMagi.Fireblast.Cast", caster) + castOgreFireblastAoE( + nil, + self, + caster, + point, + "fireblast_damage" + ) +end +function ability_ogre_magi_fireblast_custom.prototype.castFireblastInFront(self, caster) + if not IsServer() then + return + end + if not caster or caster:IsNull() or not caster:IsAlive() then + return + end + if self:GetLevel() <= 0 then + return + end + local forward = caster:GetForwardVector() + local castRange = self:GetCastRange( + caster:GetAbsOrigin(), + nil + ) + local distance = math.min( + castRange, + self:GetSpecialValueFor("attack_proc_distance") + ) + local point = caster:GetAbsOrigin() + forward * distance + EmitSoundOn("Hero_OgreMagi.Fireblast.Cast", caster) + castOgreFireblastAoE( + nil, + self, + caster, + point, + "fireblast_damage" + ) +end +ability_ogre_magi_fireblast_custom = __TS__Decorate( + ability_ogre_magi_fireblast_custom, + ability_ogre_magi_fireblast_custom, + {registerAbility(nil)}, + {kind = "class", name = "ability_ogre_magi_fireblast_custom"} +) +____exports.ability_ogre_magi_fireblast_custom = ability_ogre_magi_fireblast_custom +____exports.modifier_ogre_magi_fireblast_attack_proc = __TS__Class() +local modifier_ogre_magi_fireblast_attack_proc = ____exports.modifier_ogre_magi_fireblast_attack_proc +modifier_ogre_magi_fireblast_attack_proc.name = "modifier_ogre_magi_fireblast_attack_proc" +modifier_ogre_magi_fireblast_attack_proc.____file_path = "scripts/vscripts/abilities/heroes/ogre_magi/ability_ogre_magi_fireblast_custom.lua" +__TS__ClassExtends(modifier_ogre_magi_fireblast_attack_proc, BaseModifier) +function modifier_ogre_magi_fireblast_attack_proc.prototype.IsHidden(self) + return true +end +function modifier_ogre_magi_fireblast_attack_proc.prototype.IsPurgable(self) + return false +end +function modifier_ogre_magi_fireblast_attack_proc.prototype.DeclareFunctions(self) + return {MODIFIER_EVENT_ON_ATTACK_LANDED} +end +function modifier_ogre_magi_fireblast_attack_proc.prototype.OnAttackLanded(self, event) + if not IsServer() then + return + end + local parent = self:GetParent() + if event.attacker ~= parent then + return + end + if not parent:IsRealHero() or parent:IsIllusion() then + return + end + if parent:PassivesDisabled() then + return + end + if not HasTalent(nil, parent, ____exports.OGRE_FIREBLAST_ATTACK_PROC_TALENT) then + return + end + local target = event.target + if not target or target:IsNull() or not target:IsAlive() then + return + end + if target:GetTeamNumber() == parent:GetTeamNumber() then + return + end + local ability = self:GetAbility() + if not ability or ability:IsNull() or ability:GetLevel() <= 0 then + return + end + local chancePct = ability:GetSpecialValueFor("attack_proc_chance") + if chancePct <= 0 then + return + end + local hero = parent + if not rollLuckChance(nil, hero, chancePct / 100) then + return + end + ability:castFireblastInFront(parent) +end +modifier_ogre_magi_fireblast_attack_proc = __TS__Decorate( + modifier_ogre_magi_fireblast_attack_proc, + modifier_ogre_magi_fireblast_attack_proc, + {registerModifier(nil)}, + {kind = "class", name = "modifier_ogre_magi_fireblast_attack_proc"} +) +____exports.modifier_ogre_magi_fireblast_attack_proc = modifier_ogre_magi_fireblast_attack_proc +return ____exports diff --git a/scripts/vscripts/abilities/heroes/ogre_magi/ability_ogre_magi_ignite_custom.lua b/scripts/vscripts/abilities/heroes/ogre_magi/ability_ogre_magi_ignite_custom.lua new file mode 100644 index 0000000..fc2fb37 --- /dev/null +++ b/scripts/vscripts/abilities/heroes/ogre_magi/ability_ogre_magi_ignite_custom.lua @@ -0,0 +1,155 @@ +local ____lualib = require("lualib_bundle") +local __TS__Class = ____lualib.__TS__Class +local __TS__ClassExtends = ____lualib.__TS__ClassExtends +local __TS__Decorate = ____lualib.__TS__Decorate +local ____exports = {} +local ____dota_ts_adapter = require("lib.dota_ts_adapter") +local BaseAbility = ____dota_ts_adapter.BaseAbility +local BaseModifier = ____dota_ts_adapter.BaseModifier +local registerAbility = ____dota_ts_adapter.registerAbility +local registerModifier = ____dota_ts_adapter.registerModifier +local ____ogre_magi_damage = require("abilities.heroes.ogre_magi.ogre_magi_damage") +local getOgreMagiDamageWithMaxHealth = ____ogre_magi_damage.getOgreMagiDamageWithMaxHealth +____exports.ability_ogre_magi_ignite_custom = __TS__Class() +local ability_ogre_magi_ignite_custom = ____exports.ability_ogre_magi_ignite_custom +ability_ogre_magi_ignite_custom.name = "ability_ogre_magi_ignite_custom" +ability_ogre_magi_ignite_custom.____file_path = "scripts/vscripts/abilities/heroes/ogre_magi/ability_ogre_magi_ignite_custom.lua" +__TS__ClassExtends(ability_ogre_magi_ignite_custom, BaseAbility) +function ability_ogre_magi_ignite_custom.prototype.Precache(self, context) + PrecacheResource("particle", "particles/units/heroes/hero_ogre_magi/ogre_magi_ignite.vpcf", context) + PrecacheResource("particle", "particles/units/heroes/hero_ogre_magi/ogre_magi_ignite_debuff.vpcf", context) + PrecacheResource("soundfile", "soundevents/game_sounds_heroes/game_sounds_ogre_magi.vsndevts", context) +end +function ability_ogre_magi_ignite_custom.prototype.GetAOERadius(self) + return self:GetSpecialValueFor("ignite_radius") +end +function ability_ogre_magi_ignite_custom.prototype.OnSpellStart(self) + if not IsServer() then + return + end + local caster = self:GetCaster() + local target = self:GetCursorTarget() + if not target then + return + end + ProjectileManager:CreateTrackingProjectile({ + Ability = self, + EffectName = "particles/units/heroes/hero_ogre_magi/ogre_magi_ignite.vpcf", + Source = caster, + Target = target, + iMoveSpeed = self:GetSpecialValueFor("projectile_speed"), + bDodgeable = true + }) + EmitSoundOn("Hero_OgreMagi.Ignite.Cast", caster) +end +function ability_ogre_magi_ignite_custom.prototype.OnProjectileHit(self, target, location) + if not target then + return true + end + local caster = self:GetCaster() + local duration = self:GetSpecialValueFor("duration") + local radius = self:GetSpecialValueFor("ignite_radius") + self:applyIgniteToUnit(target, duration) + local nearby = FindUnitsInRadius( + caster:GetTeamNumber(), + location, + nil, + radius, + DOTA_UNIT_TARGET_TEAM_ENEMY, + bit.bor(DOTA_UNIT_TARGET_HERO, DOTA_UNIT_TARGET_BASIC), + DOTA_UNIT_TARGET_FLAG_NONE, + FIND_ANY_ORDER, + false + ) + for ____, enemy in ipairs(nearby) do + if enemy ~= target then + self:applyIgniteToUnit(enemy, duration) + end + end + EmitSoundOn("Hero_OgreMagi.Ignite.Target", target) + return true +end +function ability_ogre_magi_ignite_custom.prototype.applyIgniteToUnit(self, unit, duration) + local caster = self:GetCaster() + unit:AddNewModifier(caster, self, ____exports.modifier_ogre_magi_ignite_debuff.name, {duration = duration}) +end +ability_ogre_magi_ignite_custom = __TS__Decorate( + ability_ogre_magi_ignite_custom, + ability_ogre_magi_ignite_custom, + {registerAbility(nil)}, + {kind = "class", name = "ability_ogre_magi_ignite_custom"} +) +____exports.ability_ogre_magi_ignite_custom = ability_ogre_magi_ignite_custom +____exports.modifier_ogre_magi_ignite_debuff = __TS__Class() +local modifier_ogre_magi_ignite_debuff = ____exports.modifier_ogre_magi_ignite_debuff +modifier_ogre_magi_ignite_debuff.name = "modifier_ogre_magi_ignite_debuff" +modifier_ogre_magi_ignite_debuff.____file_path = "scripts/vscripts/abilities/heroes/ogre_magi/ability_ogre_magi_ignite_custom.lua" +__TS__ClassExtends(modifier_ogre_magi_ignite_debuff, BaseModifier) +function modifier_ogre_magi_ignite_debuff.prototype.IsDebuff(self) + return true +end +function modifier_ogre_magi_ignite_debuff.prototype.IsPurgable(self) + return true +end +function modifier_ogre_magi_ignite_debuff.prototype.OnCreated(self) + if not IsServer() then + return + end + self:StartIntervalThink(1) + local caster = self:GetCaster() + if caster and not caster:IsNull() and HasTalent(nil, caster, "special_bonus_unique_ogre_magi_ignite_stacks") then + self:SetStackCount(1) + end +end +function modifier_ogre_magi_ignite_debuff.prototype.OnRefresh(self) + if not IsServer() then + return + end + local caster = self:GetCaster() + if caster and not caster:IsNull() and HasTalent(nil, caster, "special_bonus_unique_ogre_magi_ignite_stacks") then + self:IncrementStackCount() + end +end +function modifier_ogre_magi_ignite_debuff.prototype.OnIntervalThink(self) + if not IsServer() then + return + end + local parent = self:GetParent() + local caster = self:GetCaster() + local ability = self:GetAbility() + if not caster or caster:IsNull() or not ability then + return + end + local baseDamage = ability:GetSpecialValueFor("burn_damage") + local stacks = self:GetStackCount() + local scaledBase = stacks > 0 and baseDamage * stacks or baseDamage + local damage = getOgreMagiDamageWithMaxHealth(nil, ability, caster, scaledBase) + ApplyDamage({ + victim = parent, + attacker = caster, + damage = damage, + damage_type = ability:GetAbilityDamageType(), + ability = ability + }) +end +function modifier_ogre_magi_ignite_debuff.prototype.DeclareFunctions(self) + return {MODIFIER_PROPERTY_MOVESPEED_BONUS_PERCENTAGE} +end +function modifier_ogre_magi_ignite_debuff.prototype.GetModifierMoveSpeedBonus_Percentage(self) + local ____opt_0 = self:GetAbility() + return ____opt_0 and ____opt_0:GetSpecialValueFor("slow_movement_speed_pct") or 0 +end +function modifier_ogre_magi_ignite_debuff.prototype.GetEffectName(self) + return "particles/units/heroes/hero_ogre_magi/ogre_magi_ignite_debuff.vpcf" +end +function modifier_ogre_magi_ignite_debuff.prototype.GetEffectAttachType(self) + return PATTACH_ABSORIGIN_FOLLOW +end +modifier_ogre_magi_ignite_debuff = __TS__Decorate( + modifier_ogre_magi_ignite_debuff, + modifier_ogre_magi_ignite_debuff, + {registerModifier(nil)}, + {kind = "class", name = "modifier_ogre_magi_ignite_debuff"} +) +____exports.modifier_ogre_magi_ignite_debuff = modifier_ogre_magi_ignite_debuff +return ____exports diff --git a/scripts/vscripts/abilities/heroes/ogre_magi/ability_ogre_magi_innate_custom.lua b/scripts/vscripts/abilities/heroes/ogre_magi/ability_ogre_magi_innate_custom.lua new file mode 100644 index 0000000..27da25f --- /dev/null +++ b/scripts/vscripts/abilities/heroes/ogre_magi/ability_ogre_magi_innate_custom.lua @@ -0,0 +1,106 @@ +local ____lualib = require("lualib_bundle") +local __TS__Class = ____lualib.__TS__Class +local __TS__ClassExtends = ____lualib.__TS__ClassExtends +local __TS__Decorate = ____lualib.__TS__Decorate +local ____exports = {} +local ____dota_ts_adapter = require("lib.dota_ts_adapter") +local BaseAbility = ____dota_ts_adapter.BaseAbility +local BaseModifier = ____dota_ts_adapter.BaseModifier +local registerAbility = ____dota_ts_adapter.registerAbility +local registerModifier = ____dota_ts_adapter.registerModifier +local ____luck = require("utils.luck") +local addLuck = ____luck.addLuck +____exports.ability_ogre_magi_innate_custom = __TS__Class() +local ability_ogre_magi_innate_custom = ____exports.ability_ogre_magi_innate_custom +ability_ogre_magi_innate_custom.name = "ability_ogre_magi_innate_custom" +ability_ogre_magi_innate_custom.____file_path = "scripts/vscripts/abilities/heroes/ogre_magi/ability_ogre_magi_innate_custom.lua" +__TS__ClassExtends(ability_ogre_magi_innate_custom, BaseAbility) +function ability_ogre_magi_innate_custom.prototype.GetIntrinsicModifierName(self) + return ____exports.modifier_ogre_magi_innate_custom.name +end +ability_ogre_magi_innate_custom = __TS__Decorate( + ability_ogre_magi_innate_custom, + ability_ogre_magi_innate_custom, + {registerAbility(nil)}, + {kind = "class", name = "ability_ogre_magi_innate_custom"} +) +____exports.ability_ogre_magi_innate_custom = ability_ogre_magi_innate_custom +____exports.modifier_ogre_magi_innate_custom = __TS__Class() +local modifier_ogre_magi_innate_custom = ____exports.modifier_ogre_magi_innate_custom +modifier_ogre_magi_innate_custom.name = "modifier_ogre_magi_innate_custom" +modifier_ogre_magi_innate_custom.____file_path = "scripts/vscripts/abilities/heroes/ogre_magi/ability_ogre_magi_innate_custom.lua" +__TS__ClassExtends(modifier_ogre_magi_innate_custom, BaseModifier) +function modifier_ogre_magi_innate_custom.prototype.____constructor(self, ...) + BaseModifier.prototype.____constructor(self, ...) + self.luckGranted = 0 +end +function modifier_ogre_magi_innate_custom.prototype.IsHidden(self) + return false +end +function modifier_ogre_magi_innate_custom.prototype.IsDebuff(self) + return false +end +function modifier_ogre_magi_innate_custom.prototype.IsPurgable(self) + return false +end +function modifier_ogre_magi_innate_custom.prototype.RemoveOnDeath(self) + return false +end +function modifier_ogre_magi_innate_custom.prototype.OnCreated(self) + self:refreshLuck() + if IsServer() then + self:StartIntervalThink(1) + end +end +function modifier_ogre_magi_innate_custom.prototype.OnRefresh(self) + self:refreshLuck() +end +function modifier_ogre_magi_innate_custom.prototype.OnIntervalThink(self) + self:refreshLuck() +end +function modifier_ogre_magi_innate_custom.prototype.OnDestroy(self) + if not IsServer() then + return + end + local parent = self:GetParent() + if not parent or parent:IsNull() or not parent:IsRealHero() then + return + end + if self.luckGranted > 0 then + addLuck(nil, parent, -self.luckGranted) + self.luckGranted = 0 + end +end +function modifier_ogre_magi_innate_custom.prototype.refreshLuck(self) + if not IsServer() then + return + end + local parent = self:GetParent() + if not parent or parent:IsNull() or not parent:IsRealHero() then + return + end + if parent:PassivesDisabled() then + return + end + local ability = self:GetAbility() + if not ability then + return + end + local hero = parent + local perLevel = ability:GetSpecialValueFor("luck_per_level") + local desired = perLevel * hero:GetLevel() + local delta = desired - self.luckGranted + if delta ~= 0 then + addLuck(nil, hero, delta) + self.luckGranted = desired + end + self:SetStackCount(desired) +end +modifier_ogre_magi_innate_custom = __TS__Decorate( + modifier_ogre_magi_innate_custom, + modifier_ogre_magi_innate_custom, + {registerModifier(nil)}, + {kind = "class", name = "modifier_ogre_magi_innate_custom"} +) +____exports.modifier_ogre_magi_innate_custom = modifier_ogre_magi_innate_custom +return ____exports diff --git a/scripts/vscripts/abilities/heroes/ogre_magi/ability_ogre_magi_multicast_custom.lua b/scripts/vscripts/abilities/heroes/ogre_magi/ability_ogre_magi_multicast_custom.lua new file mode 100644 index 0000000..4b3597f --- /dev/null +++ b/scripts/vscripts/abilities/heroes/ogre_magi/ability_ogre_magi_multicast_custom.lua @@ -0,0 +1,396 @@ +local ____lualib = require("lualib_bundle") +local __TS__Class = ____lualib.__TS__Class +local __TS__ClassExtends = ____lualib.__TS__ClassExtends +local __TS__Decorate = ____lualib.__TS__Decorate +local ____exports = {} +local ____dota_ts_adapter = require("lib.dota_ts_adapter") +local BaseAbility = ____dota_ts_adapter.BaseAbility +local BaseModifier = ____dota_ts_adapter.BaseModifier +local registerAbility = ____dota_ts_adapter.registerAbility +local registerModifier = ____dota_ts_adapter.registerModifier +local ____ogre_magi_multicast_shared = require("abilities.heroes.ogre_magi.ogre_magi_multicast_shared") +local isOgreMulticastGuarded = ____ogre_magi_multicast_shared.isOgreMulticastGuarded +local OGRE_IGNITE_DEBUFF = ____ogre_magi_multicast_shared.OGRE_IGNITE_DEBUFF +local OGRE_IGNITE_PROC_DAMAGE_TALENT = ____ogre_magi_multicast_shared.OGRE_IGNITE_PROC_DAMAGE_TALENT +local queueOgreMulticastProc = ____ogre_magi_multicast_shared.queueOgreMulticastProc +local rollOgreMulticastExtraCasts = ____ogre_magi_multicast_shared.rollOgreMulticastExtraCasts +local setOgreMulticastGuard = ____ogre_magi_multicast_shared.setOgreMulticastGuard +local tryOgreIgniteOnCastProc = ____ogre_magi_multicast_shared.tryOgreIgniteOnCastProc +____exports.ability_ogre_magi_multicast_custom = __TS__Class() +local ability_ogre_magi_multicast_custom = ____exports.ability_ogre_magi_multicast_custom +ability_ogre_magi_multicast_custom.name = "ability_ogre_magi_multicast_custom" +ability_ogre_magi_multicast_custom.____file_path = "scripts/vscripts/abilities/heroes/ogre_magi/ability_ogre_magi_multicast_custom.lua" +__TS__ClassExtends(ability_ogre_magi_multicast_custom, BaseAbility) +function ability_ogre_magi_multicast_custom.prototype.GetIntrinsicModifierName(self) + return ____exports.modifier_ogre_magi_multicast_custom.name +end +function ability_ogre_magi_multicast_custom.prototype.GetBehavior(self) + if HasShard( + nil, + self:GetCaster() + ) then + return DOTA_ABILITY_BEHAVIOR_NO_TARGET + end + return DOTA_ABILITY_BEHAVIOR_PASSIVE +end +function ability_ogre_magi_multicast_custom.prototype.GetCooldown(self, level) + if HasShard( + nil, + self:GetCaster() + ) then + return self:GetSpecialValueFor("shard_cooldown") + end + return 0 +end +function ability_ogre_magi_multicast_custom.prototype.GetManaCost(self, level) + if HasShard( + nil, + self:GetCaster() + ) then + return self:GetSpecialValueFor("shard_mana_cost") + end + return 0 +end +function ability_ogre_magi_multicast_custom.prototype.Precache(self, context) + PrecacheResource("particle", "particles/units/heroes/hero_ogre_magi/ogre_magi_multicast.vpcf", context) + PrecacheResource("particle", "particles/units/heroes/hero_ogre_magi/ogre_magi_fireblast.vpcf", context) + PrecacheResource("soundfile", "soundevents/game_sounds_heroes/game_sounds_ogre_magi.vsndevts", context) +end +function ability_ogre_magi_multicast_custom.prototype.OnSpellStart(self) + if not IsServer() then + return + end + local caster = self:GetCaster() + local chance2x = self:GetSpecialValueFor("chance_2x") + local chance3x = self:GetSpecialValueFor("chance_3x") + local chance4x = self:GetSpecialValueFor("chance_4x") + local shardRadius = self:GetSpecialValueFor("shard_radius") + local shardDuration = self:GetSpecialValueFor("shard_duration") + local allies = FindUnitsInRadius( + caster:GetTeamNumber(), + caster:GetAbsOrigin(), + nil, + shardRadius, + DOTA_UNIT_TARGET_TEAM_FRIENDLY, + DOTA_UNIT_TARGET_HERO, + DOTA_UNIT_TARGET_FLAG_NONE, + FIND_ANY_ORDER, + false + ) + for ____, ally in ipairs(allies) do + do + if ally == caster then + goto __continue12 + end + ally:AddNewModifier(caster, self, ____exports.modifier_ogre_magi_multicast_stack.name, {duration = shardDuration, chance_2x = chance2x, chance_3x = chance3x, chance_4x = chance4x}) + local pfx = ParticleManager:CreateParticle("particles/units/heroes/hero_ogre_magi/ogre_magi_fireblast.vpcf", PATTACH_ABSORIGIN_FOLLOW, ally) + ParticleManager:ReleaseParticleIndex(pfx) + EmitSoundOn("Hero_OgreMagi.FireShield.Target", ally) + end + ::__continue12:: + end + EmitSoundOn("Hero_OgreMagi.FireShield.Cast", caster) +end +ability_ogre_magi_multicast_custom = __TS__Decorate( + ability_ogre_magi_multicast_custom, + ability_ogre_magi_multicast_custom, + {registerAbility(nil)}, + {kind = "class", name = "ability_ogre_magi_multicast_custom"} +) +____exports.ability_ogre_magi_multicast_custom = ability_ogre_magi_multicast_custom +____exports.modifier_ogre_magi_multicast_custom = __TS__Class() +local modifier_ogre_magi_multicast_custom = ____exports.modifier_ogre_magi_multicast_custom +modifier_ogre_magi_multicast_custom.name = "modifier_ogre_magi_multicast_custom" +modifier_ogre_magi_multicast_custom.____file_path = "scripts/vscripts/abilities/heroes/ogre_magi/ability_ogre_magi_multicast_custom.lua" +__TS__ClassExtends(modifier_ogre_magi_multicast_custom, BaseModifier) +function modifier_ogre_magi_multicast_custom.prototype.IsHidden(self) + return false +end +function modifier_ogre_magi_multicast_custom.prototype.IsPurgable(self) + return false +end +function modifier_ogre_magi_multicast_custom.prototype.IsPurgeException(self) + return false +end +function modifier_ogre_magi_multicast_custom.prototype.RemoveOnDeath(self) + return false +end +function modifier_ogre_magi_multicast_custom.prototype.DeclareFunctions(self) + return {MODIFIER_EVENT_ON_ABILITY_EXECUTED, MODIFIER_PROPERTY_PROCATTACK_BONUS_DAMAGE_PHYSICAL} +end +function modifier_ogre_magi_multicast_custom.prototype.GetModifierProcAttack_BonusDamage_Physical(self, event) + local parent = self:GetParent() + if not HasTalent(nil, parent, OGRE_IGNITE_PROC_DAMAGE_TALENT) then + return 0 + end + if not event.target:HasModifier(OGRE_IGNITE_DEBUFF) then + return 0 + end + return event.damage * 0.3 +end +function modifier_ogre_magi_multicast_custom.prototype.OnAbilityExecuted(self, event) + if not IsServer() then + return + end + local parent = self:GetParent() + if event.unit ~= parent then + return + end + local ability = event.ability + if not ability or ability:IsNull() then + return + end + if bit.band( + ability:GetBehaviorInt(), + DOTA_ABILITY_BEHAVIOR_TOGGLE + ) ~= 0 then + return + end + if ability:GetAbilityName() == ____exports.ability_ogre_magi_multicast_custom.name then + return + end + local abilityEntindex = ability:entindex() + if isOgreMulticastGuarded(nil, abilityEntindex) then + return + end + local hero = parent + tryOgreIgniteOnCastProc(nil, hero) + local multicastAbility = self:GetAbility() + if not multicastAbility or multicastAbility:GetLevel() <= 0 then + return + end + local extraCasts = rollOgreMulticastExtraCasts(nil, hero, multicastAbility) + queueOgreMulticastProc( + nil, + hero, + multicastAbility, + ability, + extraCasts + ) +end +modifier_ogre_magi_multicast_custom = __TS__Decorate( + modifier_ogre_magi_multicast_custom, + modifier_ogre_magi_multicast_custom, + {registerModifier(nil)}, + {kind = "class", name = "modifier_ogre_magi_multicast_custom"} +) +____exports.modifier_ogre_magi_multicast_custom = modifier_ogre_magi_multicast_custom +____exports.modifier_ogre_magi_multicast_proc = __TS__Class() +local modifier_ogre_magi_multicast_proc = ____exports.modifier_ogre_magi_multicast_proc +modifier_ogre_magi_multicast_proc.name = "modifier_ogre_magi_multicast_proc" +modifier_ogre_magi_multicast_proc.____file_path = "scripts/vscripts/abilities/heroes/ogre_magi/ability_ogre_magi_multicast_custom.lua" +__TS__ClassExtends(modifier_ogre_magi_multicast_proc, BaseModifier) +function modifier_ogre_magi_multicast_proc.prototype.____constructor(self, ...) + BaseModifier.prototype.____constructor(self, ...) + self.castsLeft = 0 + self.totalCasts = 0 + self.abilityEntindex = -1 + self.cursorPos = Vector(0, 0, 0) +end +function modifier_ogre_magi_multicast_proc.prototype.IsHidden(self) + return true +end +function modifier_ogre_magi_multicast_proc.prototype.IsPurgable(self) + return false +end +function modifier_ogre_magi_multicast_proc.prototype.OnCreated(self, params) + if not IsServer() then + return + end + self.abilityEntindex = params.ability_entindex + self.castsLeft = params.casts_left + self.totalCasts = params.casts_left + 1 + self.cursorPos = Vector(params.cursor_x, params.cursor_y, params.cursor_z) + local ____opt_0 = self:GetAbility() + local delay = ____opt_0 and ____opt_0:GetSpecialValueFor("multicast_delay") or 0.6 + self:StartIntervalThink(delay) +end +function modifier_ogre_magi_multicast_proc.prototype.OnIntervalThink(self) + if not IsServer() then + return + end + if self.castsLeft <= 0 then + self:Destroy() + return + end + local ability = EntIndexToHScript(self.abilityEntindex) + if not ability or ability:IsNull() then + self:Destroy() + return + end + local luaAbility = ability + if type(luaAbility.OnSpellStart) ~= "function" then + self.castsLeft = self.castsLeft - 1 + if self.castsLeft <= 0 then + self:Destroy() + end + return + end + local parent = self:GetParent() + local hero = parent + local behavior = ability:GetBehaviorInt() + local isUnitTarget = bit.band(behavior, DOTA_ABILITY_BEHAVIOR_UNIT_TARGET) ~= 0 + local isPointTarget = bit.band(behavior, DOTA_ABILITY_BEHAVIOR_POINT) ~= 0 + setOgreMulticastGuard(nil, self.abilityEntindex, true) + if isUnitTarget then + local castRange = ability:GetCastRange( + parent:GetAbsOrigin(), + nil + ) + local targets = FindUnitsInRadius( + parent:GetTeamNumber(), + parent:GetAbsOrigin(), + nil, + castRange, + ability:GetAbilityTargetTeam(), + ability:GetAbilityTargetType(), + ability:GetAbilityTargetFlags(), + FIND_CLOSEST, + false + ) + if #targets > 0 then + parent:SetCursorCastTarget(targets[1]) + luaAbility:OnSpellStart() + tryOgreIgniteOnCastProc(nil, hero) + end + elseif isPointTarget then + parent:SetCursorPosition(self.cursorPos) + luaAbility:OnSpellStart() + tryOgreIgniteOnCastProc(nil, hero) + else + luaAbility:OnSpellStart() + tryOgreIgniteOnCastProc(nil, hero) + end + setOgreMulticastGuard(nil, self.abilityEntindex, false) + local castNumber = self.totalCasts - self.castsLeft + 1 + if self.multicastPfx ~= nil then + ParticleManager:DestroyParticle(self.multicastPfx, true) + ParticleManager:ReleaseParticleIndex(self.multicastPfx) + end + self.multicastPfx = ParticleManager:CreateParticle("particles/units/heroes/hero_ogre_magi/ogre_magi_multicast.vpcf", PATTACH_OVERHEAD_FOLLOW, parent) + ParticleManager:SetParticleControl( + self.multicastPfx, + 1, + Vector(castNumber, 0, 0) + ) + if self.castsLeft == 1 and self.multicastPfx ~= nil then + ParticleManager:ReleaseParticleIndex(self.multicastPfx) + end + local soundIndex = math.min(castNumber - 1, 3) + if soundIndex > 0 then + EmitSoundOn( + "Hero_OgreMagi.Fireblast.x" .. tostring(soundIndex), + parent + ) + end + self.castsLeft = self.castsLeft - 1 +end +modifier_ogre_magi_multicast_proc = __TS__Decorate( + modifier_ogre_magi_multicast_proc, + modifier_ogre_magi_multicast_proc, + {registerModifier(nil)}, + {kind = "class", name = "modifier_ogre_magi_multicast_proc"} +) +____exports.modifier_ogre_magi_multicast_proc = modifier_ogre_magi_multicast_proc +____exports.modifier_ogre_magi_multicast_stack = __TS__Class() +local modifier_ogre_magi_multicast_stack = ____exports.modifier_ogre_magi_multicast_stack +modifier_ogre_magi_multicast_stack.name = "modifier_ogre_magi_multicast_stack" +modifier_ogre_magi_multicast_stack.____file_path = "scripts/vscripts/abilities/heroes/ogre_magi/ability_ogre_magi_multicast_custom.lua" +__TS__ClassExtends(modifier_ogre_magi_multicast_stack, BaseModifier) +function modifier_ogre_magi_multicast_stack.prototype.____constructor(self, ...) + BaseModifier.prototype.____constructor(self, ...) + self.chance2x = 0 + self.chance3x = 0 + self.chance4x = 0 +end +function modifier_ogre_magi_multicast_stack.prototype.IsHidden(self) + return false +end +function modifier_ogre_magi_multicast_stack.prototype.IsPurgable(self) + return true +end +function modifier_ogre_magi_multicast_stack.prototype.IsDebuff(self) + return false +end +function modifier_ogre_magi_multicast_stack.prototype.GetEffectName(self) + return "particles/econ/items/ogre_magi/ogre_magi_arcana/ogre_magi_arcana_stunned.vpcf" +end +function modifier_ogre_magi_multicast_stack.prototype.OnCreated(self, params) + if not IsServer() then + return + end + self.chance2x = params.chance_2x + self.chance3x = params.chance_3x + self.chance4x = params.chance_4x + self:SetStackCount(1) +end +function modifier_ogre_magi_multicast_stack.prototype.OnRefresh(self, params) + if not IsServer() then + return + end + self.chance2x = params.chance_2x + self.chance3x = params.chance_3x + self.chance4x = params.chance_4x + if self:GetStackCount() < 4 then + self:IncrementStackCount() + end +end +function modifier_ogre_magi_multicast_stack.prototype.DeclareFunctions(self) + return {MODIFIER_EVENT_ON_ABILITY_EXECUTED} +end +function modifier_ogre_magi_multicast_stack.prototype.OnAbilityExecuted(self, event) + if not IsServer() then + return + end + local parent = self:GetParent() + if event.unit ~= parent then + return + end + local ability = event.ability + if not ability or ability:IsNull() then + return + end + if ability:GetAbilityName() == ____exports.ability_ogre_magi_multicast_custom.name then + return + end + if bit.band( + ability:GetBehaviorInt(), + DOTA_ABILITY_BEHAVIOR_TOGGLE + ) ~= 0 then + return + end + local abilityEntindex = ability:entindex() + if isOgreMulticastGuarded(nil, abilityEntindex) then + return + end + local stacks = self:GetStackCount() + if stacks <= 1 then + self:Destroy() + else + self:DecrementStackCount() + end + local multicastAbility = self:GetAbility() + if not multicastAbility then + return + end + local hero = parent + local extraCasts = rollOgreMulticastExtraCasts(nil, hero, multicastAbility, {chance2x = self.chance2x, chance3x = self.chance3x, chance4x = self.chance4x}) + if extraCasts > 0 then + queueOgreMulticastProc( + nil, + hero, + multicastAbility, + ability, + extraCasts + ) + end + tryOgreIgniteOnCastProc(nil, hero) +end +modifier_ogre_magi_multicast_stack = __TS__Decorate( + modifier_ogre_magi_multicast_stack, + modifier_ogre_magi_multicast_stack, + {registerModifier(nil)}, + {kind = "class", name = "modifier_ogre_magi_multicast_stack"} +) +____exports.modifier_ogre_magi_multicast_stack = modifier_ogre_magi_multicast_stack +return ____exports diff --git a/scripts/vscripts/abilities/heroes/ogre_magi/ability_ogre_magi_unrefined_fireblast_custom.lua b/scripts/vscripts/abilities/heroes/ogre_magi/ability_ogre_magi_unrefined_fireblast_custom.lua new file mode 100644 index 0000000..8fc8ec4 --- /dev/null +++ b/scripts/vscripts/abilities/heroes/ogre_magi/ability_ogre_magi_unrefined_fireblast_custom.lua @@ -0,0 +1,52 @@ +local ____lualib = require("lualib_bundle") +local __TS__Class = ____lualib.__TS__Class +local __TS__ClassExtends = ____lualib.__TS__ClassExtends +local __TS__Decorate = ____lualib.__TS__Decorate +local ____exports = {} +local ____dota_ts_adapter = require("lib.dota_ts_adapter") +local BaseAbility = ____dota_ts_adapter.BaseAbility +local registerAbility = ____dota_ts_adapter.registerAbility +local ____ogre_magi_fireblast_aoe = require("abilities.heroes.ogre_magi.ogre_magi_fireblast_aoe") +local castOgreFireblastAoE = ____ogre_magi_fireblast_aoe.castOgreFireblastAoE +local ____ogre_magi_particles = require("abilities.heroes.ogre_magi.ogre_magi_particles") +local OGRE_FIREBLAST_IMPACT = ____ogre_magi_particles.OGRE_FIREBLAST_IMPACT +____exports.ability_ogre_magi_unrefined_fireblast_custom = __TS__Class() +local ability_ogre_magi_unrefined_fireblast_custom = ____exports.ability_ogre_magi_unrefined_fireblast_custom +ability_ogre_magi_unrefined_fireblast_custom.name = "ability_ogre_magi_unrefined_fireblast_custom" +ability_ogre_magi_unrefined_fireblast_custom.____file_path = "scripts/vscripts/abilities/heroes/ogre_magi/ability_ogre_magi_unrefined_fireblast_custom.lua" +__TS__ClassExtends(ability_ogre_magi_unrefined_fireblast_custom, BaseAbility) +function ability_ogre_magi_unrefined_fireblast_custom.prototype.Precache(self, context) + PrecacheResource("particle", OGRE_FIREBLAST_IMPACT, context) + PrecacheResource("soundfile", "soundevents/game_sounds_heroes/game_sounds_ogre_magi.vsndevts", context) +end +function ability_ogre_magi_unrefined_fireblast_custom.prototype.GetAOERadius(self) + return self:GetSpecialValueFor("blast_radius") +end +function ability_ogre_magi_unrefined_fireblast_custom.prototype.GetManaCost(self, level) + local caster = self:GetCaster() + local pct = self:GetSpecialValueFor("mana_cost_pct") + return math.floor(caster:GetMana() * (pct / 100)) +end +function ability_ogre_magi_unrefined_fireblast_custom.prototype.OnSpellStart(self) + if not IsServer() then + return + end + local caster = self:GetCaster() + local point = self:GetCursorPosition() + EmitSoundOn("Hero_OgreMagi.Fireblast.Cast", caster) + castOgreFireblastAoE( + nil, + self, + caster, + point, + "blast_damage" + ) +end +ability_ogre_magi_unrefined_fireblast_custom = __TS__Decorate( + ability_ogre_magi_unrefined_fireblast_custom, + ability_ogre_magi_unrefined_fireblast_custom, + {registerAbility(nil)}, + {kind = "class", name = "ability_ogre_magi_unrefined_fireblast_custom"} +) +____exports.ability_ogre_magi_unrefined_fireblast_custom = ability_ogre_magi_unrefined_fireblast_custom +return ____exports diff --git a/scripts/vscripts/abilities/heroes/ogre_magi/modifier_ogre_magi_multicast_stack.lua b/scripts/vscripts/abilities/heroes/ogre_magi/modifier_ogre_magi_multicast_stack.lua new file mode 100644 index 0000000..c34edc5 --- /dev/null +++ b/scripts/vscripts/abilities/heroes/ogre_magi/modifier_ogre_magi_multicast_stack.lua @@ -0,0 +1,130 @@ +local ____lualib = require("lualib_bundle") +local __TS__Class = ____lualib.__TS__Class +local __TS__ClassExtends = ____lualib.__TS__ClassExtends +local __TS__Delete = ____lualib.__TS__Delete +local __TS__Decorate = ____lualib.__TS__Decorate +local ____exports = {} +local ____dota_ts_adapter = require("lib.dota_ts_adapter") +local BaseModifier = ____dota_ts_adapter.BaseModifier +local registerModifier = ____dota_ts_adapter.registerModifier +local ____ogre_magi_multicast_shared = require("abilities.heroes.ogre_magi.ogre_magi_multicast_shared") +local isOgreMulticastGuarded = ____ogre_magi_multicast_shared.isOgreMulticastGuarded +local rollOgreMulticastExtraCasts = ____ogre_magi_multicast_shared.rollOgreMulticastExtraCasts +local tryOgreIgniteOnCastProc = ____ogre_magi_multicast_shared.tryOgreIgniteOnCastProc +local ____ability_ogre_magi_multicast_custom = require("abilities.heroes.ogre_magi.ability_ogre_magi_multicast_custom") +local ability_ogre_magi_multicast_custom = ____ability_ogre_magi_multicast_custom.ability_ogre_magi_multicast_custom +____exports.modifier_ogre_magi_multicast_stack = __TS__Class() +local modifier_ogre_magi_multicast_stack = ____exports.modifier_ogre_magi_multicast_stack +modifier_ogre_magi_multicast_stack.name = "modifier_ogre_magi_multicast_stack" +modifier_ogre_magi_multicast_stack.____file_path = "scripts/vscripts/abilities/heroes/ogre_magi/modifier_ogre_magi_multicast_stack.lua" +__TS__ClassExtends(modifier_ogre_magi_multicast_stack, BaseModifier) +function modifier_ogre_magi_multicast_stack.prototype.____constructor(self, ...) + BaseModifier.prototype.____constructor(self, ...) + self.chance2x = 0 + self.chance3x = 0 + self.chance4x = 0 +end +function modifier_ogre_magi_multicast_stack.prototype.IsHidden(self) + return false +end +function modifier_ogre_magi_multicast_stack.prototype.IsPurgable(self) + return true +end +function modifier_ogre_magi_multicast_stack.prototype.IsDebuff(self) + return false +end +function modifier_ogre_magi_multicast_stack.prototype.GetEffectName(self) + return "particles/econ/items/ogre_magi/ogre_magi_arcana/ogre_magi_arcana_stunned.vpcf" +end +function modifier_ogre_magi_multicast_stack.prototype.OnCreated(self, params) + if not IsServer() then + return + end + self.chance2x = params.chance_2x + self.chance3x = params.chance_3x + self.chance4x = params.chance_4x + self:SetStackCount(1) +end +function modifier_ogre_magi_multicast_stack.prototype.OnRefresh(self, params) + if not IsServer() then + return + end + self.chance2x = params.chance_2x + self.chance3x = params.chance_3x + self.chance4x = params.chance_4x + if self:GetStackCount() < 4 then + self:IncrementStackCount() + end +end +function modifier_ogre_magi_multicast_stack.prototype.DeclareFunctions(self) + return {MODIFIER_EVENT_ON_ABILITY_EXECUTED} +end +function modifier_ogre_magi_multicast_stack.prototype.OnAbilityExecuted(self, event) + if not IsServer() then + return + end + local parent = self:GetParent() + if event.unit ~= parent then + return + end + local ability = event.ability + if not ability or ability:IsNull() then + return + end + if ability:GetAbilityName() == ability_ogre_magi_multicast_custom.name then + return + end + if bit.band( + ability:GetBehaviorInt(), + DOTA_ABILITY_BEHAVIOR_TOGGLE + ) ~= 0 then + return + end + local abilityEntindex = ability:entindex() + if isOgreMulticastGuarded(nil, abilityEntindex) then + return + end + local stacks = self:GetStackCount() + if stacks <= 1 then + self:Destroy() + else + self:DecrementStackCount() + end + local multicastAbility = self:GetAbility() + if not multicastAbility then + return + end + local hero = parent + local saved2 = multicastAbility:GetSpecialValueFor("chance_2x") + local saved3 = multicastAbility:GetSpecialValueFor("chance_3x") + local saved4 = multicastAbility:GetSpecialValueFor("chance_4x") + multicastAbility.__shardOverride = {c2 = self.chance2x, c3 = self.chance3x, c4 = self.chance4x} + local extraCasts = rollOgreMulticastExtraCasts(nil, hero, multicastAbility) + __TS__Delete(multicastAbility, "__shardOverride") + local cursorTarget = parent:GetCursorCastTarget() + local cursorPos = parent:GetCursorPosition() + if extraCasts > 0 then + parent:AddNewModifier( + parent, + multicastAbility, + "modifier_ogre_magi_multicast_proc", + { + ability_entindex = abilityEntindex, + casts_left = extraCasts, + target_entindex = cursorTarget and cursorTarget:entindex() or -1, + cursor_x = cursorPos.x, + cursor_y = cursorPos.y, + cursor_z = cursorPos.z + } + ) + end + tryOgreIgniteOnCastProc(nil, hero) +end +modifier_ogre_magi_multicast_stack = __TS__Decorate( + modifier_ogre_magi_multicast_stack, + modifier_ogre_magi_multicast_stack, + {registerModifier(nil)}, + {kind = "class", name = "modifier_ogre_magi_multicast_stack"} +) +____exports.modifier_ogre_magi_multicast_stack = modifier_ogre_magi_multicast_stack +return ____exports diff --git a/scripts/vscripts/abilities/heroes/ogre_magi/ogre_magi_damage.lua b/scripts/vscripts/abilities/heroes/ogre_magi/ogre_magi_damage.lua new file mode 100644 index 0000000..4e13baf --- /dev/null +++ b/scripts/vscripts/abilities/heroes/ogre_magi/ogre_magi_damage.lua @@ -0,0 +1,11 @@ +--[[ Generated with https://github.com/TypeScriptToLua/TypeScriptToLua ]] +local ____exports = {} +--- Базовый урон способности + % от макс. здоровья кастера (как у Pudge). +function ____exports.getOgreMagiDamageWithMaxHealth(self, ability, caster, baseDamage) + local pct = ability:GetSpecialValueFor("max_health_damage_pct") + if pct <= 0 then + return baseDamage + end + return baseDamage + caster:GetMaxHealth() * pct / 100 +end +return ____exports diff --git a/scripts/vscripts/abilities/heroes/ogre_magi/ogre_magi_fireblast_aoe.lua b/scripts/vscripts/abilities/heroes/ogre_magi/ogre_magi_fireblast_aoe.lua new file mode 100644 index 0000000..fc35417 --- /dev/null +++ b/scripts/vscripts/abilities/heroes/ogre_magi/ogre_magi_fireblast_aoe.lua @@ -0,0 +1,52 @@ +--[[ Generated with https://github.com/TypeScriptToLua/TypeScriptToLua ]] +local ____exports = {} +local ____ogre_magi_damage = require("abilities.heroes.ogre_magi.ogre_magi_damage") +local getOgreMagiDamageWithMaxHealth = ____ogre_magi_damage.getOgreMagiDamageWithMaxHealth +local ____ogre_magi_particles = require("abilities.heroes.ogre_magi.ogre_magi_particles") +local spawnOgreFireblastOnUnit = ____ogre_magi_particles.spawnOgreFireblastOnUnit +--- Мгновенный Fireblast в точке: урон и стан всем врагам в радиусе, партикл на каждой цели. +function ____exports.castOgreFireblastAoE(self, ability, caster, point, damageSpecial) + if not IsServer() then + return + end + local radius = ability:GetSpecialValueFor("blast_radius") + local baseDamage = ability:GetSpecialValueFor(damageSpecial) + local stunDuration = ability:GetSpecialValueFor("stun_duration") + local enemies = FindUnitsInRadius( + caster:GetTeamNumber(), + point, + nil, + radius, + DOTA_UNIT_TARGET_TEAM_ENEMY, + bit.bor(DOTA_UNIT_TARGET_HERO, DOTA_UNIT_TARGET_BASIC), + DOTA_UNIT_TARGET_FLAG_NONE, + FIND_ANY_ORDER, + false + ) + local playedAreaSound = false + for ____, enemy in ipairs(enemies) do + do + if not enemy or enemy:IsNull() or not enemy:IsAlive() then + goto __continue4 + end + spawnOgreFireblastOnUnit(nil, enemy) + EmitSoundOn("Hero_OgreMagi.Fireblast.Target", enemy) + ApplyDamage({ + victim = enemy, + attacker = caster, + damage = getOgreMagiDamageWithMaxHealth(nil, ability, caster, baseDamage), + damage_type = ability:GetAbilityDamageType(), + ability = ability + }) + if not enemy:IsBossCreature() then + enemy:AddNewModifier(caster, ability, "modifier_stunned", {duration = stunDuration}) + end + playedAreaSound = true + end + ::__continue4:: + end + if not playedAreaSound then + EmitSoundOnLocationWithCaster(point, "Hero_OgreMagi.Fireblast.Target", caster) + end +end +return ____exports diff --git a/scripts/vscripts/abilities/heroes/ogre_magi/ogre_magi_multicast_shared.lua b/scripts/vscripts/abilities/heroes/ogre_magi/ogre_magi_multicast_shared.lua new file mode 100644 index 0000000..e8a16f7 --- /dev/null +++ b/scripts/vscripts/abilities/heroes/ogre_magi/ogre_magi_multicast_shared.lua @@ -0,0 +1,105 @@ +local ____lualib = require("lualib_bundle") +local __TS__Delete = ____lualib.__TS__Delete +local ____exports = {} +local ____luck = require("utils.luck") +local rollLuckChance = ____luck.rollLuckChance +local ____ability_ogre_magi_ignite_custom = require("abilities.heroes.ogre_magi.ability_ogre_magi_ignite_custom") +local ability_ogre_magi_ignite_custom = ____ability_ogre_magi_ignite_custom.ability_ogre_magi_ignite_custom +____exports.OGRE_MULTICAST_ABILITY = "ability_ogre_magi_multicast_custom" +____exports.OGRE_MULTICAST_PROC_MODIFIER = "modifier_ogre_magi_multicast_proc" +____exports.OGRE_IGNITE_DEBUFF = "modifier_ogre_magi_ignite_debuff" +____exports.OGRE_IGNITE_ON_CAST_TALENT = "special_bonus_unique_ogre_magi_ignite_on_cast" +____exports.OGRE_IGNITE_PROC_DAMAGE_TALENT = "special_bonus_unique_ogre_magi_ignite_proc_damage" +local multicastGuard = {} +function ____exports.isOgreMulticastGuarded(self, abilityEntindex) + return multicastGuard[abilityEntindex] == true +end +function ____exports.setOgreMulticastGuard(self, abilityEntindex, guarded) + if guarded then + multicastGuard[abilityEntindex] = true + else + __TS__Delete(multicastGuard, abilityEntindex) + end +end +function ____exports.rollOgreMulticastExtraCasts(self, caster, multicast, chances) + if multicast:GetLevel() <= 0 or caster:PassivesDisabled() then + return 0 + end + local chance2 = (chances and chances.chance2x or multicast:GetSpecialValueFor("chance_2x")) / 100 + local chance3 = (chances and chances.chance3x or multicast:GetSpecialValueFor("chance_3x")) / 100 + local chance4 = (chances and chances.chance4x or multicast:GetSpecialValueFor("chance_4x")) / 100 + local totalCasts = 1 + if rollLuckChance(nil, caster, chance2) then + totalCasts = totalCasts + 1 + if rollLuckChance(nil, caster, chance3) then + totalCasts = totalCasts + 1 + if rollLuckChance(nil, caster, chance4) then + totalCasts = totalCasts + 1 + end + end + end + return math.max(0, totalCasts - 1) +end +function ____exports.tryOgreIgniteOnCastProc(self, parent) + if not IsServer() then + return + end + if not HasTalent(nil, parent, ____exports.OGRE_IGNITE_ON_CAST_TALENT) then + return + end + local igniteAbility = parent:FindAbilityByName(ability_ogre_magi_ignite_custom.name) + if not igniteAbility or igniteAbility:GetLevel() <= 0 then + return + end + if not rollLuckChance(nil, parent, 0.2) then + return + end + local castRange = igniteAbility:GetCastRange( + parent:GetAbsOrigin(), + nil + ) + local targets = FindUnitsInRadius( + parent:GetTeamNumber(), + parent:GetAbsOrigin(), + nil, + castRange, + DOTA_UNIT_TARGET_TEAM_ENEMY, + bit.bor(DOTA_UNIT_TARGET_HERO, DOTA_UNIT_TARGET_BASIC), + DOTA_UNIT_TARGET_FLAG_NONE, + FIND_CLOSEST, + false + ) + if #targets == 0 then + return + end + ProjectileManager:CreateTrackingProjectile({ + Ability = igniteAbility, + EffectName = "particles/units/heroes/hero_ogre_magi/ogre_magi_ignite.vpcf", + Source = parent, + Target = targets[1], + iMoveSpeed = igniteAbility:GetSpecialValueFor("projectile_speed"), + bDodgeable = false + }) + EmitSoundOn("Hero_OgreMagi.Ignite.Cast", parent) +end +function ____exports.queueOgreMulticastProc(self, parent, multicastAbility, ability, extraCasts) + if extraCasts <= 0 then + return + end + local cursorTarget = parent:GetCursorCastTarget() + local cursorPos = parent:GetCursorPosition() + parent:AddNewModifier( + parent, + multicastAbility, + ____exports.OGRE_MULTICAST_PROC_MODIFIER, + { + ability_entindex = ability:entindex(), + casts_left = extraCasts, + target_entindex = cursorTarget and cursorTarget:entindex() or -1, + cursor_x = cursorPos.x, + cursor_y = cursorPos.y, + cursor_z = cursorPos.z + } + ) +end +return ____exports diff --git a/scripts/vscripts/abilities/heroes/ogre_magi/ogre_magi_multicast_util.lua b/scripts/vscripts/abilities/heroes/ogre_magi/ogre_magi_multicast_util.lua new file mode 100644 index 0000000..006267b --- /dev/null +++ b/scripts/vscripts/abilities/heroes/ogre_magi/ogre_magi_multicast_util.lua @@ -0,0 +1,75 @@ +local ____lualib = require("lualib_bundle") +local Set = ____lualib.Set +local __TS__New = ____lualib.__TS__New +local ____exports = {} +local ____luck = require("utils.luck") +local rollLuckChance = ____luck.rollLuckChance +____exports.OGRE_MAGI_MULTICAST_ABILITY = "ability_ogre_magi_multicast_custom" +____exports.OGRE_MAGI_MULTICASTABLE_ABILITIES = __TS__New(Set, {"ability_ogre_magi_fireblast_custom", "ability_ogre_magi_ignite_custom", "ability_ogre_magi_bloodlust_custom"}) +local MULTICAST_REPLAY_FLAG = "__ogreMagiMulticastReplay" +--- Сколько дополнительных применений после основного (0–3). +function ____exports.getOgreMulticastExtraCastCount(self, caster) + local multicast = caster:FindAbilityByName(____exports.OGRE_MAGI_MULTICAST_ABILITY) + if not multicast or multicast:GetLevel() <= 0 or caster:PassivesDisabled() then + return 0 + end + local level = multicast:GetLevel() + local chance2 = multicast:GetSpecialValueFor("multicast_2x_chance") / 100 + local chance3 = multicast:GetSpecialValueFor("multicast_3x_chance") / 100 + local chance4 = multicast:GetSpecialValueFor("multicast_4x_chance") / 100 + local totalCasts = 1 + if rollLuckChance(nil, caster, chance2) then + totalCasts = totalCasts + 1 + end + if level >= 2 and rollLuckChance(nil, caster, chance3) then + totalCasts = totalCasts + 1 + end + if level >= 3 and rollLuckChance(nil, caster, chance4) then + totalCasts = totalCasts + 1 + end + return math.max(0, totalCasts - 1) +end +--- Повторяет колбэк каста с задержкой (без повторного Multicast). +-- +-- @param interval секунд между доп. кастами +function ____exports.scheduleOgreMulticastExtraCasts(self, caster, ability, interval, replayCast) + if not IsServer() then + return + end + if not ____exports.OGRE_MAGI_MULTICASTABLE_ABILITIES:has(ability:GetAbilityName()) then + return + end + local holder = caster + if holder[MULTICAST_REPLAY_FLAG] then + return + end + local extra = ____exports.getOgreMulticastExtraCastCount(nil, caster) + if extra <= 0 then + return + end + local multicast = caster:FindAbilityByName(____exports.OGRE_MAGI_MULTICAST_ABILITY) + if not multicast then + return + end + do + local i = 0 + while i < extra do + Timers:CreateTimer( + interval * (i + 1), + function() + if not caster or caster:IsNull() or not caster:IsAlive() then + return + end + holder[MULTICAST_REPLAY_FLAG] = true + replayCast(nil) + holder[MULTICAST_REPLAY_FLAG] = false + local pfx = ParticleManager:CreateParticle("particles/units/heroes/hero_ogre_magi/ogre_magi_multicast.vpcf", PATTACH_ABSORIGIN_FOLLOW, caster) + ParticleManager:ReleaseParticleIndex(pfx) + EmitSoundOn("Hero_OgreMagi.Fireblast.x3", caster) + end + ) + i = i + 1 + end + end +end +return ____exports diff --git a/scripts/vscripts/abilities/heroes/ogre_magi/ogre_magi_particles.lua b/scripts/vscripts/abilities/heroes/ogre_magi/ogre_magi_particles.lua new file mode 100644 index 0000000..3257069 --- /dev/null +++ b/scripts/vscripts/abilities/heroes/ogre_magi/ogre_magi_particles.lua @@ -0,0 +1,9 @@ +--[[ Generated with https://github.com/TypeScriptToLua/TypeScriptToLua ]] +local ____exports = {} +____exports.OGRE_FIREBLAST_IMPACT = "particles/units/heroes/hero_ogre_magi/ogre_magi_fireblast.vpcf" +--- Вспышка Fireblast на юните (как ванильный таргет-каст). +function ____exports.spawnOgreFireblastOnUnit(self, target) + local impact = ParticleManager:CreateParticle(____exports.OGRE_FIREBLAST_IMPACT, PATTACH_ABSORIGIN_FOLLOW, target) + ParticleManager:ReleaseParticleIndex(impact) +end +return ____exports diff --git a/scripts/vscripts/abilities/heroes/phantom_assassin/ability_phantom_assassin_blur_custom.lua b/scripts/vscripts/abilities/heroes/phantom_assassin/ability_phantom_assassin_blur_custom.lua new file mode 100644 index 0000000..f79fcb0 --- /dev/null +++ b/scripts/vscripts/abilities/heroes/phantom_assassin/ability_phantom_assassin_blur_custom.lua @@ -0,0 +1,187 @@ +local ____lualib = require("lualib_bundle") +local __TS__Class = ____lualib.__TS__Class +local __TS__ClassExtends = ____lualib.__TS__ClassExtends +local __TS__Decorate = ____lualib.__TS__Decorate +local ____exports = {} +local ____dota_ts_adapter = require("lib.dota_ts_adapter") +local BaseAbility = ____dota_ts_adapter.BaseAbility +local BaseModifier = ____dota_ts_adapter.BaseModifier +local registerAbility = ____dota_ts_adapter.registerAbility +local registerModifier = ____dota_ts_adapter.registerModifier +local ____luck = require("utils.luck") +local getLuck = ____luck.getLuck +--- Как у Hoodwink Scurry: уклонение = min(cap, luck × множитель). При желании вынести в AbilityValues blur. +local BLUR_LUCK_EVASION_MULT = 1 +local BLUR_MAX_LUCK_EVASION = 75 +____exports.ability_phantom_assassin_blur_custom = __TS__Class() +local ability_phantom_assassin_blur_custom = ____exports.ability_phantom_assassin_blur_custom +ability_phantom_assassin_blur_custom.name = "ability_phantom_assassin_blur_custom" +ability_phantom_assassin_blur_custom.____file_path = "scripts/vscripts/abilities/heroes/phantom_assassin/ability_phantom_assassin_blur_custom.lua" +__TS__ClassExtends(ability_phantom_assassin_blur_custom, BaseAbility) +function ability_phantom_assassin_blur_custom.prototype.GetIntrinsicModifierName(self) + return ____exports.modifier_phantom_assassin_blur_luck_evasion.name +end +function ability_phantom_assassin_blur_custom.prototype.OnSpellStart(self) + local caster = self:GetCaster() + local duration = self:GetSpecialValueFor("duration") + local hasShard = HasShard(nil, caster) + caster:AddNewModifier(caster, self, "modifier_phantom_assassin_blur_active_custom", {duration = duration}) + if hasShard then + local outgoing = self:GetSpecialValueFor("illusion_outgoing_damage") + local incoming = self:GetSpecialValueFor("illusion_incoming_damage") + local outgoing_final = outgoing - 100 + local illusion = CreateIllusions( + caster, + caster, + {outgoing_damage = outgoing_final, incoming_damage = incoming, duration = duration}, + 1, + 100, + false, + true + )[1] + illusion:AddNewModifier(illusion, self, "modifier_phantom_assassin_blur_illusion_custom", {duration = duration}) + end + EmitSoundOn("Hero_PhantomAssassin.Blur", caster) +end +ability_phantom_assassin_blur_custom = __TS__Decorate( + ability_phantom_assassin_blur_custom, + ability_phantom_assassin_blur_custom, + {registerAbility(nil)}, + {kind = "class", name = "ability_phantom_assassin_blur_custom"} +) +____exports.ability_phantom_assassin_blur_custom = ability_phantom_assassin_blur_custom +____exports.modifier_phantom_assassin_blur_luck_evasion = __TS__Class() +local modifier_phantom_assassin_blur_luck_evasion = ____exports.modifier_phantom_assassin_blur_luck_evasion +modifier_phantom_assassin_blur_luck_evasion.name = "modifier_phantom_assassin_blur_luck_evasion" +modifier_phantom_assassin_blur_luck_evasion.____file_path = "scripts/vscripts/abilities/heroes/phantom_assassin/ability_phantom_assassin_blur_custom.lua" +__TS__ClassExtends(modifier_phantom_assassin_blur_luck_evasion, BaseModifier) +function modifier_phantom_assassin_blur_luck_evasion.prototype.____constructor(self, ...) + BaseModifier.prototype.____constructor(self, ...) + self.luckEvasionMultiplier = BLUR_LUCK_EVASION_MULT + self.maxLuckEvasion = BLUR_MAX_LUCK_EVASION +end +function modifier_phantom_assassin_blur_luck_evasion.prototype.IsHidden(self) + return false +end +function modifier_phantom_assassin_blur_luck_evasion.prototype.IsPurgable(self) + return false +end +function modifier_phantom_assassin_blur_luck_evasion.prototype.DeclareFunctions(self) + return {MODIFIER_PROPERTY_EVASION_CONSTANT} +end +function modifier_phantom_assassin_blur_luck_evasion.prototype.OnCreated(self) + local ability = self:GetAbility() + if not ability then + return + end + local m = ability:GetSpecialValueFor("luck_evasion_multiplier") + local cap = ability:GetSpecialValueFor("max_luck_evasion") + if m > 0 then + self.luckEvasionMultiplier = m + end + if cap > 0 then + self.maxLuckEvasion = cap + end +end +function modifier_phantom_assassin_blur_luck_evasion.prototype.OnRefresh(self) + self:OnCreated() +end +function modifier_phantom_assassin_blur_luck_evasion.prototype.GetModifierEvasion_Constant(self) + if self:GetParent():PassivesDisabled() then + return 0 + end + local parent = self:GetParent() + if not parent:IsHero() then + return 0 + end + local luck = getLuck(nil, parent) + return math.min(self.maxLuckEvasion, luck * self.luckEvasionMultiplier) +end +modifier_phantom_assassin_blur_luck_evasion = __TS__Decorate( + modifier_phantom_assassin_blur_luck_evasion, + modifier_phantom_assassin_blur_luck_evasion, + {registerModifier(nil)}, + {kind = "class", name = "modifier_phantom_assassin_blur_luck_evasion"} +) +____exports.modifier_phantom_assassin_blur_luck_evasion = modifier_phantom_assassin_blur_luck_evasion +____exports.modifier_phantom_assassin_blur_active_custom = __TS__Class() +local modifier_phantom_assassin_blur_active_custom = ____exports.modifier_phantom_assassin_blur_active_custom +modifier_phantom_assassin_blur_active_custom.name = "modifier_phantom_assassin_blur_active_custom" +modifier_phantom_assassin_blur_active_custom.____file_path = "scripts/vscripts/abilities/heroes/phantom_assassin/ability_phantom_assassin_blur_custom.lua" +__TS__ClassExtends(modifier_phantom_assassin_blur_active_custom, BaseModifier) +function modifier_phantom_assassin_blur_active_custom.prototype.____constructor(self, ...) + BaseModifier.prototype.____constructor(self, ...) + self.Lock = false +end +function modifier_phantom_assassin_blur_active_custom.prototype.IsPurgable(self) + return false +end +function modifier_phantom_assassin_blur_active_custom.prototype.getEffectName(self) + return "particles/units/heroes/hero_phantom_assassin/phantom_assassin_blur_active.vpcf" +end +function modifier_phantom_assassin_blur_active_custom.prototype.CheckState(self) + return {[MODIFIER_STATE_INVISIBLE] = true, [MODIFIER_STATE_NO_UNIT_COLLISION] = true} +end +function modifier_phantom_assassin_blur_active_custom.prototype.DeclareFunctions(self) + return {MODIFIER_PROPERTY_STATS_AGILITY_BONUS, MODIFIER_PROPERTY_INVISIBILITY_LEVEL, MODIFIER_PROPERTY_MOVESPEED_BONUS_PERCENTAGE} +end +function modifier_phantom_assassin_blur_active_custom.prototype.GetModifierBonusStats_Agility(self) + if not IsServer() then + return 0 + end + if self.Lock then + return 0 + end + self.Lock = true + local agi = self:GetParent():GetAgility() + local PctBonus = self:GetAbility():GetSpecialValueFor("bonus_agility_pct") / 100 * agi + local full = PctBonus + self:GetAbility():GetSpecialValueFor("bonus_agility") + self.Lock = false + return full +end +function modifier_phantom_assassin_blur_active_custom.prototype.GetModifierInvisibilityLevel(self) + return 2 +end +function modifier_phantom_assassin_blur_active_custom.prototype.GetModifierMoveSpeedBonus_Percentage(self) + local ____opt_0 = self:GetAbility() + return ____opt_0 and ____opt_0:GetSpecialValueFor("move_speed_pct") or 0 +end +modifier_phantom_assassin_blur_active_custom = __TS__Decorate( + modifier_phantom_assassin_blur_active_custom, + modifier_phantom_assassin_blur_active_custom, + {registerModifier(nil)}, + {kind = "class", name = "modifier_phantom_assassin_blur_active_custom"} +) +____exports.modifier_phantom_assassin_blur_active_custom = modifier_phantom_assassin_blur_active_custom +____exports.modifier_phantom_assassin_blur_illusion_custom = __TS__Class() +local modifier_phantom_assassin_blur_illusion_custom = ____exports.modifier_phantom_assassin_blur_illusion_custom +modifier_phantom_assassin_blur_illusion_custom.name = "modifier_phantom_assassin_blur_illusion_custom" +modifier_phantom_assassin_blur_illusion_custom.____file_path = "scripts/vscripts/abilities/heroes/phantom_assassin/ability_phantom_assassin_blur_custom.lua" +__TS__ClassExtends(modifier_phantom_assassin_blur_illusion_custom, BaseModifier) +function modifier_phantom_assassin_blur_illusion_custom.prototype.____constructor(self, ...) + BaseModifier.prototype.____constructor(self, ...) + self.MODIFIER_PRIORITY = 10001 +end +function modifier_phantom_assassin_blur_illusion_custom.prototype.IsPurgable(self) + return false +end +function modifier_phantom_assassin_blur_illusion_custom.prototype.GetEffectName(self) + return "particles/units/heroes/hero_terrorblade/terrorblade_mirror_image.vpcf" +end +function modifier_phantom_assassin_blur_illusion_custom.prototype.GetEffectAttachType(self) + return PATTACH_ABSORIGIN_FOLLOW +end +function modifier_phantom_assassin_blur_illusion_custom.prototype.GetStatusEffectName(self) + return "particles/status_fx/status_effect_terrorblade_reflection.vpcf" +end +function modifier_phantom_assassin_blur_illusion_custom.prototype.StatusEffectPriority(self) + return self.MODIFIER_PRIORITY +end +modifier_phantom_assassin_blur_illusion_custom = __TS__Decorate( + modifier_phantom_assassin_blur_illusion_custom, + modifier_phantom_assassin_blur_illusion_custom, + {registerModifier(nil)}, + {kind = "class", name = "modifier_phantom_assassin_blur_illusion_custom"} +) +____exports.modifier_phantom_assassin_blur_illusion_custom = modifier_phantom_assassin_blur_illusion_custom +return ____exports diff --git a/scripts/vscripts/abilities/heroes/phantom_assassin/ability_phantom_assassin_coup_de_grace_custom.lua b/scripts/vscripts/abilities/heroes/phantom_assassin/ability_phantom_assassin_coup_de_grace_custom.lua new file mode 100644 index 0000000..d294840 --- /dev/null +++ b/scripts/vscripts/abilities/heroes/phantom_assassin/ability_phantom_assassin_coup_de_grace_custom.lua @@ -0,0 +1,221 @@ +local ____lualib = require("lualib_bundle") +local __TS__Class = ____lualib.__TS__Class +local __TS__ClassExtends = ____lualib.__TS__ClassExtends +local __TS__Decorate = ____lualib.__TS__Decorate +local ____exports = {} +local ____dota_ts_adapter = require("lib.dota_ts_adapter") +local BaseAbility = ____dota_ts_adapter.BaseAbility +local BaseModifier = ____dota_ts_adapter.BaseModifier +local registerAbility = ____dota_ts_adapter.registerAbility +local registerModifier = ____dota_ts_adapter.registerModifier +local ____ability_stacking_crit = require("abilities.modifiers.ability_stacking_crit") +local modifier_stacking_crit = ____ability_stacking_crit.modifier_stacking_crit +____exports.ability_phantom_assassin_coup_de_grace_custom = __TS__Class() +local ability_phantom_assassin_coup_de_grace_custom = ____exports.ability_phantom_assassin_coup_de_grace_custom +ability_phantom_assassin_coup_de_grace_custom.name = "ability_phantom_assassin_coup_de_grace_custom" +ability_phantom_assassin_coup_de_grace_custom.____file_path = "scripts/vscripts/abilities/heroes/phantom_assassin/ability_phantom_assassin_coup_de_grace_custom.lua" +__TS__ClassExtends(ability_phantom_assassin_coup_de_grace_custom, BaseAbility) +function ability_phantom_assassin_coup_de_grace_custom.prototype.GetIntrinsicModifierName(self) + return "modifier_phantom_assassin_coup_de_grace_custom" +end +function ability_phantom_assassin_coup_de_grace_custom.prototype.GetBehavior(self) + if self:GetCaster():HasScepter() then + return bit.bor(DOTA_ABILITY_BEHAVIOR_NO_TARGET, DOTA_ABILITY_BEHAVIOR_IMMEDIATE) + end + return DOTA_ABILITY_BEHAVIOR_PASSIVE +end +function ability_phantom_assassin_coup_de_grace_custom.prototype.OnSpellStart(self) + local caster = self:GetCaster() + if caster ~= nil and caster ~= nil then + caster:AddNewModifier( + caster, + self, + "modifier_phantom_assassin_coup_de_grace_custom_active", + {duration = self:GetSpecialValueFor("duration")} + ) + local ____opt_0 = modifier_stacking_crit:GetForUnit(caster) + if ____opt_0 ~= nil then + ____opt_0:GuaranteeCritUntilDestroy(true) + end + do + pcall(function() + self:SetActivated(false) + end) + end + end +end +ability_phantom_assassin_coup_de_grace_custom = __TS__Decorate( + ability_phantom_assassin_coup_de_grace_custom, + ability_phantom_assassin_coup_de_grace_custom, + {registerAbility(nil)}, + {kind = "class", name = "ability_phantom_assassin_coup_de_grace_custom"} +) +____exports.ability_phantom_assassin_coup_de_grace_custom = ability_phantom_assassin_coup_de_grace_custom +____exports.modifier_phantom_assassin_coup_de_grace_custom = __TS__Class() +local modifier_phantom_assassin_coup_de_grace_custom = ____exports.modifier_phantom_assassin_coup_de_grace_custom +modifier_phantom_assassin_coup_de_grace_custom.name = "modifier_phantom_assassin_coup_de_grace_custom" +modifier_phantom_assassin_coup_de_grace_custom.____file_path = "scripts/vscripts/abilities/heroes/phantom_assassin/ability_phantom_assassin_coup_de_grace_custom.lua" +__TS__ClassExtends(modifier_phantom_assassin_coup_de_grace_custom, BaseModifier) +function modifier_phantom_assassin_coup_de_grace_custom.prototype.DeclareFunctions(self) + local ____opt_2 = self:GetAbility() + if (____opt_2 and ____opt_2:GetSpecialValueFor("damage_mult")) > 0 then + return {MODIFIER_PROPERTY_DAMAGEOUTGOING_PERCENTAGE, MODIFIER_PROPERTY_EXTRA_HEALTH_PERCENTAGE} + end + return {} +end +function modifier_phantom_assassin_coup_de_grace_custom.prototype.GetModifierExtraHealthPercentage(self) + local ____opt_4 = self:GetAbility() + return -(____opt_4 and ____opt_4:GetSpecialValueFor("health_mult_decrease")) +end +function modifier_phantom_assassin_coup_de_grace_custom.prototype.GetModifierDamageOutgoing_Percentage(self) + local ____opt_6 = self:GetAbility() + return (____opt_6 and ____opt_6:GetSpecialValueFor("damage_mult")) - 100 +end +function modifier_phantom_assassin_coup_de_grace_custom.prototype.OnCreated(self) + if not IsServer() then + return + end + local ability = self:GetAbility() + if not ability then + return + end + local chance = ability:GetSpecialValueFor("crit_chance") + local mult = ability:GetSpecialValueFor("crit_mult") + local ____opt_8 = modifier_stacking_crit:GetForUnit(self:GetParent()) + if ____opt_8 ~= nil then + ____opt_8:UpdateCrit( + 0, + 0, + chance, + mult, + "coup_de_grace" + ) + end +end +function modifier_phantom_assassin_coup_de_grace_custom.prototype.OnRefresh(self) + if not IsServer() then + return + end + local ability = self:GetAbility() + if not ability then + return + end + local chance = ability:GetSpecialValueFor("crit_chance") + local mult = ability:GetSpecialValueFor("crit_mult") + local ____opt_10 = modifier_stacking_crit:GetForUnit(self:GetParent()) + if ____opt_10 ~= nil then + ____opt_10:UpdateCrit( + 0, + 0, + chance, + mult, + "coup_de_grace" + ) + end +end +function modifier_phantom_assassin_coup_de_grace_custom.prototype.IsHidden(self) + return true +end +function modifier_phantom_assassin_coup_de_grace_custom.prototype.IsDebuff(self) + return false +end +function modifier_phantom_assassin_coup_de_grace_custom.prototype.IsPurgable(self) + return false +end +modifier_phantom_assassin_coup_de_grace_custom = __TS__Decorate( + modifier_phantom_assassin_coup_de_grace_custom, + modifier_phantom_assassin_coup_de_grace_custom, + {registerModifier(nil)}, + {kind = "class", name = "modifier_phantom_assassin_coup_de_grace_custom"} +) +____exports.modifier_phantom_assassin_coup_de_grace_custom = modifier_phantom_assassin_coup_de_grace_custom +____exports.modifier_phantom_assassin_coup_de_grace_custom_active = __TS__Class() +local modifier_phantom_assassin_coup_de_grace_custom_active = ____exports.modifier_phantom_assassin_coup_de_grace_custom_active +modifier_phantom_assassin_coup_de_grace_custom_active.name = "modifier_phantom_assassin_coup_de_grace_custom_active" +modifier_phantom_assassin_coup_de_grace_custom_active.____file_path = "scripts/vscripts/abilities/heroes/phantom_assassin/ability_phantom_assassin_coup_de_grace_custom.lua" +__TS__ClassExtends(modifier_phantom_assassin_coup_de_grace_custom_active, BaseModifier) +function modifier_phantom_assassin_coup_de_grace_custom_active.prototype.IsHidden(self) + return false +end +function modifier_phantom_assassin_coup_de_grace_custom_active.prototype.OnDestroy(self) + if IsClient() then + return + end + local ability = self:GetAbility() + if ability ~= nil and ability ~= nil then + do + pcall(function() + local ____opt_12 = modifier_stacking_crit:GetForUnit(self:GetParent()) + if ____opt_12 ~= nil then + ____opt_12:GuaranteeCritUntilDestroy(false) + end + ability:SetActivated(true) + ability:StartCooldown(ability:GetSpecialValueFor("ability_cooldown")) + end) + end + end +end +function modifier_phantom_assassin_coup_de_grace_custom_active.prototype.DeclareFunctions(self) + return {MODIFIER_EVENT_ON_ATTACK_LANDED} +end +function modifier_phantom_assassin_coup_de_grace_custom_active.prototype.OnAttackLanded(self, event) + local target = event.target + local caster = event.attacker + if caster == self:GetParent() and target:IsAlive() then + target:AddNewModifier( + self:GetParent(), + self:GetAbility(), + "modifier_phantom_assassin_coup_de_grace_custom_armor_reduction", + {duration = 4} + ) + end +end +modifier_phantom_assassin_coup_de_grace_custom_active = __TS__Decorate( + modifier_phantom_assassin_coup_de_grace_custom_active, + modifier_phantom_assassin_coup_de_grace_custom_active, + {registerModifier(nil)}, + {kind = "class", name = "modifier_phantom_assassin_coup_de_grace_custom_active"} +) +____exports.modifier_phantom_assassin_coup_de_grace_custom_active = modifier_phantom_assassin_coup_de_grace_custom_active +____exports.modifier_phantom_assassin_coup_de_grace_custom_armor_reduction = __TS__Class() +local modifier_phantom_assassin_coup_de_grace_custom_armor_reduction = ____exports.modifier_phantom_assassin_coup_de_grace_custom_armor_reduction +modifier_phantom_assassin_coup_de_grace_custom_armor_reduction.name = "modifier_phantom_assassin_coup_de_grace_custom_armor_reduction" +modifier_phantom_assassin_coup_de_grace_custom_armor_reduction.____file_path = "scripts/vscripts/abilities/heroes/phantom_assassin/ability_phantom_assassin_coup_de_grace_custom.lua" +__TS__ClassExtends(modifier_phantom_assassin_coup_de_grace_custom_armor_reduction, BaseModifier) +function modifier_phantom_assassin_coup_de_grace_custom_armor_reduction.prototype.____constructor(self, ...) + BaseModifier.prototype.____constructor(self, ...) + self.armor_reduction = -0 +end +function modifier_phantom_assassin_coup_de_grace_custom_armor_reduction.prototype.IsHidden(self) + return false +end +function modifier_phantom_assassin_coup_de_grace_custom_armor_reduction.prototype.IsDebuff(self) + return true +end +function modifier_phantom_assassin_coup_de_grace_custom_armor_reduction.prototype.IsPurgable(self) + return true +end +function modifier_phantom_assassin_coup_de_grace_custom_armor_reduction.prototype.GetAttributes(self) + return MODIFIER_ATTRIBUTE_MULTIPLE +end +function modifier_phantom_assassin_coup_de_grace_custom_armor_reduction.prototype.DeclareFunctions(self) + return {MODIFIER_PROPERTY_PHYSICAL_ARMOR_BONUS} +end +function modifier_phantom_assassin_coup_de_grace_custom_armor_reduction.prototype.GetModifierPhysicalArmorBonus(self) + local ability = self:GetAbility() + if not ability then + return 0 + end + local strip = ability:GetSpecialValueFor("armor_reduction") + local pct = ability:GetSpecialValueFor("armor_reduction_agility_pct") / 100 + local hero = self:GetCaster():GetAgility() + return -(strip + hero * pct) +end +modifier_phantom_assassin_coup_de_grace_custom_armor_reduction = __TS__Decorate( + modifier_phantom_assassin_coup_de_grace_custom_armor_reduction, + modifier_phantom_assassin_coup_de_grace_custom_armor_reduction, + {registerModifier(nil)}, + {kind = "class", name = "modifier_phantom_assassin_coup_de_grace_custom_armor_reduction"} +) +____exports.modifier_phantom_assassin_coup_de_grace_custom_armor_reduction = modifier_phantom_assassin_coup_de_grace_custom_armor_reduction +return ____exports diff --git a/scripts/vscripts/abilities/heroes/phantom_assassin/ability_phantom_assassin_phantom_bash_custom.lua b/scripts/vscripts/abilities/heroes/phantom_assassin/ability_phantom_assassin_phantom_bash_custom.lua new file mode 100644 index 0000000..3d64968 --- /dev/null +++ b/scripts/vscripts/abilities/heroes/phantom_assassin/ability_phantom_assassin_phantom_bash_custom.lua @@ -0,0 +1,115 @@ +local ____lualib = require("lualib_bundle") +local __TS__Class = ____lualib.__TS__Class +local __TS__ClassExtends = ____lualib.__TS__ClassExtends +local __TS__Decorate = ____lualib.__TS__Decorate +local ____exports = {} +local ____dota_ts_adapter = require("lib.dota_ts_adapter") +local BaseAbility = ____dota_ts_adapter.BaseAbility +local BaseModifier = ____dota_ts_adapter.BaseModifier +local registerAbility = ____dota_ts_adapter.registerAbility +local registerModifier = ____dota_ts_adapter.registerModifier +____exports.ability_phantom_assassin_phantom_bash_custom = __TS__Class() +local ability_phantom_assassin_phantom_bash_custom = ____exports.ability_phantom_assassin_phantom_bash_custom +ability_phantom_assassin_phantom_bash_custom.name = "ability_phantom_assassin_phantom_bash_custom" +ability_phantom_assassin_phantom_bash_custom.____file_path = "scripts/vscripts/abilities/heroes/phantom_assassin/ability_phantom_assassin_phantom_bash_custom.lua" +__TS__ClassExtends(ability_phantom_assassin_phantom_bash_custom, BaseAbility) +function ability_phantom_assassin_phantom_bash_custom.prototype.GetIntrinsicModifierName(self) + return "modifier_phantom_assassin_phantom_bash_custom" +end +function ability_phantom_assassin_phantom_bash_custom.prototype.GetCooldown(self, level) + return self:GetSpecialValueFor("cooldown") - self:GetSpecialValueFor("bash_cd_level") * (self:GetCaster():GetLevel() - 1) +end +ability_phantom_assassin_phantom_bash_custom = __TS__Decorate( + ability_phantom_assassin_phantom_bash_custom, + ability_phantom_assassin_phantom_bash_custom, + {registerAbility(nil)}, + {kind = "class", name = "ability_phantom_assassin_phantom_bash_custom"} +) +____exports.ability_phantom_assassin_phantom_bash_custom = ability_phantom_assassin_phantom_bash_custom +____exports.modifier_phantom_assassin_phantom_bash_custom = __TS__Class() +local modifier_phantom_assassin_phantom_bash_custom = ____exports.modifier_phantom_assassin_phantom_bash_custom +modifier_phantom_assassin_phantom_bash_custom.name = "modifier_phantom_assassin_phantom_bash_custom" +modifier_phantom_assassin_phantom_bash_custom.____file_path = "scripts/vscripts/abilities/heroes/phantom_assassin/ability_phantom_assassin_phantom_bash_custom.lua" +__TS__ClassExtends(modifier_phantom_assassin_phantom_bash_custom, BaseModifier) +function modifier_phantom_assassin_phantom_bash_custom.prototype.IsHidden(self) + return true +end +function modifier_phantom_assassin_phantom_bash_custom.prototype.IsPurgable(self) + return false +end +function modifier_phantom_assassin_phantom_bash_custom.prototype.DeclareFunctions(self) + return {MODIFIER_EVENT_ON_ATTACK_LANDED} +end +function modifier_phantom_assassin_phantom_bash_custom.prototype.OnAttackLanded(self, event) + if not IsServer() then + return + end + if event.attacker:IsIllusion() then + return + end + if event.attacker ~= self:GetParent() then + return + end + if self:GetParent():PassivesDisabled() then + return + end + local ability = self:GetAbility() + if not ability or ability:IsCooldownReady() == false then + return + end + local target = event.target + if not target or not target:IsAlive() then + return + end + self:GetAbility():GetCaster():AddNewModifier( + self:GetParent(), + ability, + "modifier_cooldown", + {duration = self:GetAbility():GetCooldown(self:GetAbility():GetLevel())} + ) + target:AddNewModifier( + self:GetParent(), + ability, + "modifier_bashed", + {duration = 0.75} + ) + EmitSoundOn("DOTA_Item.SkullBasher", target) + local bashFx = ParticleManager:CreateParticle("particles/generic_gameplay/generic_minibash.vpcf", PATTACH_CUSTOMORIGIN, target) + ParticleManager:SetParticleControlEnt( + bashFx, + 0, + target, + PATTACH_POINT, + "attach_hitloc", + target:GetAbsOrigin(), + true + ) + ParticleManager:ReleaseParticleIndex(bashFx) + ability:StartCooldown(ability:GetCooldown(ability:GetLevel())) +end +modifier_phantom_assassin_phantom_bash_custom = __TS__Decorate( + modifier_phantom_assassin_phantom_bash_custom, + modifier_phantom_assassin_phantom_bash_custom, + {registerModifier(nil)}, + {kind = "class", name = "modifier_phantom_assassin_phantom_bash_custom"} +) +____exports.modifier_phantom_assassin_phantom_bash_custom = modifier_phantom_assassin_phantom_bash_custom +____exports.modifier_cooldown = __TS__Class() +local modifier_cooldown = ____exports.modifier_cooldown +modifier_cooldown.name = "modifier_cooldown" +modifier_cooldown.____file_path = "scripts/vscripts/abilities/heroes/phantom_assassin/ability_phantom_assassin_phantom_bash_custom.lua" +__TS__ClassExtends(modifier_cooldown, BaseModifier) +function modifier_cooldown.prototype.IsHidden(self) + return false +end +function modifier_cooldown.prototype.IsDebuff(self) + return true +end +modifier_cooldown = __TS__Decorate( + modifier_cooldown, + modifier_cooldown, + {registerModifier(nil)}, + {kind = "class", name = "modifier_cooldown"} +) +____exports.modifier_cooldown = modifier_cooldown +return ____exports diff --git a/scripts/vscripts/abilities/heroes/phantom_assassin/ability_phantom_assassin_phantom_strike_custom.lua b/scripts/vscripts/abilities/heroes/phantom_assassin/ability_phantom_assassin_phantom_strike_custom.lua new file mode 100644 index 0000000..82c5477 --- /dev/null +++ b/scripts/vscripts/abilities/heroes/phantom_assassin/ability_phantom_assassin_phantom_strike_custom.lua @@ -0,0 +1,169 @@ +local ____lualib = require("lualib_bundle") +local __TS__Class = ____lualib.__TS__Class +local __TS__ClassExtends = ____lualib.__TS__ClassExtends +local __TS__Decorate = ____lualib.__TS__Decorate +local ____exports = {} +local ____dota_ts_adapter = require("lib.dota_ts_adapter") +local BaseAbility = ____dota_ts_adapter.BaseAbility +local BaseModifier = ____dota_ts_adapter.BaseModifier +local registerAbility = ____dota_ts_adapter.registerAbility +local registerModifier = ____dota_ts_adapter.registerModifier +____exports.ability_phantom_assassin_phantom_strike_custom = __TS__Class() +local ability_phantom_assassin_phantom_strike_custom = ____exports.ability_phantom_assassin_phantom_strike_custom +ability_phantom_assassin_phantom_strike_custom.name = "ability_phantom_assassin_phantom_strike_custom" +ability_phantom_assassin_phantom_strike_custom.____file_path = "scripts/vscripts/abilities/heroes/phantom_assassin/ability_phantom_assassin_phantom_strike_custom.lua" +__TS__ClassExtends(ability_phantom_assassin_phantom_strike_custom, BaseAbility) +function ability_phantom_assassin_phantom_strike_custom.prototype.GetCooldown(self, level) + if self:GetSpecialValueFor("crit_chance") == 0 then + return self:GetSpecialValueFor("cooldown") + end + return 0 +end +function ability_phantom_assassin_phantom_strike_custom.prototype.GetBehavior(self) + if self:GetSpecialValueFor("crit_chance") > 0 then + return bit.bor(DOTA_ABILITY_BEHAVIOR_UNIT_TARGET, DOTA_ABILITY_BEHAVIOR_POINT) + end + return DOTA_ABILITY_BEHAVIOR_UNIT_TARGET +end +function ability_phantom_assassin_phantom_strike_custom.prototype.OnSpellStart(self) + local caster = self:GetCaster() + local target = self:GetCursorTarget() + local duration = self:GetSpecialValueFor("bonus_duration") + EmitSoundOn("Hero_PhantomAssassin.Strike.Start", caster) + local particleStart = ParticleManager:CreateParticle("particles/units/heroes/hero_phantom_assassin/phantom_assassin_phantom_strike_start.vpcf", PATTACH_CUSTOMORIGIN, caster) + ParticleManager:SetParticleControl( + particleStart, + 0, + caster:GetAbsOrigin() + ) + ParticleManager:ReleaseParticleIndex(particleStart) + if target then + FindClearSpaceForUnit( + caster, + target:GetAbsOrigin(), + true + ) + if target and IsValidEntity(target) and target:IsAlive() and caster and IsValidEntity(caster) and caster:IsAlive() then + ExecuteOrderFromTable({ + UnitIndex = caster:entindex(), + OrderType = DOTA_UNIT_ORDER_ATTACK_TARGET, + TargetIndex = target:entindex(), + Queue = false + }) + end + caster:AddNewModifier(caster, self, "modifier_phantom_assassin_phantom_strike_custom", {duration = duration}) + else + local point = self:GetCursorPosition() + FindClearSpaceForUnit(caster, point, true) + caster:AddNewModifier(caster, self, "modifier_phantom_assassin_phantom_strike_custom", {duration = duration}) + end + EmitSoundOn("Hero_PhantomAssassin.Strike.End", caster) +end +function ability_phantom_assassin_phantom_strike_custom.prototype.CastFilterResultTarget(self, target) + local caster = self:GetCaster() + if target == caster then + return UF_FAIL_INVALID_LOCATION + end + return UF_SUCCESS +end +function ability_phantom_assassin_phantom_strike_custom.prototype.GetCustomCastErrorTarget(self, target) + local caster = self:GetCaster() + if target == caster then + return "dota_hud_error_cant_cast_on_self" + end + return "" +end +ability_phantom_assassin_phantom_strike_custom = __TS__Decorate( + ability_phantom_assassin_phantom_strike_custom, + ability_phantom_assassin_phantom_strike_custom, + {registerAbility(nil)}, + {kind = "class", name = "ability_phantom_assassin_phantom_strike_custom"} +) +____exports.ability_phantom_assassin_phantom_strike_custom = ability_phantom_assassin_phantom_strike_custom +____exports.modifier_phantom_assassin_phantom_strike_custom = __TS__Class() +local modifier_phantom_assassin_phantom_strike_custom = ____exports.modifier_phantom_assassin_phantom_strike_custom +modifier_phantom_assassin_phantom_strike_custom.name = "modifier_phantom_assassin_phantom_strike_custom" +modifier_phantom_assassin_phantom_strike_custom.____file_path = "scripts/vscripts/abilities/heroes/phantom_assassin/ability_phantom_assassin_phantom_strike_custom.lua" +__TS__ClassExtends(modifier_phantom_assassin_phantom_strike_custom, BaseModifier) +function modifier_phantom_assassin_phantom_strike_custom.prototype.IsPurgable(self) + return true +end +function modifier_phantom_assassin_phantom_strike_custom.prototype.OnCreated(self) + if IsClient() then + return + end + local particle = ParticleManager:CreateParticle( + "particles/units/heroes/hero_phantom_assassin/phantom_assassin_phantom_strike_end.vpcf", + PATTACH_ABSORIGIN_FOLLOW, + self:GetParent() + ) + ParticleManager:ReleaseParticleIndex(particle) + local ____opt_0 = self:GetAbility() + local crit_chance = ____opt_0 and ____opt_0:GetSpecialValueFor("crit_chance") + local ____opt_2 = self:GetAbility() + local crit_mult = ____opt_2 and ____opt_2:GetSpecialValueFor("crit_mult") + local ____opt_4 = self:GetAbility() + local lifesteal = ____opt_4 and ____opt_4:GetSpecialValueFor("lifesteal_percent") or 0 + if lifesteal > 0 then + addPhysicalVampirism( + nil, + self:GetCaster(), + lifesteal + ) + end + if crit_chance <= 0 and crit_mult <= 0 then + return + end + if not IsServer() then + return + end + local stackingCritMod = self:GetParent():FindModifierByName("modifier_stacking_crit") + if stackingCritMod then + stackingCritMod:AddCustomCrit(crit_chance, crit_mult, "phantom_strike") + end +end +function modifier_phantom_assassin_phantom_strike_custom.prototype.OnDestroy(self) + if IsClient() then + return + end + local ____opt_6 = self:GetAbility() + local crit_chance = ____opt_6 and ____opt_6:GetSpecialValueFor("crit_chance") + local ____opt_8 = self:GetAbility() + local crit_mult = ____opt_8 and ____opt_8:GetSpecialValueFor("crit_mult") + local ____opt_10 = self:GetAbility() + local lifesteal = ____opt_10 and ____opt_10:GetSpecialValueFor("lifesteal_percent") or 0 + if lifesteal > 0 then + reducePhysicalVampirism( + nil, + self:GetCaster(), + lifesteal + ) + end + if crit_chance <= 0 and crit_mult <= 0 then + return + end + local parent = self:GetParent() + if not parent or not IsValidEntity(parent) then + return + end + local stackingCritModifier = parent:FindModifierByName("modifier_stacking_crit") + if not stackingCritModifier then + return + end + stackingCritModifier:RemoveCrit("phantom_strike") +end +function modifier_phantom_assassin_phantom_strike_custom.prototype.DeclareFunctions(self) + return {MODIFIER_PROPERTY_ATTACKSPEED_BONUS_CONSTANT} +end +function modifier_phantom_assassin_phantom_strike_custom.prototype.GetModifierAttackSpeedBonus_Constant(self) + local ____opt_12 = self:GetAbility() + return ____opt_12 and ____opt_12:GetSpecialValueFor("attack_speed_bonus") or 0 +end +modifier_phantom_assassin_phantom_strike_custom = __TS__Decorate( + modifier_phantom_assassin_phantom_strike_custom, + modifier_phantom_assassin_phantom_strike_custom, + {registerModifier(nil)}, + {kind = "class", name = "modifier_phantom_assassin_phantom_strike_custom"} +) +____exports.modifier_phantom_assassin_phantom_strike_custom = modifier_phantom_assassin_phantom_strike_custom +return ____exports diff --git a/scripts/vscripts/abilities/heroes/phantom_assassin/ability_phantom_assassin_stifling_dagger_custom.lua b/scripts/vscripts/abilities/heroes/phantom_assassin/ability_phantom_assassin_stifling_dagger_custom.lua new file mode 100644 index 0000000..c6737a6 --- /dev/null +++ b/scripts/vscripts/abilities/heroes/phantom_assassin/ability_phantom_assassin_stifling_dagger_custom.lua @@ -0,0 +1,264 @@ +local ____lualib = require("lualib_bundle") +local __TS__Class = ____lualib.__TS__Class +local __TS__ClassExtends = ____lualib.__TS__ClassExtends +local __TS__ArrayFindIndex = ____lualib.__TS__ArrayFindIndex +local __TS__ArraySplice = ____lualib.__TS__ArraySplice +local __TS__Decorate = ____lualib.__TS__Decorate +local __TS__ArrayIndexOf = ____lualib.__TS__ArrayIndexOf +local ____exports = {} +local ____dota_ts_adapter = require("lib.dota_ts_adapter") +local BaseModifier = ____dota_ts_adapter.BaseModifier +local registerModifier = ____dota_ts_adapter.registerModifier +local ____dota_ts_adapter = require("lib.dota_ts_adapter") +local BaseAbility = ____dota_ts_adapter.BaseAbility +local registerAbility = ____dota_ts_adapter.registerAbility +____exports.ability_phantom_assassin_stifling_dagger_custom = __TS__Class() +local ability_phantom_assassin_stifling_dagger_custom = ____exports.ability_phantom_assassin_stifling_dagger_custom +ability_phantom_assassin_stifling_dagger_custom.name = "ability_phantom_assassin_stifling_dagger_custom" +ability_phantom_assassin_stifling_dagger_custom.____file_path = "scripts/vscripts/abilities/heroes/phantom_assassin/ability_phantom_assassin_stifling_dagger_custom.lua" +__TS__ClassExtends(ability_phantom_assassin_stifling_dagger_custom, BaseAbility) +function ability_phantom_assassin_stifling_dagger_custom.prototype.____constructor(self, ...) + BaseAbility.prototype.____constructor(self, ...) + self.particle_dagger = "particles/units/heroes/hero_phantom_assassin/phantom_assassin_stifling_dagger.vpcf" + self.particle_hit = "particles/units/heroes/hero_phantom_assassin/phantom_assassin_stifling_dagger_explosion.vpcf" + self.sound_cast = "Hero_PhantomAssassin.Dagger.Cast" + self.sound_impact = "Hero_PhantomAssassin.Dagger.Target" +end +function ability_phantom_assassin_stifling_dagger_custom.prototype.GetIntrinsicModifierName(self) + return "modifier_phantom_assassin_stifling_dagger_custom_buff" +end +function ability_phantom_assassin_stifling_dagger_custom.prototype.OnSpellStart(self) + local caster = self:GetCaster() + local target = self:GetCursorTarget() + local projectile_speed = self:GetSpecialValueFor("dagger_speed") + local base_cast_range = self:GetCastRange( + caster:GetAbsOrigin(), + target + ) + local cast_range_bonus = caster:GetCastRangeBonus() + local cast_range = base_cast_range + cast_range_bonus + ProjectileManager:CreateTrackingProjectile({ + Ability = self, + EffectName = self.particle_dagger, + Source = caster, + Target = target, + iMoveSpeed = projectile_speed, + bDodgeable = true, + bVisibleToEnemies = true + }) + local enemies = FindUnitsInRadius( + caster:GetTeamNumber(), + caster:GetAbsOrigin(), + nil, + cast_range, + self:GetAbilityTargetTeam(), + self:GetAbilityTargetType(), + DOTA_UNIT_TARGET_FLAG_NONE, + FIND_CLOSEST, + false + ) + local max_targets = self:GetSpecialValueFor("max_targets") + local targetIndex = __TS__ArrayFindIndex( + enemies, + function(____, enemy) return enemy == target end + ) + if targetIndex ~= -1 then + __TS__ArraySplice(enemies, targetIndex, 1) + end + do + local i = 0 + while i < max_targets and #enemies > 0 do + local randomIndex = RandomInt(0, #enemies - 1) + local randomTarget = enemies[randomIndex + 1] + ProjectileManager:CreateTrackingProjectile({ + Ability = self, + EffectName = self.particle_dagger, + Source = caster, + Target = randomTarget, + iMoveSpeed = projectile_speed, + bDodgeable = true, + bVisibleToEnemies = true + }) + __TS__ArraySplice(enemies, randomIndex, 1) + i = i + 1 + end + end + EmitSoundOn(self.sound_cast, caster) +end +function ability_phantom_assassin_stifling_dagger_custom.prototype.OnProjectileHit(self, target, location) + if not target then + return true + end + local caster = self:GetCaster() + local slow_duration = self:GetSpecialValueFor("slow_duration") + local modifier = caster:AddNewModifier(caster, self, "modifier_phantom_assassin_stifling_dagger_damage_custom", {}) + self:GetCaster():PerformAttack( + target, + true, + true, + true, + false, + false, + false, + true + ) + if modifier ~= nil then + modifier:Destroy() + end + target:AddNewModifier(caster, self, "modifier_phantom_assassin_stifling_dagger_slow_custom", {duration = slow_duration}) + EmitSoundOn(self.sound_impact, target) + local particle = ParticleManager:CreateParticle(self.particle_hit, PATTACH_ABSORIGIN_FOLLOW, target) + ParticleManager:ReleaseParticleIndex(particle) + return true +end +ability_phantom_assassin_stifling_dagger_custom = __TS__Decorate( + ability_phantom_assassin_stifling_dagger_custom, + ability_phantom_assassin_stifling_dagger_custom, + {registerAbility(nil)}, + {kind = "class", name = "ability_phantom_assassin_stifling_dagger_custom"} +) +____exports.ability_phantom_assassin_stifling_dagger_custom = ability_phantom_assassin_stifling_dagger_custom +____exports.modifier_phantom_assassin_stifling_dagger_slow_custom = __TS__Class() +local modifier_phantom_assassin_stifling_dagger_slow_custom = ____exports.modifier_phantom_assassin_stifling_dagger_slow_custom +modifier_phantom_assassin_stifling_dagger_slow_custom.name = "modifier_phantom_assassin_stifling_dagger_slow_custom" +modifier_phantom_assassin_stifling_dagger_slow_custom.____file_path = "scripts/vscripts/abilities/heroes/phantom_assassin/ability_phantom_assassin_stifling_dagger_custom.lua" +__TS__ClassExtends(modifier_phantom_assassin_stifling_dagger_slow_custom, BaseModifier) +function modifier_phantom_assassin_stifling_dagger_slow_custom.prototype.DeclareFunctions(self) + return {MODIFIER_PROPERTY_MOVESPEED_BONUS_PERCENTAGE} +end +function modifier_phantom_assassin_stifling_dagger_slow_custom.prototype.GetEffectName(self) + return "particles/econ/items/phantom_assassin/phantom_assassin_arcana_elder_smith/phantom_assassin_stifling_dagger_debuff_e_arcana.vpcf" +end +function modifier_phantom_assassin_stifling_dagger_slow_custom.prototype.GetEffectAttachType(self) + return PATTACH_ABSORIGIN_FOLLOW +end +function modifier_phantom_assassin_stifling_dagger_slow_custom.prototype.GetModifierMoveSpeedBonus_Percentage(self) + return self:GetAbility():GetSpecialValueFor("movement_slow") +end +modifier_phantom_assassin_stifling_dagger_slow_custom = __TS__Decorate( + modifier_phantom_assassin_stifling_dagger_slow_custom, + modifier_phantom_assassin_stifling_dagger_slow_custom, + {registerModifier(nil)}, + {kind = "class", name = "modifier_phantom_assassin_stifling_dagger_slow_custom"} +) +____exports.modifier_phantom_assassin_stifling_dagger_slow_custom = modifier_phantom_assassin_stifling_dagger_slow_custom +____exports.modifier_phantom_assassin_stifling_dagger_damage_custom = __TS__Class() +local modifier_phantom_assassin_stifling_dagger_damage_custom = ____exports.modifier_phantom_assassin_stifling_dagger_damage_custom +modifier_phantom_assassin_stifling_dagger_damage_custom.name = "modifier_phantom_assassin_stifling_dagger_damage_custom" +modifier_phantom_assassin_stifling_dagger_damage_custom.____file_path = "scripts/vscripts/abilities/heroes/phantom_assassin/ability_phantom_assassin_stifling_dagger_custom.lua" +__TS__ClassExtends(modifier_phantom_assassin_stifling_dagger_damage_custom, BaseModifier) +function modifier_phantom_assassin_stifling_dagger_damage_custom.prototype.____constructor(self, ...) + BaseModifier.prototype.____constructor(self, ...) + self.attack_factor = 0 + self.damage = 0 +end +function modifier_phantom_assassin_stifling_dagger_damage_custom.prototype.OnCreated(self, params) + self.attack_factor = self:GetAbility():GetSpecialValueFor("attack_factor") + self.damage = self:GetAbility():GetSpecialValueFor("damage") +end +function modifier_phantom_assassin_stifling_dagger_damage_custom.prototype.DeclareFunctions(self) + return {MODIFIER_PROPERTY_DAMAGEOUTGOING_PERCENTAGE, MODIFIER_PROPERTY_PREATTACK_BONUS_DAMAGE} +end +function modifier_phantom_assassin_stifling_dagger_damage_custom.prototype.GetModifierDamageOutgoing_Percentage(self) + if IsServer() then + return self.attack_factor + end + return 0 +end +function modifier_phantom_assassin_stifling_dagger_damage_custom.prototype.GetModifierPreAttack_BonusDamage(self) + if IsServer() then + return self.damage + end + return 0 +end +modifier_phantom_assassin_stifling_dagger_damage_custom = __TS__Decorate( + modifier_phantom_assassin_stifling_dagger_damage_custom, + modifier_phantom_assassin_stifling_dagger_damage_custom, + {registerModifier(nil)}, + {kind = "class", name = "modifier_phantom_assassin_stifling_dagger_damage_custom"} +) +____exports.modifier_phantom_assassin_stifling_dagger_damage_custom = modifier_phantom_assassin_stifling_dagger_damage_custom +____exports.modifier_phantom_assassin_stifling_dagger_custom_buff = __TS__Class() +local modifier_phantom_assassin_stifling_dagger_custom_buff = ____exports.modifier_phantom_assassin_stifling_dagger_custom_buff +modifier_phantom_assassin_stifling_dagger_custom_buff.name = "modifier_phantom_assassin_stifling_dagger_custom_buff" +modifier_phantom_assassin_stifling_dagger_custom_buff.____file_path = "scripts/vscripts/abilities/heroes/phantom_assassin/ability_phantom_assassin_stifling_dagger_custom.lua" +__TS__ClassExtends(modifier_phantom_assassin_stifling_dagger_custom_buff, BaseModifier) +function modifier_phantom_assassin_stifling_dagger_custom_buff.prototype.IsHidden(self) + return true +end +function modifier_phantom_assassin_stifling_dagger_custom_buff.prototype.DeclareFunctions(self) + return {MODIFIER_EVENT_ON_ATTACK_LANDED} +end +function modifier_phantom_assassin_stifling_dagger_custom_buff.prototype.IsPurgable(self) + return false +end +function modifier_phantom_assassin_stifling_dagger_custom_buff.prototype.OnAttackLanded(self, params) + local ability = self:GetAbility() + if IsServer() and not self:GetParent():PassivesDisabled() and self:GetParent() == params.attacker and ability and RollPseudoRandomPercentage( + ability:GetSpecialValueFor("chance"), + 3, + self:GetParent() + ) and HasTalent( + nil, + self:GetParent(), + "special_bonus_unique_assassin_stifling_dagger_chance_again" + ) then + local target = params.target + if not target then + return + end + local caster = self:GetParent() + local base_cast_range = ability:GetCastRange( + caster:GetAbsOrigin(), + target + ) + local cast_range_bonus = caster:GetCastRangeBonus() + local cast_range = base_cast_range + cast_range_bonus + local enemies = FindUnitsInRadius( + caster:GetTeamNumber(), + caster:GetAbsOrigin(), + nil, + cast_range, + ability:GetAbilityTargetTeam(), + ability:GetAbilityTargetType(), + DOTA_UNIT_TARGET_FLAG_NONE, + FIND_CLOSEST, + false + ) + __TS__ArraySplice( + enemies, + __TS__ArrayIndexOf(enemies, target), + 1 + ) + local max_targets = 1 + self:GetAbility():GetSpecialValueFor("max_targets") + while #enemies > max_targets do + __TS__ArraySplice( + enemies, + RandomInt(0, #enemies - 1), + 1 + ) + end + if #enemies > 0 then + local random_target = enemies[RandomInt(0, #enemies - 1) + 1] + if random_target ~= nil then + ProjectileManager:CreateTrackingProjectile({ + Ability = ability, + EffectName = "particles/units/heroes/hero_phantom_assassin/phantom_assassin_stifling_dagger.vpcf", + Source = caster, + Target = random_target, + iMoveSpeed = ability:GetSpecialValueFor("dagger_speed"), + bDodgeable = true, + bVisibleToEnemies = true + }) + end + end + end +end +modifier_phantom_assassin_stifling_dagger_custom_buff = __TS__Decorate( + modifier_phantom_assassin_stifling_dagger_custom_buff, + modifier_phantom_assassin_stifling_dagger_custom_buff, + {registerModifier(nil)}, + {kind = "class", name = "modifier_phantom_assassin_stifling_dagger_custom_buff"} +) +____exports.modifier_phantom_assassin_stifling_dagger_custom_buff = modifier_phantom_assassin_stifling_dagger_custom_buff +return ____exports diff --git a/scripts/vscripts/abilities/heroes/pudge/ability_pudge_dismember_custom.lua b/scripts/vscripts/abilities/heroes/pudge/ability_pudge_dismember_custom.lua new file mode 100644 index 0000000..50d42a1 --- /dev/null +++ b/scripts/vscripts/abilities/heroes/pudge/ability_pudge_dismember_custom.lua @@ -0,0 +1,295 @@ +local ____lualib = require("lualib_bundle") +local __TS__Class = ____lualib.__TS__Class +local __TS__ClassExtends = ____lualib.__TS__ClassExtends +local __TS__Decorate = ____lualib.__TS__Decorate +local ____exports = {} +local ____dota_ts_adapter = require("lib.dota_ts_adapter") +local BaseAbility = ____dota_ts_adapter.BaseAbility +local BaseModifier = ____dota_ts_adapter.BaseModifier +local registerAbility = ____dota_ts_adapter.registerAbility +local registerModifier = ____dota_ts_adapter.registerModifier +local ____modifier_general_hunger = require("abilities.modifiers.modifier_general_hunger") +local modifier_general_hunger = ____modifier_general_hunger.modifier_general_hunger +local ____ability_pudge_flesh_heap_custom = require("abilities.heroes.pudge.ability_pudge_flesh_heap_custom") +local modifier_pudge_flesh_heap_custom = ____ability_pudge_flesh_heap_custom.modifier_pudge_flesh_heap_custom +____exports.ability_pudge_dismember_custom = __TS__Class() +local ability_pudge_dismember_custom = ____exports.ability_pudge_dismember_custom +ability_pudge_dismember_custom.name = "ability_pudge_dismember_custom" +ability_pudge_dismember_custom.____file_path = "scripts/vscripts/abilities/heroes/pudge/ability_pudge_dismember_custom.lua" +__TS__ClassExtends(ability_pudge_dismember_custom, BaseAbility) +function ability_pudge_dismember_custom.prototype.____constructor(self, ...) + BaseAbility.prototype.____constructor(self, ...) + self.pulseAccumulator = 0 + self.channelTargetPoint = Vector(0, 0, 0) +end +function ability_pudge_dismember_custom.prototype.Precache(self, context) + PrecacheResource("particle", "particles/econ/items/pudge/pudge_arcana/pudge_arcana_dismember_default.vpcf", context) +end +function ability_pudge_dismember_custom.prototype.GetBehavior(self) + return bit.bor( + bit.bor(DOTA_ABILITY_BEHAVIOR_POINT, DOTA_ABILITY_BEHAVIOR_CHANNELLED), + DOTA_ABILITY_BEHAVIOR_AOE + ) +end +function ability_pudge_dismember_custom.prototype.GetCastRange(self, location, target) + return self:GetSpecialValueFor("cast_range") +end +function ability_pudge_dismember_custom.prototype.GetChannelTime(self) + return self:GetSpecialValueFor("channel_time") +end +function ability_pudge_dismember_custom.prototype.GetAOERadius(self) + return self:GetSpecialValueFor("width") +end +function ability_pudge_dismember_custom.prototype.OnSpellStart(self) + local caster = self:GetCaster() + self.channelTargetPoint = self:GetCursorPosition() + self.pulseAccumulator = 0 + caster:AddNewModifier( + caster, + self, + ____exports.modifier_pudge_dismember_growth_custom.name, + {duration = self:GetChannelTime() + 0.1} + ) + EmitSoundOn("Hero_Pudge.DismemberSwings", caster) +end +function ability_pudge_dismember_custom.prototype.OnChannelThink(self, flInterval) + if not IsServer() then + return + end + local caster = self:GetCaster() + local maxRange = self:GetCastRange( + caster:GetAbsOrigin(), + nil + ) + local width = self:GetSpecialValueFor("width") + local pullSpeedPerSec = self:GetSpecialValueFor("pull_speed") + local pulseInterval = self:GetSpecialValueFor("pulse_interval") + local toPoint = self.channelTargetPoint - caster:GetAbsOrigin() + toPoint.z = 0 + if toPoint:Length2D() < 1 then + toPoint = caster:GetForwardVector() * maxRange + end + local clampedDistance = math.min( + toPoint:Length2D(), + maxRange + ) + local centerPoint = caster:GetAbsOrigin() + toPoint:Normalized() * clampedDistance + local enemies = FindUnitsInRadius( + caster:GetTeamNumber(), + centerPoint, + nil, + width, + DOTA_UNIT_TARGET_TEAM_ENEMY, + bit.bor(DOTA_UNIT_TARGET_HERO, DOTA_UNIT_TARGET_BASIC), + DOTA_UNIT_TARGET_FLAG_MAGIC_IMMUNE_ENEMIES, + FIND_ANY_ORDER, + false + ) + if #enemies == 0 then + caster:InterruptChannel() + return + end + for ____, enemy in ipairs(enemies) do + local delta = centerPoint - enemy:GetAbsOrigin() + delta.z = 0 + local distance = delta:Length2D() + local stopDistance = 60 + if distance > stopDistance then + local move = math.min(distance - stopDistance, pullSpeedPerSec * flInterval) + local newPos = enemy:GetAbsOrigin() + delta:Normalized() * move + enemy:SetAbsOrigin(GetGroundPosition(newPos, enemy)) + GridNav:DestroyTreesAroundPoint(newPos, 80, false) + else + FindClearSpaceForUnit( + enemy, + enemy:GetAbsOrigin(), + true + ) + end + end + self.pulseAccumulator = self.pulseAccumulator + flInterval + if self.pulseAccumulator < pulseInterval then + return + end + self.pulseAccumulator = 0 + EmitSoundOn("Hero_Pudge.Dismember", caster) + for ____, enemy in ipairs(enemies) do + ____exports.modifier_pudge_dismember_custom:apply(enemy, caster, self, {duration = pulseInterval + 0.1}) + self:ApplyDismemberTickDamage(caster, enemy) + end +end +function ability_pudge_dismember_custom.prototype.OnChannelFinish(self, interrupted) + local caster = self:GetCaster() + StopSoundOn("Hero_Pudge.DismemberSwings", caster) + local toPoint = self.channelTargetPoint - caster:GetAbsOrigin() + toPoint.z = 0 + local maxRange = self:GetCastRange( + caster:GetAbsOrigin(), + nil + ) + if toPoint:Length2D() < 1 then + toPoint = caster:GetForwardVector() * maxRange + end + local clampedDistance = math.min( + toPoint:Length2D(), + maxRange + ) + local centerPoint = caster:GetAbsOrigin() + toPoint:Normalized() * clampedDistance + local width = self:GetSpecialValueFor("width") + local enemies = FindUnitsInRadius( + caster:GetTeamNumber(), + centerPoint, + nil, + width, + DOTA_UNIT_TARGET_TEAM_ENEMY, + bit.bor(DOTA_UNIT_TARGET_HERO, DOTA_UNIT_TARGET_BASIC), + DOTA_UNIT_TARGET_FLAG_MAGIC_IMMUNE_ENEMIES, + FIND_ANY_ORDER, + false + ) + for ____, enemy in ipairs(enemies) do + FindClearSpaceForUnit( + enemy, + enemy:GetAbsOrigin(), + true + ) + end +end +function ability_pudge_dismember_custom.prototype.ApplyDismemberTickDamage(self, caster, target) + local interval = self:GetSpecialValueFor("pulse_interval") + local baseDamage = self:GetSpecialValueFor("damage_per_second") + local strMultiplier = self:GetSpecialValueFor("strength_damage_pct") * 0.01 + local maxHpDamagePct = self:GetSpecialValueFor("max_health_damage_pct") * 0.01 + local healPct = self:GetSpecialValueFor("heal_from_damage_pct") * 0.01 + local maxHpDamagePerSec = caster:GetMaxHealth() * maxHpDamagePct + local damagePerSecond = baseDamage + caster:GetStrength() * strMultiplier + maxHpDamagePerSec + local tickDamage = damagePerSecond * interval + ApplyDamage({ + victim = target, + attacker = caster, + damage = tickDamage, + damage_type = DAMAGE_TYPE_MAGICAL, + ability = self + }) + caster:Heal(tickDamage * healPct, self) +end +ability_pudge_dismember_custom = __TS__Decorate( + ability_pudge_dismember_custom, + ability_pudge_dismember_custom, + {registerAbility(nil)}, + {kind = "class", name = "ability_pudge_dismember_custom"} +) +____exports.ability_pudge_dismember_custom = ability_pudge_dismember_custom +____exports.modifier_pudge_dismember_growth_custom = __TS__Class() +local modifier_pudge_dismember_growth_custom = ____exports.modifier_pudge_dismember_growth_custom +modifier_pudge_dismember_growth_custom.name = "modifier_pudge_dismember_growth_custom" +modifier_pudge_dismember_growth_custom.____file_path = "scripts/vscripts/abilities/heroes/pudge/ability_pudge_dismember_custom.lua" +__TS__ClassExtends(modifier_pudge_dismember_growth_custom, BaseModifier) +function modifier_pudge_dismember_growth_custom.prototype.IsHidden(self) + return false +end +function modifier_pudge_dismember_growth_custom.prototype.IsPurgable(self) + return false +end +function modifier_pudge_dismember_growth_custom.prototype.DeclareFunctions(self) + return {MODIFIER_PROPERTY_MODEL_SCALE} +end +function modifier_pudge_dismember_growth_custom.prototype.GetModifierModelScale(self) + local parent = self:GetParent() + if not parent then + return 0 + end + return math.min( + 100, + math.floor(parent:GetMaxHealth() / 120) + ) +end +modifier_pudge_dismember_growth_custom = __TS__Decorate( + modifier_pudge_dismember_growth_custom, + modifier_pudge_dismember_growth_custom, + {registerModifier(nil)}, + {kind = "class", name = "modifier_pudge_dismember_growth_custom"} +) +____exports.modifier_pudge_dismember_growth_custom = modifier_pudge_dismember_growth_custom +____exports.modifier_pudge_dismember_custom = __TS__Class() +local modifier_pudge_dismember_custom = ____exports.modifier_pudge_dismember_custom +modifier_pudge_dismember_custom.name = "modifier_pudge_dismember_custom" +modifier_pudge_dismember_custom.____file_path = "scripts/vscripts/abilities/heroes/pudge/ability_pudge_dismember_custom.lua" +__TS__ClassExtends(modifier_pudge_dismember_custom, BaseModifier) +function modifier_pudge_dismember_custom.prototype.IsDebuff(self) + return true +end +function modifier_pudge_dismember_custom.prototype.IsHidden(self) + return false +end +function modifier_pudge_dismember_custom.prototype.IsPurgable(self) + return false +end +function modifier_pudge_dismember_custom.prototype.GetEffectName(self) + return "particles/econ/items/pudge/pudge_arcana/pudge_arcana_dismember_default.vpcf" +end +function modifier_pudge_dismember_custom.prototype.GetEffectAttachType(self) + return PATTACH_OVERHEAD_FOLLOW +end +function modifier_pudge_dismember_custom.prototype.CheckState(self) + return {[MODIFIER_STATE_INVISIBLE] = false} +end +function modifier_pudge_dismember_custom.prototype.DeclareFunctions(self) + return {MODIFIER_PROPERTY_OVERRIDE_ANIMATION, MODIFIER_EVENT_ON_DEATH} +end +function modifier_pudge_dismember_custom.prototype.GetOverrideAnimation(self) + return ACT_DOTA_FLAIL +end +function modifier_pudge_dismember_custom.prototype.OnDeath(self, event) + if not IsServer() then + return + end + local caster = self:GetCaster() + local target = self:GetParent() + if not caster or not target or not event.unit then + return + end + if event.unit ~= target then + return + end + if target:IsAlive() then + return + end + if target:GetTeamNumber() == caster:GetTeamNumber() then + return + end + if target:IsBuilding() or target:IsOther() or target:IsIllusion() then + return + end + local fleshHeap = caster:FindModifierByName(modifier_pudge_flesh_heap_custom.name) + if fleshHeap then + fleshHeap:AddHeapStack(1) + end + local ability = self:GetAbility() + if not ability then + return + end + local hungerModifier = caster:AddNewModifier(caster, ability, modifier_general_hunger.name, {}) + if not hungerModifier then + return + end + local hungerBonus = math.max( + 1, + math.floor(ability:GetSpecialValueFor("hunger_bonus")) + ) + do + local i = 0 + while i < hungerBonus do + hungerModifier:IncrementStackCount() + i = i + 1 + end + end +end +modifier_pudge_dismember_custom = __TS__Decorate( + modifier_pudge_dismember_custom, + modifier_pudge_dismember_custom, + {registerModifier(nil)}, + {kind = "class", name = "modifier_pudge_dismember_custom"} +) +____exports.modifier_pudge_dismember_custom = modifier_pudge_dismember_custom +return ____exports diff --git a/scripts/vscripts/abilities/heroes/pudge/ability_pudge_flesh_heap_custom.lua b/scripts/vscripts/abilities/heroes/pudge/ability_pudge_flesh_heap_custom.lua new file mode 100644 index 0000000..a90189b --- /dev/null +++ b/scripts/vscripts/abilities/heroes/pudge/ability_pudge_flesh_heap_custom.lua @@ -0,0 +1,83 @@ +local ____lualib = require("lualib_bundle") +local __TS__Class = ____lualib.__TS__Class +local __TS__ClassExtends = ____lualib.__TS__ClassExtends +local __TS__Decorate = ____lualib.__TS__Decorate +local ____exports = {} +local ____dota_ts_adapter = require("lib.dota_ts_adapter") +local BaseAbility = ____dota_ts_adapter.BaseAbility +local BaseModifier = ____dota_ts_adapter.BaseModifier +local registerAbility = ____dota_ts_adapter.registerAbility +local registerModifier = ____dota_ts_adapter.registerModifier +____exports.ability_pudge_flesh_heap_custom = __TS__Class() +local ability_pudge_flesh_heap_custom = ____exports.ability_pudge_flesh_heap_custom +ability_pudge_flesh_heap_custom.name = "ability_pudge_flesh_heap_custom" +ability_pudge_flesh_heap_custom.____file_path = "scripts/vscripts/abilities/heroes/pudge/ability_pudge_flesh_heap_custom.lua" +__TS__ClassExtends(ability_pudge_flesh_heap_custom, BaseAbility) +function ability_pudge_flesh_heap_custom.prototype.GetIntrinsicModifierName(self) + return ____exports.modifier_pudge_flesh_heap_custom.name +end +ability_pudge_flesh_heap_custom = __TS__Decorate( + ability_pudge_flesh_heap_custom, + ability_pudge_flesh_heap_custom, + {registerAbility(nil)}, + {kind = "class", name = "ability_pudge_flesh_heap_custom"} +) +____exports.ability_pudge_flesh_heap_custom = ability_pudge_flesh_heap_custom +____exports.modifier_pudge_flesh_heap_custom = __TS__Class() +local modifier_pudge_flesh_heap_custom = ____exports.modifier_pudge_flesh_heap_custom +modifier_pudge_flesh_heap_custom.name = "modifier_pudge_flesh_heap_custom" +modifier_pudge_flesh_heap_custom.____file_path = "scripts/vscripts/abilities/heroes/pudge/ability_pudge_flesh_heap_custom.lua" +__TS__ClassExtends(modifier_pudge_flesh_heap_custom, BaseModifier) +function modifier_pudge_flesh_heap_custom.prototype.____constructor(self, ...) + BaseModifier.prototype.____constructor(self, ...) + self.stackRange = 0 + self.magicResistPerStack = 0 + self.strengthPerStack = 0 +end +function modifier_pudge_flesh_heap_custom.prototype.IsHidden(self) + return false +end +function modifier_pudge_flesh_heap_custom.prototype.IsPurgable(self) + return false +end +function modifier_pudge_flesh_heap_custom.prototype.RemoveOnDeath(self) + return false +end +function modifier_pudge_flesh_heap_custom.prototype.OnCreated(self) + local ability = self:GetAbility() + if not ability then + return + end + self.stackRange = ability:GetSpecialValueFor("stack_range") + self.magicResistPerStack = ability:GetSpecialValueFor("magic_resist_per_stack") + self.strengthPerStack = ability:GetSpecialValueFor("strength_per_stack") +end +function modifier_pudge_flesh_heap_custom.prototype.OnRefresh(self) + self:OnCreated() +end +function modifier_pudge_flesh_heap_custom.prototype.DeclareFunctions(self) + return {MODIFIER_PROPERTY_MAGICAL_RESISTANCE_BONUS, MODIFIER_PROPERTY_STATS_STRENGTH_BONUS} +end +function modifier_pudge_flesh_heap_custom.prototype.GetModifierMagicalResistanceBonus(self) + if self:GetParent():PassivesDisabled() then + return 0 + end + return self:GetStackCount() * self.magicResistPerStack +end +function modifier_pudge_flesh_heap_custom.prototype.GetModifierBonusStats_Strength(self) + if self:GetParent():PassivesDisabled() then + return 0 + end + return self:GetStackCount() * self.strengthPerStack +end +function modifier_pudge_flesh_heap_custom.prototype.AddHeapStack(self, value) + self:SetStackCount(self:GetStackCount() + value) +end +modifier_pudge_flesh_heap_custom = __TS__Decorate( + modifier_pudge_flesh_heap_custom, + modifier_pudge_flesh_heap_custom, + {registerModifier(nil)}, + {kind = "class", name = "modifier_pudge_flesh_heap_custom"} +) +____exports.modifier_pudge_flesh_heap_custom = modifier_pudge_flesh_heap_custom +return ____exports diff --git a/scripts/vscripts/abilities/heroes/pudge/ability_pudge_meat_hook_custom.lua b/scripts/vscripts/abilities/heroes/pudge/ability_pudge_meat_hook_custom.lua new file mode 100644 index 0000000..7fe8f78 --- /dev/null +++ b/scripts/vscripts/abilities/heroes/pudge/ability_pudge_meat_hook_custom.lua @@ -0,0 +1,361 @@ +local ____lualib = require("lualib_bundle") +local __TS__Class = ____lualib.__TS__Class +local __TS__ClassExtends = ____lualib.__TS__ClassExtends +local __TS__Decorate = ____lualib.__TS__Decorate +local ____exports = {} +local ____dota_ts_adapter = require("lib.dota_ts_adapter") +local BaseAbility = ____dota_ts_adapter.BaseAbility +local BaseModifier = ____dota_ts_adapter.BaseModifier +local registerAbility = ____dota_ts_adapter.registerAbility +local registerModifier = ____dota_ts_adapter.registerModifier +local function isPudgeHookFacetBleedActive(self, _ability) + return false +end +____exports.ability_pudge_meat_hook_custom = __TS__Class() +local ability_pudge_meat_hook_custom = ____exports.ability_pudge_meat_hook_custom +ability_pudge_meat_hook_custom.name = "ability_pudge_meat_hook_custom" +ability_pudge_meat_hook_custom.____file_path = "scripts/vscripts/abilities/heroes/pudge/ability_pudge_meat_hook_custom.lua" +__TS__ClassExtends(ability_pudge_meat_hook_custom, BaseAbility) +function ability_pudge_meat_hook_custom.prototype.____constructor(self, ...) + BaseAbility.prototype.____constructor(self, ...) + self.hookData = {} + self.vStartPosition = Vector(0, 0, 0) + self.vHookOffset = Vector(0, 0, 96) +end +function ability_pudge_meat_hook_custom.prototype.Precache(self, context) + PrecacheResource("particle", "particles/units/heroes/hero_pudge/pudge_meathook.vpcf", context) + PrecacheResource("particle", "particles/units/heroes/hero_pudge/pudge_meathook_impact.vpcf", context) +end +function ability_pudge_meat_hook_custom.prototype.GetAbilityDamageType(self) + return DAMAGE_TYPE_PURE +end +function ability_pudge_meat_hook_custom.prototype.GetCastRange(self, location, target) + return self:GetSpecialValueFor("hook_distance") +end +function ability_pudge_meat_hook_custom.prototype.OnAbilityPhaseStart(self) + self:GetCaster():StartGesture(ACT_DOTA_OVERRIDE_ABILITY_1) + return true +end +function ability_pudge_meat_hook_custom.prototype.OnAbilityPhaseInterrupted(self) + self:GetCaster():RemoveGesture(ACT_DOTA_OVERRIDE_ABILITY_1) + return true +end +function ability_pudge_meat_hook_custom.prototype.OnSpellStart(self) + local caster = self:GetCaster() + local hookSpeed = self:GetSpecialValueFor("hook_speed") + local hookDistance = self:GetCastRange( + caster:GetOrigin(), + nil + ) + local hookWidth = self:GetSpecialValueFor("hook_width") + self.hookData = {} + local casterPoint = caster:GetOrigin() + self.vStartPosition = casterPoint + local vDirection = (casterPoint == self:GetCursorPosition() and self:GetCursorPosition() + Vector(1, 1, 0) or self:GetCursorPosition()) - casterPoint + vDirection.z = 0 + vDirection = vDirection:Normalized() * hookDistance + local vNorm = vDirection:Normalized() + local vKillswitch = Vector(hookDistance / hookSpeed * 2, 0, 0) + EmitSoundOn("Hero_Pudge.AttackHookExtend", caster) + local hookDirs = {vNorm} + do + local i = 0 + while i < #hookDirs do + local dir = hookDirs[i + 1] + local endpoint = casterPoint + dir * hookDistance + local vHookTarget = endpoint + Vector(0, 0, 96) + local fx = ParticleManager:CreateParticle("particles/units/heroes/hero_pudge/pudge_meathook.vpcf", PATTACH_CUSTOMORIGIN, caster) + ParticleManager:SetParticleAlwaysSimulate(fx) + ParticleManager:SetParticleControlEnt( + fx, + 0, + caster, + PATTACH_POINT_FOLLOW, + "attach_weapon_chain_rt", + casterPoint + self.vHookOffset, + true + ) + ParticleManager:SetParticleControl(fx, 1, vHookTarget) + ParticleManager:SetParticleControl( + fx, + 2, + Vector(hookSpeed, hookDistance, hookWidth) + ) + ParticleManager:SetParticleControl(fx, 3, vKillswitch) + ParticleManager:SetParticleControl( + fx, + 4, + Vector(1, 0, 0) + ) + ParticleManager:SetParticleControl( + fx, + 5, + Vector(0, 0, 0) + ) + local ____self_hookData_0 = self.hookData + ____self_hookData_0[#____self_hookData_0 + 1] = {particle = fx, endpoint = endpoint} + ProjectileManager:CreateLinearProjectile({ + Ability = self, + vSpawnOrigin = casterPoint, + vVelocity = dir * hookSpeed, + fDistance = hookDistance, + fStartRadius = hookWidth, + fEndRadius = hookWidth, + Source = caster, + iUnitTargetTeam = DOTA_UNIT_TARGET_TEAM_ENEMY, + iUnitTargetType = bit.bor(DOTA_UNIT_TARGET_HERO, DOTA_UNIT_TARGET_BASIC), + iUnitTargetFlags = DOTA_UNIT_TARGET_FLAG_MAGIC_IMMUNE_ENEMIES, + ExtraData = {hookIndex = i, isReturn = 0} + }) + i = i + 1 + end + end +end +function ability_pudge_meat_hook_custom.prototype.OnProjectileHit_ExtraData(self, target, location, extraData) + local caster = self:GetCaster() + local ____extraData_1 = extraData + local hookIndex = ____extraData_1.hookIndex + local isReturn = ____extraData_1.isReturn + local hook = self.hookData[hookIndex + 1] + if not hook then + return true + end + if isReturn == 1 then + ParticleManager:DestroyParticle(hook.particle, true) + ParticleManager:ReleaseParticleIndex(hook.particle) + if hookIndex == 0 then + EmitSoundOn("Hero_Pudge.AttackHookRetractStop", caster) + end + return true + end + if target and target ~= caster then + EmitSoundOn("Hero_Pudge.AttackHookImpact", target) + local hookDamage = self:GetSpecialValueFor("hook_damage") + local maxHpPct = self:GetSpecialValueFor("max_health_damage_pct") * 0.01 + local totalDamage = hookDamage + caster:GetMaxHealth() * maxHpPct + ApplyDamage({ + victim = target, + attacker = caster, + damage = totalDamage, + damage_type = self:GetAbilityDamageType(), + ability = self + }) + local impactFx = ParticleManager:CreateParticle("particles/units/heroes/hero_pudge/pudge_meathook_impact.vpcf", PATTACH_CUSTOMORIGIN, target) + ParticleManager:SetParticleControlEnt( + impactFx, + 0, + target, + PATTACH_POINT_FOLLOW, + "attach_hitloc", + caster:GetOrigin(), + true + ) + ParticleManager:ReleaseParticleIndex(impactFx) + if isPudgeHookFacetBleedActive(nil, self) then + target:AddNewModifier(caster, self, ____exports.modifier_pudge_meat_hook_bleed_custom.name, {}) + end + ____exports.modifier_pudge_meat_hook_pull:apply(target, caster, self, { + tx = hook.endpoint.x, + ty = hook.endpoint.y, + tz = hook.endpoint.z, + sx = self.vStartPosition.x, + sy = self.vStartPosition.y, + sz = self.vStartPosition.z + }) + return false + end + ParticleManager:SetParticleControlEnt( + hook.particle, + 1, + caster, + PATTACH_POINT_FOLLOW, + "attach_weapon_chain_rt", + caster:GetOrigin() + self.vHookOffset, + true + ) + if hookIndex == 0 then + StopSoundOn("Hero_Pudge.AttackHookExtend", caster) + EmitSoundOn("Hero_Pudge.AttackHookRetract", caster) + if caster:IsAlive() then + caster:FadeGesture(ACT_DOTA_OVERRIDE_ABILITY_1) + caster:StartGesture(ACT_DOTA_CHANNEL_ABILITY_1) + end + end + local vVelocity = self.vStartPosition - hook.endpoint + vVelocity.z = 0 + local flDistance = math.max( + 0, + vVelocity:Length2D() - caster:GetPaddedCollisionRadius() + ) + vVelocity = vVelocity:Normalized() * self:GetSpecialValueFor("hook_speed") + ProjectileManager:CreateLinearProjectile({ + Ability = self, + vSpawnOrigin = hook.endpoint, + vVelocity = vVelocity, + fDistance = flDistance, + Source = caster, + ExtraData = {hookIndex = hookIndex, isReturn = 1} + }) + return true +end +ability_pudge_meat_hook_custom = __TS__Decorate( + ability_pudge_meat_hook_custom, + ability_pudge_meat_hook_custom, + {registerAbility(nil)}, + {kind = "class", name = "ability_pudge_meat_hook_custom"} +) +____exports.ability_pudge_meat_hook_custom = ability_pudge_meat_hook_custom +____exports.modifier_pudge_meat_hook_bleed_custom = __TS__Class() +local modifier_pudge_meat_hook_bleed_custom = ____exports.modifier_pudge_meat_hook_bleed_custom +modifier_pudge_meat_hook_bleed_custom.name = "modifier_pudge_meat_hook_bleed_custom" +modifier_pudge_meat_hook_bleed_custom.____file_path = "scripts/vscripts/abilities/heroes/pudge/ability_pudge_meat_hook_custom.lua" +__TS__ClassExtends(modifier_pudge_meat_hook_bleed_custom, BaseModifier) +function modifier_pudge_meat_hook_bleed_custom.prototype.____constructor(self, ...) + BaseModifier.prototype.____constructor(self, ...) + self.interval = 0.5 +end +function modifier_pudge_meat_hook_bleed_custom.prototype.IsHidden(self) + return false +end +function modifier_pudge_meat_hook_bleed_custom.prototype.IsDebuff(self) + return true +end +function modifier_pudge_meat_hook_bleed_custom.prototype.IsPurgable(self) + return true +end +function modifier_pudge_meat_hook_bleed_custom.prototype.OnCreated(self) + if not IsServer() then + return + end + self:SetDuration(4, true) + self:StartIntervalThink(self.interval) +end +function modifier_pudge_meat_hook_bleed_custom.prototype.OnRefresh(self) + if not IsServer() then + return + end + self:SetDuration(4, true) +end +function modifier_pudge_meat_hook_bleed_custom.prototype.OnIntervalThink(self) + if not IsServer() then + return + end + local ability = self:GetAbility() + local caster = self:GetCaster() + local parent = self:GetParent() + if not ability or not caster or not parent or not parent:IsAlive() or not caster:IsAlive() then + return + end + if not isPudgeHookFacetBleedActive(nil, ability) then + return + end + local damage = caster:GetStrength() * 0.1 * self.interval + ApplyDamage({ + victim = parent, + attacker = caster, + damage = damage, + damage_type = DAMAGE_TYPE_PHYSICAL, + ability = ability + }) +end +modifier_pudge_meat_hook_bleed_custom = __TS__Decorate( + modifier_pudge_meat_hook_bleed_custom, + modifier_pudge_meat_hook_bleed_custom, + {registerModifier(nil)}, + {kind = "class", name = "modifier_pudge_meat_hook_bleed_custom"} +) +____exports.modifier_pudge_meat_hook_bleed_custom = modifier_pudge_meat_hook_bleed_custom +____exports.modifier_pudge_meat_hook_pull = __TS__Class() +local modifier_pudge_meat_hook_pull = ____exports.modifier_pudge_meat_hook_pull +modifier_pudge_meat_hook_pull.name = "modifier_pudge_meat_hook_pull" +modifier_pudge_meat_hook_pull.____file_path = "scripts/vscripts/abilities/heroes/pudge/ability_pudge_meat_hook_custom.lua" +__TS__ClassExtends(modifier_pudge_meat_hook_pull, BaseModifier) +function modifier_pudge_meat_hook_pull.prototype.____constructor(self, ...) + BaseModifier.prototype.____constructor(self, ...) + self.THINK_INTERVAL = 0.03 + self.REACH_THRESHOLD = 50 + self.vEndPoint = Vector(0, 0, 0) + self.vStartPoint = Vector(0, 0, 0) + self.phase = "toHookEnd" +end +function modifier_pudge_meat_hook_pull.prototype.IsHidden(self) + return false +end +function modifier_pudge_meat_hook_pull.prototype.IsPurgable(self) + return false +end +function modifier_pudge_meat_hook_pull.prototype.OnCreated(self, params) + if not IsServer() then + return + end + self.vEndPoint = Vector(params.tx, params.ty, params.tz) + self.vStartPoint = Vector(params.sx, params.sy, params.sz) + self.phase = "toHookEnd" + self:StartIntervalThink(self.THINK_INTERVAL) +end +function modifier_pudge_meat_hook_pull.prototype.CheckState(self) + return {[MODIFIER_STATE_STUNNED] = true} +end +function modifier_pudge_meat_hook_pull.prototype.DeclareFunctions(self) + return {MODIFIER_PROPERTY_OVERRIDE_ANIMATION} +end +function modifier_pudge_meat_hook_pull.prototype.GetOverrideAnimation(self) + return ACT_DOTA_FLAIL +end +function modifier_pudge_meat_hook_pull.prototype.OnIntervalThink(self) + if not IsServer() then + return + end + local parent = self:GetParent() + local caster = self:GetCaster() + if not parent:IsAlive() or not caster or not caster:IsAlive() then + self:Destroy() + return + end + local parentPos = parent:GetAbsOrigin() + local ____opt_2 = self:GetAbility() + local hookSpeed = ____opt_2 and ____opt_2:GetSpecialValueFor("hook_speed") or 1600 + local moveStep = hookSpeed * self.THINK_INTERVAL + local targetPoint = self.phase == "toHookEnd" and self.vEndPoint or self.vStartPoint + local vDiff = targetPoint - parentPos + vDiff.z = 0 + local dist = vDiff:Length2D() + if dist <= self.REACH_THRESHOLD then + if self.phase == "toHookEnd" then + self.phase = "toHookStart" + else + local casterPos = caster:GetAbsOrigin() + local finalPos = self.vStartPoint + local overlapDelta = finalPos - casterPos + overlapDelta.z = 0 + local minDist = caster:GetPaddedCollisionRadius() + parent:GetPaddedCollisionRadius() + 24 + if overlapDelta:Length2D() < minDist then + local away = finalPos - casterPos + away.z = 0 + if away:Length2D() < 0.01 then + away = parent:GetForwardVector() * -1 + else + away = away:Normalized() + end + finalPos = casterPos + away * minDist + end + FindClearSpaceForUnit( + parent, + GetGroundPosition(finalPos, parent), + true + ) + self:Destroy() + end + return + end + local newPos = parentPos + vDiff:Normalized() * math.min(moveStep, dist) + parent:SetAbsOrigin(GetGroundPosition(newPos, parent)) +end +modifier_pudge_meat_hook_pull = __TS__Decorate( + modifier_pudge_meat_hook_pull, + modifier_pudge_meat_hook_pull, + {registerModifier(nil)}, + {kind = "class", name = "modifier_pudge_meat_hook_pull"} +) +____exports.modifier_pudge_meat_hook_pull = modifier_pudge_meat_hook_pull +return ____exports diff --git a/scripts/vscripts/abilities/heroes/pudge/ability_pudge_meat_shield_custom.lua b/scripts/vscripts/abilities/heroes/pudge/ability_pudge_meat_shield_custom.lua new file mode 100644 index 0000000..94d0d9a --- /dev/null +++ b/scripts/vscripts/abilities/heroes/pudge/ability_pudge_meat_shield_custom.lua @@ -0,0 +1,107 @@ +local ____lualib = require("lualib_bundle") +local __TS__Class = ____lualib.__TS__Class +local __TS__ClassExtends = ____lualib.__TS__ClassExtends +local __TS__Decorate = ____lualib.__TS__Decorate +local ____exports = {} +local ____dota_ts_adapter = require("lib.dota_ts_adapter") +local BaseAbility = ____dota_ts_adapter.BaseAbility +local BaseModifier = ____dota_ts_adapter.BaseModifier +local registerAbility = ____dota_ts_adapter.registerAbility +local registerModifier = ____dota_ts_adapter.registerModifier +____exports.ability_pudge_meat_shield_custom = __TS__Class() +local ability_pudge_meat_shield_custom = ____exports.ability_pudge_meat_shield_custom +ability_pudge_meat_shield_custom.name = "ability_pudge_meat_shield_custom" +ability_pudge_meat_shield_custom.____file_path = "scripts/vscripts/abilities/heroes/pudge/ability_pudge_meat_shield_custom.lua" +__TS__ClassExtends(ability_pudge_meat_shield_custom, BaseAbility) +function ability_pudge_meat_shield_custom.prototype.GetIntrinsicModifierName(self) + return ____exports.modifier_pudge_meat_shield_custom.name +end +ability_pudge_meat_shield_custom = __TS__Decorate( + ability_pudge_meat_shield_custom, + ability_pudge_meat_shield_custom, + {registerAbility(nil)}, + {kind = "class", name = "ability_pudge_meat_shield_custom"} +) +____exports.ability_pudge_meat_shield_custom = ability_pudge_meat_shield_custom +____exports.modifier_pudge_meat_shield_custom = __TS__Class() +local modifier_pudge_meat_shield_custom = ____exports.modifier_pudge_meat_shield_custom +modifier_pudge_meat_shield_custom.name = "modifier_pudge_meat_shield_custom" +modifier_pudge_meat_shield_custom.____file_path = "scripts/vscripts/abilities/heroes/pudge/ability_pudge_meat_shield_custom.lua" +__TS__ClassExtends(modifier_pudge_meat_shield_custom, BaseModifier) +function modifier_pudge_meat_shield_custom.prototype.IsHidden(self) + return false +end +function modifier_pudge_meat_shield_custom.prototype.IsPurgable(self) + return false +end +function modifier_pudge_meat_shield_custom.prototype.DeclareFunctions(self) + return {MODIFIER_PROPERTY_INCOMING_DAMAGE_CONSTANT, MODIFIER_EVENT_ON_TAKEDAMAGE} +end +function modifier_pudge_meat_shield_custom.prototype.GetModifierIncomingDamageConstant(self, event) + if self:GetParent():PassivesDisabled() then + return 0 + end + local ability = self:GetAbility() + local parent = self:GetParent() + if not ability then + return 0 + end + if event.damage <= 0 then + return 0 + end + local block = ability:GetSpecialValueFor("block_damage") + if HasShard(nil, parent) then + block = block + parent:GetStrength() * ability:GetSpecialValueFor("shard_block_per_strength") + end + return -math.min(block, event.damage) +end +function modifier_pudge_meat_shield_custom.prototype.OnTakeDamage(self, event) + if not IsServer() then + return + end + if self:GetParent():PassivesDisabled() then + return + end + local parent = self:GetParent() + local ability = self:GetAbility() + if not ability then + return + end + if event.unit ~= parent then + return + end + local attacker = event.attacker + if not attacker or attacker:IsNull() or not attacker:IsAlive() then + return + end + if attacker:GetTeamNumber() == parent:GetTeamNumber() then + return + end + if attacker:IsBuilding() or attacker:IsOther() then + return + end + if event.damage <= 0 then + return + end + local reflectPct = ability:GetSpecialValueFor("reflect_pct_per_str") * 0.01 + if reflectPct <= 0 then + return + end + local reflectDamage = parent:GetStrength() * reflectPct + ApplyDamage({ + victim = attacker, + attacker = parent, + damage = reflectDamage, + damage_type = DAMAGE_TYPE_PHYSICAL, + ability = ability, + damage_flags = DOTA_DAMAGE_FLAG_REFLECTION + DOTA_DAMAGE_FLAG_NO_SPELL_AMPLIFICATION + }) +end +modifier_pudge_meat_shield_custom = __TS__Decorate( + modifier_pudge_meat_shield_custom, + modifier_pudge_meat_shield_custom, + {registerModifier(nil)}, + {kind = "class", name = "modifier_pudge_meat_shield_custom"} +) +____exports.modifier_pudge_meat_shield_custom = modifier_pudge_meat_shield_custom +return ____exports diff --git a/scripts/vscripts/abilities/heroes/pudge/ability_pudge_rot_custom.lua b/scripts/vscripts/abilities/heroes/pudge/ability_pudge_rot_custom.lua new file mode 100644 index 0000000..d35fdc7 --- /dev/null +++ b/scripts/vscripts/abilities/heroes/pudge/ability_pudge_rot_custom.lua @@ -0,0 +1,283 @@ +local ____lualib = require("lualib_bundle") +local __TS__Class = ____lualib.__TS__Class +local __TS__ClassExtends = ____lualib.__TS__ClassExtends +local __TS__Decorate = ____lualib.__TS__Decorate +local ____exports = {} +local ____dota_ts_adapter = require("lib.dota_ts_adapter") +local BaseAbility = ____dota_ts_adapter.BaseAbility +local BaseModifier = ____dota_ts_adapter.BaseModifier +local registerAbility = ____dota_ts_adapter.registerAbility +local registerModifier = ____dota_ts_adapter.registerModifier +____exports.ability_pudge_rot_custom = __TS__Class() +local ability_pudge_rot_custom = ____exports.ability_pudge_rot_custom +ability_pudge_rot_custom.name = "ability_pudge_rot_custom" +ability_pudge_rot_custom.____file_path = "scripts/vscripts/abilities/heroes/pudge/ability_pudge_rot_custom.lua" +__TS__ClassExtends(ability_pudge_rot_custom, BaseAbility) +function ability_pudge_rot_custom.prototype.GetIntrinsicModifierName(self) + return ____exports.modifier_pudge_rot_aspect_damage_custom.name +end +function ability_pudge_rot_custom.prototype.Precache(self, context) + PrecacheResource("particle", "particles/units/heroes/hero_pudge/pudge_rot.vpcf", context) + PrecacheResource("particle", "particles/units/heroes/hero_pudge/pudge_rot_recipient.vpcf", context) +end +function ability_pudge_rot_custom.prototype.GetAOERadius(self) + local caster = self:GetCaster() + return self:GetSpecialValueFor("rot_radius") + (caster and caster:HasScepter() and self:GetSpecialValueFor("scepter_bonus_radius") or 0) +end +function ability_pudge_rot_custom.prototype.OnToggle(self) + if not IsServer() then + return + end + local caster = self:GetCaster() + if self:GetToggleState() then + ____exports.modifier_pudge_rot_custom:apply(caster, caster, self, {}) + caster:StartGesture(ACT_DOTA_CAST_ABILITY_ROT) + self.nfx = ParticleManager:CreateParticle("particles/units/heroes/hero_pudge/pudge_rot.vpcf", PATTACH_ABSORIGIN_FOLLOW, caster) + ParticleManager:SetParticleControlEnt( + self.nfx, + 0, + caster, + PATTACH_ABSORIGIN_FOLLOW, + "attach_hitloc", + caster:GetAbsOrigin(), + true + ) + ParticleManager:SetParticleControl( + self.nfx, + 1, + Vector( + self:GetAOERadius(), + self:GetAOERadius(), + self:GetAOERadius() + ) + ) + EmitSoundOn("Hero_Pudge.Rot", caster) + else + caster:RemoveModifierByName(____exports.modifier_pudge_rot_custom.name) + if self.nfx ~= nil then + ParticleManager:DestroyParticle(self.nfx, true) + ParticleManager:ReleaseParticleIndex(self.nfx) + self.nfx = nil + end + StopSoundOn("Hero_Pudge.Rot", caster) + end +end +ability_pudge_rot_custom = __TS__Decorate( + ability_pudge_rot_custom, + ability_pudge_rot_custom, + {registerAbility(nil)}, + {kind = "class", name = "ability_pudge_rot_custom"} +) +____exports.ability_pudge_rot_custom = ability_pudge_rot_custom +____exports.modifier_pudge_rot_aspect_damage_custom = __TS__Class() +local modifier_pudge_rot_aspect_damage_custom = ____exports.modifier_pudge_rot_aspect_damage_custom +modifier_pudge_rot_aspect_damage_custom.name = "modifier_pudge_rot_aspect_damage_custom" +modifier_pudge_rot_aspect_damage_custom.____file_path = "scripts/vscripts/abilities/heroes/pudge/ability_pudge_rot_custom.lua" +__TS__ClassExtends(modifier_pudge_rot_aspect_damage_custom, BaseModifier) +function modifier_pudge_rot_aspect_damage_custom.prototype.IsHidden(self) + return true +end +function modifier_pudge_rot_aspect_damage_custom.prototype.IsPurgable(self) + return false +end +function modifier_pudge_rot_aspect_damage_custom.prototype.RemoveOnDeath(self) + return false +end +function modifier_pudge_rot_aspect_damage_custom.prototype.DeclareFunctions(self) + return {MODIFIER_PROPERTY_BASEDAMAGEOUTGOING_PERCENTAGE} +end +function modifier_pudge_rot_aspect_damage_custom.prototype.GetModifierBaseDamageOutgoing_Percentage(self) + return 0 +end +modifier_pudge_rot_aspect_damage_custom = __TS__Decorate( + modifier_pudge_rot_aspect_damage_custom, + modifier_pudge_rot_aspect_damage_custom, + {registerModifier(nil)}, + {kind = "class", name = "modifier_pudge_rot_aspect_damage_custom"} +) +____exports.modifier_pudge_rot_aspect_damage_custom = modifier_pudge_rot_aspect_damage_custom +____exports.modifier_pudge_rot_custom = __TS__Class() +local modifier_pudge_rot_custom = ____exports.modifier_pudge_rot_custom +modifier_pudge_rot_custom.name = "modifier_pudge_rot_custom" +modifier_pudge_rot_custom.____file_path = "scripts/vscripts/abilities/heroes/pudge/ability_pudge_rot_custom.lua" +__TS__ClassExtends(modifier_pudge_rot_custom, BaseModifier) +function modifier_pudge_rot_custom.prototype.____constructor(self, ...) + BaseModifier.prototype.____constructor(self, ...) + self.currentDamagePerSec = 0 + self.damageIncreasePerSec = 0 + self.tickInterval = 0.2 +end +function modifier_pudge_rot_custom.prototype.IsHidden(self) + return false +end +function modifier_pudge_rot_custom.prototype.IsDebuff(self) + return false +end +function modifier_pudge_rot_custom.prototype.IsPurgable(self) + return false +end +function modifier_pudge_rot_custom.prototype.OnCreated(self) + if not IsServer() then + return + end + local ability = self:GetAbility() + if not ability then + return + end + self.tickInterval = ability:GetSpecialValueFor("tick_interval") + self.currentDamagePerSec = ability:GetSpecialValueFor("rot_damage_per_sec") + self.damageIncreasePerSec = ability:GetSpecialValueFor("rot_damage_increase_per_sec") + self:StartIntervalThink(self.tickInterval) +end +function modifier_pudge_rot_custom.prototype.OnDestroy(self) + if not IsServer() then + return + end + StopSoundOn( + "Hero_Pudge.Rot", + self:GetParent() + ) +end +function modifier_pudge_rot_custom.prototype.OnIntervalThink(self) + if not IsServer() then + return + end + local parent = self:GetParent() + local ability = self:GetAbility() + if not ability or not parent:IsAlive() then + return + end + self.currentDamagePerSec = self.currentDamagePerSec + self.damageIncreasePerSec * self.tickInterval + local selfDamagePct = ability:GetSpecialValueFor("self_damage_pct_per_sec") + local selfDamage = parent:GetMaxHealth() * selfDamagePct * 0.01 * self.tickInterval + ApplyDamage({ + victim = parent, + attacker = parent, + damage = selfDamage, + damage_type = DAMAGE_TYPE_MAGICAL, + ability = ability, + damage_flags = DOTA_DAMAGE_FLAG_NON_LETHAL + }) +end +function modifier_pudge_rot_custom.prototype.IsAura(self) + return true +end +function modifier_pudge_rot_custom.prototype.GetAuraRadius(self) + local ability = self:GetAbility() + local parent = self:GetParent() + if not ability then + return 0 + end + return ability:GetSpecialValueFor("rot_radius") + (parent:HasScepter() and ability:GetSpecialValueFor("scepter_bonus_radius") or 0) +end +function modifier_pudge_rot_custom.prototype.GetAuraSearchTeam(self) + return DOTA_UNIT_TARGET_TEAM_ENEMY +end +function modifier_pudge_rot_custom.prototype.GetAuraSearchType(self) + return bit.bor(DOTA_UNIT_TARGET_HERO, DOTA_UNIT_TARGET_BASIC) +end +function modifier_pudge_rot_custom.prototype.GetAuraDuration(self) + return 0.5 +end +function modifier_pudge_rot_custom.prototype.GetModifierAura(self) + return ____exports.modifier_pudge_rot_slow_custom.name +end +function modifier_pudge_rot_custom.prototype.GetCurrentDamagePerSec(self) + return self.currentDamagePerSec +end +modifier_pudge_rot_custom = __TS__Decorate( + modifier_pudge_rot_custom, + modifier_pudge_rot_custom, + {registerModifier(nil)}, + {kind = "class", name = "modifier_pudge_rot_custom"} +) +____exports.modifier_pudge_rot_custom = modifier_pudge_rot_custom +____exports.modifier_pudge_rot_slow_custom = __TS__Class() +local modifier_pudge_rot_slow_custom = ____exports.modifier_pudge_rot_slow_custom +modifier_pudge_rot_slow_custom.name = "modifier_pudge_rot_slow_custom" +modifier_pudge_rot_slow_custom.____file_path = "scripts/vscripts/abilities/heroes/pudge/ability_pudge_rot_custom.lua" +__TS__ClassExtends(modifier_pudge_rot_slow_custom, BaseModifier) +function modifier_pudge_rot_slow_custom.prototype.IsHidden(self) + return false +end +function modifier_pudge_rot_slow_custom.prototype.IsDebuff(self) + return true +end +function modifier_pudge_rot_slow_custom.prototype.IsPurgable(self) + return true +end +function modifier_pudge_rot_slow_custom.prototype.GetEffectName(self) + return "particles/units/heroes/hero_pudge/pudge_rot_recipient.vpcf" +end +function modifier_pudge_rot_slow_custom.prototype.GetEffectAttachType(self) + return PATTACH_ABSORIGIN_FOLLOW +end +function modifier_pudge_rot_slow_custom.prototype.OnCreated(self) + if not IsServer() then + return + end + local ____self_StartIntervalThink_2 = self.StartIntervalThink + local ____opt_0 = self:GetAbility() + ____self_StartIntervalThink_2( + self, + ____opt_0 and ____opt_0:GetSpecialValueFor("tick_interval") or 0.2 + ) +end +function modifier_pudge_rot_slow_custom.prototype.OnIntervalThink(self) + if not IsServer() then + return + end + local ability = self:GetAbility() + local caster = self:GetCaster() + local parent = self:GetParent() + if not ability or not caster or not parent or not caster:IsAlive() or not caster:HasModifier(____exports.modifier_pudge_rot_custom.name) then + return + end + local auraOwner = caster:FindModifierByName(____exports.modifier_pudge_rot_custom.name) + if not auraOwner then + return + end + local tickInterval = ability:GetSpecialValueFor("tick_interval") + local rampDamage = auraOwner:GetCurrentDamagePerSec() * tickInterval + local maxHpPct = ability:GetSpecialValueFor("max_health_damage_pct") * 0.01 + local maxHpDamage = caster:GetMaxHealth() * maxHpPct * tickInterval + local damage = rampDamage + maxHpDamage + if damage <= 0 then + return + end + ApplyDamage({ + victim = parent, + attacker = caster, + damage = damage, + damage_type = DAMAGE_TYPE_MAGICAL, + ability = ability + }) +end +function modifier_pudge_rot_slow_custom.prototype.DeclareFunctions(self) + return {MODIFIER_PROPERTY_MOVESPEED_BONUS_PERCENTAGE, MODIFIER_PROPERTY_HP_REGEN_AMPLIFY_PERCENTAGE} +end +function modifier_pudge_rot_slow_custom.prototype.GetModifierMoveSpeedBonus_Percentage(self) + local ability = self:GetAbility() + local caster = self:GetCaster() + if not ability or not caster then + return 0 + end + local slowPct = ability:GetSpecialValueFor("rot_slow_pct") + return -slowPct +end +function modifier_pudge_rot_slow_custom.prototype.GetModifierHPRegenAmplify_Percentage(self) + local ability = self:GetAbility() + local caster = self:GetCaster() + if not ability or not caster or not caster:HasScepter() then + return 0 + end + return -ability:GetSpecialValueFor("scepter_enemy_hp_regen_reduction_pct") +end +modifier_pudge_rot_slow_custom = __TS__Decorate( + modifier_pudge_rot_slow_custom, + modifier_pudge_rot_slow_custom, + {registerModifier(nil)}, + {kind = "class", name = "modifier_pudge_rot_slow_custom"} +) +____exports.modifier_pudge_rot_slow_custom = modifier_pudge_rot_slow_custom +return ____exports diff --git a/scripts/vscripts/abilities/heroes/queenofpain/queenofpain_agony_innate.lua b/scripts/vscripts/abilities/heroes/queenofpain/queenofpain_agony_innate.lua new file mode 100644 index 0000000..0d2a74a --- /dev/null +++ b/scripts/vscripts/abilities/heroes/queenofpain/queenofpain_agony_innate.lua @@ -0,0 +1,246 @@ +local ____lualib = require("lualib_bundle") +local __TS__Class = ____lualib.__TS__Class +local __TS__ClassExtends = ____lualib.__TS__ClassExtends +local __TS__Decorate = ____lualib.__TS__Decorate +local ____exports = {} +local ____dota_ts_adapter = require("lib.dota_ts_adapter") +local BaseAbility = ____dota_ts_adapter.BaseAbility +local BaseModifier = ____dota_ts_adapter.BaseModifier +local registerAbility = ____dota_ts_adapter.registerAbility +local registerModifier = ____dota_ts_adapter.registerModifier +____exports.queenofpain_agony_innate = __TS__Class() +local queenofpain_agony_innate = ____exports.queenofpain_agony_innate +queenofpain_agony_innate.name = "queenofpain_agony_innate" +queenofpain_agony_innate.____file_path = "scripts/vscripts/abilities/heroes/queenofpain/queenofpain_agony_innate.lua" +__TS__ClassExtends(queenofpain_agony_innate, BaseAbility) +function queenofpain_agony_innate.prototype.GetIntrinsicModifierName(self) + return ____exports.modifier_queenofpain_agony_innate.name +end +function queenofpain_agony_innate.prototype.GetAOERadius(self) + return self:GetSpecialValueFor("aura_radius") +end +queenofpain_agony_innate = __TS__Decorate( + queenofpain_agony_innate, + queenofpain_agony_innate, + {registerAbility(nil)}, + {kind = "class", name = "queenofpain_agony_innate"} +) +____exports.queenofpain_agony_innate = queenofpain_agony_innate +--- На герое: усиление заклинаний + аура дебаффа на врагов для отслеживания урона через OnTakeDamage. +____exports.modifier_queenofpain_agony_innate = __TS__Class() +local modifier_queenofpain_agony_innate = ____exports.modifier_queenofpain_agony_innate +modifier_queenofpain_agony_innate.name = "modifier_queenofpain_agony_innate" +modifier_queenofpain_agony_innate.____file_path = "scripts/vscripts/abilities/heroes/queenofpain/queenofpain_agony_innate.lua" +__TS__ClassExtends(modifier_queenofpain_agony_innate, BaseModifier) +function modifier_queenofpain_agony_innate.prototype.IsHidden(self) + return true +end +function modifier_queenofpain_agony_innate.prototype.IsPurgable(self) + return false +end +function modifier_queenofpain_agony_innate.prototype.IsAura(self) + return true +end +function modifier_queenofpain_agony_innate.prototype.GetModifierAura(self) + return ____exports.modifier_queenofpain_agony_innate_listener.name +end +function modifier_queenofpain_agony_innate.prototype.GetAuraRadius(self) + local innate = self:GetAbility() + return innate and innate:GetSpecialValueFor("aura_radius") or 25000 +end +function modifier_queenofpain_agony_innate.prototype.GetAuraDuration(self) + return 0.5 +end +function modifier_queenofpain_agony_innate.prototype.GetAuraSearchTeam(self) + return DOTA_UNIT_TARGET_TEAM_ENEMY +end +function modifier_queenofpain_agony_innate.prototype.GetAuraSearchType(self) + return bit.bor(DOTA_UNIT_TARGET_HERO, DOTA_UNIT_TARGET_BASIC) +end +function modifier_queenofpain_agony_innate.prototype.GetAuraSearchFlags(self) + return DOTA_UNIT_TARGET_FLAG_NONE +end +function modifier_queenofpain_agony_innate.prototype.DeclareFunctions(self) + return {MODIFIER_PROPERTY_SPELL_AMPLIFY_PERCENTAGE, MODIFIER_EVENT_ON_DEATH} +end +function modifier_queenofpain_agony_innate.prototype.GetModifierSpellAmplify_Percentage(self) + if not IsServer() then + return 0 + end + if self.parentHero:PassivesDisabled() then + return 0 + end + local innate = self:GetAbility() + if not innate then + return 0 + end + local baseSpellAmp = innate:GetSpecialValueFor("spell_amp_bonus") + local rawPerMissingPct = innate:GetSpecialValueFor("spell_amp_per_missing_hp_pct") + local perMissingPct = rawPerMissingPct > 0 and rawPerMissingPct or ____exports.modifier_queenofpain_agony_innate.DEFAULT_MISSING_HP_SPELL_AMP_PER_PCT + local hpMax = self.parentHero:GetMaxHealth() + if hpMax <= 0 then + return baseSpellAmp + end + local missingHpPct = math.max( + 0, + math.min( + 100, + (hpMax - self.parentHero:GetHealth()) / hpMax * 100 + ) + ) + local bonusFromMissingHp = missingHpPct * perMissingPct + return baseSpellAmp + bonusFromMissingHp +end +function modifier_queenofpain_agony_innate.prototype.OnCreated(self) + if not IsServer() then + return + end + self.parentHero = self:GetParent() +end +function modifier_queenofpain_agony_innate.prototype.OnDeath(self, event) + if not IsServer() then + return + end + if self.parentHero:PassivesDisabled() then + return + end + local attacker = event.attacker + if not attacker or attacker:IsNull() then + return + end + if attacker ~= self.parentHero then + return + end + local deadUnit = event.unit + if not deadUnit or deadUnit:IsNull() then + return + end + if deadUnit == self.parentHero then + return + end + local healAmount = self.parentHero:GetMaxHealth() * 0.05 + if healAmount <= 0 then + return + end + self.parentHero:Heal( + healAmount, + self:GetAbility() + ) +end +modifier_queenofpain_agony_innate.DEFAULT_MISSING_HP_SPELL_AMP_PER_PCT = 0.1 +modifier_queenofpain_agony_innate = __TS__Decorate( + modifier_queenofpain_agony_innate, + modifier_queenofpain_agony_innate, + {registerModifier(nil)}, + {kind = "class", name = "modifier_queenofpain_agony_innate"} +) +____exports.modifier_queenofpain_agony_innate = modifier_queenofpain_agony_innate +--- На врагах в радиусе ауры: при получении урона от способностей носителя ауры — самоурон носителю. +____exports.modifier_queenofpain_agony_innate_listener = __TS__Class() +local modifier_queenofpain_agony_innate_listener = ____exports.modifier_queenofpain_agony_innate_listener +modifier_queenofpain_agony_innate_listener.name = "modifier_queenofpain_agony_innate_listener" +modifier_queenofpain_agony_innate_listener.____file_path = "scripts/vscripts/abilities/heroes/queenofpain/queenofpain_agony_innate.lua" +__TS__ClassExtends(modifier_queenofpain_agony_innate_listener, BaseModifier) +function modifier_queenofpain_agony_innate_listener.prototype.IsHidden(self) + return true +end +function modifier_queenofpain_agony_innate_listener.prototype.IsPurgable(self) + return false +end +function modifier_queenofpain_agony_innate_listener.prototype.IsDebuff(self) + return true +end +function modifier_queenofpain_agony_innate_listener.prototype.RemoveOnDeath(self) + return true +end +function modifier_queenofpain_agony_innate_listener.prototype.DeclareFunctions(self) + return {MODIFIER_EVENT_ON_TAKEDAMAGE} +end +function modifier_queenofpain_agony_innate_listener.prototype.OnTakeDamage(self, event) + if not IsServer() then + return + end + if event.unit ~= self:GetParent() then + return + end + if event.damage <= 0 then + return + end + local caster = self:GetCaster() + if not caster or caster:IsNull() or not caster:IsAlive() then + return + end + if caster:PassivesDisabled() then + return + end + local attacker = event.attacker + if not attacker or attacker:IsNull() or not attacker:IsAlive() then + return + end + if attacker ~= caster then + return + end + local victim = event.unit + if not victim or victim:IsNull() or not victim:IsAlive() then + return + end + if victim:GetTeamNumber() == caster:GetTeamNumber() then + return + end + local inflictorEnt = self:resolveInflictorAbility(event) + if not inflictorEnt or inflictorEnt:IsNull() then + return + end + if inflictorEnt:IsItem() then + return + end + if inflictorEnt:GetCaster() ~= caster then + return + end + local innate = self:GetAbility() + if not innate then + return + end + local pctPerLevel = innate:GetSpecialValueFor("self_damage_pct_per_level") + local level = math.max( + 1, + caster:GetLevel() + ) + local mitigationPerLevel = innate:GetSpecialValueFor("mitigation_pct_per_level") + local mitigationFactor = math.max(0, 1 - mitigationPerLevel * 0.01 * level) + local selfDamage = event.damage * (pctPerLevel * 0.01) * level * mitigationFactor + if selfDamage < 0.5 then + return + end + ApplyDamage({ + victim = caster, + attacker = caster, + damage = selfDamage, + damage_type = DAMAGE_TYPE_MAGICAL, + damage_flags = DOTA_DAMAGE_FLAG_HPLOSS + DOTA_DAMAGE_FLAG_NO_SPELL_AMPLIFICATION + DOTA_DAMAGE_FLAG_NON_LETHAL, + ability = innate + }) +end +function modifier_queenofpain_agony_innate_listener.prototype.resolveInflictorAbility(self, event) + local inf = event.inflictor + if inf and not inf:IsNull() then + return inf + end + local idx = event.entindex_inflictor + if idx == nil or idx <= 0 then + return nil + end + local ent = EntIndexToHScript(idx) + if not ent or not IsValidEntity(ent) then + return nil + end + return ent +end +modifier_queenofpain_agony_innate_listener = __TS__Decorate( + modifier_queenofpain_agony_innate_listener, + modifier_queenofpain_agony_innate_listener, + {registerModifier(nil)}, + {kind = "class", name = "modifier_queenofpain_agony_innate_listener"} +) +____exports.modifier_queenofpain_agony_innate_listener = modifier_queenofpain_agony_innate_listener +return ____exports diff --git a/scripts/vscripts/abilities/heroes/queenofpain/queenofpain_blink_custom.lua b/scripts/vscripts/abilities/heroes/queenofpain/queenofpain_blink_custom.lua new file mode 100644 index 0000000..1c15dca --- /dev/null +++ b/scripts/vscripts/abilities/heroes/queenofpain/queenofpain_blink_custom.lua @@ -0,0 +1,153 @@ +local ____lualib = require("lualib_bundle") +local __TS__Class = ____lualib.__TS__Class +local __TS__ClassExtends = ____lualib.__TS__ClassExtends +local __TS__Decorate = ____lualib.__TS__Decorate +local ____exports = {} +local ____dota_ts_adapter = require("lib.dota_ts_adapter") +local BaseAbility = ____dota_ts_adapter.BaseAbility +local BaseModifier = ____dota_ts_adapter.BaseModifier +local registerModifier = ____dota_ts_adapter.registerModifier +local registerAbility = ____dota_ts_adapter.registerAbility +local ____queenofpain_mana_bonus = require("abilities.heroes.queenofpain.queenofpain_mana_bonus") +local getQueenOfPainManaFlatDamage = ____queenofpain_mana_bonus.getQueenOfPainManaFlatDamage +____exports.queenofpain_blink_custom = __TS__Class() +local queenofpain_blink_custom = ____exports.queenofpain_blink_custom +queenofpain_blink_custom.name = "queenofpain_blink_custom" +queenofpain_blink_custom.____file_path = "scripts/vscripts/abilities/heroes/queenofpain/queenofpain_blink_custom.lua" +__TS__ClassExtends(queenofpain_blink_custom, BaseAbility) +function queenofpain_blink_custom.prototype.GetAOERadius(self) + return self:GetSpecialValueFor("radius") +end +function queenofpain_blink_custom.prototype.GetCastRange(self, location, target) + if IsClient() then + return self:GetSpecialValueFor("cast_range") + self:GetCaster():GetCastRangeBonus() + end + return 0 +end +function queenofpain_blink_custom.prototype.OnSpellStart(self) + local caster = self:GetCaster() + local position = self:GetCursorPosition() + local casterPos = caster:GetAbsOrigin() + local radius = self:GetSpecialValueFor("radius") + local distance = position - casterPos + distance.z = 0 + local blinkRange = self:GetSpecialValueFor("cast_range") + caster:GetCastRangeBonus() + if distance:Length2D() > blinkRange then + position = GetGroundPosition( + casterPos:__add(distance:Normalized():__mul(blinkRange)), + caster + ) + end + ProjectileManager:ProjectileDodge(caster) + caster:EmitSound("Hero_QueenOfPain.Blink_in") + caster:EmitSound("Hero_QueenOfPain.Blink_in.Shard") + local blinkPfx = ParticleManager:CreateParticle("particles/units/heroes/hero_queenofpain/queen_blink_start.vpcf", PATTACH_ABSORIGIN, caster) + ParticleManager:SetParticleControl( + blinkPfx, + 0, + caster:GetAbsOrigin() + ) + ParticleManager:SetParticleControl(blinkPfx, 1, casterPos) + ParticleManager:ReleaseParticleIndex(blinkPfx) + local blinkShardPfx = ParticleManager:CreateParticle("particles/units/heroes/hero_queenofpain/queen_blink_shard_start.vpcf", PATTACH_WORLDORIGIN, nil) + ParticleManager:SetParticleControl(blinkShardPfx, 0, casterPos) + ParticleManager:SetParticleControl(blinkShardPfx, 1, casterPos) + ParticleManager:SetParticleControl( + blinkShardPfx, + 2, + Vector(radius, radius, radius) + ) + ParticleManager:ReleaseParticleIndex(blinkShardPfx) + self:DealDamageUnits(casterPos) + FindClearSpaceForUnit(caster, position, true) + self:DealDamageUnits(position) + caster:EmitSound("Hero_QueenOfPain.Blink_out") + caster:EmitSound("Hero_QueenOfPain.Blink_out.Shard") + local blinkEndPfx = ParticleManager:CreateParticle("particles/units/heroes/hero_queenofpain/queen_blink_end.vpcf", PATTACH_ABSORIGIN, caster) + ParticleManager:SetParticleControl(blinkEndPfx, 0, position) + ParticleManager:SetParticleControlForward( + blinkEndPfx, + 0, + (position - caster:GetAbsOrigin()):Normalized() + ) + ParticleManager:ReleaseParticleIndex(blinkEndPfx) + local blinkEndDamagePfx = ParticleManager:CreateParticle("particles/units/heroes/hero_queenofpain/queen_blink_shard_end.vpcf", PATTACH_WORLDORIGIN, nil) + ParticleManager:SetParticleControl(blinkEndDamagePfx, 0, position) + ParticleManager:SetParticleControl(blinkEndDamagePfx, 1, position) + ParticleManager:SetParticleControl( + blinkEndDamagePfx, + 2, + Vector(radius, radius, radius) + ) + ParticleManager:ReleaseParticleIndex(blinkEndDamagePfx) + caster:StartGesture(ACT_DOTA_CAST_ABILITY_2_END) +end +function queenofpain_blink_custom.prototype.DealDamageUnits(self, position) + local caster = self:GetCaster() + local radius = self:GetSpecialValueFor("radius") + local duration = self:GetSpecialValueFor("duration") + local baseDamage = self:GetSpecialValueFor("damage") + local extra = getQueenOfPainManaFlatDamage(nil, self, caster) + local damage = baseDamage + extra + local units = FindUnitsInRadius( + caster:GetTeamNumber(), + position, + nil, + radius, + DOTA_UNIT_TARGET_TEAM_ENEMY, + DOTA_UNIT_TARGET_HERO + DOTA_UNIT_TARGET_BASIC, + DOTA_UNIT_TARGET_FLAG_NONE, + FIND_ANY_ORDER, + false + ) + for ____, unit in ipairs(units) do + if not unit:IsBossCreature() then + unit:AddNewModifier(caster, self, ____exports.modifier_queenofpain_blink_custom_debuff.name, {duration = duration}) + end + ApplyDamage({ + victim = unit, + attacker = caster, + damage = damage, + damage_type = self:GetAbilityDamageType(), + ability = self + }) + end +end +queenofpain_blink_custom = __TS__Decorate( + queenofpain_blink_custom, + queenofpain_blink_custom, + {registerAbility(nil)}, + {kind = "class", name = "queenofpain_blink_custom"} +) +____exports.queenofpain_blink_custom = queenofpain_blink_custom +____exports.modifier_queenofpain_blink_custom_debuff = __TS__Class() +local modifier_queenofpain_blink_custom_debuff = ____exports.modifier_queenofpain_blink_custom_debuff +modifier_queenofpain_blink_custom_debuff.name = "modifier_queenofpain_blink_custom_debuff" +modifier_queenofpain_blink_custom_debuff.____file_path = "scripts/vscripts/abilities/heroes/queenofpain/queenofpain_blink_custom.lua" +__TS__ClassExtends(modifier_queenofpain_blink_custom_debuff, BaseModifier) +function modifier_queenofpain_blink_custom_debuff.prototype.IsHidden(self) + return false +end +function modifier_queenofpain_blink_custom_debuff.prototype.IsDebuff(self) + return true +end +function modifier_queenofpain_blink_custom_debuff.prototype.IsPurgable(self) + return false +end +function modifier_queenofpain_blink_custom_debuff.prototype.CheckState(self) + return {[MODIFIER_STATE_SILENCED] = true} +end +function modifier_queenofpain_blink_custom_debuff.prototype.GetEffectName(self) + return "particles/generic_gameplay/generic_silenced.vpcf" +end +function modifier_queenofpain_blink_custom_debuff.prototype.GetEffectAttachType(self) + return PATTACH_OVERHEAD_FOLLOW +end +modifier_queenofpain_blink_custom_debuff = __TS__Decorate( + modifier_queenofpain_blink_custom_debuff, + modifier_queenofpain_blink_custom_debuff, + {registerModifier(nil)}, + {kind = "class", name = "modifier_queenofpain_blink_custom_debuff"} +) +____exports.modifier_queenofpain_blink_custom_debuff = modifier_queenofpain_blink_custom_debuff +return ____exports diff --git a/scripts/vscripts/abilities/heroes/queenofpain/queenofpain_mana_bonus.lua b/scripts/vscripts/abilities/heroes/queenofpain/queenofpain_mana_bonus.lua new file mode 100644 index 0000000..826f8b7 --- /dev/null +++ b/scripts/vscripts/abilities/heroes/queenofpain/queenofpain_mana_bonus.lua @@ -0,0 +1,14 @@ +--[[ Generated with https://github.com/TypeScriptToLua/TypeScriptToLua ]] +local ____exports = {} +--- Плоский бонус к магическому урону кастомок QoP: текущая мана × (pct/100) из `mana_damage_from_current_pct` в AbilityValues. +function ____exports.getQueenOfPainManaFlatDamage(self, ability, caster) + if caster == nil or not caster:IsAlive() or not caster:IsHero() then + return 0 + end + local pct = ability:GetSpecialValueFor("mana_damage_from_current_pct") + if pct <= 0 then + return 0 + end + return caster:GetMana() * (pct * 0.01) +end +return ____exports diff --git a/scripts/vscripts/abilities/heroes/queenofpain/queenofpain_scream_of_pain_custom.lua b/scripts/vscripts/abilities/heroes/queenofpain/queenofpain_scream_of_pain_custom.lua new file mode 100644 index 0000000..dce5d3d --- /dev/null +++ b/scripts/vscripts/abilities/heroes/queenofpain/queenofpain_scream_of_pain_custom.lua @@ -0,0 +1,143 @@ +local ____lualib = require("lualib_bundle") +local __TS__Class = ____lualib.__TS__Class +local __TS__ClassExtends = ____lualib.__TS__ClassExtends +local __TS__Decorate = ____lualib.__TS__Decorate +local ____exports = {} +local ____dota_ts_adapter = require("lib.dota_ts_adapter") +local BaseAbility = ____dota_ts_adapter.BaseAbility +local BaseModifier = ____dota_ts_adapter.BaseModifier +local registerAbility = ____dota_ts_adapter.registerAbility +local registerModifier = ____dota_ts_adapter.registerModifier +local ____queenofpain_mana_bonus = require("abilities.heroes.queenofpain.queenofpain_mana_bonus") +local getQueenOfPainManaFlatDamage = ____queenofpain_mana_bonus.getQueenOfPainManaFlatDamage +--- Aghanim's Shard: на кого можно повесить уязвимость после Scream. +local function isQueenofpainScreamShardMarkTarget(self, unit) + if not IsServer() or not unit or not unit:IsAlive() then + return false + end + if unit:IsBuilding() then + return false + end + if unit:IsBossCreature() then + return false + end + if unit:IsCourier() then + return false + end + return true +end +____exports.queenofpain_scream_of_pain_custom = __TS__Class() +local queenofpain_scream_of_pain_custom = ____exports.queenofpain_scream_of_pain_custom +queenofpain_scream_of_pain_custom.name = "queenofpain_scream_of_pain_custom" +queenofpain_scream_of_pain_custom.____file_path = "scripts/vscripts/abilities/heroes/queenofpain/queenofpain_scream_of_pain_custom.lua" +__TS__ClassExtends(queenofpain_scream_of_pain_custom, BaseAbility) +function queenofpain_scream_of_pain_custom.prototype.GetCastRange(self, location, target) + return self:GetSpecialValueFor("radius") +end +function queenofpain_scream_of_pain_custom.prototype.OnSpellStart(self, newCastmen) + if not IsServer() then + return + end + local caster = self:GetCaster() + local castmen = newCastmen or caster + local point = castmen:GetAbsOrigin() + local enemies = FindUnitsInRadius( + caster:GetTeamNumber(), + point, + nil, + self:GetSpecialValueFor("radius"), + DOTA_UNIT_TARGET_TEAM_ENEMY, + bit.bor(DOTA_UNIT_TARGET_HERO, DOTA_UNIT_TARGET_BASIC), + 0, + FIND_ANY_ORDER, + false + ) + local projectInfo = { + Target = caster, + Source = castmen, + Ability = self, + EffectName = "particles/units/heroes/hero_queenofpain/queen_scream_of_pain.vpcf", + iMoveSpeed = self:GetSpecialValueFor("projectile_speed"), + vSourceLoc = point, + bDrawsOnMinimap = false, + bDodgeable = false, + bIsAttack = false, + bVisibleToEnemies = true, + bReplaceExisting = false, + bProvidesVision = false + } + for ____, enemy in ipairs(enemies) do + projectInfo.Target = enemy + ProjectileManager:CreateTrackingProjectile(projectInfo) + end + local particle = ParticleManager:CreateParticle("particles/units/heroes/hero_queenofpain/queen_scream_of_pain_owner.vpcf", PATTACH_ABSORIGIN, caster) + ParticleManager:SetParticleControl(particle, 0, point) + ParticleManager:ReleaseParticleIndex(particle) + EmitSoundOnLocationWithCaster(point, "Hero_QueenOfPain.ScreamOfPain", caster) +end +function queenofpain_scream_of_pain_custom.prototype.OnProjectileHit(self, target, location) + if not IsServer() then + return + end + if not target then + return + end + local caster = self:GetCaster() + local baseDamage = self:GetSpecialValueFor("damage") + local extra = getQueenOfPainManaFlatDamage(nil, self, caster) + local damage = baseDamage + extra + ApplyDamage({ + attacker = caster, + victim = target, + damage = damage, + damage_type = self:GetAbilityDamageType(), + ability = self + }) + if HasShard(nil, caster) and isQueenofpainScreamShardMarkTarget(nil, target) then + local dur = self:GetSpecialValueFor("shard_mark_duration") + ____exports.modifier_queenofpain_scream_shard_vulnerable:apply(target, caster, self, {duration = dur}) + end +end +queenofpain_scream_of_pain_custom = __TS__Decorate( + queenofpain_scream_of_pain_custom, + queenofpain_scream_of_pain_custom, + {registerAbility(nil)}, + {kind = "class", name = "queenofpain_scream_of_pain_custom"} +) +____exports.queenofpain_scream_of_pain_custom = queenofpain_scream_of_pain_custom +--- Шард: цель получает больше входящего урона от любых источников. +____exports.modifier_queenofpain_scream_shard_vulnerable = __TS__Class() +local modifier_queenofpain_scream_shard_vulnerable = ____exports.modifier_queenofpain_scream_shard_vulnerable +modifier_queenofpain_scream_shard_vulnerable.name = "modifier_queenofpain_scream_shard_vulnerable" +modifier_queenofpain_scream_shard_vulnerable.____file_path = "scripts/vscripts/abilities/heroes/queenofpain/queenofpain_scream_of_pain_custom.lua" +__TS__ClassExtends(modifier_queenofpain_scream_shard_vulnerable, BaseModifier) +function modifier_queenofpain_scream_shard_vulnerable.prototype.IsHidden(self) + return false +end +function modifier_queenofpain_scream_shard_vulnerable.prototype.IsDebuff(self) + return true +end +function modifier_queenofpain_scream_shard_vulnerable.prototype.IsPurgable(self) + return true +end +function modifier_queenofpain_scream_shard_vulnerable.prototype.GetAttributes(self) + return MODIFIER_ATTRIBUTE_MULTIPLE +end +function modifier_queenofpain_scream_shard_vulnerable.prototype.DeclareFunctions(self) + return {MODIFIER_PROPERTY_INCOMING_DAMAGE_PERCENTAGE} +end +function modifier_queenofpain_scream_shard_vulnerable.prototype.GetModifierIncomingDamage_Percentage(self) + local ab = self:GetAbility() + if not ab then + return 0 + end + return ab:GetSpecialValueFor("shard_incoming_damage_pct") +end +modifier_queenofpain_scream_shard_vulnerable = __TS__Decorate( + modifier_queenofpain_scream_shard_vulnerable, + modifier_queenofpain_scream_shard_vulnerable, + {registerModifier(nil)}, + {kind = "class", name = "modifier_queenofpain_scream_shard_vulnerable"} +) +____exports.modifier_queenofpain_scream_shard_vulnerable = modifier_queenofpain_scream_shard_vulnerable +return ____exports diff --git a/scripts/vscripts/abilities/heroes/queenofpain/queenofpain_shadow_strike_custom.lua b/scripts/vscripts/abilities/heroes/queenofpain/queenofpain_shadow_strike_custom.lua new file mode 100644 index 0000000..a593795 --- /dev/null +++ b/scripts/vscripts/abilities/heroes/queenofpain/queenofpain_shadow_strike_custom.lua @@ -0,0 +1,258 @@ +local ____lualib = require("lualib_bundle") +local __TS__Class = ____lualib.__TS__Class +local __TS__ClassExtends = ____lualib.__TS__ClassExtends +local __TS__Decorate = ____lualib.__TS__Decorate +local ____exports = {} +local ____dota_ts_adapter = require("lib.dota_ts_adapter") +local registerAbility = ____dota_ts_adapter.registerAbility +local BaseAbility = ____dota_ts_adapter.BaseAbility +local registerModifier = ____dota_ts_adapter.registerModifier +local BaseModifier = ____dota_ts_adapter.BaseModifier +local ____queenofpain_mana_bonus = require("abilities.heroes.queenofpain.queenofpain_mana_bonus") +local getQueenOfPainManaFlatDamage = ____queenofpain_mana_bonus.getQueenOfPainManaFlatDamage +____exports.queenofpain_shadow_strike_custom = __TS__Class() +local queenofpain_shadow_strike_custom = ____exports.queenofpain_shadow_strike_custom +queenofpain_shadow_strike_custom.name = "queenofpain_shadow_strike_custom" +queenofpain_shadow_strike_custom.____file_path = "scripts/vscripts/abilities/heroes/queenofpain/queenofpain_shadow_strike_custom.lua" +__TS__ClassExtends(queenofpain_shadow_strike_custom, BaseAbility) +function queenofpain_shadow_strike_custom.prototype.GetAOERadius(self) + return self:GetSpecialValueFor("radius") +end +function queenofpain_shadow_strike_custom.prototype.OnSpellStart(self) + local caster = self:GetCaster() + local target = self:GetCursorTarget() + caster:EmitSound("Hero_QueenOfPain.ShadowStrike") + local projectile_speed = self:GetSpecialValueFor("projectile_speed") + local caster_pfx = ParticleManager:CreateParticle("particles/units/heroes/hero_queenofpain/queen_shadow_strike_body.vpcf", PATTACH_ABSORIGIN_FOLLOW, caster) + ParticleManager:SetParticleControl( + caster_pfx, + 0, + caster:GetAbsOrigin() + ) + ParticleManager:SetParticleControl( + caster_pfx, + 1, + target:GetAbsOrigin() + ) + ParticleManager:SetParticleControl( + caster_pfx, + 3, + Vector(projectile_speed, 0, 0) + ) + ParticleManager:ReleaseParticleIndex(caster_pfx) + local radius = self:GetSpecialValueFor("radius") + local units = FindUnitsInRadius( + caster:GetTeamNumber(), + target:GetAbsOrigin(), + nil, + radius, + DOTA_UNIT_TARGET_TEAM_ENEMY, + DOTA_UNIT_TARGET_HERO + DOTA_UNIT_TARGET_BASIC, + DOTA_UNIT_TARGET_FLAG_NONE, + FIND_ANY_ORDER, + false + ) + local projectInfo = { + Target = target, + Source = caster, + Ability = self, + EffectName = "particles/units/heroes/hero_queenofpain/queen_shadow_strike.vpcf", + iMoveSpeed = projectile_speed, + vSourceLoc = caster:GetAbsOrigin(), + bDrawsOnMinimap = false, + bDodgeable = true, + bIsAttack = false, + bVisibleToEnemies = true, + bReplaceExisting = false, + bProvidesVision = false + } + for ____, unit in ipairs(units) do + projectInfo.Target = unit + ProjectileManager:CreateTrackingProjectile(projectInfo) + end +end +function queenofpain_shadow_strike_custom.prototype.OnProjectileHit(self, target, position) + if not target then + return + end + if target:TriggerSpellAbsorb(self) then + return + end + local caster = self:GetCaster() + local baseDamage = self:GetSpecialValueFor("strike_damage") + local extra = getQueenOfPainManaFlatDamage(nil, self, caster) + local damage = baseDamage + extra + local duration = self:GetSpecialValueFor("duration") + if target:HasModifier(____exports.modifier_queenofpain_shadow_strike_custom_debuff.name) then + self:ProcScepter(target) + end + ____exports.modifier_queenofpain_shadow_strike_custom_debuff:apply( + target, + self:GetCaster(), + self, + {duration = duration} + ) + ApplyDamage({ + victim = target, + attacker = caster, + damage = damage, + damage_type = self:GetAbilityDamageType(), + ability = self + }) +end +function queenofpain_shadow_strike_custom.prototype.ProcScepter(self, target) + local caster = self:GetCaster() + if not caster:HasScepter() then + return + end + local screamOfPain = caster:FindAbilityByName("queenofpain_scream_of_pain_custom") + if screamOfPain and screamOfPain:GetLevel() >= 1 then + screamOfPain:OnSpellStart(target) + end +end +queenofpain_shadow_strike_custom = __TS__Decorate( + queenofpain_shadow_strike_custom, + queenofpain_shadow_strike_custom, + {registerAbility(nil)}, + {kind = "class", name = "queenofpain_shadow_strike_custom"} +) +____exports.queenofpain_shadow_strike_custom = queenofpain_shadow_strike_custom +____exports.modifier_queenofpain_shadow_strike_custom_debuff = __TS__Class() +local modifier_queenofpain_shadow_strike_custom_debuff = ____exports.modifier_queenofpain_shadow_strike_custom_debuff +modifier_queenofpain_shadow_strike_custom_debuff.name = "modifier_queenofpain_shadow_strike_custom_debuff" +modifier_queenofpain_shadow_strike_custom_debuff.____file_path = "scripts/vscripts/abilities/heroes/queenofpain/queenofpain_shadow_strike_custom.lua" +__TS__ClassExtends(modifier_queenofpain_shadow_strike_custom_debuff, BaseModifier) +function modifier_queenofpain_shadow_strike_custom_debuff.prototype.____constructor(self, ...) + BaseModifier.prototype.____constructor(self, ...) + self.damage_interval = 0 + self.duration_damage = 0 + self.movement_slow = 0 +end +function modifier_queenofpain_shadow_strike_custom_debuff.prototype.IsHidden(self) + return false +end +function modifier_queenofpain_shadow_strike_custom_debuff.prototype.IsDebuff(self) + return true +end +function modifier_queenofpain_shadow_strike_custom_debuff.prototype.IsPurgable(self) + return true +end +function modifier_queenofpain_shadow_strike_custom_debuff.prototype.DeclareFunctions(self) + return {MODIFIER_PROPERTY_MOVESPEED_BONUS_PERCENTAGE, MODIFIER_EVENT_ON_ATTACK_LANDED} +end +function modifier_queenofpain_shadow_strike_custom_debuff.prototype.GetModifierMoveSpeedBonus_Percentage(self) + return self.movement_slow +end +function modifier_queenofpain_shadow_strike_custom_debuff.prototype.OnCreated(self) + local ability = self:GetAbility() + self.damage_interval = ability:GetSpecialValueFor("damage_interval") + self.duration_damage = ability:GetSpecialValueFor("duration_damage") + self.movement_slow = ability:GetSpecialValueFor("movement_slow") + if IsClient() then + return + end + local parent = self:GetParent() + local dagger_pfx = ParticleManager:CreateParticle("particles/units/heroes/hero_queenofpain/queen_shadow_strike_debuff.vpcf", PATTACH_POINT_FOLLOW, parent) + ParticleManager:SetParticleControlEnt( + dagger_pfx, + 0, + parent, + PATTACH_POINT_FOLLOW, + "attach_hitloc", + parent:GetAbsOrigin(), + true + ) + self:AddParticle( + dagger_pfx, + false, + false, + 0, + true, + false + ) + self:StartIntervalThink(self.damage_interval) +end +function modifier_queenofpain_shadow_strike_custom_debuff.prototype.OnDestroy(self) + if IsClient() then + return + end + local ability = self:GetAbility() + if ability ~= nil then + ability:ProcScepter(self:GetParent()) + end +end +function modifier_queenofpain_shadow_strike_custom_debuff.prototype.OnIntervalThink(self) + if IsClient() then + return + end + local ability = self:GetAbility() + if not ability then + return + end + local ac = ability:GetCaster() + local base = self.duration_damage + local extra = getQueenOfPainManaFlatDamage(nil, ability, ac) + local tickDamage = base + extra + ApplyDamage({ + victim = self:GetParent(), + attacker = ac, + damage = tickDamage, + damage_type = ability:GetAbilityDamageType(), + ability = ability + }) + SendOverheadEventMessage( + nil, + OVERHEAD_ALERT_BONUS_POISON_DAMAGE, + self:GetParent(), + tickDamage, + nil + ) +end +function modifier_queenofpain_shadow_strike_custom_debuff.prototype.OnAttackLanded(self, event) + local caster = self:GetCaster() + if not caster then + return + end + if event.attacker == caster and event.target == self:GetParent() then + local ability = self:GetAbility() + if ability and ability:GetSpecialValueFor("attack_speed") > 0 then + caster:AddNewModifier(caster, ability, ____exports.modifier_queenofpain_shadow_strike_custom_buff.name, {duration = 1}) + end + end +end +modifier_queenofpain_shadow_strike_custom_debuff = __TS__Decorate( + modifier_queenofpain_shadow_strike_custom_debuff, + modifier_queenofpain_shadow_strike_custom_debuff, + {registerModifier(nil)}, + {kind = "class", name = "modifier_queenofpain_shadow_strike_custom_debuff"} +) +____exports.modifier_queenofpain_shadow_strike_custom_debuff = modifier_queenofpain_shadow_strike_custom_debuff +____exports.modifier_queenofpain_shadow_strike_custom_buff = __TS__Class() +local modifier_queenofpain_shadow_strike_custom_buff = ____exports.modifier_queenofpain_shadow_strike_custom_buff +modifier_queenofpain_shadow_strike_custom_buff.name = "modifier_queenofpain_shadow_strike_custom_buff" +modifier_queenofpain_shadow_strike_custom_buff.____file_path = "scripts/vscripts/abilities/heroes/queenofpain/queenofpain_shadow_strike_custom.lua" +__TS__ClassExtends(modifier_queenofpain_shadow_strike_custom_buff, BaseModifier) +function modifier_queenofpain_shadow_strike_custom_buff.prototype.____constructor(self, ...) + BaseModifier.prototype.____constructor(self, ...) + self.bonusAttackSpeed = 0 +end +function modifier_queenofpain_shadow_strike_custom_buff.prototype.IsHidden(self) + return false +end +function modifier_queenofpain_shadow_strike_custom_buff.prototype.DeclareFunctions(self) + return {MODIFIER_PROPERTY_ATTACKSPEED_BONUS_CONSTANT} +end +function modifier_queenofpain_shadow_strike_custom_buff.prototype.OnCreated(self) + self.bonusAttackSpeed = self:GetAbility():GetSpecialValueFor("attack_speed") +end +function modifier_queenofpain_shadow_strike_custom_buff.prototype.GetModifierAttackSpeedBonus_Constant(self) + return self.bonusAttackSpeed +end +modifier_queenofpain_shadow_strike_custom_buff = __TS__Decorate( + modifier_queenofpain_shadow_strike_custom_buff, + modifier_queenofpain_shadow_strike_custom_buff, + {registerModifier(nil)}, + {kind = "class", name = "modifier_queenofpain_shadow_strike_custom_buff"} +) +____exports.modifier_queenofpain_shadow_strike_custom_buff = modifier_queenofpain_shadow_strike_custom_buff +return ____exports diff --git a/scripts/vscripts/abilities/heroes/rubick/ability_rubick_arcane_supremacy.lua b/scripts/vscripts/abilities/heroes/rubick/ability_rubick_arcane_supremacy.lua new file mode 100644 index 0000000..b36f8ad --- /dev/null +++ b/scripts/vscripts/abilities/heroes/rubick/ability_rubick_arcane_supremacy.lua @@ -0,0 +1,262 @@ +local ____lualib = require("lualib_bundle") +local Map = ____lualib.Map +local __TS__New = ____lualib.__TS__New +local __TS__ArrayFilter = ____lualib.__TS__ArrayFilter +local __TS__Class = ____lualib.__TS__Class +local __TS__ClassExtends = ____lualib.__TS__ClassExtends +local __TS__Decorate = ____lualib.__TS__Decorate +local ____exports = {} +local ____dota_ts_adapter = require("lib.dota_ts_adapter") +local BaseAbility = ____dota_ts_adapter.BaseAbility +local BaseModifier = ____dota_ts_adapter.BaseModifier +local registerAbility = ____dota_ts_adapter.registerAbility +local registerModifier = ____dota_ts_adapter.registerModifier +local isApplyingArcaneSupremacyDamage = false +local pendingDamageMap = __TS__New(Map) +local AGGREGATE_WINDOW = 0.15 +local function getPendingKey(self, victim, attacker) + return (tostring(victim:entindex()) .. "_") .. tostring(attacker:entindex()) +end +local function processArcaneSupremacyDamage(self, pending) + if not IsServer() then + return + end + local ____pending_0 = pending + local totalDamage = ____pending_0.totalDamage + local victim = ____pending_0.victim + local attacker = ____pending_0.attacker + local caster = ____pending_0.caster + local ability = ____pending_0.ability + if victim:IsNull() or attacker:IsNull() or caster:IsNull() or not ability then + return + end + local radius = ability:GetSpecialValueFor("aura_radius") + local allEnemies = FindUnitsInRadius( + caster:GetTeamNumber(), + caster:GetAbsOrigin(), + nil, + radius, + DOTA_UNIT_TARGET_TEAM_ENEMY, + bit.bor(DOTA_UNIT_TARGET_HERO, DOTA_UNIT_TARGET_BASIC), + DOTA_UNIT_TARGET_FLAG_NONE, + FIND_ANY_ORDER, + false + ) + local enemies = __TS__ArrayFilter( + allEnemies, + function(____, e) return e ~= victim end + ) + local enemyCount = #enemies + if enemyCount <= 0 then + return + end + local damagePct = ability:GetSpecialValueFor("damage_pct") * 0.01 + local finalDamage = totalDamage * damagePct / enemyCount + if finalDamage <= 0 then + return + end + isApplyingArcaneSupremacyDamage = true + local particleName = "particles/units/heroes/hero_rubick/rubick_finger_of_death_core_cloud.vpcf" + for ____, enemy in ipairs(enemies) do + do + if not enemy or enemy:IsNull() or not enemy:IsAlive() then + goto __continue9 + end + local particle = ParticleManager:CreateParticle(particleName, PATTACH_ABSORIGIN_FOLLOW, enemy) + ParticleManager:ReleaseParticleIndex(particle) + SendOverheadEventMessage( + nil, + OVERHEAD_ALERT_BONUS_SPELL_DAMAGE, + enemy, + finalDamage, + nil + ) + ApplyDamage({ + victim = enemy, + attacker = attacker, + damage = finalDamage, + damage_type = DAMAGE_TYPE_MAGICAL, + damage_flags = DOTA_DAMAGE_FLAG_NO_SPELL_AMPLIFICATION, + ability = ability + }) + end + ::__continue9:: + end + Timers:CreateTimer( + 0.034, + function() + isApplyingArcaneSupremacyDamage = false + return nil + end + ) +end +____exports.ability_rubick_arcane_supremacy = __TS__Class() +local ability_rubick_arcane_supremacy = ____exports.ability_rubick_arcane_supremacy +ability_rubick_arcane_supremacy.name = "ability_rubick_arcane_supremacy" +ability_rubick_arcane_supremacy.____file_path = "scripts/vscripts/abilities/heroes/rubick/ability_rubick_arcane_supremacy.lua" +__TS__ClassExtends(ability_rubick_arcane_supremacy, BaseAbility) +function ability_rubick_arcane_supremacy.prototype.GetIntrinsicModifierName(self) + return ____exports.modifier_rubick_arcane_supremacy_aura.name +end +function ability_rubick_arcane_supremacy.prototype.Precache(self, context) + PrecacheResource("particle", "particles/units/heroes/hero_rubick/rubick_finger_of_death_core_cloud.vpcf", context) +end +function ability_rubick_arcane_supremacy.prototype.GetAOERadius(self) + return self:GetSpecialValueFor("aura_radius") +end +ability_rubick_arcane_supremacy = __TS__Decorate( + ability_rubick_arcane_supremacy, + ability_rubick_arcane_supremacy, + {registerAbility(nil)}, + {kind = "class", name = "ability_rubick_arcane_supremacy"} +) +____exports.ability_rubick_arcane_supremacy = ability_rubick_arcane_supremacy +____exports.modifier_rubick_arcane_supremacy_aura = __TS__Class() +local modifier_rubick_arcane_supremacy_aura = ____exports.modifier_rubick_arcane_supremacy_aura +modifier_rubick_arcane_supremacy_aura.name = "modifier_rubick_arcane_supremacy_aura" +modifier_rubick_arcane_supremacy_aura.____file_path = "scripts/vscripts/abilities/heroes/rubick/ability_rubick_arcane_supremacy.lua" +__TS__ClassExtends(modifier_rubick_arcane_supremacy_aura, BaseModifier) +function modifier_rubick_arcane_supremacy_aura.prototype.IsHidden(self) + return true +end +function modifier_rubick_arcane_supremacy_aura.prototype.IsPurgable(self) + return false +end +function modifier_rubick_arcane_supremacy_aura.prototype.IsDebuff(self) + return false +end +function modifier_rubick_arcane_supremacy_aura.prototype.RemoveOnDeath(self) + return true +end +function modifier_rubick_arcane_supremacy_aura.prototype.IsAura(self) + return true +end +function modifier_rubick_arcane_supremacy_aura.prototype.GetModifierAura(self) + return ____exports.modifier_rubick_arcane_supremacy_enemy.name +end +function modifier_rubick_arcane_supremacy_aura.prototype.GetAuraRadius(self) + return self:GetAbility():GetSpecialValueFor("aura_radius") +end +function modifier_rubick_arcane_supremacy_aura.prototype.GetAuraDuration(self) + return 0.5 +end +function modifier_rubick_arcane_supremacy_aura.prototype.GetAuraSearchTeam(self) + return DOTA_UNIT_TARGET_TEAM_ENEMY +end +function modifier_rubick_arcane_supremacy_aura.prototype.GetAuraSearchType(self) + return bit.bor(DOTA_UNIT_TARGET_HERO, DOTA_UNIT_TARGET_BASIC) +end +function modifier_rubick_arcane_supremacy_aura.prototype.GetAuraSearchFlags(self) + return DOTA_UNIT_TARGET_FLAG_NONE +end +function modifier_rubick_arcane_supremacy_aura.prototype.DeclareFunctions(self) + return {MODIFIER_PROPERTY_SPELL_AMPLIFY_PERCENTAGE} +end +function modifier_rubick_arcane_supremacy_aura.prototype.GetModifierSpellAmplify_Percentage(self, event) + if self:GetParent():PassivesDisabled() then + return 0 + end + return self:GetAbility():GetSpecialValueFor("spell_amp") + self:GetAbility():GetSpecialValueFor("spell_amp_pct_lvl") * self:GetCaster():GetLevel() +end +modifier_rubick_arcane_supremacy_aura = __TS__Decorate( + modifier_rubick_arcane_supremacy_aura, + modifier_rubick_arcane_supremacy_aura, + {registerModifier(nil)}, + {kind = "class", name = "modifier_rubick_arcane_supremacy_aura"} +) +____exports.modifier_rubick_arcane_supremacy_aura = modifier_rubick_arcane_supremacy_aura +____exports.modifier_rubick_arcane_supremacy_enemy = __TS__Class() +local modifier_rubick_arcane_supremacy_enemy = ____exports.modifier_rubick_arcane_supremacy_enemy +modifier_rubick_arcane_supremacy_enemy.name = "modifier_rubick_arcane_supremacy_enemy" +modifier_rubick_arcane_supremacy_enemy.____file_path = "scripts/vscripts/abilities/heroes/rubick/ability_rubick_arcane_supremacy.lua" +__TS__ClassExtends(modifier_rubick_arcane_supremacy_enemy, BaseModifier) +function modifier_rubick_arcane_supremacy_enemy.prototype.IsHidden(self) + return true +end +function modifier_rubick_arcane_supremacy_enemy.prototype.IsPurgable(self) + return false +end +function modifier_rubick_arcane_supremacy_enemy.prototype.IsDebuff(self) + return true +end +function modifier_rubick_arcane_supremacy_enemy.prototype.RemoveOnDeath(self) + return true +end +function modifier_rubick_arcane_supremacy_enemy.prototype.DeclareFunctions(self) + return {MODIFIER_EVENT_ON_TAKEDAMAGE} +end +function modifier_rubick_arcane_supremacy_enemy.prototype.OnTakeDamage(self, event) + if not IsServer() then + return + end + if isApplyingArcaneSupremacyDamage then + return + end + local victim = event.unit + local attacker = event.attacker + local damage = event.damage + local damageType = event.damage_type + if not victim or not attacker then + return + end + local caster = self:GetCaster() + local ability = self:GetAbility() + if not caster or not ability or caster:IsNull() or ability:IsNull() then + return + end + if damageType ~= DAMAGE_TYPE_MAGICAL then + return + end + if caster:PassivesDisabled() then + return + end + local radius = ability:GetSpecialValueFor("aura_radius") + local isCaster = attacker == caster + local dist = (attacker:GetAbsOrigin() - caster:GetAbsOrigin()):Length2D() + local isAllyInRadius = attacker:GetTeamNumber() == caster:GetTeamNumber() and dist <= radius + if not isCaster and not isAllyInRadius then + return + end + local key = getPendingKey(nil, victim, attacker) + local existing = pendingDamageMap:get(key) + if existing then + existing.totalDamage = existing.totalDamage + damage + if not existing.timerScheduled then + existing.timerScheduled = true + Timers:CreateTimer( + AGGREGATE_WINDOW, + function() + processArcaneSupremacyDamage(nil, existing) + pendingDamageMap:delete(key) + return nil + end + ) + end + return + end + local pending = { + totalDamage = damage, + victim = victim, + attacker = attacker, + caster = caster, + ability = ability, + timerScheduled = true + } + pendingDamageMap:set(key, pending) + Timers:CreateTimer( + AGGREGATE_WINDOW, + function() + processArcaneSupremacyDamage(nil, pending) + pendingDamageMap:delete(key) + return nil + end + ) +end +modifier_rubick_arcane_supremacy_enemy = __TS__Decorate( + modifier_rubick_arcane_supremacy_enemy, + modifier_rubick_arcane_supremacy_enemy, + {registerModifier(nil)}, + {kind = "class", name = "modifier_rubick_arcane_supremacy_enemy"} +) +____exports.modifier_rubick_arcane_supremacy_enemy = modifier_rubick_arcane_supremacy_enemy +return ____exports diff --git a/scripts/vscripts/abilities/heroes/rubick/ability_rubick_fade_bolt_custom.lua b/scripts/vscripts/abilities/heroes/rubick/ability_rubick_fade_bolt_custom.lua new file mode 100644 index 0000000..9e33095 --- /dev/null +++ b/scripts/vscripts/abilities/heroes/rubick/ability_rubick_fade_bolt_custom.lua @@ -0,0 +1,377 @@ +local ____lualib = require("lualib_bundle") +local __TS__Class = ____lualib.__TS__Class +local __TS__ClassExtends = ____lualib.__TS__ClassExtends +local __TS__Decorate = ____lualib.__TS__Decorate +local Map = ____lualib.Map +local __TS__New = ____lualib.__TS__New +local __TS__ArrayFind = ____lualib.__TS__ArrayFind +local ____exports = {} +local addFadeBoltSpellAmpStacks +local ____dota_ts_adapter = require("lib.dota_ts_adapter") +local BaseAbility = ____dota_ts_adapter.BaseAbility +local BaseModifier = ____dota_ts_adapter.BaseModifier +local registerAbility = ____dota_ts_adapter.registerAbility +local registerModifier = ____dota_ts_adapter.registerModifier +function addFadeBoltSpellAmpStacks(self, caster, ability, duration, reducedAmount) + if reducedAmount <= 0 then + return + end + local talent = caster and caster:FindAbilityByName("special_bonus_unique_rubick_fade_bolt_convert") + if not talent or talent:GetLevel() <= 0 then + return + end + local existing = caster:FindModifierByName(____exports.modifier_rubick_fade_bolt_spell_amp.name) + if existing then + existing:AddStacks(duration, reducedAmount) + else + caster:AddNewModifier(caster, ability, ____exports.modifier_rubick_fade_bolt_spell_amp.name, {duration = duration, added_stacks = reducedAmount}) + end +end +local FADE_BOLT_PARTICLE = "particles/units/heroes/hero_rubick/rubick_fade_bolt.vpcf" +local fadeBoltCastId = 0 +--- Fade Bolt — вторая способность Рубика. +-- Луч от кастера к первому врагу, далее отскоки по цепочке через IntervalThink (jump_delay). +____exports.ability_rubick_fade_bolt_custom = __TS__Class() +local ability_rubick_fade_bolt_custom = ____exports.ability_rubick_fade_bolt_custom +ability_rubick_fade_bolt_custom.name = "ability_rubick_fade_bolt_custom" +ability_rubick_fade_bolt_custom.____file_path = "scripts/vscripts/abilities/heroes/rubick/ability_rubick_fade_bolt_custom.lua" +__TS__ClassExtends(ability_rubick_fade_bolt_custom, BaseAbility) +function ability_rubick_fade_bolt_custom.prototype.Precache(self, context) + PrecacheResource("particle", FADE_BOLT_PARTICLE, context) + PrecacheResource("soundfile", "soundevents/game_sounds_heroes/game_sounds_rubick.vsndevts", context) +end +function ability_rubick_fade_bolt_custom.prototype.OnSpellStart(self) + if not IsServer() then + return + end + local caster = self:GetCaster() + local target = self:GetCursorTarget() + if not target or not target:IsAlive() then + return + end + EmitSoundOn("Hero_Rubick.FadeBolt.Cast", caster) + fadeBoltCastId = fadeBoltCastId + 1 + local castId = fadeBoltCastId + print(((((((("[fade_bolt] cast start castId=" .. tostring(castId)) .. " caster=") .. caster:GetUnitName()) .. " target=") .. target:GetUnitName()) .. "(") .. tostring(target:entindex())) .. ")") + local damage = self:GetSpecialValueFor("damage") + local duration = self:GetSpecialValueFor("duration") + local attackDamageReduction = self:GetSpecialValueFor("attack_damage_reduction") + local jumpDamageReductionPct = self:GetSpecialValueFor("jump_damage_reduction_pct") + local nextDamage = damage * (1 - jumpDamageReductionPct / 100) + print((((((((((("[fade_bolt] cast params castId=" .. tostring(castId)) .. " damage=") .. tostring(damage)) .. " nextDamage=") .. tostring(nextDamage)) .. " jumpReducePct=") .. tostring(jumpDamageReductionPct)) .. " radius=") .. tostring(self:GetSpecialValueFor("radius"))) .. " jumpDelay=") .. tostring(self:GetSpecialValueFor("jump_delay"))) + if damage > 0 then + ApplyDamage({ + victim = target, + attacker = caster, + damage = damage, + damage_type = DAMAGE_TYPE_MAGICAL, + ability = self + }) + SendOverheadEventMessage( + nil, + OVERHEAD_ALERT_BONUS_SPELL_DAMAGE, + target, + math.floor(damage), + nil + ) + end + target:AddNewModifier(caster, self, ____exports.modifier_rubick_fade_bolt_debuff.name, {duration = duration, attack_damage_reduction = attackDamageReduction}) + local reducedAmount = math.floor((target:GetAttackDamage() or 0) * (attackDamageReduction / 100)) + addFadeBoltSpellAmpStacks( + nil, + caster, + self, + duration, + reducedAmount + ) + EmitSoundOn("Hero_Rubick.FadeBolt.Target", target) + local projectileSpeed = self:GetSpecialValueFor("projectile_speed") + ProjectileManager:CreateTrackingProjectile({ + Ability = self, + EffectName = FADE_BOLT_PARTICLE, + Source = caster, + Target = target, + iMoveSpeed = projectileSpeed, + bDodgeable = false, + bVisibleToEnemies = true + }) + if nextDamage >= 1 then + local pos = target:GetAbsOrigin() + print((((((((("[fade_bolt] thinker create castId=" .. tostring(castId)) .. " fromTarget=") .. tostring(target:entindex())) .. " pos=") .. tostring(pos.x)) .. ",") .. tostring(pos.y)) .. ",") .. tostring(pos.z)) + caster:AddNewModifier( + caster, + self, + ____exports.modifier_rubick_fade_bolt_thinker.name, + { + duration = 10, + cast_id = castId, + last_target_entindex = target:entindex(), + last_pos_x = pos.x, + last_pos_y = pos.y, + last_pos_z = pos.z, + current_damage = nextDamage + } + ) + else + print(("[fade_bolt] stop castId=" .. tostring(castId)) .. " reason=nextDamage_below_1") + end +end +ability_rubick_fade_bolt_custom = __TS__Decorate( + ability_rubick_fade_bolt_custom, + ability_rubick_fade_bolt_custom, + {registerAbility(nil)}, + {kind = "class", name = "ability_rubick_fade_bolt_custom"} +) +____exports.ability_rubick_fade_bolt_custom = ability_rubick_fade_bolt_custom +--- Модификатор-тикер: каждые jump_delay ищет следующую цель, наносит урон, создаёт визуал болта. +____exports.modifier_rubick_fade_bolt_thinker = __TS__Class() +local modifier_rubick_fade_bolt_thinker = ____exports.modifier_rubick_fade_bolt_thinker +modifier_rubick_fade_bolt_thinker.name = "modifier_rubick_fade_bolt_thinker" +modifier_rubick_fade_bolt_thinker.____file_path = "scripts/vscripts/abilities/heroes/rubick/ability_rubick_fade_bolt_custom.lua" +__TS__ClassExtends(modifier_rubick_fade_bolt_thinker, BaseModifier) +function modifier_rubick_fade_bolt_thinker.prototype.____constructor(self, ...) + BaseModifier.prototype.____constructor(self, ...) + self.castId = 0 + self.lastTargetEntindex = 0 + self.lastPos = Vector(0, 0, 0) + self.currentDamage = 0 + self.hitUnits = __TS__New(Map) +end +function modifier_rubick_fade_bolt_thinker.prototype.IsHidden(self) + return true +end +function modifier_rubick_fade_bolt_thinker.prototype.IsPurgable(self) + return false +end +function modifier_rubick_fade_bolt_thinker.prototype.OnCreated(self, params) + if not IsServer() then + return + end + self.castId = tonumber(tostring(params.cast_id or 0)) or 0 + self.lastTargetEntindex = tonumber(tostring(params.last_target_entindex or 0)) or 0 + local lastPosX = tonumber(tostring(params.last_pos_x or 0)) or 0 + local lastPosY = tonumber(tostring(params.last_pos_y or 0)) or 0 + local lastPosZ = tonumber(tostring(params.last_pos_z or 0)) or 0 + self.lastPos = Vector(lastPosX, lastPosY, lastPosZ) + self.currentDamage = tonumber(tostring(params.current_damage or 0)) or 0 + if self.lastTargetEntindex > 0 then + self.hitUnits:set(self.lastTargetEntindex, 1) + end + local ____opt_0 = self:GetAbility() + local jumpDelay = ____opt_0 and ____opt_0:GetSpecialValueFor("jump_delay") or 0.25 + print((((((((((((("[fade_bolt] thinker created castId=" .. tostring(self.castId)) .. " lastTarget=") .. tostring(self.lastTargetEntindex)) .. " pos=") .. tostring(lastPosX)) .. ",") .. tostring(lastPosY)) .. ",") .. tostring(lastPosZ)) .. " currentDamage=") .. tostring(self.currentDamage)) .. " jumpDelay=") .. tostring(jumpDelay)) + self:StartIntervalThink(jumpDelay) +end +function modifier_rubick_fade_bolt_thinker.prototype.OnIntervalThink(self) + if not IsServer() then + return + end + local caster = self:GetCaster() + local ability = self:GetAbility() + if not caster or not ability or caster:IsNull() or ability:IsNull() then + self:Destroy() + return + end + local baseRadius = ability:GetSpecialValueFor("radius") + local baseJumpDamageReductionPct = ability:GetSpecialValueFor("jump_damage_reduction_pct") + local duration = ability:GetSpecialValueFor("duration") + local attackDamageReduction = ability:GetSpecialValueFor("attack_damage_reduction") + local projectileSpeed = ability:GetSpecialValueFor("projectile_speed") + local hasShard = caster:HasModifier("modifier_item_aghanims_shard") + local hasScepter = caster:HasScepter() + local radius = hasScepter and baseRadius * 1.35 or baseRadius + local jumpDamageReductionPct = hasScepter and baseJumpDamageReductionPct * 0.5 or baseJumpDamageReductionPct + local maxHitsPerTarget = hasShard and 2 or 1 + local nextDamage = self.currentDamage * (1 - jumpDamageReductionPct / 100) + if nextDamage < 1 then + print((("[fade_bolt] stop castId=" .. tostring(self.castId)) .. " reason=next_damage_below_1 nextDamage=") .. tostring(nextDamage)) + self:Destroy() + return + end + local enemies = FindUnitsInRadius( + caster:GetTeamNumber(), + self.lastPos, + nil, + radius, + DOTA_UNIT_TARGET_TEAM_ENEMY, + bit.bor(DOTA_UNIT_TARGET_HERO, DOTA_UNIT_TARGET_BASIC), + DOTA_UNIT_TARGET_FLAG_NONE, + FIND_CLOSEST, + false + ) + print((((((((((((((((("[fade_bolt] tick castId=" .. tostring(self.castId)) .. " fromTarget=") .. tostring(self.lastTargetEntindex)) .. " currentDamage=") .. tostring(self.currentDamage)) .. " nextDamage=") .. tostring(nextDamage)) .. " radius=") .. tostring(radius)) .. " enemiesFound=") .. tostring(#enemies)) .. " visitedTargets=") .. tostring(self.hitUnits.size)) .. " maxHitsPerTarget=") .. tostring(maxHitsPerTarget)) .. " hasScepter=") .. tostring(hasScepter)) + local nextTarget = __TS__ArrayFind( + enemies, + function(____, e) + if not e:IsAlive() then + return false + end + local hits = self.hitUnits:get(e:entindex()) or 0 + return hits < maxHitsPerTarget + end + ) + if not nextTarget then + print(("[fade_bolt] stop castId=" .. tostring(self.castId)) .. " reason=no_next_target") + self:Destroy() + return + end + local nextTargetEntIndex = nextTarget:entindex() + local currentHits = self.hitUnits:get(nextTargetEntIndex) or 0 + self.hitUnits:set(nextTargetEntIndex, currentHits + 1) + print((((((((("[fade_bolt] jump castId=" .. tostring(self.castId)) .. " nextTarget=") .. nextTarget:GetUnitName()) .. "(") .. tostring(nextTargetEntIndex)) .. ") hitCount=") .. tostring(currentHits + 1)) .. "/") .. tostring(maxHitsPerTarget)) + if self.currentDamage > 0 then + ApplyDamage({ + victim = nextTarget, + attacker = caster, + damage = self.currentDamage, + damage_type = DAMAGE_TYPE_MAGICAL, + ability = ability + }) + SendOverheadEventMessage( + nil, + OVERHEAD_ALERT_BONUS_SPELL_DAMAGE, + nextTarget, + math.floor(self.currentDamage), + nil + ) + end + nextTarget:AddNewModifier(caster, ability, ____exports.modifier_rubick_fade_bolt_debuff.name, {duration = duration, attack_damage_reduction = attackDamageReduction}) + local reducedAmount = math.floor((nextTarget:GetAttackDamage() or 0) * (attackDamageReduction / 100)) + addFadeBoltSpellAmpStacks( + nil, + caster, + ability, + duration, + reducedAmount + ) + EmitSoundOn("Hero_Rubick.FadeBolt.Target", nextTarget) + local lastTarget = EntIndexToHScript(self.lastTargetEntindex) + if lastTarget and not lastTarget:IsNull() and lastTarget:IsAlive() then + ProjectileManager:CreateTrackingProjectile({ + Ability = ability, + EffectName = FADE_BOLT_PARTICLE, + Source = lastTarget, + Target = nextTarget, + iMoveSpeed = projectileSpeed, + bDodgeable = false, + bVisibleToEnemies = true + }) + end + self.lastTargetEntindex = nextTarget:entindex() + self.lastPos = nextTarget:GetAbsOrigin() + self.currentDamage = nextDamage +end +function modifier_rubick_fade_bolt_thinker.prototype.OnDestroy(self) + if IsServer() then + print("[fade_bolt] thinker destroy castId=" .. tostring(self.castId)) + end +end +modifier_rubick_fade_bolt_thinker = __TS__Decorate( + modifier_rubick_fade_bolt_thinker, + modifier_rubick_fade_bolt_thinker, + {registerModifier(nil)}, + {kind = "class", name = "modifier_rubick_fade_bolt_thinker"} +) +____exports.modifier_rubick_fade_bolt_thinker = modifier_rubick_fade_bolt_thinker +--- Дебафф Fade Bolt — снижает урон от атак на X%%. +____exports.modifier_rubick_fade_bolt_debuff = __TS__Class() +local modifier_rubick_fade_bolt_debuff = ____exports.modifier_rubick_fade_bolt_debuff +modifier_rubick_fade_bolt_debuff.name = "modifier_rubick_fade_bolt_debuff" +modifier_rubick_fade_bolt_debuff.____file_path = "scripts/vscripts/abilities/heroes/rubick/ability_rubick_fade_bolt_custom.lua" +__TS__ClassExtends(modifier_rubick_fade_bolt_debuff, BaseModifier) +function modifier_rubick_fade_bolt_debuff.prototype.____constructor(self, ...) + BaseModifier.prototype.____constructor(self, ...) + self.attack_damage_reduction = 0 +end +function modifier_rubick_fade_bolt_debuff.prototype.IsHidden(self) + return false +end +function modifier_rubick_fade_bolt_debuff.prototype.IsPurgable(self) + return true +end +function modifier_rubick_fade_bolt_debuff.prototype.IsDebuff(self) + return true +end +function modifier_rubick_fade_bolt_debuff.prototype.OnCreated(self, params) + self.attack_damage_reduction = params.attack_damage_reduction or 0 +end +function modifier_rubick_fade_bolt_debuff.prototype.DeclareFunctions(self) + return {MODIFIER_PROPERTY_DAMAGEOUTGOING_PERCENTAGE} +end +function modifier_rubick_fade_bolt_debuff.prototype.GetModifierDamageOutgoing_Percentage(self) + return -self.attack_damage_reduction +end +function modifier_rubick_fade_bolt_debuff.prototype.GetEffectName(self) + return "particles/units/heroes/hero_rubick/rubick_fade_bolt_debuff.vpcf" +end +function modifier_rubick_fade_bolt_debuff.prototype.GetEffectAttachType(self) + return PATTACH_ABSORIGIN_FOLLOW +end +modifier_rubick_fade_bolt_debuff = __TS__Decorate( + modifier_rubick_fade_bolt_debuff, + modifier_rubick_fade_bolt_debuff, + {registerModifier(nil)}, + {kind = "class", name = "modifier_rubick_fade_bolt_debuff"} +) +____exports.modifier_rubick_fade_bolt_debuff = modifier_rubick_fade_bolt_debuff +--- Бонус spell amp: стаки = накопленное «количество уменьшенного урона» (урон атаки врага × снижение %). Spell amp % = стаки / 2 + талант. +____exports.modifier_rubick_fade_bolt_spell_amp = __TS__Class() +local modifier_rubick_fade_bolt_spell_amp = ____exports.modifier_rubick_fade_bolt_spell_amp +modifier_rubick_fade_bolt_spell_amp.name = "modifier_rubick_fade_bolt_spell_amp" +modifier_rubick_fade_bolt_spell_amp.____file_path = "scripts/vscripts/abilities/heroes/rubick/ability_rubick_fade_bolt_custom.lua" +__TS__ClassExtends(modifier_rubick_fade_bolt_spell_amp, BaseModifier) +function modifier_rubick_fade_bolt_spell_amp.prototype.____constructor(self, ...) + BaseModifier.prototype.____constructor(self, ...) + self.totalReduced = 0 +end +function modifier_rubick_fade_bolt_spell_amp.prototype.IsHidden(self) + local ____opt_4 = self:GetCaster() + local talent = ____opt_4 and ____opt_4:FindAbilityByName("special_bonus_unique_rubick_fade_bolt_convert") + if not talent or talent:GetLevel() <= 0 then + return true + end + return false +end +function modifier_rubick_fade_bolt_spell_amp.prototype.IsPurgable(self) + return true +end +function modifier_rubick_fade_bolt_spell_amp.prototype.IsDebuff(self) + return false +end +function modifier_rubick_fade_bolt_spell_amp.prototype.OnCreated(self, params) + local add = math.floor(params.added_stacks or 0) + self.totalReduced = add + self:SetStackCount(add) +end +function modifier_rubick_fade_bolt_spell_amp.prototype.AddStacks(self, duration, reducedAmount) + local add = math.floor(reducedAmount) + if add <= 0 then + return + end + self.totalReduced = self.totalReduced + add + self:SetStackCount(self.totalReduced) + self:SetDuration(duration, true) +end +function modifier_rubick_fade_bolt_spell_amp.prototype.DeclareFunctions(self) + return {MODIFIER_PROPERTY_SPELL_AMPLIFY_PERCENTAGE} +end +function modifier_rubick_fade_bolt_spell_amp.prototype.GetModifierSpellAmplify_Percentage(self) + local caster = self:GetCaster() + if not caster then + return 0 + end + local talent = caster:FindAbilityByName("special_bonus_unique_rubick_fade_bolt_convert") + if not talent or talent:GetLevel() <= 0 then + return 0 + end + local ampFromStacks = self:GetStackCount() + return ampFromStacks +end +modifier_rubick_fade_bolt_spell_amp = __TS__Decorate( + modifier_rubick_fade_bolt_spell_amp, + modifier_rubick_fade_bolt_spell_amp, + {registerModifier(nil)}, + {kind = "class", name = "modifier_rubick_fade_bolt_spell_amp"} +) +____exports.modifier_rubick_fade_bolt_spell_amp = modifier_rubick_fade_bolt_spell_amp +return ____exports diff --git a/scripts/vscripts/abilities/heroes/rubick/ability_rubick_spellsteal_custom.lua b/scripts/vscripts/abilities/heroes/rubick/ability_rubick_spellsteal_custom.lua new file mode 100644 index 0000000..cf8b824 --- /dev/null +++ b/scripts/vscripts/abilities/heroes/rubick/ability_rubick_spellsteal_custom.lua @@ -0,0 +1,254 @@ +local ____lualib = require("lualib_bundle") +local Set = ____lualib.Set +local __TS__New = ____lualib.__TS__New +local Map = ____lualib.Map +local __TS__Class = ____lualib.__TS__Class +local __TS__ClassExtends = ____lualib.__TS__ClassExtends +local __TS__ArrayIncludes = ____lualib.__TS__ArrayIncludes +local __TS__Decorate = ____lualib.__TS__Decorate +local ____exports = {} +local isHeroAbilityStealable, getLastUsedAbilityFromUnit, UNSTEALABLE_NAMES +local ____dota_ts_adapter = require("lib.dota_ts_adapter") +local BaseAbility = ____dota_ts_adapter.BaseAbility +local BaseModifier = ____dota_ts_adapter.BaseModifier +local registerAbility = ____dota_ts_adapter.registerAbility +local registerModifier = ____dota_ts_adapter.registerModifier +function isHeroAbilityStealable(self, hero, abilityName) + if not abilityName then + return false + end + if UNSTEALABLE_NAMES:has(abilityName) then + return false + end + local ability = hero:FindAbilityByName(abilityName) + if not ability or ability:IsNull() then + return false + end + if ability:IsItem() or ability:IsHidden() then + return false + end + if ability:IsPassive() then + return false + end + if ability:GetLevel() <= 0 then + return false + end + return true +end +function getLastUsedAbilityFromUnit(self, unit) + local best = nil + do + local i = 0 + while i < 16 do + do + local ab = unit:GetAbilityByIndex(i) + if not ab or ab:IsNull() then + goto __continue13 + end + local name = ab:GetAbilityName() + if not isHeroAbilityStealable(nil, unit, name) then + goto __continue13 + end + local cd = ab:GetCooldownTimeRemaining() + if cd > 0 and (not best or cd < best.cd) then + best = {name = name, cd = cd} + end + end + ::__continue13:: + i = i + 1 + end + end + return best and best.name or "" +end +local STOLEN_SLOT_1 = 3 +local STOLEN_SLOT_2 = 4 +local EMPTY_SLOTS = {"rubick_empty1", "rubick_empty2"} +UNSTEALABLE_NAMES = __TS__New(Set, {"ability_rubick_spellsteal_custom"}) +local lastAbilityByUnit = __TS__New(Map) +local function getStealableAbilityFromTarget(self, target) + if not target:IsRealHero() then + return "" + end + local heroTarget = target + local lastUsedName = lastAbilityByUnit:get(target:entindex()) or "" + if isHeroAbilityStealable(nil, heroTarget, lastUsedName) then + return lastUsedName + end + return getLastUsedAbilityFromUnit(nil, heroTarget) +end +if IsServer() then + ListenToGameEvent( + "dota_player_used_ability", + function(event) + local name = event.abilityname + local playerId = event.PlayerID + if not name or playerId == nil then + return + end + local ____opt_2 = PlayerResource:GetPlayer(playerId) + local hero = ____opt_2 and ____opt_2:GetAssignedHero() + if hero and not hero:IsNull() and hero:IsRealHero() and isHeroAbilityStealable(nil, hero, name) then + lastAbilityByUnit:set( + hero:entindex(), + name + ) + print((((("[SpellSteal] dota_player_used_ability: " .. hero:GetUnitName()) .. " (") .. tostring(hero:entindex())) .. ") cast ") .. name) + end + end, + nil + ) +end +____exports.ability_rubick_spellsteal_custom = __TS__Class() +local ability_rubick_spellsteal_custom = ____exports.ability_rubick_spellsteal_custom +ability_rubick_spellsteal_custom.name = "ability_rubick_spellsteal_custom" +ability_rubick_spellsteal_custom.____file_path = "scripts/vscripts/abilities/heroes/rubick/ability_rubick_spellsteal_custom.lua" +__TS__ClassExtends(ability_rubick_spellsteal_custom, BaseAbility) +function ability_rubick_spellsteal_custom.prototype.Precache(self, context) + PrecacheResource("particle", "particles/econ/items/rubick/rubick_arcana/rubick_arc_loadout_spell_steal.vpcf", context) + PrecacheResource("soundfile", "soundevents/game_sounds_heroes/game_sounds_rubick.vsndevts", context) +end +function ability_rubick_spellsteal_custom.prototype.CastFilterResultTarget(self, target) + local caster = self:GetCaster() + if target == caster then + return UF_FAIL_CUSTOM + end + local result = UnitFilter( + target, + DOTA_UNIT_TARGET_TEAM_FRIENDLY, + DOTA_UNIT_TARGET_HERO, + DOTA_UNIT_TARGET_FLAG_NONE, + caster:GetTeamNumber() + ) + if result ~= UF_SUCCESS then + return result + end + if not target:IsRealHero() then + return UF_FAIL_CUSTOM + end + local lastAbilityName = getStealableAbilityFromTarget(nil, target) + if not lastAbilityName then + return UF_FAIL_CUSTOM + end + if UNSTEALABLE_NAMES:has(lastAbilityName) then + return UF_FAIL_CUSTOM + end + return UF_SUCCESS +end +function ability_rubick_spellsteal_custom.prototype.GetCustomCastErrorTarget(self, target) + local caster = self:GetCaster() + if target == caster then + return "#dota_hud_error_rubick_spellsteal_self" + end + local lastAbilityName = getStealableAbilityFromTarget(nil, target) + if not lastAbilityName then + return "#dota_hud_error_rubick_spellsteal_no_ability" + end + if UNSTEALABLE_NAMES:has(lastAbilityName) then + return "#dota_hud_error_rubick_spellsteal_unstealable" + end + return "" +end +function ability_rubick_spellsteal_custom.prototype.OnSpellStart(self) + if not IsServer() then + return + end + local ability = self + local caster = ability:GetCaster() + local target = ability:GetCursorTarget() + if not caster or not target or target:IsNull() or target == caster then + return + end + if not target:IsRealHero() then + EmitSoundOn("Hero_Rubick.SpellSteal.Fail", caster) + return + end + local duration = -1 + print(((((("[SpellSteal] OnSpellStart: caster=" .. caster:GetUnitName()) .. ", target=") .. target:GetUnitName()) .. " (") .. tostring(target:entindex())) .. ")") + local lastAbilityName = getStealableAbilityFromTarget(nil, target) + if not lastAbilityName or UNSTEALABLE_NAMES:has(lastAbilityName) then + EmitSoundOn("Hero_Rubick.SpellSteal.Fail", caster) + return + end + print("[SpellSteal] stealing: " .. lastAbilityName) + local targetAbility = target:FindAbilityByName(lastAbilityName) + local stolenLevel = targetAbility and targetAbility:GetLevel() or 1 + local hasAghs = caster:HasScepter() + local slot1Ability = caster:GetAbilityByIndex(STOLEN_SLOT_1) + local slot1Name = slot1Ability and not slot1Ability:IsNull() and slot1Ability:GetAbilityName() or nil + local slot2Ability = caster:GetAbilityByIndex(STOLEN_SLOT_2) + local slot2Name = slot2Ability and not slot2Ability:IsNull() and slot2Ability:GetAbilityName() or nil + if lastAbilityName ~= slot1Name and lastAbilityName ~= slot2Name then + local newAbility = caster:AddAbility(lastAbilityName) + if newAbility and not newAbility:IsNull() then + newAbility:SetLevel(math.min( + stolenLevel, + newAbility:GetMaxLevel() + )) + newAbility:SetStolen(true) + if slot1Name then + caster:SwapAbilities(slot1Name, lastAbilityName, false, true) + if hasAghs and not __TS__ArrayIncludes(EMPTY_SLOTS, slot1Name) then + if slot2Name then + caster:SwapAbilities(slot2Name, slot1Name, false, true) + caster:RemoveAbility(slot2Name) + end + else + caster:RemoveAbility(slot1Name) + end + end + end + print((((("[SpellSteal] SUCCESS: stole " .. lastAbilityName) .. " (lvl ") .. tostring(stolenLevel)) .. ")") .. (hasAghs and " [Aghs]" or "")) + else + print(("[SpellSteal] already have " .. lastAbilityName) .. ", skipping swap") + end + caster:AddNewModifier(caster, ability, ____exports.modifier_rubick_spellsteal_stolen.name, {duration = duration}) + local arcanaParticle = "particles/econ/items/rubick/rubick_arcana/rubick_arc_loadout_spell_steal.vpcf" + local particle = ParticleManager:CreateParticle(arcanaParticle, PATTACH_WORLDORIGIN, nil) + ParticleManager:SetParticleControlEnt( + particle, + 0, + target, + PATTACH_POINT_FOLLOW, + "attach_hitloc", + Vector(0, 0, 0), + true + ) + ParticleManager:SetParticleControlEnt( + particle, + 1, + caster, + PATTACH_POINT_FOLLOW, + "attach_hitloc", + Vector(0, 0, 0), + true + ) + ParticleManager:ReleaseParticleIndex(particle) + EmitSoundOn("Hero_Rubick.SpellSteal.Target", target) + EmitSoundOn("Hero_Rubick.SpellSteal", caster) +end +ability_rubick_spellsteal_custom = __TS__Decorate( + ability_rubick_spellsteal_custom, + ability_rubick_spellsteal_custom, + {registerAbility(nil)}, + {kind = "class", name = "ability_rubick_spellsteal_custom"} +) +____exports.ability_rubick_spellsteal_custom = ability_rubick_spellsteal_custom +____exports.modifier_rubick_spellsteal_stolen = __TS__Class() +local modifier_rubick_spellsteal_stolen = ____exports.modifier_rubick_spellsteal_stolen +modifier_rubick_spellsteal_stolen.name = "modifier_rubick_spellsteal_stolen" +modifier_rubick_spellsteal_stolen.____file_path = "scripts/vscripts/abilities/heroes/rubick/ability_rubick_spellsteal_custom.lua" +__TS__ClassExtends(modifier_rubick_spellsteal_stolen, BaseModifier) +function modifier_rubick_spellsteal_stolen.prototype.IsHidden(self) + return true +end +function modifier_rubick_spellsteal_stolen.prototype.IsPurgable(self) + return false +end +modifier_rubick_spellsteal_stolen = __TS__Decorate( + modifier_rubick_spellsteal_stolen, + modifier_rubick_spellsteal_stolen, + {registerModifier(nil)}, + {kind = "class", name = "modifier_rubick_spellsteal_stolen"} +) +____exports.modifier_rubick_spellsteal_stolen = modifier_rubick_spellsteal_stolen +return ____exports diff --git a/scripts/vscripts/abilities/heroes/rubick/ability_rubick_telekinesis_custom.lua b/scripts/vscripts/abilities/heroes/rubick/ability_rubick_telekinesis_custom.lua new file mode 100644 index 0000000..d858075 --- /dev/null +++ b/scripts/vscripts/abilities/heroes/rubick/ability_rubick_telekinesis_custom.lua @@ -0,0 +1,830 @@ +local ____lualib = require("lualib_bundle") +local __TS__Class = ____lualib.__TS__Class +local __TS__ClassExtends = ____lualib.__TS__ClassExtends +local __TS__StringStartsWith = ____lualib.__TS__StringStartsWith +local __TS__Decorate = ____lualib.__TS__Decorate +local ____exports = {} +local ____dota_ts_adapter = require("lib.dota_ts_adapter") +local BaseAbility = ____dota_ts_adapter.BaseAbility +local BaseModifier = ____dota_ts_adapter.BaseModifier +local BaseModifierMotionBoth = ____dota_ts_adapter.BaseModifierMotionBoth +local registerAbility = ____dota_ts_adapter.registerAbility +local registerModifier = ____dota_ts_adapter.registerModifier +local ____heal_tracker = require("utils.heal_tracker") +local HealWithBattlePass = ____heal_tracker.HealWithBattlePass +local HOMER_UNIT_NAME = "npc_homer" +____exports.ability_rubick_telekinesis_custom = __TS__Class() +local ability_rubick_telekinesis_custom = ____exports.ability_rubick_telekinesis_custom +ability_rubick_telekinesis_custom.name = "ability_rubick_telekinesis_custom" +ability_rubick_telekinesis_custom.____file_path = "scripts/vscripts/abilities/heroes/rubick/ability_rubick_telekinesis_custom.lua" +__TS__ClassExtends(ability_rubick_telekinesis_custom, BaseAbility) +function ability_rubick_telekinesis_custom.prototype.clearActiveTargetIfMatch(self, unit) + if self.activeTelekinesisTargetEntIndex == unit:GetEntityIndex() then + self.activeTelekinesisTargetEntIndex = nil + end +end +function ability_rubick_telekinesis_custom.prototype.clearTelekinesisFromPreviousTarget(self, newTarget) + local prevIndex = self.activeTelekinesisTargetEntIndex + if prevIndex == nil or prevIndex == newTarget:GetEntityIndex() then + return + end + self.activeTelekinesisTargetEntIndex = nil + local prev = EntIndexToHScript(prevIndex) + if not prev or not IsValidEntity(prev) then + return + end + self:removeTelekinesisFromUnit(prev, true) +end +function ability_rubick_telekinesis_custom.prototype.removeTelekinesisFromUnit(self, unit, suppressLandEffect) + local caster = self:GetCaster() + local liftMod = unit:FindModifierByName(____exports.modifier_rubick_telekinesis_lift.name) + if liftMod and liftMod:GetCaster() == caster then + liftMod:EndTelekinesisEffect(unit, suppressLandEffect) + return + end + local landMod = unit:FindModifierByName(____exports.modifier_rubick_telekinesis_land.name) + if landMod then + landMod:SetSuppressLandEffect(suppressLandEffect) + end + unit:RemoveModifierByName(____exports.modifier_rubick_telekinesis_lift.name) + unit:RemoveModifierByName(____exports.modifier_rubick_telekinesis_land.name) +end +function ability_rubick_telekinesis_custom.isBossTarget(self, target) + local unitName = target:GetUnitName() + if __TS__StringStartsWith(unitName, "npc_boss_") or __TS__StringStartsWith(unitName, "npc_wave_boss") then + return true + end + return target:IsBossCreature() or target:IsBoss() +end +function ability_rubick_telekinesis_custom.isHomerTarget(self, target) + return target:GetUnitName() == HOMER_UNIT_NAME +end +function ability_rubick_telekinesis_custom.isBlockedTarget(self, target) + return ____exports.ability_rubick_telekinesis_custom:isBossTarget(target) or ____exports.ability_rubick_telekinesis_custom:isHomerTarget(target) +end +function ability_rubick_telekinesis_custom.getCasterBreakMaxDistance(self, ability, target) + local castRange = ability:GetCastRange( + target:GetAbsOrigin(), + target + ) + local mult = ability:GetSpecialValueFor("caster_break_range_mult") + return castRange * (mult > 0 and mult or 2) +end +function ability_rubick_telekinesis_custom.isCasterWithinBreakRange(self, ability, caster, target) + local maxDist = ____exports.ability_rubick_telekinesis_custom:getCasterBreakMaxDistance(ability, target) + return (caster:GetAbsOrigin() - target:GetAbsOrigin()):Length2D() <= maxDist +end +function ability_rubick_telekinesis_custom.prototype.CastFilterResultTarget(self, target) + if ____exports.ability_rubick_telekinesis_custom:isBlockedTarget(target) then + return UF_FAIL_CUSTOM + end + return UF_SUCCESS +end +function ability_rubick_telekinesis_custom.prototype.GetCustomCastErrorTarget(self, target) + if ____exports.ability_rubick_telekinesis_custom:isHomerTarget(target) then + return "#dota_hud_error_rubick_telekinesis_homer" + end + if ____exports.ability_rubick_telekinesis_custom:isBossTarget(target) then + return "#dota_hud_error_rubick_telekinesis_boss" + end + return "" +end +function ability_rubick_telekinesis_custom.prototype.Precache(self, context) + PrecacheResource("particle", "particles/econ/items/rubick/rubick_force_ambient/rubick_telekinesis_force_cube.vpcf", context) + PrecacheResource("particle", "particles/econ/items/rubick/rubick_puppet_master/rubick_telekinesis_puppet_string_src.vpcf", context) + PrecacheResource("particle", "particles/econ/items/rubick/rubick_force_gold_ambient/rubick_telekinesis_land_force_gold.vpcf", context) + PrecacheResource("particle", "particles/units/heroes/hero_rubick/rubick_telekinesis_debuff.vpcf", context) + PrecacheResource("particle", "particles/econ/items/rubick/rubick_puppet_master/rubick_telekinesis_puppet_string_attach.vpcf", context) + PrecacheResource("particle", "particles/units/heroes/hero_rubick/rubick_base_attack.vpcf", context) +end +function ability_rubick_telekinesis_custom.prototype.OnSpellStart(self) + local caster = self:GetCaster() + local target = self:GetCursorTarget() + if not target or not target:IsAlive() then + return + end + if ____exports.ability_rubick_telekinesis_custom:isBlockedTarget(target) then + return + end + self:clearTelekinesisFromPreviousTarget(target) + target:RemoveModifierByName(____exports.modifier_rubick_telekinesis_lift.name) + target:RemoveModifierByName(____exports.modifier_rubick_telekinesis_land.name) + local air_time = self:GetSpecialValueFor("air_time") + local lift_time = self:GetSpecialValueFor("lift_time") + local drop_time = self:GetSpecialValueFor("drop_time") + local throw_distance = self:GetSpecialValueFor("throw_distance") + local lift_height = self:GetSpecialValueFor("lift_height") + local cursor_pos = self:GetCursorPosition() + local target_origin = target:GetAbsOrigin() + local direction = cursor_pos - target_origin + local dist_to_cursor = direction:Length2D() + if dist_to_cursor > 1 then + direction = direction:Normalized() + else + direction = target_origin - caster:GetAbsOrigin() + direction = direction:Normalized() + end + local actual_distance = math.min(dist_to_cursor, throw_distance) + local land_pos = target_origin + direction * actual_distance + local ground_land = GetGroundPosition(land_pos, target) + EmitSoundOn("Hero_Rubick.Telekinesis.Cast", caster) + local is_ally = target:GetTeamNumber() == caster:GetTeamNumber() + local hasScepter = caster:HasScepter() + local isInfiniteAirTarget = is_ally + local isInfiniteAllyAir = isInfiniteAirTarget and hasScepter + local actual_air_time = is_ally and air_time or air_time * 0.5 + local total_duration = lift_time + actual_air_time + drop_time + local appliedDuration = isInfiniteAllyAir and 99999 or total_duration + ____exports.modifier_rubick_telekinesis_lift:apply( + target, + caster, + self, + { + duration = appliedDuration, + land_x = ground_land.x, + land_y = ground_land.y, + land_z = ground_land.z, + direction_x = direction.x, + direction_y = direction.y, + distance = actual_distance, + height = lift_height, + air_time = actual_air_time, + lift_time = lift_time, + drop_time = drop_time, + ally_air_radius = self:GetSpecialValueFor("ally_air_radius"), + ally_air_max_radius = self:GetSpecialValueFor("ally_air_max_radius"), + ally_air_move_speed_mult = self:GetSpecialValueFor("ally_air_move_speed_mult"), + ally_infinite_air_with_scepter = isInfiniteAllyAir and 1 or 0 + } + ) + target:AddNewModifier( + caster, + self, + ____exports.modifier_rubick_telekinesis_land.name, + { + duration = appliedDuration, + land_radius = self:GetSpecialValueFor("land_stun_radius"), + land_stun = self:GetSpecialValueFor("land_stun_duration"), + land_damage = self:GetSpecialValueFor("land_damage"), + ally_heal_per_second = self:GetSpecialValueFor("ally_heal_per_second") * 0.2, + ally_attack_range_bonus = self:GetSpecialValueFor("ally_attack_range_bonus"), + ally_attack_speed_bonus = self:GetSpecialValueFor("ally_attack_speed_bonus"), + ally_bonus_magic_damage = self:GetSpecialValueFor("ally_bonus_magic_damage"), + ally_bonus_magic_damage_per_int = self:GetSpecialValueFor("ally_bonus_magic_damage_per_int"), + ally_infinite_air_with_scepter = isInfiniteAllyAir and 1 or 0 + } + ) + self.activeTelekinesisTargetEntIndex = target:GetEntityIndex() +end +ability_rubick_telekinesis_custom = __TS__Decorate( + ability_rubick_telekinesis_custom, + ability_rubick_telekinesis_custom, + {registerAbility(nil)}, + {kind = "class", name = "ability_rubick_telekinesis_custom"} +) +____exports.ability_rubick_telekinesis_custom = ability_rubick_telekinesis_custom +--- Подъём → 5 сек в воздухе → приземление. Враги: стан. Союзники: без стана, могут передвигаться в воздухе. +____exports.modifier_rubick_telekinesis_lift = __TS__Class() +local modifier_rubick_telekinesis_lift = ____exports.modifier_rubick_telekinesis_lift +modifier_rubick_telekinesis_lift.name = "modifier_rubick_telekinesis_lift" +modifier_rubick_telekinesis_lift.____file_path = "scripts/vscripts/abilities/heroes/rubick/ability_rubick_telekinesis_custom.lua" +__TS__ClassExtends(modifier_rubick_telekinesis_lift, BaseModifierMotionBoth) +function modifier_rubick_telekinesis_lift.prototype.____constructor(self, ...) + BaseModifierMotionBoth.prototype.____constructor(self, ...) + self.start_pos = Vector(0, 0, 0) + self.apex_pos = Vector(0, 0, 0) + self.land_pos = Vector(0, 0, 0) + self.apex_z = 0 + self.ally_air_radius = 150 + self.ally_air_max_radius = 350 + self.ally_air_move_speed_mult = 1.5 + self.ally_drop_start_pos = nil + self.ally_infinite_air_with_scepter = false + self.ally_drop_started = false + self.last_pos = nil + self.throw_distance = 375 +end +function modifier_rubick_telekinesis_lift.prototype.IsHidden(self) + return true +end +function modifier_rubick_telekinesis_lift.prototype.IsPurgable(self) + return false +end +function modifier_rubick_telekinesis_lift.prototype.GetEffectName(self) + return "" +end +function modifier_rubick_telekinesis_lift.prototype.GetEffectAttachType(self) + return PATTACH_ABSORIGIN_FOLLOW +end +function modifier_rubick_telekinesis_lift.prototype.IsAlly(self) + local caster = self:GetCaster() + return not not (caster and self:GetParent():GetTeamNumber() == caster:GetTeamNumber()) +end +function modifier_rubick_telekinesis_lift.prototype.OnCreated(self, params) + if IsClient() then + return + end + local parent = self:GetParent() + self.start_pos = parent:GetAbsOrigin() + local ground_start = GetGroundHeight(self.start_pos, parent) + self.land_pos = Vector(params.land_x, params.land_y, params.land_z) + self.air_time = params.air_time + self.lift_time = params.lift_time + self.drop_time = params.drop_time + self.ally_air_radius = params.ally_air_radius or 150 + self.ally_air_max_radius = params.ally_air_max_radius or 350 + self.ally_air_move_speed_mult = params.ally_air_move_speed_mult or 1.5 + self.ally_infinite_air_with_scepter = (params.ally_infinite_air_with_scepter or 0) == 1 + self.throw_distance = params.distance or 375 + self.last_pos = parent:GetAbsOrigin() + self.apex_pos = Vector(self.start_pos.x, self.start_pos.y, ground_start + params.height) + self.apex_z = ground_start + params.height + self:RefreshLiftParticle() + if self:IsInfiniteAllyAirActive() then + self:StartIntervalThink(2) + else + self:StartIntervalThink(-1) + end + if self:ApplyHorizontalMotionController() == false or self:ApplyVerticalMotionController() == false then + self:Destroy() + return + end +end +function modifier_rubick_telekinesis_lift.prototype.IsInfiniteAllyAirActive(self) + return self:IsAlly() and self.ally_infinite_air_with_scepter and not self.ally_drop_started +end +function modifier_rubick_telekinesis_lift.prototype.StartForcedDrop(self, me) + if self.ally_drop_started then + return + end + self.ally_drop_started = true + self:StartIntervalThink(-1) + self:EndTelekinesisEffect(me, false) +end +function modifier_rubick_telekinesis_lift.prototype.EndTelekinesisEffect(self, me, suppressLandEffect) + local landMod = me:FindModifierByName(____exports.modifier_rubick_telekinesis_land.name) + if landMod then + landMod:SetSuppressLandEffect(suppressLandEffect) + end + me:RemoveModifierByName(____exports.modifier_rubick_telekinesis_land.name) + self:Destroy() +end +function modifier_rubick_telekinesis_lift.prototype.GetEffectAnchorPos(self, me) + local t = self:GetElapsedTime() + if self:IsAlly() and t >= self.lift_time and (self:IsInfiniteAllyAirActive() or t < self.lift_time + self.air_time) then + return Vector(self.apex_pos.x, self.apex_pos.y, 0) + end + local origin = self.start_pos + return Vector(origin.x, origin.y, 0) +end +function modifier_rubick_telekinesis_lift.prototype.GetMaxEffectDistance(self) + if self:IsAlly() and self:GetElapsedTime() >= self.lift_time then + return self.ally_air_max_radius + 150 + end + return self.throw_distance + 300 +end +function modifier_rubick_telekinesis_lift.prototype.CheckAndBreakIfCasterTooFar(self, me) + local caster = self:GetCaster() + local ability = self:GetAbility() + if not ability then + return false + end + if not caster or caster:IsNull() or not caster:IsAlive() then + self:EndTelekinesisEffect(me, true) + return true + end + if not ____exports.ability_rubick_telekinesis_custom:isCasterWithinBreakRange(ability, caster, me) then + self:EndTelekinesisEffect(me, true) + return true + end + return false +end +function modifier_rubick_telekinesis_lift.prototype.CheckAndBreakOnTeleport(self, me) + if not IsServer() then + return false + end + if self:CheckAndBreakIfCasterTooFar(me) then + return true + end + local cur = me:GetAbsOrigin() + local cur2d = Vector(cur.x, cur.y, 0) + if self.last_pos then + local last2d = Vector(self.last_pos.x, self.last_pos.y, 0) + local jump = (cur2d - last2d):Length2D() + if jump >= ____exports.modifier_rubick_telekinesis_lift.TELEPORT_JUMP_THRESHOLD then + self:EndTelekinesisEffect(me, true) + return true + end + end + local anchor = self:GetEffectAnchorPos(me) + local distFromAnchor = (cur2d - anchor):Length2D() + if distFromAnchor > self:GetMaxEffectDistance() then + self:EndTelekinesisEffect(me, true) + return true + end + self.last_pos = cur + return false +end +function modifier_rubick_telekinesis_lift.prototype.RefreshLiftParticle(self) + if self.liftParticle ~= nil then + ParticleManager:DestroyParticle(self.liftParticle, false) + ParticleManager:ReleaseParticleIndex(self.liftParticle) + self.liftParticle = nil + end + self.liftParticle = ParticleManager:CreateParticle( + "particles/econ/items/rubick/rubick_force_ambient/rubick_telekinesis_force_cube.vpcf", + PATTACH_ROOTBONE_FOLLOW, + self:GetParent() + ) + self:AddParticle( + self.liftParticle, + false, + false, + -1, + false, + false + ) +end +function modifier_rubick_telekinesis_lift.prototype.OnIntervalThink(self) + if not IsServer() then + return + end + local parent = self:GetParent() + if self:CheckAndBreakOnTeleport(parent) then + return + end + if not self:IsInfiniteAllyAirActive() then + self:StartIntervalThink(-1) + return + end + local cur = parent:GetAbsOrigin() + self.apex_pos = Vector(cur.x, cur.y, self.apex_z) + self:RefreshLiftParticle() +end +function modifier_rubick_telekinesis_lift.prototype.OnDestroy(self) + if IsClient() then + return + end + local ability = self:GetAbility() + if ability ~= nil then + ability:clearActiveTargetIfMatch(self:GetParent()) + end + if self.liftParticle ~= nil then + ParticleManager:DestroyParticle(self.liftParticle, false) + ParticleManager:ReleaseParticleIndex(self.liftParticle) + self.liftParticle = nil + end + self:GetParent():RemoveHorizontalMotionController(self) + self:GetParent():RemoveVerticalMotionController(self) +end +function modifier_rubick_telekinesis_lift.prototype.GetCurrentTargetPosition(self, me) + local t = self:GetElapsedTime() + local function lerp(____, a, b, p) + return a + (b - a) * p + end + if t < self.lift_time then + local p = t / self.lift_time + return Vector( + lerp(nil, self.start_pos.x, self.apex_pos.x, p), + lerp(nil, self.start_pos.y, self.apex_pos.y, p), + lerp(nil, self.start_pos.z, self.apex_pos.z, p) + ) + end + if self:IsInfiniteAllyAirActive() then + local cur = me:GetAbsOrigin() + return Vector(cur.x, cur.y, self.apex_z) + end + if t < self.lift_time + self.air_time then + if self:IsAlly() then + local cur = me:GetAbsOrigin() + return Vector(cur.x, cur.y, self.apex_z) + end + return self.apex_pos + end + local drop_t = t - self.lift_time - self.air_time + if self:IsAlly() and not self.ally_drop_start_pos then + self.ally_drop_start_pos = me:GetAbsOrigin() + self.land_pos = GetGroundPosition(self.ally_drop_start_pos, me) + end + local drop_from = self:IsAlly() and self.ally_drop_start_pos and self.ally_drop_start_pos or self.apex_pos + local raw_p = math.max( + 0, + math.min(1, drop_t / self.drop_time) + ) + local p = raw_p * raw_p * raw_p * raw_p * raw_p + return Vector( + lerp(nil, drop_from.x, self.land_pos.x, p), + lerp(nil, drop_from.y, self.land_pos.y, p), + lerp(nil, drop_from.z, self.land_pos.z, p) + ) +end +function modifier_rubick_telekinesis_lift.prototype.UpdateHorizontalMotion(self, me, dt) + if self:CheckAndBreakOnTeleport(me) then + return + end + local t = self:GetElapsedTime() + if self:IsAlly() and t >= self.lift_time and (self:IsInfiniteAllyAirActive() or t < self.lift_time + self.air_time) then + if self.ally_drop_started then + me:SetOrigin(self:GetCurrentTargetPosition(me)) + return + end + local cur = me:GetAbsOrigin() + local center = Vector(self.apex_pos.x, self.apex_pos.y, 0) + local cur_2d = Vector(cur.x, cur.y, 0) + local dist = (cur_2d - center):Length2D() + if dist > self.ally_air_max_radius then + local from_center = cur_2d - center + local len = from_center:Length2D() + if len > 0.01 then + local norm = from_center:Normalized() + local clamped = Vector(center.x + norm.x * self.ally_air_max_radius, center.y + norm.y * self.ally_air_max_radius, 0) + me:SetOrigin(Vector(clamped.x, clamped.y, self.apex_z)) + else + me:SetOrigin(Vector(cur.x, cur.y, self.apex_z)) + end + return + end + if me:IsMoving() then + local fwd = me:GetForwardVector() + local dir = Vector(fwd.x, fwd.y, 0) + local len = dir:Length2D() + if len > 0.01 then + local norm = dir:Normalized() + local from_center = cur_2d - center + local dot = norm.x * from_center.x + norm.y * from_center.y + local moving_away = dot > 0 + if self:IsInfiniteAllyAirActive() and moving_away and dist >= self.ally_air_max_radius - 8 then + self:StartForcedDrop(me) + return + end + local speed_mult = 1 + if moving_away and dist > self.ally_air_radius then + local range = self.ally_air_max_radius - self.ally_air_radius + speed_mult = math.max(0, 1 - (dist - self.ally_air_radius) / range) + end + local speed = me:GetMoveSpeedModifier( + me:GetBaseMoveSpeed(), + false + ) * self.ally_air_move_speed_mult * dt * speed_mult + local new_x = cur.x + norm.x * speed + local new_y = cur.y + norm.y * speed + local new_dist = (Vector(new_x, new_y, 0) - center):Length2D() + if self:IsInfiniteAllyAirActive() and new_dist > self.ally_air_max_radius then + self:StartForcedDrop(me) + return + end + if new_dist > self.ally_air_max_radius then + local scale = self.ally_air_max_radius / new_dist + new_x = center.x + (new_x - center.x) * scale + new_y = center.y + (new_y - center.y) * scale + end + me:SetOrigin(Vector(new_x, new_y, self.apex_z)) + return + end + end + me:SetOrigin(Vector(cur.x, cur.y, self.apex_z)) + return + end + me:SetOrigin(self:GetCurrentTargetPosition(me)) +end +function modifier_rubick_telekinesis_lift.prototype.UpdateVerticalMotion(self, me, dt) + if self:CheckAndBreakOnTeleport(me) then + return + end + local t = self:GetElapsedTime() + if self:IsAlly() and t >= self.lift_time and (self:IsInfiniteAllyAirActive() or t < self.lift_time + self.air_time) then + if self.ally_drop_started then + me:SetOrigin(self:GetCurrentTargetPosition(me)) + return + end + local cur = me:GetAbsOrigin() + me:SetOrigin(Vector(cur.x, cur.y, self.apex_z)) + return + end + me:SetOrigin(self:GetCurrentTargetPosition(me)) +end +function modifier_rubick_telekinesis_lift.prototype.OnHorizontalMotionInterrupted(self) + if not IsServer() then + return + end + self:EndTelekinesisEffect( + self:GetParent(), + true + ) +end +function modifier_rubick_telekinesis_lift.prototype.OnVerticalMotionInterrupted(self) + if not IsServer() then + return + end + self:EndTelekinesisEffect( + self:GetParent(), + true + ) +end +function modifier_rubick_telekinesis_lift.prototype.DeclareFunctions(self) + return {MODIFIER_PROPERTY_OVERRIDE_ANIMATION, MODIFIER_PROPERTY_OVERRIDE_ANIMATION_RATE} +end +function modifier_rubick_telekinesis_lift.prototype.GetOverrideAnimation(self) + if self:GetParent():GetTeamNumber() == self:GetCaster():GetTeamNumber() then + return ACT_IDLE + end + return ACT_DOTA_FLAIL +end +function modifier_rubick_telekinesis_lift.prototype.GetOverrideAnimationRate(self) + return 0.75 +end +modifier_rubick_telekinesis_lift.TELEPORT_JUMP_THRESHOLD = 400 +modifier_rubick_telekinesis_lift = __TS__Decorate( + modifier_rubick_telekinesis_lift, + modifier_rubick_telekinesis_lift, + {registerModifier(nil)}, + {kind = "class", name = "modifier_rubick_telekinesis_lift"} +) +____exports.modifier_rubick_telekinesis_lift = modifier_rubick_telekinesis_lift +____exports.modifier_rubick_telekinesis_land = __TS__Class() +local modifier_rubick_telekinesis_land = ____exports.modifier_rubick_telekinesis_land +modifier_rubick_telekinesis_land.name = "modifier_rubick_telekinesis_land" +modifier_rubick_telekinesis_land.____file_path = "scripts/vscripts/abilities/heroes/rubick/ability_rubick_telekinesis_custom.lua" +__TS__ClassExtends(modifier_rubick_telekinesis_land, BaseModifier) +function modifier_rubick_telekinesis_land.prototype.____constructor(self, ...) + BaseModifier.prototype.____constructor(self, ...) + self.land_radius = 375 + self.land_stun = 1 + self.land_damage = 0 + self.ally_heal_per_second = 0 + self.ally_attack_range_bonus = 0 + self.ally_attack_speed_bonus = 0 + self.ally_bonus_magic_damage = 0 + self.ally_bonus_magic_damage_per_int = 0 + self.ally_infinite_air_with_scepter = false + self.nextAttachParticleRefreshTime = 0 + self.suppress_land_effect = false + self.last_pos = nil +end +function modifier_rubick_telekinesis_land.prototype.GetEffectName(self) + return "" +end +function modifier_rubick_telekinesis_land.prototype.GetEffectAttachType(self) + return PATTACH_ABSORIGIN_FOLLOW +end +function modifier_rubick_telekinesis_land.prototype.IsHidden(self) + return true +end +function modifier_rubick_telekinesis_land.prototype.IsPurgable(self) + return false +end +function modifier_rubick_telekinesis_land.prototype.IsAlly(self) + local caster = self:GetCaster() + return not not (caster and self:GetParent():GetTeamNumber() == caster:GetTeamNumber()) +end +function modifier_rubick_telekinesis_land.prototype.CheckState(self) + if not self:IsAlly() then + return {[MODIFIER_STATE_STUNNED] = true} + end + return {[MODIFIER_STATE_INVULNERABLE] = true, [MODIFIER_STATE_FLYING] = true} +end +function modifier_rubick_telekinesis_land.prototype.OnCreated(self, params) + self.land_radius = params.land_radius or 375 + self.land_stun = params.land_stun or 1 + self.land_damage = params.land_damage or 0 + self.ally_heal_per_second = params.ally_heal_per_second or 0 + self.ally_attack_range_bonus = params.ally_attack_range_bonus or 0 + self.ally_attack_speed_bonus = params.ally_attack_speed_bonus or 0 + self.ally_bonus_magic_damage = params.ally_bonus_magic_damage or 0 + self.ally_bonus_magic_damage_per_int = params.ally_bonus_magic_damage_per_int or 0 + self.ally_infinite_air_with_scepter = (params.ally_infinite_air_with_scepter or 0) == 1 + if IsServer() then + self.last_pos = self:GetParent():GetAbsOrigin() + self:RefreshAttachParticle() + if self.ally_infinite_air_with_scepter then + self.nextAttachParticleRefreshTime = GameRules:GetGameTime() + 2 + end + end + if IsServer() and self:IsAlly() and (self.ally_heal_per_second > 0 or self.ally_infinite_air_with_scepter) then + self:StartIntervalThink(0.2) + end +end +function modifier_rubick_telekinesis_land.prototype.SetSuppressLandEffect(self, value) + self.suppress_land_effect = value +end +function modifier_rubick_telekinesis_land.prototype.CheckAndBreakOnTeleport(self) + if not IsServer() then + return false + end + local parent = self:GetParent() + local liftMod = parent:FindModifierByName(____exports.modifier_rubick_telekinesis_lift.name) + if liftMod and liftMod:CheckAndBreakOnTeleport(parent) then + return true + end + local caster = self:GetCaster() + local ability = self:GetAbility() + if ability and (not caster or caster:IsNull() or not caster:IsAlive() or not ____exports.ability_rubick_telekinesis_custom:isCasterWithinBreakRange(ability, caster, parent)) then + self:SetSuppressLandEffect(true) + parent:RemoveModifierByName(____exports.modifier_rubick_telekinesis_lift.name) + parent:RemoveModifierByName(____exports.modifier_rubick_telekinesis_land.name) + return true + end + local cur = parent:GetAbsOrigin() + if self.last_pos then + local jump = (Vector(cur.x, cur.y, 0) - Vector(self.last_pos.x, self.last_pos.y, 0)):Length2D() + if jump >= ____exports.modifier_rubick_telekinesis_land.TELEPORT_JUMP_THRESHOLD then + self:SetSuppressLandEffect(true) + parent:RemoveModifierByName(____exports.modifier_rubick_telekinesis_lift.name) + parent:RemoveModifierByName(____exports.modifier_rubick_telekinesis_land.name) + return true + end + end + self.last_pos = cur + return false +end +function modifier_rubick_telekinesis_land.prototype.RefreshAttachParticle(self) + if not IsServer() then + return + end + if self.attachParticle ~= nil then + ParticleManager:DestroyParticle(self.attachParticle, false) + ParticleManager:ReleaseParticleIndex(self.attachParticle) + self.attachParticle = nil + end + self.attachParticle = ParticleManager:CreateParticle( + "particles/econ/items/rubick/rubick_puppet_master/rubick_telekinesis_puppet_string_attach.vpcf", + PATTACH_ABSORIGIN_FOLLOW, + self:GetParent() + ) + self:AddParticle( + self.attachParticle, + false, + false, + -1, + false, + false + ) +end +function modifier_rubick_telekinesis_land.prototype.DeclareFunctions(self) + local funcs = {} + if self:IsAlly() then + funcs[#funcs + 1] = MODIFIER_PROPERTY_ATTACK_RANGE_BONUS + funcs[#funcs + 1] = MODIFIER_PROPERTY_ATTACKSPEED_BONUS_CONSTANT + funcs[#funcs + 1] = MODIFIER_PROPERTY_PROCATTACK_BONUS_DAMAGE_MAGICAL + funcs[#funcs + 1] = MODIFIER_PROPERTY_PROJECTILE_NAME + funcs[#funcs + 1] = MODIFIER_EVENT_ON_ATTACK_LANDED + end + return funcs +end +function modifier_rubick_telekinesis_land.prototype.GetModifierAttackRangeBonus(self) + if not self:IsAlly() or not self:GetParent():IsRangedAttacker() then + return 0 + end + return self.ally_attack_range_bonus +end +function modifier_rubick_telekinesis_land.prototype.GetModifierAttackSpeedBonus_Constant(self) + if not self:IsAlly() then + return 0 + end + return self.ally_attack_speed_bonus +end +function modifier_rubick_telekinesis_land.prototype.GetBonusMagicDamage(self) + if not self:IsAlly() then + return 0 + end + local caster = self:GetCaster() + if not caster or not caster:IsHero() then + return self.ally_bonus_magic_damage + end + local int = caster:GetIntellect(true) + return self.ally_bonus_magic_damage + self.ally_bonus_magic_damage_per_int * int +end +function modifier_rubick_telekinesis_land.prototype.GetModifierProcAttack_BonusDamage_Magical(self) + if not IsServer() or not self:IsAlly() then + return 0 + end + local damage = self:GetBonusMagicDamage() + return damage <= 0 and 0 or damage +end +function modifier_rubick_telekinesis_land.prototype.OnAttackLanded(self, event) + if not IsServer() or not self:IsAlly() then + return + end + if event.attacker ~= self:GetParent() then + return + end + local damage = self:GetBonusMagicDamage() + if damage <= 0 then + return + end + local target = event.target + if not target or not target:IsAlive() then + return + end + SendOverheadEventMessage( + nil, + OVERHEAD_ALERT_BONUS_SPELL_DAMAGE, + target, + math.floor(damage), + nil + ) +end +function modifier_rubick_telekinesis_land.prototype.OnIntervalThink(self) + if not IsServer() then + return + end + if self:CheckAndBreakOnTeleport() then + return + end + local parent = self:GetParent() + local ability = self:GetAbility() + if not parent:IsAlive() then + return + end + if self.ally_infinite_air_with_scepter then + local now = GameRules:GetGameTime() + if now >= self.nextAttachParticleRefreshTime then + self:RefreshAttachParticle() + self.nextAttachParticleRefreshTime = now + 2 + end + end + if not self:IsAlly() or self.ally_heal_per_second <= 0 or not ability then + return + end + HealWithBattlePass( + nil, + parent, + self.ally_heal_per_second, + ability, + self:GetCaster() + ) + SendOverheadEventMessage( + nil, + OVERHEAD_ALERT_HEAL, + parent, + self.ally_heal_per_second, + nil + ) +end +function modifier_rubick_telekinesis_land.prototype.GetModifierProjectileName(self) + return "particles/units/heroes/hero_rubick/rubick_base_attack.vpcf" +end +function modifier_rubick_telekinesis_land.prototype.OnDestroy(self) + if not IsServer() then + return + end + if self.attachParticle ~= nil then + ParticleManager:DestroyParticle(self.attachParticle, false) + ParticleManager:ReleaseParticleIndex(self.attachParticle) + self.attachParticle = nil + end + local parent = self:GetParent() + local caster = self:GetCaster() + local ability = self:GetAbility() + if not caster or not ability or not parent:IsAlive() then + return + end + if self.suppress_land_effect then + return + end + local land_pos = parent:GetAbsOrigin() + if not self:IsAlly() then + EmitSoundOnLocationWithCaster(land_pos, "Hero_Rubick.Telekinesis.Stun", caster) + local particle = ParticleManager:CreateParticle("particles/econ/items/rubick/rubick_force_gold_ambient/rubick_telekinesis_land_force_gold.vpcf", PATTACH_WORLDORIGIN, nil) + ParticleManager:SetParticleControl(particle, 0, land_pos) + ParticleManager:SetParticleControl( + particle, + 1, + Vector(self.land_radius, 0, 0) + ) + ParticleManager:ReleaseParticleIndex(particle) + local land_damage = ability:GetSpecialValueFor("land_damage") + local land_stun = ability:GetSpecialValueFor("land_stun_duration") + local land_radius = ability:GetSpecialValueFor("land_stun_radius") + local enemies = FindUnitsInRadius( + caster:GetTeamNumber(), + land_pos, + nil, + land_radius, + DOTA_UNIT_TARGET_TEAM_ENEMY, + DOTA_UNIT_TARGET_HERO + DOTA_UNIT_TARGET_BASIC, + DOTA_UNIT_TARGET_FLAG_NONE, + FIND_ANY_ORDER, + false + ) + for ____, enemy in ipairs(enemies) do + ApplyDamage({ + victim = enemy, + attacker = caster, + damage = land_damage, + damage_type = DAMAGE_TYPE_MAGICAL, + ability = ability + }) + enemy:AddNewModifier(caster, ability, "modifier_stunned", {duration = land_stun}) + end + end +end +modifier_rubick_telekinesis_land.TELEPORT_JUMP_THRESHOLD = 400 +modifier_rubick_telekinesis_land = __TS__Decorate( + modifier_rubick_telekinesis_land, + modifier_rubick_telekinesis_land, + {registerModifier(nil)}, + {kind = "class", name = "modifier_rubick_telekinesis_land"} +) +____exports.modifier_rubick_telekinesis_land = modifier_rubick_telekinesis_land +return ____exports diff --git a/scripts/vscripts/abilities/heroes/sand_king/sandking_burrowstrike_custom.lua b/scripts/vscripts/abilities/heroes/sand_king/sandking_burrowstrike_custom.lua new file mode 100644 index 0000000..2b403bd --- /dev/null +++ b/scripts/vscripts/abilities/heroes/sand_king/sandking_burrowstrike_custom.lua @@ -0,0 +1,226 @@ +local ____lualib = require("lualib_bundle") +local __TS__Class = ____lualib.__TS__Class +local __TS__ClassExtends = ____lualib.__TS__ClassExtends +local __TS__Decorate = ____lualib.__TS__Decorate +local ____exports = {} +local modifier_sandking_burrowstrike_burrow +local ____dota_ts_adapter = require("lib.dota_ts_adapter") +local BaseAbility = ____dota_ts_adapter.BaseAbility +local BaseModifier = ____dota_ts_adapter.BaseModifier +local registerAbility = ____dota_ts_adapter.registerAbility +local registerModifier = ____dota_ts_adapter.registerModifier +--- Burrowstrike по образцу dota1x6-free: линейный снаряд для попаданий + модификатор «под землёй» +-- со станом кастера до телепорта на точку (без HorizontalMotionController). +____exports.sandking_burrowstrike_custom = __TS__Class() +local sandking_burrowstrike_custom = ____exports.sandking_burrowstrike_custom +sandking_burrowstrike_custom.name = "sandking_burrowstrike_custom" +sandking_burrowstrike_custom.____file_path = "scripts/vscripts/abilities/heroes/sand_king/sandking_burrowstrike_custom.lua" +__TS__ClassExtends(sandking_burrowstrike_custom, BaseAbility) +function sandking_burrowstrike_custom.prototype.Precache(self, context) + PrecacheResource("particle", "particles/units/heroes/hero_sandking/sandking_burrowstrike.vpcf", context) + PrecacheResource("particle", "particles/econ/items/sand_king/king_of_egypt/king_of_egypt_burrowstrike_rocks.vpcf", context) + PrecacheResource("soundfile", "soundevents/game_sounds_heroes/game_sounds_sandking.vsndevts", context) +end +function sandking_burrowstrike_custom.prototype.GetBehavior(self) + return bit.bor(DOTA_ABILITY_BEHAVIOR_POINT, DOTA_ABILITY_BEHAVIOR_ROOT_DISABLES) +end +function sandking_burrowstrike_custom.prototype.GetCastRange(self, _location, _target) + if not IsServer() then + return self:GetSpecialValueFor("AbilityCastRange") + self:GetCaster():GetCastRangeBonus() + end + return 99999 +end +function sandking_burrowstrike_custom.prototype.GetBurrowEndPosition(self, caster) + local origin = caster:GetAbsOrigin() + local point = self:GetCursorPosition() + if (point - origin):Length2D() < 1 then + point = origin + caster:GetForwardVector() * 10 + end + local dir = point - origin + local maxDist = self:GetSpecialValueFor("AbilityCastRange") + caster:GetCastRangeBonus() + if dir:Length2D() >= maxDist then + dir = dir:Normalized() + dir.z = 0 + point = GetGroundPosition(origin + dir * maxDist, caster) + end + dir = point - origin + local distance = dir:Length2D() + local flatDir = distance < 1 and caster:GetForwardVector() or dir:Normalized() + local endFlat = GetGroundPosition( + origin + flatDir * math.min(distance, maxDist), + caster + ) + return endFlat, flatDir, math.min(distance, maxDist) +end +function sandking_burrowstrike_custom.prototype.OnSpellStart(self) + if not IsServer() then + return + end + local caster = self:GetCaster() + if not caster:IsAlive() then + return + end + local point, dir, distance = self:GetBurrowEndPosition(caster) + caster:FaceTowards(point) + caster:SetForwardVector(dir) + local speed = math.max( + 1, + self:GetSpecialValueFor("burrow_speed") + ) + local width = self:GetSpecialValueFor("burrow_width") + local animTime = math.max( + 0.12, + self:GetSpecialValueFor("burrow_anim_time") + ) + local delay = distance / speed + local burrowModDuration = math.max(delay, animTime) + ProjectileManager:CreateLinearProjectile({ + Ability = self, + vSpawnOrigin = caster:GetAbsOrigin(), + fStartRadius = width, + fEndRadius = width, + vVelocity = dir * speed, + fDistance = distance, + Source = caster, + iUnitTargetTeam = DOTA_UNIT_TARGET_TEAM_ENEMY, + iUnitTargetType = bit.bor(DOTA_UNIT_TARGET_HERO, DOTA_UNIT_TARGET_BASIC), + iUnitTargetFlags = DOTA_UNIT_TARGET_FLAG_MAGIC_IMMUNE_ENEMIES, + bProvidesVision = true, + iVisionTeamNumber = caster:GetTeamNumber(), + iVisionRadius = width + }) + ProjectileManager:ProjectileDodge(caster) + caster:AddNewModifier(caster, self, modifier_sandking_burrowstrike_burrow.name, { + duration = burrowModDuration + 0.02, + delay = delay, + pos_x = point.x, + pos_y = point.y, + pos_z = point.z + }) + self:PlayBurrowFx( + caster:GetAbsOrigin(), + point + ) +end +function sandking_burrowstrike_custom.prototype.OnProjectileHit(self, target, _location) + if not IsServer() or not target then + return false + end + local caster = self:GetCaster() + local stunDur = self:GetSpecialValueFor("stun_duration") + target:AddNewModifier( + caster, + self, + "modifier_stunned", + {duration = stunDur * (1 - target:GetStatusResistance())} + ) + local baseDamage = self:GetAbilityDamage() + local hpPct = self:GetSpecialValueFor("burrow_bonus_damage_max_hp_pct") / 100 + local bonusFromHp = caster:GetMaxHealth() * hpPct + ApplyDamage({ + victim = target, + attacker = caster, + damage = baseDamage + bonusFromHp, + damage_type = DAMAGE_TYPE_MAGICAL, + ability = self + }) + local finale = caster:FindAbilityByName("sandking_caustic_finale_custom") + if finale and not finale:IsNull() then + finale:ApplyPoisonFromHit(target) + end + return false +end +function sandking_burrowstrike_custom.prototype.PlayBurrowFx(self, origin, target) + local caster = self:GetCaster() + local fx = ParticleManager:CreateParticle("particles/units/heroes/hero_sandking/sandking_burrowstrike.vpcf", PATTACH_WORLDORIGIN, caster) + ParticleManager:SetParticleControl(fx, 0, origin) + ParticleManager:SetParticleControl(fx, 1, target) + ParticleManager:ReleaseParticleIndex(fx) + EmitSoundOnLocationWithCaster(target, "SandKing.BurrowStrike", caster) +end +sandking_burrowstrike_custom = __TS__Decorate( + sandking_burrowstrike_custom, + sandking_burrowstrike_custom, + {registerAbility(nil)}, + {kind = "class", name = "sandking_burrowstrike_custom"} +) +____exports.sandking_burrowstrike_custom = sandking_burrowstrike_custom +modifier_sandking_burrowstrike_burrow = __TS__Class() +modifier_sandking_burrowstrike_burrow.name = "modifier_sandking_burrowstrike_burrow" +modifier_sandking_burrowstrike_burrow.____file_path = "scripts/vscripts/abilities/heroes/sand_king/sandking_burrowstrike_custom.lua" +__TS__ClassExtends(modifier_sandking_burrowstrike_burrow, BaseModifier) +function modifier_sandking_burrowstrike_burrow.prototype.____constructor(self, ...) + BaseModifier.prototype.____constructor(self, ...) + self.delay = 0 + self.teleportPoint = Vector(0, 0, 0) + self.ended = false +end +function modifier_sandking_burrowstrike_burrow.prototype.IsHidden(self) + return true +end +function modifier_sandking_burrowstrike_burrow.prototype.IsPurgable(self) + return false +end +function modifier_sandking_burrowstrike_burrow.prototype.CheckState(self) + return {[MODIFIER_STATE_STUNNED] = true} +end +function modifier_sandking_burrowstrike_burrow.prototype.OnCreated(self, params) + if not IsServer() then + return + end + local parent = self:GetParent() + self.delay = params.delay or 0 + local zx = params.pos_x or parent:GetAbsOrigin().x + local zy = params.pos_y or parent:GetAbsOrigin().y + local zz = params.pos_z or parent:GetAbsOrigin().z + self.teleportPoint = GetGroundPosition( + Vector(zx, zy, zz), + parent + ) + parent:StartGesture(ACT_DOTA_CAST_ABILITY_1) + local thinkDelay = math.max( + 0.03, + self.delay - FrameTime() + ) + self:StartIntervalThink(thinkDelay) +end +function modifier_sandking_burrowstrike_burrow.prototype.OnDestroy(self) + if not IsServer() then + return + end + self:teleportFinish() +end +function modifier_sandking_burrowstrike_burrow.prototype.OnIntervalThink(self) + if not IsServer() then + return + end + self:teleportFinish() +end +function modifier_sandking_burrowstrike_burrow.prototype.teleportFinish(self) + if not IsServer() or self.ended then + return + end + self.ended = true + self:StartIntervalThink(-1) + local parent = self:GetParent() + parent:FadeGesture(ACT_DOTA_CAST_ABILITY_1) + parent:StartGesture(ACT_DOTA_OVERRIDE_ABILITY_1) + FindClearSpaceForUnit(parent, self.teleportPoint, true) + local ability = self:GetAbility() + local width = ability and ability:GetSpecialValueFor("burrow_width") or 150 + local fx = ParticleManager:CreateParticle("particles/econ/items/sand_king/king_of_egypt/king_of_egypt_burrowstrike_rocks.vpcf", PATTACH_WORLDORIGIN, nil) + ParticleManager:SetParticleControl(fx, 0, self.teleportPoint) + ParticleManager:SetParticleControl( + fx, + 1, + Vector(width, width, 1) + ) + ParticleManager:ReleaseParticleIndex(fx) +end +modifier_sandking_burrowstrike_burrow = __TS__Decorate( + modifier_sandking_burrowstrike_burrow, + modifier_sandking_burrowstrike_burrow, + {registerModifier(nil)}, + {kind = "class", name = "modifier_sandking_burrowstrike_burrow"} +) +return ____exports diff --git a/scripts/vscripts/abilities/heroes/sand_king/sandking_caustic_finale_custom.lua b/scripts/vscripts/abilities/heroes/sand_king/sandking_caustic_finale_custom.lua new file mode 100644 index 0000000..ba09d68 --- /dev/null +++ b/scripts/vscripts/abilities/heroes/sand_king/sandking_caustic_finale_custom.lua @@ -0,0 +1,274 @@ +local ____lualib = require("lualib_bundle") +local __TS__Class = ____lualib.__TS__Class +local __TS__ClassExtends = ____lualib.__TS__ClassExtends +local __TS__Decorate = ____lualib.__TS__Decorate +local ____exports = {} +local modifier_sandking_caustic_finale_passive, modifier_sandking_caustic_finale_stacks, modifier_sandking_caustic_explosion_slow +local ____dota_ts_adapter = require("lib.dota_ts_adapter") +local BaseAbility = ____dota_ts_adapter.BaseAbility +local BaseModifier = ____dota_ts_adapter.BaseModifier +local registerAbility = ____dota_ts_adapter.registerAbility +local registerModifier = ____dota_ts_adapter.registerModifier +--- Caustic Finale по логике dota1x6-free: каждое попадание ApplyPoisonFromHit → стаки на цели; +-- взрыв DealExplosionDamage (частица на attach_hitloc как в DealDamage референса). +____exports.sandking_caustic_finale_custom = __TS__Class() +local sandking_caustic_finale_custom = ____exports.sandking_caustic_finale_custom +sandking_caustic_finale_custom.name = "sandking_caustic_finale_custom" +sandking_caustic_finale_custom.____file_path = "scripts/vscripts/abilities/heroes/sand_king/sandking_caustic_finale_custom.lua" +__TS__ClassExtends(sandking_caustic_finale_custom, BaseAbility) +function sandking_caustic_finale_custom.prototype.Precache(self, context) + PrecacheResource("particle", "particles/units/heroes/hero_sandking/sandking_caustic_finale_explode.vpcf", context) + PrecacheResource("soundfile", "soundevents/game_sounds_heroes/game_sounds_sandking.vsndevts", context) +end +function sandking_caustic_finale_custom.prototype.GetIntrinsicModifierName(self) + return modifier_sandking_caustic_finale_passive.name +end +function sandking_caustic_finale_custom.prototype.ApplyPoisonFromHit(self, target) + if not IsServer() then + return + end + local caster = self:GetCaster() + if not caster or not target or not target:IsAlive() or caster:PassivesDisabled() then + return + end + if target:GetTeamNumber() == caster:GetTeamNumber() then + return + end + local duration = self:GetSpecialValueFor("caustic_finale_duration") + local maxAttacks = self:GetSpecialValueFor("max_attacks") + target:AddNewModifier(caster, self, modifier_sandking_caustic_finale_stacks.name, {duration = duration, max_attacks = maxAttacks}) +end +function sandking_caustic_finale_custom.prototype.DealExplosionDamage(self, sourceUnit, fromDeathExplosion) + if not IsServer() then + return + end + local caster = self:GetCaster() + if not caster or caster:IsNull() then + return + end + local radius = self:GetSpecialValueFor("caustic_finale_radius") + local slowPct = self:GetSpecialValueFor("explosion_slow_pct") + local hero = caster + local heroLevel = hero:IsHero() and math.max( + 1, + hero:GetLevel() + ) or 1 + local dmgPerHeroLevel = self:GetSpecialValueFor("explosion_damage_per_hero_level") + local baseDamage = dmgPerHeroLevel * heroLevel + local pctBase = self:GetSpecialValueFor("kill_explosion_max_hp_pct_base") + local pctPerHeroLevel = self:GetSpecialValueFor("kill_explosion_max_hp_pct_per_level") + local killPctTotal = pctBase + pctPerHeroLevel * heroLevel + local killBonus = 0 + if fromDeathExplosion and sourceUnit and not sourceUnit:IsNull() then + killBonus = sourceUnit:GetMaxHealth() * killPctTotal / 100 + end + local damage = baseDamage + killBonus + local pfx = ParticleManager:CreateParticle("particles/units/heroes/hero_sandking/sandking_caustic_finale_explode.vpcf", PATTACH_CUSTOMORIGIN, caster) + ParticleManager:SetParticleControlEnt( + pfx, + 0, + sourceUnit, + PATTACH_POINT_FOLLOW, + "attach_hitloc", + sourceUnit:GetAbsOrigin(), + true + ) + ParticleManager:ReleaseParticleIndex(pfx) + EmitSoundOn("Ability.SandKing_CausticFinale", sourceUnit) + local origin = GetGroundPosition( + sourceUnit:GetAbsOrigin(), + sourceUnit + ) + local enemies = FindUnitsInRadius( + caster:GetTeamNumber(), + origin, + nil, + radius, + DOTA_UNIT_TARGET_TEAM_ENEMY, + bit.bor(DOTA_UNIT_TARGET_HERO, DOTA_UNIT_TARGET_BASIC), + DOTA_UNIT_TARGET_FLAG_NONE, + FIND_ANY_ORDER, + false + ) + for ____, enemy in ipairs(enemies) do + do + if not enemy:IsAlive() then + goto __continue12 + end + if enemy == sourceUnit and sourceUnit:IsAlive() then + goto __continue12 + end + ApplyDamage({ + victim = enemy, + attacker = caster, + damage = damage, + damage_type = DAMAGE_TYPE_MAGICAL, + ability = self + }) + enemy:AddNewModifier( + caster, + self, + modifier_sandking_caustic_explosion_slow.name, + { + duration = self:GetSpecialValueFor("caustic_finale_duration"), + slow_pct = slowPct + } + ) + end + ::__continue12:: + end +end +sandking_caustic_finale_custom = __TS__Decorate( + sandking_caustic_finale_custom, + sandking_caustic_finale_custom, + {registerAbility(nil)}, + {kind = "class", name = "sandking_caustic_finale_custom"} +) +____exports.sandking_caustic_finale_custom = sandking_caustic_finale_custom +modifier_sandking_caustic_finale_passive = __TS__Class() +modifier_sandking_caustic_finale_passive.name = "modifier_sandking_caustic_finale_passive" +modifier_sandking_caustic_finale_passive.____file_path = "scripts/vscripts/abilities/heroes/sand_king/sandking_caustic_finale_custom.lua" +__TS__ClassExtends(modifier_sandking_caustic_finale_passive, BaseModifier) +function modifier_sandking_caustic_finale_passive.prototype.IsHidden(self) + return true +end +function modifier_sandking_caustic_finale_passive.prototype.IsPurgable(self) + return false +end +function modifier_sandking_caustic_finale_passive.prototype.DeclareFunctions(self) + return {MODIFIER_EVENT_ON_ATTACK_LANDED} +end +function modifier_sandking_caustic_finale_passive.prototype.OnAttackLanded(self, event) + if not IsServer() then + return + end + local parent = self:GetParent() + if event.attacker ~= parent or not event.target or event.target:IsNull() then + return + end + if event.target:GetTeamNumber() == parent:GetTeamNumber() then + return + end + local ability = self:GetAbility() + if not ability or ability:IsNull() then + return + end + ability:ApplyPoisonFromHit(event.target) +end +modifier_sandking_caustic_finale_passive = __TS__Decorate( + modifier_sandking_caustic_finale_passive, + modifier_sandking_caustic_finale_passive, + {registerModifier(nil)}, + {kind = "class", name = "modifier_sandking_caustic_finale_passive"} +) +modifier_sandking_caustic_finale_stacks = __TS__Class() +modifier_sandking_caustic_finale_stacks.name = "modifier_sandking_caustic_finale_stacks" +modifier_sandking_caustic_finale_stacks.____file_path = "scripts/vscripts/abilities/heroes/sand_king/sandking_caustic_finale_custom.lua" +__TS__ClassExtends(modifier_sandking_caustic_finale_stacks, BaseModifier) +function modifier_sandking_caustic_finale_stacks.prototype.____constructor(self, ...) + BaseModifier.prototype.____constructor(self, ...) + self.maxAttacks = 3 +end +function modifier_sandking_caustic_finale_stacks.prototype.IsHidden(self) + return false +end +function modifier_sandking_caustic_finale_stacks.prototype.IsDebuff(self) + return true +end +function modifier_sandking_caustic_finale_stacks.prototype.IsPurgable(self) + return true +end +function modifier_sandking_caustic_finale_stacks.prototype.GetTexture(self) + return "sandking_caustic_finale" +end +function modifier_sandking_caustic_finale_stacks.prototype.OnCreated(self, params) + if not IsServer() then + return + end + local ____params_max_attacks_2 = params.max_attacks + if ____params_max_attacks_2 == nil then + local ____opt_0 = self:GetAbility() + ____params_max_attacks_2 = ____opt_0 and ____opt_0:GetSpecialValueFor("max_attacks") + end + self.maxAttacks = ____params_max_attacks_2 or 3 + self:SetStackCount(1) +end +function modifier_sandking_caustic_finale_stacks.prototype.OnRefresh(self, params) + if not IsServer() then + return + end + if params.max_attacks ~= nil then + self.maxAttacks = params.max_attacks + end + self:IncrementStackCount() + if self:GetStackCount() >= self.maxAttacks then + local parent = self:GetParent() + local ability = self:GetAbility() + self:Destroy() + if ability and parent and not parent:IsNull() then + ability:DealExplosionDamage(parent, false) + end + end +end +function modifier_sandking_caustic_finale_stacks.prototype.DeclareFunctions(self) + return {MODIFIER_EVENT_ON_DEATH} +end +function modifier_sandking_caustic_finale_stacks.prototype.OnDeath(self, event) + if not IsServer() then + return + end + if event.unit ~= self:GetParent() then + return + end + local ability = self:GetAbility() + if ability and not ability:IsNull() then + ability:DealExplosionDamage( + self:GetParent(), + true + ) + end +end +modifier_sandking_caustic_finale_stacks = __TS__Decorate( + modifier_sandking_caustic_finale_stacks, + modifier_sandking_caustic_finale_stacks, + {registerModifier(nil)}, + {kind = "class", name = "modifier_sandking_caustic_finale_stacks"} +) +modifier_sandking_caustic_explosion_slow = __TS__Class() +modifier_sandking_caustic_explosion_slow.name = "modifier_sandking_caustic_explosion_slow" +modifier_sandking_caustic_explosion_slow.____file_path = "scripts/vscripts/abilities/heroes/sand_king/sandking_caustic_finale_custom.lua" +__TS__ClassExtends(modifier_sandking_caustic_explosion_slow, BaseModifier) +function modifier_sandking_caustic_explosion_slow.prototype.____constructor(self, ...) + BaseModifier.prototype.____constructor(self, ...) + self.slowPct = -20 +end +function modifier_sandking_caustic_explosion_slow.prototype.IsHidden(self) + return false +end +function modifier_sandking_caustic_explosion_slow.prototype.IsDebuff(self) + return true +end +function modifier_sandking_caustic_explosion_slow.prototype.IsPurgable(self) + return true +end +function modifier_sandking_caustic_explosion_slow.prototype.OnCreated(self, params) + if not IsServer() then + return + end + if params.slow_pct ~= nil then + self.slowPct = params.slow_pct + end +end +function modifier_sandking_caustic_explosion_slow.prototype.DeclareFunctions(self) + return {MODIFIER_PROPERTY_MOVESPEED_BONUS_PERCENTAGE} +end +function modifier_sandking_caustic_explosion_slow.prototype.GetModifierMoveSpeedBonus_Percentage(self) + return -math.abs(self.slowPct) +end +modifier_sandking_caustic_explosion_slow = __TS__Decorate( + modifier_sandking_caustic_explosion_slow, + modifier_sandking_caustic_explosion_slow, + {registerModifier(nil)}, + {kind = "class", name = "modifier_sandking_caustic_explosion_slow"} +) +return ____exports diff --git a/scripts/vscripts/abilities/heroes/sand_king/sandking_epicenter_custom.lua b/scripts/vscripts/abilities/heroes/sand_king/sandking_epicenter_custom.lua new file mode 100644 index 0000000..20e7138 --- /dev/null +++ b/scripts/vscripts/abilities/heroes/sand_king/sandking_epicenter_custom.lua @@ -0,0 +1,448 @@ +local ____lualib = require("lualib_bundle") +local __TS__Class = ____lualib.__TS__Class +local __TS__ClassExtends = ____lualib.__TS__ClassExtends +local __TS__Decorate = ____lualib.__TS__Decorate +local ____exports = {} +local modifier_sandking_epicenter_active, modifier_sandking_epicenter_slow +local ____dota_ts_adapter = require("lib.dota_ts_adapter") +local BaseAbility = ____dota_ts_adapter.BaseAbility +local BaseModifier = ____dota_ts_adapter.BaseModifier +local registerAbility = ____dota_ts_adapter.registerAbility +local registerModifier = ____dota_ts_adapter.registerModifier +local ____luck = require("utils.luck") +local getLuck = ____luck.getLuck +local ____sandking_scorpion_strike_custom = require("abilities.heroes.sand_king.sandking_scorpion_strike_custom") +local performSandKingScorpionStrikeAtPoint = ____sandking_scorpion_strike_custom.performSandKingScorpionStrikeAtPoint +____exports.sandking_epicenter_custom = __TS__Class() +local sandking_epicenter_custom = ____exports.sandking_epicenter_custom +sandking_epicenter_custom.name = "sandking_epicenter_custom" +sandking_epicenter_custom.____file_path = "scripts/vscripts/abilities/heroes/sand_king/sandking_epicenter_custom.lua" +__TS__ClassExtends(sandking_epicenter_custom, BaseAbility) +function sandking_epicenter_custom.prototype.Precache(self, context) + PrecacheResource("particle", "particles/units/heroes/hero_sandking/sandking_epicenter_tell.vpcf", context) + PrecacheResource("particle", "particles/units/heroes/hero_sandking/sandking_epicenter.vpcf", context) + PrecacheResource("soundfile", "soundevents/game_sounds_heroes/game_sounds_sandking.vsndevts", context) +end +function sandking_epicenter_custom.prototype.GetBehavior(self) + return bit.bor(DOTA_ABILITY_BEHAVIOR_NO_TARGET, DOTA_ABILITY_BEHAVIOR_IGNORE_BACKSWING) +end +function sandking_epicenter_custom.prototype.GetCastPoint(self) + local cp = BaseAbility.prototype.GetCastPoint(self) + local caster = self:GetCaster() + if caster and not caster:IsNull() and HasShard(nil, caster) then + cp = math.max( + 0.05, + cp - self:GetSpecialValueFor("shard_cast_reduction") + ) + end + return cp +end +function sandking_epicenter_custom.prototype.GetPulsePhaseDuration(self) + return math.max( + 0.01, + self:GetSpecialValueFor("pulse_phase_duration") + ) +end +function sandking_epicenter_custom.prototype.OnAbilityPhaseStart(self) + if not IsServer() then + return true + end + local caster = self:GetCaster() + EmitSoundOn("Ability.SandKing_Epicenter.spell", caster) + local cp = math.max( + 0.05, + self:GetCastPoint() + ) + local playbackRate = 2 / cp + caster:StartGestureWithPlaybackRate(ACT_DOTA_CAST_ABILITY_4, playbackRate) + self.epicenterTellFx = ParticleManager:CreateParticle("particles/units/heroes/hero_sandking/sandking_epicenter_tell.vpcf", PATTACH_CUSTOMORIGIN_FOLLOW, caster) + local tailFollow = PATTACH_POINT_FOLLOW + ParticleManager:SetParticleControlEnt( + self.epicenterTellFx, + 0, + caster, + tailFollow, + "attach_tail", + caster:GetAbsOrigin(), + true + ) + ParticleManager:SetParticleControlEnt( + self.epicenterTellFx, + 1, + caster, + tailFollow, + "attach_tail", + caster:GetAbsOrigin(), + true + ) + ParticleManager:SetParticleControlEnt( + self.epicenterTellFx, + 2, + caster, + tailFollow, + "attach_tail", + caster:GetAbsOrigin(), + true + ) + return true +end +function sandking_epicenter_custom.prototype.OnAbilityPhaseInterrupted(self) + if not IsServer() then + return true + end + local caster = self:GetCaster() + StopSoundOn("Ability.SandKing_Epicenter.spell", caster) + caster:FadeGesture(ACT_DOTA_CAST_ABILITY_4) + self:clearEpicenterTellFx() + return true +end +function sandking_epicenter_custom.prototype.clearEpicenterTellFx(self) + if self.epicenterTellFx == nil then + return + end + ParticleManager:DestroyParticle(self.epicenterTellFx, false) + ParticleManager:ReleaseParticleIndex(self.epicenterTellFx) + self.epicenterTellFx = nil +end +function sandking_epicenter_custom.prototype.OnSpellStart(self) + if not IsServer() then + return + end + local caster = self:GetCaster() + if HasShard(nil, caster) then + StopSoundOn("Ability.SandKing_Epicenter.spell", caster) + end + caster:FadeGesture(ACT_DOTA_CAST_ABILITY_4) + self:clearEpicenterTellFx() + local phaseDur = self:GetPulsePhaseDuration() + local pulses = math.max( + 1, + self:GetSpecialValueFor("epicenter_pulses") + ) + local pulseGap = phaseDur / pulses + caster:AddNewModifier(caster, self, modifier_sandking_epicenter_active.name, {duration = phaseDur + pulseGap + 0.45}) +end +sandking_epicenter_custom = __TS__Decorate( + sandking_epicenter_custom, + sandking_epicenter_custom, + {registerAbility(nil)}, + {kind = "class", name = "sandking_epicenter_custom"} +) +____exports.sandking_epicenter_custom = sandking_epicenter_custom +modifier_sandking_epicenter_active = __TS__Class() +modifier_sandking_epicenter_active.name = "modifier_sandking_epicenter_active" +modifier_sandking_epicenter_active.____file_path = "scripts/vscripts/abilities/heroes/sand_king/sandking_epicenter_custom.lua" +__TS__ClassExtends(modifier_sandking_epicenter_active, BaseModifier) +function modifier_sandking_epicenter_active.prototype.____constructor(self, ...) + BaseModifier.prototype.____constructor(self, ...) + self.nextPulseIndex = 1 + self.pulseInterval = 0.5 + self.thinkStep = 0.1 + self.pulseTimeBank = 0 + self.radiusBase = 0 + self.radiusInc = 0 + self.pulseDamage = 0 + self.slowPct = 0 + self.slowAs = 0 + self.slowDur = 0 + self.totalPulses = 12 + self.shardBreakEvery = 4 + self.shardBreakDur = 3 + self.shardBreakRadius = 300 + self.scepterRollTimeBank = 0 + self.scepterTimeSlotCounter = 0 +end +function modifier_sandking_epicenter_active.prototype.IsHidden(self) + return true +end +function modifier_sandking_epicenter_active.prototype.IsDebuff(self) + return false +end +function modifier_sandking_epicenter_active.prototype.IsPurgable(self) + return false +end +function modifier_sandking_epicenter_active.prototype.OnCreated(self) + if not IsServer() then + return + end + local ability = self:GetAbility() + if not ability then + self:Destroy() + return + end + local parent = self:GetParent() + local phaseDur = ability:GetPulsePhaseDuration() + self.totalPulses = math.max( + 1, + ability:GetSpecialValueFor("epicenter_pulses") + ) + self.pulseInterval = math.max(0.03, phaseDur / self.totalPulses) + self.radiusBase = ability:GetSpecialValueFor("epicenter_radius_base") + self.radiusInc = ability:GetSpecialValueFor("epicenter_radius_increment") + self.pulseDamage = ability:GetSpecialValueFor("epicenter_damage") + self.slowPct = ability:GetSpecialValueFor("epicenter_slow") + self.slowAs = ability:GetSpecialValueFor("epicenter_slow_as") + self.slowDur = ability:GetSpecialValueFor("slow_duration") + self.shardBreakEvery = math.max( + 1, + ability:GetSpecialValueFor("shard_break_count") + ) + self.shardBreakDur = ability:GetSpecialValueFor("shard_break_duration") + self.shardBreakRadius = ability:GetSpecialValueFor("shard_break_radius") + local rollsPerSec = math.max( + 1, + ability:GetSpecialValueFor("scepter_rolls_per_second") + ) + local rollPeriod = 1 / rollsPerSec + self.thinkStep = math.max( + 0.03, + math.min(self.pulseInterval, rollPeriod) + ) + EmitSoundOn("Ability.SandKing_Epicenter", parent) + parent:StartGesture(ACT_DOTA_OVERRIDE_ABILITY_4) + self.pulseTimeBank = 0 + self.scepterRollTimeBank = 0 + self.scepterTimeSlotCounter = 0 + self:DoPulse(parent, ability, 0) + self.nextPulseIndex = 1 + self:StartIntervalThink(self.thinkStep) +end +function modifier_sandking_epicenter_active.prototype.OnDestroy(self) + if not IsServer() then + return + end + local parent = self:GetParent() + parent:FadeGesture(ACT_DOTA_OVERRIDE_ABILITY_4) + StopSoundOn("Ability.SandKing_Epicenter", parent) +end +function modifier_sandking_epicenter_active.prototype.OnIntervalThink(self) + if not IsServer() then + return + end + local parent = self:GetParent() + local ability = self:GetAbility() + if not parent or not parent:IsAlive() or not ability then + self:Destroy() + return + end + self.pulseTimeBank = self.pulseTimeBank + self.thinkStep + while self.pulseTimeBank >= self.pulseInterval and self.nextPulseIndex < self.totalPulses do + local pulseIdx = self.nextPulseIndex + self:DoPulse(parent, ability, pulseIdx) + if HasShard(nil, parent) and self.shardBreakEvery > 0 and (pulseIdx + 1) % self.shardBreakEvery == 0 then + local enemies = FindUnitsInRadius( + parent:GetTeamNumber(), + GetGroundPosition( + parent:GetAbsOrigin(), + parent + ), + nil, + self.shardBreakRadius, + DOTA_UNIT_TARGET_TEAM_ENEMY, + bit.bor(DOTA_UNIT_TARGET_HERO, DOTA_UNIT_TARGET_BASIC), + DOTA_UNIT_TARGET_FLAG_NONE, + FIND_ANY_ORDER, + false + ) + for ____, enemy in ipairs(enemies) do + enemy:AddNewModifier( + parent, + ability, + "modifier_stunned", + {duration = self.shardBreakDur * (1 - enemy:GetStatusResistance())} + ) + end + end + self.pulseTimeBank = self.pulseTimeBank - self.pulseInterval + self.nextPulseIndex = self.nextPulseIndex + 1 + end + if parent:HasScepter() then + local hero = parent + local strike = parent:FindAbilityByName("sandking_scorpion_strike_custom") + if hero:IsHero() and strike and not strike:IsNull() and strike:GetLevel() > 0 then + self.scepterRollTimeBank = self.scepterRollTimeBank + self.thinkStep + local ringForTail = math.max(0, self.nextPulseIndex - 1) + self:tryScepterScorpionStrikesFromBank( + parent, + ability, + strike, + hero, + ringForTail + ) + end + end + if self.nextPulseIndex >= self.totalPulses then + self:Destroy() + end +end +function modifier_sandking_epicenter_active.prototype.tryScepterScorpionStrikesFromBank(self, parent, ability, strike, hero, pulseIdx) + local rollsPerSec = math.max( + 1, + ability:GetSpecialValueFor("scepter_rolls_per_second") + ) + local period = 1 / rollsPerSec + local everyNTicks = math.max( + 1, + ability:GetSpecialValueFor("scepter_proc_every_n_time_checks") + ) + while self.scepterRollTimeBank >= period do + do + self.scepterRollTimeBank = self.scepterRollTimeBank - period + self.scepterTimeSlotCounter = self.scepterTimeSlotCounter + 1 + if self.scepterTimeSlotCounter % everyNTicks ~= 0 then + goto __continue35 + end + self:performScepterTailVolleyOnEnemies( + parent, + ability, + strike, + hero, + pulseIdx + ) + end + ::__continue35:: + end +end +function modifier_sandking_epicenter_active.prototype.findEnemiesInPulseRing(self, caster, pulseIdx) + local radius = self.radiusBase + pulseIdx * self.radiusInc + local ground = GetGroundPosition( + caster:GetAbsOrigin(), + caster + ) + return FindUnitsInRadius( + caster:GetTeamNumber(), + ground, + nil, + radius, + DOTA_UNIT_TARGET_TEAM_ENEMY, + bit.bor(DOTA_UNIT_TARGET_HERO, DOTA_UNIT_TARGET_BASIC), + DOTA_UNIT_TARGET_FLAG_NONE, + FIND_ANY_ORDER, + false + ) +end +function modifier_sandking_epicenter_active.prototype.performScepterTailVolleyOnEnemies(self, parent, ability, strike, hero, pulseIdx) + local enemies = self:findEnemiesInPulseRing(parent, pulseIdx) + if #enemies == 0 then + return + end + local luckPerExtra = math.max( + 1, + ability:GetSpecialValueFor("scepter_luck_per_extra_tail") + ) + local strikeCount = 1 + math.floor(getLuck(nil, hero) / luckPerExtra) + local start = RandomInt(0, #enemies - 1) + do + local i = 0 + while i < strikeCount do + do + local enemy = enemies[(start + i) % #enemies + 1] + if not enemy or enemy:IsNull() or not enemy:IsAlive() then + goto __continue40 + end + local pt = GetGroundPosition( + enemy:GetAbsOrigin(), + parent + ) + performSandKingScorpionStrikeAtPoint(nil, parent, strike, pt) + end + ::__continue40:: + i = i + 1 + end + end +end +function modifier_sandking_epicenter_active.prototype.DoPulse(self, caster, ability, index) + local radius = self.radiusBase + index * self.radiusInc + local ground = GetGroundPosition( + caster:GetAbsOrigin(), + caster + ) + local pulseFx = ParticleManager:CreateParticle("particles/units/heroes/hero_sandking/sandking_epicenter.vpcf", PATTACH_WORLDORIGIN, nil) + ParticleManager:SetParticleControl(pulseFx, 0, ground) + ParticleManager:SetParticleControl( + pulseFx, + 1, + Vector(radius, radius, 1) + ) + ParticleManager:ReleaseParticleIndex(pulseFx) + EmitSoundOnLocationWithCaster(ground, "SandKing.Pulse", caster) + local enemies = FindUnitsInRadius( + caster:GetTeamNumber(), + ground, + nil, + radius, + DOTA_UNIT_TARGET_TEAM_ENEMY, + bit.bor(DOTA_UNIT_TARGET_HERO, DOTA_UNIT_TARGET_BASIC), + DOTA_UNIT_TARGET_FLAG_NONE, + FIND_ANY_ORDER, + false + ) + for ____, enemy in ipairs(enemies) do + ApplyDamage({ + victim = enemy, + attacker = caster, + damage = self.pulseDamage, + damage_type = DAMAGE_TYPE_MAGICAL, + ability = ability + }) + enemy:AddNewModifier(caster, ability, modifier_sandking_epicenter_slow.name, {duration = self.slowDur, slow_pct = self.slowPct, slow_as = self.slowAs}) + end +end +modifier_sandking_epicenter_active = __TS__Decorate( + modifier_sandking_epicenter_active, + modifier_sandking_epicenter_active, + {registerModifier(nil)}, + {kind = "class", name = "modifier_sandking_epicenter_active"} +) +modifier_sandking_epicenter_slow = __TS__Class() +modifier_sandking_epicenter_slow.name = "modifier_sandking_epicenter_slow" +modifier_sandking_epicenter_slow.____file_path = "scripts/vscripts/abilities/heroes/sand_king/sandking_epicenter_custom.lua" +__TS__ClassExtends(modifier_sandking_epicenter_slow, BaseModifier) +function modifier_sandking_epicenter_slow.prototype.____constructor(self, ...) + BaseModifier.prototype.____constructor(self, ...) + self.slowPct = 0 + self.slowAs = 0 +end +function modifier_sandking_epicenter_slow.prototype.IsHidden(self) + return false +end +function modifier_sandking_epicenter_slow.prototype.IsDebuff(self) + return true +end +function modifier_sandking_epicenter_slow.prototype.IsPurgable(self) + return true +end +function modifier_sandking_epicenter_slow.prototype.OnCreated(self, params) + if not IsServer() then + return + end + self.slowPct = params.slow_pct or -30 + self.slowAs = params.slow_as or -50 +end +function modifier_sandking_epicenter_slow.prototype.OnRefresh(self, params) + if not IsServer() then + return + end + if params.slow_pct ~= nil then + self.slowPct = params.slow_pct + end + if params.slow_as ~= nil then + self.slowAs = params.slow_as + end +end +function modifier_sandking_epicenter_slow.prototype.DeclareFunctions(self) + return {MODIFIER_PROPERTY_MOVESPEED_BONUS_PERCENTAGE, MODIFIER_PROPERTY_ATTACKSPEED_BONUS_CONSTANT} +end +function modifier_sandking_epicenter_slow.prototype.GetModifierMoveSpeedBonus_Percentage(self) + return self.slowPct +end +function modifier_sandking_epicenter_slow.prototype.GetModifierAttackSpeedBonus_Constant(self) + return self.slowAs +end +modifier_sandking_epicenter_slow = __TS__Decorate( + modifier_sandking_epicenter_slow, + modifier_sandking_epicenter_slow, + {registerModifier(nil)}, + {kind = "class", name = "modifier_sandking_epicenter_slow"} +) +return ____exports diff --git a/scripts/vscripts/abilities/heroes/sand_king/sandking_sand_storm_custom.lua b/scripts/vscripts/abilities/heroes/sand_king/sandking_sand_storm_custom.lua new file mode 100644 index 0000000..76eda5a --- /dev/null +++ b/scripts/vscripts/abilities/heroes/sand_king/sandking_sand_storm_custom.lua @@ -0,0 +1,256 @@ +local ____lualib = require("lualib_bundle") +local __TS__Class = ____lualib.__TS__Class +local __TS__ClassExtends = ____lualib.__TS__ClassExtends +local __TS__Decorate = ____lualib.__TS__Decorate +local ____exports = {} +local modifier_sandking_sand_storm_thinker, modifier_sandking_sand_storm_slow +local ____dota_ts_adapter = require("lib.dota_ts_adapter") +local BaseAbility = ____dota_ts_adapter.BaseAbility +local BaseModifier = ____dota_ts_adapter.BaseModifier +local registerAbility = ____dota_ts_adapter.registerAbility +local registerModifier = ____dota_ts_adapter.registerModifier +--- Суммарный HP/сек: GetHealthRegen у героя в рантайме, иначе базовый регген. +local function getSandKingHealthRegenPerSecond(self, unit) + local u = unit + if u.GetHealthRegen ~= nil and type(u.GetHealthRegen) == "function" then + return math.max( + 0, + u:GetHealthRegen() + ) + end + return math.max( + 0, + unit:GetBaseHealthRegen() + ) +end +--- Sand Storm как в dota1x6-free: modifier thinker следует за героем, урон тиками damage_tick_rate × sand_storm_damage (DPS). +____exports.sandking_sand_storm_custom = __TS__Class() +local sandking_sand_storm_custom = ____exports.sandking_sand_storm_custom +sandking_sand_storm_custom.name = "sandking_sand_storm_custom" +sandking_sand_storm_custom.____file_path = "scripts/vscripts/abilities/heroes/sand_king/sandking_sand_storm_custom.lua" +__TS__ClassExtends(sandking_sand_storm_custom, BaseAbility) +function sandking_sand_storm_custom.prototype.Precache(self, context) + PrecacheResource("particle", "particles/units/heroes/hero_sandking/sandking_sandstorm.vpcf", context) + PrecacheResource("soundfile", "soundevents/game_sounds_heroes/game_sounds_sandking.vsndevts", context) +end +function sandking_sand_storm_custom.prototype.GetBehavior(self) + return bit.bor( + bit.bor(DOTA_ABILITY_BEHAVIOR_NO_TARGET, DOTA_ABILITY_BEHAVIOR_IMMEDIATE), + DOTA_ABILITY_BEHAVIOR_DONT_RESUME_ATTACK + ) +end +function sandking_sand_storm_custom.prototype.OnSpellStart(self) + if not IsServer() then + return + end + local caster = self:GetCaster() + local duration = self:GetSpecialValueFor("duration") + CreateModifierThinker( + caster, + self, + modifier_sandking_sand_storm_thinker.name, + {duration = duration}, + caster:GetAbsOrigin(), + caster:GetTeamNumber(), + false + ) + caster:EmitSound("SandKing.SandStorm.start") +end +sandking_sand_storm_custom = __TS__Decorate( + sandking_sand_storm_custom, + sandking_sand_storm_custom, + {registerAbility(nil)}, + {kind = "class", name = "sandking_sand_storm_custom"} +) +____exports.sandking_sand_storm_custom = sandking_sand_storm_custom +modifier_sandking_sand_storm_thinker = __TS__Class() +modifier_sandking_sand_storm_thinker.name = "modifier_sandking_sand_storm_thinker" +modifier_sandking_sand_storm_thinker.____file_path = "scripts/vscripts/abilities/heroes/sand_king/sandking_sand_storm_custom.lua" +__TS__ClassExtends(modifier_sandking_sand_storm_thinker, BaseModifier) +function modifier_sandking_sand_storm_thinker.prototype.____constructor(self, ...) + BaseModifier.prototype.____constructor(self, ...) + self.radius = 0 +end +function modifier_sandking_sand_storm_thinker.prototype.IsHidden(self) + return true +end +function modifier_sandking_sand_storm_thinker.prototype.IsPurgable(self) + return false +end +function modifier_sandking_sand_storm_thinker.prototype.IsDebuff(self) + return false +end +function modifier_sandking_sand_storm_thinker.prototype.RemoveOnDeath(self) + return true +end +function modifier_sandking_sand_storm_thinker.prototype.OnCreated(self, params) + if not IsServer() then + return + end + local ability = self:GetAbility() + local parent = self:GetParent() + local caster = self:GetCaster() + if not caster or caster:IsNull() then + return + end + self.caster = caster + self.radius = ability:GetSpecialValueFor("sand_storm_radius") + local tick = math.max( + 0.05, + ability:GetSpecialValueFor("damage_tick_rate") + ) + parent:SetAbsOrigin(GetGroundPosition( + self.caster:GetAbsOrigin(), + self.caster + )) + self.particle = ParticleManager:CreateParticle("particles/units/heroes/hero_sandking/sandking_sandstorm.vpcf", PATTACH_WORLDORIGIN, nil) + ParticleManager:SetParticleControl( + self.particle, + 0, + parent:GetAbsOrigin() + ) + ParticleManager:SetParticleControl( + self.particle, + 1, + Vector(self.radius, self.radius, self.radius) + ) + self:AddParticle( + self.particle, + false, + false, + -1, + false, + false + ) + parent:EmitSound("SandKing.SandStorm.loop") + self:StartIntervalThink(tick) + self:tickStorm(false) +end +function modifier_sandking_sand_storm_thinker.prototype.OnIntervalThink(self) + if not IsServer() then + return + end + if not self.caster or self.caster:IsNull() or not self.caster:IsAlive() then + self:Destroy() + return + end + local parent = self:GetParent() + local pos = GetGroundPosition( + self.caster:GetAbsOrigin(), + self.caster + ) + parent:SetAbsOrigin(pos) + if self.particle then + ParticleManager:SetParticleControl(self.particle, 0, pos) + ParticleManager:SetParticleControl( + self.particle, + 1, + Vector(self.radius, self.radius, self.radius) + ) + end + self:tickStorm(true) +end +function modifier_sandking_sand_storm_thinker.prototype.OnDestroy(self) + if not IsServer() then + return + end + local parent = self:GetParent() + StopSoundOn("SandKing.SandStorm.loop", parent) + UTIL_Remove(parent) +end +function modifier_sandking_sand_storm_thinker.prototype.tickStorm(self, fromInterval) + local ability = self:GetAbility() + if not ability or not self.caster or not self.caster:IsAlive() then + return + end + local tickRate = math.max( + 0.05, + ability:GetSpecialValueFor("damage_tick_rate") + ) + local basePerTick = tickRate * ability:GetSpecialValueFor("sand_storm_damage") + local regenPct = ability:GetSpecialValueFor("sand_storm_regen_damage_pct") / 100 + local regen = getSandKingHealthRegenPerSecond(nil, self.caster) + local bonusFromHpRegen = tickRate * self.caster:GetMaxHealth() * regenPct * regen + local damagePerTick = basePerTick + bonusFromHpRegen + local slowPct = ability:GetSpecialValueFor("sand_storm_move_speed") + local origin = self:GetParent():GetAbsOrigin() + local enemies = FindUnitsInRadius( + self.caster:GetTeamNumber(), + origin, + nil, + self.radius, + DOTA_UNIT_TARGET_TEAM_ENEMY, + bit.bor(DOTA_UNIT_TARGET_HERO, DOTA_UNIT_TARGET_BASIC), + DOTA_UNIT_TARGET_FLAG_NONE, + FIND_ANY_ORDER, + false + ) + if #enemies == 0 and fromInterval then + return + end + for ____, enemy in ipairs(enemies) do + ApplyDamage({ + victim = enemy, + attacker = self.caster, + damage = damagePerTick, + damage_type = DAMAGE_TYPE_MAGICAL, + ability = ability + }) + enemy:AddNewModifier( + self.caster, + ability, + modifier_sandking_sand_storm_slow.name, + { + duration = math.max(tickRate * 3, 0.45), + slow_pct = slowPct + } + ) + end +end +modifier_sandking_sand_storm_thinker = __TS__Decorate( + modifier_sandking_sand_storm_thinker, + modifier_sandking_sand_storm_thinker, + {registerModifier(nil)}, + {kind = "class", name = "modifier_sandking_sand_storm_thinker"} +) +modifier_sandking_sand_storm_slow = __TS__Class() +modifier_sandking_sand_storm_slow.name = "modifier_sandking_sand_storm_slow" +modifier_sandking_sand_storm_slow.____file_path = "scripts/vscripts/abilities/heroes/sand_king/sandking_sand_storm_custom.lua" +__TS__ClassExtends(modifier_sandking_sand_storm_slow, BaseModifier) +function modifier_sandking_sand_storm_slow.prototype.____constructor(self, ...) + BaseModifier.prototype.____constructor(self, ...) + self.slowPct = 0 +end +function modifier_sandking_sand_storm_slow.prototype.IsHidden(self) + return true +end +function modifier_sandking_sand_storm_slow.prototype.IsDebuff(self) + return true +end +function modifier_sandking_sand_storm_slow.prototype.IsPurgable(self) + return true +end +function modifier_sandking_sand_storm_slow.prototype.OnCreated(self, params) + if not IsServer() then + return + end + local ____params_slow_pct_2 = params.slow_pct + if ____params_slow_pct_2 == nil then + local ____opt_0 = self:GetAbility() + ____params_slow_pct_2 = ____opt_0 and ____opt_0:GetSpecialValueFor("sand_storm_move_speed") + end + self.slowPct = ____params_slow_pct_2 or 60 +end +function modifier_sandking_sand_storm_slow.prototype.DeclareFunctions(self) + return {MODIFIER_PROPERTY_MOVESPEED_BONUS_PERCENTAGE} +end +function modifier_sandking_sand_storm_slow.prototype.GetModifierMoveSpeedBonus_Percentage(self) + return -math.abs(self.slowPct) +end +modifier_sandking_sand_storm_slow = __TS__Decorate( + modifier_sandking_sand_storm_slow, + modifier_sandking_sand_storm_slow, + {registerModifier(nil)}, + {kind = "class", name = "modifier_sandking_sand_storm_slow"} +) +return ____exports diff --git a/scripts/vscripts/abilities/heroes/sand_king/sandking_scorpion_strike_custom.lua b/scripts/vscripts/abilities/heroes/sand_king/sandking_scorpion_strike_custom.lua new file mode 100644 index 0000000..d8b32f8 --- /dev/null +++ b/scripts/vscripts/abilities/heroes/sand_king/sandking_scorpion_strike_custom.lua @@ -0,0 +1,256 @@ +local ____lualib = require("lualib_bundle") +local __TS__Class = ____lualib.__TS__Class +local __TS__ClassExtends = ____lualib.__TS__ClassExtends +local __TS__Decorate = ____lualib.__TS__Decorate +local ____exports = {} +local modifier_sandking_scorpion_strike_proc +local ____dota_ts_adapter = require("lib.dota_ts_adapter") +local BaseAbility = ____dota_ts_adapter.BaseAbility +local BaseModifier = ____dota_ts_adapter.BaseModifier +local registerAbility = ____dota_ts_adapter.registerAbility +local registerModifier = ____dota_ts_adapter.registerModifier +--- Полный эффект Scorpion Strike в точке — для каста по курсору и для прока Epicenter (без виртуального каста абилки). +function ____exports.performSandKingScorpionStrikeAtPoint(self, caster, strikeAbility, point) + if not IsServer() then + return + end + if not caster:IsAlive() or strikeAbility:IsNull() or strikeAbility:GetLevel() < 1 or not caster:IsHero() then + return + end + local ground = GetGroundPosition(point, caster) + local radius = strikeAbility:GetSpecialValueFor("radius") + local innerRadius = strikeAbility:GetSpecialValueFor("inner_radius") + local debuffDuration = strikeAbility:GetSpecialValueFor("debuff_duration") + local slowPct = strikeAbility:GetSpecialValueFor("strike_slow") + local aoeFx = ParticleManager:CreateParticle("particles/units/heroes/hero_sandking/sandking_scorpion_strike_aoe.vpcf", PATTACH_WORLDORIGIN, nil) + ParticleManager:SetParticleControl(aoeFx, 0, ground) + ParticleManager:SetParticleControl( + aoeFx, + 1, + Vector(radius * 1.1, 0, 0) + ) + ParticleManager:ReleaseParticleIndex(aoeFx) + EmitSoundOnLocationWithCaster(ground, "Hero_Sandking.Stinger", caster) + caster:AddNewModifier(caster, strikeAbility, modifier_sandking_scorpion_strike_proc.name, {}) + local enemies = FindUnitsInRadius( + caster:GetTeamNumber(), + ground, + nil, + radius, + DOTA_UNIT_TARGET_TEAM_ENEMY, + bit.bor(DOTA_UNIT_TARGET_HERO, DOTA_UNIT_TARGET_BASIC), + DOTA_UNIT_TARGET_FLAG_NONE, + FIND_ANY_ORDER, + false + ) + local procMod = caster:FindModifierByName(modifier_sandking_scorpion_strike_proc.name) + local finale = caster:FindAbilityByName("sandking_caustic_finale_custom") + local extraPoisonStacks = strikeAbility:GetSpecialValueFor("scepter_stack") + for ____, enemy in ipairs(enemies) do + local dist = (enemy:GetAbsOrigin() - ground):Length2D() + if procMod and not procMod:IsNull() then + procMod:SetStackCount(dist <= innerRadius and 1 or 0) + end + local hitFx = ParticleManager:CreateParticle("particles/units/heroes/hero_sandking/sandking_scorpion_strike_hit.vpcf", PATTACH_CUSTOMORIGIN, caster) + ParticleManager:SetParticleControlEnt( + hitFx, + 0, + caster, + PATTACH_POINT_FOLLOW, + "attach_tail", + caster:GetAbsOrigin(), + true + ) + ParticleManager:SetParticleControlEnt( + hitFx, + 1, + enemy, + PATTACH_POINT_FOLLOW, + "attach_hitloc", + enemy:GetAbsOrigin(), + true + ) + ParticleManager:ReleaseParticleIndex(hitFx) + enemy:AddNewModifier( + caster, + strikeAbility, + ____exports.modifier_sandking_scorpion_strike_slow.name, + { + duration = debuffDuration * (1 - enemy:GetStatusResistance()), + slow_pct = slowPct + } + ) + caster:PerformAttack( + enemy, + true, + true, + true, + true, + false, + false, + true + ) + if caster:HasScepter() and finale and not finale:IsNull() then + do + local i = 0 + while i < extraPoisonStacks do + finale:ApplyPoisonFromHit(enemy) + i = i + 1 + end + end + end + end + if procMod and not procMod:IsNull() then + procMod:Destroy() + end +end +--- Scorpion Strike по образцу dota1x6-free: AOE партикл, звук Hero_Sandking.Stinger, +-- PerformAttack с бонусом урона во внутреннем радиусе через временный модификатор на кастере. +____exports.sandking_scorpion_strike_custom = __TS__Class() +local sandking_scorpion_strike_custom = ____exports.sandking_scorpion_strike_custom +sandking_scorpion_strike_custom.name = "sandking_scorpion_strike_custom" +sandking_scorpion_strike_custom.____file_path = "scripts/vscripts/abilities/heroes/sand_king/sandking_scorpion_strike_custom.lua" +__TS__ClassExtends(sandking_scorpion_strike_custom, BaseAbility) +function sandking_scorpion_strike_custom.prototype.Precache(self, context) + PrecacheResource("particle", "particles/units/heroes/hero_sandking/sandking_scorpion_strike_aoe.vpcf", context) + PrecacheResource("particle", "particles/units/heroes/hero_sandking/sandking_scorpion_strike_hit.vpcf", context) + PrecacheResource("soundfile", "soundevents/game_sounds_heroes/game_sounds_sandking.vsndevts", context) +end +function sandking_scorpion_strike_custom.prototype.GetBehavior(self) + return bit.bor( + bit.bor(DOTA_ABILITY_BEHAVIOR_POINT, DOTA_ABILITY_BEHAVIOR_AOE), + DOTA_ABILITY_BEHAVIOR_IGNORE_BACKSWING + ) +end +function sandking_scorpion_strike_custom.prototype.GetAOERadius(self) + return self:GetSpecialValueFor("radius") +end +function sandking_scorpion_strike_custom.prototype.GetCooldown(self, level) + local cd = BaseAbility.prototype.GetCooldown(self, level) + if self:GetCaster():HasScepter() then + local pct = self:GetSpecialValueFor("scepter_cd_pct") + cd = cd * math.max(0, (100 - pct) / 100) + end + return cd +end +function sandking_scorpion_strike_custom.prototype.OnSpellStart(self) + if not IsServer() then + return + end + local caster = self:GetCaster() + ____exports.performSandKingScorpionStrikeAtPoint( + nil, + caster, + self, + GetGroundPosition( + self:GetCursorPosition(), + caster + ) + ) +end +sandking_scorpion_strike_custom = __TS__Decorate( + sandking_scorpion_strike_custom, + sandking_scorpion_strike_custom, + {registerAbility(nil)}, + {kind = "class", name = "sandking_scorpion_strike_custom"} +) +____exports.sandking_scorpion_strike_custom = sandking_scorpion_strike_custom +modifier_sandking_scorpion_strike_proc = __TS__Class() +modifier_sandking_scorpion_strike_proc.name = "modifier_sandking_scorpion_strike_proc" +modifier_sandking_scorpion_strike_proc.____file_path = "scripts/vscripts/abilities/heroes/sand_king/sandking_scorpion_strike_custom.lua" +__TS__ClassExtends(modifier_sandking_scorpion_strike_proc, BaseModifier) +function modifier_sandking_scorpion_strike_proc.prototype.IsHidden(self) + return true +end +function modifier_sandking_scorpion_strike_proc.prototype.IsPurgable(self) + return false +end +function modifier_sandking_scorpion_strike_proc.prototype.OnCreated(self) + if not IsServer() then + return + end + self:SetStackCount(0) +end +function modifier_sandking_scorpion_strike_proc.prototype.DeclareFunctions(self) + return {MODIFIER_PROPERTY_PROCATTACK_BONUS_DAMAGE_PHYSICAL, MODIFIER_PROPERTY_DAMAGEOUTGOING_PERCENTAGE} +end +function modifier_sandking_scorpion_strike_proc.prototype.GetModifierProcAttack_BonusPhysicalDamage(self, event) + local ability = self:GetAbility() + if not ability or event.attacker ~= self:GetParent() then + return 0 + end + local parent = self:GetParent() + local hpPct = ability:GetSpecialValueFor("scorpion_bonus_damage_max_hp_pct") / 100 + local fromMaxHp = parent:GetMaxHealth() * hpPct + return ability:GetSpecialValueFor("attack_damage") + event.attacker:GetAverageTrueAttackDamage(event.attacker) * (ability:GetSpecialValueFor("attack_damage_pct_from_attack") / 100) + fromMaxHp +end +function modifier_sandking_scorpion_strike_proc.prototype.GetModifierDamageOutgoing_Percentage(self, event) + if event.attacker ~= self:GetParent() then + return 0 + end + if self:GetStackCount() == 0 then + return 0 + end + local ability = self:GetAbility() + if not ability then + return 0 + end + return ability:GetSpecialValueFor("inner_radius_bonus_damage_pct") +end +modifier_sandking_scorpion_strike_proc = __TS__Decorate( + modifier_sandking_scorpion_strike_proc, + modifier_sandking_scorpion_strike_proc, + {registerModifier(nil)}, + {kind = "class", name = "modifier_sandking_scorpion_strike_proc"} +) +____exports.modifier_sandking_scorpion_strike_slow = __TS__Class() +local modifier_sandking_scorpion_strike_slow = ____exports.modifier_sandking_scorpion_strike_slow +modifier_sandking_scorpion_strike_slow.name = "modifier_sandking_scorpion_strike_slow" +modifier_sandking_scorpion_strike_slow.____file_path = "scripts/vscripts/abilities/heroes/sand_king/sandking_scorpion_strike_custom.lua" +__TS__ClassExtends(modifier_sandking_scorpion_strike_slow, BaseModifier) +function modifier_sandking_scorpion_strike_slow.prototype.____constructor(self, ...) + BaseModifier.prototype.____constructor(self, ...) + self.slowPct = 0 +end +function modifier_sandking_scorpion_strike_slow.prototype.IsHidden(self) + return false +end +function modifier_sandking_scorpion_strike_slow.prototype.IsDebuff(self) + return true +end +function modifier_sandking_scorpion_strike_slow.prototype.IsPurgable(self) + return true +end +function modifier_sandking_scorpion_strike_slow.prototype.OnCreated(self, params) + if not IsServer() then + return + end + local ____params_slow_pct_2 = params.slow_pct + if ____params_slow_pct_2 == nil then + local ____opt_0 = self:GetAbility() + ____params_slow_pct_2 = ____opt_0 and ____opt_0:GetSpecialValueFor("strike_slow") + end + self.slowPct = ____params_slow_pct_2 or -10 +end +function modifier_sandking_scorpion_strike_slow.prototype.OnRefresh(self, params) + if not IsServer() then + return + end + if params.slow_pct ~= nil then + self.slowPct = params.slow_pct + end +end +function modifier_sandking_scorpion_strike_slow.prototype.DeclareFunctions(self) + return {MODIFIER_PROPERTY_MOVESPEED_BONUS_PERCENTAGE} +end +function modifier_sandking_scorpion_strike_slow.prototype.GetModifierMoveSpeedBonus_Percentage(self) + return self.slowPct +end +modifier_sandking_scorpion_strike_slow = __TS__Decorate( + modifier_sandking_scorpion_strike_slow, + modifier_sandking_scorpion_strike_slow, + {registerModifier(nil)}, + {kind = "class", name = "modifier_sandking_scorpion_strike_slow"} +) +____exports.modifier_sandking_scorpion_strike_slow = modifier_sandking_scorpion_strike_slow +return ____exports diff --git a/scripts/vscripts/abilities/heroes/sargatanas/ability_firecleave.lua b/scripts/vscripts/abilities/heroes/sargatanas/ability_firecleave.lua new file mode 100644 index 0000000..c4b1e07 --- /dev/null +++ b/scripts/vscripts/abilities/heroes/sargatanas/ability_firecleave.lua @@ -0,0 +1,199 @@ +local ____lualib = require("lualib_bundle") +local __TS__Class = ____lualib.__TS__Class +local __TS__ClassExtends = ____lualib.__TS__ClassExtends +local __TS__Decorate = ____lualib.__TS__Decorate +local ____exports = {} +local ____dota_ts_adapter = require("lib.dota_ts_adapter") +local BaseAbility = ____dota_ts_adapter.BaseAbility +local BaseModifier = ____dota_ts_adapter.BaseModifier +local registerAbility = ____dota_ts_adapter.registerAbility +local registerModifier = ____dota_ts_adapter.registerModifier +local ____modifier_overheating = require("abilities.heroes.sargatanas.modifier_overheating") +local modifier_overheating = ____modifier_overheating.modifier_overheating +local function DamageHell(self, unit) + if unit:HasModifier(modifier_overheating.name) then + local modif = unit:FindModifierByName(modifier_overheating.name) + if modif then + return modif:GetStackCount() / 100 + 1 + end + end + return 1 +end +____exports.ability_firecleave = __TS__Class() +local ability_firecleave = ____exports.ability_firecleave +ability_firecleave.name = "ability_firecleave" +ability_firecleave.____file_path = "scripts/vscripts/abilities/heroes/sargatanas/ability_firecleave.lua" +__TS__ClassExtends(ability_firecleave, BaseAbility) +function ability_firecleave.prototype.GetIntrinsicModifierName(self) + return ____exports.modifier_ability_firecleave.name +end +ability_firecleave = __TS__Decorate( + ability_firecleave, + ability_firecleave, + {registerAbility(nil)}, + {kind = "class", name = "ability_firecleave"} +) +____exports.ability_firecleave = ability_firecleave +____exports.modifier_ability_firecleave = __TS__Class() +local modifier_ability_firecleave = ____exports.modifier_ability_firecleave +modifier_ability_firecleave.name = "modifier_ability_firecleave" +modifier_ability_firecleave.____file_path = "scripts/vscripts/abilities/heroes/sargatanas/ability_firecleave.lua" +__TS__ClassExtends(modifier_ability_firecleave, BaseModifier) +function modifier_ability_firecleave.prototype.____constructor(self, ...) + BaseModifier.prototype.____constructor(self, ...) + self.cleaveStartingWidth = 0 + self.cleaveEndingWidth = 0 + self.cleaveDistance = 0 + self.greatCleaveDamage = 0 +end +function modifier_ability_firecleave.prototype.IsHidden(self) + return true +end +function modifier_ability_firecleave.prototype.IsPurgable(self) + return false +end +function modifier_ability_firecleave.prototype.IsDebuff(self) + return false +end +function modifier_ability_firecleave.prototype.IsBuff(self) + return true +end +function modifier_ability_firecleave.prototype.RemoveOnDeath(self) + return true +end +function modifier_ability_firecleave.prototype.AllowIllusionDuplicate(self) + return false +end +function modifier_ability_firecleave.prototype.DeclareFunctions(self) + return {MODIFIER_EVENT_ON_ATTACK_LANDED} +end +function modifier_ability_firecleave.prototype.OnCreated(self) + if not IsServer() then + return + end + local ability = self:GetAbility() + if not ability then + return + end + self.cleaveStartingWidth = ability:GetSpecialValueFor("cleave_starting_width") + self.cleaveEndingWidth = ability:GetSpecialValueFor("cleave_ending_width") + self.cleaveDistance = ability:GetSpecialValueFor("cleave_distance") +end +function modifier_ability_firecleave.prototype.OnRefresh(self) + self:OnCreated() +end +function modifier_ability_firecleave.prototype.OnAttackLanded(self, event) + if not IsServer() then + return + end + local caster = self:GetParent() + local target = event.target + local attacker = event.attacker + local ability = self:GetAbility() + if not ability then + return + end + local duration = ability:GetSpecialValueFor("duration") + if caster == attacker and not caster:PassivesDisabled() then + self.greatCleaveDamage = ability:GetSpecialValueFor("great_cleave_damage") / 100 * self:GetCaster():GetAverageTrueAttackDamage(event.target) + if target:HasModifier(____exports.modifier_ability_firecleave_fire.name) then + local modif = target:FindModifierByName(____exports.modifier_ability_firecleave_fire.name) + if modif then + modif:IncrementStackCount() + modif:SetDuration(duration, true) + end + else + local newModif = target:AddNewModifier(caster, ability, ____exports.modifier_ability_firecleave_fire.name, {duration = duration}) + if newModif ~= nil and newModif ~= nil then + newModif:SetStackCount(1) + end + end + local fx = "particles/econ/items/sven/sven_ti7_sword/sven_ti7_sword_spell_great_cleave_gods_strength_crit_b.vpcf" + DoCleaveAttack( + caster, + target, + ability, + self.greatCleaveDamage, + self.cleaveStartingWidth, + self.cleaveEndingWidth, + self.cleaveDistance, + fx + ) + end +end +modifier_ability_firecleave = __TS__Decorate( + modifier_ability_firecleave, + modifier_ability_firecleave, + {registerModifier(nil)}, + {kind = "class", name = "modifier_ability_firecleave"} +) +____exports.modifier_ability_firecleave = modifier_ability_firecleave +____exports.modifier_ability_firecleave_fire = __TS__Class() +local modifier_ability_firecleave_fire = ____exports.modifier_ability_firecleave_fire +modifier_ability_firecleave_fire.name = "modifier_ability_firecleave_fire" +modifier_ability_firecleave_fire.____file_path = "scripts/vscripts/abilities/heroes/sargatanas/ability_firecleave.lua" +__TS__ClassExtends(modifier_ability_firecleave_fire, BaseModifier) +function modifier_ability_firecleave_fire.prototype.IsHidden(self) + return false +end +function modifier_ability_firecleave_fire.prototype.IsPurgable(self) + return true +end +function modifier_ability_firecleave_fire.prototype.IsDebuff(self) + return true +end +function modifier_ability_firecleave_fire.prototype.IsBuff(self) + return false +end +function modifier_ability_firecleave_fire.prototype.RemoveOnDeath(self) + return true +end +function modifier_ability_firecleave_fire.prototype.AllowIllusionDuplicate(self) + return false +end +function modifier_ability_firecleave_fire.prototype.GetEffectName(self) + return "particles/econ/items/huskar/huskar_2021_immortal/huskar_2021_immortal_burning_spear_debuff_flame_circulate.vpcf" +end +function modifier_ability_firecleave_fire.prototype.GetEffectAttachType(self) + return PATTACH_ABSORIGIN_FOLLOW +end +function modifier_ability_firecleave_fire.prototype.OnCreated(self) + if not IsServer() then + return + end + self:StartIntervalThink(1) +end +function modifier_ability_firecleave_fire.prototype.OnIntervalThink(self) + if not IsServer() then + return + end + local ability = self:GetAbility() + local caster = self:GetCaster() + if not ability or not caster then + return + end + local damage + if caster:HasModifier("modifier_ability_metamorphosis") then + damage = ability:GetSpecialValueFor("fire_damage") * 2 + else + damage = ability:GetSpecialValueFor("fire_damage") + end + ApplyDamage({ + victim = self:GetParent(), + attacker = caster, + damage = damage * DamageHell( + nil, + self:GetParent() + ), + damage_type = ability:GetAbilityDamageType(), + ability = ability + }) +end +modifier_ability_firecleave_fire = __TS__Decorate( + modifier_ability_firecleave_fire, + modifier_ability_firecleave_fire, + {registerModifier(nil)}, + {kind = "class", name = "modifier_ability_firecleave_fire"} +) +____exports.modifier_ability_firecleave_fire = modifier_ability_firecleave_fire +return ____exports diff --git a/scripts/vscripts/abilities/heroes/sargatanas/ability_firecleave_creep.lua b/scripts/vscripts/abilities/heroes/sargatanas/ability_firecleave_creep.lua new file mode 100644 index 0000000..41d0ce3 --- /dev/null +++ b/scripts/vscripts/abilities/heroes/sargatanas/ability_firecleave_creep.lua @@ -0,0 +1,162 @@ +local ____lualib = require("lualib_bundle") +local __TS__Class = ____lualib.__TS__Class +local __TS__ClassExtends = ____lualib.__TS__ClassExtends +local __TS__Decorate = ____lualib.__TS__Decorate +local ____exports = {} +local ____dota_ts_adapter = require("lib.dota_ts_adapter") +local BaseAbility = ____dota_ts_adapter.BaseAbility +local BaseModifier = ____dota_ts_adapter.BaseModifier +local registerAbility = ____dota_ts_adapter.registerAbility +local registerModifier = ____dota_ts_adapter.registerModifier +local ____modifier_overheating = require("abilities.heroes.sargatanas.modifier_overheating") +local modifier_overheating = ____modifier_overheating.modifier_overheating +local function DamageHell(self, unit) + if unit:HasModifier(modifier_overheating.name) then + local modif = unit:FindModifierByName(modifier_overheating.name) + if modif then + return modif:GetStackCount() / 100 + 1 + end + end + return 1 +end +____exports.ability_firecleave_creep = __TS__Class() +local ability_firecleave_creep = ____exports.ability_firecleave_creep +ability_firecleave_creep.name = "ability_firecleave_creep" +ability_firecleave_creep.____file_path = "scripts/vscripts/abilities/heroes/sargatanas/ability_firecleave_creep.lua" +__TS__ClassExtends(ability_firecleave_creep, BaseAbility) +function ability_firecleave_creep.prototype.GetIntrinsicModifierName(self) + return ____exports.modifier_ability_firecleave_creep.name +end +ability_firecleave_creep = __TS__Decorate( + ability_firecleave_creep, + ability_firecleave_creep, + {registerAbility(nil)}, + {kind = "class", name = "ability_firecleave_creep"} +) +____exports.ability_firecleave_creep = ability_firecleave_creep +____exports.modifier_ability_firecleave_creep = __TS__Class() +local modifier_ability_firecleave_creep = ____exports.modifier_ability_firecleave_creep +modifier_ability_firecleave_creep.name = "modifier_ability_firecleave_creep" +modifier_ability_firecleave_creep.____file_path = "scripts/vscripts/abilities/heroes/sargatanas/ability_firecleave_creep.lua" +__TS__ClassExtends(modifier_ability_firecleave_creep, BaseModifier) +function modifier_ability_firecleave_creep.prototype.IsHidden(self) + return true +end +function modifier_ability_firecleave_creep.prototype.IsPurgable(self) + return false +end +function modifier_ability_firecleave_creep.prototype.IsDebuff(self) + return false +end +function modifier_ability_firecleave_creep.prototype.IsBuff(self) + return true +end +function modifier_ability_firecleave_creep.prototype.RemoveOnDeath(self) + return true +end +function modifier_ability_firecleave_creep.prototype.AllowIllusionDuplicate(self) + return false +end +function modifier_ability_firecleave_creep.prototype.DeclareFunctions(self) + return {MODIFIER_EVENT_ON_ATTACK_LANDED} +end +function modifier_ability_firecleave_creep.prototype.OnAttackLanded(self, event) + if not IsServer() then + return + end + local caster = self:GetParent() + local target = event.target + local attacker = event.attacker + local ability = self:GetAbility() + if not ability then + return + end + local duration = ability:GetSpecialValueFor("duration") + if caster == attacker and not caster:PassivesDisabled() then + target:AddNewModifier(caster, ability, ____exports.modifier_ability_firecleave_creep_fire.name, {duration = duration}) + local modifier = target:FindModifierByName(modifier_overheating.name) + if modifier then + modifier:SetStackCount(modifier:GetStackCount() + 1) + modifier:SetDuration(8, true) + else + modifier = target:AddNewModifier(caster, ability, modifier_overheating.name, {duration = 8}) + if modifier then + modifier:SetStackCount(1) + end + end + end +end +modifier_ability_firecleave_creep = __TS__Decorate( + modifier_ability_firecleave_creep, + modifier_ability_firecleave_creep, + {registerModifier(nil)}, + {kind = "class", name = "modifier_ability_firecleave_creep"} +) +____exports.modifier_ability_firecleave_creep = modifier_ability_firecleave_creep +____exports.modifier_ability_firecleave_creep_fire = __TS__Class() +local modifier_ability_firecleave_creep_fire = ____exports.modifier_ability_firecleave_creep_fire +modifier_ability_firecleave_creep_fire.name = "modifier_ability_firecleave_creep_fire" +modifier_ability_firecleave_creep_fire.____file_path = "scripts/vscripts/abilities/heroes/sargatanas/ability_firecleave_creep.lua" +__TS__ClassExtends(modifier_ability_firecleave_creep_fire, BaseModifier) +function modifier_ability_firecleave_creep_fire.prototype.IsHidden(self) + return false +end +function modifier_ability_firecleave_creep_fire.prototype.IsPurgable(self) + return true +end +function modifier_ability_firecleave_creep_fire.prototype.IsDebuff(self) + return true +end +function modifier_ability_firecleave_creep_fire.prototype.IsBuff(self) + return false +end +function modifier_ability_firecleave_creep_fire.prototype.RemoveOnDeath(self) + return true +end +function modifier_ability_firecleave_creep_fire.prototype.AllowIllusionDuplicate(self) + return false +end +function modifier_ability_firecleave_creep_fire.prototype.GetEffectName(self) + return "particles/econ/items/huskar/huskar_2021_immortal/huskar_2021_immortal_burning_spear_debuff_flame_circulate.vpcf" +end +function modifier_ability_firecleave_creep_fire.prototype.GetEffectAttachType(self) + return PATTACH_ABSORIGIN_FOLLOW +end +function modifier_ability_firecleave_creep_fire.prototype.GetAttributes(self) + return MODIFIER_ATTRIBUTE_MULTIPLE +end +function modifier_ability_firecleave_creep_fire.prototype.OnCreated(self) + if not IsServer() then + return + end + self:StartIntervalThink(1) +end +function modifier_ability_firecleave_creep_fire.prototype.OnIntervalThink(self) + if not IsServer() then + return + end + local ability = self:GetAbility() + local caster = self:GetCaster() + if not ability or not caster then + return + end + local damage = ability:GetSpecialValueFor("fire_damage") + ApplyDamage({ + victim = self:GetParent(), + attacker = caster, + damage = damage * DamageHell( + nil, + self:GetParent() + ), + damage_type = ability:GetAbilityDamageType(), + ability = ability + }) +end +modifier_ability_firecleave_creep_fire = __TS__Decorate( + modifier_ability_firecleave_creep_fire, + modifier_ability_firecleave_creep_fire, + {registerModifier(nil)}, + {kind = "class", name = "modifier_ability_firecleave_creep_fire"} +) +____exports.modifier_ability_firecleave_creep_fire = modifier_ability_firecleave_creep_fire +return ____exports diff --git a/scripts/vscripts/abilities/heroes/sargatanas/ability_hell_summon.lua b/scripts/vscripts/abilities/heroes/sargatanas/ability_hell_summon.lua new file mode 100644 index 0000000..5232311 --- /dev/null +++ b/scripts/vscripts/abilities/heroes/sargatanas/ability_hell_summon.lua @@ -0,0 +1,171 @@ +local ____lualib = require("lualib_bundle") +local __TS__Class = ____lualib.__TS__Class +local __TS__ClassExtends = ____lualib.__TS__ClassExtends +local __TS__Decorate = ____lualib.__TS__Decorate +local ____exports = {} +local ____dota_ts_adapter = require("lib.dota_ts_adapter") +local BaseAbility = ____dota_ts_adapter.BaseAbility +local BaseModifier = ____dota_ts_adapter.BaseModifier +local registerAbility = ____dota_ts_adapter.registerAbility +local registerModifier = ____dota_ts_adapter.registerModifier +____exports.ability_hell_summon = __TS__Class() +local ability_hell_summon = ____exports.ability_hell_summon +ability_hell_summon.name = "ability_hell_summon" +ability_hell_summon.____file_path = "scripts/vscripts/abilities/heroes/sargatanas/ability_hell_summon.lua" +__TS__ClassExtends(ability_hell_summon, BaseAbility) +function ability_hell_summon.prototype.OnSpellStart(self) + if not IsServer() then + return + end + local caster = self:GetCaster() + local point = caster:GetAbsOrigin() + local portal = CreateUnitByName( + "npc_portal", + point, + true, + nil, + nil, + DOTA_TEAM_GOODGUYS + ) + if not portal then + return + end + local pointForUnit = portal:GetAbsOrigin() + local unitName = self:UnitnameHell() + local strDmg = caster:GetStrength() * self:GetSpecialValueFor("str_dmg") + local strHp = caster:GetStrength() * self:GetSpecialValueFor("str_hp") + portal:AddNewModifier(caster, self, ____exports.modifier_hell_summon_portal.name, {}) + local units = FindUnitsInRadius( + DOTA_TEAM_GOODGUYS, + caster:GetAbsOrigin(), + nil, + -1, + DOTA_UNIT_TARGET_TEAM_FRIENDLY, + DOTA_UNIT_TARGET_BASIC, + DOTA_UNIT_TARGET_FLAG_NONE, + FIND_ANY_ORDER, + false + ) + for ____, unit in ipairs(units) do + if unit:GetOwner() == caster then + unit:Destroy() + end + end + Timers:CreateTimer( + 2, + function() + if not caster:IsAlive() then + return nil + end + if caster:HasModifier("modifier_star_devour_stack") then + local modif = caster:FindModifierByName("modifier_star_devour_stack") + if modif then + local stackCount = modif:GetStackCount() + do + local i = 0 + while i < stackCount do + local unit = CreateUnitByName( + unitName, + pointForUnit, + true, + nil, + nil, + DOTA_TEAM_GOODGUYS + ) + if unit ~= nil and unit ~= nil then + unit:SetOwner(caster) + unit:SetControllableByPlayer( + caster:GetPlayerID(), + true + ) + FindClearSpaceForUnit(unit, pointForUnit, true) + unit:SetBaseMaxHealth(strHp) + unit:SetBaseDamageMin(strDmg) + unit:SetBaseDamageMax(strDmg) + end + i = i + 1 + end + end + caster:RemoveModifierByName("modifier_star_devour_stack") + end + end + return nil + end + ) +end +function ability_hell_summon.prototype.UnitnameHell(self) + local abilityLvl = self:GetLevel() + if abilityLvl == 1 then + return "npc_spirit_sargatanas_hell_summon" + elseif abilityLvl == 2 then + return "npc_golem_sargatanas_hell_summon" + elseif abilityLvl == 3 then + return "npc_scorpion_sargatanas_hell_summon" + elseif abilityLvl == 4 then + return "npc_dragon_sargatanas_hell_summon" + elseif abilityLvl == 5 then + return "npc_kaban_sargatanas_hell_summon" + end + return "npc_spirit_sargatanas_hell_summon" +end +ability_hell_summon = __TS__Decorate( + ability_hell_summon, + ability_hell_summon, + {registerAbility(nil)}, + {kind = "class", name = "ability_hell_summon"} +) +____exports.ability_hell_summon = ability_hell_summon +____exports.modifier_hell_summon_portal = __TS__Class() +local modifier_hell_summon_portal = ____exports.modifier_hell_summon_portal +modifier_hell_summon_portal.name = "modifier_hell_summon_portal" +modifier_hell_summon_portal.____file_path = "scripts/vscripts/abilities/heroes/sargatanas/ability_hell_summon.lua" +__TS__ClassExtends(modifier_hell_summon_portal, BaseModifier) +function modifier_hell_summon_portal.prototype.IsHidden(self) + return true +end +function modifier_hell_summon_portal.prototype.IsPurgable(self) + return false +end +function modifier_hell_summon_portal.prototype.RemoveOnDeath(self) + return false +end +function modifier_hell_summon_portal.prototype.AllowIllusionDuplicate(self) + return true +end +function modifier_hell_summon_portal.prototype.GetAttributes(self) + return MODIFIER_ATTRIBUTE_IGNORE_INVULNERABLE +end +function modifier_hell_summon_portal.prototype.OnCreated(self) + if not IsServer() then + return + end + local particleName2 = "particles/units/heroes/heroes_underlord/abbysal_underlord_portal_ambient.vpcf" + self.effectCast = ParticleManager:CreateParticle( + particleName2, + PATTACH_ABSORIGIN_FOLLOW, + self:GetParent() + ) + self:StartIntervalThink(5) +end +function modifier_hell_summon_portal.prototype.OnIntervalThink(self) + if not IsServer() then + return + end + self:GetParent():Destroy() +end +function modifier_hell_summon_portal.prototype.OnDestroy(self) + if not IsServer() then + return + end + if self.effectCast ~= nil then + ParticleManager:DestroyParticle(self.effectCast, true) + end +end +modifier_hell_summon_portal = __TS__Decorate( + modifier_hell_summon_portal, + modifier_hell_summon_portal, + {registerModifier(nil)}, + {kind = "class", name = "modifier_hell_summon_portal"} +) +____exports.modifier_hell_summon_portal = modifier_hell_summon_portal +return ____exports diff --git a/scripts/vscripts/abilities/heroes/sargatanas/ability_hellstep.lua b/scripts/vscripts/abilities/heroes/sargatanas/ability_hellstep.lua new file mode 100644 index 0000000..a0c9e44 --- /dev/null +++ b/scripts/vscripts/abilities/heroes/sargatanas/ability_hellstep.lua @@ -0,0 +1,489 @@ +local ____lualib = require("lualib_bundle") +local __TS__Class = ____lualib.__TS__Class +local __TS__ClassExtends = ____lualib.__TS__ClassExtends +local __TS__Decorate = ____lualib.__TS__Decorate +local ____exports = {} +local ____dota_ts_adapter = require("lib.dota_ts_adapter") +local BaseAbility = ____dota_ts_adapter.BaseAbility +local BaseModifier = ____dota_ts_adapter.BaseModifier +local registerAbility = ____dota_ts_adapter.registerAbility +local registerModifier = ____dota_ts_adapter.registerModifier +local ____heal_tracker = require("utils.heal_tracker") +local HealWithBattlePass = ____heal_tracker.HealWithBattlePass +local ____modifier_overheating = require("abilities.heroes.sargatanas.modifier_overheating") +local modifier_overheating = ____modifier_overheating.modifier_overheating +local function DamageHell(self, unit) + if unit:HasModifier(modifier_overheating.name) then + local modif = unit:FindModifierByName(modifier_overheating.name) + if modif then + return modif:GetStackCount() / 100 + 1 + end + end + return 1 +end +local function ModifierStackInc(self, unit, modifierName, setStack, dur, beginStack, ability) + local modifier = unit:FindModifierByName(modifierName) + if modifier then + modifier:SetStackCount(modifier:GetStackCount() + setStack) + modifier:SetDuration(dur, true) + else + modifier = unit:AddNewModifier(unit, ability, modifierName, {duration = dur}) + if modifier then + modifier:SetStackCount(beginStack) + end + end +end +____exports.ability_hellstep = __TS__Class() +local ability_hellstep = ____exports.ability_hellstep +ability_hellstep.name = "ability_hellstep" +ability_hellstep.____file_path = "scripts/vscripts/abilities/heroes/sargatanas/ability_hellstep.lua" +__TS__ClassExtends(ability_hellstep, BaseAbility) +function ability_hellstep.prototype.GetAOERadius(self) + return self:GetSpecialValueFor("radius") +end +function ability_hellstep.prototype.GetBehavior(self) + if self:GetCaster():HasModifier("modifier_ability_metamorphosis") then + return bit.bor(DOTA_ABILITY_BEHAVIOR_UNIT_TARGET, DOTA_ABILITY_BEHAVIOR_AOE) + else + return bit.bor(DOTA_ABILITY_BEHAVIOR_POINT, DOTA_ABILITY_BEHAVIOR_AOE) + end +end +function ability_hellstep.prototype.OnSpellStart(self) + if not IsServer() then + return + end + local caster = self:GetCaster() + if caster:HasModifier("modifier_ability_metamorphosis") then + caster:AddNewModifier( + caster, + self, + ____exports.modifier_ability_hellstep_aura.name, + {duration = self:GetSpecialValueFor("duration")} + ) + else + local point = self:GetCursorPosition() + local projectileSpeed = self:GetSpecialValueFor("projectile_speed") + local vector = point - caster:GetAbsOrigin() + local projectileDistance = vector:Length2D() + local projectileDirection = vector + projectileDirection.z = 0 + projectileDirection = projectileDirection:Normalized() + GridNav:DestroyTreesAroundPoint( + point, + self:GetSpecialValueFor("radius"), + true + ) + local info = { + Ability = self, + EffectName = "", + vSpawnOrigin = caster:GetAbsOrigin(), + fDistance = projectileDistance, + fStartRadius = 0, + fEndRadius = 0, + Source = caster, + bHasFrontalCone = false, + iUnitTargetTeam = DOTA_UNIT_TARGET_TEAM_ENEMY, + iUnitTargetFlags = DOTA_UNIT_TARGET_FLAG_NONE, + iUnitTargetType = bit.bor(DOTA_UNIT_TARGET_HERO, DOTA_UNIT_TARGET_BASIC), + vVelocity = projectileDirection * projectileSpeed, + bProvidesVision = false, + iVisionRadius = 0, + iVisionTeamNumber = caster:GetTeamNumber() + } + ProjectileManager:CreateLinearProjectile(info) + end +end +function ability_hellstep.prototype.OnProjectileHit(self, target, location) + if target then + return false + end + if not IsServer() then + return false + end + local duration = self:GetSpecialValueFor("duration") + CreateModifierThinker( + self:GetCaster(), + self, + ____exports.modifier_ability_hellstep_thinker.name, + {duration = duration}, + location, + self:GetCaster():GetTeamNumber(), + false + ) + return false +end +ability_hellstep = __TS__Decorate( + ability_hellstep, + ability_hellstep, + {registerAbility(nil)}, + {kind = "class", name = "ability_hellstep"} +) +____exports.ability_hellstep = ability_hellstep +____exports.modifier_ability_hellstep_thinker = __TS__Class() +local modifier_ability_hellstep_thinker = ____exports.modifier_ability_hellstep_thinker +modifier_ability_hellstep_thinker.name = "modifier_ability_hellstep_thinker" +modifier_ability_hellstep_thinker.____file_path = "scripts/vscripts/abilities/heroes/sargatanas/ability_hellstep.lua" +__TS__ClassExtends(modifier_ability_hellstep_thinker, BaseModifier) +function modifier_ability_hellstep_thinker.prototype.____constructor(self, ...) + BaseModifier.prototype.____constructor(self, ...) + self.radius = 0 +end +function modifier_ability_hellstep_thinker.prototype.IsHidden(self) + return true +end +function modifier_ability_hellstep_thinker.prototype.IsPurgable(self) + return false +end +function modifier_ability_hellstep_thinker.prototype.IsPurgeException(self) + return false +end +function modifier_ability_hellstep_thinker.prototype.IsDebuff(self) + return false +end +function modifier_ability_hellstep_thinker.prototype.IsBuff(self) + return true +end +function modifier_ability_hellstep_thinker.prototype.RemoveOnDeath(self) + return true +end +function modifier_ability_hellstep_thinker.prototype.OnCreated(self) + if not IsServer() then + return + end + local ability = self:GetAbility() + if not ability then + return + end + self.radius = ability:GetSpecialValueFor("radius") + EmitSoundOn( + "Hero_DragonKnight.BreathFire", + self:GetParent() + ) + local fx = ParticleManager:CreateParticle( + "particles/units/heroes/hero_dragon_knight/dragon_knight_shard_fireball.vpcf", + PATTACH_CUSTOMORIGIN, + self:GetCaster() + ) + ParticleManager:SetParticleControl( + fx, + 0, + self:GetParent():GetAbsOrigin() + ) + ParticleManager:SetParticleControl( + fx, + 1, + self:GetParent():GetAbsOrigin() + ) + ParticleManager:SetParticleControl( + fx, + 2, + Vector(self.radius, 0, 0) + ) + self:AddParticle( + fx, + false, + false, + 0, + false, + false + ) + Timers:CreateTimer( + ability:GetSpecialValueFor("duration"), + function() + ParticleManager:DestroyParticle(self.fx, true) + return nil + end + ) +end +function modifier_ability_hellstep_thinker.prototype.IsAura(self) + return true +end +function modifier_ability_hellstep_thinker.prototype.GetModifierAura(self) + return ____exports.modifier_ability_hellstep.name +end +function modifier_ability_hellstep_thinker.prototype.GetAuraRadius(self) + return self.radius +end +function modifier_ability_hellstep_thinker.prototype.GetAuraSearchTeam(self) + return DOTA_UNIT_TARGET_TEAM_BOTH +end +function modifier_ability_hellstep_thinker.prototype.GetAuraDuration(self) + return 0.5 +end +function modifier_ability_hellstep_thinker.prototype.GetAuraSearchType(self) + return bit.bor(DOTA_UNIT_TARGET_HERO, DOTA_UNIT_TARGET_BASIC) +end +function modifier_ability_hellstep_thinker.prototype.GetAuraSearchFlags(self) + return DOTA_UNIT_TARGET_FLAG_NONE +end +modifier_ability_hellstep_thinker = __TS__Decorate( + modifier_ability_hellstep_thinker, + modifier_ability_hellstep_thinker, + {registerModifier(nil)}, + {kind = "class", name = "modifier_ability_hellstep_thinker"} +) +____exports.modifier_ability_hellstep_thinker = modifier_ability_hellstep_thinker +____exports.modifier_ability_hellstep_aura = __TS__Class() +local modifier_ability_hellstep_aura = ____exports.modifier_ability_hellstep_aura +modifier_ability_hellstep_aura.name = "modifier_ability_hellstep_aura" +modifier_ability_hellstep_aura.____file_path = "scripts/vscripts/abilities/heroes/sargatanas/ability_hellstep.lua" +__TS__ClassExtends(modifier_ability_hellstep_aura, BaseModifier) +function modifier_ability_hellstep_aura.prototype.____constructor(self, ...) + BaseModifier.prototype.____constructor(self, ...) + self.radius = 0 +end +function modifier_ability_hellstep_aura.prototype.IsHidden(self) + return false +end +function modifier_ability_hellstep_aura.prototype.IsPurgable(self) + return false +end +function modifier_ability_hellstep_aura.prototype.IsDebuff(self) + return false +end +function modifier_ability_hellstep_aura.prototype.IsBuff(self) + return true +end +function modifier_ability_hellstep_aura.prototype.RemoveOnDeath(self) + return true +end +function modifier_ability_hellstep_aura.prototype.OnCreated(self) + if not IsServer() then + return + end + local ability = self:GetAbility() + if not ability then + return + end + self.radius = ability:GetSpecialValueFor("radius") + EmitSoundOn( + "Hero_DragonKnight.BreathFire", + self:GetParent() + ) + self.effectCast = ParticleManager:CreateParticle( + "particles/units/heroes/hero_doom_bringer/doom_scorched_earth.vpcf", + PATTACH_ABSORIGIN_FOLLOW, + self:GetParent() + ) + ParticleManager:SetParticleControl( + self.effectCast, + 1, + Vector(self.radius, 0, 0) + ) +end +function modifier_ability_hellstep_aura.prototype.OnRefresh(self) + self:OnCreated() +end +function modifier_ability_hellstep_aura.prototype.IsAura(self) + return true +end +function modifier_ability_hellstep_aura.prototype.GetModifierAura(self) + return ____exports.modifier_ability_hellstep.name +end +function modifier_ability_hellstep_aura.prototype.GetAuraRadius(self) + return self.radius +end +function modifier_ability_hellstep_aura.prototype.GetAuraDuration(self) + return 0.5 +end +function modifier_ability_hellstep_aura.prototype.GetAuraSearchTeam(self) + return DOTA_UNIT_TARGET_TEAM_BOTH +end +function modifier_ability_hellstep_aura.prototype.GetAuraSearchType(self) + return bit.bor(DOTA_UNIT_TARGET_HERO, DOTA_UNIT_TARGET_BASIC) +end +function modifier_ability_hellstep_aura.prototype.GetAuraSearchFlags(self) + return DOTA_UNIT_TARGET_FLAG_NONE +end +function modifier_ability_hellstep_aura.prototype.OnDestroy(self) + if not IsServer() then + return + end + if self.effectCast ~= nil then + ParticleManager:DestroyParticle(self.effectCast, true) + end + StopSoundOn( + "Hero_DragonKnight.BreathFire", + self:GetParent() + ) +end +modifier_ability_hellstep_aura = __TS__Decorate( + modifier_ability_hellstep_aura, + modifier_ability_hellstep_aura, + {registerModifier(nil)}, + {kind = "class", name = "modifier_ability_hellstep_aura"} +) +____exports.modifier_ability_hellstep_aura = modifier_ability_hellstep_aura +____exports.modifier_ability_hellstep = __TS__Class() +local modifier_ability_hellstep = ____exports.modifier_ability_hellstep +modifier_ability_hellstep.name = "modifier_ability_hellstep" +modifier_ability_hellstep.____file_path = "scripts/vscripts/abilities/heroes/sargatanas/ability_hellstep.lua" +__TS__ClassExtends(modifier_ability_hellstep, BaseModifier) +function modifier_ability_hellstep.prototype.____constructor(self, ...) + BaseModifier.prototype.____constructor(self, ...) + self.bonusDamage = 0 + self.bonusAttackSpeed = 0 + self.minDamage = 0 + self.maxDamage = 0 + self.maxDuration = 0 + self.duration = 0 + self.damageMidPerTick = 0 + self.damagePerTick = 0 + self.stackOverhell = 0 + self.ticks = 1 + self.timeOld = 0 +end +function modifier_ability_hellstep.prototype.IsHidden(self) + return false +end +function modifier_ability_hellstep.prototype.IsPurgable(self) + return false +end +function modifier_ability_hellstep.prototype.IsPurgeException(self) + return false +end +function modifier_ability_hellstep.prototype.IsDebuff(self) + return true +end +function modifier_ability_hellstep.prototype.IsBuff(self) + return false +end +function modifier_ability_hellstep.prototype.RemoveOnDeath(self) + return true +end +function modifier_ability_hellstep.prototype.DeclareFunctions(self) + return {MODIFIER_PROPERTY_ATTACKSPEED_BONUS_CONSTANT, MODIFIER_PROPERTY_PREATTACK_BONUS_DAMAGE} +end +function modifier_ability_hellstep.prototype.GetAttributes(self) + return MODIFIER_ATTRIBUTE_MULTIPLE +end +function modifier_ability_hellstep.prototype.OnCreated(self) + if not IsServer() then + return + end + local ability = self:GetAbility() + if not ability then + return + end + self.bonusDamage = ability:GetSpecialValueFor("bonus_damage") + self.bonusAttackSpeed = ability:GetSpecialValueFor("bonus_attack_speed") + self.minDamage = ability:GetSpecialValueFor("min_damage") + self.maxDamage = ability:GetSpecialValueFor("max_damage") + self.maxDuration = ability:GetSpecialValueFor("max_duration") + self.duration = ability:GetSpecialValueFor("duration") + self.damageMidPerTick = (self.maxDamage - self.minDamage) / (self.duration / 0.4) + self.damagePerTick = 0.05 * math.ceil(100 * self.damageMidPerTick) + self.stackOverhell = ability:GetSpecialValueFor("stack_overhell") + self.ticks = 1 + self.timeOld = GameRules:GetGameTime() + self:StartIntervalThink(0.5) + self.effectCast = ParticleManager:CreateParticle( + "particles/econ/items/wraith_king/wraith_king_ti6_bracer/wraith_king_ti6_ambient_fireball_lava.vpcf", + PATTACH_CUSTOMORIGIN_FOLLOW, + self:GetCaster() + ) + ParticleManager:SetParticleControlEnt( + self.effectCast, + 0, + self:GetParent(), + PATTACH_ABSORIGIN_FOLLOW, + "", + self:GetParent():GetAbsOrigin(), + true + ) + self:AddParticle( + self.effectCast, + false, + false, + -1, + false, + false + ) +end +function modifier_ability_hellstep.prototype.GetModifierAttackSpeedBonus_Constant(self) + local parent = self:GetParent() + local caster = self:GetCaster() + if not parent or not caster then + return 0 + end + if parent:GetTeamNumber() == caster:GetTeamNumber() then + return self.bonusAttackSpeed + end + return 0 +end +function modifier_ability_hellstep.prototype.GetModifierPreAttack_BonusDamage(self) + local parent = self:GetParent() + local caster = self:GetCaster() + if not parent or not caster then + return 0 + end + if parent:GetTeamNumber() == caster:GetTeamNumber() then + return self.bonusDamage + end + return 0 +end +function modifier_ability_hellstep.prototype.OnIntervalThink(self) + if not IsServer() then + return + end + local timeNow = GameRules:GetGameTime() + if math.floor(timeNow - self.timeOld) <= self.maxDuration then + self.ticks = self.ticks + 1 + end + local damage = self.damagePerTick * self.ticks + local ability = self:GetAbility() + local caster = self:GetCaster() + if not ability or not caster then + return + end + if self:GetParent():GetTeamNumber() ~= caster:GetTeamNumber() then + ApplyDamage({ + victim = self:GetParent(), + attacker = caster, + damage = damage * DamageHell( + nil, + self:GetParent() + ), + damage_type = ability:GetAbilityDamageType(), + ability = ability + }) + if self:GetParent():GetTeamNumber() ~= caster:GetTeamNumber() then + ModifierStackInc( + nil, + self:GetParent(), + modifier_overheating.name, + self.stackOverhell, + 8, + self.stackOverhell, + ability + ) + end + EmitSoundOn("Hero_Viper.NetherToxin.Damage", caster) + else + HealWithBattlePass( + nil, + caster, + damage * DamageHell( + nil, + self:GetParent() + ), + ability, + caster + ) + SendOverheadEventMessage( + nil, + OVERHEAD_ALERT_HEAL, + caster, + damage, + caster:GetPlayerOwner() + ) + end +end +modifier_ability_hellstep = __TS__Decorate( + modifier_ability_hellstep, + modifier_ability_hellstep, + {registerModifier(nil)}, + {kind = "class", name = "modifier_ability_hellstep"} +) +____exports.modifier_ability_hellstep = modifier_ability_hellstep +return ____exports diff --git a/scripts/vscripts/abilities/heroes/sargatanas/ability_metamorphosis.lua b/scripts/vscripts/abilities/heroes/sargatanas/ability_metamorphosis.lua new file mode 100644 index 0000000..b557cc4 --- /dev/null +++ b/scripts/vscripts/abilities/heroes/sargatanas/ability_metamorphosis.lua @@ -0,0 +1,260 @@ +local ____lualib = require("lualib_bundle") +local __TS__Class = ____lualib.__TS__Class +local __TS__ClassExtends = ____lualib.__TS__ClassExtends +local __TS__Decorate = ____lualib.__TS__Decorate +local ____exports = {} +local ____dota_ts_adapter = require("lib.dota_ts_adapter") +local BaseAbility = ____dota_ts_adapter.BaseAbility +local BaseModifier = ____dota_ts_adapter.BaseModifier +local registerAbility = ____dota_ts_adapter.registerAbility +local registerModifier = ____dota_ts_adapter.registerModifier +local ____incoming_damage_reduction_combine = require("utils.incoming_damage_reduction_combine") +local removeIncomingDamageReductionSource = ____incoming_damage_reduction_combine.removeIncomingDamageReductionSource +local setIncomingDamageReductionSource = ____incoming_damage_reduction_combine.setIncomingDamageReductionSource +local METAMORPHOSIS_INCOMING_SOURCE = "modifier_ability_metamorphosis" +____exports.ability_metamorphosis = __TS__Class() +local ability_metamorphosis = ____exports.ability_metamorphosis +ability_metamorphosis.name = "ability_metamorphosis" +ability_metamorphosis.____file_path = "scripts/vscripts/abilities/heroes/sargatanas/ability_metamorphosis.lua" +__TS__ClassExtends(ability_metamorphosis, BaseAbility) +function ability_metamorphosis.prototype.Precache(self, context) + PrecacheResource("model", "models/items/terrorblade/endless_purgatory_demon/endless_purgatory_demon.vmdl", context) + PrecacheResource("particle", "particles/units/heroes/hero_terrorblade/terrorblade_metamorphosis.vpcf", context) + PrecacheResource("soundfile", "soundevents/game_sounds_heroes/game_sounds_terrorblade.vsndevts", context) +end +function ability_metamorphosis.prototype.OnSpellStart(self) + if not IsServer() then + return + end + local caster = self:GetCaster() + local duration = self:GetSpecialValueFor("duration") + caster:AddNewModifier(caster, self, ____exports.modifier_ability_metamorphosis.name, {duration = duration}) + caster:AddNewModifier(caster, self, ____exports.modifier_ability_metamorphosis_aura.name, {duration = duration}) +end +ability_metamorphosis = __TS__Decorate( + ability_metamorphosis, + ability_metamorphosis, + {registerAbility(nil)}, + {kind = "class", name = "ability_metamorphosis"} +) +____exports.ability_metamorphosis = ability_metamorphosis +____exports.modifier_ability_metamorphosis = __TS__Class() +local modifier_ability_metamorphosis = ____exports.modifier_ability_metamorphosis +modifier_ability_metamorphosis.name = "modifier_ability_metamorphosis" +modifier_ability_metamorphosis.____file_path = "scripts/vscripts/abilities/heroes/sargatanas/ability_metamorphosis.lua" +__TS__ClassExtends(modifier_ability_metamorphosis, BaseModifier) +function modifier_ability_metamorphosis.prototype.____constructor(self, ...) + BaseModifier.prototype.____constructor(self, ...) + self.bonusResistance = 0 + self.maxResistance = 0 + self.resistance = 0 +end +function modifier_ability_metamorphosis.prototype.IsHidden(self) + return false +end +function modifier_ability_metamorphosis.prototype.IsDebuff(self) + return false +end +function modifier_ability_metamorphosis.prototype.IsStunDebuff(self) + return false +end +function modifier_ability_metamorphosis.prototype.IsPurgable(self) + return false +end +function modifier_ability_metamorphosis.prototype.OnCreated(self) + if not IsServer() then + return + end + local ability = self:GetAbility() + if not ability then + return + end + local parent = self:GetParent() + if not parent:IsRealHero() then + return + end + local hero = parent + self.bonusResistance = ability:GetSpecialValueFor("bonus_resistance") * (hero:GetStrength() / 0.2) + self.maxResistance = ability:GetSpecialValueFor("max_resistance") + self.resistance = math.min(self.bonusResistance, self.maxResistance) + setIncomingDamageReductionSource( + nil, + parent, + METAMORPHOSIS_INCOMING_SOURCE, + function() return math.max(0, self.resistance) end + ) + EmitSoundOn("Hero_Terrorblade.Metamorphosis", parent) +end +function modifier_ability_metamorphosis.prototype.OnRefresh(self) + self:OnCreated() +end +function modifier_ability_metamorphosis.prototype.OnDestroy(self) + if not IsServer() then + return + end + removeIncomingDamageReductionSource( + nil, + self:GetParent(), + METAMORPHOSIS_INCOMING_SOURCE + ) +end +function modifier_ability_metamorphosis.prototype.DeclareFunctions(self) + return {MODIFIER_PROPERTY_MODEL_CHANGE, MODIFIER_PROPERTY_MODEL_SCALE, MODIFIER_PROPERTY_TRANSLATE_ATTACK_SOUND} +end +function modifier_ability_metamorphosis.prototype.GetModifierModelScale(self) + return 10 +end +function modifier_ability_metamorphosis.prototype.GetModifierModelChange(self) + return "models/items/terrorblade/endless_purgatory_demon/endless_purgatory_demon.vmdl" +end +function modifier_ability_metamorphosis.prototype.GetAttackSound(self) + return "Hero_Terrorblade_Morphed.Attack" +end +function modifier_ability_metamorphosis.prototype.GetEffectName(self) + return "particles/units/heroes/hero_terrorblade/terrorblade_metamorphosis.vpcf" +end +function modifier_ability_metamorphosis.prototype.GetEffectAttachType(self) + return PATTACH_ABSORIGIN_FOLLOW +end +modifier_ability_metamorphosis = __TS__Decorate( + modifier_ability_metamorphosis, + modifier_ability_metamorphosis, + {registerModifier(nil)}, + {kind = "class", name = "modifier_ability_metamorphosis"} +) +____exports.modifier_ability_metamorphosis = modifier_ability_metamorphosis +____exports.modifier_ability_metamorphosis_aura = __TS__Class() +local modifier_ability_metamorphosis_aura = ____exports.modifier_ability_metamorphosis_aura +modifier_ability_metamorphosis_aura.name = "modifier_ability_metamorphosis_aura" +modifier_ability_metamorphosis_aura.____file_path = "scripts/vscripts/abilities/heroes/sargatanas/ability_metamorphosis.lua" +__TS__ClassExtends(modifier_ability_metamorphosis_aura, BaseModifier) +function modifier_ability_metamorphosis_aura.prototype.____constructor(self, ...) + BaseModifier.prototype.____constructor(self, ...) + self.radius = 0 +end +function modifier_ability_metamorphosis_aura.prototype.IsHidden(self) + return false +end +function modifier_ability_metamorphosis_aura.prototype.IsPurgable(self) + return false +end +function modifier_ability_metamorphosis_aura.prototype.IsDebuff(self) + return false +end +function modifier_ability_metamorphosis_aura.prototype.IsBuff(self) + return true +end +function modifier_ability_metamorphosis_aura.prototype.RemoveOnDeath(self) + return true +end +function modifier_ability_metamorphosis_aura.prototype.OnCreated(self) + if not IsServer() then + return + end + local ability = self:GetAbility() + if not ability then + return + end + self.radius = ability:GetSpecialValueFor("radius") +end +function modifier_ability_metamorphosis_aura.prototype.OnRefresh(self) + self:OnCreated() +end +function modifier_ability_metamorphosis_aura.prototype.IsAura(self) + return true +end +function modifier_ability_metamorphosis_aura.prototype.GetModifierAura(self) + return ____exports.modifier_ability_metamorphosis_burn.name +end +function modifier_ability_metamorphosis_aura.prototype.GetAuraRadius(self) + return self.radius +end +function modifier_ability_metamorphosis_aura.prototype.GetAuraDuration(self) + return 1 +end +function modifier_ability_metamorphosis_aura.prototype.GetAuraSearchTeam(self) + return DOTA_UNIT_TARGET_TEAM_FRIENDLY +end +function modifier_ability_metamorphosis_aura.prototype.GetAuraSearchType(self) + return DOTA_UNIT_TARGET_BASIC +end +function modifier_ability_metamorphosis_aura.prototype.GetAuraSearchFlags(self) + return DOTA_UNIT_TARGET_FLAG_NONE +end +function modifier_ability_metamorphosis_aura.prototype.GetAuraEntityReject(self, hEntity) + if not IsServer() then + return false + end + local parent = self:GetParent() + if not parent:IsRealHero() or not hEntity:IsRealHero() then + return false + end + local parentHero = parent + local entityHero = hEntity + if entityHero:GetPlayerOwnerID() ~= parentHero:GetPlayerOwnerID() then + return true + end + return false +end +modifier_ability_metamorphosis_aura = __TS__Decorate( + modifier_ability_metamorphosis_aura, + modifier_ability_metamorphosis_aura, + {registerModifier(nil)}, + {kind = "class", name = "modifier_ability_metamorphosis_aura"} +) +____exports.modifier_ability_metamorphosis_aura = modifier_ability_metamorphosis_aura +____exports.modifier_ability_metamorphosis_burn = __TS__Class() +local modifier_ability_metamorphosis_burn = ____exports.modifier_ability_metamorphosis_burn +modifier_ability_metamorphosis_burn.name = "modifier_ability_metamorphosis_burn" +modifier_ability_metamorphosis_burn.____file_path = "scripts/vscripts/abilities/heroes/sargatanas/ability_metamorphosis.lua" +__TS__ClassExtends(modifier_ability_metamorphosis_burn, BaseModifier) +function modifier_ability_metamorphosis_burn.prototype.IsHidden(self) + return false +end +function modifier_ability_metamorphosis_burn.prototype.IsPurgable(self) + return false +end +function modifier_ability_metamorphosis_burn.prototype.IsDebuff(self) + return false +end +function modifier_ability_metamorphosis_burn.prototype.IsBuff(self) + return true +end +function modifier_ability_metamorphosis_burn.prototype.RemoveOnDeath(self) + return true +end +function modifier_ability_metamorphosis_burn.prototype.DeclareFunctions(self) + return {MODIFIER_PROPERTY_ATTACKSPEED_BONUS_CONSTANT, MODIFIER_PROPERTY_PHYSICAL_ARMOR_BONUS} +end +function modifier_ability_metamorphosis_burn.prototype.GetEffectName(self) + return "particles/econ/courier/courier_golden_doomling/courier_golden_doomling_ambient.vpcf" +end +function modifier_ability_metamorphosis_burn.prototype.GetEffectAttachType(self) + return PATTACH_ABSORIGIN_FOLLOW +end +function modifier_ability_metamorphosis_burn.prototype.GetModifierAttackSpeedBonus_Constant(self) + local caster = self:GetCaster() + local ability = self:GetAbility() + if not caster or not caster:IsRealHero() or not ability then + return 0 + end + local hero = caster + return hero:GetAgility() * ability:GetSpecialValueFor("attack_speed_ag") +end +function modifier_ability_metamorphosis_burn.prototype.GetModifierPhysicalArmorBonus(self) + local caster = self:GetCaster() + local ability = self:GetAbility() + if not caster or not caster:IsRealHero() or not ability then + return 0 + end + local hero = caster + return hero:GetAgility() * ability:GetSpecialValueFor("armor_ag") +end +modifier_ability_metamorphosis_burn = __TS__Decorate( + modifier_ability_metamorphosis_burn, + modifier_ability_metamorphosis_burn, + {registerModifier(nil)}, + {kind = "class", name = "modifier_ability_metamorphosis_burn"} +) +____exports.modifier_ability_metamorphosis_burn = modifier_ability_metamorphosis_burn +return ____exports diff --git a/scripts/vscripts/abilities/heroes/sargatanas/ability_star_devour.lua b/scripts/vscripts/abilities/heroes/sargatanas/ability_star_devour.lua new file mode 100644 index 0000000..d36489a --- /dev/null +++ b/scripts/vscripts/abilities/heroes/sargatanas/ability_star_devour.lua @@ -0,0 +1,234 @@ +local ____lualib = require("lualib_bundle") +local __TS__Class = ____lualib.__TS__Class +local __TS__ClassExtends = ____lualib.__TS__ClassExtends +local __TS__Decorate = ____lualib.__TS__Decorate +local ____exports = {} +local ____dota_ts_adapter = require("lib.dota_ts_adapter") +local BaseAbility = ____dota_ts_adapter.BaseAbility +local BaseModifier = ____dota_ts_adapter.BaseModifier +local registerAbility = ____dota_ts_adapter.registerAbility +local registerModifier = ____dota_ts_adapter.registerModifier +____exports.ability_star_devour = __TS__Class() +local ability_star_devour = ____exports.ability_star_devour +ability_star_devour.name = "ability_star_devour" +ability_star_devour.____file_path = "scripts/vscripts/abilities/heroes/sargatanas/ability_star_devour.lua" +__TS__ClassExtends(ability_star_devour, BaseAbility) +function ability_star_devour.prototype.CastFilterResultTarget(self, target) + local nResult = UnitFilter( + target, + DOTA_UNIT_TARGET_TEAM_ENEMY, + DOTA_UNIT_TARGET_CREEP, + bit.bor( + bit.bor(DOTA_UNIT_TARGET_FLAG_MAGIC_IMMUNE_ENEMIES, DOTA_UNIT_TARGET_FLAG_NOT_ANCIENTS), + DOTA_UNIT_TARGET_FLAG_NOT_CREEP_HERO + ), + self:GetCaster():GetTeamNumber() + ) + if nResult ~= UF_SUCCESS then + return nResult + end + return UF_SUCCESS +end +function ability_star_devour.prototype.OnSpellStart(self) + if not IsServer() then + return + end + local caster = self:GetCaster() + local target = self:GetCursorTarget() + if not target then + return + end + local duration = self:GetSpecialValueFor("devour_time") + caster:AddNewModifier(caster, self, ____exports.modifier_star_devour.name, {duration = duration}) + if target:IsNeutralUnitType() and self:GetAutoCastState() and not self:IsStolen() then + local targetAbilities = {} + do + local i = 0 + while i <= 5 do + local ability = target:GetAbilityByIndex(i) + if ability then + targetAbilities[#targetAbilities + 1] = ability:GetAbilityName() + if #targetAbilities > 1 then + break + end + end + i = i + 1 + end + end + if #targetAbilities > 0 then + do + local i = 0 + while i < 2 do + local empty = "doom_bringer_empty" .. tostring(i + 1) + local newAbilityName = targetAbilities[i + 1] or empty + local oldAbility = caster:GetAbilityByIndex(i + (self.addIndex or 0)) + local oldAbilityName = oldAbility and oldAbility:GetAbilityName() or "" + if newAbilityName ~= oldAbilityName then + local ability = caster:AddAbility(newAbilityName) + caster:SwapAbilities(oldAbilityName, newAbilityName, false, true) + caster:RemoveAbility(oldAbilityName) + if newAbilityName ~= empty then + ability:SetLevel(1) + end + end + i = i + 1 + end + end + end + end + target:AddNoDraw() + target:Kill(self, caster) + local effectCast = ParticleManager:CreateParticle("particles/units/heroes/hero_doom_bringer/doom_bringer_devour.vpcf", PATTACH_ABSORIGIN_FOLLOW, target) + ParticleManager:SetParticleControlEnt( + effectCast, + 1, + caster, + PATTACH_POINT_FOLLOW, + "attach_hitloc", + Vector(0, 0, 0), + true + ) + ParticleManager:ReleaseParticleIndex(effectCast) + EmitSoundOn("Hero_DoomBringer.Devour", caster) + EmitSoundOn("Hero_DoomBringer.DevourCast", target) +end +function ability_star_devour.prototype.OnUpgrade(self) + if self:GetLevel() == 1 then + if not self:GetAutoCastState() then + self:ToggleAutoCast() + end + end + if self.addIndex == nil then + do + local i = 0 + while i <= 10 do + local ability = self:GetCaster():GetAbilityByIndex(i) + if ability and ability:GetAbilityName() == "doom_bringer_empty1" then + self.addIndex = i - 1 + break + end + i = i + 1 + end + end + end +end +ability_star_devour = __TS__Decorate( + ability_star_devour, + ability_star_devour, + {registerAbility(nil)}, + {kind = "class", name = "ability_star_devour"} +) +____exports.ability_star_devour = ability_star_devour +____exports.modifier_star_devour = __TS__Class() +local modifier_star_devour = ____exports.modifier_star_devour +modifier_star_devour.name = "modifier_star_devour" +modifier_star_devour.____file_path = "scripts/vscripts/abilities/heroes/sargatanas/ability_star_devour.lua" +__TS__ClassExtends(modifier_star_devour, BaseModifier) +function modifier_star_devour.prototype.____constructor(self, ...) + BaseModifier.prototype.____constructor(self, ...) + self.bonusGold = 0 + self.bonusRegen = 0 + self.maxStack = 0 +end +function modifier_star_devour.prototype.CheckState(self) + return {[MODIFIER_STATE_DISARMED] = true} +end +function modifier_star_devour.prototype.IsHidden(self) + return false +end +function modifier_star_devour.prototype.IsDebuff(self) + return false +end +function modifier_star_devour.prototype.IsPurgable(self) + return false +end +function modifier_star_devour.prototype.GetAttributes(self) + return MODIFIER_ATTRIBUTE_MULTIPLE +end +function modifier_star_devour.prototype.RemoveOnDeath(self) + return false +end +function modifier_star_devour.prototype.OnCreated(self) + if not IsServer() then + return + end + local ability = self:GetAbility() + if not ability then + return + end + self.bonusGold = ability:GetSpecialValueFor("bonus_gold") + self.bonusRegen = ability:GetSpecialValueFor("regen") + self.maxStack = ability:GetSpecialValueFor("max_stack") +end +function modifier_star_devour.prototype.OnDestroy(self) + if not IsServer() then + return + end + local parent = self:GetParent() + if not parent:IsAlive() then + return + end + local playerId = parent:GetPlayerOwnerID() + if playerId >= 0 then + PlayerResource:ModifyGold(playerId, self.bonusGold, false, DOTA_ModifyGold_Unspecified) + end + local modifier = parent:FindModifierByName(____exports.modifier_star_devour_stack.name) + if modifier then + if modifier:GetStackCount() == self.maxStack then + modifier:SetStackCount(self.maxStack) + else + modifier:IncrementStackCount() + end + else + modifier = parent:AddNewModifier( + parent, + self:GetAbility(), + ____exports.modifier_star_devour_stack.name, + {} + ) + if modifier then + modifier:SetStackCount(1) + end + end +end +function modifier_star_devour.prototype.DeclareFunctions(self) + return {MODIFIER_PROPERTY_HEALTH_REGEN_CONSTANT} +end +function modifier_star_devour.prototype.GetModifierConstantHealthRegen(self) + return self.bonusRegen +end +modifier_star_devour = __TS__Decorate( + modifier_star_devour, + modifier_star_devour, + {registerModifier(nil)}, + {kind = "class", name = "modifier_star_devour"} +) +____exports.modifier_star_devour = modifier_star_devour +____exports.modifier_star_devour_stack = __TS__Class() +local modifier_star_devour_stack = ____exports.modifier_star_devour_stack +modifier_star_devour_stack.name = "modifier_star_devour_stack" +modifier_star_devour_stack.____file_path = "scripts/vscripts/abilities/heroes/sargatanas/ability_star_devour.lua" +__TS__ClassExtends(modifier_star_devour_stack, BaseModifier) +function modifier_star_devour_stack.prototype.IsHidden(self) + return false +end +function modifier_star_devour_stack.prototype.IsPurgable(self) + return false +end +function modifier_star_devour_stack.prototype.IsDebuff(self) + return false +end +function modifier_star_devour_stack.prototype.IsBuff(self) + return true +end +function modifier_star_devour_stack.prototype.RemoveOnDeath(self) + return false +end +modifier_star_devour_stack = __TS__Decorate( + modifier_star_devour_stack, + modifier_star_devour_stack, + {registerModifier(nil)}, + {kind = "class", name = "modifier_star_devour_stack"} +) +____exports.modifier_star_devour_stack = modifier_star_devour_stack +return ____exports diff --git a/scripts/vscripts/abilities/heroes/sargatanas/ability_sunflame.lua b/scripts/vscripts/abilities/heroes/sargatanas/ability_sunflame.lua new file mode 100644 index 0000000..9b11180 --- /dev/null +++ b/scripts/vscripts/abilities/heroes/sargatanas/ability_sunflame.lua @@ -0,0 +1,119 @@ +local ____lualib = require("lualib_bundle") +local __TS__Class = ____lualib.__TS__Class +local __TS__ClassExtends = ____lualib.__TS__ClassExtends +local __TS__Decorate = ____lualib.__TS__Decorate +local ____exports = {} +local ____dota_ts_adapter = require("lib.dota_ts_adapter") +local BaseAbility = ____dota_ts_adapter.BaseAbility +local registerAbility = ____dota_ts_adapter.registerAbility +local ____modifier_overheating = require("abilities.heroes.sargatanas.modifier_overheating") +local modifier_overheating = ____modifier_overheating.modifier_overheating +local function DamageHell(self, unit) + if unit:HasModifier(modifier_overheating.name) then + local modif = unit:FindModifierByName(modifier_overheating.name) + if modif then + return modif:GetStackCount() / 100 + 1 + end + end + return 1 +end +____exports.ability_sunflame = __TS__Class() +local ability_sunflame = ____exports.ability_sunflame +ability_sunflame.name = "ability_sunflame" +ability_sunflame.____file_path = "scripts/vscripts/abilities/heroes/sargatanas/ability_sunflame.lua" +__TS__ClassExtends(ability_sunflame, BaseAbility) +function ability_sunflame.prototype.____constructor(self, ...) + BaseAbility.prototype.____constructor(self, ...) + self.projectiles = {} +end +function ability_sunflame.prototype.OnSpellStart(self) + if not IsServer() then + return + end + local caster = self:GetCaster() + local target = self:GetCursorTarget() + local point = self:GetCursorPosition() + if target then + point = target:GetAbsOrigin() + end + local damage + local unduced + local projectileName + if caster:HasModifier("modifier_ability_metamorphosis") then + projectileName = "particles/units/heroes/hero_dragon_knight/dragon_knight_breathe_fire.vpcf" + damage = self:GetSpecialValueFor("damage_meta") + unduced = self:GetSpecialValueFor("unduced_meta") / 100 + 1 + else + projectileName = "particles/units/heroes/hero_dragon_knight/dragon_knight_breathe_fire.vpcf" + damage = self:GetSpecialValueFor("damage") + unduced = self:GetSpecialValueFor("unduced") / 100 + 1 + end + local projectileDistance = self:GetSpecialValueFor("range") + local projectileStartRadius = self:GetSpecialValueFor("start_radius") + local projectileEndRadius = self:GetSpecialValueFor("end_radius") + local projectileSpeed = self:GetSpecialValueFor("speed") + local projectileDirection = point - caster:GetAbsOrigin() + projectileDirection.z = 0 + projectileDirection = projectileDirection:Normalized() + local info = { + Source = caster, + Ability = self, + vSpawnOrigin = caster:GetAbsOrigin(), + iUnitTargetTeam = DOTA_UNIT_TARGET_TEAM_ENEMY, + iUnitTargetType = bit.bor(DOTA_UNIT_TARGET_HERO, DOTA_UNIT_TARGET_BASIC), + EffectName = projectileName, + fDistance = projectileDistance, + fStartRadius = projectileStartRadius, + fEndRadius = projectileEndRadius, + vVelocity = projectileDirection * projectileSpeed + } + local projectile = ProjectileManager:CreateLinearProjectile(info) + self.projectiles[projectile] = {damage = damage, unduced = unduced} + EmitSoundOn("Hero_DragonKnight.BreathFire", caster) +end +function ability_sunflame.prototype.OnProjectileHitHandle(self, target, location, handle) + if not target then + return false + end + if not IsServer() then + return false + end + local stackOverhell = self:GetSpecialValueFor("stack_overhell") + local data = self.projectiles[handle] + if not data then + return false + end + local damage = data.damage + ApplyDamage({ + victim = target, + attacker = self:GetCaster(), + damage = damage * DamageHell(nil, target), + damage_type = self:GetAbilityDamageType(), + ability = self + }) + data.damage = damage * data.unduced + local modifier = target:FindModifierByName(modifier_overheating.name) + if modifier then + modifier:SetStackCount(modifier:GetStackCount() + stackOverhell) + modifier:SetDuration(8, true) + else + modifier = target:AddNewModifier( + self:GetCaster(), + self, + modifier_overheating.name, + {duration = 8} + ) + if modifier then + modifier:SetStackCount(stackOverhell) + end + end + return false +end +ability_sunflame = __TS__Decorate( + ability_sunflame, + ability_sunflame, + {registerAbility(nil)}, + {kind = "class", name = "ability_sunflame"} +) +____exports.ability_sunflame = ability_sunflame +return ____exports diff --git a/scripts/vscripts/abilities/heroes/sargatanas/modifier_overheating.lua b/scripts/vscripts/abilities/heroes/sargatanas/modifier_overheating.lua new file mode 100644 index 0000000..dc2ca83 --- /dev/null +++ b/scripts/vscripts/abilities/heroes/sargatanas/modifier_overheating.lua @@ -0,0 +1,64 @@ +local ____lualib = require("lualib_bundle") +local __TS__Class = ____lualib.__TS__Class +local __TS__ClassExtends = ____lualib.__TS__ClassExtends +local __TS__Decorate = ____lualib.__TS__Decorate +local ____exports = {} +local ____dota_ts_adapter = require("lib.dota_ts_adapter") +local BaseModifier = ____dota_ts_adapter.BaseModifier +local registerModifier = ____dota_ts_adapter.registerModifier +____exports.modifier_overheating = __TS__Class() +local modifier_overheating = ____exports.modifier_overheating +modifier_overheating.name = "modifier_overheating" +modifier_overheating.____file_path = "scripts/vscripts/abilities/heroes/sargatanas/modifier_overheating.lua" +__TS__ClassExtends(modifier_overheating, BaseModifier) +function modifier_overheating.prototype.IsHidden(self) + return false +end +function modifier_overheating.prototype.IsPurgable(self) + return false +end +function modifier_overheating.prototype.IsPurgeException(self) + return false +end +function modifier_overheating.prototype.IsDebuff(self) + return true +end +function modifier_overheating.prototype.IsBuff(self) + return false +end +function modifier_overheating.prototype.RemoveOnDeath(self) + return true +end +function modifier_overheating.prototype.OnCreated(self) + if not IsServer() then + return + end + self:StartIntervalThink(1) +end +function modifier_overheating.prototype.OnIntervalThink(self) + if not IsServer() then + return + end + local stackCount = self:GetStackCount() + local parent = self:GetParent() + if stackCount >= 100 then + if IsServer() then + parent:SetRenderColor(72, 6, 7) + end + elseif stackCount >= 25 then + if IsServer() then + parent:SetRenderColor(255, 36, 0) + end + end +end +function modifier_overheating.prototype.GetTexture(self) + return "overhell" +end +modifier_overheating = __TS__Decorate( + modifier_overheating, + modifier_overheating, + {registerModifier(nil)}, + {kind = "class", name = "modifier_overheating"} +) +____exports.modifier_overheating = modifier_overheating +return ____exports diff --git a/scripts/vscripts/abilities/heroes/silencer/ability_global_silence.lua b/scripts/vscripts/abilities/heroes/silencer/ability_global_silence.lua new file mode 100644 index 0000000..1eb280c --- /dev/null +++ b/scripts/vscripts/abilities/heroes/silencer/ability_global_silence.lua @@ -0,0 +1,121 @@ +local ____lualib = require("lualib_bundle") +local __TS__Class = ____lualib.__TS__Class +local __TS__ClassExtends = ____lualib.__TS__ClassExtends +local __TS__Decorate = ____lualib.__TS__Decorate +local ____exports = {} +local modifier_ability_global_silence +local ____dota_ts_adapter = require("lib.dota_ts_adapter") +local BaseAbility = ____dota_ts_adapter.BaseAbility +local BaseModifier = ____dota_ts_adapter.BaseModifier +local registerAbility = ____dota_ts_adapter.registerAbility +local registerModifier = ____dota_ts_adapter.registerModifier +____exports.ability_global_silence = __TS__Class() +local ability_global_silence = ____exports.ability_global_silence +ability_global_silence.name = "ability_global_silence" +ability_global_silence.____file_path = "scripts/vscripts/abilities/heroes/silencer/ability_global_silence.lua" +__TS__ClassExtends(ability_global_silence, BaseAbility) +function ability_global_silence.prototype.OnSpellStart(self) + if not IsServer() then + return + end + local caster = self:GetCaster() + local duration = self:GetSpecialValueFor("tooltip_duration") + local enemies = FindUnitsInRadius( + caster:GetTeamNumber(), + caster:GetOrigin(), + nil, + FIND_UNITS_EVERYWHERE, + DOTA_UNIT_TARGET_TEAM_ENEMY, + DOTA_UNIT_TARGET_ALL, + bit.bor(DOTA_UNIT_TARGET_FLAG_MAGIC_IMMUNE_ENEMIES, DOTA_UNIT_TARGET_FLAG_MANA_ONLY), + FIND_ANY_ORDER, + false + ) + for ____, enemy in ipairs(enemies) do + enemy:AddNewModifier(caster, self, modifier_ability_global_silence.name, {duration = duration}) + if enemy:IsHero() then + local heroFx = ParticleManager:CreateParticle("particles/units/heroes/hero_silencer/silencer_global_silence_hero.vpcf", PATTACH_ABSORIGIN_FOLLOW, enemy) + ParticleManager:SetParticleControlEnt( + heroFx, + 1, + enemy, + PATTACH_ABSORIGIN_FOLLOW, + "attach_attack1", + Vector(0, 0, 0), + true + ) + ParticleManager:ReleaseParticleIndex(heroFx) + local playerOwner = enemy:GetPlayerOwner() + if playerOwner ~= nil and playerOwner ~= nil then + EmitSoundOnClient("Hero_Silencer.GlobalSilence.Effect", playerOwner) + end + end + end + local castFx = ParticleManager:CreateParticle("particles/units/heroes/hero_silencer/silencer_global_silence.vpcf", PATTACH_ABSORIGIN_FOLLOW, caster) + ParticleManager:SetParticleControlForward( + castFx, + 0, + caster:GetForwardVector() + ) + ParticleManager:SetParticleControlEnt( + castFx, + 1, + caster, + PATTACH_POINT_FOLLOW, + "attach_attack1", + Vector(0, 0, 0), + true + ) + ParticleManager:ReleaseParticleIndex(castFx) + EmitGlobalSound("Hero_Silencer.GlobalSilence.Cast") +end +ability_global_silence = __TS__Decorate( + ability_global_silence, + ability_global_silence, + {registerAbility(nil)}, + {kind = "class", name = "ability_global_silence"} +) +____exports.ability_global_silence = ability_global_silence +modifier_ability_global_silence = __TS__Class() +modifier_ability_global_silence.name = "modifier_ability_global_silence" +modifier_ability_global_silence.____file_path = "scripts/vscripts/abilities/heroes/silencer/ability_global_silence.lua" +__TS__ClassExtends(modifier_ability_global_silence, BaseModifier) +function modifier_ability_global_silence.prototype.____constructor(self, ...) + BaseModifier.prototype.____constructor(self, ...) + self.incomingPct = 0 +end +function modifier_ability_global_silence.prototype.IsDebuff(self) + return true +end +function modifier_ability_global_silence.prototype.IsPurgable(self) + return true +end +function modifier_ability_global_silence.prototype.OnCreated(self) + local ____opt_0 = self:GetAbility() + self.incomingPct = ____opt_0 and ____opt_0:GetSpecialValueFor("icnoming_enemy") or 0 +end +function modifier_ability_global_silence.prototype.GetPriority(self) + return MODIFIER_PRIORITY_SUPER_ULTRA +end +function modifier_ability_global_silence.prototype.CheckState(self) + return {[MODIFIER_STATE_SILENCED] = true} +end +function modifier_ability_global_silence.prototype.DeclareFunctions(self) + return {MODIFIER_PROPERTY_INCOMING_DAMAGE_PERCENTAGE} +end +function modifier_ability_global_silence.prototype.GetModifierIncomingDamage_Percentage(self) + return self.incomingPct +end +function modifier_ability_global_silence.prototype.GetEffectName(self) + return "particles/generic_gameplay/generic_silenced.vpcf" +end +function modifier_ability_global_silence.prototype.GetEffectAttachType(self) + return PATTACH_OVERHEAD_FOLLOW +end +modifier_ability_global_silence = __TS__Decorate( + modifier_ability_global_silence, + modifier_ability_global_silence, + {registerModifier(nil)}, + {kind = "class", name = "modifier_ability_global_silence"} +) +return ____exports diff --git a/scripts/vscripts/abilities/heroes/silencer/ability_last_word.lua b/scripts/vscripts/abilities/heroes/silencer/ability_last_word.lua new file mode 100644 index 0000000..09bbae1 --- /dev/null +++ b/scripts/vscripts/abilities/heroes/silencer/ability_last_word.lua @@ -0,0 +1,167 @@ +local ____lualib = require("lualib_bundle") +local __TS__Class = ____lualib.__TS__Class +local __TS__ClassExtends = ____lualib.__TS__ClassExtends +local __TS__Decorate = ____lualib.__TS__Decorate +local ____exports = {} +local modifier_ability_last_word, modifier_ability_last_word_silence +local ____dota_ts_adapter = require("lib.dota_ts_adapter") +local BaseAbility = ____dota_ts_adapter.BaseAbility +local BaseModifier = ____dota_ts_adapter.BaseModifier +local registerAbility = ____dota_ts_adapter.registerAbility +local registerModifier = ____dota_ts_adapter.registerModifier +____exports.ability_last_word = __TS__Class() +local ability_last_word = ____exports.ability_last_word +ability_last_word.name = "ability_last_word" +ability_last_word.____file_path = "scripts/vscripts/abilities/heroes/silencer/ability_last_word.lua" +__TS__ClassExtends(ability_last_word, BaseAbility) +function ability_last_word.prototype.OnSpellStart(self) + if not IsServer() then + return + end + local caster = self:GetCaster() + local target = self:GetCursorTarget() + if not target then + return + end + local debuffDuration = self:GetSpecialValueFor("debuff_duration") + target:AddNewModifier(caster, self, modifier_ability_last_word.name, {duration = debuffDuration}) + local direction = target:GetOrigin() - caster:GetOrigin() + direction.z = 0 + local normalized = direction:Normalized() + local castFx = ParticleManager:CreateParticle("particles/units/heroes/hero_silencer/silencer_last_word_status_cast.vpcf", PATTACH_ABSORIGIN_FOLLOW, caster) + ParticleManager:SetParticleControlEnt( + castFx, + 0, + caster, + PATTACH_POINT_FOLLOW, + "attach_attack1", + Vector(0, 0, 0), + true + ) + ParticleManager:SetParticleControlForward(castFx, 1, normalized) + ParticleManager:ReleaseParticleIndex(castFx) + EmitSoundOn("Hero_Silencer.LastWord.Cast", caster) +end +ability_last_word = __TS__Decorate( + ability_last_word, + ability_last_word, + {registerAbility(nil)}, + {kind = "class", name = "ability_last_word"} +) +____exports.ability_last_word = ability_last_word +modifier_ability_last_word = __TS__Class() +modifier_ability_last_word.name = "modifier_ability_last_word" +modifier_ability_last_word.____file_path = "scripts/vscripts/abilities/heroes/silencer/ability_last_word.lua" +__TS__ClassExtends(modifier_ability_last_word, BaseModifier) +function modifier_ability_last_word.prototype.____constructor(self, ...) + BaseModifier.prototype.____constructor(self, ...) + self.silenceDuration = 0 + self.damage = 0 + self.intPct = 0 +end +function modifier_ability_last_word.prototype.IsDebuff(self) + return true +end +function modifier_ability_last_word.prototype.IsPurgable(self) + return true +end +function modifier_ability_last_word.prototype.OnCreated(self, params) + local ability = self:GetAbility() + if not ability then + return + end + self.silenceDuration = ability:GetSpecialValueFor("duration") + self.damage = ability:GetSpecialValueFor("damage") + self.intPct = ability:GetSpecialValueFor("int") + if not IsServer() then + return + end + self:StartIntervalThink(params.duration or 0) + EmitSoundOn( + "Hero_Silencer.LastWord.Target", + self:GetParent() + ) +end +function modifier_ability_last_word.prototype.DeclareFunctions(self) + return {MODIFIER_PROPERTY_PROVIDES_FOW_POSITION, MODIFIER_EVENT_ON_ABILITY_FULLY_CAST} +end +function modifier_ability_last_word.prototype.GetModifierProvidesFOWVision(self) + return 1 +end +function modifier_ability_last_word.prototype.OnAbilityFullyCast(self, event) + if not IsServer() then + return + end + if event.unit ~= self:GetParent() then + return + end + if not event.ability or event.ability:IsItem() then + return + end + self:TriggerLastWord() +end +function modifier_ability_last_word.prototype.OnIntervalThink(self) + if not IsServer() then + return + end + self:TriggerLastWord() +end +function modifier_ability_last_word.prototype.TriggerLastWord(self) + local ability = self:GetAbility() + local caster = self:GetCaster() + local parent = self:GetParent() + if not ability or not caster then + return + end + parent:AddNewModifier(caster, ability, modifier_ability_last_word_silence.name, {duration = self.silenceDuration}) + ApplyDamage({ + victim = parent, + attacker = caster, + damage = self.damage + caster:GetIntellect(true) * (self.intPct / 100), + damage_type = ability:GetAbilityDamageType(), + ability = ability + }) + local dmgFx = ParticleManager:CreateParticle("particles/units/heroes/hero_silencer/silencer_last_word_dmg.vpcf", PATTACH_ABSORIGIN_FOLLOW, parent) + ParticleManager:ReleaseParticleIndex(dmgFx) + EmitSoundOn("Hero_Silencer.LastWord.Damage", parent) + StopSoundOn("Hero_Silencer.LastWord.Target", parent) + self:Destroy() +end +function modifier_ability_last_word.prototype.GetEffectName(self) + return "particles/units/heroes/hero_silencer/silencer_last_word_status.vpcf" +end +function modifier_ability_last_word.prototype.GetEffectAttachType(self) + return PATTACH_ABSORIGIN_FOLLOW +end +modifier_ability_last_word = __TS__Decorate( + modifier_ability_last_word, + modifier_ability_last_word, + {registerModifier(nil)}, + {kind = "class", name = "modifier_ability_last_word"} +) +modifier_ability_last_word_silence = __TS__Class() +modifier_ability_last_word_silence.name = "modifier_ability_last_word_silence" +modifier_ability_last_word_silence.____file_path = "scripts/vscripts/abilities/heroes/silencer/ability_last_word.lua" +__TS__ClassExtends(modifier_ability_last_word_silence, BaseModifier) +function modifier_ability_last_word_silence.prototype.IsDebuff(self) + return true +end +function modifier_ability_last_word_silence.prototype.IsPurgable(self) + return true +end +function modifier_ability_last_word_silence.prototype.CheckState(self) + return {[MODIFIER_STATE_SILENCED] = true} +end +function modifier_ability_last_word_silence.prototype.GetEffectName(self) + return "particles/generic_gameplay/generic_silenced.vpcf" +end +function modifier_ability_last_word_silence.prototype.GetEffectAttachType(self) + return PATTACH_OVERHEAD_FOLLOW +end +modifier_ability_last_word_silence = __TS__Decorate( + modifier_ability_last_word_silence, + modifier_ability_last_word_silence, + {registerModifier(nil)}, + {kind = "class", name = "modifier_ability_last_word_silence"} +) +return ____exports diff --git a/scripts/vscripts/abilities/heroes/silencer/glaives_of_wisdom.lua b/scripts/vscripts/abilities/heroes/silencer/glaives_of_wisdom.lua new file mode 100644 index 0000000..159abc6 --- /dev/null +++ b/scripts/vscripts/abilities/heroes/silencer/glaives_of_wisdom.lua @@ -0,0 +1,181 @@ +local ____lualib = require("lualib_bundle") +local __TS__Class = ____lualib.__TS__Class +local __TS__ClassExtends = ____lualib.__TS__ClassExtends +local __TS__Decorate = ____lualib.__TS__Decorate +local ____exports = {} +local ____dota_ts_adapter = require("lib.dota_ts_adapter") +local BaseAbility = ____dota_ts_adapter.BaseAbility +local BaseModifier = ____dota_ts_adapter.BaseModifier +local registerAbility = ____dota_ts_adapter.registerAbility +local registerModifier = ____dota_ts_adapter.registerModifier +____exports.glaives_of_wisdom = __TS__Class() +local glaives_of_wisdom = ____exports.glaives_of_wisdom +glaives_of_wisdom.name = "glaives_of_wisdom" +glaives_of_wisdom.____file_path = "scripts/vscripts/abilities/heroes/silencer/glaives_of_wisdom.lua" +__TS__ClassExtends(glaives_of_wisdom, BaseAbility) +function glaives_of_wisdom.prototype.GetIntrinsicModifierName(self) + return ____exports.modifier_glaives_of_wisdom.name +end +glaives_of_wisdom = __TS__Decorate( + glaives_of_wisdom, + glaives_of_wisdom, + {registerAbility(nil)}, + {kind = "class", name = "glaives_of_wisdom"} +) +____exports.glaives_of_wisdom = glaives_of_wisdom +____exports.modifier_glaives_of_wisdom = __TS__Class() +local modifier_glaives_of_wisdom = ____exports.modifier_glaives_of_wisdom +modifier_glaives_of_wisdom.name = "modifier_glaives_of_wisdom" +modifier_glaives_of_wisdom.____file_path = "scripts/vscripts/abilities/heroes/silencer/glaives_of_wisdom.lua" +__TS__ClassExtends(modifier_glaives_of_wisdom, BaseModifier) +function modifier_glaives_of_wisdom.prototype.____constructor(self, ...) + BaseModifier.prototype.____constructor(self, ...) + self.ricochetAttackDepth = 0 + self.ricochetProjectileSpeed = 1250 +end +function modifier_glaives_of_wisdom.prototype.IsPurgable(self) + return false +end +function modifier_glaives_of_wisdom.prototype.DeclareFunctions(self) + return {MODIFIER_PROPERTY_PROCATTACK_BONUS_DAMAGE_PURE, MODIFIER_PROPERTY_PROJECTILE_NAME, MODIFIER_EVENT_ON_ATTACK_LANDED} +end +function modifier_glaives_of_wisdom.prototype.GetModifierProcAttack_BonusDamage_Pure(self, event) + if not IsServer() then + return 0 + end + if event.attacker ~= self:GetParent() then + return 0 + end + if not event.target then + return 0 + end + local caster = self:GetCaster() + local ability = self:GetAbility() + if not caster or not ability then + return 0 + end + local damage = caster:GetIntellect(true) * (ability:GetSpecialValueFor("intellect_damage_pct") / 100) + SendOverheadEventMessage( + nil, + OVERHEAD_ALERT_BONUS_SPELL_DAMAGE, + event.target, + damage, + nil + ) + EmitSoundOn("Hero_Silencer.GlaivesOfWisdom.Damage", event.target) + return damage +end +function modifier_glaives_of_wisdom.prototype.GetModifierProjectileName(self) + return "particles/units/heroes/hero_silencer/silencer_glaives_of_wisdom.vpcf" +end +function modifier_glaives_of_wisdom.prototype.OnAttackLanded(self, event) + if not IsServer() then + return + end + if self.ricochetAttackDepth > 0 then + return + end + local attacker = self:GetParent() + local ability = self:GetAbility() + if not attacker or not ability then + return + end + if event.attacker ~= attacker then + return + end + local primaryTarget = event.target + if not primaryTarget or not IsValidEntity(primaryTarget) or not primaryTarget:IsAlive() then + return + end + if primaryTarget:GetTeamNumber() == attacker:GetTeamNumber() then + return + end + if attacker:PassivesDisabled() then + return + end + local talent = attacker:FindAbilityByName("special_bonus_unique_silencer_glaives_bounces") + if not talent or talent:GetLevel() <= 0 then + return + end + local ricochetCount = talent:GetSpecialValueFor("bonus_bounce_count") + if ricochetCount <= 0 then + return + end + local ricochetRange = attacker:Script_GetAttackRange() + 150 + local enemies = FindUnitsInRadius( + attacker:GetTeamNumber(), + primaryTarget:GetAbsOrigin(), + nil, + ricochetRange, + DOTA_UNIT_TARGET_TEAM_ENEMY, + bit.bor(DOTA_UNIT_TARGET_HERO, DOTA_UNIT_TARGET_BASIC), + DOTA_UNIT_TARGET_FLAG_NO_INVIS, + FIND_CLOSEST, + false + ) + local launched = 0 + for ____, enemy in ipairs(enemies) do + do + if launched >= ricochetCount then + break + end + if enemy == primaryTarget then + goto __continue21 + end + if not enemy:IsAlive() then + goto __continue21 + end + if enemy:GetTeamNumber() == attacker:GetTeamNumber() then + goto __continue21 + end + self:LaunchRicochetProjectile(attacker, primaryTarget, enemy) + launched = launched + 1 + end + ::__continue21:: + end +end +function modifier_glaives_of_wisdom.prototype.LaunchRicochetProjectile(self, attacker, source, target) + local ability = self:GetAbility() + if not ability then + return + end + ProjectileManager:CreateTrackingProjectile({ + Source = source, + Target = target, + Ability = ability, + EffectName = self:GetModifierProjectileName(), + iMoveSpeed = self.ricochetProjectileSpeed, + bDodgeable = true, + bVisibleToEnemies = true, + bReplaceExisting = false, + bProvidesVision = false + }) + Timers:CreateTimer( + 0.2, + function() + if not attacker:IsAlive() or not target:IsAlive() then + return + end + self.ricochetAttackDepth = self.ricochetAttackDepth + 1 + attacker:PerformAttack( + target, + true, + true, + true, + false, + false, + false, + false + ) + self.ricochetAttackDepth = math.max(0, self.ricochetAttackDepth - 1) + end + ) +end +modifier_glaives_of_wisdom = __TS__Decorate( + modifier_glaives_of_wisdom, + modifier_glaives_of_wisdom, + {registerModifier(nil)}, + {kind = "class", name = "modifier_glaives_of_wisdom"} +) +____exports.modifier_glaives_of_wisdom = modifier_glaives_of_wisdom +return ____exports diff --git a/scripts/vscripts/abilities/heroes/silencer/int.lua b/scripts/vscripts/abilities/heroes/silencer/int.lua new file mode 100644 index 0000000..52bfea1 --- /dev/null +++ b/scripts/vscripts/abilities/heroes/silencer/int.lua @@ -0,0 +1,100 @@ +local ____lualib = require("lualib_bundle") +local __TS__Class = ____lualib.__TS__Class +local __TS__ClassExtends = ____lualib.__TS__ClassExtends +local __TS__Decorate = ____lualib.__TS__Decorate +local ____exports = {} +local modifier_int_buff +local ____dota_ts_adapter = require("lib.dota_ts_adapter") +local BaseAbility = ____dota_ts_adapter.BaseAbility +local BaseModifier = ____dota_ts_adapter.BaseModifier +local registerAbility = ____dota_ts_adapter.registerAbility +local registerModifier = ____dota_ts_adapter.registerModifier +____exports.int = __TS__Class() +local int = ____exports.int +int.name = "int" +int.____file_path = "scripts/vscripts/abilities/heroes/silencer/int.lua" +__TS__ClassExtends(int, BaseAbility) +function int.prototype.GetIntrinsicModifierName(self) + return modifier_int_buff.name +end +function int.prototype.Precache(self, context) + PrecacheResource("particle", "particles/silencer_count.vpcf", context) + PrecacheResource("particle", "particles/units/heroes/hero_nevermore/nevermore_necro_souls.vpcf", context) + PrecacheResource("soundfile", "soundevents/game_sounds_heroes/game_sounds_nevermore.vsndevts", context) +end +int = __TS__Decorate( + int, + int, + {registerAbility(nil)}, + {kind = "class", name = "int"} +) +____exports.int = int +modifier_int_buff = __TS__Class() +modifier_int_buff.name = "modifier_int_buff" +modifier_int_buff.____file_path = "scripts/vscripts/abilities/heroes/silencer/int.lua" +__TS__ClassExtends(modifier_int_buff, BaseModifier) +function modifier_int_buff.prototype.IsHidden(self) + return false +end +function modifier_int_buff.prototype.IsPurgable(self) + return false +end +function modifier_int_buff.prototype.RemoveOnDeath(self) + return false +end +function modifier_int_buff.prototype.OnCreated(self) + if not IsServer() then + return + end + local ____opt_0 = self:GetAbility() + local interval = ____opt_0 and ____opt_0:GetSpecialValueFor("stack_interval") or 10 + self:StartIntervalThink(interval) +end +function modifier_int_buff.prototype.OnRefresh(self) + if not IsServer() then + return + end + local ____opt_2 = self:GetAbility() + local interval = ____opt_2 and ____opt_2:GetSpecialValueFor("stack_interval") or 10 + self:StartIntervalThink(interval) +end +function modifier_int_buff.prototype.OnIntervalThink(self) + if not IsServer() then + return + end + if self:GetParent():PassivesDisabled() then + return + end + self:SetStackCount(self:GetStackCount() + 1) + local parent = self:GetParent() + local fx = ParticleManager:CreateParticle("particles/silencer_count.vpcf", PATTACH_OVERHEAD_FOLLOW, parent) + ParticleManager:SetParticleControl( + fx, + 15, + Vector(0, 0, 255) + ) + ParticleManager:SetParticleControl( + fx, + 16, + Vector(1, 0, 0) + ) + ParticleManager:ReleaseParticleIndex(fx) +end +function modifier_int_buff.prototype.DeclareFunctions(self) + return {MODIFIER_PROPERTY_STATS_INTELLECT_BONUS} +end +function modifier_int_buff.prototype.GetModifierBonusStats_Intellect(self) + if self:GetParent():PassivesDisabled() then + return 0 + end + local ____opt_4 = self:GetAbility() + local growInt = ____opt_4 and ____opt_4:GetSpecialValueFor("grow_int") or 0 + return self:GetStackCount() * growInt +end +modifier_int_buff = __TS__Decorate( + modifier_int_buff, + modifier_int_buff, + {registerModifier(nil)}, + {kind = "class", name = "modifier_int_buff"} +) +return ____exports diff --git a/scripts/vscripts/abilities/heroes/silencer/razor_eye_of_the_storm_lua.lua b/scripts/vscripts/abilities/heroes/silencer/razor_eye_of_the_storm_lua.lua new file mode 100644 index 0000000..9e2078b --- /dev/null +++ b/scripts/vscripts/abilities/heroes/silencer/razor_eye_of_the_storm_lua.lua @@ -0,0 +1,212 @@ +local ____lualib = require("lualib_bundle") +local __TS__Class = ____lualib.__TS__Class +local __TS__ClassExtends = ____lualib.__TS__ClassExtends +local __TS__Decorate = ____lualib.__TS__Decorate +local ____exports = {} +local modifier_razor_eye_of_the_storm_lua, modifier_razor_eye_of_the_storm_lua_debuff +local ____dota_ts_adapter = require("lib.dota_ts_adapter") +local BaseAbility = ____dota_ts_adapter.BaseAbility +local BaseModifier = ____dota_ts_adapter.BaseModifier +local registerAbility = ____dota_ts_adapter.registerAbility +local registerModifier = ____dota_ts_adapter.registerModifier +____exports.razor_eye_of_the_storm_lua = __TS__Class() +local razor_eye_of_the_storm_lua = ____exports.razor_eye_of_the_storm_lua +razor_eye_of_the_storm_lua.name = "razor_eye_of_the_storm_lua" +razor_eye_of_the_storm_lua.____file_path = "scripts/vscripts/abilities/heroes/silencer/razor_eye_of_the_storm_lua.lua" +__TS__ClassExtends(razor_eye_of_the_storm_lua, BaseAbility) +function razor_eye_of_the_storm_lua.prototype.Precache(self, context) + PrecacheResource("soundfile", "soundevents/game_sounds_heroes/game_sounds_razor.vsndevts", context) + PrecacheResource("particle", "particles/econ/items/razor/razor_arcana/razor_arcana_eye_of_the_storm_rain.vpcf", context) + PrecacheResource("particle", "particles/econ/items/razor/razor_arcana/razor_arcana_eye_of_the_storm.vpcf", context) +end +function razor_eye_of_the_storm_lua.prototype.OnSpellStart(self) + if not IsServer() then + return + end + self:GetCaster():AddNewModifier( + self:GetCaster(), + self, + modifier_razor_eye_of_the_storm_lua.name, + {duration = self:GetSpecialValueFor("duration")} + ) +end +razor_eye_of_the_storm_lua = __TS__Decorate( + razor_eye_of_the_storm_lua, + razor_eye_of_the_storm_lua, + {registerAbility(nil)}, + {kind = "class", name = "razor_eye_of_the_storm_lua"} +) +____exports.razor_eye_of_the_storm_lua = razor_eye_of_the_storm_lua +modifier_razor_eye_of_the_storm_lua = __TS__Class() +modifier_razor_eye_of_the_storm_lua.name = "modifier_razor_eye_of_the_storm_lua" +modifier_razor_eye_of_the_storm_lua.____file_path = "scripts/vscripts/abilities/heroes/silencer/razor_eye_of_the_storm_lua.lua" +__TS__ClassExtends(modifier_razor_eye_of_the_storm_lua, BaseModifier) +function modifier_razor_eye_of_the_storm_lua.prototype.____constructor(self, ...) + BaseModifier.prototype.____constructor(self, ...) + self.radius = 0 + self.damage = 0 + self.strikeInterval = 0.75 + self.armorReduction = 0 + self.strikes = 1 +end +function modifier_razor_eye_of_the_storm_lua.prototype.IsPurgable(self) + return false +end +function modifier_razor_eye_of_the_storm_lua.prototype.OnCreated(self) + local ability = self:GetAbility() + local parent = self:GetParent() + if not ability then + return + end + self:PlayStartEffects() + if not IsServer() then + return + end + self.radius = ability:GetSpecialValueFor("radius") + self.damage = ability:GetSpecialValueFor("damage") + parent:GetIntellect(true) + self.strikeInterval = ability:GetSpecialValueFor("strike_interval") + self.armorReduction = ability:GetSpecialValueFor("armor_reduction") + local scepterBonusTargets = ability:GetSpecialValueFor("scepter_bonus_targets") + self.strikes = parent:HasScepter() and 1 + scepterBonusTargets or 1 + self:StartIntervalThink(self.strikeInterval) + self:OnIntervalThink() +end +function modifier_razor_eye_of_the_storm_lua.prototype.OnDestroy(self) + if not IsServer() then + return + end + StopSoundOn( + "Hero_Razor.Storm.Loop", + self:GetParent() + ) + EmitSoundOn( + "Hero_Razor.StormEnd", + self:GetParent() + ) +end +function modifier_razor_eye_of_the_storm_lua.prototype.OnIntervalThink(self) + if not IsServer() then + return + end + local parent = self:GetParent() + local ability = self:GetAbility() + if not ability then + return + end + local enemies = FindUnitsInRadius( + parent:GetTeamNumber(), + parent:GetAbsOrigin(), + nil, + self.radius, + DOTA_UNIT_TARGET_TEAM_ENEMY, + bit.bor( + bit.bor(DOTA_UNIT_TARGET_HERO, DOTA_UNIT_TARGET_BASIC), + DOTA_UNIT_TARGET_BUILDING + ), + DOTA_UNIT_TARGET_FLAG_MAGIC_IMMUNE_ENEMIES, + FIND_CLOSEST, + false + ) + if #enemies == 0 then + return + end + local hitCount = 0 + for ____, enemy in ipairs(enemies) do + do + if hitCount >= self.strikes then + break + end + if enemy:IsBuilding() and not enemy:IsTower() then + goto __continue15 + end + hitCount = hitCount + 1 + ApplyDamage({ + victim = enemy, + attacker = parent, + damage = self.damage, + damage_type = ability:GetAbilityDamageType(), + ability = ability + }) + enemy:AddNewModifier( + parent, + ability, + modifier_razor_eye_of_the_storm_lua_debuff.name, + { + duration = self:GetRemainingTime(), + armor = self.armorReduction + } + ) + self:PlayHitEffects(enemy) + end + ::__continue15:: + end +end +function modifier_razor_eye_of_the_storm_lua.prototype.PlayStartEffects(self) + local parent = self:GetParent() + local castFx = ParticleManager:CreateParticle("particles/econ/items/razor/razor_arcana/razor_arcana_eye_of_the_storm_rain.vpcf", PATTACH_ABSORIGIN_FOLLOW, parent) + self:AddParticle( + castFx, + false, + false, + -1, + false, + false + ) + EmitSoundOn("Hero_Razor.Storm.Cast", parent) + EmitSoundOn("Hero_Razor.Storm.Loop", parent) +end +function modifier_razor_eye_of_the_storm_lua.prototype.PlayHitEffects(self, enemy) + local parent = self:GetParent() + local hitFx = ParticleManager:CreateParticle("particles/econ/items/razor/razor_arcana/razor_arcana_eye_of_the_storm.vpcf", PATTACH_CUSTOMORIGIN, parent) + ParticleManager:SetParticleControl( + hitFx, + 0, + parent:GetOrigin() + Vector(0, 0, 500) + ) + ParticleManager:SetParticleControlEnt( + hitFx, + 1, + enemy, + PATTACH_POINT_FOLLOW, + "attach_hitloc", + Vector(0, 0, 0), + true + ) + ParticleManager:ReleaseParticleIndex(hitFx) + EmitSoundOn("Hero_razor.lightning", enemy) +end +modifier_razor_eye_of_the_storm_lua = __TS__Decorate( + modifier_razor_eye_of_the_storm_lua, + modifier_razor_eye_of_the_storm_lua, + {registerModifier(nil)}, + {kind = "class", name = "modifier_razor_eye_of_the_storm_lua"} +) +modifier_razor_eye_of_the_storm_lua_debuff = __TS__Class() +modifier_razor_eye_of_the_storm_lua_debuff.name = "modifier_razor_eye_of_the_storm_lua_debuff" +modifier_razor_eye_of_the_storm_lua_debuff.____file_path = "scripts/vscripts/abilities/heroes/silencer/razor_eye_of_the_storm_lua.lua" +__TS__ClassExtends(modifier_razor_eye_of_the_storm_lua_debuff, BaseModifier) +function modifier_razor_eye_of_the_storm_lua_debuff.prototype.IsDebuff(self) + return true +end +function modifier_razor_eye_of_the_storm_lua_debuff.prototype.IsPurgable(self) + return false +end +function modifier_razor_eye_of_the_storm_lua_debuff.prototype.OnCreated(self, params) + self:SetStackCount(params.armor or 0) +end +function modifier_razor_eye_of_the_storm_lua_debuff.prototype.OnRefresh(self, params) + self:SetStackCount(self:GetStackCount() + (params.armor or 0)) +end +function modifier_razor_eye_of_the_storm_lua_debuff.prototype.DeclareFunctions(self) + return {MODIFIER_PROPERTY_PHYSICAL_ARMOR_BONUS} +end +function modifier_razor_eye_of_the_storm_lua_debuff.prototype.GetModifierPhysicalArmorBonus(self) + return -self:GetStackCount() +end +modifier_razor_eye_of_the_storm_lua_debuff = __TS__Decorate( + modifier_razor_eye_of_the_storm_lua_debuff, + modifier_razor_eye_of_the_storm_lua_debuff, + {registerModifier(nil)}, + {kind = "class", name = "modifier_razor_eye_of_the_storm_lua_debuff"} +) +return ____exports diff --git a/scripts/vscripts/abilities/heroes/silencer/silencer_curse_of_the_silent.lua b/scripts/vscripts/abilities/heroes/silencer/silencer_curse_of_the_silent.lua new file mode 100644 index 0000000..0a35f98 --- /dev/null +++ b/scripts/vscripts/abilities/heroes/silencer/silencer_curse_of_the_silent.lua @@ -0,0 +1,160 @@ +local ____lualib = require("lualib_bundle") +local __TS__Class = ____lualib.__TS__Class +local __TS__ClassExtends = ____lualib.__TS__ClassExtends +local __TS__Decorate = ____lualib.__TS__Decorate +local ____exports = {} +local ____dota_ts_adapter = require("lib.dota_ts_adapter") +local BaseAbility = ____dota_ts_adapter.BaseAbility +local BaseModifier = ____dota_ts_adapter.BaseModifier +local registerAbility = ____dota_ts_adapter.registerAbility +local registerModifier = ____dota_ts_adapter.registerModifier +____exports.silencer_curse_of_the_silent = __TS__Class() +local silencer_curse_of_the_silent = ____exports.silencer_curse_of_the_silent +silencer_curse_of_the_silent.name = "silencer_curse_of_the_silent" +silencer_curse_of_the_silent.____file_path = "scripts/vscripts/abilities/heroes/silencer/silencer_curse_of_the_silent.lua" +__TS__ClassExtends(silencer_curse_of_the_silent, BaseAbility) +function silencer_curse_of_the_silent.prototype.GetAOERadius(self) + return self:GetSpecialValueFor("radius") +end +function silencer_curse_of_the_silent.prototype.OnSpellStart(self) + if not IsServer() then + return + end + local caster = self:GetCaster() + local point = self:GetCursorPosition() + local duration = self:GetSpecialValueFor("duration") + local radius = self:GetSpecialValueFor("radius") + local enemies = FindUnitsInRadius( + caster:GetTeamNumber(), + point, + nil, + radius, + DOTA_UNIT_TARGET_TEAM_ENEMY, + bit.bor(DOTA_UNIT_TARGET_HERO, DOTA_UNIT_TARGET_BASIC), + DOTA_UNIT_TARGET_FLAG_NONE, + FIND_ANY_ORDER, + false + ) + for ____, enemy in ipairs(enemies) do + enemy:AddNewModifier(caster, self, ____exports.modifier_silencer_curse_of_the_silent.name, {duration = duration}) + end + local castFx = ParticleManager:CreateParticle("particles/units/heroes/hero_silencer/silencer_curse_cast.vpcf", PATTACH_ABSORIGIN_FOLLOW, caster) + ParticleManager:SetParticleControlEnt( + castFx, + 0, + caster, + PATTACH_POINT_FOLLOW, + "attach_attack1", + Vector(0, 0, 0), + true + ) + ParticleManager:ReleaseParticleIndex(castFx) + local aoeFx = ParticleManager:CreateParticle("particles/units/heroes/hero_silencer/silencer_curse_aoe.vpcf", PATTACH_WORLDORIGIN, nil) + ParticleManager:SetParticleControl(aoeFx, 0, point) + ParticleManager:SetParticleControl( + aoeFx, + 1, + Vector(radius, 0, 0) + ) + ParticleManager:ReleaseParticleIndex(aoeFx) + EmitSoundOn("Hero_Silencer.Curse.Cast", caster) + EmitSoundOnLocationWithCaster(point, "Hero_Silencer.Curse", caster) +end +silencer_curse_of_the_silent = __TS__Decorate( + silencer_curse_of_the_silent, + silencer_curse_of_the_silent, + {registerAbility(nil)}, + {kind = "class", name = "silencer_curse_of_the_silent"} +) +____exports.silencer_curse_of_the_silent = silencer_curse_of_the_silent +____exports.modifier_silencer_curse_of_the_silent = __TS__Class() +local modifier_silencer_curse_of_the_silent = ____exports.modifier_silencer_curse_of_the_silent +modifier_silencer_curse_of_the_silent.name = "modifier_silencer_curse_of_the_silent" +modifier_silencer_curse_of_the_silent.____file_path = "scripts/vscripts/abilities/heroes/silencer/silencer_curse_of_the_silent.lua" +__TS__ClassExtends(modifier_silencer_curse_of_the_silent, BaseModifier) +function modifier_silencer_curse_of_the_silent.prototype.____constructor(self, ...) + BaseModifier.prototype.____constructor(self, ...) + self.penaltyDuration = 0 + self.moveSlowPct = 0 + self.tickDamage = 0 +end +function modifier_silencer_curse_of_the_silent.prototype.IsDebuff(self) + return true +end +function modifier_silencer_curse_of_the_silent.prototype.IsPurgable(self) + return true +end +function modifier_silencer_curse_of_the_silent.prototype.OnCreated(self) + local ability = self:GetAbility() + local caster = self:GetCaster() + if not ability or not caster then + return + end + self.penaltyDuration = ability:GetSpecialValueFor("penalty_duration") + self.moveSlowPct = ability:GetSpecialValueFor("movespeed") + self.tickDamage = ability:GetSpecialValueFor("damage") + caster:GetIntellect(true) + if not IsServer() then + return + end + EmitSoundOn( + "Hero_Silencer.Curse.Impact", + self:GetParent() + ) + self:StartIntervalThink(1) +end +function modifier_silencer_curse_of_the_silent.prototype.OnIntervalThink(self) + if not IsServer() then + return + end + local ability = self:GetAbility() + local caster = self:GetCaster() + if not ability or not caster then + return + end + ApplyDamage({ + victim = self:GetParent(), + attacker = caster, + damage = self.tickDamage, + damage_type = ability:GetAbilityDamageType(), + ability = ability + }) + EmitSoundOn( + "Hero_Silencer.Curse_Tick", + self:GetParent() + ) +end +function modifier_silencer_curse_of_the_silent.prototype.OnAbilityFullyCast(self, event) + if not IsServer() then + return + end + if event.unit ~= self:GetParent() then + return + end + if not event.ability or event.ability:IsItem() then + return + end + self:SetDuration( + self:GetRemainingTime() + self.penaltyDuration, + true + ) +end +function modifier_silencer_curse_of_the_silent.prototype.DeclareFunctions(self) + return {MODIFIER_PROPERTY_MOVESPEED_BONUS_PERCENTAGE, MODIFIER_EVENT_ON_ABILITY_FULLY_CAST} +end +function modifier_silencer_curse_of_the_silent.prototype.GetModifierMoveSpeedBonus_Percentage(self) + return self.moveSlowPct +end +function modifier_silencer_curse_of_the_silent.prototype.GetEffectName(self) + return "particles/units/heroes/hero_silencer/silencer_curse.vpcf" +end +function modifier_silencer_curse_of_the_silent.prototype.GetEffectAttachType(self) + return PATTACH_OVERHEAD_FOLLOW +end +modifier_silencer_curse_of_the_silent = __TS__Decorate( + modifier_silencer_curse_of_the_silent, + modifier_silencer_curse_of_the_silent, + {registerModifier(nil)}, + {kind = "class", name = "modifier_silencer_curse_of_the_silent"} +) +____exports.modifier_silencer_curse_of_the_silent = modifier_silencer_curse_of_the_silent +return ____exports diff --git a/scripts/vscripts/abilities/heroes/skywrath_mage/skywrath_mage_ancient_seal_custom.lua b/scripts/vscripts/abilities/heroes/skywrath_mage/skywrath_mage_ancient_seal_custom.lua new file mode 100644 index 0000000..bae11b8 --- /dev/null +++ b/scripts/vscripts/abilities/heroes/skywrath_mage/skywrath_mage_ancient_seal_custom.lua @@ -0,0 +1,109 @@ +local ____lualib = require("lualib_bundle") +local __TS__Class = ____lualib.__TS__Class +local __TS__ClassExtends = ____lualib.__TS__ClassExtends +local __TS__Decorate = ____lualib.__TS__Decorate +local ____exports = {} +local ____dota_ts_adapter = require("lib.dota_ts_adapter") +local BaseAbility = ____dota_ts_adapter.BaseAbility +local BaseModifier = ____dota_ts_adapter.BaseModifier +local registerAbility = ____dota_ts_adapter.registerAbility +local registerModifier = ____dota_ts_adapter.registerModifier +____exports.skywrath_mage_ancient_seal_custom = __TS__Class() +local skywrath_mage_ancient_seal_custom = ____exports.skywrath_mage_ancient_seal_custom +skywrath_mage_ancient_seal_custom.name = "skywrath_mage_ancient_seal_custom" +skywrath_mage_ancient_seal_custom.____file_path = "scripts/vscripts/abilities/heroes/skywrath_mage/skywrath_mage_ancient_seal_custom.lua" +__TS__ClassExtends(skywrath_mage_ancient_seal_custom, BaseAbility) +function skywrath_mage_ancient_seal_custom.prototype.GetAOERadius(self) + return self:GetSpecialValueFor("radius") +end +function skywrath_mage_ancient_seal_custom.prototype.Precache(self, context) + PrecacheResource("particle", "particles/units/heroes/hero_skywrath_mage/skywrath_mage_ancient_seal_debuff.vpcf", context) +end +function skywrath_mage_ancient_seal_custom.prototype.OnSpellStart(self) + if not IsServer() then + return + end + local target = self:GetCursorPosition() + if not target then + return + end + local caster = self:GetCaster() + local duration = self:GetSpecialValueFor("seal_duration") + local radius = self:GetSpecialValueFor("radius") + local enemies = FindUnitsInRadius( + caster:GetTeamNumber(), + target, + nil, + radius, + DOTA_UNIT_TARGET_TEAM_ENEMY, + bit.bor(DOTA_UNIT_TARGET_HERO, DOTA_UNIT_TARGET_BASIC), + DOTA_UNIT_TARGET_FLAG_NONE, + FIND_ANY_ORDER, + false + ) + for ____, enemy in ipairs(enemies) do + enemy:AddNewModifier(caster, self, ____exports.modifier_skywrath_mage_ancient_seal_custom.name, {duration = duration}) + EmitSoundOn("Hero_SkywrathMage.AncientSeal.Target", enemy) + end +end +skywrath_mage_ancient_seal_custom = __TS__Decorate( + skywrath_mage_ancient_seal_custom, + skywrath_mage_ancient_seal_custom, + {registerAbility(nil)}, + {kind = "class", name = "skywrath_mage_ancient_seal_custom"} +) +____exports.skywrath_mage_ancient_seal_custom = skywrath_mage_ancient_seal_custom +____exports.modifier_skywrath_mage_ancient_seal_custom = __TS__Class() +local modifier_skywrath_mage_ancient_seal_custom = ____exports.modifier_skywrath_mage_ancient_seal_custom +modifier_skywrath_mage_ancient_seal_custom.name = "modifier_skywrath_mage_ancient_seal_custom" +modifier_skywrath_mage_ancient_seal_custom.____file_path = "scripts/vscripts/abilities/heroes/skywrath_mage/skywrath_mage_ancient_seal_custom.lua" +__TS__ClassExtends(modifier_skywrath_mage_ancient_seal_custom, BaseModifier) +function modifier_skywrath_mage_ancient_seal_custom.prototype.____constructor(self, ...) + BaseModifier.prototype.____constructor(self, ...) + self.magicAmpPct = 0 +end +function modifier_skywrath_mage_ancient_seal_custom.prototype.IsHidden(self) + return false +end +function modifier_skywrath_mage_ancient_seal_custom.prototype.IsPurgable(self) + return true +end +function modifier_skywrath_mage_ancient_seal_custom.prototype.IsDebuff(self) + return true +end +function modifier_skywrath_mage_ancient_seal_custom.prototype.GetAttributes(self) + return MODIFIER_ATTRIBUTE_MULTIPLE +end +function modifier_skywrath_mage_ancient_seal_custom.prototype.OnCreated(self) + local ability = self:GetAbility() + if not ability then + return + end + local caster = self:GetCaster() + local intMultiplier = ability:GetSpecialValueFor("int_multiplier") + local intBonus = caster and caster:GetIntellect(true) * intMultiplier or 0 + self.magicAmpPct = ability:GetSpecialValueFor("resist_debuff") - intBonus +end +function modifier_skywrath_mage_ancient_seal_custom.prototype.CheckState(self) + return {[MODIFIER_STATE_SILENCED] = true} +end +function modifier_skywrath_mage_ancient_seal_custom.prototype.DeclareFunctions(self) + return {MODIFIER_PROPERTY_MAGICAL_RESISTANCE_BONUS} +end +function modifier_skywrath_mage_ancient_seal_custom.prototype.GetModifierMagicalResistanceBonus(self) + return self.magicAmpPct +end +function modifier_skywrath_mage_ancient_seal_custom.prototype.GetEffectName(self) + return "particles/units/heroes/hero_skywrath_mage/skywrath_mage_ancient_seal_debuff.vpcf" +end +function modifier_skywrath_mage_ancient_seal_custom.prototype.GetEffectAttachType(self) + return PATTACH_OVERHEAD_FOLLOW +end +modifier_skywrath_mage_ancient_seal_custom = __TS__Decorate( + modifier_skywrath_mage_ancient_seal_custom, + modifier_skywrath_mage_ancient_seal_custom, + {registerModifier(nil)}, + {kind = "class", name = "modifier_skywrath_mage_ancient_seal_custom"} +) +____exports.modifier_skywrath_mage_ancient_seal_custom = modifier_skywrath_mage_ancient_seal_custom +return ____exports diff --git a/scripts/vscripts/abilities/heroes/skywrath_mage/skywrath_mage_arcane_bolt_custom.lua b/scripts/vscripts/abilities/heroes/skywrath_mage/skywrath_mage_arcane_bolt_custom.lua new file mode 100644 index 0000000..0de42ac --- /dev/null +++ b/scripts/vscripts/abilities/heroes/skywrath_mage/skywrath_mage_arcane_bolt_custom.lua @@ -0,0 +1,81 @@ +local ____lualib = require("lualib_bundle") +local __TS__Class = ____lualib.__TS__Class +local __TS__ClassExtends = ____lualib.__TS__ClassExtends +local __TS__Decorate = ____lualib.__TS__Decorate +local ____exports = {} +local ____dota_ts_adapter = require("lib.dota_ts_adapter") +local BaseAbility = ____dota_ts_adapter.BaseAbility +local registerAbility = ____dota_ts_adapter.registerAbility +____exports.skywrath_mage_arcane_bolt_custom = __TS__Class() +local skywrath_mage_arcane_bolt_custom = ____exports.skywrath_mage_arcane_bolt_custom +skywrath_mage_arcane_bolt_custom.name = "skywrath_mage_arcane_bolt_custom" +skywrath_mage_arcane_bolt_custom.____file_path = "scripts/vscripts/abilities/heroes/skywrath_mage/skywrath_mage_arcane_bolt_custom.lua" +__TS__ClassExtends(skywrath_mage_arcane_bolt_custom, BaseAbility) +function skywrath_mage_arcane_bolt_custom.prototype.Precache(self, context) + PrecacheResource("particle", "particles/units/heroes/hero_skywrath_mage/skywrath_mage_arcane_bolt.vpcf", context) +end +function skywrath_mage_arcane_bolt_custom.prototype.OnSpellStart(self) + if not IsServer() then + return + end + local caster = self:GetCaster() + local target = self:GetCursorTarget() + if not target then + return + end + if target:TriggerSpellAbsorb(self) then + return + end + local speed = self:GetSpecialValueFor("bolt_speed") + local vision = self:GetSpecialValueFor("bolt_vision") + ProjectileManager:CreateTrackingProjectile({ + Ability = self, + EffectName = "particles/units/heroes/hero_skywrath_mage/skywrath_mage_arcane_bolt.vpcf", + Source = caster, + Target = target, + bDodgeable = false, + bProvidesVision = true, + iMoveSpeed = speed, + iVisionRadius = vision, + iVisionTeamNumber = caster:GetTeamNumber() + }) + EmitSoundOn("Hero_SkywrathMage.ArcaneBolt.Cast", caster) +end +function skywrath_mage_arcane_bolt_custom.prototype.OnProjectileHit(self, target, location) + if not IsServer() then + return + end + if not target then + return + end + local caster = self:GetCaster() + local boltDamage = self:GetSpecialValueFor("bolt_damage") + local intMultiplier = self:GetSpecialValueFor("int_multiplier") + local vision = self:GetSpecialValueFor("bolt_vision") + local visionDuration = self:GetSpecialValueFor("vision_duration") + local damage = boltDamage + caster:GetIntellect(true) * intMultiplier + ApplyDamage({ + victim = target, + attacker = caster, + damage = damage, + damage_type = DAMAGE_TYPE_MAGICAL, + ability = self + }) + AddFOWViewer( + caster:GetTeamNumber(), + location, + vision, + visionDuration, + false + ) + StopSoundOn("Hero_SkywrathMage.ArcaneBolt.Cast", caster) + EmitSoundOn("Hero_SkywrathMage.ArcaneBolt.Impact", target) +end +skywrath_mage_arcane_bolt_custom = __TS__Decorate( + skywrath_mage_arcane_bolt_custom, + skywrath_mage_arcane_bolt_custom, + {registerAbility(nil)}, + {kind = "class", name = "skywrath_mage_arcane_bolt_custom"} +) +____exports.skywrath_mage_arcane_bolt_custom = skywrath_mage_arcane_bolt_custom +return ____exports diff --git a/scripts/vscripts/abilities/heroes/skywrath_mage/skywrath_mage_concussive_shot_custom.lua b/scripts/vscripts/abilities/heroes/skywrath_mage/skywrath_mage_concussive_shot_custom.lua new file mode 100644 index 0000000..31b5aaf --- /dev/null +++ b/scripts/vscripts/abilities/heroes/skywrath_mage/skywrath_mage_concussive_shot_custom.lua @@ -0,0 +1,173 @@ +local ____lualib = require("lualib_bundle") +local __TS__Class = ____lualib.__TS__Class +local __TS__ClassExtends = ____lualib.__TS__ClassExtends +local __TS__Decorate = ____lualib.__TS__Decorate +local ____exports = {} +local ____dota_ts_adapter = require("lib.dota_ts_adapter") +local BaseAbility = ____dota_ts_adapter.BaseAbility +local BaseModifier = ____dota_ts_adapter.BaseModifier +local registerAbility = ____dota_ts_adapter.registerAbility +local registerModifier = ____dota_ts_adapter.registerModifier +____exports.skywrath_mage_concussive_shot_custom = __TS__Class() +local skywrath_mage_concussive_shot_custom = ____exports.skywrath_mage_concussive_shot_custom +skywrath_mage_concussive_shot_custom.name = "skywrath_mage_concussive_shot_custom" +skywrath_mage_concussive_shot_custom.____file_path = "scripts/vscripts/abilities/heroes/skywrath_mage/skywrath_mage_concussive_shot_custom.lua" +__TS__ClassExtends(skywrath_mage_concussive_shot_custom, BaseAbility) +function skywrath_mage_concussive_shot_custom.prototype.Precache(self, context) + PrecacheResource("particle", "particles/units/heroes/hero_skywrath_mage/skywrath_mage_concussive_shot.vpcf", context) + PrecacheResource("particle", "particles/units/heroes/hero_skywrath_mage/skywrath_mage_concussive_shot_cast.vpcf", context) + PrecacheResource("particle", "particles/units/heroes/hero_skywrath_mage/skywrath_mage_concussive_shot_failure.vpcf", context) +end +function skywrath_mage_concussive_shot_custom.prototype.OnSpellStart(self) + if not IsServer() then + return + end + local caster = self:GetCaster() + local launchRadius = self:GetSpecialValueFor("launch_radius") + local projectileSpeed = self:GetSpecialValueFor("speed") + local enemies = FindUnitsInRadius( + caster:GetTeamNumber(), + caster:GetAbsOrigin(), + nil, + launchRadius, + DOTA_UNIT_TARGET_TEAM_ENEMY, + DOTA_UNIT_TARGET_HERO + DOTA_UNIT_TARGET_BASIC, + DOTA_UNIT_TARGET_FLAG_NO_INVIS + DOTA_UNIT_TARGET_FLAG_FOW_VISIBLE, + FIND_CLOSEST, + false + ) + local target = enemies[1] + if not target then + local fail = ParticleManager:CreateParticle("particles/units/heroes/hero_skywrath_mage/skywrath_mage_concussive_shot_failure.vpcf", PATTACH_ABSORIGIN_FOLLOW, caster) + ParticleManager:ReleaseParticleIndex(fail) + return + end + ProjectileManager:CreateTrackingProjectile({ + Ability = self, + Source = caster, + Target = target, + EffectName = "particles/units/heroes/hero_skywrath_mage/skywrath_mage_concussive_shot.vpcf", + iMoveSpeed = projectileSpeed, + bDodgeable = true, + bProvidesVision = false + }) + local castParticle = ParticleManager:CreateParticle("particles/units/heroes/hero_skywrath_mage/skywrath_mage_concussive_shot_cast.vpcf", PATTACH_ABSORIGIN_FOLLOW, caster) + ParticleManager:SetParticleControlEnt( + castParticle, + 0, + caster, + PATTACH_POINT_FOLLOW, + "attach_hitloc", + Vector(0, 0, 0), + true + ) + ParticleManager:SetParticleControlEnt( + castParticle, + 1, + target, + PATTACH_POINT_FOLLOW, + "attach_hitloc", + Vector(0, 0, 0), + true + ) + ParticleManager:SetParticleControl( + castParticle, + 2, + Vector(projectileSpeed, 0, 0) + ) + ParticleManager:ReleaseParticleIndex(castParticle) + EmitSoundOn("Hero_SkywrathMage.ConcussiveShot.Cast", caster) +end +function skywrath_mage_concussive_shot_custom.prototype.OnProjectileHit(self, target, _location) + if not IsServer() then + return + end + if not target then + return + end + local caster = self:GetCaster() + local radius = self:GetSpecialValueFor("slow_radius") + local damage = self:GetSpecialValueFor("damage") + local intMultiplier = self:GetSpecialValueFor("int_multiplier") + local duration = self:GetSpecialValueFor("slow_duration") + local creepMult = self:GetSpecialValueFor("creep_damage_pct") + local vision = self:GetSpecialValueFor("shot_vision") + local visionDuration = self:GetSpecialValueFor("vision_duration") + local enemies = FindUnitsInRadius( + caster:GetTeamNumber(), + target:GetAbsOrigin(), + nil, + radius, + DOTA_UNIT_TARGET_TEAM_ENEMY, + DOTA_UNIT_TARGET_HERO + DOTA_UNIT_TARGET_BASIC, + DOTA_UNIT_TARGET_FLAG_NONE, + FIND_ANY_ORDER, + false + ) + for ____, enemy in ipairs(enemies) do + local damageWithInt = damage + caster:GetIntellect(true) * intMultiplier + local finalDamage = enemy:IsCreep() and damageWithInt * (creepMult / 100) or damageWithInt + ApplyDamage({ + victim = enemy, + attacker = caster, + damage = finalDamage, + damage_type = self:GetAbilityDamageType(), + ability = self + }) + enemy:AddNewModifier(caster, self, ____exports.modifier_skywrath_mage_concussive_shot_custom.name, {duration = duration}) + end + AddFOWViewer( + caster:GetTeamNumber(), + target:GetAbsOrigin(), + vision, + visionDuration, + false + ) + EmitSoundOn("Hero_SkywrathMage.ConcussiveShot.Target", target) +end +skywrath_mage_concussive_shot_custom = __TS__Decorate( + skywrath_mage_concussive_shot_custom, + skywrath_mage_concussive_shot_custom, + {registerAbility(nil)}, + {kind = "class", name = "skywrath_mage_concussive_shot_custom"} +) +____exports.skywrath_mage_concussive_shot_custom = skywrath_mage_concussive_shot_custom +____exports.modifier_skywrath_mage_concussive_shot_custom = __TS__Class() +local modifier_skywrath_mage_concussive_shot_custom = ____exports.modifier_skywrath_mage_concussive_shot_custom +modifier_skywrath_mage_concussive_shot_custom.name = "modifier_skywrath_mage_concussive_shot_custom" +modifier_skywrath_mage_concussive_shot_custom.____file_path = "scripts/vscripts/abilities/heroes/skywrath_mage/skywrath_mage_concussive_shot_custom.lua" +__TS__ClassExtends(modifier_skywrath_mage_concussive_shot_custom, BaseModifier) +function modifier_skywrath_mage_concussive_shot_custom.prototype.____constructor(self, ...) + BaseModifier.prototype.____constructor(self, ...) + self.movementSlow = 0 +end +function modifier_skywrath_mage_concussive_shot_custom.prototype.IsHidden(self) + return false +end +function modifier_skywrath_mage_concussive_shot_custom.prototype.IsPurgable(self) + return true +end +function modifier_skywrath_mage_concussive_shot_custom.prototype.IsDebuff(self) + return true +end +function modifier_skywrath_mage_concussive_shot_custom.prototype.OnCreated(self) + local ability = self:GetAbility() + if not ability then + return + end + self.movementSlow = -math.abs(ability:GetSpecialValueFor("slow")) +end +function modifier_skywrath_mage_concussive_shot_custom.prototype.DeclareFunctions(self) + return {MODIFIER_PROPERTY_MOVESPEED_BONUS_PERCENTAGE} +end +function modifier_skywrath_mage_concussive_shot_custom.prototype.GetModifierMoveSpeedBonus_Percentage(self) + return self.movementSlow +end +modifier_skywrath_mage_concussive_shot_custom = __TS__Decorate( + modifier_skywrath_mage_concussive_shot_custom, + modifier_skywrath_mage_concussive_shot_custom, + {registerModifier(nil)}, + {kind = "class", name = "modifier_skywrath_mage_concussive_shot_custom"} +) +____exports.modifier_skywrath_mage_concussive_shot_custom = modifier_skywrath_mage_concussive_shot_custom +return ____exports diff --git a/scripts/vscripts/abilities/heroes/skywrath_mage/skywrath_mage_innate_custom.lua b/scripts/vscripts/abilities/heroes/skywrath_mage/skywrath_mage_innate_custom.lua new file mode 100644 index 0000000..6416a6e --- /dev/null +++ b/scripts/vscripts/abilities/heroes/skywrath_mage/skywrath_mage_innate_custom.lua @@ -0,0 +1,158 @@ +local ____lualib = require("lualib_bundle") +local __TS__Class = ____lualib.__TS__Class +local __TS__ClassExtends = ____lualib.__TS__ClassExtends +local __TS__Decorate = ____lualib.__TS__Decorate +local ____exports = {} +local ____dota_ts_adapter = require("lib.dota_ts_adapter") +local BaseAbility = ____dota_ts_adapter.BaseAbility +local BaseModifier = ____dota_ts_adapter.BaseModifier +local registerAbility = ____dota_ts_adapter.registerAbility +local registerModifier = ____dota_ts_adapter.registerModifier +____exports.skywrath_mage_innate_custom = __TS__Class() +local skywrath_mage_innate_custom = ____exports.skywrath_mage_innate_custom +skywrath_mage_innate_custom.name = "skywrath_mage_innate_custom" +skywrath_mage_innate_custom.____file_path = "scripts/vscripts/abilities/heroes/skywrath_mage/skywrath_mage_innate_custom.lua" +__TS__ClassExtends(skywrath_mage_innate_custom, BaseAbility) +function skywrath_mage_innate_custom.prototype.GetIntrinsicModifierName(self) + return ____exports.modifier_skywrath_mage_innate_custom.name +end +function skywrath_mage_innate_custom.prototype.Precache(self, context) + PrecacheResource("particle", "particles/units/heroes/hero_skywrath_mage/skywrath_mage_arcane_bolt.vpcf", context) +end +function skywrath_mage_innate_custom.prototype.OnProjectileHit_ExtraData(self, target, location, _extraData) + if not IsServer() then + return + end + if not target or not target:IsAlive() then + return + end + local caster = self:GetCaster() + local boltVision = self:GetSpecialValueFor("bolt_vision") + local visionDuration = self:GetSpecialValueFor("vision_duration") + local damage = _extraData.damage or 0 + if damage <= 0 then + return + end + ApplyDamage({ + victim = target, + attacker = caster, + damage = damage, + damage_type = DAMAGE_TYPE_MAGICAL, + ability = self + }) + AddFOWViewer( + caster:GetTeamNumber(), + location, + boltVision, + visionDuration, + false + ) + EmitSoundOn("Hero_SkywrathMage.ArcaneBolt.Impact", target) +end +skywrath_mage_innate_custom = __TS__Decorate( + skywrath_mage_innate_custom, + skywrath_mage_innate_custom, + {registerAbility(nil)}, + {kind = "class", name = "skywrath_mage_innate_custom"} +) +____exports.skywrath_mage_innate_custom = skywrath_mage_innate_custom +____exports.modifier_skywrath_mage_innate_custom = __TS__Class() +local modifier_skywrath_mage_innate_custom = ____exports.modifier_skywrath_mage_innate_custom +modifier_skywrath_mage_innate_custom.name = "modifier_skywrath_mage_innate_custom" +modifier_skywrath_mage_innate_custom.____file_path = "scripts/vscripts/abilities/heroes/skywrath_mage/skywrath_mage_innate_custom.lua" +__TS__ClassExtends(modifier_skywrath_mage_innate_custom, BaseModifier) +function modifier_skywrath_mage_innate_custom.prototype.IsHidden(self) + return true +end +function modifier_skywrath_mage_innate_custom.prototype.IsPurgable(self) + return false +end +function modifier_skywrath_mage_innate_custom.prototype.DeclareFunctions(self) + return {MODIFIER_EVENT_ON_ABILITY_FULLY_CAST} +end +function modifier_skywrath_mage_innate_custom.prototype.OnAbilityFullyCast(self, event) + if not IsServer() then + return + end + local parent = self:GetParent() + if not parent or parent:PassivesDisabled() then + return + end + if event.unit ~= parent then + return + end + if not event.ability or event.ability:IsItem() or event.ability:IsToggle() then + return + end + local innate = self:GetAbility() + if not innate then + return + end + if event.ability == innate then + return + end + local caster = parent + if not caster or caster:IsIllusion() then + return + end + local arcaneBolt = caster:FindAbilityByName("skywrath_mage_arcane_bolt_custom") + if not arcaneBolt or arcaneBolt:IsNull() then + return + end + local radius = innate:GetSpecialValueFor("search_radius") + local maxTargets = innate:GetSpecialValueFor("max_targets") + local speed = arcaneBolt:GetSpecialValueFor("bolt_speed") + local vision = innate:GetSpecialValueFor("bolt_vision") + local damagePct = innate:GetSpecialValueFor("damage_pct") / 100 + local boltDamage = arcaneBolt:GetSpecialValueFor("bolt_damage") + local intMultiplier = arcaneBolt:GetSpecialValueFor("int_multiplier") + local baseDamage = boltDamage + caster:GetIntellect(true) * intMultiplier + local finalDamage = baseDamage * damagePct + if finalDamage <= 0 then + return + end + local enemies = FindUnitsInRadius( + caster:GetTeamNumber(), + caster:GetAbsOrigin(), + nil, + radius, + DOTA_UNIT_TARGET_TEAM_ENEMY, + DOTA_UNIT_TARGET_HERO + DOTA_UNIT_TARGET_BASIC, + DOTA_UNIT_TARGET_FLAG_NO_INVIS + DOTA_UNIT_TARGET_FLAG_FOW_VISIBLE, + FIND_CLOSEST, + false + ) + local launched = 0 + for ____, enemy in ipairs(enemies) do + do + if not enemy or enemy:IsNull() or not enemy:IsAlive() then + goto __continue21 + end + if launched >= maxTargets then + break + end + ProjectileManager:CreateTrackingProjectile({ + Ability = innate, + EffectName = "particles/units/heroes/hero_skywrath_mage/skywrath_mage_arcane_bolt.vpcf", + Source = caster, + Target = enemy, + bDodgeable = false, + bProvidesVision = true, + iMoveSpeed = speed, + iVisionRadius = vision, + iVisionTeamNumber = caster:GetTeamNumber(), + ExtraData = {damage = finalDamage} + }) + launched = launched + 1 + end + ::__continue21:: + end +end +modifier_skywrath_mage_innate_custom = __TS__Decorate( + modifier_skywrath_mage_innate_custom, + modifier_skywrath_mage_innate_custom, + {registerModifier(nil)}, + {kind = "class", name = "modifier_skywrath_mage_innate_custom"} +) +____exports.modifier_skywrath_mage_innate_custom = modifier_skywrath_mage_innate_custom +return ____exports diff --git a/scripts/vscripts/abilities/heroes/skywrath_mage/skywrath_mage_mystic_flare_custom.lua b/scripts/vscripts/abilities/heroes/skywrath_mage/skywrath_mage_mystic_flare_custom.lua new file mode 100644 index 0000000..0d493f7 --- /dev/null +++ b/scripts/vscripts/abilities/heroes/skywrath_mage/skywrath_mage_mystic_flare_custom.lua @@ -0,0 +1,182 @@ +local ____lualib = require("lualib_bundle") +local __TS__Class = ____lualib.__TS__Class +local __TS__ClassExtends = ____lualib.__TS__ClassExtends +local __TS__Decorate = ____lualib.__TS__Decorate +local __TS__ArrayFilter = ____lualib.__TS__ArrayFilter +local ____exports = {} +local ____dota_ts_adapter = require("lib.dota_ts_adapter") +local BaseAbility = ____dota_ts_adapter.BaseAbility +local BaseModifier = ____dota_ts_adapter.BaseModifier +local registerAbility = ____dota_ts_adapter.registerAbility +local registerModifier = ____dota_ts_adapter.registerModifier +____exports.skywrath_mage_mystic_flare_custom = __TS__Class() +local skywrath_mage_mystic_flare_custom = ____exports.skywrath_mage_mystic_flare_custom +skywrath_mage_mystic_flare_custom.name = "skywrath_mage_mystic_flare_custom" +skywrath_mage_mystic_flare_custom.____file_path = "scripts/vscripts/abilities/heroes/skywrath_mage/skywrath_mage_mystic_flare_custom.lua" +__TS__ClassExtends(skywrath_mage_mystic_flare_custom, BaseAbility) +function skywrath_mage_mystic_flare_custom.prototype.Precache(self, context) + PrecacheResource("particle", "particles/units/heroes/hero_skywrath_mage/skywrath_mage_mystic_flare.vpcf", context) + PrecacheResource("particle", "particles/units/heroes/hero_skywrath_mage/skywrath_mage_mystic_flare_ambient.vpcf", context) +end +function skywrath_mage_mystic_flare_custom.prototype.GetAOERadius(self) + return self:GetSpecialValueFor("radius") +end +function skywrath_mage_mystic_flare_custom.prototype.OnSpellStart(self) + if not IsServer() then + return + end + local caster = self:GetCaster() + local point = self:GetCursorPosition() + local duration = self:GetSpecialValueFor("duration") + CreateModifierThinker( + caster, + self, + ____exports.modifier_skywrath_mage_mystic_flare_custom_thinker.name, + {duration = duration}, + point, + caster:GetTeamNumber(), + false + ) + EmitSoundOnLocationWithCaster(point, "Hero_SkywrathMage.MysticFlare.Cast", caster) +end +skywrath_mage_mystic_flare_custom = __TS__Decorate( + skywrath_mage_mystic_flare_custom, + skywrath_mage_mystic_flare_custom, + {registerAbility(nil)}, + {kind = "class", name = "skywrath_mage_mystic_flare_custom"} +) +____exports.skywrath_mage_mystic_flare_custom = skywrath_mage_mystic_flare_custom +____exports.modifier_skywrath_mage_mystic_flare_custom_thinker = __TS__Class() +local modifier_skywrath_mage_mystic_flare_custom_thinker = ____exports.modifier_skywrath_mage_mystic_flare_custom_thinker +modifier_skywrath_mage_mystic_flare_custom_thinker.name = "modifier_skywrath_mage_mystic_flare_custom_thinker" +modifier_skywrath_mage_mystic_flare_custom_thinker.____file_path = "scripts/vscripts/abilities/heroes/skywrath_mage/skywrath_mage_mystic_flare_custom.lua" +__TS__ClassExtends(modifier_skywrath_mage_mystic_flare_custom_thinker, BaseModifier) +function modifier_skywrath_mage_mystic_flare_custom_thinker.prototype.____constructor(self, ...) + BaseModifier.prototype.____constructor(self, ...) + self.radius = 170 + self.tickInterval = 0.1 + self.damagePerTick = 0 + self.duration = 2 + self.intMultiplier = 0 +end +function modifier_skywrath_mage_mystic_flare_custom_thinker.prototype.IsHidden(self) + return true +end +function modifier_skywrath_mage_mystic_flare_custom_thinker.prototype.IsPurgable(self) + return false +end +function modifier_skywrath_mage_mystic_flare_custom_thinker.prototype.OnCreated(self) + if not IsServer() then + return + end + local ability = self:GetAbility() + local parent = self:GetParent() + if not ability then + return + end + self.radius = ability:GetSpecialValueFor("radius") + local duration = math.max( + 0.1, + ability:GetSpecialValueFor("duration") + ) + self.duration = duration + local totalDamage = ability:GetSpecialValueFor("damage") + local caster = self:GetCaster() + self.intMultiplier = ability:GetSpecialValueFor("int_multiplier") + self.tickInterval = 0.1 + local totalDamageWithInt = totalDamage + (caster and caster:GetIntellect(true) * self.intMultiplier or 0) + self.damagePerTick = totalDamageWithInt / (duration / self.tickInterval) + local point = parent:GetAbsOrigin() + local flare = ParticleManager:CreateParticle("particles/units/heroes/hero_skywrath_mage/skywrath_mage_mystic_flare.vpcf", PATTACH_WORLDORIGIN, nil) + ParticleManager:SetParticleControl(flare, 0, point) + ParticleManager:SetParticleControl( + flare, + 1, + Vector(self.radius, self.duration, self.tickInterval) + ) + self:AddParticle( + flare, + false, + false, + -1, + false, + false + ) + local ambient = ParticleManager:CreateParticle("particles/units/heroes/hero_skywrath_mage/skywrath_mage_mystic_flare_ambient.vpcf", PATTACH_WORLDORIGIN, nil) + ParticleManager:SetParticleControl(ambient, 0, point) + ParticleManager:SetParticleControl( + ambient, + 1, + Vector(self.radius, self.duration, self.tickInterval) + ) + self:AddParticle( + ambient, + false, + false, + -1, + false, + false + ) + self:StartIntervalThink(self.tickInterval) +end +function modifier_skywrath_mage_mystic_flare_custom_thinker.prototype.OnIntervalThink(self) + if not IsServer() then + return + end + local caster = self:GetCaster() + local ability = self:GetAbility() + local parent = self:GetParent() + if not caster or not ability or not parent then + return + end + local enemies = FindUnitsInRadius( + caster:GetTeamNumber(), + parent:GetAbsOrigin(), + nil, + self.radius, + DOTA_UNIT_TARGET_TEAM_ENEMY, + DOTA_UNIT_TARGET_HERO + DOTA_UNIT_TARGET_BASIC, + DOTA_UNIT_TARGET_FLAG_NONE, + FIND_ANY_ORDER, + false + ) + local heroes = __TS__ArrayFilter( + enemies, + function(____, enemy) return enemy:IsRealHero() and not enemy:IsIllusion() and not enemy:IsTempestDouble() end + ) + local targets = #heroes > 0 and heroes or enemies + if #targets <= 0 then + return + end + for ____, target in ipairs(targets) do + ApplyDamage({ + victim = target, + attacker = caster, + damage = self.damagePerTick, + damage_type = DAMAGE_TYPE_MAGICAL, + ability = ability + }) + end +end +function modifier_skywrath_mage_mystic_flare_custom_thinker.prototype.OnDestroy(self) + if not IsServer() then + return + end + local caster = self:GetCaster() + if not caster then + return + end + EmitSoundOnLocationWithCaster( + self:GetParent():GetAbsOrigin(), + "Hero_SkywrathMage.MysticFlare.End", + caster + ) +end +modifier_skywrath_mage_mystic_flare_custom_thinker = __TS__Decorate( + modifier_skywrath_mage_mystic_flare_custom_thinker, + modifier_skywrath_mage_mystic_flare_custom_thinker, + {registerModifier(nil)}, + {kind = "class", name = "modifier_skywrath_mage_mystic_flare_custom_thinker"} +) +____exports.modifier_skywrath_mage_mystic_flare_custom_thinker = modifier_skywrath_mage_mystic_flare_custom_thinker +return ____exports diff --git a/scripts/vscripts/abilities/heroes/skywrath_mage/skywrath_mage_staff_of_the_scion_custom.lua b/scripts/vscripts/abilities/heroes/skywrath_mage/skywrath_mage_staff_of_the_scion_custom.lua new file mode 100644 index 0000000..743f4b5 --- /dev/null +++ b/scripts/vscripts/abilities/heroes/skywrath_mage/skywrath_mage_staff_of_the_scion_custom.lua @@ -0,0 +1,239 @@ +local ____lualib = require("lualib_bundle") +local __TS__Class = ____lualib.__TS__Class +local __TS__ClassExtends = ____lualib.__TS__ClassExtends +local __TS__Decorate = ____lualib.__TS__Decorate +local Map = ____lualib.Map +local __TS__New = ____lualib.__TS__New +local ____exports = {} +local ____dota_ts_adapter = require("lib.dota_ts_adapter") +local BaseAbility = ____dota_ts_adapter.BaseAbility +local BaseModifier = ____dota_ts_adapter.BaseModifier +local registerAbility = ____dota_ts_adapter.registerAbility +local registerModifier = ____dota_ts_adapter.registerModifier +local PROC_CHANCES = {50, 25, 12.5, 6.25} +____exports.skywrath_mage_staff_of_the_scion_custom = __TS__Class() +local skywrath_mage_staff_of_the_scion_custom = ____exports.skywrath_mage_staff_of_the_scion_custom +skywrath_mage_staff_of_the_scion_custom.name = "skywrath_mage_staff_of_the_scion_custom" +skywrath_mage_staff_of_the_scion_custom.____file_path = "scripts/vscripts/abilities/heroes/skywrath_mage/skywrath_mage_staff_of_the_scion_custom.lua" +__TS__ClassExtends(skywrath_mage_staff_of_the_scion_custom, BaseAbility) +function skywrath_mage_staff_of_the_scion_custom.prototype.GetIntrinsicModifierName(self) + return ____exports.modifier_skywrath_mage_staff_of_the_scion_custom.name +end +function skywrath_mage_staff_of_the_scion_custom.prototype.Precache(self, context) + PrecacheResource("particle", "particles/sky_cion_full.vpcf", context) + PrecacheResource("soundfile", "sounds/items/refresher.vsnd", context) +end +skywrath_mage_staff_of_the_scion_custom = __TS__Decorate( + skywrath_mage_staff_of_the_scion_custom, + skywrath_mage_staff_of_the_scion_custom, + {registerAbility(nil)}, + {kind = "class", name = "skywrath_mage_staff_of_the_scion_custom"} +) +____exports.skywrath_mage_staff_of_the_scion_custom = skywrath_mage_staff_of_the_scion_custom +____exports.modifier_skywrath_mage_staff_of_the_scion_custom = __TS__Class() +local modifier_skywrath_mage_staff_of_the_scion_custom = ____exports.modifier_skywrath_mage_staff_of_the_scion_custom +modifier_skywrath_mage_staff_of_the_scion_custom.name = "modifier_skywrath_mage_staff_of_the_scion_custom" +modifier_skywrath_mage_staff_of_the_scion_custom.____file_path = "scripts/vscripts/abilities/heroes/skywrath_mage/skywrath_mage_staff_of_the_scion_custom.lua" +__TS__ClassExtends(modifier_skywrath_mage_staff_of_the_scion_custom, BaseModifier) +function modifier_skywrath_mage_staff_of_the_scion_custom.prototype.____constructor(self, ...) + BaseModifier.prototype.____constructor(self, ...) + self.perAbilityState = __TS__New(Map) +end +function modifier_skywrath_mage_staff_of_the_scion_custom.prototype.getTrackerModifierName(self, abilityName) + repeat + local ____switch5 = abilityName + local ____cond5 = ____switch5 == "skywrath_mage_arcane_bolt_custom" + if ____cond5 then + return ____exports.modifier_skywrath_mage_staff_of_the_scion_arcane_tracker.name + end + ____cond5 = ____cond5 or ____switch5 == "skywrath_mage_concussive_shot_custom" + if ____cond5 then + return ____exports.modifier_skywrath_mage_staff_of_the_scion_concussive_tracker.name + end + ____cond5 = ____cond5 or ____switch5 == "skywrath_mage_ancient_seal_custom" + if ____cond5 then + return ____exports.modifier_skywrath_mage_staff_of_the_scion_seal_tracker.name + end + ____cond5 = ____cond5 or ____switch5 == "skywrath_mage_mystic_flare_custom" + if ____cond5 then + return ____exports.modifier_skywrath_mage_staff_of_the_scion_flare_tracker.name + end + do + return nil + end + until true +end +function modifier_skywrath_mage_staff_of_the_scion_custom.prototype.IsHidden(self) + return true +end +function modifier_skywrath_mage_staff_of_the_scion_custom.prototype.IsPurgable(self) + return false +end +function modifier_skywrath_mage_staff_of_the_scion_custom.prototype.DeclareFunctions(self) + return {MODIFIER_EVENT_ON_ABILITY_FULLY_CAST} +end +function modifier_skywrath_mage_staff_of_the_scion_custom.prototype.OnAbilityFullyCast(self, event) + if not IsServer() then + return + end + local caster = self:GetParent() + if not caster or event.unit ~= caster then + return + end + if caster:PassivesDisabled() then + return + end + local sourceAbility = event.ability + local holderAbility = self:GetAbility() + if not sourceAbility or not holderAbility then + return + end + if sourceAbility:IsItem() or sourceAbility:IsToggle() then + return + end + if sourceAbility == holderAbility then + return + end + if sourceAbility:GetAbilityName() == "skywrath_mage_innate_custom" then + return + end + local abilityName = sourceAbility:GetAbilityName() + local state = self.perAbilityState:get(abilityName) + if not state then + state = {chanceIndex = 0, successCount = 0, resetAt = 0} + self.perAbilityState:set(abilityName, state) + end + local now = GameRules:GetGameTime() + if state.resetAt > 0 and now >= state.resetAt then + state.chanceIndex = 0 + state.successCount = 0 + state.resetAt = 0 + local trackerName = self:getTrackerModifierName(abilityName) + if trackerName then + caster:RemoveModifierByNameAndCaster(trackerName, caster) + end + end + local chance = PROC_CHANCES[math.min(state.chanceIndex, #PROC_CHANCES - 1) + 1] + local roll = RandomFloat(0, 100) + if roll > chance then + return + end + local procFx = ParticleManager:CreateParticle("particles/sky_cion_full.vpcf", PATTACH_OVERHEAD_FOLLOW, caster) + ParticleManager:ReleaseParticleIndex(procFx) + EmitSoundOn("DOTA_Item.Refresher.Activate", caster) + local maxCharges = sourceAbility:GetMaxAbilityCharges(sourceAbility:GetLevel()) + if maxCharges > 0 then + local currentCharges = sourceAbility:GetCurrentAbilityCharges() + local nextCharges = math.min(currentCharges + 1, maxCharges) + sourceAbility:SetCurrentAbilityCharges(nextCharges) + if nextCharges >= maxCharges then + sourceAbility:EndCooldown() + end + else + sourceAbility:EndCooldown() + end + state.successCount = state.successCount + 1 + local resetDuration = holderAbility:GetSpecialValueFor("reset_duration") + state.resetAt = now + resetDuration + local trackerName = self:getTrackerModifierName(abilityName) + if trackerName then + local tracker = caster:AddNewModifier(caster, holderAbility, trackerName, {duration = resetDuration}) + if tracker ~= nil then + tracker:SetStackCount(state.successCount) + end + end + if state.successCount >= holderAbility:GetSpecialValueFor("max_successes") then + if trackerName then + caster:RemoveModifierByNameAndCaster(trackerName, caster) + end + state.chanceIndex = 0 + state.successCount = 0 + state.resetAt = 0 + return + end + state.chanceIndex = math.min(state.chanceIndex + 1, #PROC_CHANCES - 1) +end +modifier_skywrath_mage_staff_of_the_scion_custom = __TS__Decorate( + modifier_skywrath_mage_staff_of_the_scion_custom, + modifier_skywrath_mage_staff_of_the_scion_custom, + {registerModifier(nil)}, + {kind = "class", name = "modifier_skywrath_mage_staff_of_the_scion_custom"} +) +____exports.modifier_skywrath_mage_staff_of_the_scion_custom = modifier_skywrath_mage_staff_of_the_scion_custom +local modifier_skywrath_mage_staff_of_the_scion_tracker_base = __TS__Class() +modifier_skywrath_mage_staff_of_the_scion_tracker_base.name = "modifier_skywrath_mage_staff_of_the_scion_tracker_base" +modifier_skywrath_mage_staff_of_the_scion_tracker_base.____file_path = "scripts/vscripts/abilities/heroes/skywrath_mage/skywrath_mage_staff_of_the_scion_custom.lua" +__TS__ClassExtends(modifier_skywrath_mage_staff_of_the_scion_tracker_base, BaseModifier) +function modifier_skywrath_mage_staff_of_the_scion_tracker_base.prototype.IsHidden(self) + return false +end +function modifier_skywrath_mage_staff_of_the_scion_tracker_base.prototype.IsPurgable(self) + return false +end +function modifier_skywrath_mage_staff_of_the_scion_tracker_base.prototype.RemoveOnDeath(self) + return true +end +function modifier_skywrath_mage_staff_of_the_scion_tracker_base.prototype.GetTexture(self) + return "skywrath_mage_staff_of_the_scion" +end +____exports.modifier_skywrath_mage_staff_of_the_scion_arcane_tracker = __TS__Class() +local modifier_skywrath_mage_staff_of_the_scion_arcane_tracker = ____exports.modifier_skywrath_mage_staff_of_the_scion_arcane_tracker +modifier_skywrath_mage_staff_of_the_scion_arcane_tracker.name = "modifier_skywrath_mage_staff_of_the_scion_arcane_tracker" +modifier_skywrath_mage_staff_of_the_scion_arcane_tracker.____file_path = "scripts/vscripts/abilities/heroes/skywrath_mage/skywrath_mage_staff_of_the_scion_custom.lua" +__TS__ClassExtends(modifier_skywrath_mage_staff_of_the_scion_arcane_tracker, modifier_skywrath_mage_staff_of_the_scion_tracker_base) +function modifier_skywrath_mage_staff_of_the_scion_arcane_tracker.prototype.GetTexture(self) + return "skywrath_mage_arcane_bolt" +end +modifier_skywrath_mage_staff_of_the_scion_arcane_tracker = __TS__Decorate( + modifier_skywrath_mage_staff_of_the_scion_arcane_tracker, + modifier_skywrath_mage_staff_of_the_scion_arcane_tracker, + {registerModifier(nil)}, + {kind = "class", name = "modifier_skywrath_mage_staff_of_the_scion_arcane_tracker"} +) +____exports.modifier_skywrath_mage_staff_of_the_scion_arcane_tracker = modifier_skywrath_mage_staff_of_the_scion_arcane_tracker +____exports.modifier_skywrath_mage_staff_of_the_scion_concussive_tracker = __TS__Class() +local modifier_skywrath_mage_staff_of_the_scion_concussive_tracker = ____exports.modifier_skywrath_mage_staff_of_the_scion_concussive_tracker +modifier_skywrath_mage_staff_of_the_scion_concussive_tracker.name = "modifier_skywrath_mage_staff_of_the_scion_concussive_tracker" +modifier_skywrath_mage_staff_of_the_scion_concussive_tracker.____file_path = "scripts/vscripts/abilities/heroes/skywrath_mage/skywrath_mage_staff_of_the_scion_custom.lua" +__TS__ClassExtends(modifier_skywrath_mage_staff_of_the_scion_concussive_tracker, modifier_skywrath_mage_staff_of_the_scion_tracker_base) +function modifier_skywrath_mage_staff_of_the_scion_concussive_tracker.prototype.GetTexture(self) + return "skywrath_mage_concussive_shot" +end +modifier_skywrath_mage_staff_of_the_scion_concussive_tracker = __TS__Decorate( + modifier_skywrath_mage_staff_of_the_scion_concussive_tracker, + modifier_skywrath_mage_staff_of_the_scion_concussive_tracker, + {registerModifier(nil)}, + {kind = "class", name = "modifier_skywrath_mage_staff_of_the_scion_concussive_tracker"} +) +____exports.modifier_skywrath_mage_staff_of_the_scion_concussive_tracker = modifier_skywrath_mage_staff_of_the_scion_concussive_tracker +____exports.modifier_skywrath_mage_staff_of_the_scion_seal_tracker = __TS__Class() +local modifier_skywrath_mage_staff_of_the_scion_seal_tracker = ____exports.modifier_skywrath_mage_staff_of_the_scion_seal_tracker +modifier_skywrath_mage_staff_of_the_scion_seal_tracker.name = "modifier_skywrath_mage_staff_of_the_scion_seal_tracker" +modifier_skywrath_mage_staff_of_the_scion_seal_tracker.____file_path = "scripts/vscripts/abilities/heroes/skywrath_mage/skywrath_mage_staff_of_the_scion_custom.lua" +__TS__ClassExtends(modifier_skywrath_mage_staff_of_the_scion_seal_tracker, modifier_skywrath_mage_staff_of_the_scion_tracker_base) +function modifier_skywrath_mage_staff_of_the_scion_seal_tracker.prototype.GetTexture(self) + return "skywrath_mage_ancient_seal" +end +modifier_skywrath_mage_staff_of_the_scion_seal_tracker = __TS__Decorate( + modifier_skywrath_mage_staff_of_the_scion_seal_tracker, + modifier_skywrath_mage_staff_of_the_scion_seal_tracker, + {registerModifier(nil)}, + {kind = "class", name = "modifier_skywrath_mage_staff_of_the_scion_seal_tracker"} +) +____exports.modifier_skywrath_mage_staff_of_the_scion_seal_tracker = modifier_skywrath_mage_staff_of_the_scion_seal_tracker +____exports.modifier_skywrath_mage_staff_of_the_scion_flare_tracker = __TS__Class() +local modifier_skywrath_mage_staff_of_the_scion_flare_tracker = ____exports.modifier_skywrath_mage_staff_of_the_scion_flare_tracker +modifier_skywrath_mage_staff_of_the_scion_flare_tracker.name = "modifier_skywrath_mage_staff_of_the_scion_flare_tracker" +modifier_skywrath_mage_staff_of_the_scion_flare_tracker.____file_path = "scripts/vscripts/abilities/heroes/skywrath_mage/skywrath_mage_staff_of_the_scion_custom.lua" +__TS__ClassExtends(modifier_skywrath_mage_staff_of_the_scion_flare_tracker, modifier_skywrath_mage_staff_of_the_scion_tracker_base) +function modifier_skywrath_mage_staff_of_the_scion_flare_tracker.prototype.GetTexture(self) + return "skywrath_mage_mystic_flare" +end +modifier_skywrath_mage_staff_of_the_scion_flare_tracker = __TS__Decorate( + modifier_skywrath_mage_staff_of_the_scion_flare_tracker, + modifier_skywrath_mage_staff_of_the_scion_flare_tracker, + {registerModifier(nil)}, + {kind = "class", name = "modifier_skywrath_mage_staff_of_the_scion_flare_tracker"} +) +____exports.modifier_skywrath_mage_staff_of_the_scion_flare_tracker = modifier_skywrath_mage_staff_of_the_scion_flare_tracker +return ____exports diff --git a/scripts/vscripts/abilities/heroes/smaug/ability_dragon_fear_aura.lua b/scripts/vscripts/abilities/heroes/smaug/ability_dragon_fear_aura.lua new file mode 100644 index 0000000..a09f230 --- /dev/null +++ b/scripts/vscripts/abilities/heroes/smaug/ability_dragon_fear_aura.lua @@ -0,0 +1,199 @@ +local ____lualib = require("lualib_bundle") +local __TS__Class = ____lualib.__TS__Class +local __TS__ClassExtends = ____lualib.__TS__ClassExtends +local __TS__Decorate = ____lualib.__TS__Decorate +local ____exports = {} +local ____dota_ts_adapter = require("lib.dota_ts_adapter") +local BaseAbility = ____dota_ts_adapter.BaseAbility +local BaseModifier = ____dota_ts_adapter.BaseModifier +local registerAbility = ____dota_ts_adapter.registerAbility +local registerModifier = ____dota_ts_adapter.registerModifier +____exports.ability_dragon_fear_aura = __TS__Class() +local ability_dragon_fear_aura = ____exports.ability_dragon_fear_aura +ability_dragon_fear_aura.name = "ability_dragon_fear_aura" +ability_dragon_fear_aura.____file_path = "scripts/vscripts/abilities/heroes/smaug/ability_dragon_fear_aura.lua" +__TS__ClassExtends(ability_dragon_fear_aura, BaseAbility) +function ability_dragon_fear_aura.prototype.GetIntrinsicModifierName(self) + return ____exports.modifier_dragon_fear_aura.name +end +ability_dragon_fear_aura = __TS__Decorate( + ability_dragon_fear_aura, + ability_dragon_fear_aura, + {registerAbility(nil)}, + {kind = "class", name = "ability_dragon_fear_aura"} +) +____exports.ability_dragon_fear_aura = ability_dragon_fear_aura +____exports.modifier_dragon_fear_aura = __TS__Class() +local modifier_dragon_fear_aura = ____exports.modifier_dragon_fear_aura +modifier_dragon_fear_aura.name = "modifier_dragon_fear_aura" +modifier_dragon_fear_aura.____file_path = "scripts/vscripts/abilities/heroes/smaug/ability_dragon_fear_aura.lua" +__TS__ClassExtends(modifier_dragon_fear_aura, BaseModifier) +function modifier_dragon_fear_aura.prototype.____constructor(self, ...) + BaseModifier.prototype.____constructor(self, ...) + self.spellAmp = 0 +end +function modifier_dragon_fear_aura.prototype.IsHidden(self) + return true +end +function modifier_dragon_fear_aura.prototype.IsPurgable(self) + return false +end +function modifier_dragon_fear_aura.prototype.OnCreated(self) + local ability = self:GetAbility() + if not ability then + return + end + self.spellAmp = ability:GetSpecialValueFor("spell_amp") +end +function modifier_dragon_fear_aura.prototype.OnRefresh(self) + local ability = self:GetAbility() + if not ability then + return + end + self.spellAmp = ability:GetSpecialValueFor("spell_amp") +end +function modifier_dragon_fear_aura.prototype.DeclareFunctions(self) + return {MODIFIER_EVENT_ON_ATTACK_START, MODIFIER_PROPERTY_SPELL_AMPLIFY_PERCENTAGE} +end +function modifier_dragon_fear_aura.prototype.OnAttackStart(self, event) + if not IsServer() then + return + end + if event.target ~= self:GetParent() then + return + end + if event.attacker:IsMagicImmune() then + return + end + event.attacker:AddNewModifier( + self:GetParent(), + self:GetAbility(), + ____exports.modifier_dragon_fear_aura_debuff.name, + {} + ) +end +function modifier_dragon_fear_aura.prototype.GetModifierSpellAmplify_Percentage(self) + return self.spellAmp +end +modifier_dragon_fear_aura = __TS__Decorate( + modifier_dragon_fear_aura, + modifier_dragon_fear_aura, + {registerModifier(nil)}, + {kind = "class", name = "modifier_dragon_fear_aura"} +) +____exports.modifier_dragon_fear_aura = modifier_dragon_fear_aura +____exports.modifier_dragon_fear_aura_debuff = __TS__Class() +local modifier_dragon_fear_aura_debuff = ____exports.modifier_dragon_fear_aura_debuff +modifier_dragon_fear_aura_debuff.name = "modifier_dragon_fear_aura_debuff" +modifier_dragon_fear_aura_debuff.____file_path = "scripts/vscripts/abilities/heroes/smaug/ability_dragon_fear_aura.lua" +__TS__ClassExtends(modifier_dragon_fear_aura_debuff, BaseModifier) +function modifier_dragon_fear_aura_debuff.prototype.____constructor(self, ...) + BaseModifier.prototype.____constructor(self, ...) + self.outgoing = 0 + self.duration = 0 + self.incoming = 0 + self.attackOther = false + self.hasAttacked = false +end +function modifier_dragon_fear_aura_debuff.prototype.IsHidden(self) + return false +end +function modifier_dragon_fear_aura_debuff.prototype.IsDebuff(self) + return true +end +function modifier_dragon_fear_aura_debuff.prototype.IsStunDebuff(self) + return false +end +function modifier_dragon_fear_aura_debuff.prototype.IsPurgable(self) + return true +end +function modifier_dragon_fear_aura_debuff.prototype.OnCreated(self) + local ability = self:GetAbility() + if not ability then + return + end + self.outgoing = ability:GetSpecialValueFor("outgoing") + self.duration = ability:GetSpecialValueFor("duration") + self.incoming = ability:GetSpecialValueFor("incoming") +end +function modifier_dragon_fear_aura_debuff.prototype.OnRefresh(self) + local ability = self:GetAbility() + if not ability then + return + end + self.outgoing = ability:GetSpecialValueFor("outgoing") + self.duration = ability:GetSpecialValueFor("duration") + self.incoming = ability:GetSpecialValueFor("incoming") +end +function modifier_dragon_fear_aura_debuff.prototype.DeclareFunctions(self) + return {MODIFIER_PROPERTY_PRE_ATTACK, MODIFIER_EVENT_ON_ATTACK, MODIFIER_PROPERTY_DAMAGEOUTGOING_PERCENTAGE, MODIFIER_PROPERTY_INCOMING_DAMAGE_PERCENTAGE} +end +function modifier_dragon_fear_aura_debuff.prototype.GetModifierPreAttack(self, event) + if not IsServer() then + return 0 + end + self.record = event.record + self.attackOther = true + local effectCast1 = ParticleManager:CreateParticle( + "particles/econ/items/grimstroke/gs_fall20_immortal/gs_fall20_immortal_soul_debuff.vpcf", + PATTACH_ABSORIGIN_FOLLOW, + self:GetParent() + ) + ParticleManager:SetParticleControlEnt( + effectCast1, + 2, + self:GetParent(), + PATTACH_ABSORIGIN_FOLLOW, + "", + self:GetParent():GetAbsOrigin(), + true + ) + self:AddParticle( + effectCast1, + false, + false, + -1, + false, + false + ) + if self.primary then + local effectCast2 = ParticleManager:CreateParticle( + "particles/econ/items/grimstroke/gs_fall20_immortal/gs_fall20_immortal_soul_dragon_model.vpcf", + PATTACH_OVERHEAD_FOLLOW, + self:GetParent() + ) + self:AddParticle( + effectCast2, + false, + false, + -1, + false, + true + ) + end + return 0 +end +function modifier_dragon_fear_aura_debuff.prototype.OnAttack(self, event) + if not IsServer() then + return + end + if event.record ~= self.record then + return + end + self:SetDuration(self.duration, true) + self.hasAttacked = true +end +function modifier_dragon_fear_aura_debuff.prototype.GetModifierDamageOutgoing_Percentage(self) + return self.outgoing +end +function modifier_dragon_fear_aura_debuff.prototype.GetModifierIncomingDamage_Percentage(self) + return self.incoming +end +modifier_dragon_fear_aura_debuff = __TS__Decorate( + modifier_dragon_fear_aura_debuff, + modifier_dragon_fear_aura_debuff, + {registerModifier(nil)}, + {kind = "class", name = "modifier_dragon_fear_aura_debuff"} +) +____exports.modifier_dragon_fear_aura_debuff = modifier_dragon_fear_aura_debuff +return ____exports diff --git a/scripts/vscripts/abilities/heroes/smaug/ability_dragon_gold_deal.lua b/scripts/vscripts/abilities/heroes/smaug/ability_dragon_gold_deal.lua new file mode 100644 index 0000000..7e5a844 --- /dev/null +++ b/scripts/vscripts/abilities/heroes/smaug/ability_dragon_gold_deal.lua @@ -0,0 +1,116 @@ +local ____lualib = require("lualib_bundle") +local __TS__Class = ____lualib.__TS__Class +local __TS__ClassExtends = ____lualib.__TS__ClassExtends +local __TS__Decorate = ____lualib.__TS__Decorate +local ____exports = {} +local ____dota_ts_adapter = require("lib.dota_ts_adapter") +local BaseAbility = ____dota_ts_adapter.BaseAbility +local BaseModifier = ____dota_ts_adapter.BaseModifier +local registerAbility = ____dota_ts_adapter.registerAbility +local registerModifier = ____dota_ts_adapter.registerModifier +____exports.ability_dragon_gold_deal = __TS__Class() +local ability_dragon_gold_deal = ____exports.ability_dragon_gold_deal +ability_dragon_gold_deal.name = "ability_dragon_gold_deal" +ability_dragon_gold_deal.____file_path = "scripts/vscripts/abilities/heroes/smaug/ability_dragon_gold_deal.lua" +__TS__ClassExtends(ability_dragon_gold_deal, BaseAbility) +function ability_dragon_gold_deal.prototype.GetIntrinsicModifierName(self) + return ____exports.modifier_dragon_gold_deal_buff.name +end +ability_dragon_gold_deal = __TS__Decorate( + ability_dragon_gold_deal, + ability_dragon_gold_deal, + {registerAbility(nil)}, + {kind = "class", name = "ability_dragon_gold_deal"} +) +____exports.ability_dragon_gold_deal = ability_dragon_gold_deal +____exports.modifier_dragon_gold_deal_buff = __TS__Class() +local modifier_dragon_gold_deal_buff = ____exports.modifier_dragon_gold_deal_buff +modifier_dragon_gold_deal_buff.name = "modifier_dragon_gold_deal_buff" +modifier_dragon_gold_deal_buff.____file_path = "scripts/vscripts/abilities/heroes/smaug/ability_dragon_gold_deal.lua" +__TS__ClassExtends(modifier_dragon_gold_deal_buff, BaseModifier) +function modifier_dragon_gold_deal_buff.prototype.____constructor(self, ...) + BaseModifier.prototype.____constructor(self, ...) + self.healthBonus = 0 + self.spellAmp = 0 + self.physicalArmor = 0 +end +function modifier_dragon_gold_deal_buff.prototype.IsHidden(self) + return true +end +function modifier_dragon_gold_deal_buff.prototype.IsPurgable(self) + return false +end +function modifier_dragon_gold_deal_buff.prototype.IsBuff(self) + return true +end +function modifier_dragon_gold_deal_buff.prototype.DeclareFunctions(self) + return {MODIFIER_PROPERTY_PHYSICAL_ARMOR_BONUS, MODIFIER_PROPERTY_SPELL_AMPLIFY_PERCENTAGE, MODIFIER_PROPERTY_HEALTH_BONUS} +end +function modifier_dragon_gold_deal_buff.prototype.OnCreated(self) + local ability = self:GetAbility() + if not ability then + return + end + self.healthBonus = ability:GetSpecialValueFor("health_bonus") + self.spellAmp = ability:GetSpecialValueFor("spell_amp") + self.physicalArmor = ability:GetSpecialValueFor("physical_armor") + self:OnIntervalThink() + self:StartIntervalThink(0.1) +end +function modifier_dragon_gold_deal_buff.prototype.OnRefresh(self) + self:OnCreated() +end +function modifier_dragon_gold_deal_buff.prototype.OnIntervalThink(self) + if not IsServer() then + return + end + local ability = self:GetAbility() + local caster = self:GetCaster() + if not ability or not caster then + return + end + local gold = ability:GetSpecialValueFor("gold") + local price = 0 + do + local i = 0 + while i <= 5 do + local item = caster:GetItemInSlot(i) + if item then + local itemPrice = item:GetCost() + price = price + itemPrice + end + i = i + 1 + end + end + local stack = price / gold + self:SetStackCount(stack) + if caster:IsRealHero() then + caster:CalculateStatBonus(true) + end +end +function modifier_dragon_gold_deal_buff.prototype.GetModifierSpellAmplify_Percentage(self) + if self:GetParent():PassivesDisabled() then + return 0 + end + return self.spellAmp * self:GetStackCount() +end +function modifier_dragon_gold_deal_buff.prototype.GetModifierHealthBonus(self) + if self:GetParent():PassivesDisabled() then + return 0 + end + return self.healthBonus * self:GetStackCount() +end +function modifier_dragon_gold_deal_buff.prototype.GetModifierPhysicalArmorBonus(self) + if self:GetParent():PassivesDisabled() then + return 0 + end + return self.physicalArmor * self:GetStackCount() +end +modifier_dragon_gold_deal_buff = __TS__Decorate( + modifier_dragon_gold_deal_buff, + modifier_dragon_gold_deal_buff, + {registerModifier(nil)}, + {kind = "class", name = "modifier_dragon_gold_deal_buff"} +) +____exports.modifier_dragon_gold_deal_buff = modifier_dragon_gold_deal_buff +return ____exports diff --git a/scripts/vscripts/abilities/heroes/smaug/ability_dragon_reward.lua b/scripts/vscripts/abilities/heroes/smaug/ability_dragon_reward.lua new file mode 100644 index 0000000..9790f68 --- /dev/null +++ b/scripts/vscripts/abilities/heroes/smaug/ability_dragon_reward.lua @@ -0,0 +1,140 @@ +local ____lualib = require("lualib_bundle") +local __TS__Class = ____lualib.__TS__Class +local __TS__ClassExtends = ____lualib.__TS__ClassExtends +local __TS__Decorate = ____lualib.__TS__Decorate +local ____exports = {} +local ____dota_ts_adapter = require("lib.dota_ts_adapter") +local BaseAbility = ____dota_ts_adapter.BaseAbility +local BaseModifier = ____dota_ts_adapter.BaseModifier +local registerAbility = ____dota_ts_adapter.registerAbility +local registerModifier = ____dota_ts_adapter.registerModifier +____exports.ability_dragon_reward = __TS__Class() +local ability_dragon_reward = ____exports.ability_dragon_reward +ability_dragon_reward.name = "ability_dragon_reward" +ability_dragon_reward.____file_path = "scripts/vscripts/abilities/heroes/smaug/ability_dragon_reward.lua" +__TS__ClassExtends(ability_dragon_reward, BaseAbility) +function ability_dragon_reward.prototype.GetIntrinsicModifierName(self) + return ____exports.modifier_dragon_reward.name +end +ability_dragon_reward = __TS__Decorate( + ability_dragon_reward, + ability_dragon_reward, + {registerAbility(nil)}, + {kind = "class", name = "ability_dragon_reward"} +) +____exports.ability_dragon_reward = ability_dragon_reward +____exports.modifier_dragon_reward = __TS__Class() +local modifier_dragon_reward = ____exports.modifier_dragon_reward +modifier_dragon_reward.name = "modifier_dragon_reward" +modifier_dragon_reward.____file_path = "scripts/vscripts/abilities/heroes/smaug/ability_dragon_reward.lua" +__TS__ClassExtends(modifier_dragon_reward, BaseModifier) +function modifier_dragon_reward.prototype.____constructor(self, ...) + BaseModifier.prototype.____constructor(self, ...) + self.max = 0 + self.bonusAttributes = 0 +end +function modifier_dragon_reward.prototype.IsHidden(self) + return false +end +function modifier_dragon_reward.prototype.IsPurgable(self) + return false +end +function modifier_dragon_reward.prototype.IsDebuff(self) + return false +end +function modifier_dragon_reward.prototype.RemoveOnDeath(self) + return false +end +function modifier_dragon_reward.prototype.DeclareFunctions(self) + return {MODIFIER_EVENT_ON_DEATH, MODIFIER_PROPERTY_STATS_STRENGTH_BONUS, MODIFIER_PROPERTY_STATS_AGILITY_BONUS, MODIFIER_PROPERTY_STATS_INTELLECT_BONUS} +end +function modifier_dragon_reward.prototype.OnCreated(self) + local ability = self:GetAbility() + if not ability then + return + end + self.max = ability:GetSpecialValueFor("max") + self.bonusAttributes = ability:GetSpecialValueFor("bonus_attributes") + if IsServer() then + self:SetStackCount(0) + end +end +function modifier_dragon_reward.prototype.OnRefresh(self) + local ability = self:GetAbility() + if not ability then + return + end + self.max = ability:GetSpecialValueFor("max") + self.bonusAttributes = ability:GetSpecialValueFor("bonus_attributes") +end +function modifier_dragon_reward.prototype.OnDeath(self, event) + if not IsServer() then + return + end + self:DeathLogic(event) + self:KillLogic(event) +end +function modifier_dragon_reward.prototype.GetModifierBonusStats_Strength(self) + return self:GetStackCount() * self.bonusAttributes +end +function modifier_dragon_reward.prototype.GetModifierBonusStats_Agility(self) + return self:GetStackCount() * self.bonusAttributes +end +function modifier_dragon_reward.prototype.GetModifierBonusStats_Intellect(self) + return self:GetStackCount() * self.bonusAttributes +end +function modifier_dragon_reward.prototype.DeathLogic(self, event) + if not IsServer() then + return + end + local unit = event.unit + if unit == self:GetParent() then + local afterDeath = math.floor(self:GetStackCount()) + end +end +function modifier_dragon_reward.prototype.KillLogic(self, event) + if not IsServer() then + return + end + local target = event.unit + local attacker = event.attacker + local parent = self:GetParent() + if attacker == parent and target and attacker:IsAlive() and not target:IsIllusion() and not target:IsBuilding() and not parent:PassivesDisabled() then + self:AddStack(1) + self:PlayEffects(target) + end +end +function modifier_dragon_reward.prototype.AddStack(self, value) + local current = self:GetStackCount() + local after = current + value + if after > self.max then + after = self.max + end + self:SetStackCount(after) +end +function modifier_dragon_reward.prototype.PlayEffects(self, target) + if not IsServer() then + return + end + local projectileName = "particles/econ/items/legion/legion_fallen/legion_fallen_press_buff.vpcf" + local info = { + Target = self:GetParent(), + Source = target, + EffectName = projectileName, + iMoveSpeed = 400, + vSourceLoc = target:GetAbsOrigin(), + bDodgeable = false, + bReplaceExisting = false, + flExpireTime = GameRules:GetGameTime() + 5, + bProvidesVision = false + } + ProjectileManager:CreateTrackingProjectile(info) +end +modifier_dragon_reward = __TS__Decorate( + modifier_dragon_reward, + modifier_dragon_reward, + {registerModifier(nil)}, + {kind = "class", name = "modifier_dragon_reward"} +) +____exports.modifier_dragon_reward = modifier_dragon_reward +return ____exports diff --git a/scripts/vscripts/abilities/heroes/smaug/ability_dragon_scales.lua b/scripts/vscripts/abilities/heroes/smaug/ability_dragon_scales.lua new file mode 100644 index 0000000..28795c1 --- /dev/null +++ b/scripts/vscripts/abilities/heroes/smaug/ability_dragon_scales.lua @@ -0,0 +1,127 @@ +local ____lualib = require("lualib_bundle") +local __TS__Class = ____lualib.__TS__Class +local __TS__ClassExtends = ____lualib.__TS__ClassExtends +local __TS__Decorate = ____lualib.__TS__Decorate +local ____exports = {} +local ____dota_ts_adapter = require("lib.dota_ts_adapter") +local BaseAbility = ____dota_ts_adapter.BaseAbility +local BaseModifier = ____dota_ts_adapter.BaseModifier +local registerAbility = ____dota_ts_adapter.registerAbility +local registerModifier = ____dota_ts_adapter.registerModifier +____exports.ability_dragon_scales = __TS__Class() +local ability_dragon_scales = ____exports.ability_dragon_scales +ability_dragon_scales.name = "ability_dragon_scales" +ability_dragon_scales.____file_path = "scripts/vscripts/abilities/heroes/smaug/ability_dragon_scales.lua" +__TS__ClassExtends(ability_dragon_scales, BaseAbility) +function ability_dragon_scales.prototype.OnSpellStart(self) + if not IsServer() then + return + end + self:GetCaster():AddNewModifier( + self:GetCaster(), + self, + ____exports.modifier_dragon_scales.name, + {duration = self:GetSpecialValueFor("duration")} + ) +end +ability_dragon_scales = __TS__Decorate( + ability_dragon_scales, + ability_dragon_scales, + {registerAbility(nil)}, + {kind = "class", name = "ability_dragon_scales"} +) +____exports.ability_dragon_scales = ability_dragon_scales +____exports.modifier_dragon_scales = __TS__Class() +local modifier_dragon_scales = ____exports.modifier_dragon_scales +modifier_dragon_scales.name = "modifier_dragon_scales" +modifier_dragon_scales.____file_path = "scripts/vscripts/abilities/heroes/smaug/ability_dragon_scales.lua" +__TS__ClassExtends(modifier_dragon_scales, BaseModifier) +function modifier_dragon_scales.prototype.IsHidden(self) + return false +end +function modifier_dragon_scales.prototype.IsPurgable(self) + return false +end +function modifier_dragon_scales.prototype.IsDebuff(self) + return true +end +function modifier_dragon_scales.prototype.IsBuff(self) + return true +end +function modifier_dragon_scales.prototype.RemoveOnDeath(self) + return false +end +function modifier_dragon_scales.prototype.DeclareFunctions(self) + return {MODIFIER_EVENT_ON_TAKEDAMAGE, MODIFIER_PROPERTY_INCOMING_DAMAGE_PERCENTAGE} +end +function modifier_dragon_scales.prototype.CheckState(self) + return {[MODIFIER_STATE_STUNNED] = true, [MODIFIER_STATE_MUTED] = true, [MODIFIER_STATE_SILENCED] = true} +end +function modifier_dragon_scales.prototype.OnTakeDamage(self, event) + if not IsServer() then + return + end + local target = event.unit + local caster = self:GetParent() + local originalDamage = event.original_damage + local damageType = event.damage_type + if target == caster and not caster:PassivesDisabled() then + local ability = self:GetAbility() + if not ability then + return + end + local reflect = ability:GetSpecialValueFor("reflect") + local minRadius = ability:GetSpecialValueFor("min_radius") + local maxRadius = ability:GetSpecialValueFor("max_radius") + local all = FindUnitsInRadius( + target:GetTeamNumber(), + caster:GetAbsOrigin(), + nil, + maxRadius, + DOTA_UNIT_TARGET_TEAM_ENEMY, + bit.bor(DOTA_UNIT_TARGET_HERO, DOTA_UNIT_TARGET_BASIC), + DOTA_UNIT_TARGET_FLAG_MAGIC_IMMUNE_ENEMIES, + FIND_ANY_ORDER, + false + ) + for ____, hero in ipairs(all) do + local distance = (caster:GetAbsOrigin() - hero:GetAbsOrigin()):Length2D() + local dif = distance - 300 + local reflectPercent = distance <= minRadius and reflect or reflect - 0.0175 * dif + local damage = originalDamage / 100 * reflectPercent + ApplyDamage({ + victim = hero, + attacker = caster, + damage = damage, + damage_type = damageType, + damage_flags = DOTA_DAMAGE_FLAG_HPLOSS, + ability = ability + }) + end + end +end +function modifier_dragon_scales.prototype.GetModifierIncomingDamage_Percentage(self) + local ability = self:GetAbility() + if not ability then + return 0 + end + local invul = ability:GetSpecialValueFor("invul") + if invul ~= nil and invul ~= nil then + return invul + end + return ability:GetSpecialValueFor("reflect") +end +function modifier_dragon_scales.prototype.GetEffectName(self) + return "particles/econ/items/medusa/medusa_daughters/medusa_daughters_mana_shield.vpcf" +end +function modifier_dragon_scales.prototype.GetEffectAttachType(self) + return PATTACH_ABSORIGIN_FOLLOW +end +modifier_dragon_scales = __TS__Decorate( + modifier_dragon_scales, + modifier_dragon_scales, + {registerModifier(nil)}, + {kind = "class", name = "modifier_dragon_scales"} +) +____exports.modifier_dragon_scales = modifier_dragon_scales +return ____exports diff --git a/scripts/vscripts/abilities/heroes/smaug/ability_elder_dragon_form.lua b/scripts/vscripts/abilities/heroes/smaug/ability_elder_dragon_form.lua new file mode 100644 index 0000000..2521bce --- /dev/null +++ b/scripts/vscripts/abilities/heroes/smaug/ability_elder_dragon_form.lua @@ -0,0 +1,431 @@ +local ____lualib = require("lualib_bundle") +local __TS__Class = ____lualib.__TS__Class +local __TS__ClassExtends = ____lualib.__TS__ClassExtends +local __TS__Decorate = ____lualib.__TS__Decorate +local ____exports = {} +local ____dota_ts_adapter = require("lib.dota_ts_adapter") +local BaseAbility = ____dota_ts_adapter.BaseAbility +local BaseModifier = ____dota_ts_adapter.BaseModifier +local registerAbility = ____dota_ts_adapter.registerAbility +local registerModifier = ____dota_ts_adapter.registerModifier +____exports.ability_elder_dragon_form = __TS__Class() +local ability_elder_dragon_form = ____exports.ability_elder_dragon_form +ability_elder_dragon_form.name = "ability_elder_dragon_form" +ability_elder_dragon_form.____file_path = "scripts/vscripts/abilities/heroes/smaug/ability_elder_dragon_form.lua" +__TS__ClassExtends(ability_elder_dragon_form, BaseAbility) +function ability_elder_dragon_form.prototype.GetIntrinsicModifierName(self) + return ____exports.modifier_ability_elder_dragon_form.name +end +ability_elder_dragon_form = __TS__Decorate( + ability_elder_dragon_form, + ability_elder_dragon_form, + {registerAbility(nil)}, + {kind = "class", name = "ability_elder_dragon_form"} +) +____exports.ability_elder_dragon_form = ability_elder_dragon_form +____exports.modifier_ability_elder_dragon_form = __TS__Class() +local modifier_ability_elder_dragon_form = ____exports.modifier_ability_elder_dragon_form +modifier_ability_elder_dragon_form.name = "modifier_ability_elder_dragon_form" +modifier_ability_elder_dragon_form.____file_path = "scripts/vscripts/abilities/heroes/smaug/ability_elder_dragon_form.lua" +__TS__ClassExtends(modifier_ability_elder_dragon_form, BaseModifier) +function modifier_ability_elder_dragon_form.prototype.____constructor(self, ...) + BaseModifier.prototype.____constructor(self, ...) + self.level = 1 + self.bonusMs = 0 + self.bonusDamage = 0 + self.bonusRange = 0 + self.magicResist = 0 + self.corrosiveDuration = 0 + self.splashRadius = 0 + self.splashPct = 0 + self.frostRadius = 0 + self.frostDuration = 0 + self.scale = 0 +end +function modifier_ability_elder_dragon_form.prototype.IsHidden(self) + return false +end +function modifier_ability_elder_dragon_form.prototype.IsDebuff(self) + return false +end +function modifier_ability_elder_dragon_form.prototype.IsPurgable(self) + return false +end +function modifier_ability_elder_dragon_form.prototype.OnCreated(self) + self.parent = self:GetParent() + self.level = 1 + local ability = self:GetAbility() + if ability then + ability:SetLevel(1) + end + if self.parent:HasScepter() then + self.level = self.level + 1 + end + if not ability then + return + end + self.bonusMs = ability:GetSpecialValueFor("bonus_movement_speed") + self.bonusDamage = ability:GetSpecialValueFor("bonus_attack_damage") + self.bonusRange = ability:GetSpecialValueFor("bonus_attack_range") + self.magicResist = 0 + self.corrosiveDuration = ability:GetSpecialValueFor("corrosive_breath_duration") + self.splashRadius = ability:GetSpecialValueFor("splash_radius") + self.splashPct = ability:GetSpecialValueFor("splash_damage_percent") / 100 + self.frostRadius = ability:GetSpecialValueFor("frost_aoe") + self.frostDuration = ability:GetSpecialValueFor("frost_duration") + if self.level == 4 then + self.bonusRange = self.bonusRange + 100 + self.splashPct = self.splashPct * 1.5 + self.magicResist = 30 + end + if not IsServer() or not self.parent then + return + end + self.parent:SetAttackCapability(DOTA_UNIT_CAP_RANGED_ATTACK) + self:StartIntervalThink(0.03) + local effectData = ____exports.modifier_ability_elder_dragon_form.effectData[self.level] + self.projectile = effectData.projectile + self.attackSound = effectData.attack_sound + self.scale = effectData.scale + self:PlayEffects() + EmitSoundOn("Hero_DragonKnight.ElderDragonForm", self.parent) +end +function modifier_ability_elder_dragon_form.prototype.innateStatsActive(self) + local p = self.parent or self:GetParent() + return p ~= nil and not p:PassivesDisabled() +end +function modifier_ability_elder_dragon_form.prototype.OnRefresh(self) + self:OnCreated() +end +function modifier_ability_elder_dragon_form.prototype.OnDestroy(self) + if not IsServer() or not self.parent then + return + end + self.parent:SetAttackCapability(DOTA_UNIT_CAP_MELEE_ATTACK) + self:PlayEffects() + EmitSoundOn("Hero_DragonKnight.ElderDragonForm.Revert", self.parent) +end +function modifier_ability_elder_dragon_form.prototype.OnIntervalThink(self) + if not self.parent then + return + end + self.parent:SetSkin(self.level - 1) +end +function modifier_ability_elder_dragon_form.prototype.DeclareFunctions(self) + return { + MODIFIER_PROPERTY_BASEATTACK_BONUSDAMAGE, + MODIFIER_PROPERTY_MOVESPEED_BONUS_CONSTANT, + MODIFIER_PROPERTY_ATTACK_RANGE_BONUS, + MODIFIER_PROPERTY_MAGICAL_RESISTANCE_BONUS, + MODIFIER_PROPERTY_MODEL_CHANGE, + MODIFIER_PROPERTY_MODEL_SCALE, + MODIFIER_PROPERTY_TRANSLATE_ATTACK_SOUND, + MODIFIER_PROPERTY_PROJECTILE_NAME, + MODIFIER_PROPERTY_PROJECTILE_SPEED_BONUS, + MODIFIER_PROPERTY_PROCATTACK_FEEDBACK + } +end +function modifier_ability_elder_dragon_form.prototype.GetModifierBaseAttack_BonusDamage(self) + if not self:innateStatsActive() then + return 0 + end + return self.bonusDamage +end +function modifier_ability_elder_dragon_form.prototype.GetModifierMoveSpeedBonus_Constant(self) + if not self:innateStatsActive() then + return 0 + end + return self.bonusMs +end +function modifier_ability_elder_dragon_form.prototype.GetModifierAttackRangeBonus(self) + if not self:innateStatsActive() then + return 0 + end + return self.bonusRange +end +function modifier_ability_elder_dragon_form.prototype.GetModifierMagicalResistanceBonus(self) + if not self:innateStatsActive() then + return 0 + end + return self.magicResist +end +function modifier_ability_elder_dragon_form.prototype.GetModifierModelChange(self) + return "models/heroes/dragon_knight/dragon_knight_dragon.vmdl" +end +function modifier_ability_elder_dragon_form.prototype.GetModifierModelScale(self) + if not self:innateStatsActive() then + return 0 + end + return self.scale +end +function modifier_ability_elder_dragon_form.prototype.GetAttackSound(self) + if not self:innateStatsActive() then + return "" + end + return self.attackSound or "" +end +function modifier_ability_elder_dragon_form.prototype.GetModifierProjectileName(self) + if not self:innateStatsActive() then + return "" + end + return self.projectile or "" +end +function modifier_ability_elder_dragon_form.prototype.GetModifierProjectileSpeedBonus(self) + if not self:innateStatsActive() then + return 0 + end + return 900 +end +function modifier_ability_elder_dragon_form.prototype.GetModifierProcAttack_Feedback(self, event) + if not self.parent then + return 0 + end + if not self:innateStatsActive() then + return 0 + end + if event.target:GetTeamNumber() == self.parent:GetTeamNumber() then + return 0 + end + if self.level == 1 then + self:Corrosive(event.target) + elseif self.level == 2 then + self:Corrosive(event.target) + self:Splash(event.target, event.damage) + elseif self.level == 3 then + self:Corrosive(event.target) + self:Splash(event.target, event.damage) + self:Frost(event.target) + else + self:Corrosive(event.target) + self:Splash(event.target, event.damage) + self:Frost(event.target) + end + EmitSoundOn("Hero_DragonKnight.ProjectileImpact", event.target) + return 0 +end +function modifier_ability_elder_dragon_form.prototype.Corrosive(self, target) + if not self.parent then + return + end + local ability = self:GetAbility() + if not ability then + return + end + target:AddNewModifier(self.parent, ability, ____exports.modifier_ability_elder_dragon_form_corrosive.name, {duration = self.corrosiveDuration}) +end +function modifier_ability_elder_dragon_form.prototype.Splash(self, target, damage) + if not self.parent then + return + end + local ability = self:GetAbility() + if not ability then + return + end + local enemies = FindUnitsInRadius( + self.parent:GetTeamNumber(), + target:GetAbsOrigin(), + nil, + self.splashRadius, + DOTA_UNIT_TARGET_TEAM_ENEMY, + bit.bor(DOTA_UNIT_TARGET_HERO, DOTA_UNIT_TARGET_BASIC), + DOTA_UNIT_TARGET_FLAG_MAGIC_IMMUNE_ENEMIES, + FIND_ANY_ORDER, + false + ) + for ____, enemy in ipairs(enemies) do + if enemy ~= target then + ApplyDamage({ + victim = enemy, + attacker = self.parent, + damage = damage * self.splashPct, + damage_type = DAMAGE_TYPE_PHYSICAL, + ability = ability + }) + self:Corrosive(enemy) + end + end +end +function modifier_ability_elder_dragon_form.prototype.Frost(self, target) + if not self.parent then + return + end + local ability = self:GetAbility() + if not ability then + return + end + local enemies = FindUnitsInRadius( + self.parent:GetTeamNumber(), + target:GetAbsOrigin(), + nil, + self.frostRadius, + DOTA_UNIT_TARGET_TEAM_ENEMY, + bit.bor(DOTA_UNIT_TARGET_HERO, DOTA_UNIT_TARGET_BASIC), + DOTA_UNIT_TARGET_FLAG_MAGIC_IMMUNE_ENEMIES, + FIND_ANY_ORDER, + false + ) + for ____, enemy in ipairs(enemies) do + enemy:AddNewModifier(self.parent, ability, ____exports.modifier_ability_elder_dragon_form_frost.name, {duration = self.frostDuration}) + end +end +function modifier_ability_elder_dragon_form.prototype.PlayEffects(self) + if not self.parent then + return + end + local particleCast = ____exports.modifier_ability_elder_dragon_form.effectData[self.level].transform + local effectCast = ParticleManager:CreateParticle(particleCast, PATTACH_ABSORIGIN_FOLLOW, self.parent) + ParticleManager:ReleaseParticleIndex(effectCast) +end +modifier_ability_elder_dragon_form.effectData = {[1] = {projectile = "particles/units/heroes/hero_dragon_knight/dragon_knight_elder_dragon_corrosive.vpcf", attack_sound = "Hero_DragonKnight.ElderDragonShoot1.Attack", transform = "particles/units/heroes/hero_dragon_knight/dragon_knight_transform_green.vpcf", scale = 0}, [2] = {projectile = "particles/units/heroes/hero_dragon_knight/dragon_knight_elder_dragon_fire.vpcf", attack_sound = "Hero_DragonKnight.ElderDragonShoot2.Attack", transform = "particles/units/heroes/hero_dragon_knight/dragon_knight_transform_red.vpcf", scale = 10}, [3] = {projectile = "particles/units/heroes/hero_dragon_knight/dragon_knight_elder_dragon_frost.vpcf", attack_sound = "Hero_DragonKnight.ElderDragonShoot3.Attack", transform = "particles/units/heroes/hero_dragon_knight/dragon_knight_transform_blue.vpcf", scale = 20}, [4] = {projectile = "particles/units/heroes/hero_dragon_knight/dragon_knight_elder_dragon_frost.vpcf", attack_sound = "Hero_DragonKnight.ElderDragonShoot3.Attack", transform = "particles/units/heroes/hero_dragon_knight/dragon_knight_transform_blue.vpcf", scale = 50}} +modifier_ability_elder_dragon_form = __TS__Decorate( + modifier_ability_elder_dragon_form, + modifier_ability_elder_dragon_form, + {registerModifier(nil)}, + {kind = "class", name = "modifier_ability_elder_dragon_form"} +) +____exports.modifier_ability_elder_dragon_form = modifier_ability_elder_dragon_form +____exports.modifier_ability_elder_dragon_form_corrosive = __TS__Class() +local modifier_ability_elder_dragon_form_corrosive = ____exports.modifier_ability_elder_dragon_form_corrosive +modifier_ability_elder_dragon_form_corrosive.name = "modifier_ability_elder_dragon_form_corrosive" +modifier_ability_elder_dragon_form_corrosive.____file_path = "scripts/vscripts/abilities/heroes/smaug/ability_elder_dragon_form.lua" +__TS__ClassExtends(modifier_ability_elder_dragon_form_corrosive, BaseModifier) +function modifier_ability_elder_dragon_form_corrosive.prototype.IsHidden(self) + return false +end +function modifier_ability_elder_dragon_form_corrosive.prototype.IsDebuff(self) + return true +end +function modifier_ability_elder_dragon_form_corrosive.prototype.IsStunDebuff(self) + return false +end +function modifier_ability_elder_dragon_form_corrosive.prototype.IsPurgable(self) + return false +end +function modifier_ability_elder_dragon_form_corrosive.prototype.OnCreated(self) + local ability = self:GetAbility() + local caster = self:GetCaster() + if not ability or not caster then + return + end + local damage = ability:GetSpecialValueFor("corrosive_breath_damage") + local level = ability:GetLevel() + if caster:HasScepter() then + level = level + 1 + end + if level == 4 then + damage = damage * 1.5 + end + self.damageTable = { + victim = self:GetParent(), + attacker = caster, + damage = damage, + damage_type = DAMAGE_TYPE_MAGICAL, + ability = ability + } + if not IsServer() then + return + end + self:StartIntervalThink(1) +end +function modifier_ability_elder_dragon_form_corrosive.prototype.OnRefresh(self) + local ability = self:GetAbility() + local caster = self:GetCaster() + if not ability or not caster or not self.damageTable then + return + end + local damage = ability:GetSpecialValueFor("corrosive_breath_damage") + local level = ability:GetLevel() + if caster:HasScepter() then + level = level + 1 + end + if level == 4 then + damage = damage * 1.5 + end + self.damageTable.damage = damage +end +function modifier_ability_elder_dragon_form_corrosive.prototype.OnIntervalThink(self) + if not IsServer() or not self.damageTable then + return + end + ApplyDamage(self.damageTable) +end +function modifier_ability_elder_dragon_form_corrosive.prototype.GetEffectName(self) + return "particles/units/heroes/hero_dragon_knight/dragon_knight_corrosion_debuff.vpcf" +end +function modifier_ability_elder_dragon_form_corrosive.prototype.GetEffectAttachType(self) + return PATTACH_ABSORIGIN_FOLLOW +end +modifier_ability_elder_dragon_form_corrosive = __TS__Decorate( + modifier_ability_elder_dragon_form_corrosive, + modifier_ability_elder_dragon_form_corrosive, + {registerModifier(nil)}, + {kind = "class", name = "modifier_ability_elder_dragon_form_corrosive"} +) +____exports.modifier_ability_elder_dragon_form_corrosive = modifier_ability_elder_dragon_form_corrosive +____exports.modifier_ability_elder_dragon_form_frost = __TS__Class() +local modifier_ability_elder_dragon_form_frost = ____exports.modifier_ability_elder_dragon_form_frost +modifier_ability_elder_dragon_form_frost.name = "modifier_ability_elder_dragon_form_frost" +modifier_ability_elder_dragon_form_frost.____file_path = "scripts/vscripts/abilities/heroes/smaug/ability_elder_dragon_form.lua" +__TS__ClassExtends(modifier_ability_elder_dragon_form_frost, BaseModifier) +function modifier_ability_elder_dragon_form_frost.prototype.____constructor(self, ...) + BaseModifier.prototype.____constructor(self, ...) + self.frostAs = 0 + self.frostMs = 0 +end +function modifier_ability_elder_dragon_form_frost.prototype.IsHidden(self) + return false +end +function modifier_ability_elder_dragon_form_frost.prototype.IsDebuff(self) + return true +end +function modifier_ability_elder_dragon_form_frost.prototype.IsStunDebuff(self) + return false +end +function modifier_ability_elder_dragon_form_frost.prototype.IsPurgable(self) + return false +end +function modifier_ability_elder_dragon_form_frost.prototype.OnCreated(self) + local ability = self:GetAbility() + local caster = self:GetCaster() + if not ability or not caster then + return + end + self.frostAs = ability:GetSpecialValueFor("frost_bonus_attack_speed") + self.frostMs = ability:GetSpecialValueFor("frost_bonus_movement_speed") + local level = ability:GetLevel() + if caster:HasScepter() then + level = level + 1 + end + if level == 4 then + self.frostAs = self.frostAs * 1.5 + self.frostMs = self.frostMs * 1.5 + end +end +function modifier_ability_elder_dragon_form_frost.prototype.OnRefresh(self) + local ability = self:GetAbility() + if not ability then + return + end + self.frostAs = ability:GetSpecialValueFor("frost_bonus_attack_speed") + self.frostMs = ability:GetSpecialValueFor("frost_bonus_movement_speed") +end +function modifier_ability_elder_dragon_form_frost.prototype.DeclareFunctions(self) + return {MODIFIER_PROPERTY_MOVESPEED_BONUS_PERCENTAGE, MODIFIER_PROPERTY_ATTACKSPEED_BONUS_CONSTANT} +end +function modifier_ability_elder_dragon_form_frost.prototype.GetModifierMoveSpeedBonus_Percentage(self) + return self.frostMs +end +function modifier_ability_elder_dragon_form_frost.prototype.GetModifierAttackSpeedBonus_Constant(self) + return self.frostAs +end +function modifier_ability_elder_dragon_form_frost.prototype.GetStatusEffectName(self) + return "particles/status_fx/status_effect_frost.vpcf" +end +modifier_ability_elder_dragon_form_frost = __TS__Decorate( + modifier_ability_elder_dragon_form_frost, + modifier_ability_elder_dragon_form_frost, + {registerModifier(nil)}, + {kind = "class", name = "modifier_ability_elder_dragon_form_frost"} +) +____exports.modifier_ability_elder_dragon_form_frost = modifier_ability_elder_dragon_form_frost +return ____exports diff --git a/scripts/vscripts/abilities/heroes/smaug/ability_fire_punishment.lua b/scripts/vscripts/abilities/heroes/smaug/ability_fire_punishment.lua new file mode 100644 index 0000000..8c81ec5 --- /dev/null +++ b/scripts/vscripts/abilities/heroes/smaug/ability_fire_punishment.lua @@ -0,0 +1,306 @@ +local ____lualib = require("lualib_bundle") +local __TS__Class = ____lualib.__TS__Class +local __TS__ClassExtends = ____lualib.__TS__ClassExtends +local __TS__Decorate = ____lualib.__TS__Decorate +local __TS__Delete = ____lualib.__TS__Delete +local __TS__SparseArrayNew = ____lualib.__TS__SparseArrayNew +local __TS__SparseArrayPush = ____lualib.__TS__SparseArrayPush +local __TS__SparseArraySpread = ____lualib.__TS__SparseArraySpread +local __TS__Number = ____lualib.__TS__Number +local ____exports = {} +local ____dota_ts_adapter = require("lib.dota_ts_adapter") +local BaseAbility = ____dota_ts_adapter.BaseAbility +local BaseModifier = ____dota_ts_adapter.BaseModifier +local registerAbility = ____dota_ts_adapter.registerAbility +local registerModifier = ____dota_ts_adapter.registerModifier +____exports.ability_fire_punishment = __TS__Class() +local ability_fire_punishment = ____exports.ability_fire_punishment +ability_fire_punishment.name = "ability_fire_punishment" +ability_fire_punishment.____file_path = "scripts/vscripts/abilities/heroes/smaug/ability_fire_punishment.lua" +__TS__ClassExtends(ability_fire_punishment, BaseAbility) +function ability_fire_punishment.prototype.GetIntrinsicModifierName(self) + return ____exports.modifier_fire_punishment_orb.name +end +function ability_fire_punishment.prototype.GetProjectileName(self) + return "particles/econ/items/gyrocopter/hero_gyrocopter_gyrotechnics/gyro_base_attack.vpcf" +end +function ability_fire_punishment.prototype.OnOrbFire(self, params) + EmitSoundOn( + "Hero_Jakiro.LiquidFire", + self:GetCaster() + ) +end +function ability_fire_punishment.prototype.OnOrbImpact(self, params) + if not IsServer() then + return + end + local caster = self:GetCaster() + local duration = self:GetDuration() + local radius = self:GetSpecialValueFor("radius") + local enemies = FindUnitsInRadius( + caster:GetTeamNumber(), + params.target:GetAbsOrigin(), + nil, + radius, + DOTA_UNIT_TARGET_TEAM_ENEMY, + bit.bor( + bit.bor(DOTA_UNIT_TARGET_HERO, DOTA_UNIT_TARGET_BASIC), + DOTA_UNIT_TARGET_BUILDING + ), + DOTA_UNIT_TARGET_FLAG_NONE, + FIND_ANY_ORDER, + false + ) + for ____, enemy in ipairs(enemies) do + enemy:AddNewModifier(caster, self, ____exports.modifier_fire_punishment_fire.name, {duration = duration}) + end + local effectCast = ParticleManager:CreateParticle("particles/units/heroes/hero_jakiro/jakiro_liquid_fire_explosion.vpcf", PATTACH_ABSORIGIN_FOLLOW, params.target) + ParticleManager:SetParticleControl( + effectCast, + 1, + Vector(radius, radius, radius) + ) + ParticleManager:ReleaseParticleIndex(effectCast) + EmitSoundOn("Hero_Jakiro.LiquidFire", caster) +end +ability_fire_punishment = __TS__Decorate( + ability_fire_punishment, + ability_fire_punishment, + {registerAbility(nil)}, + {kind = "class", name = "ability_fire_punishment"} +) +____exports.ability_fire_punishment = ability_fire_punishment +____exports.modifier_fire_punishment_fire = __TS__Class() +local modifier_fire_punishment_fire = ____exports.modifier_fire_punishment_fire +modifier_fire_punishment_fire.name = "modifier_fire_punishment_fire" +modifier_fire_punishment_fire.____file_path = "scripts/vscripts/abilities/heroes/smaug/ability_fire_punishment.lua" +__TS__ClassExtends(modifier_fire_punishment_fire, BaseModifier) +function modifier_fire_punishment_fire.prototype.____constructor(self, ...) + BaseModifier.prototype.____constructor(self, ...) + self.debuffMagic = 0 +end +function modifier_fire_punishment_fire.prototype.IsHidden(self) + return false +end +function modifier_fire_punishment_fire.prototype.IsDebuff(self) + return true +end +function modifier_fire_punishment_fire.prototype.IsStunDebuff(self) + return false +end +function modifier_fire_punishment_fire.prototype.IsPurgable(self) + return true +end +function modifier_fire_punishment_fire.prototype.GetAttributes(self) + return MODIFIER_ATTRIBUTE_MULTIPLE +end +function modifier_fire_punishment_fire.prototype.OnCreated(self) + local ability = self:GetAbility() + if not ability then + return + end + local damage = ability:GetSpecialValueFor("damage") + self.debuffMagic = ability:GetSpecialValueFor("debuff_magic") + if not IsServer() then + return + end + local caster = self:GetCaster() + if not caster then + return + end + self.damageTable = { + victim = self:GetParent(), + attacker = caster, + damage = damage + caster:GetAttackDamage() * (ability:GetSpecialValueFor("pct_dmg") / 100), + damage_type = ability:GetAbilityDamageType(), + ability = ability + } + self:StartIntervalThink(0.5) +end +function modifier_fire_punishment_fire.prototype.OnRefresh(self) + local ability = self:GetAbility() + if not ability or not self.damageTable then + return + end + local damage = ability:GetSpecialValueFor("damage") + self.debuffMagic = ability:GetSpecialValueFor("debuff_magic") + if not IsServer() then + return + end + self.damageTable.damage = damage +end +function modifier_fire_punishment_fire.prototype.DeclareFunctions(self) + return {MODIFIER_PROPERTY_MAGICAL_RESISTANCE_BONUS} +end +function modifier_fire_punishment_fire.prototype.GetModifierMagicalResistanceBonus(self) + return self.debuffMagic +end +function modifier_fire_punishment_fire.prototype.OnIntervalThink(self) + if not IsServer() or not self.damageTable then + return + end + ApplyDamage(self.damageTable) +end +function modifier_fire_punishment_fire.prototype.GetEffectName(self) + return "particles/units/heroes/hero_jakiro/jakiro_liquid_fire_debuff.vpcf" +end +function modifier_fire_punishment_fire.prototype.GetEffectAttachType(self) + return PATTACH_ABSORIGIN_FOLLOW +end +modifier_fire_punishment_fire = __TS__Decorate( + modifier_fire_punishment_fire, + modifier_fire_punishment_fire, + {registerModifier(nil)}, + {kind = "class", name = "modifier_fire_punishment_fire"} +) +____exports.modifier_fire_punishment_fire = modifier_fire_punishment_fire +____exports.modifier_fire_punishment_orb = __TS__Class() +local modifier_fire_punishment_orb = ____exports.modifier_fire_punishment_orb +modifier_fire_punishment_orb.name = "modifier_fire_punishment_orb" +modifier_fire_punishment_orb.____file_path = "scripts/vscripts/abilities/heroes/smaug/ability_fire_punishment.lua" +__TS__ClassExtends(modifier_fire_punishment_orb, BaseModifier) +function modifier_fire_punishment_orb.prototype.____constructor(self, ...) + BaseModifier.prototype.____constructor(self, ...) + self.cast = false + self.records = {} +end +function modifier_fire_punishment_orb.prototype.IsHidden(self) + return true +end +function modifier_fire_punishment_orb.prototype.IsDebuff(self) + return false +end +function modifier_fire_punishment_orb.prototype.IsPurgable(self) + return false +end +function modifier_fire_punishment_orb.prototype.GetAttributes(self) + return MODIFIER_ATTRIBUTE_PERMANENT +end +function modifier_fire_punishment_orb.prototype.OnCreated(self) + self.ability = self:GetAbility() + self.cast = false + self.records = {} +end +function modifier_fire_punishment_orb.prototype.DeclareFunctions(self) + return { + MODIFIER_EVENT_ON_ATTACK_LANDED, + MODIFIER_PROPERTY_PROCATTACK_FEEDBACK, + MODIFIER_EVENT_ON_ATTACK_RECORD_DESTROY, + MODIFIER_EVENT_ON_ORDER, + MODIFIER_PROPERTY_PROJECTILE_NAME + } +end +function modifier_fire_punishment_orb.prototype.OnAttackLanded(self, event) + if not IsServer() then + return + end + if event.attacker ~= self:GetParent() then + return + end + if self:GetParent():IsIllusion() then + return + end + if self:GetParent():PassivesDisabled() then + return + end + if self:ShouldLaunch(event.target) then + self.records[event.record] = true + if self.ability and self.ability.OnOrbFire then + self.ability:OnOrbFire(event) + end + end + self.cast = false +end +function modifier_fire_punishment_orb.prototype.GetModifierProcAttack_Feedback(self, event) + if not IsServer() then + return 0 + end + if self:GetParent():PassivesDisabled() then + return 0 + end + if self.ability and self.ability.OnOrbImpact then + self.ability:OnOrbImpact(event) + end + return 0 +end +function modifier_fire_punishment_orb.prototype.OnAttackRecordDestroy(self, event) + if not IsServer() then + return + end + __TS__Delete(self.records, event.record) +end +function modifier_fire_punishment_orb.prototype.GetModifierProjectileName(self) + if not self.ability or not self.ability.GetProjectileName then + return "" + end + local ____opt_0 = self:GetCaster() + local aggroTarget = ____opt_0 and ____opt_0:GetAggroTarget() + if aggroTarget and self:ShouldLaunch(aggroTarget) then + return self.ability:GetProjectileName() + end + return "" +end +function modifier_fire_punishment_orb.prototype.ShouldLaunch(self, target) + if not target or not self.ability then + return false + end + if self:GetParent():PassivesDisabled() then + return false + end + if self.ability:GetAutoCastState() then + if self.ability.CastFilterResultTarget ~= nil then + if self.ability:CastFilterResultTarget(target) == UF_SUCCESS then + self.cast = true + end + else + local ____UnitFilter_5 = UnitFilter + local ____array_4 = __TS__SparseArrayNew( + target, + self.ability:GetAbilityTargetTeam(), + self.ability:GetAbilityTargetType(), + self.ability:GetAbilityTargetFlags() + ) + local ____opt_2 = self:GetCaster() + __TS__SparseArrayPush( + ____array_4, + ____opt_2 and ____opt_2:GetTeamNumber() or DOTA_TEAM_NEUTRALS + ) + local nResult = ____UnitFilter_5(__TS__SparseArraySpread(____array_4)) + if nResult == UF_SUCCESS then + self.cast = true + end + end + end + if self.cast and self.ability:IsFullyCastable() and not self:GetParent():IsSilenced() then + return true + end + return false +end +function modifier_fire_punishment_orb.prototype.FlagExist(self, a, b) + local numA = __TS__Number(a) + local numB = __TS__Number(b) + local p = 1 + local c = 0 + local d = numB + local valA = numA + local valB = numB + while valA > 0 and valB > 0 do + local ra = valA % 2 + local rb = valB % 2 + if ra + rb > 1 then + c = c + p + end + valA = (valA - ra) / 2 + valB = (valB - rb) / 2 + p = p * 2 + end + return c == d +end +modifier_fire_punishment_orb = __TS__Decorate( + modifier_fire_punishment_orb, + modifier_fire_punishment_orb, + {registerModifier(nil)}, + {kind = "class", name = "modifier_fire_punishment_orb"} +) +____exports.modifier_fire_punishment_orb = modifier_fire_punishment_orb +return ____exports diff --git a/scripts/vscripts/abilities/heroes/smaug/ability_incandescent_fury.lua b/scripts/vscripts/abilities/heroes/smaug/ability_incandescent_fury.lua new file mode 100644 index 0000000..b089bcd --- /dev/null +++ b/scripts/vscripts/abilities/heroes/smaug/ability_incandescent_fury.lua @@ -0,0 +1,234 @@ +local ____lualib = require("lualib_bundle") +local __TS__Class = ____lualib.__TS__Class +local __TS__ClassExtends = ____lualib.__TS__ClassExtends +local __TS__Decorate = ____lualib.__TS__Decorate +local ____exports = {} +local ____dota_ts_adapter = require("lib.dota_ts_adapter") +local BaseAbility = ____dota_ts_adapter.BaseAbility +local BaseModifier = ____dota_ts_adapter.BaseModifier +local registerAbility = ____dota_ts_adapter.registerAbility +local registerModifier = ____dota_ts_adapter.registerModifier +____exports.ability_incandescent_fury = __TS__Class() +local ability_incandescent_fury = ____exports.ability_incandescent_fury +ability_incandescent_fury.name = "ability_incandescent_fury" +ability_incandescent_fury.____file_path = "scripts/vscripts/abilities/heroes/smaug/ability_incandescent_fury.lua" +__TS__ClassExtends(ability_incandescent_fury, BaseAbility) +function ability_incandescent_fury.prototype.GetCastRange(self, vLocation, hTarget) + return self:GetSpecialValueFor("cast_range") +end +function ability_incandescent_fury.prototype.OnSpellStart(self) + if not IsServer() then + return + end + local caster = self:GetCaster() + local point = self:GetCursorPosition() + local dir = point - caster:GetAbsOrigin() + dir.z = 0 + dir = dir:Normalized() + local duration = self:GetSpecialValueFor("duration") + CreateModifierThinker( + caster, + self, + ____exports.modifier_incandescent_fury_thinker.name, + {duration = duration, x = dir.x, y = dir.y}, + caster:GetAbsOrigin(), + caster:GetTeamNumber(), + false + ) + EmitSoundOn("Hero_Jakiro.Macropyre.Cast", caster) +end +ability_incandescent_fury = __TS__Decorate( + ability_incandescent_fury, + ability_incandescent_fury, + {registerAbility(nil)}, + {kind = "class", name = "ability_incandescent_fury"} +) +____exports.ability_incandescent_fury = ability_incandescent_fury +____exports.modifier_incandescent_fury = __TS__Class() +local modifier_incandescent_fury = ____exports.modifier_incandescent_fury +modifier_incandescent_fury.name = "modifier_incandescent_fury" +modifier_incandescent_fury.____file_path = "scripts/vscripts/abilities/heroes/smaug/ability_incandescent_fury.lua" +__TS__ClassExtends(modifier_incandescent_fury, BaseModifier) +function modifier_incandescent_fury.prototype.IsHidden(self) + return false +end +function modifier_incandescent_fury.prototype.IsDebuff(self) + return true +end +function modifier_incandescent_fury.prototype.IsStunDebuff(self) + return false +end +function modifier_incandescent_fury.prototype.IsPurgable(self) + return false +end +function modifier_incandescent_fury.prototype.OnCreated(self, kv) + if not IsServer() then + return + end + local ability = self:GetAbility() + local caster = self:GetCaster() + if not ability or not caster then + return + end + self.damageTable = { + victim = self:GetParent(), + attacker = caster, + damage = (kv.damage or 0) + caster:GetAttackDamage() * (ability:GetSpecialValueFor("pct_dmg") / 100), + damage_type = kv.damage_type or ability:GetAbilityDamageType(), + ability = ability + } + if kv.interval then + self:StartIntervalThink(kv.interval) + end +end +function modifier_incandescent_fury.prototype.OnRefresh(self, kv) + if not IsServer() or not self.damageTable then + return + end + local ability = self:GetAbility() + local caster = self:GetCaster() + if not ability or not caster then + return + end + self.damageTable.damage = (kv.damage or 0) + caster:GetAttackDamage() * (ability:GetSpecialValueFor("pct_dmg") / 100) + self.damageTable.damage_type = kv.damage_type or ability:GetAbilityDamageType() +end +function modifier_incandescent_fury.prototype.OnIntervalThink(self) + if not IsServer() or not self.damageTable then + return + end + ApplyDamage(self.damageTable) +end +function modifier_incandescent_fury.prototype.GetEffectName(self) + return "particles/units/heroes/hero_jakiro/jakiro_liquid_fire_debuff.vpcf" +end +function modifier_incandescent_fury.prototype.GetEffectAttachType(self) + return PATTACH_ABSORIGIN_FOLLOW +end +modifier_incandescent_fury = __TS__Decorate( + modifier_incandescent_fury, + modifier_incandescent_fury, + {registerModifier(nil)}, + {kind = "class", name = "modifier_incandescent_fury"} +) +____exports.modifier_incandescent_fury = modifier_incandescent_fury +____exports.modifier_incandescent_fury_thinker = __TS__Class() +local modifier_incandescent_fury_thinker = ____exports.modifier_incandescent_fury_thinker +modifier_incandescent_fury_thinker.name = "modifier_incandescent_fury_thinker" +modifier_incandescent_fury_thinker.____file_path = "scripts/vscripts/abilities/heroes/smaug/ability_incandescent_fury.lua" +__TS__ClassExtends(modifier_incandescent_fury_thinker, BaseModifier) +function modifier_incandescent_fury_thinker.prototype.____constructor(self, ...) + BaseModifier.prototype.____constructor(self, ...) + self.radius = 0 + self.duration = 0 + self.interval = 0 + self.range = 0 + self.damage = 0 +end +function modifier_incandescent_fury_thinker.prototype.IsHidden(self) + return false +end +function modifier_incandescent_fury_thinker.prototype.IsDebuff(self) + return false +end +function modifier_incandescent_fury_thinker.prototype.IsStunDebuff(self) + return false +end +function modifier_incandescent_fury_thinker.prototype.IsPurgable(self) + return false +end +function modifier_incandescent_fury_thinker.prototype.OnCreated(self, kv) + if not IsServer() then + return + end + self.caster = self:GetCaster() + self.parent = self:GetParent() + local ability = self:GetAbility() + if not ability or not self.caster or not self.parent then + return + end + self.radius = ability:GetSpecialValueFor("path_radius") + self.duration = ability:GetSpecialValueFor("linger_duration") + self.interval = ability:GetSpecialValueFor("burn_interval") + self.range = ability:GetCastRange( + self.parent:GetAbsOrigin(), + nil + ) + self.caster:GetCastRangeBonus() + self.damage = ability:GetSpecialValueFor("damage") + self.abilityDamageType = ability:GetAbilityDamageType() + self.abilityTargetTeam = ability:GetAbilityTargetTeam() + self.abilityTargetType = ability:GetAbilityTargetType() + self.abilityTargetFlags = ability:GetAbilityTargetFlags() + local startRange = 234 + self.direction = Vector(kv.x or 0, kv.y or 0, 0) + self.startpoint = self.parent:GetAbsOrigin() + self.direction * startRange + self.endpoint = self.startpoint + self.direction * self.range + local step = 0 + while step < self.range and self.startpoint and self.direction do + local loc = self.startpoint + self.direction * step + GridNav:DestroyTreesAroundPoint(loc, self.radius, true) + step = step + self.radius + end + self:StartIntervalThink(self.interval) + local duration = self:GetDuration() + self.effectCast = ParticleManager:CreateParticle("particles/econ/items/jakiro/jakiro_ti10_immortal/jakiro_ti10_macropyre.vpcf", PATTACH_WORLDORIGIN, self.parent) + if self.startpoint ~= nil and self.startpoint ~= nil and self.endpoint ~= nil and self.endpoint ~= nil then + ParticleManager:SetParticleControl(self.effectCast, 0, self.startpoint) + ParticleManager:SetParticleControl(self.effectCast, 1, self.endpoint) + end + ParticleManager:SetParticleControl( + self.effectCast, + 2, + Vector(duration, 0, 0) + ) + self:AddParticle( + self.effectCast, + false, + false, + -1, + false, + false + ) + EmitSoundOn("hero_jakiro.macropyre", self.parent) +end +function modifier_incandescent_fury_thinker.prototype.OnDestroy(self) + if not IsServer() then + return + end + if self.parent then + UTIL_Remove(self.parent) + end +end +function modifier_incandescent_fury_thinker.prototype.OnIntervalThink(self) + if not IsServer() then + return + end + if not self.caster or not self.startpoint or not self.endpoint or not self.direction then + return + end + local ability = self:GetAbility() + if not ability then + return + end + local enemies = FindUnitsInLine( + self.caster:GetTeamNumber(), + self.startpoint, + self.endpoint, + nil, + self.radius, + self.abilityTargetTeam or DOTA_UNIT_TARGET_TEAM_ENEMY, + self.abilityTargetType or bit.bor(DOTA_UNIT_TARGET_HERO, DOTA_UNIT_TARGET_BASIC), + self.abilityTargetFlags or DOTA_UNIT_TARGET_FLAG_NONE + ) + for ____, enemy in ipairs(enemies) do + enemy:AddNewModifier(self.caster, ability, ____exports.modifier_incandescent_fury.name, {duration = self.duration, interval = self.interval, damage = self.damage * self.interval, damage_type = self.abilityDamageType}) + end +end +modifier_incandescent_fury_thinker = __TS__Decorate( + modifier_incandescent_fury_thinker, + modifier_incandescent_fury_thinker, + {registerModifier(nil)}, + {kind = "class", name = "modifier_incandescent_fury_thinker"} +) +____exports.modifier_incandescent_fury_thinker = modifier_incandescent_fury_thinker +return ____exports diff --git a/scripts/vscripts/abilities/heroes/sniper/ability_sniper_assassinate_custom.lua b/scripts/vscripts/abilities/heroes/sniper/ability_sniper_assassinate_custom.lua new file mode 100644 index 0000000..63ba7ee --- /dev/null +++ b/scripts/vscripts/abilities/heroes/sniper/ability_sniper_assassinate_custom.lua @@ -0,0 +1,326 @@ +local ____lualib = require("lualib_bundle") +local __TS__Class = ____lualib.__TS__Class +local __TS__ClassExtends = ____lualib.__TS__ClassExtends +local __TS__Decorate = ____lualib.__TS__Decorate +local ____exports = {} +local ____dota_ts_adapter = require("lib.dota_ts_adapter") +local BaseAbility = ____dota_ts_adapter.BaseAbility +local BaseModifier = ____dota_ts_adapter.BaseModifier +local registerAbility = ____dota_ts_adapter.registerAbility +local registerModifier = ____dota_ts_adapter.registerModifier +local ____ability_stacking_crit = require("abilities.modifiers.ability_stacking_crit") +local modifier_stacking_crit = ____ability_stacking_crit.modifier_stacking_crit +--- R: ульта по мотивам dota1x6 — фаза помечает врагов в AoE у курсора (прицел + обзор), +-- выстрел снарядом по каждой отмеченной цели; если в зоне никого нет — по одной цели каста. +____exports.ability_sniper_assassinate_custom = __TS__Class() +local ability_sniper_assassinate_custom = ____exports.ability_sniper_assassinate_custom +ability_sniper_assassinate_custom.name = "ability_sniper_assassinate_custom" +ability_sniper_assassinate_custom.____file_path = "scripts/vscripts/abilities/heroes/sniper/ability_sniper_assassinate_custom.lua" +__TS__ClassExtends(ability_sniper_assassinate_custom, BaseAbility) +function ability_sniper_assassinate_custom.prototype.____constructor(self, ...) + BaseAbility.prototype.____constructor(self, ...) + self.phaseTargets = {} +end +function ability_sniper_assassinate_custom.prototype.Precache(self, context) + PrecacheResource("particle", "particles/units/heroes/hero_sniper/sniper_crosshair.vpcf", context) + PrecacheResource("particle", "particles/units/heroes/hero_sniper/sniper_assassinate.vpcf", context) + PrecacheResource("soundfile", "soundevents/game_sounds_heroes/game_sounds_sniper.vsndevts", context) +end +function ability_sniper_assassinate_custom.prototype.GetBehavior(self) + return bit.bor( + bit.bor(DOTA_ABILITY_BEHAVIOR_UNIT_TARGET, DOTA_ABILITY_BEHAVIOR_AOE), + DOTA_ABILITY_BEHAVIOR_IGNORE_BACKSWING + ) +end +function ability_sniper_assassinate_custom.prototype.GetAOERadius(self) + local r = self:GetSpecialValueFor("aoe_radius") + if r <= 0 then + r = 400 + end + return r +end +function ability_sniper_assassinate_custom.prototype.OnAbilityPhaseStart(self) + if not IsServer() then + return true + end + local caster = self:GetCaster() + EmitSoundOn("Ability.AssassinateLoad", caster) + self.phaseTargets = {} + local point = self:GetCursorPosition() + local aoe = self:GetSpecialValueFor("aoe_radius") + if aoe <= 0 then + aoe = 400 + end + local debuffDur = self:GetSpecialValueFor("debuff_duration") + if debuffDur <= 0 then + debuffDur = self:GetCastPoint() + 0.5 + end + local enemies = FindUnitsInRadius( + caster:GetTeamNumber(), + point, + nil, + aoe, + DOTA_UNIT_TARGET_TEAM_ENEMY, + DOTA_UNIT_TARGET_HERO + DOTA_UNIT_TARGET_BASIC, + DOTA_UNIT_TARGET_FLAG_INVULNERABLE + DOTA_UNIT_TARGET_FLAG_NO_INVIS, + FIND_CLOSEST, + false + ) + for ____, enemy in ipairs(enemies) do + do + if not enemy or not enemy:IsAlive() then + goto __continue10 + end + local ____self_phaseTargets_0 = self.phaseTargets + ____self_phaseTargets_0[#____self_phaseTargets_0 + 1] = enemy + enemy:AddNewModifier(caster, self, ____exports.modifier_sniper_assassinate_aim.name, {duration = debuffDur}) + end + ::__continue10:: + end + return true +end +function ability_sniper_assassinate_custom.prototype.OnAbilityPhaseInterrupted(self) + if not IsServer() then + return + end + self:ClearPhaseMarks() +end +function ability_sniper_assassinate_custom.prototype.OnSpellStart(self) + if not IsServer() then + return + end + local caster = self:GetCaster() + local targets = {} + if #self.phaseTargets > 0 then + for ____, u in ipairs(self.phaseTargets) do + if u and u:IsAlive() and not u:IsNull() then + targets[#targets + 1] = u + end + end + else + local t = self:GetCursorTarget() + if t and t:IsAlive() then + targets[#targets + 1] = t + end + end + self.phaseTargets = {} + if #targets == 0 then + return + end + EmitSoundOn("Ability.Assassinate", caster) + EmitSoundOn("Hero_Sniper.AssassinateProjectile", caster) + local speed = self:GetSpecialValueFor("projectile_speed") + for ____, target in ipairs(targets) do + ProjectileManager:CreateTrackingProjectile({ + Ability = self, + EffectName = "particles/units/heroes/hero_sniper/sniper_assassinate.vpcf", + Source = caster, + Target = target, + iMoveSpeed = speed, + bDodgeable = true, + bVisibleToEnemies = true, + bProvidesVision = true, + iVisionRadius = 400, + iVisionTeamNumber = caster:GetTeamNumber(), + iSourceAttachment = DOTA_PROJECTILE_ATTACHMENT_ATTACK_1 + }) + end +end +function ability_sniper_assassinate_custom.prototype.ClearPhaseMarks(self) + for ____, u in ipairs(self.phaseTargets) do + if u and not u:IsNull() and u:IsAlive() then + u:RemoveModifierByName(____exports.modifier_sniper_assassinate_aim.name) + end + end + self.phaseTargets = {} +end +function ability_sniper_assassinate_custom.prototype.OnProjectileHit(self, target, _location) + if not IsServer() then + return true + end + if not target or not target:IsAlive() then + return true + end + local caster = self:GetCaster() + if not caster or not caster:IsAlive() then + return true + end + target:RemoveModifierByName(____exports.modifier_sniper_assassinate_aim.name) + if target:TriggerSpellAbsorb(self) then + return true + end + EmitSoundOn("Hero_Sniper.AssassinateDamage", caster) + local stunDur = self:GetSpecialValueFor("ministun_duration") + if stunDur > 0 then + target:AddNewModifier( + caster, + self, + "modifier_stunned", + {duration = stunDur * (1 - target:GetStatusResistance())} + ) + end + local ____opt_1 = modifier_stacking_crit:GetForUnit(caster) + if ____opt_1 ~= nil then + ____opt_1:GuaranteeNextCrit(1) + end + caster:PerformAttack( + target, + true, + true, + true, + false, + false, + false, + true + ) + local armorDebuffDur = self:GetSpecialValueFor("armor_mark_duration") + if armorDebuffDur > 0 then + target:AddNewModifier(caster, self, "modifier_sniper_assassinate_mark", {duration = armorDebuffDur}) + end + return true +end +ability_sniper_assassinate_custom = __TS__Decorate( + ability_sniper_assassinate_custom, + ability_sniper_assassinate_custom, + {registerAbility(nil)}, + {kind = "class", name = "ability_sniper_assassinate_custom"} +) +____exports.ability_sniper_assassinate_custom = ability_sniper_assassinate_custom +--- Метка на фазе прицеливания: крест + краткий обзор (как в 1x6). +____exports.modifier_sniper_assassinate_aim = __TS__Class() +local modifier_sniper_assassinate_aim = ____exports.modifier_sniper_assassinate_aim +modifier_sniper_assassinate_aim.name = "modifier_sniper_assassinate_aim" +modifier_sniper_assassinate_aim.____file_path = "scripts/vscripts/abilities/heroes/sniper/ability_sniper_assassinate_custom.lua" +__TS__ClassExtends(modifier_sniper_assassinate_aim, BaseModifier) +function modifier_sniper_assassinate_aim.prototype.IsHidden(self) + return false +end +function modifier_sniper_assassinate_aim.prototype.IsDebuff(self) + return true +end +function modifier_sniper_assassinate_aim.prototype.IsPurgable(self) + return false +end +function modifier_sniper_assassinate_aim.prototype.GetTexture(self) + return "sniper_assassinate" +end +function modifier_sniper_assassinate_aim.prototype.OnCreated(self) + if not IsServer() then + return + end + local parent = self:GetParent() + local caster = self:GetCaster() + if not caster then + return + end + self.fx = ParticleManager:CreateParticleForTeam( + "particles/units/heroes/hero_sniper/sniper_crosshair.vpcf", + PATTACH_OVERHEAD_FOLLOW, + parent, + caster:GetTeamNumber() + ) + ParticleManager:SetParticleControl( + self.fx, + 1, + Vector(0, 0, 0) + ) + self:AddParticle( + self.fx, + false, + false, + -1, + false, + false + ) + self:StartIntervalThink(0.1) +end +function modifier_sniper_assassinate_aim.prototype.OnIntervalThink(self) + if not IsServer() then + return + end + local caster = self:GetCaster() + local parent = self:GetParent() + if not caster then + return + end + AddFOWViewer( + caster:GetTeamNumber(), + parent:GetAbsOrigin(), + 10, + 0.1, + true + ) +end +function modifier_sniper_assassinate_aim.prototype.OnDestroy(self) + if not IsServer() then + return + end + self.fx = nil +end +modifier_sniper_assassinate_aim = __TS__Decorate( + modifier_sniper_assassinate_aim, + modifier_sniper_assassinate_aim, + {registerModifier(nil)}, + {kind = "class", name = "modifier_sniper_assassinate_aim"} +) +____exports.modifier_sniper_assassinate_aim = modifier_sniper_assassinate_aim +____exports.modifier_sniper_assassinate_mark = __TS__Class() +local modifier_sniper_assassinate_mark = ____exports.modifier_sniper_assassinate_mark +modifier_sniper_assassinate_mark.name = "modifier_sniper_assassinate_mark" +modifier_sniper_assassinate_mark.____file_path = "scripts/vscripts/abilities/heroes/sniper/ability_sniper_assassinate_custom.lua" +__TS__ClassExtends(modifier_sniper_assassinate_mark, BaseModifier) +function modifier_sniper_assassinate_mark.prototype.____constructor(self, ...) + BaseModifier.prototype.____constructor(self, ...) + self.reduction = 0 +end +function modifier_sniper_assassinate_mark.prototype.IsHidden(self) + return false +end +function modifier_sniper_assassinate_mark.prototype.IsDebuff(self) + return true +end +function modifier_sniper_assassinate_mark.prototype.GetTexture(self) + return "sniper_assassinate" +end +function modifier_sniper_assassinate_mark.prototype.DeclareFunctions(self) + return {MODIFIER_PROPERTY_PHYSICAL_ARMOR_BONUS} +end +function modifier_sniper_assassinate_mark.prototype.OnCreated(self) + if not IsServer() then + return + end + self:StartIntervalThink(0.25) + self:RefreshArmorReduction() +end +function modifier_sniper_assassinate_mark.prototype.OnRefresh(self) + if not IsServer() then + return + end + self:RefreshArmorReduction() +end +function modifier_sniper_assassinate_mark.prototype.OnIntervalThink(self) + if not IsServer() then + return + end + self:RefreshArmorReduction() +end +function modifier_sniper_assassinate_mark.prototype.RefreshArmorReduction(self) + local p = self:GetParent() + local displayed = p:GetPhysicalArmorValue(false) + local trueArmor = displayed + self.reduction + self.reduction = math.max( + 0, + math.floor(trueArmor * 0.5) + ) +end +function modifier_sniper_assassinate_mark.prototype.GetModifierPhysicalArmorBonus(self) + return -self.reduction +end +modifier_sniper_assassinate_mark = __TS__Decorate( + modifier_sniper_assassinate_mark, + modifier_sniper_assassinate_mark, + {registerModifier(nil)}, + {kind = "class", name = "modifier_sniper_assassinate_mark"} +) +____exports.modifier_sniper_assassinate_mark = modifier_sniper_assassinate_mark +return ____exports diff --git a/scripts/vscripts/abilities/heroes/sniper/ability_sniper_critical_focus_custom.lua b/scripts/vscripts/abilities/heroes/sniper/ability_sniper_critical_focus_custom.lua new file mode 100644 index 0000000..61dadf1 --- /dev/null +++ b/scripts/vscripts/abilities/heroes/sniper/ability_sniper_critical_focus_custom.lua @@ -0,0 +1,121 @@ +local ____lualib = require("lualib_bundle") +local __TS__Class = ____lualib.__TS__Class +local __TS__ClassExtends = ____lualib.__TS__ClassExtends +local __TS__Decorate = ____lualib.__TS__Decorate +local ____exports = {} +local ____dota_ts_adapter = require("lib.dota_ts_adapter") +local BaseAbility = ____dota_ts_adapter.BaseAbility +local BaseModifier = ____dota_ts_adapter.BaseModifier +local registerAbility = ____dota_ts_adapter.registerAbility +local registerModifier = ____dota_ts_adapter.registerModifier +local ____ability_stacking_crit = require("abilities.modifiers.ability_stacking_crit") +local modifier_stacking_crit = ____ability_stacking_crit.modifier_stacking_crit +--- W: на время — каждая атака критует, суммируя все источники критов + доп. крит из AbilityValues (crit_chance / crit_mult). +____exports.ability_sniper_critical_focus_custom = __TS__Class() +local ability_sniper_critical_focus_custom = ____exports.ability_sniper_critical_focus_custom +ability_sniper_critical_focus_custom.name = "ability_sniper_critical_focus_custom" +ability_sniper_critical_focus_custom.____file_path = "scripts/vscripts/abilities/heroes/sniper/ability_sniper_critical_focus_custom.lua" +__TS__ClassExtends(ability_sniper_critical_focus_custom, BaseAbility) +function ability_sniper_critical_focus_custom.prototype.OnSpellStart(self) + if not IsServer() then + return + end + local caster = self:GetCaster() + local duration = self:GetSpecialValueFor("duration") + caster:AddNewModifier(caster, self, "modifier_sniper_critical_focus_active", {duration = duration}) +end +ability_sniper_critical_focus_custom = __TS__Decorate( + ability_sniper_critical_focus_custom, + ability_sniper_critical_focus_custom, + {registerAbility(nil)}, + {kind = "class", name = "ability_sniper_critical_focus_custom"} +) +____exports.ability_sniper_critical_focus_custom = ability_sniper_critical_focus_custom +local SNIPER_CRITICAL_FOCUS_CRIT_SOURCE = "sniper_critical_focus" +____exports.modifier_sniper_critical_focus_active = __TS__Class() +local modifier_sniper_critical_focus_active = ____exports.modifier_sniper_critical_focus_active +modifier_sniper_critical_focus_active.name = "modifier_sniper_critical_focus_active" +modifier_sniper_critical_focus_active.____file_path = "scripts/vscripts/abilities/heroes/sniper/ability_sniper_critical_focus_custom.lua" +__TS__ClassExtends(modifier_sniper_critical_focus_active, BaseModifier) +function modifier_sniper_critical_focus_active.prototype.IsHidden(self) + return false +end +function modifier_sniper_critical_focus_active.prototype.IsDebuff(self) + return false +end +function modifier_sniper_critical_focus_active.prototype.GetTexture(self) + return "sniper_take_aim" +end +function modifier_sniper_critical_focus_active.prototype.syncBonusCrit(self) + if not IsServer() then + return + end + local ability = self:GetAbility() + if not ability then + return + end + local chance = ability:GetSpecialValueFor("crit_chance") + local mult = ability:GetSpecialValueFor("crit_mult") + local stack = modifier_stacking_crit:GetForUnit(self:GetParent()) + if not stack then + return + end + if chance <= 0 and mult <= 0 then + stack:RemoveCrit(SNIPER_CRITICAL_FOCUS_CRIT_SOURCE) + return + end + stack:UpdateCrit( + 0, + 0, + chance, + mult, + SNIPER_CRITICAL_FOCUS_CRIT_SOURCE + ) +end +function modifier_sniper_critical_focus_active.prototype.clearBonusCrit(self) + if not IsServer() then + return + end + local ____opt_0 = modifier_stacking_crit:GetForUnit(self:GetParent()) + if ____opt_0 ~= nil then + ____opt_0:RemoveCrit(SNIPER_CRITICAL_FOCUS_CRIT_SOURCE) + end +end +function modifier_sniper_critical_focus_active.prototype.OnCreated(self) + if not IsServer() then + return + end + self:syncBonusCrit() + local ____opt_2 = modifier_stacking_crit:GetForUnit(self:GetParent()) + if ____opt_2 ~= nil then + ____opt_2:GuaranteeCritUntilDestroy(true) + end +end +function modifier_sniper_critical_focus_active.prototype.OnRefresh(self) + if not IsServer() then + return + end + self:syncBonusCrit() + local ____opt_4 = modifier_stacking_crit:GetForUnit(self:GetParent()) + if ____opt_4 ~= nil then + ____opt_4:GuaranteeCritUntilDestroy(true) + end +end +function modifier_sniper_critical_focus_active.prototype.OnDestroy(self) + if not IsServer() then + return + end + self:clearBonusCrit() + local ____opt_6 = modifier_stacking_crit:GetForUnit(self:GetParent()) + if ____opt_6 ~= nil then + ____opt_6:GuaranteeCritUntilDestroy(false) + end +end +modifier_sniper_critical_focus_active = __TS__Decorate( + modifier_sniper_critical_focus_active, + modifier_sniper_critical_focus_active, + {registerModifier(nil)}, + {kind = "class", name = "modifier_sniper_critical_focus_active"} +) +____exports.modifier_sniper_critical_focus_active = modifier_sniper_critical_focus_active +return ____exports diff --git a/scripts/vscripts/abilities/heroes/sniper/ability_sniper_keen_eye_custom.lua b/scripts/vscripts/abilities/heroes/sniper/ability_sniper_keen_eye_custom.lua new file mode 100644 index 0000000..e84e6f4 --- /dev/null +++ b/scripts/vscripts/abilities/heroes/sniper/ability_sniper_keen_eye_custom.lua @@ -0,0 +1,191 @@ +local ____lualib = require("lualib_bundle") +local __TS__Class = ____lualib.__TS__Class +local __TS__ClassExtends = ____lualib.__TS__ClassExtends +local __TS__Decorate = ____lualib.__TS__Decorate +local ____exports = {} +local ____dota_ts_adapter = require("lib.dota_ts_adapter") +local BaseAbility = ____dota_ts_adapter.BaseAbility +local BaseModifier = ____dota_ts_adapter.BaseModifier +local registerAbility = ____dota_ts_adapter.registerAbility +local registerModifier = ____dota_ts_adapter.registerModifier +local ____luck = require("utils.luck") +local rollLuckChance = ____luck.rollLuckChance +--- Пассив «Headshot» по мотивам dota1x6: шанс бонусного физического урона, нокбэк и замедление. +-- Слот был keen_eye — бонус к дальности атаки сохранён. +____exports.ability_sniper_keen_eye_custom = __TS__Class() +local ability_sniper_keen_eye_custom = ____exports.ability_sniper_keen_eye_custom +ability_sniper_keen_eye_custom.name = "ability_sniper_keen_eye_custom" +ability_sniper_keen_eye_custom.____file_path = "scripts/vscripts/abilities/heroes/sniper/ability_sniper_keen_eye_custom.lua" +__TS__ClassExtends(ability_sniper_keen_eye_custom, BaseAbility) +function ability_sniper_keen_eye_custom.prototype.Precache(self, context) + PrecacheResource("particle", "particles/units/heroes/hero_sniper/sniper_headshot_slow.vpcf", context) + PrecacheResource("particle", "particles/items_fx/force_staff.vpcf", context) +end +function ability_sniper_keen_eye_custom.prototype.GetIntrinsicModifierName(self) + return ____exports.modifier_sniper_headshot_passive.name +end +ability_sniper_keen_eye_custom = __TS__Decorate( + ability_sniper_keen_eye_custom, + ability_sniper_keen_eye_custom, + {registerAbility(nil)}, + {kind = "class", name = "ability_sniper_keen_eye_custom"} +) +____exports.ability_sniper_keen_eye_custom = ability_sniper_keen_eye_custom +____exports.modifier_sniper_headshot_passive = __TS__Class() +local modifier_sniper_headshot_passive = ____exports.modifier_sniper_headshot_passive +modifier_sniper_headshot_passive.name = "modifier_sniper_headshot_passive" +modifier_sniper_headshot_passive.____file_path = "scripts/vscripts/abilities/heroes/sniper/ability_sniper_keen_eye_custom.lua" +__TS__ClassExtends(modifier_sniper_headshot_passive, BaseModifier) +function modifier_sniper_headshot_passive.prototype.DeclareFunctions(self) + return {MODIFIER_PROPERTY_ATTACK_RANGE_BONUS, MODIFIER_EVENT_ON_ATTACK_LANDED} +end +function modifier_sniper_headshot_passive.prototype.GetModifierAttackRangeBonus(self) + local ab = self:GetAbility() + return ab and ab:GetSpecialValueFor("bonus_attack_range") or 0 +end +function modifier_sniper_headshot_passive.prototype.OnAttackLanded(self, event) + if not IsServer() then + return + end + if event.attacker ~= self:GetParent() then + return + end + local attacker = self:GetParent() + local target = event.target + if not target or target:IsBuilding() then + return + end + if target:IsOther() or not target:IsAlive() then + return + end + local ability = self:GetAbility() + if ability == nil or ability:IsNull() then + return + end + if attacker:PassivesDisabled() then + return + end + if target:IsDebuffImmune() then + return + end + local baseChance = ability:GetSpecialValueFor("proc_chance") / 100 + if not rollLuckChance(nil, attacker, baseChance) then + return + end + local bonusDamage = self:ResolveBonusDamage(ability) + local slowDur = ability:GetSpecialValueFor("slow_duration") + if slowDur <= 0 then + slowDur = 1.5 + end + slowDur = math.max(0.05, slowDur) + local resist = target:GetStatusResistance() + local slowTime = slowDur * (1 - resist) + ApplyDamage({ + victim = target, + attacker = attacker, + damage = bonusDamage, + damage_type = DAMAGE_TYPE_PHYSICAL, + ability = ability + }) + target:AddNewModifier(attacker, ability, ____exports.modifier_sniper_headshot_slow.name, {duration = slowTime}) + local knockDist = self:ScaledKnockback(ability, attacker, target) + if knockDist > 0 then + local kb = { + should_stun = 0, + knockback_duration = 0.1, + duration = 0.1, + knockback_distance = knockDist, + knockback_height = 0, + center_x = attacker:GetAbsOrigin().x, + center_y = attacker:GetAbsOrigin().y, + center_z = attacker:GetAbsOrigin().z + } + if not target:IsCurrentlyHorizontalMotionControlled() and not target:IsCurrentlyVerticalMotionControlled() then + target:AddNewModifier(attacker, ability, "modifier_knockback", kb) + end + end +end +function modifier_sniper_headshot_passive.prototype.ResolveBonusDamage(self, ability) + local d = ability:GetSpecialValueFor("headshot_bonus_damage") + if d <= 0 then + d = 40 + 10 * ability:GetLevel() + end + return math.floor(d) +end +function modifier_sniper_headshot_passive.prototype.ScaledKnockback(self, ability, attacker, target) + local dist = ability:GetSpecialValueFor("knockback_distance") + if dist <= 0 then + dist = 120 + end + local a = attacker:GetAbsOrigin() + local t = target:GetAbsOrigin() + local d2 = Vector(a.x - t.x, a.y - t.y, 0):Length2D() + return math.max( + 0.5, + (1 - math.min(d2, 800) / 800) * dist + ) +end +modifier_sniper_headshot_passive = __TS__Decorate( + modifier_sniper_headshot_passive, + modifier_sniper_headshot_passive, + {registerModifier(nil)}, + {kind = "class", name = "modifier_sniper_headshot_passive"} +) +____exports.modifier_sniper_headshot_passive = modifier_sniper_headshot_passive +____exports.modifier_sniper_headshot_slow = __TS__Class() +local modifier_sniper_headshot_slow = ____exports.modifier_sniper_headshot_slow +modifier_sniper_headshot_slow.name = "modifier_sniper_headshot_slow" +modifier_sniper_headshot_slow.____file_path = "scripts/vscripts/abilities/heroes/sniper/ability_sniper_keen_eye_custom.lua" +__TS__ClassExtends(modifier_sniper_headshot_slow, BaseModifier) +function modifier_sniper_headshot_slow.prototype.____constructor(self, ...) + BaseModifier.prototype.____constructor(self, ...) + self.movePct = -100 + self.attackSlow = -100 +end +function modifier_sniper_headshot_slow.prototype.IsHidden(self) + return false +end +function modifier_sniper_headshot_slow.prototype.IsDebuff(self) + return true +end +function modifier_sniper_headshot_slow.prototype.GetTexture(self) + return "sniper_headshot" +end +function modifier_sniper_headshot_slow.prototype.OnCreated(self) + local ability = self:GetAbility() + if ability ~= nil then + local m = ability:GetSpecialValueFor("slow_movement_pct") + if m == 0 then + m = -40 + end + self.movePct = m + local a = ability:GetSpecialValueFor("slow_attack_speed") + if a == 0 then + a = -40 + end + self.attackSlow = a + end +end +function modifier_sniper_headshot_slow.prototype.DeclareFunctions(self) + return {MODIFIER_PROPERTY_MOVESPEED_BONUS_PERCENTAGE, MODIFIER_PROPERTY_ATTACKSPEED_BONUS_CONSTANT} +end +function modifier_sniper_headshot_slow.prototype.GetModifierMoveSpeedBonus_Percentage(self) + return self.movePct +end +function modifier_sniper_headshot_slow.prototype.GetModifierAttackSpeedBonus_Constant(self) + return self.attackSlow +end +function modifier_sniper_headshot_slow.prototype.GetEffectName(self) + return "particles/units/heroes/hero_sniper/sniper_headshot_slow.vpcf" +end +function modifier_sniper_headshot_slow.prototype.GetEffectAttachType(self) + return PATTACH_OVERHEAD_FOLLOW +end +modifier_sniper_headshot_slow = __TS__Decorate( + modifier_sniper_headshot_slow, + modifier_sniper_headshot_slow, + {registerModifier(nil)}, + {kind = "class", name = "modifier_sniper_headshot_slow"} +) +____exports.modifier_sniper_headshot_slow = modifier_sniper_headshot_slow +return ____exports diff --git a/scripts/vscripts/abilities/heroes/sniper/ability_sniper_shrapnel_custom.lua b/scripts/vscripts/abilities/heroes/sniper/ability_sniper_shrapnel_custom.lua new file mode 100644 index 0000000..afb9f88 --- /dev/null +++ b/scripts/vscripts/abilities/heroes/sniper/ability_sniper_shrapnel_custom.lua @@ -0,0 +1,379 @@ +local ____lualib = require("lualib_bundle") +local __TS__Class = ____lualib.__TS__Class +local __TS__ClassExtends = ____lualib.__TS__ClassExtends +local __TS__Decorate = ____lualib.__TS__Decorate +local ____exports = {} +local ____dota_ts_adapter = require("lib.dota_ts_adapter") +local BaseAbility = ____dota_ts_adapter.BaseAbility +local BaseModifier = ____dota_ts_adapter.BaseModifier +local registerAbility = ____dota_ts_adapter.registerAbility +local registerModifier = ____dota_ts_adapter.registerModifier +local SHRAPNEL_ABILITY_NAME = "ability_sniper_shrapnel_custom" +local function isHeroUnit(self, unit) + return not not unit and not unit:IsNull() and unit:IsHero() +end +--- Каст → задержка → thinker: тик урона по врагам в радиусе + аура дебафа (замедление, +% входящего урона). +____exports.ability_sniper_shrapnel_custom = __TS__Class() +local ability_sniper_shrapnel_custom = ____exports.ability_sniper_shrapnel_custom +ability_sniper_shrapnel_custom.name = "ability_sniper_shrapnel_custom" +ability_sniper_shrapnel_custom.____file_path = "scripts/vscripts/abilities/heroes/sniper/ability_sniper_shrapnel_custom.lua" +__TS__ClassExtends(ability_sniper_shrapnel_custom, BaseAbility) +function ability_sniper_shrapnel_custom.prototype.Precache(self, context) + PrecacheResource("particle", "particles/units/heroes/hero_sniper/sniper_shrapnel_launch.vpcf", context) + PrecacheResource("particle", "particles/units/heroes/hero_sniper/sniper_shrapnel.vpcf", context) + PrecacheResource("soundfile", "soundevents/game_sounds_heroes/game_sounds_sniper.vsndevts", context) +end +function ability_sniper_shrapnel_custom.prototype.GetAOERadius(self) + return self:GetSpecialValueFor("radius") +end +function ability_sniper_shrapnel_custom.prototype.OnSpellStart(self) + if not IsServer() then + return + end + local caster = self:GetCaster() + local rawPos = self:GetCursorPosition() + local position = GetGroundPosition(rawPos, nil) + local radius = self:GetSpecialValueFor("radius") + local damageDelay = self:GetSpecialValueFor("damage_delay") + local duration = self:GetSpecialValueFor("duration") + local enemiesInAoE = FindUnitsInRadius( + caster:GetTeamNumber(), + position, + nil, + radius, + DOTA_UNIT_TARGET_TEAM_ENEMY, + DOTA_UNIT_TARGET_HERO, + DOTA_UNIT_TARGET_FLAG_NOT_ILLUSIONS + DOTA_UNIT_TARGET_FLAG_NO_INVIS + DOTA_UNIT_TARGET_FLAG_INVULNERABLE + DOTA_UNIT_TARGET_FLAG_MAGIC_IMMUNE_ENEMIES, + FIND_ANY_ORDER, + false + ) + if #enemiesInAoE >= 4 then + if RollPercentage(5) then + EmitSoundOn("sniper_snip_ability_shrapnel_06", caster) + elseif RollPercentage(75) then + local group = {"sniper_snip_ability_shrapnel_02", "sniper_snip_ability_shrapnel_04", "sniper_snip_ability_shrapnel_06"} + EmitSoundOn( + group[RandomInt(0, #group - 1) + 1], + caster + ) + end + elseif RollPercentage(75) then + local castResp = {"sniper_snip_ability_shrapnel_01", "sniper_snip_ability_shrapnel_03"} + EmitSoundOn( + castResp[RandomInt(0, #castResp - 1) + 1], + caster + ) + end + EmitSoundOn("Hero_Sniper.ShrapnelShoot", caster) + EmitSoundOnLocationWithCaster(position, "Hero_Sniper.ShrapnelShatter", caster) + local cOrig = caster:GetAbsOrigin() + local flat = Vector(position.x - cOrig.x, position.y - cOrig.y, 0) + local distance = flat:Length2D() + local direction = distance > 0 and flat:Normalized() or Vector(1, 0, 0) + local midX = cOrig.x + direction.x * (distance / 2) + local midY = cOrig.y + direction.y * (distance / 2) + local midZ = cOrig.z + direction.z * (distance / 2) + local fx = ParticleManager:CreateParticle("particles/units/heroes/hero_sniper/sniper_shrapnel_launch.vpcf", PATTACH_CUSTOMORIGIN, caster) + ParticleManager:SetParticleControlEnt( + fx, + 0, + caster, + PATTACH_POINT_FOLLOW, + "attach_attack1", + caster:GetAbsOrigin(), + true + ) + ParticleManager:SetParticleControl( + fx, + 1, + Vector(midX, midY, midZ + 1000) + ) + ParticleManager:ReleaseParticleIndex(fx) + local ability = self + Timers:CreateTimer( + damageDelay, + function() + if not IsServer() then + return nil + end + if not IsValidEntity(ability) or ability:IsNull() then + return nil + end + local sniper = ability:GetCaster() + if not isHeroUnit(nil, sniper) or not sniper:IsAlive() then + return nil + end + local thinker = CreateModifierThinker( + sniper, + ability, + ____exports.modifier_sniper_shrapnel_aura.name, + {duration = duration}, + position, + sniper:GetTeamNumber(), + false + ) + local auraMod = thinker and thinker:FindModifierByName(____exports.modifier_sniper_shrapnel_aura.name) + if auraMod ~= nil then + auraMod:bindSniper(sniper) + end + AddFOWViewer( + sniper:GetTeamNumber(), + position, + radius, + duration, + false + ) + return nil + end + ) +end +ability_sniper_shrapnel_custom = __TS__Decorate( + ability_sniper_shrapnel_custom, + ability_sniper_shrapnel_custom, + {registerAbility(nil)}, + {kind = "class", name = "ability_sniper_shrapnel_custom"} +) +____exports.ability_sniper_shrapnel_custom = ability_sniper_shrapnel_custom +--- Thinker: тик урона + аура. GetAuraOwner = Снайпер. +____exports.modifier_sniper_shrapnel_aura = __TS__Class() +local modifier_sniper_shrapnel_aura = ____exports.modifier_sniper_shrapnel_aura +modifier_sniper_shrapnel_aura.name = "modifier_sniper_shrapnel_aura" +modifier_sniper_shrapnel_aura.____file_path = "scripts/vscripts/abilities/heroes/sniper/ability_sniper_shrapnel_custom.lua" +__TS__ClassExtends(modifier_sniper_shrapnel_aura, BaseModifier) +function modifier_sniper_shrapnel_aura.prototype.____constructor(self, ...) + BaseModifier.prototype.____constructor(self, ...) + self.radius = 400 +end +function modifier_sniper_shrapnel_aura.prototype.bindSniper(self, sniper) + if not IsServer() or not isHeroUnit(nil, sniper) then + return + end + self.sniper = sniper +end +function modifier_sniper_shrapnel_aura.prototype.IsHidden(self) + return true +end +function modifier_sniper_shrapnel_aura.prototype.IsPurgable(self) + return false +end +function modifier_sniper_shrapnel_aura.prototype.IsDebuff(self) + return false +end +function modifier_sniper_shrapnel_aura.prototype.IsBuff(self) + return true +end +function modifier_sniper_shrapnel_aura.prototype.RemoveOnDeath(self) + return true +end +function modifier_sniper_shrapnel_aura.prototype.IsAura(self) + return true +end +function modifier_sniper_shrapnel_aura.prototype.GetAuraOwner(self) + return self:getSniper() or self:GetParent() +end +function modifier_sniper_shrapnel_aura.prototype.GetModifierAura(self) + return ____exports.modifier_sniper_shrapnel_slow.name +end +function modifier_sniper_shrapnel_aura.prototype.GetAuraRadius(self) + return self.radius +end +function modifier_sniper_shrapnel_aura.prototype.GetAuraSearchTeam(self) + return DOTA_UNIT_TARGET_TEAM_ENEMY +end +function modifier_sniper_shrapnel_aura.prototype.GetAuraSearchType(self) + return DOTA_UNIT_TARGET_HERO + DOTA_UNIT_TARGET_BASIC +end +function modifier_sniper_shrapnel_aura.prototype.GetAuraSearchFlags(self) + return DOTA_UNIT_TARGET_FLAG_NONE +end +function modifier_sniper_shrapnel_aura.prototype.GetAuraDuration(self) + return 0.5 +end +function modifier_sniper_shrapnel_aura.prototype.OnCreated(self) + if not IsServer() then + return + end + local ability = self:GetAbility() + local parent = self:GetParent() + if not ability or not parent then + return + end + local fromAbility = ability:GetCaster() + if isHeroUnit(nil, fromAbility) then + self.sniper = fromAbility + end + self.radius = ability:GetSpecialValueFor("radius") + local point = parent:GetAbsOrigin() + self.fx = ParticleManager:CreateParticle("particles/units/heroes/hero_sniper/sniper_shrapnel.vpcf", PATTACH_WORLDORIGIN, parent) + ParticleManager:SetParticleControl(self.fx, 0, point) + ParticleManager:SetParticleControl( + self.fx, + 1, + Vector(self.radius, self.radius, 0) + ) + ParticleManager:SetParticleControl(self.fx, 2, point) + self:AddParticle( + self.fx, + false, + false, + -1, + false, + false + ) + local interval = ability:GetSpecialValueFor("tick_interval") + self:tickDamage() + self:StartIntervalThink(interval > 0 and interval or 1) +end +function modifier_sniper_shrapnel_aura.prototype.OnIntervalThink(self) + if not IsServer() then + return + end + self:tickDamage() +end +function modifier_sniper_shrapnel_aura.prototype.OnDestroy(self) + if not IsServer() then + return + end + if self.fx ~= nil then + ParticleManager:DestroyParticle(self.fx, false) + ParticleManager:ReleaseParticleIndex(self.fx) + self.fx = nil + end +end +function modifier_sniper_shrapnel_aura.prototype.getSniper(self) + if self.sniper and not self.sniper:IsNull() and self.sniper:IsAlive() then + return self.sniper + end + local ability = self:GetAbility() + local caster = ability and ability:GetCaster() + if isHeroUnit(nil, caster) then + self.sniper = caster + return caster + end + return nil +end +function modifier_sniper_shrapnel_aura.prototype.tickDamage(self) + local ability = self:GetAbility() + local sniper = self:getSniper() + local parent = self:GetParent() + if not ability or not sniper or not parent or parent:IsNull() then + return + end + local attackPct = ability:GetSpecialValueFor("attack_damage_pct") + if attackPct <= 0 then + return + end + local enemies = FindUnitsInRadius( + sniper:GetTeamNumber(), + parent:GetAbsOrigin(), + nil, + self.radius, + DOTA_UNIT_TARGET_TEAM_ENEMY, + DOTA_UNIT_TARGET_HERO + DOTA_UNIT_TARGET_BASIC, + DOTA_UNIT_TARGET_FLAG_NONE, + FIND_ANY_ORDER, + false + ) + for ____, enemy in ipairs(enemies) do + do + if not enemy or enemy:IsNull() or not enemy:IsAlive() then + goto __continue45 + end + local damage = sniper:GetAverageTrueAttackDamage(enemy) * (attackPct / 100) + if damage <= 0 then + goto __continue45 + end + ApplyDamage({ + victim = enemy, + attacker = sniper, + damage = damage, + damage_type = DAMAGE_TYPE_PHYSICAL, + ability = ability, + damage_flags = DOTA_DAMAGE_FLAG_NO_SPELL_AMPLIFICATION + }) + end + ::__continue45:: + end +end +modifier_sniper_shrapnel_aura = __TS__Decorate( + modifier_sniper_shrapnel_aura, + modifier_sniper_shrapnel_aura, + {registerModifier(nil)}, + {kind = "class", name = "modifier_sniper_shrapnel_aura"} +) +____exports.modifier_sniper_shrapnel_aura = modifier_sniper_shrapnel_aura +--- Дебаф: замедление и +% входящего урона (урон — thinker). +____exports.modifier_sniper_shrapnel_slow = __TS__Class() +local modifier_sniper_shrapnel_slow = ____exports.modifier_sniper_shrapnel_slow +modifier_sniper_shrapnel_slow.name = "modifier_sniper_shrapnel_slow" +modifier_sniper_shrapnel_slow.____file_path = "scripts/vscripts/abilities/heroes/sniper/ability_sniper_shrapnel_custom.lua" +__TS__ClassExtends(modifier_sniper_shrapnel_slow, BaseModifier) +function modifier_sniper_shrapnel_slow.prototype.IsHidden(self) + return false +end +function modifier_sniper_shrapnel_slow.prototype.IsPurgable(self) + return false +end +function modifier_sniper_shrapnel_slow.prototype.IsDebuff(self) + return true +end +function modifier_sniper_shrapnel_slow.prototype.GetTexture(self) + return "sniper_shrapnel" +end +function modifier_sniper_shrapnel_slow.prototype.DeclareFunctions(self) + return {MODIFIER_PROPERTY_MOVESPEED_BONUS_PERCENTAGE, MODIFIER_PROPERTY_INCOMING_DAMAGE_PERCENTAGE} +end +function modifier_sniper_shrapnel_slow.prototype.OnCreated(self) + if not IsServer() then + return + end + local parent = self:GetParent() + local sniper = self:getSniper() + if not parent or not sniper then + self:Destroy() + return + end + if parent:GetTeamNumber() == sniper:GetTeamNumber() then + self:Destroy() + return + end + self.sniperEntIndex = sniper:entindex() +end +function modifier_sniper_shrapnel_slow.prototype.getShrapnelAbility(self) + local ability = self:GetAbility() + if ability and not ability:IsNull() then + return ability + end + local sniper = self:getSniper() + return sniper and sniper:FindAbilityByName(SHRAPNEL_ABILITY_NAME) +end +function modifier_sniper_shrapnel_slow.prototype.getSniper(self) + if self.sniperEntIndex ~= nil then + local unit = EntIndexToHScript(self.sniperEntIndex) + if isHeroUnit(nil, unit) then + return unit + end + end + local ability = self:GetAbility() + local caster = ability and ability:GetCaster() + if isHeroUnit(nil, caster) then + return caster + end + return nil +end +function modifier_sniper_shrapnel_slow.prototype.GetModifierMoveSpeedBonus_Percentage(self) + local ____opt_10 = self:getShrapnelAbility() + return ____opt_10 and ____opt_10:GetSpecialValueFor("slow_movement_speed") or 0 +end +function modifier_sniper_shrapnel_slow.prototype.GetModifierIncomingDamage_Percentage(self) + local ____opt_12 = self:getShrapnelAbility() + return ____opt_12 and ____opt_12:GetSpecialValueFor("incoming_damage_pct") or 0 +end +modifier_sniper_shrapnel_slow = __TS__Decorate( + modifier_sniper_shrapnel_slow, + modifier_sniper_shrapnel_slow, + {registerModifier(nil)}, + {kind = "class", name = "modifier_sniper_shrapnel_slow"} +) +____exports.modifier_sniper_shrapnel_slow = modifier_sniper_shrapnel_slow +return ____exports diff --git a/scripts/vscripts/abilities/heroes/spectre/ability_spectre_desolate_custom.lua b/scripts/vscripts/abilities/heroes/spectre/ability_spectre_desolate_custom.lua new file mode 100644 index 0000000..399f127 --- /dev/null +++ b/scripts/vscripts/abilities/heroes/spectre/ability_spectre_desolate_custom.lua @@ -0,0 +1,104 @@ +local ____lualib = require("lualib_bundle") +local __TS__Class = ____lualib.__TS__Class +local __TS__ClassExtends = ____lualib.__TS__ClassExtends +local __TS__Decorate = ____lualib.__TS__Decorate +local ____exports = {} +local ____dota_ts_adapter = require("lib.dota_ts_adapter") +local BaseAbility = ____dota_ts_adapter.BaseAbility +local BaseModifier = ____dota_ts_adapter.BaseModifier +local registerAbility = ____dota_ts_adapter.registerAbility +local registerModifier = ____dota_ts_adapter.registerModifier +____exports.ability_spectre_desolate_custom = __TS__Class() +local ability_spectre_desolate_custom = ____exports.ability_spectre_desolate_custom +ability_spectre_desolate_custom.name = "ability_spectre_desolate_custom" +ability_spectre_desolate_custom.____file_path = "scripts/vscripts/abilities/heroes/spectre/ability_spectre_desolate_custom.lua" +__TS__ClassExtends(ability_spectre_desolate_custom, BaseAbility) +function ability_spectre_desolate_custom.prototype.Precache(self, context) + PrecacheResource("particle", "particles/units/heroes/hero_spectre/spectre_desolate.vpcf", context) + PrecacheResource("soundfile", "soundevents/game_sounds_heroes/game_sounds_spectre.vsndevts", context) +end +function ability_spectre_desolate_custom.prototype.GetIntrinsicModifierName(self) + return ____exports.modifier_spectre_desolate_custom.name +end +ability_spectre_desolate_custom = __TS__Decorate( + ability_spectre_desolate_custom, + ability_spectre_desolate_custom, + {registerAbility(nil)}, + {kind = "class", name = "ability_spectre_desolate_custom"} +) +____exports.ability_spectre_desolate_custom = ability_spectre_desolate_custom +____exports.modifier_spectre_desolate_custom = __TS__Class() +local modifier_spectre_desolate_custom = ____exports.modifier_spectre_desolate_custom +modifier_spectre_desolate_custom.name = "modifier_spectre_desolate_custom" +modifier_spectre_desolate_custom.____file_path = "scripts/vscripts/abilities/heroes/spectre/ability_spectre_desolate_custom.lua" +__TS__ClassExtends(modifier_spectre_desolate_custom, BaseModifier) +function modifier_spectre_desolate_custom.prototype.IsHidden(self) + return true +end +function modifier_spectre_desolate_custom.prototype.IsPurgable(self) + return false +end +function modifier_spectre_desolate_custom.prototype.IsPurgeException(self) + return false +end +function modifier_spectre_desolate_custom.prototype.AllowIllusionDuplicate(self) + return true +end +function modifier_spectre_desolate_custom.prototype.DeclareFunctions(self) + return {MODIFIER_EVENT_ON_ATTACK_LANDED} +end +function modifier_spectre_desolate_custom.prototype.OnAttackLanded(self, event) + if not IsServer() then + return + end + local attacker = event.attacker + local target = event.target + local ability = self:GetAbility() + if not ability then + return + end + if attacker ~= self:GetParent() or target:GetTeamNumber() == attacker:GetTeamNumber() or attacker:PassivesDisabled() then + return + end + local healthPct = ability:GetSpecialValueFor("health_damage_pct") + local pureDamage = attacker:GetMaxHealth() / 100 * healthPct + if pureDamage <= 0 then + return + end + ApplyDamage({ + attacker = attacker, + victim = target, + damage = pureDamage, + damage_type = DAMAGE_TYPE_PURE, + ability = ability + }) + local particle = ParticleManager:CreateParticle("particles/units/heroes/hero_spectre/spectre_desolate.vpcf", PATTACH_POINT, target) + local ground = GetGroundPosition( + target:GetAbsOrigin(), + target + ) + ParticleManager:SetParticleControl( + particle, + 0, + Vector( + target:GetAbsOrigin().x, + target:GetAbsOrigin().y, + ground.z + 140 + ) + ) + ParticleManager:SetParticleControlForward( + particle, + 0, + attacker:GetForwardVector() + ) + ParticleManager:ReleaseParticleIndex(particle) + EmitSoundOn("Hero_Spectre.Desolate", target) +end +modifier_spectre_desolate_custom = __TS__Decorate( + modifier_spectre_desolate_custom, + modifier_spectre_desolate_custom, + {registerModifier(nil)}, + {kind = "class", name = "modifier_spectre_desolate_custom"} +) +____exports.modifier_spectre_desolate_custom = modifier_spectre_desolate_custom +return ____exports diff --git a/scripts/vscripts/abilities/heroes/spectre/ability_spectre_dispersion_custom.lua b/scripts/vscripts/abilities/heroes/spectre/ability_spectre_dispersion_custom.lua new file mode 100644 index 0000000..5ae9c47 --- /dev/null +++ b/scripts/vscripts/abilities/heroes/spectre/ability_spectre_dispersion_custom.lua @@ -0,0 +1,205 @@ +local ____lualib = require("lualib_bundle") +local __TS__Class = ____lualib.__TS__Class +local __TS__ClassExtends = ____lualib.__TS__ClassExtends +local __TS__Decorate = ____lualib.__TS__Decorate +local __TS__ArrayForEach = ____lualib.__TS__ArrayForEach +local ____exports = {} +local ____dota_ts_adapter = require("lib.dota_ts_adapter") +local BaseAbility = ____dota_ts_adapter.BaseAbility +local BaseModifier = ____dota_ts_adapter.BaseModifier +local registerAbility = ____dota_ts_adapter.registerAbility +local registerModifier = ____dota_ts_adapter.registerModifier +local ____ability_spectre_spectral_dagger_custom = require("abilities.heroes.spectre.ability_spectre_spectral_dagger_custom") +local modifier_spectre_dagger_debuff_custom = ____ability_spectre_spectral_dagger_custom.modifier_spectre_dagger_debuff_custom +____exports.ability_spectre_dispersion_custom = __TS__Class() +local ability_spectre_dispersion_custom = ____exports.ability_spectre_dispersion_custom +ability_spectre_dispersion_custom.name = "ability_spectre_dispersion_custom" +ability_spectre_dispersion_custom.____file_path = "scripts/vscripts/abilities/heroes/spectre/ability_spectre_dispersion_custom.lua" +__TS__ClassExtends(ability_spectre_dispersion_custom, BaseAbility) +function ability_spectre_dispersion_custom.prototype.Precache(self, context) + if self:GetCaster() and self:GetCaster():IsIllusion() then + return + end + PrecacheResource("particle", "particles/units/heroes/hero_spectre/spectre_dispersion_boost_effect.vpcf", context) +end +function ability_spectre_dispersion_custom.prototype.GetIntrinsicModifierName(self) + return ____exports.modifier_spectre_dispersion_custom.name +end +function ability_spectre_dispersion_custom.prototype.GetBehavior(self) + if HasShard( + nil, + self:GetCaster() + ) then + return DOTA_ABILITY_BEHAVIOR_NO_TARGET + end + return DOTA_ABILITY_BEHAVIOR_PASSIVE +end +function ability_spectre_dispersion_custom.prototype.GetDuration(self) + return self:GetSpecialValueFor("activation_duration") +end +function ability_spectre_dispersion_custom.prototype.GetManaCost(self, level) + return self:GetSpecialValueFor("activation_manacost") +end +function ability_spectre_dispersion_custom.prototype.GetCooldown(self, level) + return self:GetSpecialValueFor("activation_cooldown") +end +function ability_spectre_dispersion_custom.prototype.OnSpellStart(self) + local caster = self:GetCaster() + caster:AddNewModifier( + caster, + self, + ____exports.modifier_spectre_dispersion_custom_boosted.name, + {duration = self:GetSpecialValueFor("activation_duration")} + ) + ParticleManager:CreateParticle("particles/units/heroes/hero_spectre/spectre_dispersion_boost_effect.vpcf", PATTACH_ABSORIGIN_FOLLOW, caster) +end +ability_spectre_dispersion_custom = __TS__Decorate( + ability_spectre_dispersion_custom, + ability_spectre_dispersion_custom, + {registerAbility(nil)}, + {kind = "class", name = "ability_spectre_dispersion_custom"} +) +____exports.ability_spectre_dispersion_custom = ability_spectre_dispersion_custom +____exports.modifier_spectre_dispersion_custom = __TS__Class() +local modifier_spectre_dispersion_custom = ____exports.modifier_spectre_dispersion_custom +modifier_spectre_dispersion_custom.name = "modifier_spectre_dispersion_custom" +modifier_spectre_dispersion_custom.____file_path = "scripts/vscripts/abilities/heroes/spectre/ability_spectre_dispersion_custom.lua" +__TS__ClassExtends(modifier_spectre_dispersion_custom, BaseModifier) +function modifier_spectre_dispersion_custom.prototype.____constructor(self, ...) + BaseModifier.prototype.____constructor(self, ...) + self.parent = self:GetParent() + self.ability = self:GetAbility() +end +function modifier_spectre_dispersion_custom.prototype.IsHidden(self) + return true +end +function modifier_spectre_dispersion_custom.prototype.IsDebuff(self) + return false +end +function modifier_spectre_dispersion_custom.prototype.IsPurgable(self) + return false +end +function modifier_spectre_dispersion_custom.prototype.IsPurgeException(self) + return false +end +function modifier_spectre_dispersion_custom.prototype.IsPermanent(self) + return true +end +function modifier_spectre_dispersion_custom.prototype.AllowIllusionDuplicate(self) + return true +end +function modifier_spectre_dispersion_custom.prototype.DeclareFunctions(self) + return {MODIFIER_EVENT_ON_TAKEDAMAGE, MODIFIER_PROPERTY_INCOMING_DAMAGE_PERCENTAGE} +end +function modifier_spectre_dispersion_custom.prototype.GetModifierIncomingDamage_Percentage(self, event) + if event.attacker:GetTeamNumber() ~= self.parent:GetTeamNumber() and event.target == self.parent then + return -self.ability:GetSpecialValueFor("damage_reflection_pct") + end + return 0 +end +function modifier_spectre_dispersion_custom.prototype.OnTakeDamage(self, event) + if not IsServer() then + return + end + local parent = self:GetParent() + local ability = self:GetAbility() + local attacker = event.attacker + local damage = event.original_damage + if not attacker or not attacker:IsAlive() or parent ~= event.unit or attacker:GetTeamNumber() == parent:GetTeamNumber() or bit.band(event.damage_flags, DOTA_DAMAGE_FLAG_REFLECTION) ~= 0 or parent:PassivesDisabled() or parent:IsIllusion() then + return + end + local minRadius = ability:GetSpecialValueFor("min_radius") + local maxRadius = ability:GetSpecialValueFor("max_radius") + local enemies = FindUnitsInRadius( + parent:GetTeamNumber(), + parent:GetAbsOrigin(), + nil, + maxRadius, + DOTA_UNIT_TARGET_TEAM_ENEMY, + bit.bor(DOTA_UNIT_TARGET_HERO, DOTA_UNIT_TARGET_BASIC), + DOTA_UNIT_TARGET_FLAG_NONE, + FIND_ANY_ORDER, + false + ) + local reflectionToUse = parent:FindModifierByName(____exports.modifier_spectre_dispersion_custom_boosted.name) and (ability:GetSpecialValueFor("damage_reflection_pct") + ability:GetSpecialValueFor("activation_bonus_pct")) / 100 or ability:GetSpecialValueFor("damage_reflection_pct") / 100 + __TS__ArrayForEach( + enemies, + function(____, enemy) + local particleName = "" + local reflectDamage = 0 + local distanceCheck = true + if enemy:HasModifier(modifier_spectre_dagger_debuff_custom.name) then + distanceCheck = false + end + local distance = (enemy:GetAbsOrigin() - parent:GetAbsOrigin()):Length2D() + local multiplier = math.max(0, (maxRadius - distance) / (maxRadius - minRadius)) + reflectDamage = damage * reflectionToUse * multiplier + if not distanceCheck then + reflectDamage = damage * (reflectionToUse * ability:GetSpecialValueFor("damage_reflection_pct_boosted") / 100) + particleName = "particles/units/heroes/hero_spectre/spectre_dispersion.vpcf" + elseif distance <= minRadius then + particleName = "particles/units/heroes/hero_spectre/spectre_dispersion.vpcf" + elseif distance <= minRadius + (maxRadius - minRadius) / 2 then + particleName = "particles/units/heroes/hero_spectre/spectre_dispersion_fallback_mid.vpcf" + else + particleName = "particles/units/heroes/hero_spectre/spectre_dispersion_b_fallback_low.vpcf" + end + local particle = ParticleManager:CreateParticle(particleName, PATTACH_POINT_FOLLOW, parent) + ParticleManager:SetParticleControl( + particle, + 0, + parent:GetAbsOrigin() + ) + ParticleManager:SetParticleControl( + particle, + 1, + enemy:GetAbsOrigin() + ) + ParticleManager:SetParticleControl( + particle, + 2, + parent:GetAbsOrigin() + ) + ParticleManager:ReleaseParticleIndex(particle) + if reflectDamage > 0 then + ApplyDamage({ + attacker = parent, + victim = enemy, + damage = reflectDamage, + damage_type = event.damage_type, + ability = ability, + damage_flags = bit.bor(DOTA_DAMAGE_FLAG_REFLECTION, DOTA_DAMAGE_FLAG_HPLOSS) + }) + end + end + ) +end +modifier_spectre_dispersion_custom = __TS__Decorate( + modifier_spectre_dispersion_custom, + modifier_spectre_dispersion_custom, + {registerModifier(nil)}, + {kind = "class", name = "modifier_spectre_dispersion_custom"} +) +____exports.modifier_spectre_dispersion_custom = modifier_spectre_dispersion_custom +____exports.modifier_spectre_dispersion_custom_boosted = __TS__Class() +local modifier_spectre_dispersion_custom_boosted = ____exports.modifier_spectre_dispersion_custom_boosted +modifier_spectre_dispersion_custom_boosted.name = "modifier_spectre_dispersion_custom_boosted" +modifier_spectre_dispersion_custom_boosted.____file_path = "scripts/vscripts/abilities/heroes/spectre/ability_spectre_dispersion_custom.lua" +__TS__ClassExtends(modifier_spectre_dispersion_custom_boosted, BaseModifier) +function modifier_spectre_dispersion_custom_boosted.prototype.IsHidden(self) + return false +end +function modifier_spectre_dispersion_custom_boosted.prototype.IsPurgable(self) + return false +end +function modifier_spectre_dispersion_custom_boosted.prototype.IsPurgeException(self) + return false +end +modifier_spectre_dispersion_custom_boosted = __TS__Decorate( + modifier_spectre_dispersion_custom_boosted, + modifier_spectre_dispersion_custom_boosted, + {registerModifier(nil)}, + {kind = "class", name = "modifier_spectre_dispersion_custom_boosted"} +) +____exports.modifier_spectre_dispersion_custom_boosted = modifier_spectre_dispersion_custom_boosted +return ____exports diff --git a/scripts/vscripts/abilities/heroes/spectre/ability_spectre_haunt_custom.lua b/scripts/vscripts/abilities/heroes/spectre/ability_spectre_haunt_custom.lua new file mode 100644 index 0000000..4464c70 --- /dev/null +++ b/scripts/vscripts/abilities/heroes/spectre/ability_spectre_haunt_custom.lua @@ -0,0 +1,229 @@ +local ____lualib = require("lualib_bundle") +local __TS__Class = ____lualib.__TS__Class +local __TS__ClassExtends = ____lualib.__TS__ClassExtends +local __TS__ArrayFilter = ____lualib.__TS__ArrayFilter +local __TS__ArrayForEach = ____lualib.__TS__ArrayForEach +local __TS__Decorate = ____lualib.__TS__Decorate +local ____exports = {} +local ____dota_ts_adapter = require("lib.dota_ts_adapter") +local BaseAbility = ____dota_ts_adapter.BaseAbility +local BaseModifier = ____dota_ts_adapter.BaseModifier +local registerAbility = ____dota_ts_adapter.registerAbility +local registerModifier = ____dota_ts_adapter.registerModifier +local ____ability_spectre_spectral_dagger_custom = require("abilities.heroes.spectre.ability_spectre_spectral_dagger_custom") +local ability_spectre_spectral_dagger_custom = ____ability_spectre_spectral_dagger_custom.ability_spectre_spectral_dagger_custom +local ____ability_spectre_spectral_echo_custom = require("abilities.heroes.spectre.ability_spectre_spectral_echo_custom") +local ability_spectre_spectral_echo_custom = ____ability_spectre_spectral_echo_custom.ability_spectre_spectral_echo_custom +local modifier_spectre_spectral_echo_attack = ____ability_spectre_spectral_echo_custom.modifier_spectre_spectral_echo_attack +____exports.ability_spectre_haunt_custom = __TS__Class() +local ability_spectre_haunt_custom = ____exports.ability_spectre_haunt_custom +ability_spectre_haunt_custom.name = "ability_spectre_haunt_custom" +ability_spectre_haunt_custom.____file_path = "scripts/vscripts/abilities/heroes/spectre/ability_spectre_haunt_custom.lua" +__TS__ClassExtends(ability_spectre_haunt_custom, BaseAbility) +function ability_spectre_haunt_custom.prototype.OnSpellStart(self) + if not IsServer() then + return + end + local caster = self:GetCaster() + self:removeHauntIllusions(caster) + local illusionCount = self:GetSpecialValueFor("illusion_count") + local spawnRadius = self:GetSpecialValueFor("spawn_radius") + local spawnPositions = self:getSpawnPositions( + caster:GetAbsOrigin(), + illusionCount, + spawnRadius + ) + local echoAbility = caster:FindAbilityByName(ability_spectre_spectral_echo_custom.name) + local illusions = CreateIllusions( + caster, + caster, + { + duration = self:GetSpecialValueFor("duration"), + outgoing_damage = self:GetSpecialValueFor("illusion_damage_outgoing"), + incoming_damage = self:GetSpecialValueFor("illusion_damage_incoming"), + bounty_base = 0 + }, + illusionCount, + 0, + false, + true + ) + do + local i = 0 + while i < #illusions do + local illusion = illusions[i + 1] + local spawnPos = spawnPositions[i + 1] or caster:GetAbsOrigin() + FindClearSpaceForUnit(illusion, spawnPos, true) + illusion:AddNewModifier(caster, self, ____exports.modifier_spectre_swap_target_illusion_custom.name, {}) + if echoAbility then + illusion:AddNewModifier(caster, echoAbility, modifier_spectre_spectral_echo_attack.name, {from_haunt = 1}) + end + i = i + 1 + end + end + EmitSoundOn("Hero_Spectre.HauntCast", caster) +end +function ability_spectre_haunt_custom.prototype.removeHauntIllusions(self, caster) + __TS__ArrayForEach( + __TS__ArrayFilter( + FindUnitsInRadius( + caster:GetTeamNumber(), + caster:GetAbsOrigin(), + nil, + FIND_UNITS_EVERYWHERE, + 1, + bit.bor(1, 18), + 0, + 0, + false + ), + function(____, unit) return unit:IsIllusion() and unit:HasModifier(____exports.modifier_spectre_swap_target_illusion_custom.name) end + ), + function(____, unit) return unit:RemoveSelf() end + ) +end +function ability_spectre_haunt_custom.prototype.getSpawnPositions(self, origin, count, radius) + if count <= 1 then + return {origin} + end + local positions = {} + do + local i = 0 + while i < count do + local angle = 2 * math.pi * i / count + local offset = Vector( + math.cos(angle) * radius, + math.sin(angle) * radius, + 0 + ) + positions[#positions + 1] = origin + offset + i = i + 1 + end + end + return positions +end +ability_spectre_haunt_custom = __TS__Decorate( + ability_spectre_haunt_custom, + ability_spectre_haunt_custom, + {registerAbility(nil)}, + {kind = "class", name = "ability_spectre_haunt_custom"} +) +____exports.ability_spectre_haunt_custom = ability_spectre_haunt_custom +____exports.modifier_spectre_swap_target_illusion_custom = __TS__Class() +local modifier_spectre_swap_target_illusion_custom = ____exports.modifier_spectre_swap_target_illusion_custom +modifier_spectre_swap_target_illusion_custom.name = "modifier_spectre_swap_target_illusion_custom" +modifier_spectre_swap_target_illusion_custom.____file_path = "scripts/vscripts/abilities/heroes/spectre/ability_spectre_haunt_custom.lua" +__TS__ClassExtends(modifier_spectre_swap_target_illusion_custom, BaseModifier) +function modifier_spectre_swap_target_illusion_custom.prototype.IsHidden(self) + return true +end +function modifier_spectre_swap_target_illusion_custom.prototype.IsPurgable(self) + return false +end +function modifier_spectre_swap_target_illusion_custom.prototype.IsPurgeException(self) + return false +end +modifier_spectre_swap_target_illusion_custom = __TS__Decorate( + modifier_spectre_swap_target_illusion_custom, + modifier_spectre_swap_target_illusion_custom, + {registerModifier(nil)}, + {kind = "class", name = "modifier_spectre_swap_target_illusion_custom"} +) +____exports.modifier_spectre_swap_target_illusion_custom = modifier_spectre_swap_target_illusion_custom +____exports.ability_spectre_reality_custom = __TS__Class() +local ability_spectre_reality_custom = ____exports.ability_spectre_reality_custom +ability_spectre_reality_custom.name = "ability_spectre_reality_custom" +ability_spectre_reality_custom.____file_path = "scripts/vscripts/abilities/heroes/spectre/ability_spectre_haunt_custom.lua" +__TS__ClassExtends(ability_spectre_reality_custom, BaseAbility) +function ability_spectre_reality_custom.prototype.OnSpellStart(self) + if not IsServer() then + return + end + local caster = self:GetCaster() + local hauntIllusions = __TS__ArrayFilter( + FindUnitsInRadius( + caster:GetTeamNumber(), + caster:GetAbsOrigin(), + nil, + FIND_UNITS_EVERYWHERE, + 1, + bit.bor(1, 18), + bit.bor( + bit.bor(262144, 64), + 0 + ), + 0, + false + ), + function(____, unit) return unit:IsIllusion() and unit:HasModifier(____exports.modifier_spectre_swap_target_illusion_custom.name) and unit:IsAlive() end + ) + if #hauntIllusions == 0 then + return + end + local casterPos = caster:GetAbsOrigin() + local closestIllusion = hauntIllusions[1] + local closestDist = (closestIllusion:GetAbsOrigin() - casterPos):Length2D() + for ____, illusion in ipairs(hauntIllusions) do + local dist = (illusion:GetAbsOrigin() - casterPos):Length2D() + if dist < closestDist then + closestDist = dist + closestIllusion = illusion + end + end + local dagger = caster:FindAbilityByName(ability_spectre_spectral_dagger_custom.name) + local realityAllHaunts = HasTalent(nil, caster, "special_bonus_unique_spectre_reality_all_haunts") + if realityAllHaunts and dagger ~= nil then + for ____, illusion in ipairs(hauntIllusions) do + do + if illusion == closestIllusion then + goto __continue23 + end + self:castDaggerFromPosition( + caster, + dagger, + illusion:GetAbsOrigin() + ) + end + ::__continue23:: + end + end + local illusionPos = closestIllusion:GetAbsOrigin() + FindClearSpaceForUnit(caster, illusionPos, true) + FindClearSpaceForUnit(closestIllusion, casterPos, true) + if dagger ~= nil then + self:castDaggerFromPosition( + caster, + dagger, + caster:GetAbsOrigin() + ) + end + EmitSoundOn("Hero_Spectre.Reality", caster) +end +function ability_spectre_reality_custom.prototype.castDaggerFromPosition(self, caster, dagger, origin) + local castRange = dagger:GetCastRange(origin, nil) + caster:GetCastRangeBonus() + local enemies = FindUnitsInRadius( + caster:GetTeamNumber(), + origin, + nil, + castRange, + DOTA_UNIT_TARGET_TEAM_ENEMY, + bit.bor(DOTA_UNIT_TARGET_HERO, DOTA_UNIT_TARGET_BASIC), + DOTA_UNIT_TARGET_FLAG_NO_INVIS, + FIND_CLOSEST, + false + ) + if #enemies >= 1 then + dagger:castDaggerAt( + enemies[1]:GetAbsOrigin(), + origin + ) + end +end +ability_spectre_reality_custom = __TS__Decorate( + ability_spectre_reality_custom, + ability_spectre_reality_custom, + {registerAbility(nil)}, + {kind = "class", name = "ability_spectre_reality_custom"} +) +____exports.ability_spectre_reality_custom = ability_spectre_reality_custom +return ____exports diff --git a/scripts/vscripts/abilities/heroes/spectre/ability_spectre_shadow_step_custom.lua b/scripts/vscripts/abilities/heroes/spectre/ability_spectre_shadow_step_custom.lua new file mode 100644 index 0000000..a52b8ce --- /dev/null +++ b/scripts/vscripts/abilities/heroes/spectre/ability_spectre_shadow_step_custom.lua @@ -0,0 +1,219 @@ +local ____lualib = require("lualib_bundle") +local __TS__Class = ____lualib.__TS__Class +local __TS__ClassExtends = ____lualib.__TS__ClassExtends +local __TS__Decorate = ____lualib.__TS__Decorate +local __TS__ObjectValues = ____lualib.__TS__ObjectValues +local __TS__ArrayForEach = ____lualib.__TS__ArrayForEach +local __TS__Delete = ____lualib.__TS__Delete +local ____exports = {} +local ____dota_ts_adapter = require("lib.dota_ts_adapter") +local BaseAbility = ____dota_ts_adapter.BaseAbility +local BaseModifier = ____dota_ts_adapter.BaseModifier +local registerAbility = ____dota_ts_adapter.registerAbility +local registerModifier = ____dota_ts_adapter.registerModifier +____exports.ability_spectre_shadow_step_custom = __TS__Class() +local ability_spectre_shadow_step_custom = ____exports.ability_spectre_shadow_step_custom +ability_spectre_shadow_step_custom.name = "ability_spectre_shadow_step_custom" +ability_spectre_shadow_step_custom.____file_path = "scripts/vscripts/abilities/heroes/spectre/ability_spectre_shadow_step_custom.lua" +__TS__ClassExtends(ability_spectre_shadow_step_custom, BaseAbility) +function ability_spectre_shadow_step_custom.prototype.GetIntrinsicModifierName(self) + return ____exports.modifier_spectre_shadow_step_custom.name +end +ability_spectre_shadow_step_custom = __TS__Decorate( + ability_spectre_shadow_step_custom, + ability_spectre_shadow_step_custom, + {registerAbility(nil)}, + {kind = "class", name = "ability_spectre_shadow_step_custom"} +) +____exports.ability_spectre_shadow_step_custom = ability_spectre_shadow_step_custom +____exports.modifier_spectre_shadow_step_custom = __TS__Class() +local modifier_spectre_shadow_step_custom = ____exports.modifier_spectre_shadow_step_custom +modifier_spectre_shadow_step_custom.name = "modifier_spectre_shadow_step_custom" +modifier_spectre_shadow_step_custom.____file_path = "scripts/vscripts/abilities/heroes/spectre/ability_spectre_shadow_step_custom.lua" +__TS__ClassExtends(modifier_spectre_shadow_step_custom, BaseModifier) +function modifier_spectre_shadow_step_custom.prototype.____constructor(self, ...) + BaseModifier.prototype.____constructor(self, ...) + self.parent = self:GetParent() + self.ability = self:GetAbility() +end +function modifier_spectre_shadow_step_custom.prototype.IsHidden(self) + return true +end +function modifier_spectre_shadow_step_custom.prototype.IsPurgable(self) + return false +end +function modifier_spectre_shadow_step_custom.prototype.IsPurgeException(self) + return false +end +function modifier_spectre_shadow_step_custom.prototype.RemoveOnDeath(self) + return false +end +function modifier_spectre_shadow_step_custom.prototype.DeclareFunctions(self) + return {MODIFIER_EVENT_ON_ATTACK_LANDED} +end +function modifier_spectre_shadow_step_custom.prototype.OnAttackLanded(self, event) + if not IsServer() then + return + end + if self.parent ~= event.attacker or self.parent == event.target or self.parent:PassivesDisabled() then + return + end + local duration = self.ability:GetSpecialValueFor("duration_steal") + self.parent:AddNewModifier(self.parent, self.ability, ____exports.modifier_spectre_stack_buff.name, {duration = duration}) + event.target:AddNewModifier(self.parent, self.ability, ____exports.modifier_spectre_stack_debuff.name, {duration = duration}) +end +modifier_spectre_shadow_step_custom = __TS__Decorate( + modifier_spectre_shadow_step_custom, + modifier_spectre_shadow_step_custom, + {registerModifier(nil)}, + {kind = "class", name = "modifier_spectre_shadow_step_custom"} +) +____exports.modifier_spectre_shadow_step_custom = modifier_spectre_shadow_step_custom +____exports.modifier_spectre_stack_buff = __TS__Class() +local modifier_spectre_stack_buff = ____exports.modifier_spectre_stack_buff +modifier_spectre_stack_buff.name = "modifier_spectre_stack_buff" +modifier_spectre_stack_buff.____file_path = "scripts/vscripts/abilities/heroes/spectre/ability_spectre_shadow_step_custom.lua" +__TS__ClassExtends(modifier_spectre_stack_buff, BaseModifier) +function modifier_spectre_stack_buff.prototype.____constructor(self, ...) + BaseModifier.prototype.____constructor(self, ...) + self.ability = self:GetAbility() + self.parent = self:GetParent() + self.duration = 0 + self.healthGive = 0 + self.illusionDecrease = 0 + self.timers = {} +end +function modifier_spectre_stack_buff.prototype.IsHidden(self) + return false +end +function modifier_spectre_stack_buff.prototype.IsPurgeException(self) + return false +end +function modifier_spectre_stack_buff.prototype.IsPurgable(self) + return false +end +function modifier_spectre_stack_buff.prototype.OnCreated(self) + if not IsServer() then + return + end + self:IncrementStackCount() + self.illusionDecrease = self.ability:GetSpecialValueFor("illusion_decrease") / 100 + local healthBonusSelf = self.ability:GetSpecialValueFor("health_bonus_self") + self.healthGive = self:GetParent():IsIllusion() and healthBonusSelf * self.illusionDecrease or healthBonusSelf + self.duration = self.ability:GetSpecialValueFor("duration_steal") + self:RemoveStacksSeparately(self.duration) +end +function modifier_spectre_stack_buff.prototype.OnDestroy(self) + if not IsServer() then + return + end + __TS__ArrayForEach( + __TS__ObjectValues(self.timers), + function(____, timer) + Timers:RemoveTimer(timer) + end + ) + self.timers = {} +end +function modifier_spectre_stack_buff.prototype.OnRefresh(self) + if not IsServer() then + return + end + self:IncrementStackCount() + self:RemoveStacksSeparately(self.duration) +end +function modifier_spectre_stack_buff.prototype.RemoveStacksSeparately(self, lifeDuration) + local timer + timer = Timers:CreateTimer( + lifeDuration, + function() + __TS__Delete(self.timers, timer) + if self:GetStackCount() > 1 then + self:DecrementStackCount() + else + self.parent:RemoveModifierByName(____exports.modifier_spectre_stack_buff.name) + end + end + ) + self.timers[timer] = timer +end +function modifier_spectre_stack_buff.prototype.OnStackCountChanged(self) + if not IsServer() then + return + end + self.parent:CalculateStatBonus(true) +end +function modifier_spectre_stack_buff.prototype.DeclareFunctions(self) + return {MODIFIER_PROPERTY_EXTRA_HEALTH_BONUS} +end +function modifier_spectre_stack_buff.prototype.GetModifierExtraHealthBonus(self) + if self.parent:PassivesDisabled() then + return 0 + end + return self.healthGive * self:GetStackCount() +end +modifier_spectre_stack_buff = __TS__Decorate( + modifier_spectre_stack_buff, + modifier_spectre_stack_buff, + {registerModifier(nil)}, + {kind = "class", name = "modifier_spectre_stack_buff"} +) +____exports.modifier_spectre_stack_buff = modifier_spectre_stack_buff +____exports.modifier_spectre_stack_debuff = __TS__Class() +local modifier_spectre_stack_debuff = ____exports.modifier_spectre_stack_debuff +modifier_spectre_stack_debuff.name = "modifier_spectre_stack_debuff" +modifier_spectre_stack_debuff.____file_path = "scripts/vscripts/abilities/heroes/spectre/ability_spectre_shadow_step_custom.lua" +__TS__ClassExtends(modifier_spectre_stack_debuff, BaseModifier) +function modifier_spectre_stack_debuff.prototype.____constructor(self, ...) + BaseModifier.prototype.____constructor(self, ...) + self.healthSteal = 0 + self.illusionDecrease = 0 +end +function modifier_spectre_stack_debuff.prototype.IsHidden(self) + return false +end +function modifier_spectre_stack_debuff.prototype.IsDebuff(self) + return true +end +function modifier_spectre_stack_debuff.prototype.IsPurgeException(self) + return false +end +function modifier_spectre_stack_debuff.prototype.IsPurgable(self) + return false +end +function modifier_spectre_stack_debuff.prototype.OnCreated(self) + if not IsServer() then + return + end + self:IncrementStackCount() + self.illusionDecrease = self:GetAbility():GetSpecialValueFor("illusion_decrease") / 100 + local healthSteal = self:GetAbility():GetSpecialValueFor("health_steal") + local ____opt_0 = self:GetCaster() + self.healthSteal = ____opt_0 and ____opt_0:IsIllusion() and healthSteal * self.illusionDecrease or healthSteal +end +function modifier_spectre_stack_debuff.prototype.OnRefresh(self) + if not IsServer() then + return + end + self:IncrementStackCount() +end +function modifier_spectre_stack_debuff.prototype.OnStackCountChanged(self) + if not IsServer() then + return + end + self:GetParent():CalculateGenericBonuses() +end +function modifier_spectre_stack_debuff.prototype.DeclareFunctions(self) + return {MODIFIER_PROPERTY_EXTRA_HEALTH_BONUS} +end +function modifier_spectre_stack_debuff.prototype.GetModifierExtraHealthBonus(self) + return -self.healthSteal * self:GetStackCount() +end +modifier_spectre_stack_debuff = __TS__Decorate( + modifier_spectre_stack_debuff, + modifier_spectre_stack_debuff, + {registerModifier(nil)}, + {kind = "class", name = "modifier_spectre_stack_debuff"} +) +____exports.modifier_spectre_stack_debuff = modifier_spectre_stack_debuff +return ____exports diff --git a/scripts/vscripts/abilities/heroes/spectre/ability_spectre_spectral_dagger_custom.lua b/scripts/vscripts/abilities/heroes/spectre/ability_spectre_spectral_dagger_custom.lua new file mode 100644 index 0000000..176496f --- /dev/null +++ b/scripts/vscripts/abilities/heroes/spectre/ability_spectre_spectral_dagger_custom.lua @@ -0,0 +1,161 @@ +local ____lualib = require("lualib_bundle") +local __TS__Class = ____lualib.__TS__Class +local __TS__ClassExtends = ____lualib.__TS__ClassExtends +local __TS__Decorate = ____lualib.__TS__Decorate +local ____exports = {} +local ____dota_ts_adapter = require("lib.dota_ts_adapter") +local BaseAbility = ____dota_ts_adapter.BaseAbility +local BaseModifier = ____dota_ts_adapter.BaseModifier +local registerAbility = ____dota_ts_adapter.registerAbility +local registerModifier = ____dota_ts_adapter.registerModifier +____exports.ability_spectre_spectral_dagger_custom = __TS__Class() +local ability_spectre_spectral_dagger_custom = ____exports.ability_spectre_spectral_dagger_custom +ability_spectre_spectral_dagger_custom.name = "ability_spectre_spectral_dagger_custom" +ability_spectre_spectral_dagger_custom.____file_path = "scripts/vscripts/abilities/heroes/spectre/ability_spectre_spectral_dagger_custom.lua" +__TS__ClassExtends(ability_spectre_spectral_dagger_custom, BaseAbility) +function ability_spectre_spectral_dagger_custom.prototype.Precache(self, context) + PrecacheResource("particle", "particles/units/heroes/hero_spectre/spectre_spectral_dagger.vpcf", context) + PrecacheResource("soundfile", "soundevents/game_sounds_heroes/game_sounds_spectre.vsndevts", context) +end +function ability_spectre_spectral_dagger_custom.prototype.GetHealthCost(self, level) + local caster = self:GetCaster() + return caster:GetMaxHealth() / 100 * self:GetSpecialValueFor("health_cost_pct") +end +function ability_spectre_spectral_dagger_custom.prototype.OnSpellStart(self) + self:castDaggerAt(self:GetCursorPosition()) +end +function ability_spectre_spectral_dagger_custom.prototype.castDaggerAt(self, point, spawnOrigin) + if not IsServer() then + return + end + local caster = self:GetCaster() + local speed = self:GetSpecialValueFor("speed") + local pos = spawnOrigin or caster:GetAbsOrigin() + local targetPoint = point + if targetPoint == pos then + targetPoint = caster:GetAbsOrigin() + caster:GetForwardVector() * 25 + end + local dir = (targetPoint - pos):Normalized() + local castRange = self:GetCastRange(pos, nil) + caster:GetCastRangeBonus() + ProjectileManager:CreateLinearProjectile({ + EffectName = "particles/units/heroes/hero_spectre/spectre_spectral_dagger.vpcf", + Ability = self, + vSpawnOrigin = pos, + fDistance = castRange, + fStartRadius = self:GetSpecialValueFor("dagger_radius"), + fEndRadius = self:GetSpecialValueFor("dagger_radius"), + Source = caster, + bHasFrontalCone = false, + vVelocity = dir * speed, + iUnitTargetTeam = DOTA_UNIT_TARGET_TEAM_ENEMY, + iUnitTargetFlags = DOTA_UNIT_TARGET_FLAG_NONE, + iUnitTargetType = bit.bor(DOTA_UNIT_TARGET_HERO, DOTA_UNIT_TARGET_BASIC), + bProvidesVision = true, + iVisionRadius = self:GetSpecialValueFor("vision_radius"), + iVisionTeamNumber = caster:GetTeamNumber() + }) + local buffDuration = self:GetSpecialValueFor("buff_persistence") + caster:AddNewModifier(caster, self, ____exports.modifier_spectre_dagger_buff_custom.name, {duration = buffDuration}) + EmitSoundOn("Hero_Spectre.DaggerCast", caster) +end +function ability_spectre_spectral_dagger_custom.prototype.OnProjectileHit(self, target, location) + if not target then + return true + end + local caster = self:GetCaster() + local maxHealthDamage = caster:GetMaxHealth() / 100 * self:GetSpecialValueFor("health_cost_pct") + local totalDamage = maxHealthDamage + self:GetSpecialValueFor("damage") + ApplyDamage({ + attacker = caster, + victim = target, + damage = totalDamage, + damage_type = self:GetAbilityDamageType(), + ability = self + }) + local debuffDuration = self:GetSpecialValueFor("buff_persistence") + target:AddNewModifier(caster, self, ____exports.modifier_spectre_dagger_debuff_custom.name, {duration = debuffDuration}) + EmitSoundOn("Hero_Spectre.DaggerImpact", target) + return false +end +ability_spectre_spectral_dagger_custom = __TS__Decorate( + ability_spectre_spectral_dagger_custom, + ability_spectre_spectral_dagger_custom, + {registerAbility(nil)}, + {kind = "class", name = "ability_spectre_spectral_dagger_custom"} +) +____exports.ability_spectre_spectral_dagger_custom = ability_spectre_spectral_dagger_custom +____exports.modifier_spectre_dagger_debuff_custom = __TS__Class() +local modifier_spectre_dagger_debuff_custom = ____exports.modifier_spectre_dagger_debuff_custom +modifier_spectre_dagger_debuff_custom.name = "modifier_spectre_dagger_debuff_custom" +modifier_spectre_dagger_debuff_custom.____file_path = "scripts/vscripts/abilities/heroes/spectre/ability_spectre_spectral_dagger_custom.lua" +__TS__ClassExtends(modifier_spectre_dagger_debuff_custom, BaseModifier) +function modifier_spectre_dagger_debuff_custom.prototype.____constructor(self, ...) + BaseModifier.prototype.____constructor(self, ...) + self.movespeedSlow = 0 +end +function modifier_spectre_dagger_debuff_custom.prototype.IsDebuff(self) + return true +end +function modifier_spectre_dagger_debuff_custom.prototype.IsHidden(self) + return false +end +function modifier_spectre_dagger_debuff_custom.prototype.OnCreated(self) + local ability = self:GetAbility() + if ability then + self.movespeedSlow = -ability:GetSpecialValueFor("slow_pct") + end +end +function modifier_spectre_dagger_debuff_custom.prototype.DeclareFunctions(self) + return {MODIFIER_PROPERTY_MOVESPEED_BONUS_PERCENTAGE} +end +function modifier_spectre_dagger_debuff_custom.prototype.GetModifierMoveSpeedBonus_Percentage(self) + return self.movespeedSlow +end +modifier_spectre_dagger_debuff_custom = __TS__Decorate( + modifier_spectre_dagger_debuff_custom, + modifier_spectre_dagger_debuff_custom, + {registerModifier(nil)}, + {kind = "class", name = "modifier_spectre_dagger_debuff_custom"} +) +____exports.modifier_spectre_dagger_debuff_custom = modifier_spectre_dagger_debuff_custom +____exports.modifier_spectre_dagger_buff_custom = __TS__Class() +local modifier_spectre_dagger_buff_custom = ____exports.modifier_spectre_dagger_buff_custom +modifier_spectre_dagger_buff_custom.name = "modifier_spectre_dagger_buff_custom" +modifier_spectre_dagger_buff_custom.____file_path = "scripts/vscripts/abilities/heroes/spectre/ability_spectre_spectral_dagger_custom.lua" +__TS__ClassExtends(modifier_spectre_dagger_buff_custom, BaseModifier) +function modifier_spectre_dagger_buff_custom.prototype.____constructor(self, ...) + BaseModifier.prototype.____constructor(self, ...) + self.movespeedBonus = 0 +end +function modifier_spectre_dagger_buff_custom.prototype.IsDebuff(self) + return false +end +function modifier_spectre_dagger_buff_custom.prototype.IsHidden(self) + return false +end +function modifier_spectre_dagger_buff_custom.prototype.IsPurgable(self) + return false +end +function modifier_spectre_dagger_buff_custom.prototype.IsPurgeException(self) + return false +end +function modifier_spectre_dagger_buff_custom.prototype.OnCreated(self) + local ability = self:GetAbility() + if ability then + self.movespeedBonus = ability:GetSpecialValueFor("bonus_movespeed") + end +end +function modifier_spectre_dagger_buff_custom.prototype.DeclareFunctions(self) + return {MODIFIER_PROPERTY_MOVESPEED_BONUS_PERCENTAGE} +end +function modifier_spectre_dagger_buff_custom.prototype.GetModifierMoveSpeedBonus_Percentage(self) + return self.movespeedBonus +end +modifier_spectre_dagger_buff_custom = __TS__Decorate( + modifier_spectre_dagger_buff_custom, + modifier_spectre_dagger_buff_custom, + {registerModifier(nil)}, + {kind = "class", name = "modifier_spectre_dagger_buff_custom"} +) +____exports.modifier_spectre_dagger_buff_custom = modifier_spectre_dagger_buff_custom +return ____exports diff --git a/scripts/vscripts/abilities/heroes/spectre/ability_spectre_spectral_echo_custom.lua b/scripts/vscripts/abilities/heroes/spectre/ability_spectre_spectral_echo_custom.lua new file mode 100644 index 0000000..4fe60ac --- /dev/null +++ b/scripts/vscripts/abilities/heroes/spectre/ability_spectre_spectral_echo_custom.lua @@ -0,0 +1,360 @@ +local ____lualib = require("lualib_bundle") +local __TS__Class = ____lualib.__TS__Class +local __TS__ClassExtends = ____lualib.__TS__ClassExtends +local __TS__ArrayFilter = ____lualib.__TS__ArrayFilter +local __TS__Decorate = ____lualib.__TS__Decorate +local ____exports = {} +local ____dota_ts_adapter = require("lib.dota_ts_adapter") +local BaseAbility = ____dota_ts_adapter.BaseAbility +local BaseModifier = ____dota_ts_adapter.BaseModifier +local registerAbility = ____dota_ts_adapter.registerAbility +local registerModifier = ____dota_ts_adapter.registerModifier +local ____luck = require("utils.luck") +local rollLuckChance = ____luck.rollLuckChance +local ILLUSION_DEATH_PARTICLE = "particles/econ/items/spectre/spectre_arcana/spectre_arcana_illusion_killed_smoke_dark.vpcf" +local ILLUSION_AMBIENT_PARTICLE = "particles/econ/items/spectre/spectre_arcana/spectre_arcana_ambient.vpcf" +____exports.ability_spectre_spectral_echo_custom = __TS__Class() +local ability_spectre_spectral_echo_custom = ____exports.ability_spectre_spectral_echo_custom +ability_spectre_spectral_echo_custom.name = "ability_spectre_spectral_echo_custom" +ability_spectre_spectral_echo_custom.____file_path = "scripts/vscripts/abilities/heroes/spectre/ability_spectre_spectral_echo_custom.lua" +__TS__ClassExtends(ability_spectre_spectral_echo_custom, BaseAbility) +function ability_spectre_spectral_echo_custom.prototype.Precache(self, context) + PrecacheResource("particle", ILLUSION_DEATH_PARTICLE, context) + PrecacheResource("particle", ILLUSION_AMBIENT_PARTICLE, context) +end +function ability_spectre_spectral_echo_custom.prototype.GetIntrinsicModifierName(self) + return ____exports.modifier_spectre_spectral_echo_attack.name +end +function ability_spectre_spectral_echo_custom.prototype.trySpawnEcho(self, owner, target) + if not IsServer() or not owner:IsAlive() or not target:IsAlive() then + return false + end + local procChance = self:GetSpecialValueFor("proc_chance") + if procChance < 100 and not rollLuckChance(nil, owner, procChance / 100) then + return false + end + local shadowsPerProc = self:GetSpecialValueFor("shadows_per_proc") + local activeCount = self:countActiveEchoIllusions(owner) + if activeCount >= shadowsPerProc then + return false + end + local toSpawn = math.min(shadowsPerProc - activeCount, shadowsPerProc) + do + local i = 0 + while i < toSpawn do + self:spawnEchoIllusion(owner, target, i, toSpawn) + i = i + 1 + end + end + return toSpawn > 0 +end +function ability_spectre_spectral_echo_custom.prototype.countActiveEchoIllusions(self, owner) + local illusions = FindUnitsInRadius( + owner:GetTeamNumber(), + owner:GetAbsOrigin(), + nil, + FIND_UNITS_EVERYWHERE, + DOTA_UNIT_TARGET_TEAM_FRIENDLY, + bit.bor(DOTA_UNIT_TARGET_HERO, DOTA_UNIT_TARGET_BASIC), + DOTA_UNIT_TARGET_FLAG_NONE, + FIND_ANY_ORDER, + false + ) + return #__TS__ArrayFilter( + illusions, + function(____, unit) return unit:IsIllusion() and unit:IsAlive() and unit:GetOwner() == owner and unit:HasModifier(____exports.modifier_spectre_spectral_echo_illusion.name) end + ) +end +function ability_spectre_spectral_echo_custom.prototype.getSpawnPositionNearTarget(self, target, index, totalInWave) + local targetOrigin = target:GetAbsOrigin() + local maxRadius = self:GetSpecialValueFor("spawn_radius_near_target") + local minRadius = maxRadius * 0.35 + local angle = RandomFloat(0, 2 * math.pi) + if totalInWave > 1 then + local baseAngle = RandomFloat(0, 2 * math.pi) + angle = baseAngle + (index == 0 and 0 or math.pi) + RandomFloat(-0.35, 0.35) + end + local dist = RandomFloat(minRadius, maxRadius) + local offset = Vector( + math.cos(angle) * dist, + math.sin(angle) * dist, + 0 + ) + local pos = targetOrigin + offset + local ground = GetGroundPosition(pos, target) + return Vector(pos.x, pos.y, ground.z) +end +function ability_spectre_spectral_echo_custom.prototype.spawnEchoIllusion(self, owner, target, index, totalInWave) + local spawnPos = self:getSpawnPositionNearTarget(target, index, totalInWave) + local illusions = CreateIllusions( + owner, + owner, + { + duration = 10, + outgoing_damage = self:GetSpecialValueFor("illusion_outgoing_damage"), + incoming_damage = -200, + bounty_base = 0 + }, + 1, + 0, + false, + true + ) + if #illusions == 0 then + return + end + local illusion = illusions[1] + FindClearSpaceForUnit(illusion, spawnPos, true) + illusion:AddNewModifier( + owner, + self, + ____exports.modifier_spectre_spectral_echo_illusion.name, + {target_entindex = target:entindex()} + ) + EmitSoundOn("Hero_Spectre.Attack", illusion) +end +ability_spectre_spectral_echo_custom = __TS__Decorate( + ability_spectre_spectral_echo_custom, + ability_spectre_spectral_echo_custom, + {registerAbility(nil)}, + {kind = "class", name = "ability_spectre_spectral_echo_custom"} +) +____exports.ability_spectre_spectral_echo_custom = ability_spectre_spectral_echo_custom +____exports.modifier_spectre_spectral_echo_attack = __TS__Class() +local modifier_spectre_spectral_echo_attack = ____exports.modifier_spectre_spectral_echo_attack +modifier_spectre_spectral_echo_attack.name = "modifier_spectre_spectral_echo_attack" +modifier_spectre_spectral_echo_attack.____file_path = "scripts/vscripts/abilities/heroes/spectre/ability_spectre_spectral_echo_custom.lua" +__TS__ClassExtends(modifier_spectre_spectral_echo_attack, BaseModifier) +function modifier_spectre_spectral_echo_attack.prototype.IsHidden(self) + return true +end +function modifier_spectre_spectral_echo_attack.prototype.IsPurgable(self) + return false +end +function modifier_spectre_spectral_echo_attack.prototype.DeclareFunctions(self) + return {MODIFIER_EVENT_ON_ATTACK_LANDED} +end +function modifier_spectre_spectral_echo_attack.prototype.OnCreated(self, params) + if params and params.from_haunt then + self:SetStackCount(1) + end +end +function modifier_spectre_spectral_echo_attack.prototype.OnAttackLanded(self, event) + if not IsServer() then + return + end + local parent = self:GetParent() + local ability = self:GetAbility() + if not ability or event.attacker ~= parent then + return + end + local isHauntIllusion = self:GetStackCount() == 1 + if parent:HasModifier(____exports.modifier_spectre_spectral_echo_illusion.name) then + return + end + local ____isHauntIllusion_2 + if isHauntIllusion then + ____isHauntIllusion_2 = self:GetCaster() + else + ____isHauntIllusion_2 = parent + end + local echoOwner = ____isHauntIllusion_2 + if not echoOwner or echoOwner:IsNull() or echoOwner:PassivesDisabled() then + return + end + if parent:IsIllusion() ~= isHauntIllusion then + return + end + local target = event.target + if not target or target:IsNull() or not target:IsAlive() or target:GetTeamNumber() == echoOwner:GetTeamNumber() then + return + end + ability:trySpawnEcho(echoOwner, target) +end +modifier_spectre_spectral_echo_attack = __TS__Decorate( + modifier_spectre_spectral_echo_attack, + modifier_spectre_spectral_echo_attack, + {registerModifier(nil)}, + {kind = "class", name = "modifier_spectre_spectral_echo_attack"} +) +____exports.modifier_spectre_spectral_echo_attack = modifier_spectre_spectral_echo_attack +____exports.modifier_spectre_spectral_echo_illusion = __TS__Class() +local modifier_spectre_spectral_echo_illusion = ____exports.modifier_spectre_spectral_echo_illusion +modifier_spectre_spectral_echo_illusion.name = "modifier_spectre_spectral_echo_illusion" +modifier_spectre_spectral_echo_illusion.____file_path = "scripts/vscripts/abilities/heroes/spectre/ability_spectre_spectral_echo_custom.lua" +__TS__ClassExtends(modifier_spectre_spectral_echo_illusion, BaseModifier) +function modifier_spectre_spectral_echo_illusion.prototype.____constructor(self, ...) + BaseModifier.prototype.____constructor(self, ...) + self.attackDamagePct = 100 + self.searchRadius = 350 + self.attackSpeedBonus = 0 + self.followDistance = 140 + self.strikesRemaining = 1 +end +function modifier_spectre_spectral_echo_illusion.prototype.IsHidden(self) + return true +end +function modifier_spectre_spectral_echo_illusion.prototype.IsPurgable(self) + return false +end +function modifier_spectre_spectral_echo_illusion.prototype.OnCreated(self, params) + local ability = self:GetAbility() + if ability then + self.attackDamagePct = ability:GetSpecialValueFor("attack_damage_pct") + self.searchRadius = ability:GetSpecialValueFor("search_radius") + self.attackSpeedBonus = ability:GetSpecialValueFor("illusion_attack_speed") + self.followDistance = ability:GetSpecialValueFor("follow_distance") + self.strikesRemaining = math.max( + 1, + ability:GetSpecialValueFor("shadow_attacks") + ) + end + if params.target_entindex ~= nil then + self.attackTarget = EntIndexToHScript(params.target_entindex) + end + local parent = self:GetParent() + if IsServer() then + self:StartIntervalThink(0.25) + self:updateBehavior() + end +end +function modifier_spectre_spectral_echo_illusion.prototype.OnIntervalThink(self) + if not IsServer() then + return + end + if not self:GetParent():IsAlive() then + return + end + if not self.attackTarget or self.attackTarget:IsNull() or not self.attackTarget:IsAlive() then + self.attackTarget = nil + self.attackTarget = self:findNearestEnemy() + end + self:updateBehavior() +end +function modifier_spectre_spectral_echo_illusion.prototype.CheckState(self) + return { + [MODIFIER_STATE_INVULNERABLE] = true, + [MODIFIER_STATE_NO_HEALTH_BAR] = true, + [MODIFIER_STATE_UNSELECTABLE] = true, + [MODIFIER_STATE_NO_UNIT_COLLISION] = true, + [MODIFIER_STATE_UNTARGETABLE] = true + } +end +function modifier_spectre_spectral_echo_illusion.prototype.GetStatusEffectName(self) + return "particles/status_fx/status_effect_terrorblade_reflection.vpcf" +end +function modifier_spectre_spectral_echo_illusion.prototype.StatusEffectPriority(self) + return MODIFIER_PRIORITY_SUPER_ULTRA +end +function modifier_spectre_spectral_echo_illusion.prototype.GetEffectName(self) + return ILLUSION_AMBIENT_PARTICLE +end +function modifier_spectre_spectral_echo_illusion.prototype.GetEffectAttachType(self) + return PATTACH_ABSORIGIN_FOLLOW +end +function modifier_spectre_spectral_echo_illusion.prototype.DeclareFunctions(self) + return {MODIFIER_PROPERTY_OVERRIDE_ATTACK_DAMAGE, MODIFIER_PROPERTY_ATTACKSPEED_BONUS_CONSTANT, MODIFIER_EVENT_ON_ATTACK_LANDED} +end +function modifier_spectre_spectral_echo_illusion.prototype.OnAttackLanded(self, event) + if not IsServer() or event.attacker ~= self:GetParent() then + return + end + self.strikesRemaining = self.strikesRemaining - 1 + if self.strikesRemaining > 0 then + return + end + self:dispelIllusion() +end +function modifier_spectre_spectral_echo_illusion.prototype.GetModifierOverrideAttackDamage(self) + local owner = self:GetCaster() + if not owner or owner:IsNull() then + return 0 + end + return owner:GetDamageMax() * self.attackDamagePct / 100 +end +function modifier_spectre_spectral_echo_illusion.prototype.GetModifierAttackSpeedBonus_Constant(self) + return self.attackSpeedBonus +end +function modifier_spectre_spectral_echo_illusion.prototype.OnDestroy(self) + if not IsServer() then + return + end + local parent = self:GetParent() + local particle = ParticleManager:CreateParticle(ILLUSION_DEATH_PARTICLE, PATTACH_WORLDORIGIN, parent) + ParticleManager:SetParticleControl( + particle, + 0, + parent:GetAbsOrigin() + ) + ParticleManager:ReleaseParticleIndex(particle) +end +function modifier_spectre_spectral_echo_illusion.prototype.dispelIllusion(self) + local parent = self:GetParent() + if not parent:IsNull() and parent:IsAlive() then + parent:ForceKill(false) + end +end +function modifier_spectre_spectral_echo_illusion.prototype.findNearestEnemy(self) + local parent = self:GetParent() + local enemies = FindUnitsInRadius( + parent:GetTeamNumber(), + parent:GetAbsOrigin(), + nil, + self.searchRadius, + DOTA_UNIT_TARGET_TEAM_ENEMY, + bit.bor(DOTA_UNIT_TARGET_HERO, DOTA_UNIT_TARGET_BASIC), + DOTA_UNIT_TARGET_FLAG_NONE, + FIND_CLOSEST, + false + ) + return enemies[1] +end +function modifier_spectre_spectral_echo_illusion.prototype.updateBehavior(self) + local parent = self:GetParent() + if not parent:IsAlive() or parent:IsChanneling() then + return + end + local target = self.attackTarget + if target and not target:IsNull() and target:IsAlive() then + self:issueAttackOrder(target) + return + end + self:followOwner() +end +function modifier_spectre_spectral_echo_illusion.prototype.followOwner(self) + local parent = self:GetParent() + local owner = self:GetCaster() + if not owner or owner:IsNull() or not owner:IsAlive() then + return + end + local dist = (parent:GetAbsOrigin() - owner:GetAbsOrigin()):Length2D() + if dist <= self.followDistance then + return + end + ExecuteOrderFromTable({ + UnitIndex = parent:entindex(), + OrderType = DOTA_UNIT_ORDER_MOVE_TO_TARGET, + TargetIndex = owner:entindex(), + Queue = false + }) +end +function modifier_spectre_spectral_echo_illusion.prototype.issueAttackOrder(self, target) + local parent = self:GetParent() + if parent:GetAttackTarget() == target and parent:IsAttacking() then + return + end + ExecuteOrderFromTable({ + UnitIndex = parent:entindex(), + OrderType = DOTA_UNIT_ORDER_ATTACK_TARGET, + TargetIndex = target:entindex(), + Queue = false + }) +end +modifier_spectre_spectral_echo_illusion = __TS__Decorate( + modifier_spectre_spectral_echo_illusion, + modifier_spectre_spectral_echo_illusion, + {registerModifier(nil)}, + {kind = "class", name = "modifier_spectre_spectral_echo_illusion"} +) +____exports.modifier_spectre_spectral_echo_illusion = modifier_spectre_spectral_echo_illusion +return ____exports diff --git a/scripts/vscripts/abilities/heroes/spirit_breaker/ability_spirit_breaker_bulldoze_custom.lua b/scripts/vscripts/abilities/heroes/spirit_breaker/ability_spirit_breaker_bulldoze_custom.lua new file mode 100644 index 0000000..a60ade5 --- /dev/null +++ b/scripts/vscripts/abilities/heroes/spirit_breaker/ability_spirit_breaker_bulldoze_custom.lua @@ -0,0 +1,103 @@ +local ____lualib = require("lualib_bundle") +local __TS__Class = ____lualib.__TS__Class +local __TS__ClassExtends = ____lualib.__TS__ClassExtends +local __TS__Decorate = ____lualib.__TS__Decorate +local ____exports = {} +local ____dota_ts_adapter = require("lib.dota_ts_adapter") +local BaseAbility = ____dota_ts_adapter.BaseAbility +local BaseModifier = ____dota_ts_adapter.BaseModifier +local registerAbility = ____dota_ts_adapter.registerAbility +local registerModifier = ____dota_ts_adapter.registerModifier +____exports.ability_spirit_breaker_bulldoze_custom = __TS__Class() +local ability_spirit_breaker_bulldoze_custom = ____exports.ability_spirit_breaker_bulldoze_custom +ability_spirit_breaker_bulldoze_custom.name = "ability_spirit_breaker_bulldoze_custom" +ability_spirit_breaker_bulldoze_custom.____file_path = "scripts/vscripts/abilities/heroes/spirit_breaker/ability_spirit_breaker_bulldoze_custom.lua" +__TS__ClassExtends(ability_spirit_breaker_bulldoze_custom, BaseAbility) +function ability_spirit_breaker_bulldoze_custom.prototype.Precache(self, context) + PrecacheResource("particle", "particles/units/heroes/hero_spirit_breaker/spirit_breaker_bulldoze.vpcf", context) + PrecacheResource("soundfile", "soundevents/game_sounds_heroes/game_sounds_spirit_breaker.vsndevts", context) +end +function ability_spirit_breaker_bulldoze_custom.prototype.OnSpellStart(self) + if not IsServer() then + return + end + local caster = self:GetCaster() + local duration = self:GetSpecialValueFor("duration") + caster:AddNewModifier(caster, self, ____exports.modifier_spirit_breaker_bulldoze_buff.name, {duration = duration}) + EmitSoundOn("Hero_Spirit_Breaker.EmpoweringHaste.Cast", caster) +end +ability_spirit_breaker_bulldoze_custom = __TS__Decorate( + ability_spirit_breaker_bulldoze_custom, + ability_spirit_breaker_bulldoze_custom, + {registerAbility(nil)}, + {kind = "class", name = "ability_spirit_breaker_bulldoze_custom"} +) +____exports.ability_spirit_breaker_bulldoze_custom = ability_spirit_breaker_bulldoze_custom +____exports.modifier_spirit_breaker_bulldoze_buff = __TS__Class() +local modifier_spirit_breaker_bulldoze_buff = ____exports.modifier_spirit_breaker_bulldoze_buff +modifier_spirit_breaker_bulldoze_buff.name = "modifier_spirit_breaker_bulldoze_buff" +modifier_spirit_breaker_bulldoze_buff.____file_path = "scripts/vscripts/abilities/heroes/spirit_breaker/ability_spirit_breaker_bulldoze_custom.lua" +__TS__ClassExtends(modifier_spirit_breaker_bulldoze_buff, BaseModifier) +function modifier_spirit_breaker_bulldoze_buff.prototype.____constructor(self, ...) + BaseModifier.prototype.____constructor(self, ...) + self.moveSpeedPct = 0 + self.statusResist = 0 + self.damageBarrier = 0 +end +function modifier_spirit_breaker_bulldoze_buff.prototype.IsHidden(self) + return false +end +function modifier_spirit_breaker_bulldoze_buff.prototype.IsDebuff(self) + return false +end +function modifier_spirit_breaker_bulldoze_buff.prototype.IsPurgable(self) + return true +end +function modifier_spirit_breaker_bulldoze_buff.prototype.OnCreated(self) + local ab = self:GetAbility() + if not ab then + return + end + self.moveSpeedPct = ab:GetSpecialValueFor("movement_speed") + self.statusResist = ab:GetSpecialValueFor("status_resistance") + self.damageBarrier = ab:GetSpecialValueFor("damage_barrier") +end +function modifier_spirit_breaker_bulldoze_buff.prototype.DeclareFunctions(self) + local fns = {MODIFIER_PROPERTY_MOVESPEED_BONUS_PERCENTAGE, MODIFIER_PROPERTY_STATUS_RESISTANCE_STACKING} + if self.damageBarrier > 0 then + fns[#fns + 1] = MODIFIER_PROPERTY_INCOMING_DAMAGE_CONSTANT + end + return fns +end +function modifier_spirit_breaker_bulldoze_buff.prototype.GetModifierMoveSpeedBonus_Percentage(self) + return self.moveSpeedPct +end +function modifier_spirit_breaker_bulldoze_buff.prototype.GetModifierStatusResistanceStacking(self) + return self.statusResist +end +function modifier_spirit_breaker_bulldoze_buff.prototype.GetModifierIncomingDamageConstant(self, _event) + if not IsServer() or self.damageBarrier <= 0 then + return 0 + end + local absorb = self.damageBarrier + self.damageBarrier = 0 + self:Destroy() + return -absorb +end +function modifier_spirit_breaker_bulldoze_buff.prototype.GetEffectName(self) + return "particles/units/heroes/hero_spirit_breaker/spirit_breaker_bulldoze.vpcf" +end +function modifier_spirit_breaker_bulldoze_buff.prototype.GetEffectAttachType(self) + return PATTACH_ABSORIGIN_FOLLOW +end +function modifier_spirit_breaker_bulldoze_buff.prototype.GetTexture(self) + return "spirit_breaker_bulldoze" +end +modifier_spirit_breaker_bulldoze_buff = __TS__Decorate( + modifier_spirit_breaker_bulldoze_buff, + modifier_spirit_breaker_bulldoze_buff, + {registerModifier(nil)}, + {kind = "class", name = "modifier_spirit_breaker_bulldoze_buff"} +) +____exports.modifier_spirit_breaker_bulldoze_buff = modifier_spirit_breaker_bulldoze_buff +return ____exports diff --git a/scripts/vscripts/abilities/heroes/spirit_breaker/ability_spirit_breaker_charge_of_darkness_custom.lua b/scripts/vscripts/abilities/heroes/spirit_breaker/ability_spirit_breaker_charge_of_darkness_custom.lua new file mode 100644 index 0000000..ad54743 --- /dev/null +++ b/scripts/vscripts/abilities/heroes/spirit_breaker/ability_spirit_breaker_charge_of_darkness_custom.lua @@ -0,0 +1,363 @@ +local ____lualib = require("lualib_bundle") +local __TS__Class = ____lualib.__TS__Class +local __TS__ClassExtends = ____lualib.__TS__ClassExtends +local __TS__Decorate = ____lualib.__TS__Decorate +local ____exports = {} +local ____dota_ts_adapter = require("lib.dota_ts_adapter") +local BaseAbility = ____dota_ts_adapter.BaseAbility +local BaseModifier = ____dota_ts_adapter.BaseModifier +local BaseModifierMotionBoth = ____dota_ts_adapter.BaseModifierMotionBoth +local registerAbility = ____dota_ts_adapter.registerAbility +local registerModifier = ____dota_ts_adapter.registerModifier +local ____spirit_breaker_greater_bash_shared = require("abilities.heroes.spirit_breaker.spirit_breaker_greater_bash_shared") +local findSpiritBreakerGreaterBashAbility = ____spirit_breaker_greater_bash_shared.findSpiritBreakerGreaterBashAbility +local SPIRIT_BREAKER_CHARGE_MODIFIER = ____spirit_breaker_greater_bash_shared.SPIRIT_BREAKER_CHARGE_MODIFIER +--- Charge of Darkness — порт Elfansoer (motion + bonus MS + bash по пути). +-- +-- @see https ://github.com/Elfansoer/dota-2-lua-abilities/tree/master/scripts/vscripts/lua_abilities/spirit_breaker_charge_of_darkness_lua +____exports.ability_spirit_breaker_charge_of_darkness_custom = __TS__Class() +local ability_spirit_breaker_charge_of_darkness_custom = ____exports.ability_spirit_breaker_charge_of_darkness_custom +ability_spirit_breaker_charge_of_darkness_custom.name = "ability_spirit_breaker_charge_of_darkness_custom" +ability_spirit_breaker_charge_of_darkness_custom.____file_path = "scripts/vscripts/abilities/heroes/spirit_breaker/ability_spirit_breaker_charge_of_darkness_custom.lua" +__TS__ClassExtends(ability_spirit_breaker_charge_of_darkness_custom, BaseAbility) +function ability_spirit_breaker_charge_of_darkness_custom.prototype.Precache(self, context) + PrecacheResource("particle", "particles/units/heroes/hero_spirit_breaker/spirit_breaker_charge.vpcf", context) + PrecacheResource("particle", "particles/units/heroes/hero_spirit_breaker/spirit_breaker_charge_target.vpcf", context) + PrecacheResource("soundfile", "soundevents/game_sounds_heroes/game_sounds_spirit_breaker.vsndevts", context) +end +function ability_spirit_breaker_charge_of_darkness_custom.prototype.GetCastRange(self, _location, _target) + return 99999 +end +function ability_spirit_breaker_charge_of_darkness_custom.prototype.OnSpellStart(self) + if not IsServer() then + return + end + local caster = self:GetCaster() + local target = self:GetCursorTarget() + if not target or not target:IsAlive() then + return + end + caster:AddNewModifier( + caster, + self, + SPIRIT_BREAKER_CHARGE_MODIFIER, + {target = target:entindex()} + ) +end +ability_spirit_breaker_charge_of_darkness_custom = __TS__Decorate( + ability_spirit_breaker_charge_of_darkness_custom, + ability_spirit_breaker_charge_of_darkness_custom, + {registerAbility(nil)}, + {kind = "class", name = "ability_spirit_breaker_charge_of_darkness_custom"} +) +____exports.ability_spirit_breaker_charge_of_darkness_custom = ability_spirit_breaker_charge_of_darkness_custom +____exports.modifier_spirit_breaker_charge_of_darkness = __TS__Class() +local modifier_spirit_breaker_charge_of_darkness = ____exports.modifier_spirit_breaker_charge_of_darkness +modifier_spirit_breaker_charge_of_darkness.name = "modifier_spirit_breaker_charge_of_darkness" +modifier_spirit_breaker_charge_of_darkness.____file_path = "scripts/vscripts/abilities/heroes/spirit_breaker/ability_spirit_breaker_charge_of_darkness_custom.lua" +__TS__ClassExtends(modifier_spirit_breaker_charge_of_darkness, BaseModifierMotionBoth) +function modifier_spirit_breaker_charge_of_darkness.prototype.____constructor(self, ...) + BaseModifierMotionBoth.prototype.____constructor(self, ...) + self.bonusMs = 0 + self.radius = 0 + self.stunDuration = 0 + self.direction = Vector(0, 0, 0) + self.bashedTargets = {} + self.interrupted = false + self.orderInterruptEnabled = false + self.searchRadius = 4000 + self.treeRadius = 150 + self.minDist = 150 + self.bashOffset = 20 +end +function modifier_spirit_breaker_charge_of_darkness.prototype.IsHidden(self) + return false +end +function modifier_spirit_breaker_charge_of_darkness.prototype.IsPurgable(self) + return false +end +function modifier_spirit_breaker_charge_of_darkness.prototype.CheckState(self) + return {[MODIFIER_STATE_NO_UNIT_COLLISION] = true, [MODIFIER_STATE_FLYING_FOR_PATHING_PURPOSES_ONLY] = true} +end +function modifier_spirit_breaker_charge_of_darkness.prototype.DeclareFunctions(self) + return {MODIFIER_PROPERTY_MOVESPEED_BONUS_CONSTANT, MODIFIER_PROPERTY_IGNORE_MOVESPEED_LIMIT, MODIFIER_EVENT_ON_ORDER} +end +function modifier_spirit_breaker_charge_of_darkness.prototype.GetModifierMoveSpeedBonus_Constant(self) + return self.bonusMs +end +function modifier_spirit_breaker_charge_of_darkness.prototype.GetModifierIgnoreMovespeedLimit(self) + return 1 +end +function modifier_spirit_breaker_charge_of_darkness.prototype.OnOrder(self, params) + if not IsServer() or not self.orderInterruptEnabled then + return + end + if params.unit ~= self.parent then + return + end + local orderType = params.order_type + if orderType == DOTA_UNIT_ORDER_CAST_TARGET and params.ability_entindex ~= nil then + local orderedAbility = EntIndexToHScript(params.ability_entindex) + if orderedAbility and not orderedAbility:IsNull() and orderedAbility:GetAbilityName() == "ability_spirit_breaker_charge_of_darkness_custom" then + return + end + end + if orderType == DOTA_UNIT_ORDER_MOVE_TO_POSITION or orderType == DOTA_UNIT_ORDER_MOVE_TO_TARGET or orderType == DOTA_UNIT_ORDER_ATTACK_TARGET or orderType == DOTA_UNIT_ORDER_STOP or orderType == DOTA_UNIT_ORDER_HOLD_POSITION or orderType == DOTA_UNIT_ORDER_CAST_POSITION or orderType == DOTA_UNIT_ORDER_CAST_TARGET or orderType == DOTA_UNIT_ORDER_CAST_TARGET_TREE or orderType == DOTA_UNIT_ORDER_CAST_RUNE or orderType == DOTA_UNIT_ORDER_VECTOR_TARGET_POSITION then + self.interrupted = true + self:Destroy() + end +end +function modifier_spirit_breaker_charge_of_darkness.prototype.OnCreated(self, params) + self.parent = self:GetParent() + local ability = self:GetAbility() + if not ability then + return + end + self.bonusMs = ability:GetSpecialValueFor("movement_speed") + self.radius = ability:GetSpecialValueFor("bash_radius") + self.stunDuration = ability:GetSpecialValueFor("stun_duration") + if not IsServer() then + return + end + if params.target == nil then + self:Destroy() + return + end + local targetEnt = EntIndexToHScript(params.target) + if not targetEnt or targetEnt:IsNull() then + self:Destroy() + return + end + self.direction = self.parent:GetForwardVector() + self.bashedTargets = {} + self.interrupted = false + self.orderInterruptEnabled = false + local bashAbility = findSpiritBreakerGreaterBashAbility(nil, self.parent) + if bashAbility and bashAbility:GetLevel() >= 1 then + local intrinsicName = bashAbility:GetIntrinsicModifierName() + if intrinsicName then + local bashMod = self.parent:FindModifierByName(intrinsicName) + if bashMod then + self.bashModifier = bashMod + end + end + end + if not self:ApplyHorizontalMotionController() then + self.interrupted = true + self:Destroy() + return + end + self:setTarget(targetEnt) + ability:SetActivated(false) + EmitSoundOn("Hero_Spirit_Breaker.ChargeOfDarkness", self.parent) + Timers:CreateTimer( + 0.1, + function() + if self:IsNull() == false then + self.orderInterruptEnabled = true + end + end + ) +end +function modifier_spirit_breaker_charge_of_darkness.prototype.OnDestroy(self) + if not IsServer() then + return + end + GridNav:DestroyTreesAroundPoint( + self.parent:GetAbsOrigin(), + self.treeRadius, + true + ) + self.parent:RemoveHorizontalMotionController(self) + if self.targetDebuff and not self.targetDebuff:IsNull() then + self.targetDebuff:Destroy() + end + local ability = self:GetAbility() + if ability and not ability:IsNull() then + ability:SetActivated(true) + ability:UseResources(false, false, false, true) + end + if self.interrupted then + return + end + if not self.target or self.target:IsNull() then + return + end + if self.bashModifier and not self.bashModifier:IsNull() then + self.bashModifier:Bash(self.target, false) + end + self.target:AddNewModifier(self.parent, ability, "modifier_stunned", {duration = self.stunDuration}) + EmitSoundOn("Hero_Spirit_Breaker.Charge.Impact", self.target) + if self.target:IsAlive() then + ExecuteOrderFromTable({ + UnitIndex = self.parent:entindex(), + OrderType = DOTA_UNIT_ORDER_ATTACK_TARGET, + TargetIndex = self.target:entindex(), + Queue = false + }) + end +end +function modifier_spirit_breaker_charge_of_darkness.prototype.UpdateHorizontalMotion(self, me, dt) + if not IsServer() then + return + end + self:bashLogic() + self:cancelLogic() + if not self.target or self.target:IsNull() then + self.interrupted = true + self:Destroy() + return + end + local direction = self.target:GetAbsOrigin() - me:GetAbsOrigin() + local dist = direction:Length2D() + direction.z = 0 + direction = direction:Normalized() + if dist < self.minDist then + self:Destroy() + return + end + local speed = self:getChargeSpeed() + local pos = GetGroundPosition( + me:GetAbsOrigin() + direction * speed * dt, + me + ) + me:SetAbsOrigin(pos) + self.direction = direction + me:FaceTowards(self.target:GetAbsOrigin()) +end +function modifier_spirit_breaker_charge_of_darkness.prototype.OnHorizontalMotionInterrupted(self) + if not IsServer() then + return + end + self.interrupted = true + self:Destroy() +end +function modifier_spirit_breaker_charge_of_darkness.prototype.GetEffectName(self) + return "particles/units/heroes/hero_spirit_breaker/spirit_breaker_charge.vpcf" +end +function modifier_spirit_breaker_charge_of_darkness.prototype.GetEffectAttachType(self) + return PATTACH_ABSORIGIN_FOLLOW +end +function modifier_spirit_breaker_charge_of_darkness.prototype.getChargeSpeed(self) + local ideal = self.parent:GetIdealSpeed() + if ideal >= 200 then + return ideal + end + return math.max( + 200, + self.parent:GetBaseMoveSpeed() + self.bonusMs + ) +end +function modifier_spirit_breaker_charge_of_darkness.prototype.bashLogic(self) + if not self.bashModifier then + return + end + local loc = self.parent:GetAbsOrigin() + self.direction * self.bashOffset + local enemies = FindUnitsInRadius( + self.parent:GetTeamNumber(), + loc, + nil, + self.radius, + DOTA_UNIT_TARGET_TEAM_ENEMY, + bit.bor(DOTA_UNIT_TARGET_HERO, DOTA_UNIT_TARGET_BASIC), + DOTA_UNIT_TARGET_FLAG_MAGIC_IMMUNE_ENEMIES, + FIND_ANY_ORDER, + false + ) + for ____, enemy in ipairs(enemies) do + do + local idx = enemy:entindex() + if self.bashedTargets[idx] then + goto __continue50 + end + self.bashedTargets[idx] = true + self.bashModifier:Bash(enemy, false) + end + ::__continue50:: + end +end +function modifier_spirit_breaker_charge_of_darkness.prototype.cancelLogic(self) + if self.parent:IsHexed() or self.parent:IsStunned() or self.parent:IsRooted() then + self.interrupted = true + self:Destroy() + return + end + if not self.target or not self.target:IsAlive() then + local enemies = FindUnitsInRadius( + self.parent:GetTeamNumber(), + self.parent:GetAbsOrigin(), + nil, + self.searchRadius, + DOTA_UNIT_TARGET_TEAM_ENEMY, + bit.bor(DOTA_UNIT_TARGET_HERO, DOTA_UNIT_TARGET_BASIC), + bit.bor( + bit.bor(DOTA_UNIT_TARGET_FLAG_MAGIC_IMMUNE_ENEMIES, DOTA_UNIT_TARGET_FLAG_FOW_VISIBLE), + DOTA_UNIT_TARGET_FLAG_NO_INVIS + ), + FIND_CLOSEST, + false + ) + if #enemies < 1 then + self.interrupted = true + self:Destroy() + return + end + self:setTarget(enemies[1]) + end +end +function modifier_spirit_breaker_charge_of_darkness.prototype.setTarget(self, target) + if self.targetDebuff and not self.targetDebuff:IsNull() then + self.targetDebuff:Destroy() + end + local ability = self:GetAbility() + if not ability then + return + end + self.targetDebuff = target:AddNewModifier(self.parent, ability, ____exports.modifier_spirit_breaker_charge_target.name, {}) + self.target = target + self.bashedTargets[target:entindex()] = true +end +modifier_spirit_breaker_charge_of_darkness = __TS__Decorate( + modifier_spirit_breaker_charge_of_darkness, + modifier_spirit_breaker_charge_of_darkness, + {registerModifier(nil)}, + {kind = "class", name = "modifier_spirit_breaker_charge_of_darkness"} +) +____exports.modifier_spirit_breaker_charge_of_darkness = modifier_spirit_breaker_charge_of_darkness +____exports.modifier_spirit_breaker_charge_target = __TS__Class() +local modifier_spirit_breaker_charge_target = ____exports.modifier_spirit_breaker_charge_target +modifier_spirit_breaker_charge_target.name = "modifier_spirit_breaker_charge_target" +modifier_spirit_breaker_charge_target.____file_path = "scripts/vscripts/abilities/heroes/spirit_breaker/ability_spirit_breaker_charge_of_darkness_custom.lua" +__TS__ClassExtends(modifier_spirit_breaker_charge_target, BaseModifier) +function modifier_spirit_breaker_charge_target.prototype.IsHidden(self) + return true +end +function modifier_spirit_breaker_charge_target.prototype.IsDebuff(self) + return true +end +function modifier_spirit_breaker_charge_target.prototype.IsPurgable(self) + return false +end +function modifier_spirit_breaker_charge_target.prototype.DeclareFunctions(self) + return {MODIFIER_PROPERTY_PROVIDES_FOW_POSITION} +end +function modifier_spirit_breaker_charge_target.prototype.GetModifierProvidesFOWVision(self) + return 1 +end +function modifier_spirit_breaker_charge_target.prototype.CheckState(self) + return {[MODIFIER_STATE_PROVIDES_VISION] = true} +end +function modifier_spirit_breaker_charge_target.prototype.GetEffectName(self) + return "particles/units/heroes/hero_spirit_breaker/spirit_breaker_charge_target.vpcf" +end +function modifier_spirit_breaker_charge_target.prototype.GetEffectAttachType(self) + return PATTACH_OVERHEAD_FOLLOW +end +modifier_spirit_breaker_charge_target = __TS__Decorate( + modifier_spirit_breaker_charge_target, + modifier_spirit_breaker_charge_target, + {registerModifier(nil)}, + {kind = "class", name = "modifier_spirit_breaker_charge_target"} +) +____exports.modifier_spirit_breaker_charge_target = modifier_spirit_breaker_charge_target +return ____exports diff --git a/scripts/vscripts/abilities/heroes/spirit_breaker/ability_spirit_breaker_greater_bash_custom.lua b/scripts/vscripts/abilities/heroes/spirit_breaker/ability_spirit_breaker_greater_bash_custom.lua new file mode 100644 index 0000000..2a3f5eb --- /dev/null +++ b/scripts/vscripts/abilities/heroes/spirit_breaker/ability_spirit_breaker_greater_bash_custom.lua @@ -0,0 +1,133 @@ +local ____lualib = require("lualib_bundle") +local __TS__Class = ____lualib.__TS__Class +local __TS__ClassExtends = ____lualib.__TS__ClassExtends +local __TS__Decorate = ____lualib.__TS__Decorate +local ____exports = {} +local ____dota_ts_adapter = require("lib.dota_ts_adapter") +local BaseAbility = ____dota_ts_adapter.BaseAbility +local BaseModifier = ____dota_ts_adapter.BaseModifier +local registerAbility = ____dota_ts_adapter.registerAbility +local registerModifier = ____dota_ts_adapter.registerModifier +local ____spirit_breaker_greater_bash_shared = require("abilities.heroes.spirit_breaker.spirit_breaker_greater_bash_shared") +local performSpiritBreakerGreaterBash = ____spirit_breaker_greater_bash_shared.performSpiritBreakerGreaterBash +local rollSpiritBreakerGreaterBashProc = ____spirit_breaker_greater_bash_shared.rollSpiritBreakerGreaterBashProc +____exports.ability_spirit_breaker_greater_bash_custom = __TS__Class() +local ability_spirit_breaker_greater_bash_custom = ____exports.ability_spirit_breaker_greater_bash_custom +ability_spirit_breaker_greater_bash_custom.name = "ability_spirit_breaker_greater_bash_custom" +ability_spirit_breaker_greater_bash_custom.____file_path = "scripts/vscripts/abilities/heroes/spirit_breaker/ability_spirit_breaker_greater_bash_custom.lua" +__TS__ClassExtends(ability_spirit_breaker_greater_bash_custom, BaseAbility) +function ability_spirit_breaker_greater_bash_custom.prototype.Precache(self, context) + PrecacheResource("particle", "particles/units/heroes/hero_spirit_breaker/spirit_breaker_greater_bash.vpcf", context) + PrecacheResource("soundfile", "soundevents/game_sounds_heroes/game_sounds_spirit_breaker.vsndevts", context) +end +function ability_spirit_breaker_greater_bash_custom.prototype.GetIntrinsicModifierName(self) + return ____exports.modifier_spirit_breaker_greater_bash_intrinsic.name +end +ability_spirit_breaker_greater_bash_custom = __TS__Decorate( + ability_spirit_breaker_greater_bash_custom, + ability_spirit_breaker_greater_bash_custom, + {registerAbility(nil)}, + {kind = "class", name = "ability_spirit_breaker_greater_bash_custom"} +) +____exports.ability_spirit_breaker_greater_bash_custom = ability_spirit_breaker_greater_bash_custom +____exports.modifier_spirit_breaker_greater_bash_intrinsic = __TS__Class() +local modifier_spirit_breaker_greater_bash_intrinsic = ____exports.modifier_spirit_breaker_greater_bash_intrinsic +modifier_spirit_breaker_greater_bash_intrinsic.name = "modifier_spirit_breaker_greater_bash_intrinsic" +modifier_spirit_breaker_greater_bash_intrinsic.____file_path = "scripts/vscripts/abilities/heroes/spirit_breaker/ability_spirit_breaker_greater_bash_custom.lua" +__TS__ClassExtends(modifier_spirit_breaker_greater_bash_intrinsic, BaseModifier) +function modifier_spirit_breaker_greater_bash_intrinsic.prototype.IsHidden(self) + return true +end +function modifier_spirit_breaker_greater_bash_intrinsic.prototype.IsPurgable(self) + return false +end +function modifier_spirit_breaker_greater_bash_intrinsic.prototype.DeclareFunctions(self) + return {MODIFIER_EVENT_ON_ATTACK_LANDED} +end +function modifier_spirit_breaker_greater_bash_intrinsic.prototype.OnAttackLanded(self, event) + if not IsServer() then + return + end + local parent = self:GetParent() + local ability = self:GetAbility() + if not ability or parent:PassivesDisabled() then + return + end + if event.attacker ~= parent then + return + end + local target = event.target + if not target or not target:IsAlive() or target:GetTeamNumber() == parent:GetTeamNumber() then + return + end + if not rollSpiritBreakerGreaterBashProc(nil, parent, ability) then + return + end + self:Bash(target, false) +end +function modifier_spirit_breaker_greater_bash_intrinsic.prototype.Bash(self, target, doubleKnockback) + if not IsServer() then + return + end + local ability = self:GetAbility() + if not ability or ability:GetLevel() < 1 then + return + end + performSpiritBreakerGreaterBash( + nil, + self:GetParent(), + target, + ability, + {forceProc = true, skipAttackProcCooldown = true, knockbackDistanceMult = doubleKnockback and 2 or 1} + ) +end +modifier_spirit_breaker_greater_bash_intrinsic = __TS__Decorate( + modifier_spirit_breaker_greater_bash_intrinsic, + modifier_spirit_breaker_greater_bash_intrinsic, + {registerModifier(nil)}, + {kind = "class", name = "modifier_spirit_breaker_greater_bash_intrinsic"} +) +____exports.modifier_spirit_breaker_greater_bash_intrinsic = modifier_spirit_breaker_greater_bash_intrinsic +____exports.modifier_spirit_breaker_greater_bash_haste = __TS__Class() +local modifier_spirit_breaker_greater_bash_haste = ____exports.modifier_spirit_breaker_greater_bash_haste +modifier_spirit_breaker_greater_bash_haste.name = "modifier_spirit_breaker_greater_bash_haste" +modifier_spirit_breaker_greater_bash_haste.____file_path = "scripts/vscripts/abilities/heroes/spirit_breaker/ability_spirit_breaker_greater_bash_custom.lua" +__TS__ClassExtends(modifier_spirit_breaker_greater_bash_haste, BaseModifier) +function modifier_spirit_breaker_greater_bash_haste.prototype.____constructor(self, ...) + BaseModifier.prototype.____constructor(self, ...) + self.bonusPct = 0 +end +function modifier_spirit_breaker_greater_bash_haste.prototype.IsHidden(self) + return false +end +function modifier_spirit_breaker_greater_bash_haste.prototype.IsDebuff(self) + return false +end +function modifier_spirit_breaker_greater_bash_haste.prototype.IsPurgable(self) + return true +end +function modifier_spirit_breaker_greater_bash_haste.prototype.OnCreated(self, params) + local ____params_bonus_pct_2 = params.bonus_pct + if ____params_bonus_pct_2 == nil then + local ____opt_0 = self:GetAbility() + ____params_bonus_pct_2 = ____opt_0 and ____opt_0:GetSpecialValueFor("bonus_movespeed_pct") + end + self.bonusPct = ____params_bonus_pct_2 or 0 +end +function modifier_spirit_breaker_greater_bash_haste.prototype.DeclareFunctions(self) + return {MODIFIER_PROPERTY_MOVESPEED_BONUS_PERCENTAGE} +end +function modifier_spirit_breaker_greater_bash_haste.prototype.GetModifierMoveSpeedBonus_Percentage(self) + return self.bonusPct +end +function modifier_spirit_breaker_greater_bash_haste.prototype.GetTexture(self) + return "spirit_breaker_greater_bash" +end +modifier_spirit_breaker_greater_bash_haste = __TS__Decorate( + modifier_spirit_breaker_greater_bash_haste, + modifier_spirit_breaker_greater_bash_haste, + {registerModifier(nil)}, + {kind = "class", name = "modifier_spirit_breaker_greater_bash_haste"} +) +____exports.modifier_spirit_breaker_greater_bash_haste = modifier_spirit_breaker_greater_bash_haste +return ____exports diff --git a/scripts/vscripts/abilities/heroes/spirit_breaker/ability_spirit_breaker_nether_strike_custom.lua b/scripts/vscripts/abilities/heroes/spirit_breaker/ability_spirit_breaker_nether_strike_custom.lua new file mode 100644 index 0000000..fdb5bb2 --- /dev/null +++ b/scripts/vscripts/abilities/heroes/spirit_breaker/ability_spirit_breaker_nether_strike_custom.lua @@ -0,0 +1,85 @@ +local ____lualib = require("lualib_bundle") +local __TS__Class = ____lualib.__TS__Class +local __TS__ClassExtends = ____lualib.__TS__ClassExtends +local __TS__Decorate = ____lualib.__TS__Decorate +local ____exports = {} +local ____dota_ts_adapter = require("lib.dota_ts_adapter") +local BaseAbility = ____dota_ts_adapter.BaseAbility +local registerAbility = ____dota_ts_adapter.registerAbility +local ____spirit_breaker_greater_bash_shared = require("abilities.heroes.spirit_breaker.spirit_breaker_greater_bash_shared") +local findSpiritBreakerGreaterBashAbility = ____spirit_breaker_greater_bash_shared.findSpiritBreakerGreaterBashAbility +local performSpiritBreakerGreaterBash = ____spirit_breaker_greater_bash_shared.performSpiritBreakerGreaterBash +____exports.ability_spirit_breaker_nether_strike_custom = __TS__Class() +local ability_spirit_breaker_nether_strike_custom = ____exports.ability_spirit_breaker_nether_strike_custom +ability_spirit_breaker_nether_strike_custom.name = "ability_spirit_breaker_nether_strike_custom" +ability_spirit_breaker_nether_strike_custom.____file_path = "scripts/vscripts/abilities/heroes/spirit_breaker/ability_spirit_breaker_nether_strike_custom.lua" +__TS__ClassExtends(ability_spirit_breaker_nether_strike_custom, BaseAbility) +function ability_spirit_breaker_nether_strike_custom.prototype.Precache(self, context) + PrecacheResource("particle", "particles/units/heroes/hero_spirit_breaker/spirit_breaker_nether_strike.vpcf", context) + PrecacheResource("particle", "particles/units/heroes/hero_spirit_breaker/spirit_breaker_nether_strike_tgt.vpcf", context) + PrecacheResource("soundfile", "soundevents/game_sounds_heroes/game_sounds_spirit_breaker.vsndevts", context) +end +function ability_spirit_breaker_nether_strike_custom.prototype.OnAbilityPhaseStart(self) + if IsServer() then + EmitSoundOn( + "Hero_Spirit_Breaker.NetherStrike.Begin", + self:GetCaster() + ) + end + return true +end +function ability_spirit_breaker_nether_strike_custom.prototype.OnSpellStart(self) + if not IsServer() then + return + end + local caster = self:GetCaster() + local target = self:GetCursorTarget() + if not target or target:IsNull() or not target:IsAlive() then + return + end + local targetPos = target:GetAbsOrigin() + local behind = target:GetForwardVector() * -72 + local landPos = GetGroundPosition(targetPos + behind, nil) + FindClearSpaceForUnit(caster, landPos, true) + local faceDir = targetPos - caster:GetAbsOrigin() + faceDir.z = 0 + if faceDir:Length2D() > 0.01 then + caster:SetForwardVector(faceDir:Normalized()) + end + ApplyDamage({ + victim = target, + attacker = caster, + damage = self:GetSpecialValueFor("damage"), + damage_type = DAMAGE_TYPE_MAGICAL, + ability = self + }) + local bashAbility = findSpiritBreakerGreaterBashAbility(nil, caster) + if bashAbility then + performSpiritBreakerGreaterBash( + nil, + caster, + target, + bashAbility, + {forceProc = true, skipAttackProcCooldown = true, knockbackDistanceMult = 2} + ) + end + local impactFx = ParticleManager:CreateParticle("particles/units/heroes/hero_spirit_breaker/spirit_breaker_nether_strike_tgt.vpcf", PATTACH_ABSORIGIN_FOLLOW, target) + ParticleManager:ReleaseParticleIndex(impactFx) + EmitSoundOn("Hero_Spirit_Breaker.NetherStrike", target) + if target:IsAlive() then + ExecuteOrderFromTable({ + UnitIndex = caster:entindex(), + OrderType = DOTA_UNIT_ORDER_ATTACK_TARGET, + TargetIndex = target:entindex(), + Queue = false + }) + end +end +ability_spirit_breaker_nether_strike_custom = __TS__Decorate( + ability_spirit_breaker_nether_strike_custom, + ability_spirit_breaker_nether_strike_custom, + {registerAbility(nil)}, + {kind = "class", name = "ability_spirit_breaker_nether_strike_custom"} +) +____exports.ability_spirit_breaker_nether_strike_custom = ability_spirit_breaker_nether_strike_custom +return ____exports diff --git a/scripts/vscripts/abilities/heroes/spirit_breaker/spirit_breaker_greater_bash.lua b/scripts/vscripts/abilities/heroes/spirit_breaker/spirit_breaker_greater_bash.lua new file mode 100644 index 0000000..7b2f9ee --- /dev/null +++ b/scripts/vscripts/abilities/heroes/spirit_breaker/spirit_breaker_greater_bash.lua @@ -0,0 +1,117 @@ +local ____lualib = require("lualib_bundle") +local __TS__Class = ____lualib.__TS__Class +local __TS__ClassExtends = ____lualib.__TS__ClassExtends +local __TS__Decorate = ____lualib.__TS__Decorate +local ____exports = {} +local ____dota_ts_adapter = require("lib.dota_ts_adapter") +local BaseAbility = ____dota_ts_adapter.BaseAbility +local BaseModifier = ____dota_ts_adapter.BaseModifier +local registerAbility = ____dota_ts_adapter.registerAbility +local registerModifier = ____dota_ts_adapter.registerModifier +local ____spirit_breaker_greater_bash_shared = require("abilities.heroes.spirit_breaker.spirit_breaker_greater_bash_shared") +local performSpiritBreakerGreaterBash = ____spirit_breaker_greater_bash_shared.performSpiritBreakerGreaterBash +local rollSpiritBreakerGreaterBashProc = ____spirit_breaker_greater_bash_shared.rollSpiritBreakerGreaterBashProc +____exports.spirit_breaker_greater_bash = __TS__Class() +local spirit_breaker_greater_bash = ____exports.spirit_breaker_greater_bash +spirit_breaker_greater_bash.name = "spirit_breaker_greater_bash" +spirit_breaker_greater_bash.____file_path = "scripts/vscripts/abilities/heroes/spirit_breaker/spirit_breaker_greater_bash.lua" +__TS__ClassExtends(spirit_breaker_greater_bash, BaseAbility) +function spirit_breaker_greater_bash.prototype.Precache(self, context) + PrecacheResource("particle", "particles/units/heroes/hero_spirit_breaker/spirit_breaker_greater_bash.vpcf", context) + PrecacheResource("soundfile", "soundevents/game_sounds_heroes/game_sounds_spirit_breaker.vsndevts", context) +end +function spirit_breaker_greater_bash.prototype.GetIntrinsicModifierName(self) + return ____exports.modifier_spirit_breaker_greater_bash_intrinsic.name +end +spirit_breaker_greater_bash = __TS__Decorate( + spirit_breaker_greater_bash, + spirit_breaker_greater_bash, + {registerAbility(nil)}, + {kind = "class", name = "spirit_breaker_greater_bash"} +) +____exports.spirit_breaker_greater_bash = spirit_breaker_greater_bash +____exports.modifier_spirit_breaker_greater_bash_intrinsic = __TS__Class() +local modifier_spirit_breaker_greater_bash_intrinsic = ____exports.modifier_spirit_breaker_greater_bash_intrinsic +modifier_spirit_breaker_greater_bash_intrinsic.name = "modifier_spirit_breaker_greater_bash_intrinsic" +modifier_spirit_breaker_greater_bash_intrinsic.____file_path = "scripts/vscripts/abilities/heroes/spirit_breaker/spirit_breaker_greater_bash.lua" +__TS__ClassExtends(modifier_spirit_breaker_greater_bash_intrinsic, BaseModifier) +function modifier_spirit_breaker_greater_bash_intrinsic.prototype.IsHidden(self) + return true +end +function modifier_spirit_breaker_greater_bash_intrinsic.prototype.IsPurgable(self) + return false +end +function modifier_spirit_breaker_greater_bash_intrinsic.prototype.DeclareFunctions(self) + return {MODIFIER_EVENT_ON_ATTACK_LANDED} +end +function modifier_spirit_breaker_greater_bash_intrinsic.prototype.OnAttackLanded(self, event) + if not IsServer() then + return + end + local parent = self:GetParent() + local ability = self:GetAbility() + if not ability or parent:PassivesDisabled() then + return + end + if event.attacker ~= parent then + return + end + local target = event.target + if not target or not target:IsAlive() or target:GetTeamNumber() == parent:GetTeamNumber() then + return + end + if not rollSpiritBreakerGreaterBashProc(nil, parent, ability) then + return + end + performSpiritBreakerGreaterBash(nil, parent, target, ability) +end +modifier_spirit_breaker_greater_bash_intrinsic = __TS__Decorate( + modifier_spirit_breaker_greater_bash_intrinsic, + modifier_spirit_breaker_greater_bash_intrinsic, + {registerModifier(nil)}, + {kind = "class", name = "modifier_spirit_breaker_greater_bash_intrinsic"} +) +____exports.modifier_spirit_breaker_greater_bash_intrinsic = modifier_spirit_breaker_greater_bash_intrinsic +____exports.modifier_spirit_breaker_greater_bash_haste = __TS__Class() +local modifier_spirit_breaker_greater_bash_haste = ____exports.modifier_spirit_breaker_greater_bash_haste +modifier_spirit_breaker_greater_bash_haste.name = "modifier_spirit_breaker_greater_bash_haste" +modifier_spirit_breaker_greater_bash_haste.____file_path = "scripts/vscripts/abilities/heroes/spirit_breaker/spirit_breaker_greater_bash.lua" +__TS__ClassExtends(modifier_spirit_breaker_greater_bash_haste, BaseModifier) +function modifier_spirit_breaker_greater_bash_haste.prototype.____constructor(self, ...) + BaseModifier.prototype.____constructor(self, ...) + self.bonusPct = 0 +end +function modifier_spirit_breaker_greater_bash_haste.prototype.IsHidden(self) + return false +end +function modifier_spirit_breaker_greater_bash_haste.prototype.IsDebuff(self) + return false +end +function modifier_spirit_breaker_greater_bash_haste.prototype.IsPurgable(self) + return true +end +function modifier_spirit_breaker_greater_bash_haste.prototype.OnCreated(self, params) + local ____params_bonus_pct_2 = params.bonus_pct + if ____params_bonus_pct_2 == nil then + local ____opt_0 = self:GetAbility() + ____params_bonus_pct_2 = ____opt_0 and ____opt_0:GetSpecialValueFor("bonus_movespeed_pct") + end + self.bonusPct = ____params_bonus_pct_2 or 0 +end +function modifier_spirit_breaker_greater_bash_haste.prototype.DeclareFunctions(self) + return {MODIFIER_PROPERTY_MOVESPEED_BONUS_PERCENTAGE} +end +function modifier_spirit_breaker_greater_bash_haste.prototype.GetModifierMoveSpeedBonus_Percentage(self) + return self.bonusPct +end +function modifier_spirit_breaker_greater_bash_haste.prototype.GetTexture(self) + return "spirit_breaker_greater_bash" +end +modifier_spirit_breaker_greater_bash_haste = __TS__Decorate( + modifier_spirit_breaker_greater_bash_haste, + modifier_spirit_breaker_greater_bash_haste, + {registerModifier(nil)}, + {kind = "class", name = "modifier_spirit_breaker_greater_bash_haste"} +) +____exports.modifier_spirit_breaker_greater_bash_haste = modifier_spirit_breaker_greater_bash_haste +return ____exports diff --git a/scripts/vscripts/abilities/heroes/spirit_breaker/spirit_breaker_greater_bash_shared.lua b/scripts/vscripts/abilities/heroes/spirit_breaker/spirit_breaker_greater_bash_shared.lua new file mode 100644 index 0000000..400584e --- /dev/null +++ b/scripts/vscripts/abilities/heroes/spirit_breaker/spirit_breaker_greater_bash_shared.lua @@ -0,0 +1,117 @@ +local ____lualib = require("lualib_bundle") +local Map = ____lualib.Map +local __TS__New = ____lualib.__TS__New +local ____exports = {} +local ____luck = require("utils.luck") +local calculateLuckChance = ____luck.calculateLuckChance +____exports.SPIRIT_BREAKER_GREATER_BASH_ABILITY = "ability_spirit_breaker_greater_bash_custom" +--- Имя модификатора Charge — как в ваниле (SpawnManager и AI). +____exports.SPIRIT_BREAKER_CHARGE_MODIFIER = "modifier_spirit_breaker_charge_of_darkness" +local GREATER_BASH_PRD_SLOT = 71 +function ____exports.findSpiritBreakerGreaterBashAbility(self, caster) + local ab = caster:FindAbilityByName(____exports.SPIRIT_BREAKER_GREATER_BASH_ABILITY) + if not ab or ab:IsNull() then + return nil + end + return ab +end +--- Последний момент срабатывания bash по автоатаке (не Charge/Nether). +local lastAttackBashGameTime = __TS__New(Map) +function ____exports.performSpiritBreakerGreaterBash(self, caster, target, ability, opts) + if not IsServer() then + return false + end + if not caster or caster:IsNull() or not caster:IsAlive() then + return false + end + if not target or target:IsNull() or not target:IsAlive() then + return false + end + if not ability or ability:IsNull() then + return false + end + if target:GetTeamNumber() == caster:GetTeamNumber() then + return false + end + if target:IsInvulnerable() then + return false + end + local bashAbility = ability:GetAbilityName() == ____exports.SPIRIT_BREAKER_GREATER_BASH_ABILITY and ability or ____exports.findSpiritBreakerGreaterBashAbility(nil, caster) + if not bashAbility then + return false + end + if not (opts and opts.skipAttackProcCooldown) then + local icd = bashAbility:GetSpecialValueFor("AbilityCooldown") + local last = lastAttackBashGameTime:get(caster:entindex()) or -999 + local now = GameRules:GetGameTime() + if not (opts and opts.forceProc) and now < last + icd then + return false + end + if not (opts and opts.forceProc) then + lastAttackBashGameTime:set( + caster:entindex(), + now + ) + end + end + local damagePct = bashAbility:GetSpecialValueFor("damage") + local creepMult = bashAbility:GetSpecialValueFor("creep_multiplier") + local stunDuration = bashAbility:GetSpecialValueFor("duration") + local knockbackDuration = bashAbility:GetSpecialValueFor("knockback_duration") + local knockbackDistance = bashAbility:GetSpecialValueFor("knockback_distance") + local knockbackHeight = bashAbility:GetSpecialValueFor("knockback_height") + local msBonusPct = bashAbility:GetSpecialValueFor("bonus_movespeed_pct") + local msBonusDuration = bashAbility:GetSpecialValueFor("movespeed_duration") + local knockMult = opts and opts.knockbackDistanceMult or 1 + knockbackDistance = knockbackDistance * knockMult + local moveSpeed = math.max( + 1, + caster:GetIdealSpeed() + ) + local damage = moveSpeed * damagePct / 100 + if not target:IsHero() then + damage = damage * creepMult + end + local casterPos = caster:GetAbsOrigin() + local targetPos = target:GetAbsOrigin() + target:AddNewModifier(caster, bashAbility, "modifier_stunned", {duration = stunDuration}) + target:AddNewModifier(caster, bashAbility, "modifier_knockback", { + center_x = casterPos.x, + center_y = casterPos.y, + center_z = casterPos.z, + knockback_duration = knockbackDuration, + knockback_distance = knockbackDistance, + knockback_height = knockbackHeight, + should_stun = 0 + }) + ApplyDamage({ + victim = target, + attacker = caster, + damage = damage, + damage_type = DAMAGE_TYPE_MAGICAL, + ability = bashAbility, + damage_flags = DOTA_DAMAGE_FLAG_NONE + }) + local bashFx = ParticleManager:CreateParticle("particles/units/heroes/hero_spirit_breaker/spirit_breaker_greater_bash.vpcf", PATTACH_ABSORIGIN_FOLLOW, target) + ParticleManager:SetParticleControl(bashFx, 0, targetPos) + ParticleManager:ReleaseParticleIndex(bashFx) + EmitSoundOn("Hero_Spirit_Breaker.GreaterBash", target) + if msBonusPct > 0 and msBonusDuration > 0 then + caster:AddNewModifier(caster, bashAbility, "modifier_spirit_breaker_greater_bash_haste", {duration = msBonusDuration, bonus_pct = msBonusPct}) + end + return true +end +function ____exports.rollSpiritBreakerGreaterBashProc(self, caster, bashAbility) + local basePct = bashAbility:GetSpecialValueFor("chance_pct") + local pid = caster:GetPlayerOwnerID() + if pid >= 0 and caster:IsRealHero() then + local luckPct = math.floor(calculateLuckChance(nil, caster, basePct / 100) * 100) + local clamped = math.min( + 95, + math.max(5, luckPct) + ) + return RollPseudoRandomPercentage(clamped, GREATER_BASH_PRD_SLOT, caster) + end + return RollPercentage(basePct) +end +return ____exports diff --git a/scripts/vscripts/abilities/heroes/sven/ability_sven_gods_strength_custom.lua b/scripts/vscripts/abilities/heroes/sven/ability_sven_gods_strength_custom.lua new file mode 100644 index 0000000..ab72e95 --- /dev/null +++ b/scripts/vscripts/abilities/heroes/sven/ability_sven_gods_strength_custom.lua @@ -0,0 +1,143 @@ +local ____lualib = require("lualib_bundle") +local __TS__Class = ____lualib.__TS__Class +local __TS__ClassExtends = ____lualib.__TS__ClassExtends +local __TS__Decorate = ____lualib.__TS__Decorate +local ____exports = {} +local ____dota_ts_adapter = require("lib.dota_ts_adapter") +local BaseAbility = ____dota_ts_adapter.BaseAbility +local registerAbility = ____dota_ts_adapter.registerAbility +local ____dota_ts_adapter = require("lib.dota_ts_adapter") +local BaseModifier = ____dota_ts_adapter.BaseModifier +local registerModifier = ____dota_ts_adapter.registerModifier +require("utils.utils") +____exports.ability_sven_gods_strength_custom = __TS__Class() +local ability_sven_gods_strength_custom = ____exports.ability_sven_gods_strength_custom +ability_sven_gods_strength_custom.name = "ability_sven_gods_strength_custom" +ability_sven_gods_strength_custom.____file_path = "scripts/vscripts/abilities/heroes/sven/ability_sven_gods_strength_custom.lua" +__TS__ClassExtends(ability_sven_gods_strength_custom, BaseAbility) +function ability_sven_gods_strength_custom.prototype.GetIntrinsicModifierName(self) + return "modifier_sven_gods_strength_custom_passive" +end +function ability_sven_gods_strength_custom.prototype.OnSpellStart(self) + local caster = self:GetCaster() + self.bonusDamagePercentage = self:GetSpecialValueFor("gods_strength_damage_bonus") + self.duration = self:GetSpecialValueFor("duration") + caster:AddNewModifier(caster, self, "modifier_sven_gods_strength_custom", {duration = self.duration}) + caster:EmitSound("Hero_Sven.GodsStrength") + local particleId = ParticleManager:CreateParticle("particles/units/heroes/hero_sven/sven_spell_gods_strength.vpcf", PATTACH_ABSORIGIN_FOLLOW, caster) + ParticleManager:ReleaseParticleIndex(particleId) +end +ability_sven_gods_strength_custom = __TS__Decorate( + ability_sven_gods_strength_custom, + ability_sven_gods_strength_custom, + {registerAbility(nil)}, + {kind = "class", name = "ability_sven_gods_strength_custom"} +) +____exports.ability_sven_gods_strength_custom = ability_sven_gods_strength_custom +____exports.modifier_sven_gods_strength_custom = __TS__Class() +local modifier_sven_gods_strength_custom = ____exports.modifier_sven_gods_strength_custom +modifier_sven_gods_strength_custom.name = "modifier_sven_gods_strength_custom" +modifier_sven_gods_strength_custom.____file_path = "scripts/vscripts/abilities/heroes/sven/ability_sven_gods_strength_custom.lua" +__TS__ClassExtends(modifier_sven_gods_strength_custom, BaseModifier) +function modifier_sven_gods_strength_custom.prototype.____constructor(self, ...) + BaseModifier.prototype.____constructor(self, ...) + self.bonusDamagePercentage = 0 + self.bonusArmorPercentage = 0 + self.bonusStatusResistance = 0 +end +function modifier_sven_gods_strength_custom.prototype.IsHidden(self) + return false +end +function modifier_sven_gods_strength_custom.prototype.IsDebuff(self) + return false +end +function modifier_sven_gods_strength_custom.prototype.IsPurgable(self) + return false +end +function modifier_sven_gods_strength_custom.prototype.GetEffectName(self) + return "particles/units/heroes/hero_sven/sven_gods_strength_hero_effect.vpcf" +end +function modifier_sven_gods_strength_custom.prototype.GetEffectAttachType(self) + return PATTACH_ABSORIGIN_FOLLOW +end +function modifier_sven_gods_strength_custom.prototype.GetStatusEffectName(self) + return "particles/status_fx/status_effect_gods_strength.vpcf" +end +function modifier_sven_gods_strength_custom.prototype.StatusEffectPriority(self) + return 25 +end +function modifier_sven_gods_strength_custom.prototype.GetHeroEffectName(self) + return "particles/units/heroes/hero_sven/sven_gods_strength_hero_effect.vpcf" +end +function modifier_sven_gods_strength_custom.prototype.HeroEffectPriority(self) + return 25 +end +function modifier_sven_gods_strength_custom.prototype.OnDestroy(self) + if IsClient() then + return + end + if self.particleEffect then + ParticleManager:DestroyParticle(self.particleEffect, false) + ParticleManager:ReleaseParticleIndex(self.particleEffect) + end +end +function modifier_sven_gods_strength_custom.prototype.DeclareFunctions(self) + return {MODIFIER_PROPERTY_DAMAGEOUTGOING_PERCENTAGE, MODIFIER_PROPERTY_STATS_STRENGTH_BONUS, MODIFIER_PROPERTY_MAGICAL_RESISTANCE_BONUS} +end +function modifier_sven_gods_strength_custom.prototype.GetModifierBonusStats_Strength(self) + return self:GetAbility():GetSpecialValueFor("gods_strength_bonus_strength_pct") * self:GetParent():GetPhysicalArmorValue(true) +end +function modifier_sven_gods_strength_custom.prototype.GetModifierDamageOutgoing_Percentage(self) + return self:GetAbility():GetSpecialValueFor("gods_strength_damage_bonus") +end +function modifier_sven_gods_strength_custom.prototype.GetModifierMagicalResistanceBonus(self) + local ability = self:GetAbility() + if not ability then + return 0 + end + if not HasShard( + nil, + self:GetParent() + ) then + return 0 + end + return ability:GetSpecialValueFor("gods_strength_shard_magic_resist") +end +modifier_sven_gods_strength_custom = __TS__Decorate( + modifier_sven_gods_strength_custom, + modifier_sven_gods_strength_custom, + {registerModifier(nil)}, + {kind = "class", name = "modifier_sven_gods_strength_custom"} +) +____exports.modifier_sven_gods_strength_custom = modifier_sven_gods_strength_custom +____exports.modifier_sven_gods_strength_custom_passive = __TS__Class() +local modifier_sven_gods_strength_custom_passive = ____exports.modifier_sven_gods_strength_custom_passive +modifier_sven_gods_strength_custom_passive.name = "modifier_sven_gods_strength_custom_passive" +modifier_sven_gods_strength_custom_passive.____file_path = "scripts/vscripts/abilities/heroes/sven/ability_sven_gods_strength_custom.lua" +__TS__ClassExtends(modifier_sven_gods_strength_custom_passive, BaseModifier) +function modifier_sven_gods_strength_custom_passive.prototype.IsHidden(self) + return true +end +function modifier_sven_gods_strength_custom_passive.prototype.IsDebuff(self) + return false +end +function modifier_sven_gods_strength_custom_passive.prototype.IsPurgable(self) + return false +end +function modifier_sven_gods_strength_custom_passive.prototype.DeclareFunctions(self) + return {MODIFIER_PROPERTY_DAMAGEOUTGOING_PERCENTAGE} +end +function modifier_sven_gods_strength_custom_passive.prototype.GetModifierDamageOutgoing_Percentage(self) + if self:GetParent():PassivesDisabled() then + return 0 + end + return self:GetAbility():GetSpecialValueFor("gods_strength_damage_bonus_per_health") * (self:GetParent():GetMaxHealth() - self:GetParent():GetHealth()) +end +modifier_sven_gods_strength_custom_passive = __TS__Decorate( + modifier_sven_gods_strength_custom_passive, + modifier_sven_gods_strength_custom_passive, + {registerModifier(nil)}, + {kind = "class", name = "modifier_sven_gods_strength_custom_passive"} +) +____exports.modifier_sven_gods_strength_custom_passive = modifier_sven_gods_strength_custom_passive +return ____exports diff --git a/scripts/vscripts/abilities/heroes/sven/ability_sven_great_cleave_custom.lua b/scripts/vscripts/abilities/heroes/sven/ability_sven_great_cleave_custom.lua new file mode 100644 index 0000000..d3164b5 --- /dev/null +++ b/scripts/vscripts/abilities/heroes/sven/ability_sven_great_cleave_custom.lua @@ -0,0 +1,250 @@ +local ____lualib = require("lualib_bundle") +local __TS__Class = ____lualib.__TS__Class +local __TS__ClassExtends = ____lualib.__TS__ClassExtends +local Set = ____lualib.Set +local __TS__New = ____lualib.__TS__New +local __TS__Decorate = ____lualib.__TS__Decorate +local ____exports = {} +local ____dota_ts_adapter = require("lib.dota_ts_adapter") +local BaseAbility = ____dota_ts_adapter.BaseAbility +local BaseModifier = ____dota_ts_adapter.BaseModifier +local registerAbility = ____dota_ts_adapter.registerAbility +local registerModifier = ____dota_ts_adapter.registerModifier +____exports.ability_sven_great_cleave_custom = __TS__Class() +local ability_sven_great_cleave_custom = ____exports.ability_sven_great_cleave_custom +ability_sven_great_cleave_custom.name = "ability_sven_great_cleave_custom" +ability_sven_great_cleave_custom.____file_path = "scripts/vscripts/abilities/heroes/sven/ability_sven_great_cleave_custom.lua" +__TS__ClassExtends(ability_sven_great_cleave_custom, BaseAbility) +function ability_sven_great_cleave_custom.prototype.____constructor(self, ...) + BaseAbility.prototype.____constructor(self, ...) + self.hitTargets = __TS__New(Set) +end +function ability_sven_great_cleave_custom.prototype.ClearHitTargets(self) + self.hitTargets:clear() +end +function ability_sven_great_cleave_custom.prototype.GetIntrinsicModifierName(self) + return "modifier_sven_great_cleave_custom" +end +function ability_sven_great_cleave_custom.prototype.OnSpellStart(self) + self:SetActivated(false) + local ____opt_0 = self:GetCaster() + if ____opt_0 ~= nil then + ____opt_0:AddNewModifier( + self:GetCaster(), + self, + "modifier_sven_great_cleave_custom_active", + {} + ) + end + local stackingCritModifier = self:GetCaster():FindModifierByName("modifier_stacking_crit") + if stackingCritModifier then + stackingCritModifier:GuaranteeNextCrit() + end +end +function ability_sven_great_cleave_custom.prototype.GetHealthCost(self, level) + if self:GetSpecialValueFor("cleave_damage_multiple_facet") > 0 then + return self:GetCaster():GetHealth() * (self:GetSpecialValueFor("health_cost_pct") / 100) + end + return 0 +end +function ability_sven_great_cleave_custom.prototype.GetBehavior(self) + if self:GetSpecialValueFor("cleave_damage_multiple_facet") > 0 then + return bit.bor(DOTA_ABILITY_BEHAVIOR_NO_TARGET, DOTA_ABILITY_BEHAVIOR_IMMEDIATE) + end + return DOTA_ABILITY_BEHAVIOR_PASSIVE +end +function ability_sven_great_cleave_custom.prototype.OnProjectileHit(self, target, location) + if not target then + return + end + local caster = self:GetCaster() + if not caster then + return + end + local entityIndex = target:GetEntityIndex() + if self.hitTargets:has(entityIndex) then + return + end + self.hitTargets:add(entityIndex) + local ____opt_2 = self:GetCaster() + if ____opt_2 ~= nil then + ____opt_2:AddNewModifier( + self:GetCaster(), + self, + "modifier_sven_great_cleave_custom_damage", + {} + ) + end + caster:PerformAttack( + target, + true, + true, + true, + false, + false, + false, + true + ) + local ____opt_4 = self:GetCaster() + if ____opt_4 ~= nil then + ____opt_4:RemoveModifierByName("modifier_sven_great_cleave_custom_damage") + end +end +ability_sven_great_cleave_custom = __TS__Decorate( + ability_sven_great_cleave_custom, + ability_sven_great_cleave_custom, + {registerAbility(nil)}, + {kind = "class", name = "ability_sven_great_cleave_custom"} +) +____exports.ability_sven_great_cleave_custom = ability_sven_great_cleave_custom +____exports.modifier_sven_great_cleave_custom = __TS__Class() +local modifier_sven_great_cleave_custom = ____exports.modifier_sven_great_cleave_custom +modifier_sven_great_cleave_custom.name = "modifier_sven_great_cleave_custom" +modifier_sven_great_cleave_custom.____file_path = "scripts/vscripts/abilities/heroes/sven/ability_sven_great_cleave_custom.lua" +__TS__ClassExtends(modifier_sven_great_cleave_custom, BaseModifier) +function modifier_sven_great_cleave_custom.prototype.____constructor(self, ...) + BaseModifier.prototype.____constructor(self, ...) + self.cleaveDamage = 0 + self.cleaveRadius = 0 + self.startWidth = 0 + self.endWidth = 0 +end +function modifier_sven_great_cleave_custom.prototype.IsHidden(self) + return true +end +function modifier_sven_great_cleave_custom.prototype.DeclareFunctions(self) + return {MODIFIER_EVENT_ON_ATTACK_LANDED} +end +function modifier_sven_great_cleave_custom.prototype.OnCreated(self) + local ability = self:GetAbility() + if not ability then + return + end + self.cleaveDamage = ability:GetSpecialValueFor("cleave_damage_pct") + self.cleaveRadius = ability:GetSpecialValueFor("cleave_radius") + self.startWidth = ability:GetSpecialValueFor("cleave_starting_width") + self.endWidth = ability:GetSpecialValueFor("cleave_ending_width") +end +function modifier_sven_great_cleave_custom.prototype.OnAttackLanded(self, event) + if not IsServer() then + return + end + local parent = event.attacker + if parent ~= self:GetParent() then + return + end + if parent:PassivesDisabled() then + return + end + local ability = self:GetAbility() + if not ability then + return + end + local target = event.target + local attackDamage = parent:GetAverageTrueAttackDamage(target) + local cleaveDamage = attackDamage * (self.cleaveDamage / 100) + DoCleaveAttack( + parent, + target, + ability, + cleaveDamage, + self.startWidth, + self.endWidth, + self.cleaveRadius, + "particles/units/heroes/hero_sven/sven_spell_great_cleave.vpcf" + ) + EmitSoundOn("Hero_Sven.GreatCleave", parent) +end +modifier_sven_great_cleave_custom = __TS__Decorate( + modifier_sven_great_cleave_custom, + modifier_sven_great_cleave_custom, + {registerModifier(nil)}, + {kind = "class", name = "modifier_sven_great_cleave_custom"} +) +____exports.modifier_sven_great_cleave_custom = modifier_sven_great_cleave_custom +____exports.modifier_sven_great_cleave_custom_active = __TS__Class() +local modifier_sven_great_cleave_custom_active = ____exports.modifier_sven_great_cleave_custom_active +modifier_sven_great_cleave_custom_active.name = "modifier_sven_great_cleave_custom_active" +modifier_sven_great_cleave_custom_active.____file_path = "scripts/vscripts/abilities/heroes/sven/ability_sven_great_cleave_custom.lua" +__TS__ClassExtends(modifier_sven_great_cleave_custom_active, BaseModifier) +function modifier_sven_great_cleave_custom_active.prototype.RemoveOnDeath(self) + return false +end +function modifier_sven_great_cleave_custom_active.prototype.IsPurgable(self) + return false +end +function modifier_sven_great_cleave_custom_active.prototype.DeclareFunctions(self) + return {MODIFIER_EVENT_ON_ATTACK_LANDED} +end +function modifier_sven_great_cleave_custom_active.prototype.OnAttackLanded(self, event) + local parent = self:GetParent() + if not parent or parent ~= event.attacker or parent:IsIllusion() then + return + end + local target = event.target + if not target or not target:IsAlive() then + return + end + local point = target:GetAbsOrigin() + local radius = self:GetAbility():GetSpecialValueFor("cleave_starting_width") + local direction = point - parent:GetAbsOrigin() + direction.z = 0 + direction = direction:Normalized() + self:GetAbility():ClearHitTargets() + ProjectileManager:CreateLinearProjectile({ + Source = parent, + Ability = self:GetAbility(), + vSpawnOrigin = parent:GetAbsOrigin(), + iUnitTargetTeam = DOTA_UNIT_TARGET_TEAM_ENEMY, + iUnitTargetFlags = DOTA_UNIT_TARGET_FLAG_NONE, + iUnitTargetType = bit.bor(DOTA_UNIT_TARGET_HERO, DOTA_UNIT_TARGET_BASIC), + EffectName = "particles/great_cleave_shockwave.vpcf", + fDistance = 650, + fStartRadius = radius, + fEndRadius = radius, + vVelocity = direction * 1150 + }) + local ____opt_6 = self:GetAbility() + if ____opt_6 ~= nil then + ____opt_6:SetActivated(true) + end + local ____opt_8 = self:GetAbility() + if ____opt_8 ~= nil then + local ____opt_8_StartCooldown_11 = ____opt_8.StartCooldown + local ____opt_9 = self:GetAbility() + ____opt_8_StartCooldown_11( + ____opt_8, + ____opt_9 and ____opt_9:GetCooldown(self:GetAbility():GetLevel()) + ) + end + self:GetParent():RemoveModifierByName("modifier_sven_great_cleave_custom_active") +end +modifier_sven_great_cleave_custom_active = __TS__Decorate( + modifier_sven_great_cleave_custom_active, + modifier_sven_great_cleave_custom_active, + {registerModifier(nil)}, + {kind = "class", name = "modifier_sven_great_cleave_custom_active"} +) +____exports.modifier_sven_great_cleave_custom_active = modifier_sven_great_cleave_custom_active +____exports.modifier_sven_great_cleave_custom_damage = __TS__Class() +local modifier_sven_great_cleave_custom_damage = ____exports.modifier_sven_great_cleave_custom_damage +modifier_sven_great_cleave_custom_damage.name = "modifier_sven_great_cleave_custom_damage" +modifier_sven_great_cleave_custom_damage.____file_path = "scripts/vscripts/abilities/heroes/sven/ability_sven_great_cleave_custom.lua" +__TS__ClassExtends(modifier_sven_great_cleave_custom_damage, BaseModifier) +function modifier_sven_great_cleave_custom_damage.prototype.IsHidden(self) + return true +end +function modifier_sven_great_cleave_custom_damage.prototype.DeclareFunctions(self) + return {MODIFIER_PROPERTY_DAMAGEOUTGOING_PERCENTAGE} +end +function modifier_sven_great_cleave_custom_damage.prototype.GetModifierDamageOutgoing_Percentage(self, event) + return self:GetAbility():GetSpecialValueFor("cleave_damage_multiple_facet") - 100 +end +modifier_sven_great_cleave_custom_damage = __TS__Decorate( + modifier_sven_great_cleave_custom_damage, + modifier_sven_great_cleave_custom_damage, + {registerModifier(nil)}, + {kind = "class", name = "modifier_sven_great_cleave_custom_damage"} +) +____exports.modifier_sven_great_cleave_custom_damage = modifier_sven_great_cleave_custom_damage +return ____exports diff --git a/scripts/vscripts/abilities/heroes/sven/ability_sven_storm_hammer_custom.lua b/scripts/vscripts/abilities/heroes/sven/ability_sven_storm_hammer_custom.lua new file mode 100644 index 0000000..0c9f655 --- /dev/null +++ b/scripts/vscripts/abilities/heroes/sven/ability_sven_storm_hammer_custom.lua @@ -0,0 +1,267 @@ +local ____lualib = require("lualib_bundle") +local __TS__Class = ____lualib.__TS__Class +local __TS__ClassExtends = ____lualib.__TS__ClassExtends +local __TS__StringIncludes = ____lualib.__TS__StringIncludes +local __TS__Decorate = ____lualib.__TS__Decorate +local ____exports = {} +local ____dota_ts_adapter = require("lib.dota_ts_adapter") +local BaseAbility = ____dota_ts_adapter.BaseAbility +local BaseModifier = ____dota_ts_adapter.BaseModifier +local registerAbility = ____dota_ts_adapter.registerAbility +local registerModifier = ____dota_ts_adapter.registerModifier +--- Storm Hammer - Свен бросает свой магический молот, оглушая и нанося урон врагам в области. +____exports.ability_sven_storm_hammer_custom = __TS__Class() +local ability_sven_storm_hammer_custom = ____exports.ability_sven_storm_hammer_custom +ability_sven_storm_hammer_custom.name = "ability_sven_storm_hammer_custom" +ability_sven_storm_hammer_custom.____file_path = "scripts/vscripts/abilities/heroes/sven/ability_sven_storm_hammer_custom.lua" +__TS__ClassExtends(ability_sven_storm_hammer_custom, BaseAbility) +function ability_sven_storm_hammer_custom.prototype.GetBehavior(self) + local behavior = bit.bor( + bit.bor(DOTA_ABILITY_BEHAVIOR_UNIT_TARGET, DOTA_ABILITY_BEHAVIOR_AOE), + DOTA_ABILITY_BEHAVIOR_IGNORE_BACKSWING + ) + if self:GetSpecialValueFor("sven_storm_hammer_purger") > 0 then + behavior = bit.bor(behavior, DOTA_ABILITY_BEHAVIOR_AUTOCAST) + end + return behavior +end +function ability_sven_storm_hammer_custom.prototype.GetCastRange(self, location, target) + return BaseAbility.prototype.GetCastRange(self, location, target) +end +function ability_sven_storm_hammer_custom.prototype.GetAOERadius(self) + return self:GetSpecialValueFor("radius") +end +function ability_sven_storm_hammer_custom.prototype.GetIntrinsicModifierName(self) + return "modifier_sven_storm_hammer_passive_handler" +end +function ability_sven_storm_hammer_custom.prototype.OnAbilityPhaseStart(self) + local caster = self:GetCaster() + local particle = ParticleManager:CreateParticle("particles/units/heroes/hero_sven/sven_spell_storm_bolt_lightning.vpcf", PATTACH_CUSTOMORIGIN, caster) + ParticleManager:SetParticleControlEnt( + particle, + 0, + caster, + PATTACH_POINT_FOLLOW, + "attach_sword", + caster:GetOrigin(), + true + ) + ParticleManager:ReleaseParticleIndex(particle) + return true +end +function ability_sven_storm_hammer_custom.prototype.OnSpellStart(self) + local caster = self:GetCaster() + local target = self:GetCursorTarget() + local point = self:GetCursorPosition() + local cast_range = self:GetSpecialValueFor("cast_range") + local speed = self:GetSpecialValueFor("projectile_speed") + local radius = self:GetSpecialValueFor("radius") + local stun_duration = self:GetSpecialValueFor("stun_duration") + local damage = self:GetSpecialValueFor("damage") + self.sound_cast = "Hero_Sven.StormBolt" + EmitSoundOn(self.sound_cast, caster) + if target then + self.scepterProjectile = ProjectileManager:CreateTrackingProjectile({ + EffectName = "particles/units/heroes/hero_sven/sven_spell_storm_bolt.vpcf", + Ability = self, + iMoveSpeed = speed, + Source = caster, + Target = target, + bDodgeable = true, + bProvidesVision = true, + iVisionTeamNumber = caster:GetTeamNumber(), + iVisionRadius = radius, + iSourceAttachment = DOTA_PROJECTILE_ATTACHMENT_ATTACK_2 + }) + if self:GetAutoCastState() and caster ~= target then + caster:AddNewModifier(caster, self, "modifier_sven_storm_hammer_handler", {}) + end + else + local direction = (point - caster:GetAbsOrigin()):Normalized() + local distance = (point - caster:GetAbsOrigin()):Length2D() + local actual_distance = math.min(distance, cast_range) + local target_pos = caster:GetAbsOrigin() + direction * actual_distance + local projectile = { + Ability = self, + EffectName = "particles/units/heroes/hero_sven/sven_spell_storm_bolt.vpcf", + vSpawnOrigin = caster:GetAttachmentOrigin(caster:ScriptLookupAttachment("attach_attack1")), + fDistance = actual_distance, + fStartRadius = radius, + fEndRadius = radius, + Source = caster, + bHasFrontalCone = false, + bReplaceExisting = false, + iUnitTargetTeam = DOTA_UNIT_TARGET_TEAM_ENEMY, + iUnitTargetFlags = DOTA_UNIT_TARGET_FLAG_NONE, + iUnitTargetType = DOTA_UNIT_TARGET_HERO + DOTA_UNIT_TARGET_BASIC, + fExpireTime = GameRules:GetGameTime() + 10, + bDeleteOnHit = true, + vVelocity = direction * speed, + bProvidesVision = true, + iVisionRadius = radius, + iVisionTeamNumber = caster:GetTeamNumber() + } + ProjectileManager:CreateLinearProjectile(projectile) + Timers:CreateTimer( + actual_distance / speed, + function() + self:OnProjectileHit(nil, target_pos) + end + ) + end +end +function ability_sven_storm_hammer_custom.prototype.OnProjectileThink(self, location) + local caster = self:GetCaster() + if not caster or not caster:IsAlive() then + return + end + if caster:HasModifier("modifier_sven_storm_hammer_handler") and self.scepterProjectile then + local projLoc = ProjectileManager:GetTrackingProjectileLocation(self.scepterProjectile) + local vectorDiff = caster:GetAttachmentOrigin(caster:ScriptLookupAttachment("attach_attack2")) - caster:GetAbsOrigin() + local direction = (projLoc - caster:GetAbsOrigin()):Normalized() + local newPos = GetGroundPosition(projLoc - direction * 128, caster) + caster:SetAbsOrigin(newPos) + end +end +function ability_sven_storm_hammer_custom.prototype.OnProjectileHit(self, target, location) + local caster = self:GetCaster() + if not caster or not caster:IsAlive() then + return true + end + local radius = self:GetSpecialValueFor("radius") + local stun_duration = self:GetSpecialValueFor("stun_duration") + local damage = self:GetSpecialValueFor("damage") + self.sound_impact = "Hero_Sven.StormBoltImpact" + EmitSoundOn(self.sound_impact, target or caster) + self.particle_explosion = ParticleManager:CreateParticle("particles/units/heroes/hero_sven/sven_spell_storm_bolt_lightning.vpcf", PATTACH_WORLDORIGIN, nil) + local position = target and target:GetOrigin() or location + ParticleManager:SetParticleControl(self.particle_explosion, 0, position) + ParticleManager:SetParticleControl( + self.particle_explosion, + 1, + Vector(radius, 0, 0) + ) + ParticleManager:ReleaseParticleIndex(self.particle_explosion) + if target and target:IsAlive() and target:TriggerSpellAbsorb(self) then + return true + end + if self:GetSpecialValueFor("sven_storm_hammer_purger") > 0 and target and target:IsAlive() and target ~= caster then + target:Purge( + true, + false, + false, + false, + false + ) + caster:PerformAttack( + target, + true, + true, + true, + false, + false, + false, + true + ) + end + local enemies = FindUnitsInRadius( + caster:GetTeamNumber(), + position, + nil, + radius, + DOTA_UNIT_TARGET_TEAM_ENEMY, + DOTA_UNIT_TARGET_HERO + DOTA_UNIT_TARGET_BASIC, + DOTA_UNIT_TARGET_FLAG_NONE, + FIND_ANY_ORDER, + false + ) + for ____, enemy in ipairs(enemies) do + ApplyDamage({ + victim = enemy, + attacker = caster, + damage = damage, + damage_type = self:GetAbilityDamageType(), + ability = self + }) + local actualStunDuration = stun_duration + if enemy.GetUnitName and __TS__StringIncludes( + string.lower(enemy:GetUnitName()), + "boss" + ) then + actualStunDuration = stun_duration * self:GetSpecialValueFor("stun_duration_for_boss_mult") + end + enemy:AddNewModifier(caster, self, "modifier_stunned", {duration = actualStunDuration}) + end + caster:RemoveModifierByName("modifier_sven_storm_hammer_handler") + return true +end +ability_sven_storm_hammer_custom = __TS__Decorate( + ability_sven_storm_hammer_custom, + ability_sven_storm_hammer_custom, + {registerAbility(nil)}, + {kind = "class", name = "ability_sven_storm_hammer_custom"} +) +____exports.ability_sven_storm_hammer_custom = ability_sven_storm_hammer_custom +____exports.modifier_sven_storm_hammer_passive_handler = __TS__Class() +local modifier_sven_storm_hammer_passive_handler = ____exports.modifier_sven_storm_hammer_passive_handler +modifier_sven_storm_hammer_passive_handler.name = "modifier_sven_storm_hammer_passive_handler" +modifier_sven_storm_hammer_passive_handler.____file_path = "scripts/vscripts/abilities/heroes/sven/ability_sven_storm_hammer_custom.lua" +__TS__ClassExtends(modifier_sven_storm_hammer_passive_handler, BaseModifier) +function modifier_sven_storm_hammer_passive_handler.prototype.IsHidden(self) + return true +end +modifier_sven_storm_hammer_passive_handler = __TS__Decorate( + modifier_sven_storm_hammer_passive_handler, + modifier_sven_storm_hammer_passive_handler, + {registerModifier(nil)}, + {kind = "class", name = "modifier_sven_storm_hammer_passive_handler"} +) +____exports.modifier_sven_storm_hammer_passive_handler = modifier_sven_storm_hammer_passive_handler +____exports.modifier_sven_storm_hammer_handler = __TS__Class() +local modifier_sven_storm_hammer_handler = ____exports.modifier_sven_storm_hammer_handler +modifier_sven_storm_hammer_handler.name = "modifier_sven_storm_hammer_handler" +modifier_sven_storm_hammer_handler.____file_path = "scripts/vscripts/abilities/heroes/sven/ability_sven_storm_hammer_custom.lua" +__TS__ClassExtends(modifier_sven_storm_hammer_handler, BaseModifier) +function modifier_sven_storm_hammer_handler.prototype.IsHidden(self) + return true +end +function modifier_sven_storm_hammer_handler.prototype.CheckState(self) + return {[MODIFIER_STATE_IGNORING_MOVE_AND_ATTACK_ORDERS] = true, [MODIFIER_STATE_INVULNERABLE] = true} +end +function modifier_sven_storm_hammer_handler.prototype.DeclareFunctions(self) + return {MODIFIER_PROPERTY_OVERRIDE_ANIMATION} +end +function modifier_sven_storm_hammer_handler.prototype.GetOverrideAnimation(self) + return ACT_DOTA_OVERRIDE_ABILITY_1 +end +function modifier_sven_storm_hammer_handler.prototype.OnDestroy(self) + if IsServer() then + ResolveNPCPositions( + self:GetParent():GetAbsOrigin(), + 256 + ) + end +end +modifier_sven_storm_hammer_handler = __TS__Decorate( + modifier_sven_storm_hammer_handler, + modifier_sven_storm_hammer_handler, + {registerModifier(nil)}, + {kind = "class", name = "modifier_sven_storm_hammer_handler"} +) +____exports.modifier_sven_storm_hammer_handler = modifier_sven_storm_hammer_handler +____exports.modifier_sven_storm_hammer_buff = __TS__Class() +local modifier_sven_storm_hammer_buff = ____exports.modifier_sven_storm_hammer_buff +modifier_sven_storm_hammer_buff.name = "modifier_sven_storm_hammer_buff" +modifier_sven_storm_hammer_buff.____file_path = "scripts/vscripts/abilities/heroes/sven/ability_sven_storm_hammer_custom.lua" +__TS__ClassExtends(modifier_sven_storm_hammer_buff, BaseModifier) +function modifier_sven_storm_hammer_buff.prototype.IsHidden(self) + return true +end +modifier_sven_storm_hammer_buff = __TS__Decorate( + modifier_sven_storm_hammer_buff, + modifier_sven_storm_hammer_buff, + {registerModifier(nil)}, + {kind = "class", name = "modifier_sven_storm_hammer_buff"} +) +____exports.modifier_sven_storm_hammer_buff = modifier_sven_storm_hammer_buff +return ____exports diff --git a/scripts/vscripts/abilities/heroes/sven/ability_sven_warcry_custom.lua b/scripts/vscripts/abilities/heroes/sven/ability_sven_warcry_custom.lua new file mode 100644 index 0000000..a04d056 --- /dev/null +++ b/scripts/vscripts/abilities/heroes/sven/ability_sven_warcry_custom.lua @@ -0,0 +1,229 @@ +local ____lualib = require("lualib_bundle") +local __TS__Class = ____lualib.__TS__Class +local __TS__ClassExtends = ____lualib.__TS__ClassExtends +local __TS__Decorate = ____lualib.__TS__Decorate +local ____exports = {} +local ____dota_ts_adapter = require("lib.dota_ts_adapter") +local BaseAbility = ____dota_ts_adapter.BaseAbility +local BaseModifier = ____dota_ts_adapter.BaseModifier +local registerAbility = ____dota_ts_adapter.registerAbility +local registerModifier = ____dota_ts_adapter.registerModifier +____exports.ability_sven_warcry_custom = __TS__Class() +local ability_sven_warcry_custom = ____exports.ability_sven_warcry_custom +ability_sven_warcry_custom.name = "ability_sven_warcry_custom" +ability_sven_warcry_custom.____file_path = "scripts/vscripts/abilities/heroes/sven/ability_sven_warcry_custom.lua" +__TS__ClassExtends(ability_sven_warcry_custom, BaseAbility) +function ability_sven_warcry_custom.prototype.GetIntrinsicModifierName(self) + return "modifier_sven_warcry_custom_passive" +end +function ability_sven_warcry_custom.prototype.GetHealthCost(self, level) + if self:GetSpecialValueFor("shield_pct") > 0 then + return self:GetCaster():GetHealth() + end + return 0 +end +function ability_sven_warcry_custom.prototype.OnSpellStart(self) + if not IsServer() then + return + end + local caster = self:GetCaster() + if not caster then + return + end + local duration = self:GetSpecialValueFor("duration") + local radius = self:GetSpecialValueFor("radius") + local nearbyHeroes = FindUnitsInRadius( + caster:GetTeamNumber(), + caster:GetAbsOrigin(), + nil, + radius, + DOTA_UNIT_TARGET_TEAM_FRIENDLY, + DOTA_UNIT_TARGET_HERO, + DOTA_UNIT_TARGET_FLAG_NONE, + FIND_ANY_ORDER, + false + ) + for ____, unit in ipairs(nearbyHeroes) do + if unit and IsValidEntity(unit) and unit:IsAlive() then + unit:Purge( + false, + true, + false, + true, + true + ) + end + end + caster:AddNewModifier(caster, self, "modifier_sven_warcry_custom_active", {duration = duration}) + if self:GetSpecialValueFor("shield_pct") > 0 then + local shieldAmount = self:GetCaster():GetMaxHealth() * (self:GetSpecialValueFor("shield_pct") / 100) + local casterModifier = caster:AddNewModifier(caster, self, "modifier_sven_warcry_custom_buff", {}) + if casterModifier ~= nil then + casterModifier:SetStackCount(shieldAmount) + end + end + for ____, ally in ipairs(nearbyHeroes) do + if ally ~= caster then + ally:AddNewModifier(caster, self, "modifier_sven_warcry_custom_active", {duration = duration}) + if self:GetSpecialValueFor("shield_pct") > 0 then + local shieldAmount = self:GetCaster():GetMaxHealth() * (self:GetSpecialValueFor("shield_pct") / 100) + local allyModifier = ally:AddNewModifier(caster, self, "modifier_sven_warcry_custom_buff", {}) + if allyModifier ~= nil then + allyModifier:SetStackCount(shieldAmount) + end + end + end + end + EmitSoundOn("Hero_Sven.WarCry", caster) + local particleId = ParticleManager:CreateParticle("particles/units/heroes/hero_sven/sven_spell_warcry.vpcf", PATTACH_ABSORIGIN_FOLLOW, caster) + ParticleManager:ReleaseParticleIndex(particleId) +end +ability_sven_warcry_custom = __TS__Decorate( + ability_sven_warcry_custom, + ability_sven_warcry_custom, + {registerAbility(nil)}, + {kind = "class", name = "ability_sven_warcry_custom"} +) +____exports.ability_sven_warcry_custom = ability_sven_warcry_custom +____exports.modifier_sven_warcry_custom_passive = __TS__Class() +local modifier_sven_warcry_custom_passive = ____exports.modifier_sven_warcry_custom_passive +modifier_sven_warcry_custom_passive.name = "modifier_sven_warcry_custom_passive" +modifier_sven_warcry_custom_passive.____file_path = "scripts/vscripts/abilities/heroes/sven/ability_sven_warcry_custom.lua" +__TS__ClassExtends(modifier_sven_warcry_custom_passive, BaseModifier) +function modifier_sven_warcry_custom_passive.prototype.IsHidden(self) + return true +end +function modifier_sven_warcry_custom_passive.prototype.IsDebuff(self) + return false +end +function modifier_sven_warcry_custom_passive.prototype.IsPurgable(self) + return false +end +function modifier_sven_warcry_custom_passive.prototype.DeclareFunctions(self) + return {MODIFIER_PROPERTY_PREATTACK_BONUS_DAMAGE} +end +function modifier_sven_warcry_custom_passive.prototype.GetModifierPreAttack_BonusDamage(self) + if self:GetParent():PassivesDisabled() then + return 0 + end + local ability = self:GetAbility() + if not ability then + return 0 + end + return ability:GetSpecialValueFor("damage_bonus_per_armor") * self:GetParent():GetPhysicalArmorValue(false) +end +modifier_sven_warcry_custom_passive = __TS__Decorate( + modifier_sven_warcry_custom_passive, + modifier_sven_warcry_custom_passive, + {registerModifier(nil)}, + {kind = "class", name = "modifier_sven_warcry_custom_passive"} +) +____exports.modifier_sven_warcry_custom_passive = modifier_sven_warcry_custom_passive +____exports.modifier_sven_warcry_custom_active = __TS__Class() +local modifier_sven_warcry_custom_active = ____exports.modifier_sven_warcry_custom_active +modifier_sven_warcry_custom_active.name = "modifier_sven_warcry_custom_active" +modifier_sven_warcry_custom_active.____file_path = "scripts/vscripts/abilities/heroes/sven/ability_sven_warcry_custom.lua" +__TS__ClassExtends(modifier_sven_warcry_custom_active, BaseModifier) +function modifier_sven_warcry_custom_active.prototype.____constructor(self, ...) + BaseModifier.prototype.____constructor(self, ...) + self.armor_bonus = 0 + self.move_speed_bonus = 0 + self.attack_speed_bonus = 0 +end +function modifier_sven_warcry_custom_active.prototype.IsHidden(self) + return false +end +function modifier_sven_warcry_custom_active.prototype.IsDebuff(self) + return false +end +function modifier_sven_warcry_custom_active.prototype.IsPurgable(self) + return true +end +function modifier_sven_warcry_custom_active.prototype.OnCreated(self) + local ability = self:GetAbility() + if not ability then + return + end + self.armor_bonus = ability:GetSpecialValueFor("armor_bonus") + self.move_speed_bonus = ability:GetSpecialValueFor("movespeed_bonus") + self.attack_speed_bonus = ability:GetSpecialValueFor("attackspeed_bonus") +end +function modifier_sven_warcry_custom_active.prototype.DeclareFunctions(self) + return {MODIFIER_PROPERTY_PHYSICAL_ARMOR_BONUS, MODIFIER_PROPERTY_MOVESPEED_BONUS_PERCENTAGE, MODIFIER_PROPERTY_ATTACKSPEED_PERCENTAGE} +end +function modifier_sven_warcry_custom_active.prototype.GetModifierAttackSpeedPercentage(self) + return self.attack_speed_bonus +end +function modifier_sven_warcry_custom_active.prototype.GetModifierPhysicalArmorBonus(self) + return self.armor_bonus +end +function modifier_sven_warcry_custom_active.prototype.GetModifierMoveSpeedBonus_Percentage(self) + return self.move_speed_bonus +end +function modifier_sven_warcry_custom_active.prototype.GetEffectName(self) + return "particles/units/heroes/hero_sven/sven_warcry_buff.vpcf" +end +function modifier_sven_warcry_custom_active.prototype.GetEffectAttachType(self) + return PATTACH_ABSORIGIN_FOLLOW +end +modifier_sven_warcry_custom_active = __TS__Decorate( + modifier_sven_warcry_custom_active, + modifier_sven_warcry_custom_active, + {registerModifier(nil)}, + {kind = "class", name = "modifier_sven_warcry_custom_active"} +) +____exports.modifier_sven_warcry_custom_active = modifier_sven_warcry_custom_active +____exports.modifier_sven_warcry_custom_buff = __TS__Class() +local modifier_sven_warcry_custom_buff = ____exports.modifier_sven_warcry_custom_buff +modifier_sven_warcry_custom_buff.name = "modifier_sven_warcry_custom_buff" +modifier_sven_warcry_custom_buff.____file_path = "scripts/vscripts/abilities/heroes/sven/ability_sven_warcry_custom.lua" +__TS__ClassExtends(modifier_sven_warcry_custom_buff, BaseModifier) +function modifier_sven_warcry_custom_buff.prototype.IsHidden(self) + return false +end +function modifier_sven_warcry_custom_buff.prototype.IsPurgable(self) + return false +end +function modifier_sven_warcry_custom_buff.prototype.GetEffectName(self) + return "particles/units/heroes/hero_medusa/medusa_mana_shield_buff.vpcf" +end +function modifier_sven_warcry_custom_buff.prototype.GetEffectAttachType(self) + return PATTACH_ABSORIGIN_FOLLOW +end +function modifier_sven_warcry_custom_buff.prototype.OnCreated(self) + if not IsServer() then + return + end +end +function modifier_sven_warcry_custom_buff.prototype.DeclareFunctions(self) + return {MODIFIER_PROPERTY_INCOMING_DAMAGE_CONSTANT} +end +function modifier_sven_warcry_custom_buff.prototype.GetModifierIncomingDamageConstant(self, event) + if IsClient() then + return self:GetStackCount() + end + if not IsServer() then + return 0 + end + if event.inflictor and event.inflictor == self:GetAbility() then + return 0 + end + if self:GetStackCount() > event.damage then + self:SetStackCount(self:GetStackCount() - event.damage) + local damage = event.damage + return -damage + else + local damage = self:GetStackCount() + self:SetStackCount(0) + self:Destroy() + return -damage + end +end +modifier_sven_warcry_custom_buff = __TS__Decorate( + modifier_sven_warcry_custom_buff, + modifier_sven_warcry_custom_buff, + {registerModifier(nil)}, + {kind = "class", name = "modifier_sven_warcry_custom_buff"} +) +____exports.modifier_sven_warcry_custom_buff = modifier_sven_warcry_custom_buff +return ____exports diff --git a/scripts/vscripts/abilities/heroes/templar_assassin/ability_templar_assassin_bedlam_custom.lua b/scripts/vscripts/abilities/heroes/templar_assassin/ability_templar_assassin_bedlam_custom.lua new file mode 100644 index 0000000..f00d71d --- /dev/null +++ b/scripts/vscripts/abilities/heroes/templar_assassin/ability_templar_assassin_bedlam_custom.lua @@ -0,0 +1,440 @@ +local ____lualib = require("lualib_bundle") +local __TS__Class = ____lualib.__TS__Class +local __TS__ClassExtends = ____lualib.__TS__ClassExtends +local __TS__Decorate = ____lualib.__TS__Decorate +local ____exports = {} +local ____dota_ts_adapter = require("lib.dota_ts_adapter") +local BaseAbility = ____dota_ts_adapter.BaseAbility +local BaseModifier = ____dota_ts_adapter.BaseModifier +local registerAbility = ____dota_ts_adapter.registerAbility +local registerModifier = ____dota_ts_adapter.registerModifier +____exports.ability_templar_assassin_bedlam_custom = __TS__Class() +local ability_templar_assassin_bedlam_custom = ____exports.ability_templar_assassin_bedlam_custom +ability_templar_assassin_bedlam_custom.name = "ability_templar_assassin_bedlam_custom" +ability_templar_assassin_bedlam_custom.____file_path = "scripts/vscripts/abilities/heroes/templar_assassin/ability_templar_assassin_bedlam_custom.lua" +__TS__ClassExtends(ability_templar_assassin_bedlam_custom, BaseAbility) +function ability_templar_assassin_bedlam_custom.prototype.GetIntrinsicModifierName(self) + return ____exports.modifier_ability_bedlam.name +end +function ability_templar_assassin_bedlam_custom.prototype.Precache(self, context) + PrecacheResource("particle", "particles/units/heroes/hero_dark_willow/dark_willow_wisp_aoe_cast.vpcf", context) + PrecacheResource("particle", "particles/units/heroes/hero_dark_willow/dark_willow_wisp_aoe.vpcf", context) + PrecacheResource("particle", "particles/units/heroes/hero_dark_willow/dark_willow_willowisp_base_attack.vpcf", context) + PrecacheResource("particle", "particles/units/heroes/hero_dark_willow/dark_willow_willowisp_ambient.vpcf", context) + PrecacheResource("model", "models/items/dark_willow/dark_willow_ether_wisp/dark_willow_ether_wisp.vmdl", context) + PrecacheResource("model", "models/heroes/dark_willow/dark_willow_wisp.vmdl", context) +end +function ability_templar_assassin_bedlam_custom.prototype.OnProjectileHit_ExtraData(self, target, _location, _extraData) + if not IsServer() then + return true + end + if not target or not target:IsAlive() then + return true + end + local caster = self:GetCaster() + if not caster or not caster:IsAlive() then + return true + end + caster:PerformAttack( + target, + true, + true, + true, + false, + false, + false, + true + ) + return true +end +ability_templar_assassin_bedlam_custom = __TS__Decorate( + ability_templar_assassin_bedlam_custom, + ability_templar_assassin_bedlam_custom, + {registerAbility(nil)}, + {kind = "class", name = "ability_templar_assassin_bedlam_custom"} +) +____exports.ability_templar_assassin_bedlam_custom = ability_templar_assassin_bedlam_custom +____exports.modifier_ability_bedlam = __TS__Class() +local modifier_ability_bedlam = ____exports.modifier_ability_bedlam +modifier_ability_bedlam.name = "modifier_ability_bedlam" +modifier_ability_bedlam.____file_path = "scripts/vscripts/abilities/heroes/templar_assassin/ability_templar_assassin_bedlam_custom.lua" +__TS__ClassExtends(modifier_ability_bedlam, BaseModifier) +function modifier_ability_bedlam.prototype.____constructor(self, ...) + BaseModifier.prototype.____constructor(self, ...) + self.zero = Vector(0, 0, 0) + self.interval = 0.03 + self.baseFacing = Vector(0, 1, 0) + self.relativePos = Vector(0, 0, 0) + self.rotateDelta = 0 + self.rotation = 0 +end +function modifier_ability_bedlam.prototype.IsHidden(self) + return false +end +function modifier_ability_bedlam.prototype.IsDebuff(self) + return false +end +function modifier_ability_bedlam.prototype.IsPurgable(self) + return false +end +function modifier_ability_bedlam.prototype.OnCreated(self) + self.parent = self:GetParent() + local ability = self:GetAbility() + if not ability then + return + end + local revolution = math.max( + 0.1, + ability:GetSpecialValueFor("roaming_seconds_per_rotation") + ) + local rotateRadius = ability:GetSpecialValueFor("roaming_radius") + self.relativePos = Vector(-rotateRadius, 0, 100) + self.rotateDelta = 360 / revolution * self.interval + if not IsServer() then + return + end + self:spawnWispIfNeeded() + if self.wisp and IsValidEntity(self.wisp) then + self:PlayEffects(rotateRadius) + end + self:StartIntervalThink(self.interval) +end +function modifier_ability_bedlam.prototype.spawnWispIfNeeded(self) + if not IsServer() or not self.parent then + return + end + if self.parent:PassivesDisabled() then + return + end + if self.wisp and IsValidEntity(self.wisp) then + return + end + local ability = self:GetAbility() + if not ability then + return + end + local position = self.parent:GetAbsOrigin() + self.relativePos + self.wisp = CreateUnitByName( + "npc_templar_secret_friend", + position, + true, + self.parent, + self.parent:GetOwner(), + self.parent:GetTeamNumber() + ) + if not self.wisp then + return + end + self.wisp:SetForwardVector(self.baseFacing) + self.wisp:AddNewModifier( + self:GetCaster(), + ability, + ____exports.modifier_wisp_ambient.name, + {} + ) + self.wisp:AddNewModifier( + self:GetCaster(), + ability, + ____exports.modifier_ability_bedlam_attack.name, + {} + ) +end +function modifier_ability_bedlam.prototype.OnDestroy(self) + if not IsServer() then + return + end + if self.wisp and IsValidEntity(self.wisp) then + UTIL_Remove(self.wisp) + self.wisp = nil + end +end +function modifier_ability_bedlam.prototype.OnIntervalThink(self) + if not IsServer() then + return + end + if not self.parent then + return + end + if self.parent:PassivesDisabled() then + if self.wisp and IsValidEntity(self.wisp) then + UTIL_Remove(self.wisp) + self.wisp = nil + end + return + end + if not self.wisp or not IsValidEntity(self.wisp) then + local ability = self:GetAbility() + if ability then + local rotateRadius = ability:GetSpecialValueFor("roaming_radius") + self.relativePos = Vector(-rotateRadius, 0, 100) + end + self:spawnWispIfNeeded() + end + if not self.wisp or not IsValidEntity(self.wisp) then + return + end + self.rotation = self.rotation + self.rotateDelta + local origin = self.parent:GetAbsOrigin() + local pos = RotatePosition( + origin, + QAngle(0, -self.rotation, 0), + origin + self.relativePos + ) + local facing = RotatePosition( + self.zero, + QAngle(0, -self.rotation, 0), + self.baseFacing + ) + self.wisp:SetAbsOrigin(pos) + self.wisp:SetForwardVector(facing) +end +function modifier_ability_bedlam.prototype.PlayEffects(self, rotateRadius) + if not IsServer() then + return + end + local effect = ParticleManager:CreateParticle( + "particles/units/heroes/hero_dark_willow/dark_willow_wisp_aoe_cast.vpcf", + PATTACH_ABSORIGIN_FOLLOW, + self:GetParent() + ) + ParticleManager:SetParticleControl( + effect, + 3, + Vector(rotateRadius, rotateRadius, rotateRadius) + ) + ParticleManager:ReleaseParticleIndex(effect) +end +modifier_ability_bedlam = __TS__Decorate( + modifier_ability_bedlam, + modifier_ability_bedlam, + {registerModifier(nil)}, + {kind = "class", name = "modifier_ability_bedlam"} +) +____exports.modifier_ability_bedlam = modifier_ability_bedlam +____exports.modifier_ability_bedlam_attack = __TS__Class() +local modifier_ability_bedlam_attack = ____exports.modifier_ability_bedlam_attack +modifier_ability_bedlam_attack.name = "modifier_ability_bedlam_attack" +modifier_ability_bedlam_attack.____file_path = "scripts/vscripts/abilities/heroes/templar_assassin/ability_templar_assassin_bedlam_custom.lua" +__TS__ClassExtends(modifier_ability_bedlam_attack, BaseModifier) +function modifier_ability_bedlam_attack.prototype.____constructor(self, ...) + BaseModifier.prototype.____constructor(self, ...) + self.interval = 0.35 + self.radius = 600 + self.attackTargets = 1 + self.hadScepter = false +end +function modifier_ability_bedlam_attack.prototype.IsHidden(self) + return false +end +function modifier_ability_bedlam_attack.prototype.IsDebuff(self) + return false +end +function modifier_ability_bedlam_attack.prototype.IsPurgable(self) + return false +end +function modifier_ability_bedlam_attack.prototype.OnCreated(self) + if not IsServer() then + return + end + self:RecalculateAttackParams(true) + self:StartIntervalThink(self.interval) + self:PlayAreaFx() +end +function modifier_ability_bedlam_attack.prototype.OnRefresh(self) + if not IsServer() then + return + end + self:RecalculateAttackParams(true) +end +function modifier_ability_bedlam_attack.prototype.OnIntervalThink(self) + if not IsServer() then + return + end + local ability = self:GetAbility() + local caster = self:GetCaster() + if not caster or not caster:IsAlive() then + return + end + if caster:PassivesDisabled() then + return + end + local wisp = self:GetParent() + if not ability or not caster or not wisp then + return + end + self:RecalculateAttackParams() + local enemies = FindUnitsInRadius( + caster:GetTeamNumber(), + wisp:GetAbsOrigin(), + nil, + self.radius, + DOTA_UNIT_TARGET_TEAM_ENEMY, + DOTA_UNIT_TARGET_HERO + DOTA_UNIT_TARGET_BASIC, + DOTA_UNIT_TARGET_FLAG_NO_INVIS + DOTA_UNIT_TARGET_FLAG_FOW_VISIBLE, + FIND_CLOSEST, + false + ) + local fired = 0 + for ____, enemy in ipairs(enemies) do + do + if not enemy or not enemy:IsAlive() then + goto __continue46 + end + if fired >= self.attackTargets then + break + end + ProjectileManager:CreateTrackingProjectile({ + Target = enemy, + Source = wisp, + Ability = ability, + EffectName = caster:GetRangedProjectileName(), + iMoveSpeed = math.max( + 700, + caster:GetProjectileSpeed() + ), + bDodgeable = true, + bIsAttack = true + }) + EmitSoundOn("Hero_DarkWillow.WillOWisp.Damage", wisp) + fired = fired + 1 + end + ::__continue46:: + end +end +function modifier_ability_bedlam_attack.prototype.RecalculateAttackParams(self, forceIntervalUpdate) + if forceIntervalUpdate == nil then + forceIntervalUpdate = false + end + local ability = self:GetAbility() + local caster = self:GetCaster() + if not ability or not caster then + return + end + local baseInterval = ability:GetSpecialValueFor("attack_interval") + local baseRadius = ability:GetSpecialValueFor("attack_radius") + local baseTargets = math.max( + 1, + ability:GetSpecialValueFor("attack_targets") + ) + local hasScepter = caster:HasScepter() + local nextInterval = baseInterval + local nextTargets = baseTargets + if hasScepter then + nextTargets = nextTargets + ability:GetSpecialValueFor("scepter_bonus_targets") + local intervalPct = math.max( + 1, + ability:GetSpecialValueFor("scepter_interval_mult_pct") + ) + nextInterval = baseInterval * (intervalPct / 100) + end + local intervalChanged = math.abs(nextInterval - self.interval) > 0.0001 + self.radius = baseRadius + self.attackTargets = nextTargets + if forceIntervalUpdate or intervalChanged or hasScepter ~= self.hadScepter then + self.interval = nextInterval + self:StartIntervalThink(self.interval) + else + self.interval = nextInterval + end + self.hadScepter = hasScepter +end +function modifier_ability_bedlam_attack.prototype.PlayAreaFx(self) + if not IsServer() then + return + end + local effect = ParticleManager:CreateParticle( + "particles/units/heroes/hero_dark_willow/dark_willow_wisp_aoe.vpcf", + PATTACH_ABSORIGIN_FOLLOW, + self:GetParent() + ) + ParticleManager:SetParticleControl( + effect, + 1, + Vector(self.radius, self.radius, self.radius) + ) + self:AddParticle( + effect, + false, + false, + -1, + false, + false + ) + EmitSoundOn( + "Hero_DarkWillow.WispStrike.Cast", + self:GetParent() + ) +end +modifier_ability_bedlam_attack = __TS__Decorate( + modifier_ability_bedlam_attack, + modifier_ability_bedlam_attack, + {registerModifier(nil)}, + {kind = "class", name = "modifier_ability_bedlam_attack"} +) +____exports.modifier_ability_bedlam_attack = modifier_ability_bedlam_attack +____exports.modifier_wisp_ambient = __TS__Class() +local modifier_wisp_ambient = ____exports.modifier_wisp_ambient +modifier_wisp_ambient.name = "modifier_wisp_ambient" +modifier_wisp_ambient.____file_path = "scripts/vscripts/abilities/heroes/templar_assassin/ability_templar_assassin_bedlam_custom.lua" +__TS__ClassExtends(modifier_wisp_ambient, BaseModifier) +function modifier_wisp_ambient.prototype.IsHidden(self) + return false +end +function modifier_wisp_ambient.prototype.IsPurgable(self) + return false +end +function modifier_wisp_ambient.prototype.OnCreated(self) + if not IsServer() then + return + end + self:GetParent():SetModel("models/heroes/dark_willow/dark_willow_wisp.vmdl") + self:PlayEffects() +end +function modifier_wisp_ambient.prototype.DeclareFunctions(self) + return {MODIFIER_PROPERTY_BASEATTACK_BONUSDAMAGE} +end +function modifier_wisp_ambient.prototype.GetModifierBaseAttack_BonusDamage(self) + if not IsServer() then + return 0 + end + if self.effectCast ~= nil then + local target = self:GetParent():GetAbsOrigin() + self:GetParent():GetForwardVector() + ParticleManager:SetParticleControl(self.effectCast, 2, target) + end + return 0 +end +function modifier_wisp_ambient.prototype.CheckState(self) + return { + [MODIFIER_STATE_INVULNERABLE] = true, + [MODIFIER_STATE_UNSELECTABLE] = true, + [MODIFIER_STATE_UNTARGETABLE] = true, + [MODIFIER_STATE_OUT_OF_GAME] = true, + [MODIFIER_STATE_NO_HEALTH_BAR] = true + } +end +function modifier_wisp_ambient.prototype.PlayEffects(self) + if not IsServer() then + return + end + self.effectCast = ParticleManager:CreateParticle( + "particles/units/heroes/hero_dark_willow/dark_willow_willowisp_ambient.vpcf", + PATTACH_ABSORIGIN_FOLLOW, + self:GetParent() + ) + self:AddParticle( + self.effectCast, + false, + false, + -1, + false, + false + ) +end +modifier_wisp_ambient = __TS__Decorate( + modifier_wisp_ambient, + modifier_wisp_ambient, + {registerModifier(nil)}, + {kind = "class", name = "modifier_wisp_ambient"} +) +____exports.modifier_wisp_ambient = modifier_wisp_ambient +return ____exports diff --git a/scripts/vscripts/abilities/heroes/templar_assassin/ability_templar_assassin_meld_custom.lua b/scripts/vscripts/abilities/heroes/templar_assassin/ability_templar_assassin_meld_custom.lua new file mode 100644 index 0000000..2ccdb15 --- /dev/null +++ b/scripts/vscripts/abilities/heroes/templar_assassin/ability_templar_assassin_meld_custom.lua @@ -0,0 +1,269 @@ +local ____lualib = require("lualib_bundle") +local __TS__Class = ____lualib.__TS__Class +local __TS__ClassExtends = ____lualib.__TS__ClassExtends +local __TS__Decorate = ____lualib.__TS__Decorate +local ____exports = {} +local ____dota_ts_adapter = require("lib.dota_ts_adapter") +local BaseAbility = ____dota_ts_adapter.BaseAbility +local BaseModifier = ____dota_ts_adapter.BaseModifier +local registerAbility = ____dota_ts_adapter.registerAbility +local registerModifier = ____dota_ts_adapter.registerModifier +____exports.ability_templar_assassin_meld_custom = __TS__Class() +local ability_templar_assassin_meld_custom = ____exports.ability_templar_assassin_meld_custom +ability_templar_assassin_meld_custom.name = "ability_templar_assassin_meld_custom" +ability_templar_assassin_meld_custom.____file_path = "scripts/vscripts/abilities/heroes/templar_assassin/ability_templar_assassin_meld_custom.lua" +__TS__ClassExtends(ability_templar_assassin_meld_custom, BaseAbility) +function ability_templar_assassin_meld_custom.prototype.Precache(self, context) + PrecacheResource("particle", "particles/econ/items/templar_assassin/templar_assassin_butterfly/templar_assassin_trap_explode_butterfly.vpcf", context) +end +function ability_templar_assassin_meld_custom.prototype.LaunchMeldProjectiles(self, caster, duration) + local radius = self:GetSpecialValueFor("radius") + local maxTargets = math.max( + 1, + self:GetSpecialValueFor("blink_attack_targets") + ) + if HasShard(nil, caster) then + maxTargets = maxTargets + self:GetSpecialValueFor("shard_bonus_targets") + end + local enemies = FindUnitsInRadius( + caster:GetTeamNumber(), + caster:GetAbsOrigin(), + nil, + radius, + DOTA_UNIT_TARGET_TEAM_ENEMY, + DOTA_UNIT_TARGET_HERO + DOTA_UNIT_TARGET_BASIC, + DOTA_UNIT_TARGET_FLAG_NONE, + FIND_CLOSEST, + false + ) + local attacked = 0 + for ____, enemy in ipairs(enemies) do + if attacked >= maxTargets then + break + end + ProjectileManager:CreateTrackingProjectile({ + Target = enemy, + Source = caster, + Ability = self, + EffectName = caster:GetRangedProjectileName(), + iMoveSpeed = caster:GetProjectileSpeed(), + iSourceAttachment = DOTA_PROJECTILE_ATTACHMENT_HITLOCATION, + bDodgeable = true, + bIsAttack = true, + ExtraData = {duration = duration} + }) + attacked = attacked + 1 + end +end +function ability_templar_assassin_meld_custom.prototype.OnSpellStart(self) + if not IsServer() then + return + end + local caster = self:GetCaster() + local isAltCast = self:IsAltCastAbility() + local duration = self:GetSpecialValueFor("duration") + if not isAltCast then + caster:AddNewModifier(caster, self, ____exports.modifier_meld_invis.name, {duration = duration}) + EmitSoundOn("Hero_TemplarAssassin.Meld", caster) + return + end + local point = self:GetCursorPosition() + local origin = caster:GetAbsOrigin() + local maxRange = self:GetSpecialValueFor("blink_range") + local minRange = self:GetSpecialValueFor("min_blink_range") + local radius = self:GetSpecialValueFor("radius") + local direction = point - origin + local currentLength = direction:Length2D() + if currentLength > maxRange then + direction = direction:Normalized() * maxRange + end + if currentLength < minRange then + direction = direction:Normalized() * minRange + end + FindClearSpaceForUnit(caster, origin + direction, true) + EmitSoundOnLocationWithCaster( + caster:GetAbsOrigin(), + "Hero_TemplarAssassin.Meld", + caster + ) + local blinkFx = ParticleManager:CreateParticle("particles/econ/items/templar_assassin/templar_assassin_butterfly/templar_assassin_trap_explode_butterfly.vpcf", PATTACH_ABSORIGIN, caster) + ParticleManager:SetParticleControl( + blinkFx, + 0, + caster:GetAbsOrigin() + ) + ParticleManager:ReleaseParticleIndex(blinkFx) + caster:AddNewModifier(caster, self, ____exports.modifier_meld_damage.name, {}) + self:LaunchMeldProjectiles(caster, duration) +end +function ability_templar_assassin_meld_custom.prototype.OnProjectileHit_ExtraData(self, target, _location, extraData) + if not IsServer() then + return true + end + if not target or not target:IsAlive() then + return true + end + local caster = self:GetCaster() + if not caster or not caster:IsAlive() then + return true + end + local duration = extraData.duration or self:GetSpecialValueFor("duration") + target:AddNewModifier(caster, self, ____exports.modifier_meld_reduction.name, {duration = duration}) + caster:PerformAttack( + target, + false, + true, + true, + false, + false, + false, + true + ) + return true +end +ability_templar_assassin_meld_custom = __TS__Decorate( + ability_templar_assassin_meld_custom, + ability_templar_assassin_meld_custom, + {registerAbility(nil)}, + {kind = "class", name = "ability_templar_assassin_meld_custom"} +) +____exports.ability_templar_assassin_meld_custom = ability_templar_assassin_meld_custom +____exports.modifier_meld_invis = __TS__Class() +local modifier_meld_invis = ____exports.modifier_meld_invis +modifier_meld_invis.name = "modifier_meld_invis" +modifier_meld_invis.____file_path = "scripts/vscripts/abilities/heroes/templar_assassin/ability_templar_assassin_meld_custom.lua" +__TS__ClassExtends(modifier_meld_invis, BaseModifier) +function modifier_meld_invis.prototype.IsPurgable(self) + return true +end +function modifier_meld_invis.prototype.IsDebuff(self) + return false +end +function modifier_meld_invis.prototype.CheckState(self) + return {[MODIFIER_STATE_INVISIBLE] = true, [MODIFIER_STATE_NO_UNIT_COLLISION] = true} +end +function modifier_meld_invis.prototype.DeclareFunctions(self) + return {MODIFIER_PROPERTY_INVISIBILITY_LEVEL, MODIFIER_EVENT_ON_ATTACK_LANDED} +end +function modifier_meld_invis.prototype.GetModifierInvisibilityLevel(self) + return 1 +end +function modifier_meld_invis.prototype.OnAttackLanded(self, event) + if not IsServer() then + return + end + if event.attacker ~= self:GetParent() then + return + end + if event.no_attack_cooldown then + return + end + if not event.target then + return + end + if event.target:GetTeamNumber() == self:GetParent():GetTeamNumber() then + return + end + if event.target:IsOther() then + return + end + local ability = self:GetAbility() + if ability then + local duration = ability:GetSpecialValueFor("duration") + ability:LaunchMeldProjectiles( + self:GetParent(), + duration + ) + end + self:Destroy() +end +modifier_meld_invis = __TS__Decorate( + modifier_meld_invis, + modifier_meld_invis, + {registerModifier(nil)}, + {kind = "class", name = "modifier_meld_invis"} +) +____exports.modifier_meld_invis = modifier_meld_invis +____exports.modifier_meld_damage = __TS__Class() +local modifier_meld_damage = ____exports.modifier_meld_damage +modifier_meld_damage.name = "modifier_meld_damage" +modifier_meld_damage.____file_path = "scripts/vscripts/abilities/heroes/templar_assassin/ability_templar_assassin_meld_custom.lua" +__TS__ClassExtends(modifier_meld_damage, BaseModifier) +function modifier_meld_damage.prototype.____constructor(self, ...) + BaseModifier.prototype.____constructor(self, ...) + self.damage = 0 +end +function modifier_meld_damage.prototype.IsHidden(self) + return true +end +function modifier_meld_damage.prototype.IsPurgable(self) + return false +end +function modifier_meld_damage.prototype.OnCreated(self) + local ____opt_0 = self:GetAbility() + self.damage = ____opt_0 and ____opt_0:GetSpecialValueFor("meld_damage_deal") or 0 +end +function modifier_meld_damage.prototype.DeclareFunctions(self) + return {MODIFIER_PROPERTY_PREATTACK_BONUS_DAMAGE, MODIFIER_PROPERTY_SUPPRESS_CLEAVE, MODIFIER_EVENT_ON_ATTACK_LANDED} +end +function modifier_meld_damage.prototype.GetModifierPreAttack_BonusDamage(self) + return self.damage +end +function modifier_meld_damage.prototype.GetSuppressCleave(self) + return 1 +end +function modifier_meld_damage.prototype.OnAttackLanded(self, event) + if not IsServer() then + return + end + if event.attacker ~= self:GetParent() then + return + end + self:Destroy() +end +modifier_meld_damage = __TS__Decorate( + modifier_meld_damage, + modifier_meld_damage, + {registerModifier(nil)}, + {kind = "class", name = "modifier_meld_damage"} +) +____exports.modifier_meld_damage = modifier_meld_damage +____exports.modifier_meld_reduction = __TS__Class() +local modifier_meld_reduction = ____exports.modifier_meld_reduction +modifier_meld_reduction.name = "modifier_meld_reduction" +modifier_meld_reduction.____file_path = "scripts/vscripts/abilities/heroes/templar_assassin/ability_templar_assassin_meld_custom.lua" +__TS__ClassExtends(modifier_meld_reduction, BaseModifier) +function modifier_meld_reduction.prototype.____constructor(self, ...) + BaseModifier.prototype.____constructor(self, ...) + self.armorReduction = 0 +end +function modifier_meld_reduction.prototype.GetAttributes(self) + return MODIFIER_ATTRIBUTE_MULTIPLE +end +function modifier_meld_reduction.prototype.IsDebuff(self) + return true +end +function modifier_meld_reduction.prototype.IsPurgable(self) + return true +end +function modifier_meld_reduction.prototype.OnCreated(self) + local ____opt_2 = self:GetAbility() + self.armorReduction = ____opt_2 and ____opt_2:GetSpecialValueFor("armor_reduction") or 0 +end +function modifier_meld_reduction.prototype.OnRefresh(self) + self:OnCreated() +end +function modifier_meld_reduction.prototype.DeclareFunctions(self) + return {MODIFIER_PROPERTY_PHYSICAL_ARMOR_BONUS} +end +function modifier_meld_reduction.prototype.GetModifierPhysicalArmorBonus(self) + return self.armorReduction +end +modifier_meld_reduction = __TS__Decorate( + modifier_meld_reduction, + modifier_meld_reduction, + {registerModifier(nil)}, + {kind = "class", name = "modifier_meld_reduction"} +) +____exports.modifier_meld_reduction = modifier_meld_reduction +return ____exports diff --git a/scripts/vscripts/abilities/heroes/templar_assassin/ability_templar_assassin_psi_blades_custom.lua b/scripts/vscripts/abilities/heroes/templar_assassin/ability_templar_assassin_psi_blades_custom.lua new file mode 100644 index 0000000..135984e --- /dev/null +++ b/scripts/vscripts/abilities/heroes/templar_assassin/ability_templar_assassin_psi_blades_custom.lua @@ -0,0 +1,139 @@ +local ____lualib = require("lualib_bundle") +local __TS__Class = ____lualib.__TS__Class +local __TS__ClassExtends = ____lualib.__TS__ClassExtends +local __TS__Decorate = ____lualib.__TS__Decorate +local ____exports = {} +local ____dota_ts_adapter = require("lib.dota_ts_adapter") +local BaseAbility = ____dota_ts_adapter.BaseAbility +local BaseModifier = ____dota_ts_adapter.BaseModifier +local registerAbility = ____dota_ts_adapter.registerAbility +local registerModifier = ____dota_ts_adapter.registerModifier +____exports.ability_templar_assassin_psi_blades_custom = __TS__Class() +local ability_templar_assassin_psi_blades_custom = ____exports.ability_templar_assassin_psi_blades_custom +ability_templar_assassin_psi_blades_custom.name = "ability_templar_assassin_psi_blades_custom" +ability_templar_assassin_psi_blades_custom.____file_path = "scripts/vscripts/abilities/heroes/templar_assassin/ability_templar_assassin_psi_blades_custom.lua" +__TS__ClassExtends(ability_templar_assassin_psi_blades_custom, BaseAbility) +function ability_templar_assassin_psi_blades_custom.prototype.Precache(self, context) + PrecacheResource("particle", "particles/units/heroes/hero_templar_assassin/templar_assassin_psi_blade.vpcf", context) +end +function ability_templar_assassin_psi_blades_custom.prototype.GetIntrinsicModifierName(self) + return ____exports.modifier_templar_assassin_psi_blades_custom.name +end +function ability_templar_assassin_psi_blades_custom.prototype.GetBehavior(self) + return DOTA_ABILITY_BEHAVIOR_PASSIVE +end +ability_templar_assassin_psi_blades_custom = __TS__Decorate( + ability_templar_assassin_psi_blades_custom, + ability_templar_assassin_psi_blades_custom, + {registerAbility(nil)}, + {kind = "class", name = "ability_templar_assassin_psi_blades_custom"} +) +____exports.ability_templar_assassin_psi_blades_custom = ability_templar_assassin_psi_blades_custom +____exports.modifier_templar_assassin_psi_blades_custom = __TS__Class() +local modifier_templar_assassin_psi_blades_custom = ____exports.modifier_templar_assassin_psi_blades_custom +modifier_templar_assassin_psi_blades_custom.name = "modifier_templar_assassin_psi_blades_custom" +modifier_templar_assassin_psi_blades_custom.____file_path = "scripts/vscripts/abilities/heroes/templar_assassin/ability_templar_assassin_psi_blades_custom.lua" +__TS__ClassExtends(modifier_templar_assassin_psi_blades_custom, BaseModifier) +function modifier_templar_assassin_psi_blades_custom.prototype.IsHidden(self) + return true +end +function modifier_templar_assassin_psi_blades_custom.prototype.IsPurgable(self) + return false +end +function modifier_templar_assassin_psi_blades_custom.prototype.DeclareFunctions(self) + return {MODIFIER_PROPERTY_ATTACK_RANGE_BONUS, MODIFIER_EVENT_ON_ATTACK_LANDED} +end +function modifier_templar_assassin_psi_blades_custom.prototype.GetModifierAttackRangeBonus(self) + local ability = self:GetAbility() + if not ability then + return 0 + end + local parent = self:GetParent() + local level = parent and parent:IsHero() and math.max( + 1, + parent:GetLevel() + ) or 1 + local base = ability:GetSpecialValueFor("bonus_attack_range") + local perLevel = ability:GetSpecialValueFor("bonus_attack_range_per_hero_level") + return base + (level - 1) * perLevel +end +function modifier_templar_assassin_psi_blades_custom.prototype.OnAttackLanded(self, event) + if not IsServer() then + return + end + local parent = self:GetParent() + local ability = self:GetAbility() + if not ability then + return + end + if event.attacker ~= parent then + return + end + if not event.target or event.target:IsBuilding() then + return + end + if parent:PassivesDisabled() then + return + end + local damage = event.damage + local distance = ability:GetSpecialValueFor("attack_spill_range") + local width = ability:GetSpecialValueFor("attack_spill_width") + local spillPct = ability:GetSpecialValueFor("attack_spill_pct") / 100 + local direction = (event.target:GetAbsOrigin() - parent:GetAbsOrigin()):Normalized() + local start = event.target:GetAbsOrigin() + local ____end = start + direction * distance + local enemies = FindUnitsInLine( + parent:GetTeamNumber(), + start, + ____end, + nil, + width, + DOTA_UNIT_TARGET_TEAM_ENEMY, + DOTA_UNIT_TARGET_HERO + DOTA_UNIT_TARGET_BASIC, + DOTA_UNIT_TARGET_FLAG_MAGIC_IMMUNE_ENEMIES + ) + for ____, enemy in ipairs(enemies) do + do + if enemy == event.target then + goto __continue16 + end + local psiParticle = ParticleManager:CreateParticle("particles/units/heroes/hero_templar_assassin/templar_assassin_psi_blade.vpcf", PATTACH_POINT_FOLLOW, parent) + ParticleManager:SetParticleControlEnt( + psiParticle, + 0, + event.target, + PATTACH_POINT_FOLLOW, + "attach_hitloc", + event.target:GetAbsOrigin(), + true + ) + ParticleManager:SetParticleControlEnt( + psiParticle, + 1, + enemy, + PATTACH_POINT_FOLLOW, + "attach_hitloc", + enemy:GetAbsOrigin(), + true + ) + ParticleManager:ReleaseParticleIndex(psiParticle) + ApplyDamage({ + victim = enemy, + attacker = parent, + damage = damage * spillPct, + damage_type = DAMAGE_TYPE_PURE, + damage_flags = DOTA_DAMAGE_FLAG_ATTACK_MODIFIER, + ability = ability + }) + end + ::__continue16:: + end +end +modifier_templar_assassin_psi_blades_custom = __TS__Decorate( + modifier_templar_assassin_psi_blades_custom, + modifier_templar_assassin_psi_blades_custom, + {registerModifier(nil)}, + {kind = "class", name = "modifier_templar_assassin_psi_blades_custom"} +) +____exports.modifier_templar_assassin_psi_blades_custom = modifier_templar_assassin_psi_blades_custom +return ____exports diff --git a/scripts/vscripts/abilities/heroes/templar_assassin/ability_templar_assassin_refraction_custom.lua b/scripts/vscripts/abilities/heroes/templar_assassin/ability_templar_assassin_refraction_custom.lua new file mode 100644 index 0000000..6a46dd3 --- /dev/null +++ b/scripts/vscripts/abilities/heroes/templar_assassin/ability_templar_assassin_refraction_custom.lua @@ -0,0 +1,197 @@ +local ____lualib = require("lualib_bundle") +local __TS__Class = ____lualib.__TS__Class +local __TS__ClassExtends = ____lualib.__TS__ClassExtends +local __TS__Decorate = ____lualib.__TS__Decorate +local ____exports = {} +local ____dota_ts_adapter = require("lib.dota_ts_adapter") +local BaseAbility = ____dota_ts_adapter.BaseAbility +local BaseModifier = ____dota_ts_adapter.BaseModifier +local registerAbility = ____dota_ts_adapter.registerAbility +local registerModifier = ____dota_ts_adapter.registerModifier +____exports.ability_templar_assassin_refraction_custom = __TS__Class() +local ability_templar_assassin_refraction_custom = ____exports.ability_templar_assassin_refraction_custom +ability_templar_assassin_refraction_custom.name = "ability_templar_assassin_refraction_custom" +ability_templar_assassin_refraction_custom.____file_path = "scripts/vscripts/abilities/heroes/templar_assassin/ability_templar_assassin_refraction_custom.lua" +__TS__ClassExtends(ability_templar_assassin_refraction_custom, BaseAbility) +function ability_templar_assassin_refraction_custom.prototype.Precache(self, context) + PrecacheResource("particle", "particles/units/heroes/hero_templar_assassin/templar_assassin_refraction_dmg.vpcf", context) + PrecacheResource("particle", "particles/units/heroes/hero_templar_assassin/templar_assassin_refraction.vpcf", context) + PrecacheResource("particle", "particles/units/heroes/hero_templar_assassin/templar_assassin_refract_hit.vpcf", context) +end +function ability_templar_assassin_refraction_custom.prototype.OnSpellStart(self) + if not IsServer() then + return + end + local caster = self:GetCaster() + local duration = self:GetSpecialValueFor("duration") + caster:AddNewModifier(caster, self, ____exports.modifier_templar_assassin_refraction_custom_damage.name, {duration = duration}) + caster:AddNewModifier(caster, self, ____exports.modifier_templar_assassin_refraction_custom_absorb.name, {duration = duration}) + caster:EmitSound("Hero_TemplarAssassin.Refraction") + self:EndCooldown() + self:SetActivated(false) +end +ability_templar_assassin_refraction_custom = __TS__Decorate( + ability_templar_assassin_refraction_custom, + ability_templar_assassin_refraction_custom, + {registerAbility(nil)}, + {kind = "class", name = "ability_templar_assassin_refraction_custom"} +) +____exports.ability_templar_assassin_refraction_custom = ability_templar_assassin_refraction_custom +____exports.modifier_templar_assassin_refraction_custom_damage = __TS__Class() +local modifier_templar_assassin_refraction_custom_damage = ____exports.modifier_templar_assassin_refraction_custom_damage +modifier_templar_assassin_refraction_custom_damage.name = "modifier_templar_assassin_refraction_custom_damage" +modifier_templar_assassin_refraction_custom_damage.____file_path = "scripts/vscripts/abilities/heroes/templar_assassin/ability_templar_assassin_refraction_custom.lua" +__TS__ClassExtends(modifier_templar_assassin_refraction_custom_damage, BaseModifier) +function modifier_templar_assassin_refraction_custom_damage.prototype.____constructor(self, ...) + BaseModifier.prototype.____constructor(self, ...) + self.bonusDamage = 0 +end +function modifier_templar_assassin_refraction_custom_damage.prototype.IsPurgable(self) + return false +end +function modifier_templar_assassin_refraction_custom_damage.prototype.GetPriority(self) + return MODIFIER_PRIORITY_ULTRA +end +function modifier_templar_assassin_refraction_custom_damage.prototype.OnCreated(self) + local ability = self:GetAbility() + if not ability then + return + end + self.bonusDamage = ability:GetSpecialValueFor("bonus_damage") + if not IsServer() then + return + end + local instances = ability:GetSpecialValueFor("instances") + local caster = self:GetCaster() + if caster and HasShard(nil, caster) then + instances = instances + ability:GetSpecialValueFor("shard_instances_bonus") + end + self:SetStackCount(instances) +end +function modifier_templar_assassin_refraction_custom_damage.prototype.OnRefresh(self) + self:OnCreated() +end +function modifier_templar_assassin_refraction_custom_damage.prototype.OnStackCountChanged(self) + if not IsServer() then + return + end + if self:GetStackCount() <= 0 then + self:Destroy() + end +end +function modifier_templar_assassin_refraction_custom_damage.prototype.DeclareFunctions(self) + return {MODIFIER_PROPERTY_PREATTACK_BONUS_DAMAGE, MODIFIER_EVENT_ON_ATTACK_LANDED} +end +function modifier_templar_assassin_refraction_custom_damage.prototype.GetModifierPreAttack_BonusDamage(self) + return self.bonusDamage +end +function modifier_templar_assassin_refraction_custom_damage.prototype.OnAttackLanded(self, event) + if not IsServer() then + return + end + if event.attacker ~= self:GetParent() then + return + end + self:DecrementStackCount() + if event.target ~= nil then + event.target:EmitSound("Hero_TemplarAssassin.Refraction.Damage") + end +end +function modifier_templar_assassin_refraction_custom_damage.prototype.GetTexture(self) + return "templar_assassin_refraction_damage" +end +modifier_templar_assassin_refraction_custom_damage = __TS__Decorate( + modifier_templar_assassin_refraction_custom_damage, + modifier_templar_assassin_refraction_custom_damage, + {registerModifier(nil)}, + {kind = "class", name = "modifier_templar_assassin_refraction_custom_damage"} +) +____exports.modifier_templar_assassin_refraction_custom_damage = modifier_templar_assassin_refraction_custom_damage +____exports.modifier_templar_assassin_refraction_custom_absorb = __TS__Class() +local modifier_templar_assassin_refraction_custom_absorb = ____exports.modifier_templar_assassin_refraction_custom_absorb +modifier_templar_assassin_refraction_custom_absorb.name = "modifier_templar_assassin_refraction_custom_absorb" +modifier_templar_assassin_refraction_custom_absorb.____file_path = "scripts/vscripts/abilities/heroes/templar_assassin/ability_templar_assassin_refraction_custom.lua" +__TS__ClassExtends(modifier_templar_assassin_refraction_custom_absorb, BaseModifier) +function modifier_templar_assassin_refraction_custom_absorb.prototype.____constructor(self, ...) + BaseModifier.prototype.____constructor(self, ...) + self.damageThreshold = 0 +end +function modifier_templar_assassin_refraction_custom_absorb.prototype.IsPurgable(self) + return false +end +function modifier_templar_assassin_refraction_custom_absorb.prototype.GetPriority(self) + return MODIFIER_PRIORITY_ULTRA +end +function modifier_templar_assassin_refraction_custom_absorb.prototype.OnCreated(self) + local ability = self:GetAbility() + if not ability then + return + end + self.damageThreshold = ability:GetSpecialValueFor("damage_threshold") + if not IsServer() then + return + end + local instances = ability:GetSpecialValueFor("instances") + local caster = self:GetCaster() + if caster and HasShard(nil, caster) then + instances = instances + ability:GetSpecialValueFor("shard_instances_bonus") + end + self:SetStackCount(instances) +end +function modifier_templar_assassin_refraction_custom_absorb.prototype.OnRefresh(self) + self:OnCreated() +end +function modifier_templar_assassin_refraction_custom_absorb.prototype.OnDestroy(self) + if not IsServer() then + return + end + local ability = self:GetAbility() + if not ability then + return + end + ability:SetActivated(true) + ability:UseResources(false, false, false, true) +end +function modifier_templar_assassin_refraction_custom_absorb.prototype.OnStackCountChanged(self) + if not IsServer() then + return + end + if self:GetStackCount() <= 0 then + self:GetParent():EmitSound("TA.Shield_break") + self:Destroy() + end +end +function modifier_templar_assassin_refraction_custom_absorb.prototype.DeclareFunctions(self) + return {MODIFIER_PROPERTY_TOTAL_CONSTANT_BLOCK} +end +function modifier_templar_assassin_refraction_custom_absorb.prototype.GetModifierTotal_ConstantBlock(self, event) + if not IsServer() then + return 0 + end + if event.damage < self.damageThreshold then + return 0 + end + if bit.band(event.damage_flags, DOTA_DAMAGE_FLAG_HPLOSS) == DOTA_DAMAGE_FLAG_HPLOSS then + return 0 + end + self:GetParent():EmitSound("Hero_TemplarAssassin.Refraction.Absorb") + self:DecrementStackCount() + return event.damage +end +function modifier_templar_assassin_refraction_custom_absorb.prototype.GetEffectName(self) + return "particles/units/heroes/hero_templar_assassin/templar_assassin_refraction.vpcf" +end +function modifier_templar_assassin_refraction_custom_absorb.prototype.GetEffectAttachType(self) + return PATTACH_ABSORIGIN_FOLLOW +end +function modifier_templar_assassin_refraction_custom_absorb.prototype.GetTexture(self) + return "templar_assassin_refraction" +end +modifier_templar_assassin_refraction_custom_absorb = __TS__Decorate( + modifier_templar_assassin_refraction_custom_absorb, + modifier_templar_assassin_refraction_custom_absorb, + {registerModifier(nil)}, + {kind = "class", name = "modifier_templar_assassin_refraction_custom_absorb"} +) +____exports.modifier_templar_assassin_refraction_custom_absorb = modifier_templar_assassin_refraction_custom_absorb +return ____exports diff --git a/scripts/vscripts/abilities/heroes/templar_assassin/ability_templar_assassin_refusion_trap_custom.lua b/scripts/vscripts/abilities/heroes/templar_assassin/ability_templar_assassin_refusion_trap_custom.lua new file mode 100644 index 0000000..12fa7a1 --- /dev/null +++ b/scripts/vscripts/abilities/heroes/templar_assassin/ability_templar_assassin_refusion_trap_custom.lua @@ -0,0 +1,148 @@ +local ____lualib = require("lualib_bundle") +local __TS__Class = ____lualib.__TS__Class +local __TS__ClassExtends = ____lualib.__TS__ClassExtends +local __TS__Decorate = ____lualib.__TS__Decorate +local ____exports = {} +local ____dota_ts_adapter = require("lib.dota_ts_adapter") +local BaseAbility = ____dota_ts_adapter.BaseAbility +local BaseModifier = ____dota_ts_adapter.BaseModifier +local registerAbility = ____dota_ts_adapter.registerAbility +local registerModifier = ____dota_ts_adapter.registerModifier +____exports.ability_templar_assassin_refusion_trap_custom = __TS__Class() +local ability_templar_assassin_refusion_trap_custom = ____exports.ability_templar_assassin_refusion_trap_custom +ability_templar_assassin_refusion_trap_custom.name = "ability_templar_assassin_refusion_trap_custom" +ability_templar_assassin_refusion_trap_custom.____file_path = "scripts/vscripts/abilities/heroes/templar_assassin/ability_templar_assassin_refusion_trap_custom.lua" +__TS__ClassExtends(ability_templar_assassin_refusion_trap_custom, BaseAbility) +function ability_templar_assassin_refusion_trap_custom.prototype.OnSpellStart(self) + if not IsServer() then + return + end + local caster = self:GetCaster() + local point = self:GetCursorPosition() + local duration = self:GetSpecialValueFor("duration") + local trap = CreateUnitByName( + "npc_templar_trap", + point, + true, + nil, + nil, + DOTA_TEAM_NEUTRALS + ) + trap:AddNewModifier(caster, self, ____exports.modifier_refusion_trap_infinity.name, {duration = duration}) +end +function ability_templar_assassin_refusion_trap_custom.prototype.OnProjectileHit(self, target) + if not target or target:IsInvulnerable() then + return true + end + self:GetCaster():PerformAttack( + target, + true, + true, + true, + false, + false, + false, + true + ) + return true +end +ability_templar_assassin_refusion_trap_custom = __TS__Decorate( + ability_templar_assassin_refusion_trap_custom, + ability_templar_assassin_refusion_trap_custom, + {registerAbility(nil)}, + {kind = "class", name = "ability_templar_assassin_refusion_trap_custom"} +) +____exports.ability_templar_assassin_refusion_trap_custom = ability_templar_assassin_refusion_trap_custom +____exports.modifier_refusion_trap_infinity = __TS__Class() +local modifier_refusion_trap_infinity = ____exports.modifier_refusion_trap_infinity +modifier_refusion_trap_infinity.name = "modifier_refusion_trap_infinity" +modifier_refusion_trap_infinity.____file_path = "scripts/vscripts/abilities/heroes/templar_assassin/ability_templar_assassin_refusion_trap_custom.lua" +__TS__ClassExtends(modifier_refusion_trap_infinity, BaseModifier) +function modifier_refusion_trap_infinity.prototype.IsHidden(self) + return true +end +function modifier_refusion_trap_infinity.prototype.IsPurgable(self) + return false +end +function modifier_refusion_trap_infinity.prototype.DeclareFunctions(self) + return {MODIFIER_EVENT_ON_ATTACK_LANDED, MODIFIER_PROPERTY_MIN_HEALTH} +end +function modifier_refusion_trap_infinity.prototype.GetMinHealth(self) + return 1 +end +function modifier_refusion_trap_infinity.prototype.CheckState(self) + return {[MODIFIER_STATE_NO_HEALTH_BAR] = true, [MODIFIER_STATE_NO_UNIT_COLLISION] = true} +end +function modifier_refusion_trap_infinity.prototype.OnAttackLanded(self, event) + if not IsServer() then + return + end + local trap = self:GetParent() + local caster = self:GetCaster() + local ability = self:GetAbility() + if not ability then + return + end + if event.target ~= trap or event.attacker ~= caster then + return + end + local enemies = FindUnitsInRadius( + caster:GetTeamNumber(), + trap:GetAbsOrigin(), + nil, + ability:GetSpecialValueFor("radius"), + DOTA_UNIT_TARGET_TEAM_ENEMY, + DOTA_UNIT_TARGET_HERO + DOTA_UNIT_TARGET_BASIC, + ability:GetAbilityTargetFlags(), + FIND_ANY_ORDER, + false + ) + local count = 0 + local maxCount = ability:GetSpecialValueFor("count") + for ____, enemy in ipairs(enemies) do + do + if enemy == trap or count >= maxCount then + goto __continue15 + end + ProjectileManager:CreateTrackingProjectile({ + Target = enemy, + Source = trap, + Ability = ability, + EffectName = caster:GetRangedProjectileName(), + iMoveSpeed = caster:GetProjectileSpeed(), + iSourceAttachment = DOTA_PROJECTILE_ATTACHMENT_HITLOCATION, + bDodgeable = true, + bIsAttack = true + }) + count = count + 1 + end + ::__continue15:: + end +end +function modifier_refusion_trap_infinity.prototype.OnDestroy(self) + if not IsServer() then + return + end + self:GetParent():RemoveModifierByName(____exports.modifier_refusion_trap_infinity.name) + self:GetParent():RemoveSelf() + self:GetParent():AddNoDraw() + local blinkFx = ParticleManager:CreateParticle( + "particles/econ/items/templar_assassin/templar_assassin_butterfly/templar_assassin_trap_explode_butterfly.vpcf", + PATTACH_ABSORIGIN, + self:GetParent() + ) + ParticleManager:SetParticleControl( + blinkFx, + 0, + self:GetParent():GetAbsOrigin() + ) + ParticleManager:ReleaseParticleIndex(blinkFx) +end +modifier_refusion_trap_infinity = __TS__Decorate( + modifier_refusion_trap_infinity, + modifier_refusion_trap_infinity, + {registerModifier(nil)}, + {kind = "class", name = "modifier_refusion_trap_infinity"} +) +____exports.modifier_refusion_trap_infinity = modifier_refusion_trap_infinity +return ____exports diff --git a/scripts/vscripts/abilities/heroes/templar_assassin/ability_templar_assassin_templar_secret_custom.lua b/scripts/vscripts/abilities/heroes/templar_assassin/ability_templar_assassin_templar_secret_custom.lua new file mode 100644 index 0000000..9429aef --- /dev/null +++ b/scripts/vscripts/abilities/heroes/templar_assassin/ability_templar_assassin_templar_secret_custom.lua @@ -0,0 +1,91 @@ +local ____lualib = require("lualib_bundle") +local __TS__Class = ____lualib.__TS__Class +local __TS__ClassExtends = ____lualib.__TS__ClassExtends +local __TS__Decorate = ____lualib.__TS__Decorate +local ____exports = {} +local ____dota_ts_adapter = require("lib.dota_ts_adapter") +local BaseAbility = ____dota_ts_adapter.BaseAbility +local BaseModifier = ____dota_ts_adapter.BaseModifier +local registerAbility = ____dota_ts_adapter.registerAbility +local registerModifier = ____dota_ts_adapter.registerModifier +local ____ability_stacking_crit = require("abilities.modifiers.ability_stacking_crit") +local modifier_stacking_crit = ____ability_stacking_crit.modifier_stacking_crit +____exports.ability_templar_assassin_templar_secret_custom = __TS__Class() +local ability_templar_assassin_templar_secret_custom = ____exports.ability_templar_assassin_templar_secret_custom +ability_templar_assassin_templar_secret_custom.name = "ability_templar_assassin_templar_secret_custom" +ability_templar_assassin_templar_secret_custom.____file_path = "scripts/vscripts/abilities/heroes/templar_assassin/ability_templar_assassin_templar_secret_custom.lua" +__TS__ClassExtends(ability_templar_assassin_templar_secret_custom, BaseAbility) +function ability_templar_assassin_templar_secret_custom.prototype.GetIntrinsicModifierName(self) + return ____exports.modifier_templar_secret.name +end +ability_templar_assassin_templar_secret_custom = __TS__Decorate( + ability_templar_assassin_templar_secret_custom, + ability_templar_assassin_templar_secret_custom, + {registerAbility(nil)}, + {kind = "class", name = "ability_templar_assassin_templar_secret_custom"} +) +____exports.ability_templar_assassin_templar_secret_custom = ability_templar_assassin_templar_secret_custom +____exports.modifier_templar_secret = __TS__Class() +local modifier_templar_secret = ____exports.modifier_templar_secret +modifier_templar_secret.name = "modifier_templar_secret" +modifier_templar_secret.____file_path = "scripts/vscripts/abilities/heroes/templar_assassin/ability_templar_assassin_templar_secret_custom.lua" +__TS__ClassExtends(modifier_templar_secret, BaseModifier) +function modifier_templar_secret.prototype.IsHidden(self) + return true +end +function modifier_templar_secret.prototype.IsPurgable(self) + return false +end +function modifier_templar_secret.prototype.OnCreated(self) + if not IsServer() then + return + end + self:syncCustomCrit() +end +function modifier_templar_secret.prototype.OnRefresh(self) + if not IsServer() then + return + end + self:syncCustomCrit() +end +function modifier_templar_secret.prototype.OnDestroy(self) + if not IsServer() then + return + end + local ____opt_0 = modifier_stacking_crit:GetForUnit(self:GetParent()) + if ____opt_0 ~= nil then + ____opt_0:RemoveCrit("templar_secret") + end +end +function modifier_templar_secret.prototype.syncCustomCrit(self) + local ability = self:GetAbility() + if not ability then + return + end + local chance = ability:GetSpecialValueFor("temp_crit_chance") + local mult = ability:GetSpecialValueFor("temp_crit_mult") + local ____opt_2 = modifier_stacking_crit:GetForUnit(self:GetParent()) + if ____opt_2 ~= nil then + ____opt_2:UpdateCrit( + 0, + 0, + chance, + mult, + "templar_secret" + ) + end +end +function modifier_templar_secret.prototype.DeclareFunctions(self) + return {MODIFIER_PROPERTY_PROJECTILE_NAME} +end +function modifier_templar_secret.prototype.GetModifierProjectileName(self) + return "particles/econ/items/templar_assassin/templar_assassin_butterfly/templar_assassin_meld_attack_butterfly.vpcf" +end +modifier_templar_secret = __TS__Decorate( + modifier_templar_secret, + modifier_templar_secret, + {registerModifier(nil)}, + {kind = "class", name = "modifier_templar_secret"} +) +____exports.modifier_templar_secret = modifier_templar_secret +return ____exports diff --git a/scripts/vscripts/abilities/heroes/troll_warlord/troll_warlord_active_axes_melee.lua b/scripts/vscripts/abilities/heroes/troll_warlord/troll_warlord_active_axes_melee.lua new file mode 100644 index 0000000..df04ffd --- /dev/null +++ b/scripts/vscripts/abilities/heroes/troll_warlord/troll_warlord_active_axes_melee.lua @@ -0,0 +1,287 @@ +local ____lualib = require("lualib_bundle") +local __TS__Class = ____lualib.__TS__Class +local __TS__ClassExtends = ____lualib.__TS__ClassExtends +local __TS__Decorate = ____lualib.__TS__Decorate +local __TS__ArrayFind = ____lualib.__TS__ArrayFind +local ____exports = {} +local ____dota_ts_adapter = require("lib.dota_ts_adapter") +local BaseAbility = ____dota_ts_adapter.BaseAbility +local BaseModifier = ____dota_ts_adapter.BaseModifier +local registerAbility = ____dota_ts_adapter.registerAbility +local registerModifier = ____dota_ts_adapter.registerModifier +____exports.troll_warlord_active_axes_melee = __TS__Class() +local troll_warlord_active_axes_melee = ____exports.troll_warlord_active_axes_melee +troll_warlord_active_axes_melee.name = "troll_warlord_active_axes_melee" +troll_warlord_active_axes_melee.____file_path = "scripts/vscripts/abilities/heroes/troll_warlord/troll_warlord_active_axes_melee.lua" +__TS__ClassExtends(troll_warlord_active_axes_melee, BaseAbility) +function troll_warlord_active_axes_melee.prototype.Precache(self, context) + PrecacheResource("particle", "particles/units/heroes/hero_troll_warlord/troll_warlord_whirling_axe_melee.vpcf", context) +end +function troll_warlord_active_axes_melee.prototype.OnUpgrade(self) + self:SetActivated(true) +end +function troll_warlord_active_axes_melee.prototype.OnSpellStart(self) + local caster = self:GetCaster() + local switchStance = caster:FindAbilityByName("troll_warlord_switch_stance_custom") + local ____opt_0 = switchStance and switchStance.ForceSetMeleeState + if ____opt_0 ~= nil then + ____opt_0(switchStance, true) + end + caster:EmitSound("Hero_TrollWarlord.WhirlingAxes.Melee") + caster:StartGesture(ACT_DOTA_CAST_ABILITY_3) + local whirlDuration = self:GetSpecialValueFor("whirl_duration") + local casterLocation = caster:GetAbsOrigin() + local max = 1 + local moreDamage = 0 + local particle = "particles/units/heroes/hero_troll_warlord/troll_warlord_whirling_axe_melee.vpcf" + do + local i = 1 + while i <= max do + local angleEast = QAngle(0, 90, 0) + local angleWest = QAngle(0, -90, 0) + if i == 2 then + angleEast = QAngle(0, 0, 0) + angleWest = QAngle(0, 180, 0) + end + local startRadius = 100 + local forwardVector = caster:GetForwardVector() + local frontPosition = casterLocation:__add(forwardVector:__mul(startRadius)) + local positionEast = RotatePosition(casterLocation, angleEast, frontPosition) + local positionWest = RotatePosition(casterLocation, angleWest, frontPosition) + positionEast = GetGroundPosition(positionEast, caster) + positionWest = GetGroundPosition(positionWest, caster) + positionEast.z = positionEast.z + 75 + positionWest.z = positionWest.z + 75 + local index = DoUniqueString("index") + self[index] = {} + self.whirlingAxesEast = CreateUnitByName( + "npc_dota_troll_warlord_axe", + positionEast, + false, + caster, + caster, + caster:GetTeam() + ) + self.whirlingAxesEast:SetAbsOrigin(positionEast) + self.whirlingAxesEast:AddNewModifier(caster, self, ____exports.modifier_troll_warlord_whirling_axes_melee_custom_thinker.name, {more_damage = moreDamage, i = i, index = index, count = 1}) + self.whirlingAxesEast.particle = ParticleManager:CreateParticle(particle, PATTACH_ABSORIGIN_FOLLOW, self.whirlingAxesEast) + ParticleManager:SetParticleControl( + self.whirlingAxesEast.particle, + 0, + self.whirlingAxesEast:GetAbsOrigin() + ) + ParticleManager:SetParticleControl( + self.whirlingAxesEast.particle, + 1, + self.whirlingAxesEast:GetAbsOrigin() + ) + ParticleManager:SetParticleControl( + self.whirlingAxesEast.particle, + 4, + Vector(whirlDuration, 0, 0) + ) + self.whirlingAxesEast.axeRadius = startRadius + self.whirlingAxesEast.startTime = GameRules:GetGameTime() + self.whirlingAxesEast.side = 0 + self.whirlingAxesWest = CreateUnitByName( + "npc_dota_troll_warlord_axe", + positionWest, + false, + caster, + caster, + caster:GetTeam() + ) + self.whirlingAxesWest:SetAbsOrigin(positionWest) + self.whirlingAxesWest:AddNewModifier(caster, self, ____exports.modifier_troll_warlord_whirling_axes_melee_custom_thinker.name, {more_damage = moreDamage, i = i, index = index, count = 2}) + self.whirlingAxesWest.particle = ParticleManager:CreateParticle(particle, PATTACH_ABSORIGIN_FOLLOW, self.whirlingAxesWest) + ParticleManager:SetParticleControl( + self.whirlingAxesWest.particle, + 0, + self.whirlingAxesWest:GetAbsOrigin() + ) + ParticleManager:SetParticleControl( + self.whirlingAxesWest.particle, + 1, + self.whirlingAxesWest:GetAbsOrigin() + ) + ParticleManager:SetParticleControl( + self.whirlingAxesWest.particle, + 4, + Vector(whirlDuration, 0, 0) + ) + self.whirlingAxesWest.axeRadius = startRadius + self.whirlingAxesWest.startTime = GameRules:GetGameTime() + self.whirlingAxesWest.side = 1 + i = i + 1 + end + end +end +troll_warlord_active_axes_melee = __TS__Decorate( + troll_warlord_active_axes_melee, + troll_warlord_active_axes_melee, + {registerAbility(nil)}, + {kind = "class", name = "troll_warlord_active_axes_melee"} +) +____exports.troll_warlord_active_axes_melee = troll_warlord_active_axes_melee +____exports.modifier_troll_warlord_whirling_axes_melee_custom_thinker = __TS__Class() +local modifier_troll_warlord_whirling_axes_melee_custom_thinker = ____exports.modifier_troll_warlord_whirling_axes_melee_custom_thinker +modifier_troll_warlord_whirling_axes_melee_custom_thinker.name = "modifier_troll_warlord_whirling_axes_melee_custom_thinker" +modifier_troll_warlord_whirling_axes_melee_custom_thinker.____file_path = "scripts/vscripts/abilities/heroes/troll_warlord/troll_warlord_active_axes_melee.lua" +__TS__ClassExtends(modifier_troll_warlord_whirling_axes_melee_custom_thinker, BaseModifier) +function modifier_troll_warlord_whirling_axes_melee_custom_thinker.prototype.OnCreated(self, params) + if not IsServer() then + return + end + self.index = params.index + self.moreDamage = params.more_damage + self:StartIntervalThink(FrameTime()) + self.i = params.i +end +function modifier_troll_warlord_whirling_axes_melee_custom_thinker.prototype.OnIntervalThink(self) + if not IsServer() then + return + end + local parent = self:GetParent() + local caster = self:GetCaster() + local ability = self:GetAbility() + if not caster:IsAlive() then + parent:RemoveSelf() + return + end + if not parent.startTime then + return + end + local elapsedTime = GameRules:GetGameTime() - parent.startTime + local damage = ability:GetSpecialValueFor("damage") + local attackDamagePct = ability:GetSpecialValueFor("attack_damage_pct") + local hitRadius = ability:GetSpecialValueFor("hit_radius") + local maxRange = ability:GetSpecialValueFor("max_range") + local axeMovementSpeed = ability:GetSpecialValueFor("axe_movement_speed") + local blindDuration = ability:GetSpecialValueFor("debuff_duration") + local whirlDuration = ability:GetSpecialValueFor("whirl_duration") + local casterLocation = caster:GetAbsOrigin() + local currentRadius = parent.axeRadius + local deltaRadius = axeMovementSpeed / whirlDuration / 2 * FrameTime() + if elapsedTime >= whirlDuration * 0.65 then + currentRadius = currentRadius - deltaRadius + else + currentRadius = currentRadius + deltaRadius + end + currentRadius = math.min(currentRadius, maxRange - hitRadius) + parent.axeRadius = currentRadius + local rotationAngle + if self.i == 1 then + rotationAngle = parent.side == 1 and elapsedTime * 360 or elapsedTime * 360 + 180 + else + rotationAngle = parent.side == 1 and elapsedTime * 360 + 90 or elapsedTime * 360 - 90 + end + local relPos = RotatePosition( + Vector(0, 0, 0), + QAngle(0, -rotationAngle, 0), + Vector(0, currentRadius, 0) + ) + local absPos = GetGroundPosition( + relPos:__add(casterLocation), + parent + ) + absPos.z = absPos.z + 75 + parent:SetAbsOrigin(absPos) + ParticleManager:SetParticleControl( + parent.particle, + 0, + parent:GetAbsOrigin() + ) + ParticleManager:SetParticleControl( + parent.particle, + 1, + parent:GetAbsOrigin() + ) + ParticleManager:SetParticleControl( + parent.particle, + 3, + parent:GetAbsOrigin() + ) + if elapsedTime >= whirlDuration then + parent:RemoveSelf() + return + end + local units = FindUnitsInRadius( + caster:GetTeamNumber(), + parent:GetAbsOrigin(), + nil, + hitRadius, + DOTA_UNIT_TARGET_TEAM_ENEMY, + DOTA_UNIT_TARGET_HERO + DOTA_UNIT_TARGET_BASIC, + DOTA_UNIT_TARGET_FLAG_NONE, + FIND_CLOSEST, + false + ) + for ____, unit in ipairs(units) do + local wasHit = __TS__ArrayFind( + ability[self.index], + function(____, storedTarget) return unit == storedTarget end + ) + if not wasHit then + ability[self.index] = ability[self.index] or ({}) + local ____ability_self_index_4 = ability[self.index] + ____ability_self_index_4[#____ability_self_index_4 + 1] = unit + if not unit:IsBossCreature() then + ____exports.modifier_troll_warlord_whirling_axes_melee_custom:apply(unit, caster, ability, {duration = blindDuration}) + end + local attackDamage = caster:GetAverageTrueAttackDamage(unit) * (attackDamagePct / 100) + local totalDamage = damage + attackDamage + ApplyDamage({ + victim = unit, + attacker = caster, + damage = totalDamage, + damage_type = ability:GetAbilityDamageType(), + ability = ability + }) + unit:EmitSound("Hero_TrollWarlord.WhirlingAxes.Target") + end + end +end +function modifier_troll_warlord_whirling_axes_melee_custom_thinker.prototype.CheckState(self) + return {[MODIFIER_STATE_NO_UNIT_COLLISION] = true, [MODIFIER_STATE_NO_HEALTH_BAR] = true, [MODIFIER_STATE_INVULNERABLE] = true} +end +function modifier_troll_warlord_whirling_axes_melee_custom_thinker.prototype.OnDestroy(self) + if not IsServer() then + return + end + UTIL_Remove(self:GetParent()) +end +modifier_troll_warlord_whirling_axes_melee_custom_thinker = __TS__Decorate( + modifier_troll_warlord_whirling_axes_melee_custom_thinker, + modifier_troll_warlord_whirling_axes_melee_custom_thinker, + {registerModifier(nil)}, + {kind = "class", name = "modifier_troll_warlord_whirling_axes_melee_custom_thinker"} +) +____exports.modifier_troll_warlord_whirling_axes_melee_custom_thinker = modifier_troll_warlord_whirling_axes_melee_custom_thinker +____exports.modifier_troll_warlord_whirling_axes_melee_custom = __TS__Class() +local modifier_troll_warlord_whirling_axes_melee_custom = ____exports.modifier_troll_warlord_whirling_axes_melee_custom +modifier_troll_warlord_whirling_axes_melee_custom.name = "modifier_troll_warlord_whirling_axes_melee_custom" +modifier_troll_warlord_whirling_axes_melee_custom.____file_path = "scripts/vscripts/abilities/heroes/troll_warlord/troll_warlord_active_axes_melee.lua" +__TS__ClassExtends(modifier_troll_warlord_whirling_axes_melee_custom, BaseModifier) +function modifier_troll_warlord_whirling_axes_melee_custom.prototype.IsHidden(self) + return false +end +function modifier_troll_warlord_whirling_axes_melee_custom.prototype.IsPurgable(self) + return true +end +function modifier_troll_warlord_whirling_axes_melee_custom.prototype.CheckState(self) + return {[MODIFIER_STATE_DISARMED] = true} +end +function modifier_troll_warlord_whirling_axes_melee_custom.prototype.GetEffectName(self) + return "particles/units/heroes/hero_demonartist/demonartist_engulf_disarm/items2_fx/heavens_halberd.vpcf" +end +function modifier_troll_warlord_whirling_axes_melee_custom.prototype.GetEffectAttachType(self) + return PATTACH_OVERHEAD_FOLLOW +end +modifier_troll_warlord_whirling_axes_melee_custom = __TS__Decorate( + modifier_troll_warlord_whirling_axes_melee_custom, + modifier_troll_warlord_whirling_axes_melee_custom, + {registerModifier(nil)}, + {kind = "class", name = "modifier_troll_warlord_whirling_axes_melee_custom"} +) +____exports.modifier_troll_warlord_whirling_axes_melee_custom = modifier_troll_warlord_whirling_axes_melee_custom +return ____exports diff --git a/scripts/vscripts/abilities/heroes/troll_warlord/troll_warlord_active_axes_ranged.lua b/scripts/vscripts/abilities/heroes/troll_warlord/troll_warlord_active_axes_ranged.lua new file mode 100644 index 0000000..e3045d3 --- /dev/null +++ b/scripts/vscripts/abilities/heroes/troll_warlord/troll_warlord_active_axes_ranged.lua @@ -0,0 +1,163 @@ +local ____lualib = require("lualib_bundle") +local __TS__Class = ____lualib.__TS__Class +local __TS__ClassExtends = ____lualib.__TS__ClassExtends +local __TS__Delete = ____lualib.__TS__Delete +local __TS__Decorate = ____lualib.__TS__Decorate +local ____exports = {} +local ____dota_ts_adapter = require("lib.dota_ts_adapter") +local BaseAbility = ____dota_ts_adapter.BaseAbility +local BaseModifier = ____dota_ts_adapter.BaseModifier +local registerAbility = ____dota_ts_adapter.registerAbility +local registerModifier = ____dota_ts_adapter.registerModifier +--- Поворот единичного направления на угол (градусы) в плоскости XY +local function rotateVector2D(self, dir, degrees) + local rad = degrees * math.pi / 180 + local c = math.cos(rad) + local s = math.sin(rad) + return Vector(dir.x * c - dir.y * s, dir.x * s + dir.y * c, dir.z) +end +____exports.troll_warlord_active_axes_ranged = __TS__Class() +local troll_warlord_active_axes_ranged = ____exports.troll_warlord_active_axes_ranged +troll_warlord_active_axes_ranged.name = "troll_warlord_active_axes_ranged" +troll_warlord_active_axes_ranged.____file_path = "scripts/vscripts/abilities/heroes/troll_warlord/troll_warlord_active_axes_ranged.lua" +__TS__ClassExtends(troll_warlord_active_axes_ranged, BaseAbility) +function troll_warlord_active_axes_ranged.prototype.____constructor(self, ...) + BaseAbility.prototype.____constructor(self, ...) + self.projectileTarget = {} +end +function troll_warlord_active_axes_ranged.prototype.Precache(self, context) + PrecacheResource("particle", "particles/units/heroes/hero_troll_warlord/troll_warlord_whirling_axe_ranged.vpcf", context) +end +function troll_warlord_active_axes_ranged.prototype.OnUpgrade(self) + self:SetActivated(true) +end +function troll_warlord_active_axes_ranged.prototype.OnSpellStart(self) + local caster = self:GetCaster() + local switchStance = caster:FindAbilityByName("troll_warlord_switch_stance_custom") + local ____opt_0 = switchStance and switchStance.ForceSetMeleeState + if ____opt_0 ~= nil then + ____opt_0(switchStance, false) + end + local point = self:GetCursorPosition() + local caster_abs = caster:GetAbsOrigin() + local direction = (point - caster_abs):Normalized() + local axe_count = self:GetSpecialValueFor("axe_count") + local axe_width = self:GetSpecialValueFor("axe_width") + local axe_speed = self:GetSpecialValueFor("axe_speed") + local axe_range = self:GetSpecialValueFor("axe_range") + local axe_damage = self:GetSpecialValueFor("axe_damage") + local attackDamagePct = self:GetSpecialValueFor("attack_damage_pct") + local duration = self:GetSpecialValueFor("axe_slow_duration") + local axe_spread = self:GetSpecialValueFor("axe_spread") + local start_angle = -axe_spread + local interval_angle = axe_spread * 2 / (axe_count - 1) + local particle = "particles/units/heroes/hero_troll_warlord/troll_warlord_whirling_axe_ranged.vpcf" + local index = DoUniqueString("index") + self.projectileTarget[index] = {} + do + local i = 1 + while i <= axe_count do + local angle = start_angle + (i - 1) * interval_angle + local velocity = rotateVector2D(nil, direction, angle):__mul(axe_speed) + local data = { + Ability = self, + EffectName = particle, + vSpawnOrigin = caster_abs, + fDistance = axe_range, + fStartRadius = axe_width, + fEndRadius = axe_width, + Source = self:GetCaster(), + bHasFrontalCone = false, + bReplaceExisting = false, + iUnitTargetTeam = self:GetAbilityTargetTeam(), + iUnitTargetFlags = self:GetAbilityTargetFlags(), + iUnitTargetType = self:GetAbilityTargetType(), + fExpireTime = GameRules:GetGameTime() + 10, + bDeleteOnHit = false, + vVelocity = Vector(velocity.x, velocity.y, 0), + bProvidesVision = false, + ExtraData = { + index = index, + damage = axe_damage, + attack_damage_pct = attackDamagePct, + duration = duration, + axe_count = axe_count + } + } + ProjectileManager:CreateLinearProjectile(data) + i = i + 1 + end + end + caster:EmitSound("Hero_TrollWarlord.WhirlingAxes.Ranged") +end +function troll_warlord_active_axes_ranged.prototype.OnProjectileHit_ExtraData(self, target, location, extraData) + local projectileData = self.projectileTarget[extraData.index] + if not target then + projectileData.count = projectileData.count or 0 + projectileData.count = projectileData.count + 1 + if projectileData.count == extraData.axe_count then + __TS__Delete(self.projectileTarget, extraData.index) + end + return + end + local wasHitTarget = projectileData[target:entindex()] + if not not wasHitTarget then + return + end + projectileData[target:entindex()] = 1 + local attackDamage = self:GetCaster():GetAverageTrueAttackDamage(target) * (extraData.attack_damage_pct / 100) + local totalDamage = self:GetSpecialValueFor("axe_damage") + attackDamage + ApplyDamage({ + victim = target, + attacker = self:GetCaster(), + damage = totalDamage, + damage_type = self:GetAbilityDamageType(), + ability = self + }) + ____exports.modifier_troll_warlord_active_axes_ranged:apply( + target, + self:GetCaster(), + self, + {duration = self:GetSpecialValueFor("axe_slow_duration")} + ) + target:EmitSound("Hero_TrollWarlord.WhirlingAxes.Target") +end +troll_warlord_active_axes_ranged = __TS__Decorate( + troll_warlord_active_axes_ranged, + troll_warlord_active_axes_ranged, + {registerAbility(nil)}, + {kind = "class", name = "troll_warlord_active_axes_ranged"} +) +____exports.troll_warlord_active_axes_ranged = troll_warlord_active_axes_ranged +____exports.modifier_troll_warlord_active_axes_ranged = __TS__Class() +local modifier_troll_warlord_active_axes_ranged = ____exports.modifier_troll_warlord_active_axes_ranged +modifier_troll_warlord_active_axes_ranged.name = "modifier_troll_warlord_active_axes_ranged" +modifier_troll_warlord_active_axes_ranged.____file_path = "scripts/vscripts/abilities/heroes/troll_warlord/troll_warlord_active_axes_ranged.lua" +__TS__ClassExtends(modifier_troll_warlord_active_axes_ranged, BaseModifier) +function modifier_troll_warlord_active_axes_ranged.prototype.____constructor(self, ...) + BaseModifier.prototype.____constructor(self, ...) + self.slowMoveSpeed = 0 +end +function modifier_troll_warlord_active_axes_ranged.prototype.IsHidden(self) + return false +end +function modifier_troll_warlord_active_axes_ranged.prototype.IsPurgable(self) + return true +end +function modifier_troll_warlord_active_axes_ranged.prototype.OnCreated(self) + self.slowMoveSpeed = self:GetAbility():GetSpecialValueFor("movement_speed") +end +function modifier_troll_warlord_active_axes_ranged.prototype.DeclareFunctions(self) + return {MODIFIER_PROPERTY_MOVESPEED_BONUS_PERCENTAGE} +end +function modifier_troll_warlord_active_axes_ranged.prototype.GetModifierMoveSpeedBonus_Percentage(self) + return -self.slowMoveSpeed +end +modifier_troll_warlord_active_axes_ranged = __TS__Decorate( + modifier_troll_warlord_active_axes_ranged, + modifier_troll_warlord_active_axes_ranged, + {registerModifier(nil)}, + {kind = "class", name = "modifier_troll_warlord_active_axes_ranged"} +) +____exports.modifier_troll_warlord_active_axes_ranged = modifier_troll_warlord_active_axes_ranged +return ____exports diff --git a/scripts/vscripts/abilities/heroes/troll_warlord/troll_warlord_battle_trance_custom.lua b/scripts/vscripts/abilities/heroes/troll_warlord/troll_warlord_battle_trance_custom.lua new file mode 100644 index 0000000..c5c6c4a --- /dev/null +++ b/scripts/vscripts/abilities/heroes/troll_warlord/troll_warlord_battle_trance_custom.lua @@ -0,0 +1,120 @@ +local ____lualib = require("lualib_bundle") +local __TS__Class = ____lualib.__TS__Class +local __TS__ClassExtends = ____lualib.__TS__ClassExtends +local __TS__Decorate = ____lualib.__TS__Decorate +local ____exports = {} +local ____dota_ts_adapter = require("lib.dota_ts_adapter") +local BaseAbility = ____dota_ts_adapter.BaseAbility +local BaseModifier = ____dota_ts_adapter.BaseModifier +local registerAbility = ____dota_ts_adapter.registerAbility +local registerModifier = ____dota_ts_adapter.registerModifier +local ____vampirism = require("utils.vampirism") +local addPhysicalVampirism = ____vampirism.addPhysicalVampirism +local reducePhysicalVampirism = ____vampirism.reducePhysicalVampirism +____exports.troll_warlord_battle_trance_custom = __TS__Class() +local troll_warlord_battle_trance_custom = ____exports.troll_warlord_battle_trance_custom +troll_warlord_battle_trance_custom.name = "troll_warlord_battle_trance_custom" +troll_warlord_battle_trance_custom.____file_path = "scripts/vscripts/abilities/heroes/troll_warlord/troll_warlord_battle_trance_custom.lua" +__TS__ClassExtends(troll_warlord_battle_trance_custom, BaseAbility) +function troll_warlord_battle_trance_custom.prototype.Precache(self, context) + PrecacheResource("particle", "particles/units/heroes/hero_troll_warlord/troll_warlord_battle_trance_buff.vpcf", context) +end +function troll_warlord_battle_trance_custom.prototype.GetManaCost(self, _level) + return self:GetSpecialValueFor("AbilityManaCost") +end +function troll_warlord_battle_trance_custom.prototype.GetCooldown(self, _level) + return self:GetSpecialValueFor("AbilityCooldown") +end +function troll_warlord_battle_trance_custom.prototype.OnSpellStart(self) + if not IsServer() then + return + end + local caster = self:GetCaster() + if not caster or not caster:IsAlive() then + return + end + local duration = self:GetSpecialValueFor("trance_duration") + caster:EmitSound("Hero_TrollWarlord.BattleTrance.Cast") + caster:AddNewModifier(caster, self, ____exports.modifier_troll_warlord_battle_trance_custom_buff.name, {duration = duration}) +end +troll_warlord_battle_trance_custom = __TS__Decorate( + troll_warlord_battle_trance_custom, + troll_warlord_battle_trance_custom, + {registerAbility(nil)}, + {kind = "class", name = "troll_warlord_battle_trance_custom"} +) +____exports.troll_warlord_battle_trance_custom = troll_warlord_battle_trance_custom +____exports.modifier_troll_warlord_battle_trance_custom_buff = __TS__Class() +local modifier_troll_warlord_battle_trance_custom_buff = ____exports.modifier_troll_warlord_battle_trance_custom_buff +modifier_troll_warlord_battle_trance_custom_buff.name = "modifier_troll_warlord_battle_trance_custom_buff" +modifier_troll_warlord_battle_trance_custom_buff.____file_path = "scripts/vscripts/abilities/heroes/troll_warlord/troll_warlord_battle_trance_custom.lua" +__TS__ClassExtends(modifier_troll_warlord_battle_trance_custom_buff, BaseModifier) +function modifier_troll_warlord_battle_trance_custom_buff.prototype.____constructor(self, ...) + BaseModifier.prototype.____constructor(self, ...) + self.attackSpeed = 0 + self.moveSpeed = 0 + self.lifestealPct = 0 + self.vampApplied = false +end +function modifier_troll_warlord_battle_trance_custom_buff.prototype.IsHidden(self) + return false +end +function modifier_troll_warlord_battle_trance_custom_buff.prototype.IsPurgable(self) + return false +end +function modifier_troll_warlord_battle_trance_custom_buff.prototype.IsDebuff(self) + return false +end +function modifier_troll_warlord_battle_trance_custom_buff.prototype.GetEffectName(self) + return "particles/units/heroes/hero_troll_warlord/troll_warlord_battle_trance_buff.vpcf" +end +function modifier_troll_warlord_battle_trance_custom_buff.prototype.GetEffectAttachType(self) + return PATTACH_ABSORIGIN_FOLLOW +end +function modifier_troll_warlord_battle_trance_custom_buff.prototype.OnCreated(self) + local ability = self:GetAbility() + if not ability then + return + end + self.attackSpeed = ability:GetSpecialValueFor("attack_speed") + self.moveSpeed = ability:GetSpecialValueFor("movement_speed") + self.lifestealPct = ability:GetSpecialValueFor("lifesteal") + if not IsServer() then + return + end + local parent = self:GetParent() + if parent:IsRealHero() then + addPhysicalVampirism(nil, parent, self.lifestealPct) + self.vampApplied = true + end +end +function modifier_troll_warlord_battle_trance_custom_buff.prototype.OnRefresh(self) + self:OnCreated() +end +function modifier_troll_warlord_battle_trance_custom_buff.prototype.DeclareFunctions(self) + return {MODIFIER_PROPERTY_ATTACKSPEED_BONUS_CONSTANT, MODIFIER_PROPERTY_MOVESPEED_BONUS_CONSTANT} +end +function modifier_troll_warlord_battle_trance_custom_buff.prototype.GetModifierAttackSpeedBonus_Constant(self) + return self.attackSpeed +end +function modifier_troll_warlord_battle_trance_custom_buff.prototype.GetModifierMoveSpeedBonus_Constant(self) + return self.moveSpeed +end +function modifier_troll_warlord_battle_trance_custom_buff.prototype.OnDestroy(self) + if not IsServer() or not self.vampApplied then + return + end + local parent = self:GetParent() + if parent and parent:IsRealHero() then + reducePhysicalVampirism(nil, parent, self.lifestealPct) + end + self.vampApplied = false +end +modifier_troll_warlord_battle_trance_custom_buff = __TS__Decorate( + modifier_troll_warlord_battle_trance_custom_buff, + modifier_troll_warlord_battle_trance_custom_buff, + {registerModifier(nil)}, + {kind = "class", name = "modifier_troll_warlord_battle_trance_custom_buff"} +) +____exports.modifier_troll_warlord_battle_trance_custom_buff = modifier_troll_warlord_battle_trance_custom_buff +return ____exports diff --git a/scripts/vscripts/abilities/heroes/troll_warlord/troll_warlord_fervor_custom.lua b/scripts/vscripts/abilities/heroes/troll_warlord/troll_warlord_fervor_custom.lua new file mode 100644 index 0000000..38613d7 --- /dev/null +++ b/scripts/vscripts/abilities/heroes/troll_warlord/troll_warlord_fervor_custom.lua @@ -0,0 +1,143 @@ +local ____lualib = require("lualib_bundle") +local __TS__Class = ____lualib.__TS__Class +local __TS__ClassExtends = ____lualib.__TS__ClassExtends +local __TS__Decorate = ____lualib.__TS__Decorate +local ____exports = {} +local ____dota_ts_adapter = require("lib.dota_ts_adapter") +local BaseAbility = ____dota_ts_adapter.BaseAbility +local BaseModifier = ____dota_ts_adapter.BaseModifier +local registerAbility = ____dota_ts_adapter.registerAbility +local registerModifier = ____dota_ts_adapter.registerModifier +____exports.troll_warlord_fervor_custom = __TS__Class() +local troll_warlord_fervor_custom = ____exports.troll_warlord_fervor_custom +troll_warlord_fervor_custom.name = "troll_warlord_fervor_custom" +troll_warlord_fervor_custom.____file_path = "scripts/vscripts/abilities/heroes/troll_warlord/troll_warlord_fervor_custom.lua" +__TS__ClassExtends(troll_warlord_fervor_custom, BaseAbility) +function troll_warlord_fervor_custom.prototype.GetIntrinsicModifierName(self) + return ____exports.modifier_troll_warlord_fervor_custom.name +end +troll_warlord_fervor_custom = __TS__Decorate( + troll_warlord_fervor_custom, + troll_warlord_fervor_custom, + {registerAbility(nil)}, + {kind = "class", name = "troll_warlord_fervor_custom"} +) +____exports.troll_warlord_fervor_custom = troll_warlord_fervor_custom +____exports.modifier_troll_warlord_fervor_custom = __TS__Class() +local modifier_troll_warlord_fervor_custom = ____exports.modifier_troll_warlord_fervor_custom +modifier_troll_warlord_fervor_custom.name = "modifier_troll_warlord_fervor_custom" +modifier_troll_warlord_fervor_custom.____file_path = "scripts/vscripts/abilities/heroes/troll_warlord/troll_warlord_fervor_custom.lua" +__TS__ClassExtends(modifier_troll_warlord_fervor_custom, BaseModifier) +function modifier_troll_warlord_fervor_custom.prototype.____constructor(self, ...) + BaseModifier.prototype.____constructor(self, ...) + self.abiltiy = self:GetAbility() + self.bonusAttackSpeed = 0 + self.maxStack = 0 + self.lockedAttackSpeed = 0 + self.lastAttackGameTime = -1 + self.lingerDuration = 0 + self.barExtra = 0 +end +function modifier_troll_warlord_fervor_custom.prototype.IsHidden(self) + return self:GetStackCount() == 0 +end +function modifier_troll_warlord_fervor_custom.prototype.IsPurgable(self) + return false +end +function modifier_troll_warlord_fervor_custom.prototype.IsPurgeException(self) + return false +end +function modifier_troll_warlord_fervor_custom.prototype.IsPermanent(self) + return self:GetStackCount() <= 0 +end +function modifier_troll_warlord_fervor_custom.prototype.RemoveOnDeath(self) + return false +end +function modifier_troll_warlord_fervor_custom.prototype.OnCreated(self) + local ability = self:GetAbility() + self.bonusAttackSpeed = ability:GetSpecialValueFor("attack_speed") + self.maxStack = ability:GetSpecialValueFor("max_stacks") + self.lockedAttackSpeed = ability:GetSpecialValueFor("locked_attack_speed") / 100 - 1 + self.lingerDuration = ability:GetSpecialValueFor("stack_linger_duration") + self.barExtra = math.max( + 0.25, + ability:GetSpecialValueFor("stack_linger_bar_extra") + ) + if IsServer() then + self:StartIntervalThink(0.25) + self:SetDuration(-1, true) + end +end +function modifier_troll_warlord_fervor_custom.prototype.OnRefresh(self, params) + self:OnCreated() +end +function modifier_troll_warlord_fervor_custom.prototype.refreshDurationVisual(self) + if not IsServer() then + return + end + if self:GetStackCount() <= 0 then + self:SetDuration(-1, true) + return + end + self:SetDuration(self.lingerDuration + self.barExtra, true) +end +function modifier_troll_warlord_fervor_custom.prototype.OnIntervalThink(self) + if not IsServer() then + return + end + if self.lastAttackGameTime < 0 then + return + end + if self:GetStackCount() <= 0 then + return + end + if GameRules:GetGameTime() - self.lastAttackGameTime > self.lingerDuration then + self:SetStackCount(0) + self.lastAttackGameTime = -1 + self:SetDuration(-1, true) + end +end +function modifier_troll_warlord_fervor_custom.prototype.DeclareFunctions(self) + return {MODIFIER_EVENT_ON_ATTACK_LANDED, MODIFIER_PROPERTY_ATTACKSPEED_BONUS_CONSTANT, MODIFIER_PROPERTY_DAMAGEOUTGOING_PERCENTAGE} +end +function modifier_troll_warlord_fervor_custom.prototype.GetModifierAttackSpeedBonus_Constant(self) + if self:GetParent():PassivesDisabled() then + return 0 + end + return self.bonusAttackSpeed * self:GetStackCount() +end +function modifier_troll_warlord_fervor_custom.prototype.OnAttackLanded(self, event) + local parent = self:GetParent() + if event.attacker ~= parent then + return + end + if event.no_attack_cooldown then + return + end + if parent:PassivesDisabled() then + return + end + self:SetStackCount(math.min( + self:GetStackCount() + 1, + self.maxStack + )) + self.lastAttackGameTime = GameRules:GetGameTime() + self:refreshDurationVisual() +end +function modifier_troll_warlord_fervor_custom.prototype.GetModifierDamageOutgoing_Percentage(self) + local parent = self:GetParent() + if parent:PassivesDisabled() then + return 0 + end + local attack_speed = parent:GetIncreasedAttackSpeed(false) - self.lockedAttackSpeed + local bonus_damage = attack_speed * self.abiltiy:GetSpecialValueFor("pct_damage_per_attack_speed") * 100 + return math.max(bonus_damage, 0) +end +modifier_troll_warlord_fervor_custom = __TS__Decorate( + modifier_troll_warlord_fervor_custom, + modifier_troll_warlord_fervor_custom, + {registerModifier(nil)}, + {kind = "class", name = "modifier_troll_warlord_fervor_custom"} +) +____exports.modifier_troll_warlord_fervor_custom = modifier_troll_warlord_fervor_custom +return ____exports diff --git a/scripts/vscripts/abilities/heroes/troll_warlord/troll_warlord_switch_stance_custom.lua b/scripts/vscripts/abilities/heroes/troll_warlord/troll_warlord_switch_stance_custom.lua new file mode 100644 index 0000000..cd14a6f --- /dev/null +++ b/scripts/vscripts/abilities/heroes/troll_warlord/troll_warlord_switch_stance_custom.lua @@ -0,0 +1,334 @@ +local ____lualib = require("lualib_bundle") +local __TS__Class = ____lualib.__TS__Class +local __TS__ClassExtends = ____lualib.__TS__ClassExtends +local __TS__Decorate = ____lualib.__TS__Decorate +local ____exports = {} +local ____dota_ts_adapter = require("lib.dota_ts_adapter") +local BaseAbility = ____dota_ts_adapter.BaseAbility +local BaseModifier = ____dota_ts_adapter.BaseModifier +local registerAbility = ____dota_ts_adapter.registerAbility +local registerModifier = ____dota_ts_adapter.registerModifier +local ____luck = require("utils.luck") +local rollLuckChance = ____luck.rollLuckChance +____exports.troll_warlord_switch_stance_custom = __TS__Class() +local troll_warlord_switch_stance_custom = ____exports.troll_warlord_switch_stance_custom +troll_warlord_switch_stance_custom.name = "troll_warlord_switch_stance_custom" +troll_warlord_switch_stance_custom.____file_path = "scripts/vscripts/abilities/heroes/troll_warlord/troll_warlord_switch_stance_custom.lua" +__TS__ClassExtends(troll_warlord_switch_stance_custom, BaseAbility) +function troll_warlord_switch_stance_custom.prototype.Precache(self, context) + PrecacheResource("particle", "particles/units/heroes/hero_siren/siren_net_projectile.vpcf", context) + PrecacheResource("particle", "particles/neutral_fx/dark_troll_ensnare.vpcf", context) + PrecacheResource("particle", "particles/units/heroes/hero_troll_warlord/troll_warlord_berserk_buff.vpcf", context) +end +function troll_warlord_switch_stance_custom.prototype.GetIntrinsicModifierName(self) + return ____exports.modifier_troll_warlord_switch_stance_custom_passive.name +end +function troll_warlord_switch_stance_custom.prototype.ResetToggleOnRespawn(self) + return false +end +function troll_warlord_switch_stance_custom.prototype.GetAbilityTextureName(self) + local caster = self:GetCaster() + return caster:HasModifier(____exports.modifier_troll_warlord_switch_stance_custom.name) and "troll_warlord_berserkers_rage_active" or "troll_warlord_switch_stance" +end +function troll_warlord_switch_stance_custom.prototype.ApplyMeleeState(self, isMelee) + local caster = self:GetCaster() + caster:StartGesture(ACT_DOTA_CAST_ABILITY_1) + if isMelee then + caster:SetAttackCapability(DOTA_UNIT_CAP_MELEE_ATTACK) + self.modifier = caster:AddNewModifier(caster, self, ____exports.modifier_troll_warlord_switch_stance_custom.name, {}) + else + caster:SetAttackCapability(DOTA_UNIT_CAP_RANGED_ATTACK) + if self.modifier then + self.modifier:Destroy() + self.modifier = nil + else + caster:RemoveModifierByName(____exports.modifier_troll_warlord_switch_stance_custom.name) + end + end + caster:EmitSound("Hero_TrollWarlord.BerserkersRage.Toggle") +end +function troll_warlord_switch_stance_custom.prototype.ForceSetMeleeState(self, isMelee) + local caster = self:GetCaster() + local hasMeleeMod = caster:HasModifier(____exports.modifier_troll_warlord_switch_stance_custom.name) + if isMelee ~= hasMeleeMod then + self:ToggleAbility() + return + end + self:ApplyMeleeState(isMelee) +end +function troll_warlord_switch_stance_custom.prototype.OnToggle(self) + self:ApplyMeleeState(self:GetToggleState()) +end +function troll_warlord_switch_stance_custom.prototype.OnUpgrade(self) + if self.modifier then + self.modifier:ForceRefresh() + end +end +function troll_warlord_switch_stance_custom.prototype.OnProjectileHit(self, target, location) + if not target then + return + end + local caster = self:GetCaster() + ____exports.modifier_troll_warlord_switch_stance_custom_debuff:apply( + target, + caster, + self, + {duration = self:GetSpecialValueFor("duration_ensnare")} + ) + target:EmitSound("n_creep_TrollWarlord.Ensnare") +end +troll_warlord_switch_stance_custom = __TS__Decorate( + troll_warlord_switch_stance_custom, + troll_warlord_switch_stance_custom, + {registerAbility(nil)}, + {kind = "class", name = "troll_warlord_switch_stance_custom"} +) +____exports.troll_warlord_switch_stance_custom = troll_warlord_switch_stance_custom +____exports.modifier_troll_warlord_switch_stance_custom_passive = __TS__Class() +local modifier_troll_warlord_switch_stance_custom_passive = ____exports.modifier_troll_warlord_switch_stance_custom_passive +modifier_troll_warlord_switch_stance_custom_passive.name = "modifier_troll_warlord_switch_stance_custom_passive" +modifier_troll_warlord_switch_stance_custom_passive.____file_path = "scripts/vscripts/abilities/heroes/troll_warlord/troll_warlord_switch_stance_custom.lua" +__TS__ClassExtends(modifier_troll_warlord_switch_stance_custom_passive, BaseModifier) +function modifier_troll_warlord_switch_stance_custom_passive.prototype.____constructor(self, ...) + BaseModifier.prototype.____constructor(self, ...) + self.ability = self:GetAbility() + self.abilityCooldownEnsnare = 0 + self.speedEnsnare = 0 + self.splitShot = false + self.cooldownEnsnare = false +end +function modifier_troll_warlord_switch_stance_custom_passive.prototype.IsHidden(self) + return true +end +function modifier_troll_warlord_switch_stance_custom_passive.prototype.IsPurgable(self) + return false +end +function modifier_troll_warlord_switch_stance_custom_passive.prototype.IsPurgeException(self) + return false +end +function modifier_troll_warlord_switch_stance_custom_passive.prototype.DeclareFunctions(self) + return {MODIFIER_EVENT_ON_ATTACK, MODIFIER_EVENT_ON_ATTACK_LANDED} +end +function modifier_troll_warlord_switch_stance_custom_passive.prototype.OnCreated(self) + self.speedEnsnare = self.ability:GetSpecialValueFor("ensnare_speed") + self.abilityCooldownEnsnare = self.ability:GetSpecialValueFor("cooldown_ensnare") +end +function modifier_troll_warlord_switch_stance_custom_passive.prototype.OnRefresh(self, params) + self:OnCreated() +end +function modifier_troll_warlord_switch_stance_custom_passive.prototype.OnAttack(self, event) + local parent = self:GetParent() + if event.attacker ~= parent then + return + end + if parent:PassivesDisabled() then + return + end + if not parent:HasModifier("modifier_item_aghanims_shard") then + return + end + if parent:GetAttackCapability() ~= DOTA_UNIT_CAP_RANGED_ATTACK then + return + end + if self.splitShot then + return + end + local target = event.target + local radius = parent:Script_GetAttackRange() + self.ability:GetSpecialValueFor("split_radius") + local enemies = FindUnitsInRadius( + parent:GetTeamNumber(), + parent:GetOrigin(), + nil, + radius, + DOTA_UNIT_TARGET_TEAM_ENEMY, + DOTA_UNIT_TARGET_HERO + DOTA_UNIT_TARGET_BASIC + DOTA_UNIT_TARGET_COURIER, + DOTA_UNIT_TARGET_FLAG_FOW_VISIBLE + DOTA_UNIT_TARGET_FLAG_NO_INVIS + DOTA_UNIT_TARGET_FLAG_MAGIC_IMMUNE_ENEMIES, + FIND_ANY_ORDER, + false + ) + local count = 0 + local maxCount = self.ability:GetSpecialValueFor("max_split_attack") + for ____, enemy in ipairs(enemies) do + if enemy ~= target then + self.splitShot = true + parent:PerformAttack( + enemy, + false, + true, + true, + false, + true, + false, + false + ) + self.splitShot = false + count = count + 1 + if count >= maxCount then + break + end + end + end +end +function modifier_troll_warlord_switch_stance_custom_passive.prototype.OnAttackLanded(self, event) + local parent = self:GetParent() + if event.attacker ~= parent or self.cooldownEnsnare then + return + end + if parent:PassivesDisabled() then + return + end + if parent:GetAttackCapability() ~= DOTA_UNIT_CAP_RANGED_ATTACK then + return + end + local target = event.target + local chancePct = self.ability:GetSpecialValueFor("chance_ensnare") + local hero = parent + local ____temp_0 + if target:IsBossCreature() or target:IsMagicImmune() then + ____temp_0 = false + else + ____temp_0 = rollLuckChance(nil, hero, chancePct / 100) + end + local rolled = ____temp_0 + if rolled then + ProjectileManager:CreateTrackingProjectile({ + EffectName = "particles/units/heroes/hero_siren/siren_net_projectile.vpcf", + Ability = self.ability, + Source = parent, + Target = target, + iMoveSpeed = self.speedEnsnare + }) + self.cooldownEnsnare = true + Timers:CreateTimer( + self.abilityCooldownEnsnare, + function() + self.cooldownEnsnare = false + end + ) + end +end +modifier_troll_warlord_switch_stance_custom_passive = __TS__Decorate( + modifier_troll_warlord_switch_stance_custom_passive, + modifier_troll_warlord_switch_stance_custom_passive, + {registerModifier(nil)}, + {kind = "class", name = "modifier_troll_warlord_switch_stance_custom_passive"} +) +____exports.modifier_troll_warlord_switch_stance_custom_passive = modifier_troll_warlord_switch_stance_custom_passive +____exports.modifier_troll_warlord_switch_stance_custom_debuff = __TS__Class() +local modifier_troll_warlord_switch_stance_custom_debuff = ____exports.modifier_troll_warlord_switch_stance_custom_debuff +modifier_troll_warlord_switch_stance_custom_debuff.name = "modifier_troll_warlord_switch_stance_custom_debuff" +modifier_troll_warlord_switch_stance_custom_debuff.____file_path = "scripts/vscripts/abilities/heroes/troll_warlord/troll_warlord_switch_stance_custom.lua" +__TS__ClassExtends(modifier_troll_warlord_switch_stance_custom_debuff, BaseModifier) +function modifier_troll_warlord_switch_stance_custom_debuff.prototype.IsHidden(self) + return false +end +function modifier_troll_warlord_switch_stance_custom_debuff.prototype.IsPurgable(self) + return true +end +function modifier_troll_warlord_switch_stance_custom_debuff.prototype.GetEffectName(self) + return "particles/neutral_fx/dark_troll_ensnare.vpcf" +end +function modifier_troll_warlord_switch_stance_custom_debuff.prototype.CheckState(self) + return {[MODIFIER_STATE_ROOTED] = true} +end +modifier_troll_warlord_switch_stance_custom_debuff = __TS__Decorate( + modifier_troll_warlord_switch_stance_custom_debuff, + modifier_troll_warlord_switch_stance_custom_debuff, + {registerModifier(nil)}, + {kind = "class", name = "modifier_troll_warlord_switch_stance_custom_debuff"} +) +____exports.modifier_troll_warlord_switch_stance_custom_debuff = modifier_troll_warlord_switch_stance_custom_debuff +____exports.modifier_troll_warlord_switch_stance_custom = __TS__Class() +local modifier_troll_warlord_switch_stance_custom = ____exports.modifier_troll_warlord_switch_stance_custom +modifier_troll_warlord_switch_stance_custom.name = "modifier_troll_warlord_switch_stance_custom" +modifier_troll_warlord_switch_stance_custom.____file_path = "scripts/vscripts/abilities/heroes/troll_warlord/troll_warlord_switch_stance_custom.lua" +__TS__ClassExtends(modifier_troll_warlord_switch_stance_custom, BaseModifier) +function modifier_troll_warlord_switch_stance_custom.prototype.____constructor(self, ...) + BaseModifier.prototype.____constructor(self, ...) + self.baseAttackTime = 0 + self.bonusMoveSpeed = 0 + self.bonusArmor = 0 + self.meleRange = -350 +end +function modifier_troll_warlord_switch_stance_custom.prototype.IsHidden(self) + return true +end +function modifier_troll_warlord_switch_stance_custom.prototype.IsPurgable(self) + return false +end +function modifier_troll_warlord_switch_stance_custom.prototype.IsPurgeException(self) + return false +end +function modifier_troll_warlord_switch_stance_custom.prototype.RemoveOnDeath(self) + return false +end +function modifier_troll_warlord_switch_stance_custom.prototype.OnCreated(self) + local ability = self:GetAbility() + local parent = self:GetParent() + self.baseAttackTime = ability:GetSpecialValueFor("base_attack_time") + self.bonusMoveSpeed = ability:GetSpecialValueFor("bonus_move_speed") + self.bonusArmor = ability:GetSpecialValueFor("bonus_armor") + if IsClient() then + return + end + parent:SetAttackCapability(DOTA_UNIT_CAP_MELEE_ATTACK) + parent:FadeGesture(ACT_DOTA_RUN) +end +function modifier_troll_warlord_switch_stance_custom.prototype.OnRefresh(self, params) + local ability = self:GetAbility() + self.baseAttackTime = ability:GetSpecialValueFor("base_attack_time") + self.bonusMoveSpeed = ability:GetSpecialValueFor("bonus_move_speed") + self.bonusArmor = ability:GetSpecialValueFor("bonus_armor") +end +function modifier_troll_warlord_switch_stance_custom.prototype.OnDestroy(self) + if IsClient() then + return + end + self:GetParent():SetAttackCapability(DOTA_UNIT_CAP_RANGED_ATTACK) + self:GetParent():FadeGesture(ACT_DOTA_RUN) +end +function modifier_troll_warlord_switch_stance_custom.prototype.DeclareFunctions(self) + return { + MODIFIER_PROPERTY_BASE_ATTACK_TIME_CONSTANT, + MODIFIER_PROPERTY_ATTACK_RANGE_BONUS, + MODIFIER_PROPERTY_MOVESPEED_BONUS_CONSTANT, + MODIFIER_PROPERTY_PHYSICAL_ARMOR_BONUS, + MODIFIER_PROPERTY_TRANSLATE_ACTIVITY_MODIFIERS, + MODIFIER_PROPERTY_TRANSLATE_ATTACK_SOUND + } +end +function modifier_troll_warlord_switch_stance_custom.prototype.GetModifierAttackRangeBonus(self) + return self.meleRange +end +function modifier_troll_warlord_switch_stance_custom.prototype.GetModifierBaseAttackTimeConstant(self) + return self.baseAttackTime +end +function modifier_troll_warlord_switch_stance_custom.prototype.GetModifierMoveSpeedBonus_Constant(self) + return self.bonusMoveSpeed +end +function modifier_troll_warlord_switch_stance_custom.prototype.GetModifierPhysicalArmorBonus(self) + return self.bonusArmor +end +function modifier_troll_warlord_switch_stance_custom.prototype.GetAttackSound(self) + return "Hero_TrollWarlord.ProjectileImpact" +end +function modifier_troll_warlord_switch_stance_custom.prototype.GetActivityTranslationModifiers(self) + return "melee" +end +function modifier_troll_warlord_switch_stance_custom.prototype.GetPriority(self) + return 1 +end +function modifier_troll_warlord_switch_stance_custom.prototype.GetEffectName(self) + return "particles/units/heroes/hero_troll_warlord/troll_warlord_berserk_buff.vpcf" +end +function modifier_troll_warlord_switch_stance_custom.prototype.GetEffectAttachType(self) + return PATTACH_OVERHEAD_FOLLOW +end +modifier_troll_warlord_switch_stance_custom = __TS__Decorate( + modifier_troll_warlord_switch_stance_custom, + modifier_troll_warlord_switch_stance_custom, + {registerModifier(nil)}, + {kind = "class", name = "modifier_troll_warlord_switch_stance_custom"} +) +____exports.modifier_troll_warlord_switch_stance_custom = modifier_troll_warlord_switch_stance_custom +return ____exports diff --git a/scripts/vscripts/abilities/heroes/vengefulspirit/ability_vengefulspirit_command_aura_custom.lua b/scripts/vscripts/abilities/heroes/vengefulspirit/ability_vengefulspirit_command_aura_custom.lua new file mode 100644 index 0000000..bc6d3f2 --- /dev/null +++ b/scripts/vscripts/abilities/heroes/vengefulspirit/ability_vengefulspirit_command_aura_custom.lua @@ -0,0 +1,189 @@ +local ____lualib = require("lualib_bundle") +local __TS__Class = ____lualib.__TS__Class +local __TS__ClassExtends = ____lualib.__TS__ClassExtends +local __TS__Decorate = ____lualib.__TS__Decorate +local ____exports = {} +local ____dota_ts_adapter = require("lib.dota_ts_adapter") +local BaseAbility = ____dota_ts_adapter.BaseAbility +local BaseModifier = ____dota_ts_adapter.BaseModifier +local registerAbility = ____dota_ts_adapter.registerAbility +local registerModifier = ____dota_ts_adapter.registerModifier +local ____vampirism = require("utils.vampirism") +local addMagicalVampirism = ____vampirism.addMagicalVampirism +local addPhysicalVampirism = ____vampirism.addPhysicalVampirism +local precacheVampirismParticle = ____vampirism.precacheVampirismParticle +local reduceMagicalVampirism = ____vampirism.reduceMagicalVampirism +local reducePhysicalVampirism = ____vampirism.reducePhysicalVampirism +____exports.ability_vengefulspirit_command_aura_custom = __TS__Class() +local ability_vengefulspirit_command_aura_custom = ____exports.ability_vengefulspirit_command_aura_custom +ability_vengefulspirit_command_aura_custom.name = "ability_vengefulspirit_command_aura_custom" +ability_vengefulspirit_command_aura_custom.____file_path = "scripts/vscripts/abilities/heroes/vengefulspirit/ability_vengefulspirit_command_aura_custom.lua" +__TS__ClassExtends(ability_vengefulspirit_command_aura_custom, BaseAbility) +function ability_vengefulspirit_command_aura_custom.prototype.Precache(self, context) + precacheVampirismParticle(nil, context) +end +function ability_vengefulspirit_command_aura_custom.prototype.GetIntrinsicModifierName(self) + return ____exports.modifier_vengefulspirit_command_aura_custom.name +end +function ability_vengefulspirit_command_aura_custom.prototype.GetAOERadius(self) + return self:GetSpecialValueFor("aura_radius") +end +ability_vengefulspirit_command_aura_custom = __TS__Decorate( + ability_vengefulspirit_command_aura_custom, + ability_vengefulspirit_command_aura_custom, + {registerAbility(nil)}, + {kind = "class", name = "ability_vengefulspirit_command_aura_custom"} +) +____exports.ability_vengefulspirit_command_aura_custom = ability_vengefulspirit_command_aura_custom +____exports.modifier_vengefulspirit_command_aura_custom = __TS__Class() +local modifier_vengefulspirit_command_aura_custom = ____exports.modifier_vengefulspirit_command_aura_custom +modifier_vengefulspirit_command_aura_custom.name = "modifier_vengefulspirit_command_aura_custom" +modifier_vengefulspirit_command_aura_custom.____file_path = "scripts/vscripts/abilities/heroes/vengefulspirit/ability_vengefulspirit_command_aura_custom.lua" +__TS__ClassExtends(modifier_vengefulspirit_command_aura_custom, BaseModifier) +function modifier_vengefulspirit_command_aura_custom.prototype.IsHidden(self) + return true +end +function modifier_vengefulspirit_command_aura_custom.prototype.IsPurgable(self) + return false +end +function modifier_vengefulspirit_command_aura_custom.prototype.IsAura(self) + return true +end +function modifier_vengefulspirit_command_aura_custom.prototype.GetModifierAura(self) + return ____exports.modifier_vengefulspirit_command_aura_buff.name +end +function modifier_vengefulspirit_command_aura_custom.prototype.GetAuraRadius(self) + return self:GetAbility():GetSpecialValueFor("aura_radius") +end +function modifier_vengefulspirit_command_aura_custom.prototype.GetAuraDuration(self) + return 0.5 +end +function modifier_vengefulspirit_command_aura_custom.prototype.GetAuraSearchTeam(self) + return DOTA_UNIT_TARGET_TEAM_FRIENDLY +end +function modifier_vengefulspirit_command_aura_custom.prototype.GetAuraSearchType(self) + return bit.bor(DOTA_UNIT_TARGET_HERO, DOTA_UNIT_TARGET_BASIC) +end +function modifier_vengefulspirit_command_aura_custom.prototype.GetAuraSearchFlags(self) + return DOTA_UNIT_TARGET_FLAG_NONE +end +function modifier_vengefulspirit_command_aura_custom.prototype.GetAuraEntityReject(self, _target) + return self:GetParent():PassivesDisabled() +end +modifier_vengefulspirit_command_aura_custom = __TS__Decorate( + modifier_vengefulspirit_command_aura_custom, + modifier_vengefulspirit_command_aura_custom, + {registerModifier(nil)}, + {kind = "class", name = "modifier_vengefulspirit_command_aura_custom"} +) +____exports.modifier_vengefulspirit_command_aura_custom = modifier_vengefulspirit_command_aura_custom +____exports.modifier_vengefulspirit_command_aura_buff = __TS__Class() +local modifier_vengefulspirit_command_aura_buff = ____exports.modifier_vengefulspirit_command_aura_buff +modifier_vengefulspirit_command_aura_buff.name = "modifier_vengefulspirit_command_aura_buff" +modifier_vengefulspirit_command_aura_buff.____file_path = "scripts/vscripts/abilities/heroes/vengefulspirit/ability_vengefulspirit_command_aura_custom.lua" +__TS__ClassExtends(modifier_vengefulspirit_command_aura_buff, BaseModifier) +function modifier_vengefulspirit_command_aura_buff.prototype.____constructor(self, ...) + BaseModifier.prototype.____constructor(self, ...) + self.appliedPhysicalVampirism = 0 + self.appliedMagicalVampirism = 0 +end +function modifier_vengefulspirit_command_aura_buff.prototype.IsHidden(self) + return false +end +function modifier_vengefulspirit_command_aura_buff.prototype.IsPurgable(self) + return false +end +function modifier_vengefulspirit_command_aura_buff.prototype.IsDebuff(self) + return false +end +function modifier_vengefulspirit_command_aura_buff.prototype.OnCreated(self) + if not IsServer() then + return + end + self:refreshBonuses() +end +function modifier_vengefulspirit_command_aura_buff.prototype.OnRefresh(self) + if not IsServer() then + return + end + self:refreshBonuses() +end +function modifier_vengefulspirit_command_aura_buff.prototype.OnDestroy(self) + if not IsServer() then + return + end + self:clearVampirism() +end +function modifier_vengefulspirit_command_aura_buff.prototype.DeclareFunctions(self) + return {MODIFIER_PROPERTY_PREATTACK_BONUS_DAMAGE} +end +function modifier_vengefulspirit_command_aura_buff.prototype.GetModifierPreAttack_BonusDamage(self) + local caster = self:GetCaster() + if caster and caster:PassivesDisabled() then + return 0 + end + local ability = self:GetAbility() + if not ability then + return 0 + end + return ability:GetSpecialValueFor("bonus_base_damage") +end +function modifier_vengefulspirit_command_aura_buff.prototype.refreshVampirismForHero(self, hero, desiredPhysical, desiredMagical) + if desiredPhysical > self.appliedPhysicalVampirism then + addPhysicalVampirism(nil, hero, desiredPhysical - self.appliedPhysicalVampirism) + self.appliedPhysicalVampirism = desiredPhysical + elseif desiredPhysical < self.appliedPhysicalVampirism then + reducePhysicalVampirism(nil, hero, self.appliedPhysicalVampirism - desiredPhysical) + self.appliedPhysicalVampirism = desiredPhysical + end + if desiredMagical > self.appliedMagicalVampirism then + addMagicalVampirism(nil, hero, desiredMagical - self.appliedMagicalVampirism) + self.appliedMagicalVampirism = desiredMagical + elseif desiredMagical < self.appliedMagicalVampirism then + reduceMagicalVampirism(nil, hero, self.appliedMagicalVampirism - desiredMagical) + self.appliedMagicalVampirism = desiredMagical + end +end +function modifier_vengefulspirit_command_aura_buff.prototype.refreshBonuses(self) + local parent = self:GetParent() + if not parent or parent:IsNull() or not parent:IsRealHero() then + return + end + local ability = self:GetAbility() + if not ability then + return + end + local caster = self:GetCaster() + if caster and caster:PassivesDisabled() then + self:clearVampirism() + return + end + self:refreshVampirismForHero( + parent, + ability:GetSpecialValueFor("physical_vampirism"), + ability:GetSpecialValueFor("magical_vampirism") + ) +end +function modifier_vengefulspirit_command_aura_buff.prototype.clearVampirism(self) + local parent = self:GetParent() + if not parent or parent:IsNull() or not parent:IsRealHero() then + return + end + local hero = parent + if self.appliedPhysicalVampirism > 0 then + reducePhysicalVampirism(nil, hero, self.appliedPhysicalVampirism) + self.appliedPhysicalVampirism = 0 + end + if self.appliedMagicalVampirism > 0 then + reduceMagicalVampirism(nil, hero, self.appliedMagicalVampirism) + self.appliedMagicalVampirism = 0 + end +end +modifier_vengefulspirit_command_aura_buff = __TS__Decorate( + modifier_vengefulspirit_command_aura_buff, + modifier_vengefulspirit_command_aura_buff, + {registerModifier(nil)}, + {kind = "class", name = "modifier_vengefulspirit_command_aura_buff"} +) +____exports.modifier_vengefulspirit_command_aura_buff = modifier_vengefulspirit_command_aura_buff +return ____exports diff --git a/scripts/vscripts/abilities/heroes/vengefulspirit/ability_vengefulspirit_spirit_debt_innate.lua b/scripts/vscripts/abilities/heroes/vengefulspirit/ability_vengefulspirit_spirit_debt_innate.lua new file mode 100644 index 0000000..db9aefc --- /dev/null +++ b/scripts/vscripts/abilities/heroes/vengefulspirit/ability_vengefulspirit_spirit_debt_innate.lua @@ -0,0 +1,189 @@ +local ____lualib = require("lualib_bundle") +local __TS__Class = ____lualib.__TS__Class +local __TS__ClassExtends = ____lualib.__TS__ClassExtends +local __TS__Decorate = ____lualib.__TS__Decorate +local ____exports = {} +local ____dota_ts_adapter = require("lib.dota_ts_adapter") +local BaseAbility = ____dota_ts_adapter.BaseAbility +local BaseModifier = ____dota_ts_adapter.BaseModifier +local registerAbility = ____dota_ts_adapter.registerAbility +local registerModifier = ____dota_ts_adapter.registerModifier +local ____heal_tracker = require("utils.heal_tracker") +local HealWithBattlePass = ____heal_tracker.HealWithBattlePass +--- Метка «долг духа» на враге — при смерти лечит Шендел и союзников в радиуре ауры. +function ____exports.applyVengefulSpiritDebtMark(self, target, caster, ability) + if not IsServer() then + return + end + if not target or target:IsNull() or not target:IsAlive() then + return + end + if target:GetTeamNumber() == caster:GetTeamNumber() then + return + end + local innate = caster:FindAbilityByName(____exports.ability_vengefulspirit_spirit_debt_innate.name) + if not innate or innate:GetLevel() <= 0 then + return + end + local duration = innate:GetSpecialValueFor("mark_duration") + target:AddNewModifier(caster, innate, ____exports.modifier_vengefulspirit_spirit_debt_mark.name, {duration = duration}) +end +____exports.ability_vengefulspirit_spirit_debt_innate = __TS__Class() +local ability_vengefulspirit_spirit_debt_innate = ____exports.ability_vengefulspirit_spirit_debt_innate +ability_vengefulspirit_spirit_debt_innate.name = "ability_vengefulspirit_spirit_debt_innate" +ability_vengefulspirit_spirit_debt_innate.____file_path = "scripts/vscripts/abilities/heroes/vengefulspirit/ability_vengefulspirit_spirit_debt_innate.lua" +__TS__ClassExtends(ability_vengefulspirit_spirit_debt_innate, BaseAbility) +function ability_vengefulspirit_spirit_debt_innate.prototype.GetIntrinsicModifierName(self) + return ____exports.modifier_vengefulspirit_spirit_debt_innate.name +end +ability_vengefulspirit_spirit_debt_innate = __TS__Decorate( + ability_vengefulspirit_spirit_debt_innate, + ability_vengefulspirit_spirit_debt_innate, + {registerAbility(nil)}, + {kind = "class", name = "ability_vengefulspirit_spirit_debt_innate"} +) +____exports.ability_vengefulspirit_spirit_debt_innate = ability_vengefulspirit_spirit_debt_innate +____exports.modifier_vengefulspirit_spirit_debt_innate = __TS__Class() +local modifier_vengefulspirit_spirit_debt_innate = ____exports.modifier_vengefulspirit_spirit_debt_innate +modifier_vengefulspirit_spirit_debt_innate.name = "modifier_vengefulspirit_spirit_debt_innate" +modifier_vengefulspirit_spirit_debt_innate.____file_path = "scripts/vscripts/abilities/heroes/vengefulspirit/ability_vengefulspirit_spirit_debt_innate.lua" +__TS__ClassExtends(modifier_vengefulspirit_spirit_debt_innate, BaseModifier) +function modifier_vengefulspirit_spirit_debt_innate.prototype.IsHidden(self) + return true +end +function modifier_vengefulspirit_spirit_debt_innate.prototype.IsPurgable(self) + return false +end +function modifier_vengefulspirit_spirit_debt_innate.prototype.DeclareFunctions(self) + return {MODIFIER_EVENT_ON_DEATH} +end +function modifier_vengefulspirit_spirit_debt_innate.prototype.OnCreated(self) + if not IsServer() then + return + end + self.parentHero = self:GetParent() +end +function modifier_vengefulspirit_spirit_debt_innate.prototype.OnDeath(self, event) + if not IsServer() then + return + end + if self.parentHero:PassivesDisabled() then + return + end + local dead = event.unit + if not dead or dead:IsNull() then + return + end + if dead:GetTeamNumber() == self.parentHero:GetTeamNumber() then + return + end + local mark = dead:FindModifierByName(____exports.modifier_vengefulspirit_spirit_debt_mark.name) + if not mark then + return + end + if mark:GetCaster() ~= self.parentHero then + return + end + local innate = self:GetAbility() + if not innate then + return + end + local healPct = innate:GetSpecialValueFor("heal_on_kill_pct") / 100 + local manaRestore = innate:GetSpecialValueFor("mana_restore") + local allySharePct = innate:GetSpecialValueFor("ally_heal_share_pct") / 100 + local auraRadius = self:getCommandAuraRadius(innate) + local baseHeal = dead:GetMaxHealth() * healPct + if baseHeal > 0 then + HealWithBattlePass( + nil, + self.parentHero, + baseHeal, + innate, + self.parentHero, + false + ) + self.parentHero:GiveMana(manaRestore) + SendOverheadEventMessage( + nil, + OVERHEAD_ALERT_HEAL, + self.parentHero, + baseHeal, + nil + ) + end + if allySharePct <= 0 or auraRadius <= 0 then + return + end + local allies = FindUnitsInRadius( + self.parentHero:GetTeamNumber(), + self.parentHero:GetAbsOrigin(), + nil, + auraRadius, + DOTA_UNIT_TARGET_TEAM_FRIENDLY, + DOTA_UNIT_TARGET_HERO, + DOTA_UNIT_TARGET_FLAG_NONE, + FIND_ANY_ORDER, + false + ) + local allyHeal = baseHeal * allySharePct + if allyHeal <= 0 then + return + end + for ____, ally in ipairs(allies) do + do + if not ally or ally:IsNull() or not ally:IsAlive() or ally == self.parentHero then + goto __continue24 + end + HealWithBattlePass( + nil, + ally, + allyHeal, + innate, + self.parentHero + ) + end + ::__continue24:: + end +end +function modifier_vengefulspirit_spirit_debt_innate.prototype.getCommandAuraRadius(self, innate) + local aura = self.parentHero:FindAbilityByName("ability_vengefulspirit_command_aura_custom") + if aura then + return aura:GetSpecialValueFor("aura_radius") + end + return innate:GetSpecialValueFor("fallback_aura_radius") +end +modifier_vengefulspirit_spirit_debt_innate = __TS__Decorate( + modifier_vengefulspirit_spirit_debt_innate, + modifier_vengefulspirit_spirit_debt_innate, + {registerModifier(nil)}, + {kind = "class", name = "modifier_vengefulspirit_spirit_debt_innate"} +) +____exports.modifier_vengefulspirit_spirit_debt_innate = modifier_vengefulspirit_spirit_debt_innate +____exports.modifier_vengefulspirit_spirit_debt_mark = __TS__Class() +local modifier_vengefulspirit_spirit_debt_mark = ____exports.modifier_vengefulspirit_spirit_debt_mark +modifier_vengefulspirit_spirit_debt_mark.name = "modifier_vengefulspirit_spirit_debt_mark" +modifier_vengefulspirit_spirit_debt_mark.____file_path = "scripts/vscripts/abilities/heroes/vengefulspirit/ability_vengefulspirit_spirit_debt_innate.lua" +__TS__ClassExtends(modifier_vengefulspirit_spirit_debt_mark, BaseModifier) +function modifier_vengefulspirit_spirit_debt_mark.prototype.IsHidden(self) + return false +end +function modifier_vengefulspirit_spirit_debt_mark.prototype.IsDebuff(self) + return true +end +function modifier_vengefulspirit_spirit_debt_mark.prototype.IsPurgable(self) + return false +end +function modifier_vengefulspirit_spirit_debt_mark.prototype.GetEffectName(self) + return "particles/units/heroes/hero_vengeful/vengeful_wave_of_terror_recipient.vpcf" +end +function modifier_vengefulspirit_spirit_debt_mark.prototype.GetEffectAttachType(self) + return PATTACH_ABSORIGIN_FOLLOW +end +modifier_vengefulspirit_spirit_debt_mark = __TS__Decorate( + modifier_vengefulspirit_spirit_debt_mark, + modifier_vengefulspirit_spirit_debt_mark, + {registerModifier(nil)}, + {kind = "class", name = "modifier_vengefulspirit_spirit_debt_mark"} +) +____exports.modifier_vengefulspirit_spirit_debt_mark = modifier_vengefulspirit_spirit_debt_mark +return ____exports diff --git a/scripts/vscripts/abilities/heroes/vengefulspirit/vengefulspirit_magic_missile_custom.lua b/scripts/vscripts/abilities/heroes/vengefulspirit/vengefulspirit_magic_missile_custom.lua new file mode 100644 index 0000000..5a6c798 --- /dev/null +++ b/scripts/vscripts/abilities/heroes/vengefulspirit/vengefulspirit_magic_missile_custom.lua @@ -0,0 +1,108 @@ +local ____lualib = require("lualib_bundle") +local __TS__Class = ____lualib.__TS__Class +local __TS__ClassExtends = ____lualib.__TS__ClassExtends +local __TS__ArrayFilter = ____lualib.__TS__ArrayFilter +local __TS__Decorate = ____lualib.__TS__Decorate +local ____exports = {} +local ____dota_ts_adapter = require("lib.dota_ts_adapter") +local BaseAbility = ____dota_ts_adapter.BaseAbility +local registerAbility = ____dota_ts_adapter.registerAbility +local ____ability_vengefulspirit_spirit_debt_innate = require("abilities.heroes.vengefulspirit.ability_vengefulspirit_spirit_debt_innate") +local applyVengefulSpiritDebtMark = ____ability_vengefulspirit_spirit_debt_innate.applyVengefulSpiritDebtMark +____exports.vengefulspirit_magic_missile_custom = __TS__Class() +local vengefulspirit_magic_missile_custom = ____exports.vengefulspirit_magic_missile_custom +vengefulspirit_magic_missile_custom.name = "vengefulspirit_magic_missile_custom" +vengefulspirit_magic_missile_custom.____file_path = "scripts/vscripts/abilities/heroes/vengefulspirit/vengefulspirit_magic_missile_custom.lua" +__TS__ClassExtends(vengefulspirit_magic_missile_custom, BaseAbility) +function vengefulspirit_magic_missile_custom.prototype.OnSpellStart(self) + local caster = self:GetCaster() + local target = self:GetCursorTarget() + caster:EmitSound("Hero_VengefulSpirit.MagicMissile") + local projectile = ProjectileManager:CreateTrackingProjectile({ + Target = target, + iMoveSpeed = self:GetSpecialValueFor("speed"), + bDodgeable = true, + iSourceAttachment = DOTA_PROJECTILE_ATTACHMENT_ATTACK_1, + EffectName = "particles/units/heroes/hero_vengeful/vengeful_magic_missle.vpcf", + Ability = self, + Source = caster, + ExtraData = {isFirstHit = 1} + }) +end +function vengefulspirit_magic_missile_custom.prototype.OnProjectileHit_ExtraData(self, target, location, extraData) + if not target then + return + end + if target:TriggerSpellAbsorb(self) then + return false + end + local caster = self:GetCaster() + local damage = self:GetSpecialValueFor("damage") + local actualManaBreak = math.min( + target:GetMana(), + self:GetSpecialValueFor("mana_burn") + ) + if actualManaBreak > 0 then + target:Script_ReduceMana(actualManaBreak, self) + local particle = ParticleManager:CreateParticle("particles/generic_gameplay/generic_manaburn.vpcf", PATTACH_ABSORIGIN_FOLLOW, target) + ParticleManager:ReleaseParticleIndex(particle) + end + ApplyDamage({ + victim = target, + attacker = caster, + damage = damage, + damage_type = self:GetAbilityDamageType(), + ability = self + }) + if not target:IsBossCreature() then + target:AddNewModifier( + caster, + self, + "modifier_stunned", + {duration = self:GetSpecialValueFor("stun_duration")} + ) + end + applyVengefulSpiritDebtMark(nil, target, caster, self) + EmitSoundOn("Hero_VengefulSpirit.MagicMissileImpact", target) + if extraData.isFirstHit == 1 then + local radius = self:GetCastRange( + caster:GetAbsOrigin(), + nil + ) + caster:GetCastRangeBonus() + local enemies = FindUnitsInRadius( + caster:GetTeamNumber(), + location, + nil, + radius, + DOTA_UNIT_TARGET_TEAM_ENEMY, + bit.bor(DOTA_UNIT_TARGET_HERO, DOTA_UNIT_TARGET_BASIC), + DOTA_UNIT_TARGET_FLAG_NONE, + FIND_CLOSEST, + false + ) + local filterEnemies = __TS__ArrayFilter( + enemies, + function(____, enemy) return enemy ~= target end + ) + local enemy = filterEnemies[1] + if not not enemy then + local projectile = ProjectileManager:CreateTrackingProjectile({ + Target = enemy, + iMoveSpeed = self:GetSpecialValueFor("speed"), + bDodgeable = true, + EffectName = "particles/units/heroes/hero_vengeful/vengeful_magic_missle.vpcf", + Ability = self, + Source = target, + ExtraData = {isFirstHit = 0} + }) + end + end +end +vengefulspirit_magic_missile_custom = __TS__Decorate( + vengefulspirit_magic_missile_custom, + vengefulspirit_magic_missile_custom, + {registerAbility(nil)}, + {kind = "class", name = "vengefulspirit_magic_missile_custom"} +) +____exports.vengefulspirit_magic_missile_custom = vengefulspirit_magic_missile_custom +return ____exports diff --git a/scripts/vscripts/abilities/heroes/vengefulspirit/vengefulspirit_nether_swap_custom.lua b/scripts/vscripts/abilities/heroes/vengefulspirit/vengefulspirit_nether_swap_custom.lua new file mode 100644 index 0000000..4b96651 --- /dev/null +++ b/scripts/vscripts/abilities/heroes/vengefulspirit/vengefulspirit_nether_swap_custom.lua @@ -0,0 +1,239 @@ +local ____lualib = require("lualib_bundle") +local Set = ____lualib.Set +local __TS__New = ____lualib.__TS__New +local __TS__Class = ____lualib.__TS__Class +local __TS__ClassExtends = ____lualib.__TS__ClassExtends +local __TS__StringIncludes = ____lualib.__TS__StringIncludes +local __TS__Decorate = ____lualib.__TS__Decorate +local ____exports = {} +local ____dota_ts_adapter = require("lib.dota_ts_adapter") +local BaseAbility = ____dota_ts_adapter.BaseAbility +local registerAbility = ____dota_ts_adapter.registerAbility +local ____ability_rubick_telekinesis_custom = require("abilities.heroes.rubick.ability_rubick_telekinesis_custom") +local ability_rubick_telekinesis_custom = ____ability_rubick_telekinesis_custom.ability_rubick_telekinesis_custom +local modifier_rubick_telekinesis_land = ____ability_rubick_telekinesis_custom.modifier_rubick_telekinesis_land +local modifier_rubick_telekinesis_lift = ____ability_rubick_telekinesis_custom.modifier_rubick_telekinesis_lift +local ____ability_vengefulspirit_spirit_debt_innate = require("abilities.heroes.vengefulspirit.ability_vengefulspirit_spirit_debt_innate") +local applyVengefulSpiritDebtMark = ____ability_vengefulspirit_spirit_debt_innate.applyVengefulSpiritDebtMark +local ____vengefulspirit_wave_of_terror_custom = require("abilities.heroes.vengefulspirit.vengefulspirit_wave_of_terror_custom") +local modifier_vengefulspirit_wave_of_terror_custom_buff = ____vengefulspirit_wave_of_terror_custom.modifier_vengefulspirit_wave_of_terror_custom_buff +local HOMER_UNIT_NAME = "npc_homer" +--- Модификаторы, которые нельзя копировать союзнику при Nether Swap (ломают логику способности). +local NETHER_SWAP_NON_TRANSFERABLE_MODIFIERS = __TS__New(Set, {modifier_rubick_telekinesis_lift.name, modifier_rubick_telekinesis_land.name}) +local function isNetherSwapNonTransferableModifier(self, modifier) + local name = modifier:GetName() + if NETHER_SWAP_NON_TRANSFERABLE_MODIFIERS:has(name) then + return true + end + local ability = modifier:GetAbility() + return (ability and ability:GetAbilityName()) == ability_rubick_telekinesis_custom.name +end +____exports.vengefulspirit_nether_swap_custom = __TS__Class() +local vengefulspirit_nether_swap_custom = ____exports.vengefulspirit_nether_swap_custom +vengefulspirit_nether_swap_custom.name = "vengefulspirit_nether_swap_custom" +vengefulspirit_nether_swap_custom.____file_path = "scripts/vscripts/abilities/heroes/vengefulspirit/vengefulspirit_nether_swap_custom.lua" +__TS__ClassExtends(vengefulspirit_nether_swap_custom, BaseAbility) +function vengefulspirit_nether_swap_custom.prototype.CastFilterResultTarget(self, target) + if IsClient() then + return UF_SUCCESS + end + local caster = self:GetCaster() + if target:IsBossCreature() or caster == target or target:IsCourier() or target:IsBuilding() or __TS__StringIncludes( + target:GetUnitName(), + "tower" + ) or target:IsInvulnerable() then + return UF_FAIL_CUSTOM + end + return UnitFilter( + target, + self:GetAbilityTargetTeam(), + self:GetAbilityTargetType(), + self:GetAbilityTargetFlags(), + caster:GetTeamNumber() + ) +end +function vengefulspirit_nether_swap_custom.prototype.GetCustomCastErrorTarget(self, target) + if IsClient() then + return "" + end + local caster = self:GetCaster() + if target:IsBossCreature() then + return "dota_error_target_boss" + end + if caster == target then + return "#dota_hud_error_cant_cast_on_self" + end + if target:IsCourier() then + return "#dota_hud_error_cant_cast_on_courier" + end + if target:IsBuilding() or __TS__StringIncludes( + target:GetUnitName(), + "tower" + ) then + return "#dota_hud_error_cant_cast_on_building" + end + if target:IsInvulnerable() then + return "#dota_hud_error_target_invulnerable" + end + return "" +end +function vengefulspirit_nether_swap_custom.prototype.OnSpellStart(self) + local caster = self:GetCaster() + local target = self:GetCursorTarget() + local min_pct = self:GetSpecialValueFor("hit_point_minimum_pct") + if target:TriggerSpellAbsorb(self) then + return + end + if target:GetTeamNumber() ~= caster:GetTeamNumber() then + applyVengefulSpiritDebtMark(nil, target, caster, self) + end + local health1 = math.max( + min_pct, + caster:GetHealthPercent() + ) / 100 + local health2 = math.max( + min_pct, + target:GetHealthPercent() + ) / 100 + caster:ModifyHealth( + caster:GetMaxHealth() * health2, + self, + false, + bit.bor(DOTA_DAMAGE_FLAG_HPLOSS, DOTA_DAMAGE_FLAG_NO_DIRECTOR_EVENT) + ) + target:ModifyHealth( + target:GetMaxHealth() * health1, + self, + false, + bit.bor(DOTA_DAMAGE_FLAG_HPLOSS, DOTA_DAMAGE_FLAG_NO_DIRECTOR_EVENT) + ) + local isTargetAlly = target:GetTeamNumber() == caster:GetTeamNumber() + local isHomerTarget = target:GetUnitName() == HOMER_UNIT_NAME + if isTargetAlly and not isHomerTarget then + local casterModifiers = caster:FindAllModifiers() + for ____, modifier in ipairs(casterModifiers) do + local isDurationModifier = modifier:GetDuration() ~= -1 + if isDurationModifier and not isNetherSwapNonTransferableModifier(nil, modifier) then + local modifierName = modifier:GetName() + local stackCount = modifier:GetStackCount() + local duration = modifier:GetRemainingTime() + local data = {duration = duration} + if modifierName == modifier_vengefulspirit_wave_of_terror_custom_buff.name then + data.damage = modifier.damage + data.armor = modifier.armor + end + local newModifier = target:AddNewModifier( + modifier:GetCaster() or caster, + modifier:GetAbility(), + modifierName, + data + ) + if stackCount > 0 then + local ____opt_2 = target:FindModifierByName(modifierName) + local currentStack = ____opt_2 and ____opt_2:GetStackCount() or 0 + newModifier:SetStackCount(stackCount + currentStack) + end + modifier:Destroy() + end + end + end + if self:GetSpecialValueFor("has_dispell") ~= 0 then + if target:GetTeamNumber() == caster:GetTeamNumber() then + target:Purge( + false, + true, + false, + false, + false + ) + else + target:Purge( + true, + false, + false, + false, + false + ) + end + end + local healOrDamage = self:GetSpecialValueFor("heal_or_damage") + if healOrDamage > 0 then + local valueTarget = target:GetMaxHealth() * (healOrDamage / 100) + if isTargetAlly then + target:Heal(valueTarget, self) + SendOverheadEventMessage( + nil, + OVERHEAD_ALERT_HEAL, + target, + valueTarget, + caster:GetPlayerOwner() + ) + else + ApplyDamage({ + victim = target, + attacker = caster, + damage = valueTarget, + damage_type = DAMAGE_TYPE_MAGICAL, + ability = self + }) + SendOverheadEventMessage( + nil, + OVERHEAD_ALERT_BONUS_SPELL_DAMAGE, + target, + valueTarget, + caster:GetPlayerOwner() + ) + end + end + local caster_pfx = ParticleManager:CreateParticle("particles/units/heroes/hero_vengeful/vengeful_nether_swap.vpcf", PATTACH_ABSORIGIN, caster) + ParticleManager:SetParticleControlEnt( + caster_pfx, + 0, + caster, + PATTACH_POINT_FOLLOW, + "attach_hitloc", + caster:GetAbsOrigin(), + true + ) + ParticleManager:SetParticleControlEnt( + caster_pfx, + 1, + target, + PATTACH_POINT_FOLLOW, + "attach_hitloc", + target:GetAbsOrigin(), + true + ) + local target_pfx = ParticleManager:CreateParticle("particles/units/heroes/hero_vengeful/vengeful_nether_swap_target.vpcf", PATTACH_ABSORIGIN, target) + ParticleManager:SetParticleControlEnt( + target_pfx, + 0, + target, + PATTACH_POINT_FOLLOW, + "attach_hitloc", + target:GetAbsOrigin(), + true + ) + ParticleManager:SetParticleControlEnt( + target_pfx, + 1, + caster, + PATTACH_POINT_FOLLOW, + "attach_hitloc", + caster:GetAbsOrigin(), + true + ) + EmitSoundOn( + "Hero_VengefulSpirit.NetherSwap", + self:GetCaster() + ) + EmitSoundOn("Hero_VengefulSpirit.NetherSwap", target) +end +vengefulspirit_nether_swap_custom = __TS__Decorate( + vengefulspirit_nether_swap_custom, + vengefulspirit_nether_swap_custom, + {registerAbility(nil)}, + {kind = "class", name = "vengefulspirit_nether_swap_custom"} +) +____exports.vengefulspirit_nether_swap_custom = vengefulspirit_nether_swap_custom +return ____exports diff --git a/scripts/vscripts/abilities/heroes/vengefulspirit/vengefulspirit_revenge.lua b/scripts/vscripts/abilities/heroes/vengefulspirit/vengefulspirit_revenge.lua new file mode 100644 index 0000000..92f5d12 --- /dev/null +++ b/scripts/vscripts/abilities/heroes/vengefulspirit/vengefulspirit_revenge.lua @@ -0,0 +1,95 @@ +local ____lualib = require("lualib_bundle") +local __TS__Class = ____lualib.__TS__Class +local __TS__ClassExtends = ____lualib.__TS__ClassExtends +local __TS__Decorate = ____lualib.__TS__Decorate +local ____exports = {} +local ____dota_ts_adapter = require("lib.dota_ts_adapter") +local BaseAbility = ____dota_ts_adapter.BaseAbility +local BaseModifier = ____dota_ts_adapter.BaseModifier +local registerAbility = ____dota_ts_adapter.registerAbility +local registerModifier = ____dota_ts_adapter.registerModifier +____exports.vengefulspirit_revenge = __TS__Class() +local vengefulspirit_revenge = ____exports.vengefulspirit_revenge +vengefulspirit_revenge.name = "vengefulspirit_revenge" +vengefulspirit_revenge.____file_path = "scripts/vscripts/abilities/heroes/vengefulspirit/vengefulspirit_revenge.lua" +__TS__ClassExtends(vengefulspirit_revenge, BaseAbility) +function vengefulspirit_revenge.prototype.Precache(self, context) + PrecacheResource("particle", "particles/heroes/vengeful/vengefulspirit_revenge_buff.vpcf", context) + PrecacheResource("particle", "particles/status_fx/status_effect_dark_willow_shadow_realm.vpcf", context) + PrecacheResource("soundfile", "soundevents/game_sounds_heroes/game_sounds_dark_willow.vsndevts", context) +end +function vengefulspirit_revenge.prototype.OnSpellStart(self) + local caster = self:GetCaster() + ____exports.modifier_vengefulspirit_revenge:apply( + caster, + caster, + self, + {duration = self:GetSpecialValueFor("duration")} + ) +end +vengefulspirit_revenge = __TS__Decorate( + vengefulspirit_revenge, + vengefulspirit_revenge, + {registerAbility(nil)}, + {kind = "class", name = "vengefulspirit_revenge"} +) +____exports.vengefulspirit_revenge = vengefulspirit_revenge +____exports.modifier_vengefulspirit_revenge = __TS__Class() +local modifier_vengefulspirit_revenge = ____exports.modifier_vengefulspirit_revenge +modifier_vengefulspirit_revenge.name = "modifier_vengefulspirit_revenge" +modifier_vengefulspirit_revenge.____file_path = "scripts/vscripts/abilities/heroes/vengefulspirit/vengefulspirit_revenge.lua" +__TS__ClassExtends(modifier_vengefulspirit_revenge, BaseModifier) +function modifier_vengefulspirit_revenge.prototype.____constructor(self, ...) + BaseModifier.prototype.____constructor(self, ...) + self.critBonus = self:GetAbility():GetSpecialValueFor("crit_bonus") +end +function modifier_vengefulspirit_revenge.prototype.IsHidden(self) + return false +end +function modifier_vengefulspirit_revenge.prototype.IsPurgable(self) + return true +end +function modifier_vengefulspirit_revenge.prototype.GetEffectName(self) + return "particles/heroes/vengeful/vengefulspirit_revenge_buff.vpcf" +end +function modifier_vengefulspirit_revenge.prototype.GetStatusEffectName(self) + return "particles/status_fx/status_effect_dark_willow_shadow_realm.vpcf" +end +function modifier_vengefulspirit_revenge.prototype.StatusEffectPriority(self) + return MODIFIER_PRIORITY_SUPER_ULTRA +end +function modifier_vengefulspirit_revenge.prototype.DeclareFunctions(self) + return {MODIFIER_PROPERTY_PREATTACK_CRITICALSTRIKE} +end +function modifier_vengefulspirit_revenge.prototype.OnCreated(self, params) + if IsClient() then + return + end + EmitSoundOn( + "Hero_DarkWillow.Shadow_Realm", + self:GetParent() + ) +end +function modifier_vengefulspirit_revenge.prototype.OnDestroy(self) + if IsClient() then + return + end + StopSoundOn( + "Hero_DarkWillow.Shadow_Realm", + self:GetParent() + ) +end +function modifier_vengefulspirit_revenge.prototype.CheckState(self) + return {[MODIFIER_STATE_ATTACK_IMMUNE] = true, [MODIFIER_STATE_UNTARGETABLE] = true} +end +function modifier_vengefulspirit_revenge.prototype.GetModifierPreAttack_CriticalStrike(self, event) + return self.critBonus +end +modifier_vengefulspirit_revenge = __TS__Decorate( + modifier_vengefulspirit_revenge, + modifier_vengefulspirit_revenge, + {registerModifier(nil)}, + {kind = "class", name = "modifier_vengefulspirit_revenge"} +) +____exports.modifier_vengefulspirit_revenge = modifier_vengefulspirit_revenge +return ____exports diff --git a/scripts/vscripts/abilities/heroes/vengefulspirit/vengefulspirit_wave_of_terror_custom.lua b/scripts/vscripts/abilities/heroes/vengefulspirit/vengefulspirit_wave_of_terror_custom.lua new file mode 100644 index 0000000..d6fc164 --- /dev/null +++ b/scripts/vscripts/abilities/heroes/vengefulspirit/vengefulspirit_wave_of_terror_custom.lua @@ -0,0 +1,206 @@ +local ____lualib = require("lualib_bundle") +local __TS__Class = ____lualib.__TS__Class +local __TS__ClassExtends = ____lualib.__TS__ClassExtends +local __TS__Delete = ____lualib.__TS__Delete +local __TS__Decorate = ____lualib.__TS__Decorate +local ____exports = {} +local ____dota_ts_adapter = require("lib.dota_ts_adapter") +local BaseAbility = ____dota_ts_adapter.BaseAbility +local BaseModifier = ____dota_ts_adapter.BaseModifier +local registerAbility = ____dota_ts_adapter.registerAbility +local registerModifier = ____dota_ts_adapter.registerModifier +local ____ability_vengefulspirit_spirit_debt_innate = require("abilities.heroes.vengefulspirit.ability_vengefulspirit_spirit_debt_innate") +local applyVengefulSpiritDebtMark = ____ability_vengefulspirit_spirit_debt_innate.applyVengefulSpiritDebtMark +____exports.vengefulspirit_wave_of_terror_custom = __TS__Class() +local vengefulspirit_wave_of_terror_custom = ____exports.vengefulspirit_wave_of_terror_custom +vengefulspirit_wave_of_terror_custom.name = "vengefulspirit_wave_of_terror_custom" +vengefulspirit_wave_of_terror_custom.____file_path = "scripts/vscripts/abilities/heroes/vengefulspirit/vengefulspirit_wave_of_terror_custom.lua" +__TS__ClassExtends(vengefulspirit_wave_of_terror_custom, BaseAbility) +function vengefulspirit_wave_of_terror_custom.prototype.____constructor(self, ...) + BaseAbility.prototype.____constructor(self, ...) + self.projectilesId = {} +end +function vengefulspirit_wave_of_terror_custom.prototype.OnSpellStart(self) + local caster = self:GetCaster() + local targetPoint = self:GetCursorPosition() + local vDirection = targetPoint - caster:GetOrigin() + vDirection = vDirection:Normalized() + local waveWidth = self:GetSpecialValueFor("wave_width") + local waveSpeed = self:GetSpecialValueFor("wave_speed") + local info = { + EffectName = "particles/units/heroes/hero_vengeful/vengeful_wave_of_terror.vpcf", + Ability = self, + vSpawnOrigin = caster:GetOrigin(), + fStartRadius = waveWidth, + fEndRadius = waveWidth, + vVelocity = vDirection * waveSpeed, + fDistance = self:GetCastRange( + caster:GetOrigin(), + caster + ), + Source = caster, + iUnitTargetTeam = DOTA_UNIT_TARGET_TEAM_ENEMY, + iUnitTargetType = bit.bor(DOTA_UNIT_TARGET_HERO, DOTA_UNIT_TARGET_BASIC), + iUnitTargetFlags = DOTA_UNIT_TARGET_FLAG_MAGIC_IMMUNE_ENEMIES, + bProvidesVision = true, + iVisionTeamNumber = caster:GetTeamNumber(), + iVisionRadius = self:GetSpecialValueFor("vision_aoe") + } + self.projectilesId[ProjectileManager:CreateLinearProjectile(info)] = {damage = 0, armor = 0} + EmitSoundOn("Hero_VengefulSpirit.WaveOfTerror", caster) +end +function vengefulspirit_wave_of_terror_custom.prototype.OnProjectileHitHandle(self, target, vLocation, projectileId) + local stealPct = self:GetSpecialValueFor("steal_pct") / 100 + if not target then + local ____self_projectilesId_projectileId_0 = self.projectilesId[projectileId] + local damage = ____self_projectilesId_projectileId_0.damage + local armor = ____self_projectilesId_projectileId_0.armor + local caster = self:GetCaster() + if stealPct > 0 then + ____exports.modifier_vengefulspirit_wave_of_terror_custom_buff:apply( + caster, + caster, + self, + { + duration = self:GetDuration(), + damage = damage, + armor = armor + } + ) + end + __TS__Delete(self.projectilesId, projectileId) + end + if target then + local damage = { + victim = target, + attacker = self:GetCaster(), + damage = self:GetSpecialValueFor("damage"), + damage_type = self:GetAbilityDamageType(), + ability = self + } + ApplyDamage(damage) + target:AddNewModifier( + self:GetCaster(), + self, + ____exports.modifier_vengefulspirit_wave_of_terror_custom.name, + {duration = self:GetDuration()} + ) + applyVengefulSpiritDebtMark( + nil, + target, + self:GetCaster(), + self + ) + if stealPct > 0 then + local ____table = self.projectilesId[projectileId] + local flatStolen = math.abs(self:GetSpecialValueFor("armor_reduction")) * stealPct + local baseArmor = target:GetPhysicalArmorBaseValue() + local pctStolen = baseArmor > 0 and baseArmor * (self:GetSpecialValueFor("armor_reduction_pct") / 100) * stealPct or 0 + self.projectilesId[projectileId] = { + damage = ____table.damage + target:GetAttackDamage() * (self:GetSpecialValueFor("damage") / 100) * stealPct, + armor = ____table.armor + math.ceil(flatStolen + pctStolen) + } + end + end + return false +end +function vengefulspirit_wave_of_terror_custom.prototype.OnProjectileThink(self, vLocation) + AddFOWViewer( + self:GetCaster():GetTeamNumber(), + vLocation, + self:GetSpecialValueFor("vision_aoe"), + self:GetSpecialValueFor("vision_duration"), + false + ) +end +vengefulspirit_wave_of_terror_custom = __TS__Decorate( + vengefulspirit_wave_of_terror_custom, + vengefulspirit_wave_of_terror_custom, + {registerAbility(nil)}, + {kind = "class", name = "vengefulspirit_wave_of_terror_custom"} +) +____exports.vengefulspirit_wave_of_terror_custom = vengefulspirit_wave_of_terror_custom +____exports.modifier_vengefulspirit_wave_of_terror_custom = __TS__Class() +local modifier_vengefulspirit_wave_of_terror_custom = ____exports.modifier_vengefulspirit_wave_of_terror_custom +modifier_vengefulspirit_wave_of_terror_custom.name = "modifier_vengefulspirit_wave_of_terror_custom" +modifier_vengefulspirit_wave_of_terror_custom.____file_path = "scripts/vscripts/abilities/heroes/vengefulspirit/vengefulspirit_wave_of_terror_custom.lua" +__TS__ClassExtends(modifier_vengefulspirit_wave_of_terror_custom, BaseModifier) +function modifier_vengefulspirit_wave_of_terror_custom.prototype.IsDebuff(self) + return true +end +function modifier_vengefulspirit_wave_of_terror_custom.prototype.GetEffectName(self) + return "particles/units/heroes/hero_vengeful/vengeful_wave_of_terror_recipient.vpcf" +end +function modifier_vengefulspirit_wave_of_terror_custom.prototype.GetEffectAttachType(self) + return PATTACH_ABSORIGIN_FOLLOW +end +function modifier_vengefulspirit_wave_of_terror_custom.prototype.OnCreated(self, params) + local ability = self:GetAbility() + if ability then + self.armor_reduction = ability:GetSpecialValueFor("armor_reduction") + self.armor_reduction_pct = ability:GetSpecialValueFor("armor_reduction_pct") + self.attack_reduction = ability:GetSpecialValueFor("attack_reduction") + end +end +function modifier_vengefulspirit_wave_of_terror_custom.prototype.DeclareFunctions(self) + return {MODIFIER_PROPERTY_PHYSICAL_ARMOR_BONUS, MODIFIER_PROPERTY_DAMAGEOUTGOING_PERCENTAGE} +end +function modifier_vengefulspirit_wave_of_terror_custom.prototype.GetModifierPhysicalArmorBonus(self, _params) + local parent = self:GetParent() + local baseArmor = parent:GetPhysicalArmorBaseValue() + local pctReduction = baseArmor > 0 and -baseArmor * (self.armor_reduction_pct / 100) or 0 + return self.armor_reduction + pctReduction +end +function modifier_vengefulspirit_wave_of_terror_custom.prototype.GetModifierDamageOutgoing_Percentage(self, params) + return -self.attack_reduction +end +modifier_vengefulspirit_wave_of_terror_custom = __TS__Decorate( + modifier_vengefulspirit_wave_of_terror_custom, + modifier_vengefulspirit_wave_of_terror_custom, + {registerModifier(nil)}, + {kind = "class", name = "modifier_vengefulspirit_wave_of_terror_custom"} +) +____exports.modifier_vengefulspirit_wave_of_terror_custom = modifier_vengefulspirit_wave_of_terror_custom +____exports.modifier_vengefulspirit_wave_of_terror_custom_buff = __TS__Class() +local modifier_vengefulspirit_wave_of_terror_custom_buff = ____exports.modifier_vengefulspirit_wave_of_terror_custom_buff +modifier_vengefulspirit_wave_of_terror_custom_buff.name = "modifier_vengefulspirit_wave_of_terror_custom_buff" +modifier_vengefulspirit_wave_of_terror_custom_buff.____file_path = "scripts/vscripts/abilities/heroes/vengefulspirit/vengefulspirit_wave_of_terror_custom.lua" +__TS__ClassExtends(modifier_vengefulspirit_wave_of_terror_custom_buff, BaseModifier) +function modifier_vengefulspirit_wave_of_terror_custom_buff.prototype.IsHidden(self) + return false +end +function modifier_vengefulspirit_wave_of_terror_custom_buff.prototype.DeclareFunctions(self) + return {MODIFIER_PROPERTY_PHYSICAL_ARMOR_BONUS, MODIFIER_PROPERTY_PREATTACK_BONUS_DAMAGE} +end +function modifier_vengefulspirit_wave_of_terror_custom_buff.prototype.GetAttributes(self) + return MODIFIER_ATTRIBUTE_MULTIPLE +end +function modifier_vengefulspirit_wave_of_terror_custom_buff.prototype.OnCreated(self, params) + if IsClient() then + return + end + self.armor = math.abs(params.armor) + self.damage = params.damage + self:SetHasCustomTransmitterData(true) +end +function modifier_vengefulspirit_wave_of_terror_custom_buff.prototype.AddCustomTransmitterData(self) + return {armor = self.armor, damage = self.damage} +end +function modifier_vengefulspirit_wave_of_terror_custom_buff.prototype.HandleCustomTransmitterData(self, data) + self.armor = data.armor + self.damage = data.damage +end +function modifier_vengefulspirit_wave_of_terror_custom_buff.prototype.GetModifierPhysicalArmorBonus(self, params) + return self.armor +end +function modifier_vengefulspirit_wave_of_terror_custom_buff.prototype.GetModifierPreAttack_BonusDamage(self) + return self.damage +end +modifier_vengefulspirit_wave_of_terror_custom_buff = __TS__Decorate( + modifier_vengefulspirit_wave_of_terror_custom_buff, + modifier_vengefulspirit_wave_of_terror_custom_buff, + {registerModifier(nil)}, + {kind = "class", name = "modifier_vengefulspirit_wave_of_terror_custom_buff"} +) +____exports.modifier_vengefulspirit_wave_of_terror_custom_buff = modifier_vengefulspirit_wave_of_terror_custom_buff +return ____exports diff --git a/scripts/vscripts/abilities/heroes/yuki-onna/ability_yuki_challenge.lua b/scripts/vscripts/abilities/heroes/yuki-onna/ability_yuki_challenge.lua new file mode 100644 index 0000000..16903dc --- /dev/null +++ b/scripts/vscripts/abilities/heroes/yuki-onna/ability_yuki_challenge.lua @@ -0,0 +1,273 @@ +local ____lualib = require("lualib_bundle") +local __TS__Class = ____lualib.__TS__Class +local __TS__ClassExtends = ____lualib.__TS__ClassExtends +local __TS__Decorate = ____lualib.__TS__Decorate +local ____exports = {} +local ____dota_ts_adapter = require("lib.dota_ts_adapter") +local BaseAbility = ____dota_ts_adapter.BaseAbility +local BaseModifier = ____dota_ts_adapter.BaseModifier +local registerAbility = ____dota_ts_adapter.registerAbility +local registerModifier = ____dota_ts_adapter.registerModifier +____exports.ability_yuki_challenge = __TS__Class() +local ability_yuki_challenge = ____exports.ability_yuki_challenge +ability_yuki_challenge.name = "ability_yuki_challenge" +ability_yuki_challenge.____file_path = "scripts/vscripts/abilities/heroes/yuki-onna/ability_yuki_challenge.lua" +__TS__ClassExtends(ability_yuki_challenge, BaseAbility) +function ability_yuki_challenge.prototype.OnSpellStart(self) + if not IsServer() then + return + end + local target = self:GetCursorTarget() + if not target then + return + end + target:AddNewModifier( + self:GetCaster(), + self, + ____exports.modifier_challenge_debuff.name, + {duration = self:GetSpecialValueFor("duration")} + ) +end +ability_yuki_challenge = __TS__Decorate( + ability_yuki_challenge, + ability_yuki_challenge, + {registerAbility(nil)}, + {kind = "class", name = "ability_yuki_challenge"} +) +____exports.ability_yuki_challenge = ability_yuki_challenge +____exports.modifier_challenge_debuff = __TS__Class() +local modifier_challenge_debuff = ____exports.modifier_challenge_debuff +modifier_challenge_debuff.name = "modifier_challenge_debuff" +modifier_challenge_debuff.____file_path = "scripts/vscripts/abilities/heroes/yuki-onna/ability_yuki_challenge.lua" +__TS__ClassExtends(modifier_challenge_debuff, BaseModifier) +function modifier_challenge_debuff.prototype.____constructor(self, ...) + BaseModifier.prototype.____constructor(self, ...) + self.interval = 0 + self.dmgPerAtr = 0 + self.fullDamage = 0 +end +function modifier_challenge_debuff.prototype.IsHidden(self) + return false +end +function modifier_challenge_debuff.prototype.IsPurgable(self) + return false +end +function modifier_challenge_debuff.prototype.IsDebuff(self) + return true +end +function modifier_challenge_debuff.prototype.IsBuff(self) + return false +end +function modifier_challenge_debuff.prototype.RemoveOnDeath(self) + return true +end +function modifier_challenge_debuff.prototype.DeclareFunctions(self) + return {MODIFIER_PROPERTY_DISABLE_HEALING} +end +function modifier_challenge_debuff.prototype.CheckState(self) + return { + [MODIFIER_STATE_MUTED] = true, + [MODIFIER_STATE_SILENCED] = true, + [MODIFIER_STATE_STUNNED] = true, + [MODIFIER_STATE_ROOTED] = true, + [MODIFIER_STATE_DISARMED] = true + } +end +function modifier_challenge_debuff.prototype.GetEffectName(self) + return "particles/econ/items/winter_wyvern/winter_wyvern_ti7/wyvern_cold_embrace_ti7buff.vpcf" +end +function modifier_challenge_debuff.prototype.GetDisableHealing(self) + return 1 +end +function modifier_challenge_debuff.prototype.OnCreated(self) + if not IsServer() then + return + end + self.parent = self:GetParent() + if not self.parent or not self.parent:IsRealHero() then + return + end + local ability = self:GetAbility() + if not ability then + return + end + local hero = self.parent + if not hero.yuki_challenge then + hero.yuki_challenge = 100 + end + local primaryAttribute = hero:GetPrimaryAttribute() + local primaryStatValue = hero:GetPrimaryStatValue() + self.interval = ability:GetSpecialValueFor("interval") + self.dmgPerAtr = ability:GetSpecialValueFor("dmg_per_atr") + local yukiChallenge = hero.yuki_challenge + self.fullDamage = self.dmgPerAtr * primaryStatValue * (yukiChallenge / 100) + self:StartIntervalThink(self.interval) + EmitSoundOn("Hero_Winter_Wyvern.ColdEmbrace", hero) +end +function modifier_challenge_debuff.prototype.OnRefresh(self) + self:OnCreated() +end +function modifier_challenge_debuff.prototype.OnIntervalThink(self) + if not IsServer() then + return + end + local ability = self:GetAbility() + local caster = self:GetCaster() + if not ability or not caster or not self.parent then + return + end + local intervalDur = self.interval / ability:GetSpecialValueFor("duration") + local damage = self.fullDamage * intervalDur + ApplyDamage({ + victim = self.parent, + attacker = caster, + damage = damage, + damage_type = DAMAGE_TYPE_PURE, + ability = ability + }) +end +function modifier_challenge_debuff.prototype.OnDestroy(self) + if not IsServer() then + return + end + if self.parent then + StopSoundOn("Hero_Winter_Wyvern.ColdEmbrace", self.parent) + if not self.parent:IsAlive() then + local ability = self:GetAbility() + if ability then + self.parent.yuki_challenge = self.parent.yuki_challenge - ability:GetSpecialValueFor("dmg_reduce") + end + return + end + local ability = self:GetAbility() + if ability then + self.parent.yuki_challenge = self.parent.yuki_challenge + ability:GetSpecialValueFor("dmg_incr") + local caster = self:GetCaster() + if caster then + local existing = self.parent:FindModifierByName(____exports.modifier_challenge_buff.name) + if existing then + existing:AddCompletedTrialStack() + else + local modif = self.parent:AddNewModifier(caster, ability, ____exports.modifier_challenge_buff.name, {}) + if modif ~= nil then + modif:AddCompletedTrialStack() + end + end + end + end + end +end +modifier_challenge_debuff = __TS__Decorate( + modifier_challenge_debuff, + modifier_challenge_debuff, + {registerModifier(nil)}, + {kind = "class", name = "modifier_challenge_debuff"} +) +____exports.modifier_challenge_debuff = modifier_challenge_debuff +____exports.modifier_challenge_buff = __TS__Class() +local modifier_challenge_buff = ____exports.modifier_challenge_buff +modifier_challenge_buff.name = "modifier_challenge_buff" +modifier_challenge_buff.____file_path = "scripts/vscripts/abilities/heroes/yuki-onna/ability_yuki_challenge.lua" +__TS__ClassExtends(modifier_challenge_buff, BaseModifier) +function modifier_challenge_buff.prototype.____constructor(self, ...) + BaseModifier.prototype.____constructor(self, ...) + self.lockedFlatBonus = 0 +end +function modifier_challenge_buff.prototype.IsHidden(self) + return false +end +function modifier_challenge_buff.prototype.IsPurgable(self) + return true +end +function modifier_challenge_buff.prototype.IsDebuff(self) + return false +end +function modifier_challenge_buff.prototype.IsBuff(self) + return true +end +function modifier_challenge_buff.prototype.RemoveOnDeath(self) + return false +end +function modifier_challenge_buff.prototype.OnCreated(self) + self.lockedFlatBonus = 0 + self.primaryAttribute = nil + local parent = self:GetParent() + if parent and parent:IsRealHero() then + self.primaryAttribute = parent:GetPrimaryAttribute() + end +end +function modifier_challenge_buff.prototype.DeclareFunctions(self) + return {MODIFIER_PROPERTY_STATS_STRENGTH_BONUS, MODIFIER_PROPERTY_STATS_AGILITY_BONUS, MODIFIER_PROPERTY_STATS_INTELLECT_BONUS, MODIFIER_PROPERTY_TOOLTIP} +end +function modifier_challenge_buff.prototype.OnTooltip(self) + return math.max( + 0, + math.floor(self:GetStackCount()) + ) +end +function modifier_challenge_buff.prototype.AddCompletedTrialStack(self) + if not IsServer() then + return + end + local parent = self:GetParent() + if not parent or not parent:IsRealHero() then + return + end + local hero = parent + if self.primaryAttribute == nil then + self.primaryAttribute = hero:GetPrimaryAttribute() + end + self.lockedFlatBonus = self.lockedFlatBonus + self:ComputeFlatBonusForOneStack(hero) + self:SyncLockedBonusToStackCount() +end +function modifier_challenge_buff.prototype.SyncLockedBonusToStackCount(self) + self:SetStackCount(math.max( + 0, + math.floor(self.lockedFlatBonus) + )) +end +function modifier_challenge_buff.prototype.ComputeFlatBonusForOneStack(self, hero) + local ability = self:GetAbility() + local caster = self:GetCaster() + if not ability then + return 0 + end + local pct = ability:GetSpecialValueFor("bonus_main") + if caster and caster:HasScepter() then + pct = pct + ability:GetSpecialValueFor("main_pct") + end + if pct <= 0 then + return 0 + end + local primaryValue = hero:GetPrimaryStatValue() + return math.floor(primaryValue * (pct / 100)) +end +function modifier_challenge_buff.prototype.GetLockedBonusForPrimaryStat(self) + return self.lockedFlatBonus +end +function modifier_challenge_buff.prototype.GetModifierBonusStats_Strength(self) + if self.primaryAttribute == DOTA_ATTRIBUTE_STRENGTH or self.primaryAttribute == DOTA_ATTRIBUTE_ALL then + return self:GetLockedBonusForPrimaryStat() + end + return 0 +end +function modifier_challenge_buff.prototype.GetModifierBonusStats_Agility(self) + if self.primaryAttribute == DOTA_ATTRIBUTE_AGILITY or self.primaryAttribute == DOTA_ATTRIBUTE_ALL then + return self:GetLockedBonusForPrimaryStat() + end + return 0 +end +function modifier_challenge_buff.prototype.GetModifierBonusStats_Intellect(self) + if self.primaryAttribute == DOTA_ATTRIBUTE_INTELLECT or self.primaryAttribute == DOTA_ATTRIBUTE_ALL then + return self:GetLockedBonusForPrimaryStat() + end + return 0 +end +modifier_challenge_buff = __TS__Decorate( + modifier_challenge_buff, + modifier_challenge_buff, + {registerModifier(nil)}, + {kind = "class", name = "modifier_challenge_buff"} +) +____exports.modifier_challenge_buff = modifier_challenge_buff +return ____exports diff --git a/scripts/vscripts/abilities/heroes/yuki-onna/ability_yuki_frostshtorm.lua b/scripts/vscripts/abilities/heroes/yuki-onna/ability_yuki_frostshtorm.lua new file mode 100644 index 0000000..9f0c3af --- /dev/null +++ b/scripts/vscripts/abilities/heroes/yuki-onna/ability_yuki_frostshtorm.lua @@ -0,0 +1,181 @@ +local ____lualib = require("lualib_bundle") +local __TS__Class = ____lualib.__TS__Class +local __TS__ClassExtends = ____lualib.__TS__ClassExtends +local __TS__Decorate = ____lualib.__TS__Decorate +local ____exports = {} +local ____dota_ts_adapter = require("lib.dota_ts_adapter") +local BaseAbility = ____dota_ts_adapter.BaseAbility +local BaseModifier = ____dota_ts_adapter.BaseModifier +local registerAbility = ____dota_ts_adapter.registerAbility +local registerModifier = ____dota_ts_adapter.registerModifier +____exports.ability_yuki_frostshtorm = __TS__Class() +local ability_yuki_frostshtorm = ____exports.ability_yuki_frostshtorm +ability_yuki_frostshtorm.name = "ability_yuki_frostshtorm" +ability_yuki_frostshtorm.____file_path = "scripts/vscripts/abilities/heroes/yuki-onna/ability_yuki_frostshtorm.lua" +__TS__ClassExtends(ability_yuki_frostshtorm, BaseAbility) +function ability_yuki_frostshtorm.prototype.GetAOERadius(self) + return self:GetSpecialValueFor("radius") +end +function ability_yuki_frostshtorm.prototype.OnSpellStart(self) + if not IsServer() then + return + end + local caster = self:GetCaster() + local point = self:GetCursorPosition() + local duration = self:GetSpecialValueFor("duration") + CreateModifierThinker( + caster, + self, + ____exports.modifier_froststorm_thunker.name, + {duration = duration}, + point, + caster:GetTeamNumber(), + false + ) + EmitSoundOn("Hero_Ancient_Apparition.IceVortexCast", caster) +end +ability_yuki_frostshtorm = __TS__Decorate( + ability_yuki_frostshtorm, + ability_yuki_frostshtorm, + {registerAbility(nil)}, + {kind = "class", name = "ability_yuki_frostshtorm"} +) +____exports.ability_yuki_frostshtorm = ability_yuki_frostshtorm +____exports.modifier_froststorm_thunker = __TS__Class() +local modifier_froststorm_thunker = ____exports.modifier_froststorm_thunker +modifier_froststorm_thunker.name = "modifier_froststorm_thunker" +modifier_froststorm_thunker.____file_path = "scripts/vscripts/abilities/heroes/yuki-onna/ability_yuki_frostshtorm.lua" +__TS__ClassExtends(modifier_froststorm_thunker, BaseModifier) +function modifier_froststorm_thunker.prototype.____constructor(self, ...) + BaseModifier.prototype.____constructor(self, ...) + self.radiusShadow = 0 +end +function modifier_froststorm_thunker.prototype.IsHidden(self) + return false +end +function modifier_froststorm_thunker.prototype.IsPurgable(self) + return false +end +function modifier_froststorm_thunker.prototype.IsDebuff(self) + return false +end +function modifier_froststorm_thunker.prototype.IsBuff(self) + return true +end +function modifier_froststorm_thunker.prototype.RemoveOnDeath(self) + return false +end +function modifier_froststorm_thunker.prototype.OnCreated(self) + if not IsServer() then + return + end + local particleCast = "particles/econ/items/ancient_apparition/ancient_apparation_ti8/ancient_ice_vortex_ti8.vpcf" + local ability = self:GetAbility() + if not ability then + return + end + self.radiusShadow = ability:GetSpecialValueFor("radius") + local parent = self:GetParent() + self.effectCast = ParticleManager:CreateParticle(particleCast, PATTACH_WORLDORIGIN, parent) + ParticleManager:SetParticleControl( + self.effectCast, + 0, + parent:GetAbsOrigin() + ) + ParticleManager:SetParticleControl( + self.effectCast, + 5, + Vector(self.radiusShadow, 0, 0) + ) + EmitSoundOn("Hero_Ancient_Apparition.IceVortex", parent) +end +function modifier_froststorm_thunker.prototype.OnDestroy(self) + if not IsServer() then + return + end + if self.effectCast ~= nil then + ParticleManager:DestroyParticle(self.effectCast, false) + ParticleManager:ReleaseParticleIndex(self.effectCast) + end + StopSoundOn( + "Hero_Ancient_Apparition.IceVortex", + self:GetParent() + ) +end +function modifier_froststorm_thunker.prototype.IsAura(self) + return true +end +function modifier_froststorm_thunker.prototype.GetModifierAura(self) + return ____exports.modifier_froststorm_buff.name +end +function modifier_froststorm_thunker.prototype.GetAuraRadius(self) + return self.radiusShadow +end +function modifier_froststorm_thunker.prototype.GetAuraDuration(self) + return 0.1 +end +function modifier_froststorm_thunker.prototype.GetAuraSearchTeam(self) + return DOTA_UNIT_TARGET_TEAM_BOTH +end +function modifier_froststorm_thunker.prototype.GetAuraSearchType(self) + return bit.bor(DOTA_UNIT_TARGET_HERO, DOTA_UNIT_TARGET_BASIC) +end +function modifier_froststorm_thunker.prototype.GetAuraSearchFlags(self) + local ability = self:GetAbility() + if not ability then + return DOTA_UNIT_TARGET_FLAG_NONE + end + return ability:GetAbilityTargetFlags() +end +modifier_froststorm_thunker = __TS__Decorate( + modifier_froststorm_thunker, + modifier_froststorm_thunker, + {registerModifier(nil)}, + {kind = "class", name = "modifier_froststorm_thunker"} +) +____exports.modifier_froststorm_thunker = modifier_froststorm_thunker +____exports.modifier_froststorm_buff = __TS__Class() +local modifier_froststorm_buff = ____exports.modifier_froststorm_buff +modifier_froststorm_buff.name = "modifier_froststorm_buff" +modifier_froststorm_buff.____file_path = "scripts/vscripts/abilities/heroes/yuki-onna/ability_yuki_frostshtorm.lua" +__TS__ClassExtends(modifier_froststorm_buff, BaseModifier) +function modifier_froststorm_buff.prototype.IsHidden(self) + return false +end +function modifier_froststorm_buff.prototype.IsPurgable(self) + return false +end +function modifier_froststorm_buff.prototype.IsDebuff(self) + return false +end +function modifier_froststorm_buff.prototype.IsBuff(self) + return true +end +function modifier_froststorm_buff.prototype.RemoveOnDeath(self) + return true +end +function modifier_froststorm_buff.prototype.DeclareFunctions(self) + return {MODIFIER_PROPERTY_PREATTACK_BONUS_DAMAGE} +end +function modifier_froststorm_buff.prototype.GetModifierPreAttack_BonusDamage(self) + local parent = self:GetParent() + local caster = self:GetCaster() + local ability = self:GetAbility() + if not parent or not caster or not ability then + return 0 + end + local dmgReduced = ability:GetSpecialValueFor("dmg_reduced") + if parent:GetTeamNumber() ~= caster:GetTeamNumber() then + return dmgReduced + else + return -dmgReduced + end +end +modifier_froststorm_buff = __TS__Decorate( + modifier_froststorm_buff, + modifier_froststorm_buff, + {registerModifier(nil)}, + {kind = "class", name = "modifier_froststorm_buff"} +) +____exports.modifier_froststorm_buff = modifier_froststorm_buff +return ____exports diff --git a/scripts/vscripts/abilities/heroes/yuki-onna/ability_yuki_pohela.lua b/scripts/vscripts/abilities/heroes/yuki-onna/ability_yuki_pohela.lua new file mode 100644 index 0000000..a2f1584 --- /dev/null +++ b/scripts/vscripts/abilities/heroes/yuki-onna/ability_yuki_pohela.lua @@ -0,0 +1,173 @@ +local ____lualib = require("lualib_bundle") +local __TS__Class = ____lualib.__TS__Class +local __TS__ClassExtends = ____lualib.__TS__ClassExtends +local __TS__Decorate = ____lualib.__TS__Decorate +local ____exports = {} +local ____dota_ts_adapter = require("lib.dota_ts_adapter") +local BaseAbility = ____dota_ts_adapter.BaseAbility +local BaseModifier = ____dota_ts_adapter.BaseModifier +local registerAbility = ____dota_ts_adapter.registerAbility +local registerModifier = ____dota_ts_adapter.registerModifier +local ____incoming_damage_reduction_combine = require("utils.incoming_damage_reduction_combine") +local removeIncomingDamageReductionSource = ____incoming_damage_reduction_combine.removeIncomingDamageReductionSource +local setIncomingDamageReductionSource = ____incoming_damage_reduction_combine.setIncomingDamageReductionSource +local POHELA_BUFF_INCOMING_SOURCE = "modifier_pohela_buff" +____exports.ability_yuki_pohela = __TS__Class() +local ability_yuki_pohela = ____exports.ability_yuki_pohela +ability_yuki_pohela.name = "ability_yuki_pohela" +ability_yuki_pohela.____file_path = "scripts/vscripts/abilities/heroes/yuki-onna/ability_yuki_pohela.lua" +__TS__ClassExtends(ability_yuki_pohela, BaseAbility) +function ability_yuki_pohela.prototype.OnSpellStart(self) + if not IsServer() then + return + end + local target = self:GetCursorTarget() + if not target then + return + end + target:AddNewModifier( + self:GetCaster(), + self, + ____exports.modifier_pohela_buff.name, + {duration = self:GetChannelTime()} + ) + EmitSoundOn("hero_Crystal.frostbite", target) +end +function ability_yuki_pohela.prototype.OnChannelFinish(self, interrupted) + if not IsServer() then + return + end + local target = self:GetCursorTarget() + if target then + target:RemoveModifierByName(____exports.modifier_pohela_buff.name) + StopSoundOn("hero_Crystal.frostbite", target) + end +end +ability_yuki_pohela = __TS__Decorate( + ability_yuki_pohela, + ability_yuki_pohela, + {registerAbility(nil)}, + {kind = "class", name = "ability_yuki_pohela"} +) +____exports.ability_yuki_pohela = ability_yuki_pohela +____exports.modifier_pohela_buff = __TS__Class() +local modifier_pohela_buff = ____exports.modifier_pohela_buff +modifier_pohela_buff.name = "modifier_pohela_buff" +modifier_pohela_buff.____file_path = "scripts/vscripts/abilities/heroes/yuki-onna/ability_yuki_pohela.lua" +__TS__ClassExtends(modifier_pohela_buff, BaseModifier) +function modifier_pohela_buff.prototype.IsHidden(self) + return false +end +function modifier_pohela_buff.prototype.IsPurgable(self) + return false +end +function modifier_pohela_buff.prototype.IsDebuff(self) + return false +end +function modifier_pohela_buff.prototype.IsBuff(self) + return true +end +function modifier_pohela_buff.prototype.RemoveOnDeath(self) + return true +end +function modifier_pohela_buff.prototype.DeclareFunctions(self) + return {MODIFIER_EVENT_ON_ATTACK_LANDED} +end +function modifier_pohela_buff.prototype.GetEffectName(self) + return "particles/units/heroes/hero_crystalmaiden/maiden_frostbite_buff.vpcf" +end +function modifier_pohela_buff.prototype.OnCreated(self) + self.parent = self:GetParent() + if not IsServer() then + return + end + setIncomingDamageReductionSource( + nil, + self.parent, + POHELA_BUFF_INCOMING_SOURCE, + function() + local ability = self:GetAbility() + if not ability then + return 0 + end + return math.max( + 0, + ability:GetSpecialValueFor("bonus_resistance") + ) + end + ) +end +function modifier_pohela_buff.prototype.OnDestroy(self) + if not IsServer() then + return + end + if self.parent then + removeIncomingDamageReductionSource(nil, self.parent, POHELA_BUFF_INCOMING_SOURCE) + end +end +function modifier_pohela_buff.prototype.OnAttackLanded(self, event) + if not IsServer() then + return + end + if not self.parent or event.target ~= self.parent or event.attacker == self.parent then + return + end + local ability = self:GetAbility() + if not ability then + return + end + event.attacker:AddNewModifier( + self.parent, + ability, + ____exports.modifier_pohela_debuff.name, + {duration = ability:GetSpecialValueFor("debuff_duration")} + ) +end +modifier_pohela_buff = __TS__Decorate( + modifier_pohela_buff, + modifier_pohela_buff, + {registerModifier(nil)}, + {kind = "class", name = "modifier_pohela_buff"} +) +____exports.modifier_pohela_buff = modifier_pohela_buff +____exports.modifier_pohela_debuff = __TS__Class() +local modifier_pohela_debuff = ____exports.modifier_pohela_debuff +modifier_pohela_debuff.name = "modifier_pohela_debuff" +modifier_pohela_debuff.____file_path = "scripts/vscripts/abilities/heroes/yuki-onna/ability_yuki_pohela.lua" +__TS__ClassExtends(modifier_pohela_debuff, BaseModifier) +function modifier_pohela_debuff.prototype.IsHidden(self) + return false +end +function modifier_pohela_debuff.prototype.IsPurgable(self) + return true +end +function modifier_pohela_debuff.prototype.IsDebuff(self) + return true +end +function modifier_pohela_debuff.prototype.IsBuff(self) + return false +end +function modifier_pohela_debuff.prototype.RemoveOnDeath(self) + return true +end +function modifier_pohela_debuff.prototype.DeclareFunctions(self) + return {MODIFIER_PROPERTY_ATTACKSPEED_BONUS_CONSTANT} +end +function modifier_pohela_debuff.prototype.GetEffectName(self) + return "particles/generic_gameplay/generic_slowed_cold.vpcf" +end +function modifier_pohela_debuff.prototype.GetModifierAttackSpeedBonus_Constant(self) + local ability = self:GetAbility() + if not ability then + return 0 + end + return -ability:GetSpecialValueFor("reduce_atack_speed") +end +modifier_pohela_debuff = __TS__Decorate( + modifier_pohela_debuff, + modifier_pohela_debuff, + {registerModifier(nil)}, + {kind = "class", name = "modifier_pohela_debuff"} +) +____exports.modifier_pohela_debuff = modifier_pohela_debuff +return ____exports diff --git a/scripts/vscripts/abilities/heroes/yuki-onna/ability_yuki_revenge.lua b/scripts/vscripts/abilities/heroes/yuki-onna/ability_yuki_revenge.lua new file mode 100644 index 0000000..a3bf802 --- /dev/null +++ b/scripts/vscripts/abilities/heroes/yuki-onna/ability_yuki_revenge.lua @@ -0,0 +1,201 @@ +local ____lualib = require("lualib_bundle") +local __TS__Class = ____lualib.__TS__Class +local __TS__ClassExtends = ____lualib.__TS__ClassExtends +local __TS__Decorate = ____lualib.__TS__Decorate +local ____exports = {} +local ____dota_ts_adapter = require("lib.dota_ts_adapter") +local BaseAbility = ____dota_ts_adapter.BaseAbility +local BaseModifier = ____dota_ts_adapter.BaseModifier +local registerAbility = ____dota_ts_adapter.registerAbility +local registerModifier = ____dota_ts_adapter.registerModifier +____exports.ability_yuki_revenge = __TS__Class() +local ability_yuki_revenge = ____exports.ability_yuki_revenge +ability_yuki_revenge.name = "ability_yuki_revenge" +ability_yuki_revenge.____file_path = "scripts/vscripts/abilities/heroes/yuki-onna/ability_yuki_revenge.lua" +__TS__ClassExtends(ability_yuki_revenge, BaseAbility) +function ability_yuki_revenge.prototype.GetIntrinsicModifierName(self) + return ____exports.modifier_revenge_aura.name +end +ability_yuki_revenge = __TS__Decorate( + ability_yuki_revenge, + ability_yuki_revenge, + {registerAbility(nil)}, + {kind = "class", name = "ability_yuki_revenge"} +) +____exports.ability_yuki_revenge = ability_yuki_revenge +____exports.modifier_revenge_aura = __TS__Class() +local modifier_revenge_aura = ____exports.modifier_revenge_aura +modifier_revenge_aura.name = "modifier_revenge_aura" +modifier_revenge_aura.____file_path = "scripts/vscripts/abilities/heroes/yuki-onna/ability_yuki_revenge.lua" +__TS__ClassExtends(modifier_revenge_aura, BaseModifier) +function modifier_revenge_aura.prototype.____constructor(self, ...) + BaseModifier.prototype.____constructor(self, ...) + self.radius = 0 +end +function modifier_revenge_aura.prototype.IsHidden(self) + return true +end +function modifier_revenge_aura.prototype.IsPurgable(self) + return false +end +function modifier_revenge_aura.prototype.IsDebuff(self) + return false +end +function modifier_revenge_aura.prototype.IsBuff(self) + return true +end +function modifier_revenge_aura.prototype.RemoveOnDeath(self) + return true +end +function modifier_revenge_aura.prototype.OnCreated(self) + if not IsServer() then + return + end + local particleCast = "particles/units/heroes/hero_witchdoctor/witchdoctor_voodoo_restoration.vpcf" + local ability = self:GetAbility() + if not ability then + return + end + self.radius = ability:GetSpecialValueFor("radius") + local parent = self:GetParent() + if self.effectCast ~= nil then + ParticleManager:DestroyParticle(self.effectCast, false) + ParticleManager:ReleaseParticleIndex(self.effectCast) + end + self.effectCast = ParticleManager:CreateParticle(particleCast, PATTACH_ABSORIGIN_FOLLOW, parent) + ParticleManager:SetParticleControl( + self.effectCast, + 1, + Vector(self.radius, 0, 0) + ) +end +function modifier_revenge_aura.prototype.OnRefresh(self) + self:OnCreated() +end +function modifier_revenge_aura.prototype.IsAura(self) + return true +end +function modifier_revenge_aura.prototype.GetModifierAura(self) + return ____exports.modifier_revenge_buff.name +end +function modifier_revenge_aura.prototype.GetAuraRadius(self) + return self.radius +end +function modifier_revenge_aura.prototype.GetAuraDuration(self) + return 0.1 +end +function modifier_revenge_aura.prototype.GetAuraSearchTeam(self) + return DOTA_UNIT_TARGET_TEAM_BOTH +end +function modifier_revenge_aura.prototype.GetAuraSearchType(self) + return DOTA_UNIT_TARGET_ALL +end +function modifier_revenge_aura.prototype.GetAuraSearchFlags(self) + local ability = self:GetAbility() + if not ability then + return DOTA_UNIT_TARGET_FLAG_NONE + end + return ability:GetAbilityTargetFlags() +end +function modifier_revenge_aura.prototype.GetAuraEntityReject(self, _target) + return self:GetParent():PassivesDisabled() +end +modifier_revenge_aura = __TS__Decorate( + modifier_revenge_aura, + modifier_revenge_aura, + {registerModifier(nil)}, + {kind = "class", name = "modifier_revenge_aura"} +) +____exports.modifier_revenge_aura = modifier_revenge_aura +____exports.modifier_revenge_buff = __TS__Class() +local modifier_revenge_buff = ____exports.modifier_revenge_buff +modifier_revenge_buff.name = "modifier_revenge_buff" +modifier_revenge_buff.____file_path = "scripts/vscripts/abilities/heroes/yuki-onna/ability_yuki_revenge.lua" +__TS__ClassExtends(modifier_revenge_buff, BaseModifier) +function modifier_revenge_buff.prototype.____constructor(self, ...) + BaseModifier.prototype.____constructor(self, ...) + self.intThink = 0 +end +function modifier_revenge_buff.prototype.IsHidden(self) + return false +end +function modifier_revenge_buff.prototype.IsPurgable(self) + return false +end +function modifier_revenge_buff.prototype.IsDebuff(self) + return false +end +function modifier_revenge_buff.prototype.IsBuff(self) + return true +end +function modifier_revenge_buff.prototype.RemoveOnDeath(self) + return true +end +function modifier_revenge_buff.prototype.OnCreated(self) + if not IsServer() then + return + end + local ability = self:GetAbility() + if not ability then + return + end + self.intThink = ability:GetSpecialValueFor("int_think") + self:StartIntervalThink(self.intThink) +end +function modifier_revenge_buff.prototype.OnRefresh(self) + self:OnCreated() +end +function modifier_revenge_buff.prototype.OnIntervalThink(self) + if not IsServer() then + return + end + local ability = self:GetAbility() + local caster = self:GetCaster() + local parent = self:GetParent() + if not ability or not caster or not parent then + return + end + if caster:PassivesDisabled() then + return + end + local baseDamage = ability:GetSpecialValueFor("base_damage") + local manaDamagePct = ability:GetSpecialValueFor("mana_damage_pct") + local heal = ability:GetSpecialValueFor("heal") + if caster:IsRealHero() then + local hero = caster + local damage = hero:GetMaxMana() * (manaDamagePct / 100) + baseDamage + if parent:GetTeamNumber() ~= caster:GetTeamNumber() then + ApplyDamage({ + victim = parent, + attacker = caster, + damage = damage, + damage_type = ability:GetAbilityDamageType(), + ability = ability + }) + SendOverheadEventMessage( + nil, + OVERHEAD_ALERT_BONUS_SPELL_DAMAGE, + parent, + damage, + nil + ) + else + parent:Heal(heal, ability) + SendOverheadEventMessage( + nil, + OVERHEAD_ALERT_HEAL, + parent, + heal, + nil + ) + end + end +end +modifier_revenge_buff = __TS__Decorate( + modifier_revenge_buff, + modifier_revenge_buff, + {registerModifier(nil)}, + {kind = "class", name = "modifier_revenge_buff"} +) +____exports.modifier_revenge_buff = modifier_revenge_buff +return ____exports diff --git a/scripts/vscripts/abilities/heroes/yuki-onna/ability_yuki_ritual.lua b/scripts/vscripts/abilities/heroes/yuki-onna/ability_yuki_ritual.lua new file mode 100644 index 0000000..35e90e0 --- /dev/null +++ b/scripts/vscripts/abilities/heroes/yuki-onna/ability_yuki_ritual.lua @@ -0,0 +1,207 @@ +local ____lualib = require("lualib_bundle") +local __TS__Class = ____lualib.__TS__Class +local __TS__ClassExtends = ____lualib.__TS__ClassExtends +local __TS__Decorate = ____lualib.__TS__Decorate +local ____exports = {} +local ____dota_ts_adapter = require("lib.dota_ts_adapter") +local BaseAbility = ____dota_ts_adapter.BaseAbility +local BaseModifier = ____dota_ts_adapter.BaseModifier +local registerAbility = ____dota_ts_adapter.registerAbility +local registerModifier = ____dota_ts_adapter.registerModifier +____exports.ability_yuki_ritual = __TS__Class() +local ability_yuki_ritual = ____exports.ability_yuki_ritual +ability_yuki_ritual.name = "ability_yuki_ritual" +ability_yuki_ritual.____file_path = "scripts/vscripts/abilities/heroes/yuki-onna/ability_yuki_ritual.lua" +__TS__ClassExtends(ability_yuki_ritual, BaseAbility) +function ability_yuki_ritual.prototype.OnAbilityPhaseStart(self) + local particleCast = "particles/econ/items/crystal_maiden/crystal_maiden_maiden_of_icewrack/maiden_freezing_field_snow_arcana1.vpcf" + local caster = self:GetCaster() + local radius = self:GetCastRange( + caster:GetAbsOrigin(), + caster + ) + self.effectCast = ParticleManager:CreateParticle(particleCast, PATTACH_ABSORIGIN_FOLLOW, caster) + ParticleManager:SetParticleControl( + self.effectCast, + 0, + caster:GetAbsOrigin() + ) + ParticleManager:SetParticleControl( + self.effectCast, + 2, + Vector(radius, 0, 0) + ) + EmitSoundOn("hero_Crystal.freezingField.wind", caster) + return true +end +function ability_yuki_ritual.prototype.OnAbilityPhaseInterrupted(self) + local caster = self:GetCaster() + if self.effectCast ~= nil then + ParticleManager:DestroyParticle(self.effectCast, false) + ParticleManager:ReleaseParticleIndex(self.effectCast) + end + StopSoundOn("hero_Crystal.freezingField.wind", caster) +end +function ability_yuki_ritual.prototype.OnSpellStart(self) + local caster = self:GetCaster() + local radius = self:GetCastRange( + caster:GetAbsOrigin(), + caster + ) + local units = FindUnitsInRadius( + caster:GetTeamNumber(), + caster:GetAbsOrigin(), + nil, + radius, + DOTA_UNIT_TARGET_TEAM_BOTH, + bit.bor(DOTA_UNIT_TARGET_HERO, DOTA_UNIT_TARGET_BASIC), + DOTA_UNIT_TARGET_FLAG_MAGIC_IMMUNE_ENEMIES, + FIND_ANY_ORDER, + false + ) + for ____, unit in ipairs(units) do + if unit ~= nil and unit ~= nil then + unit:AddNewModifier( + caster, + self, + ____exports.modifier_ritual_buff.name, + {duration = self:GetSpecialValueFor("duration")} + ) + end + end + if self.effectCast ~= nil then + ParticleManager:DestroyParticle(self.effectCast, false) + ParticleManager:ReleaseParticleIndex(self.effectCast) + end + StopSoundOn("hero_Crystal.freezingField.wind", caster) +end +ability_yuki_ritual = __TS__Decorate( + ability_yuki_ritual, + ability_yuki_ritual, + {registerAbility(nil)}, + {kind = "class", name = "ability_yuki_ritual"} +) +____exports.ability_yuki_ritual = ability_yuki_ritual +____exports.modifier_ritual_buff = __TS__Class() +local modifier_ritual_buff = ____exports.modifier_ritual_buff +modifier_ritual_buff.name = "modifier_ritual_buff" +modifier_ritual_buff.____file_path = "scripts/vscripts/abilities/heroes/yuki-onna/ability_yuki_ritual.lua" +__TS__ClassExtends(modifier_ritual_buff, BaseModifier) +function modifier_ritual_buff.prototype.____constructor(self, ...) + BaseModifier.prototype.____constructor(self, ...) + self.armor = 0 + self.hpRegen = 0 + self.hpRegenPct = 0 + self.ms = 0 + self.as = 0 +end +function modifier_ritual_buff.prototype.IsHidden(self) + return false +end +function modifier_ritual_buff.prototype.IsPurgable(self) + return false +end +function modifier_ritual_buff.prototype.IsDebuff(self) + return false +end +function modifier_ritual_buff.prototype.IsBuff(self) + return true +end +function modifier_ritual_buff.prototype.RemoveOnDeath(self) + return true +end +function modifier_ritual_buff.prototype.DeclareFunctions(self) + return { + MODIFIER_PROPERTY_PHYSICAL_ARMOR_BONUS, + MODIFIER_PROPERTY_HP_REGEN_AMPLIFY_PERCENTAGE, + MODIFIER_PROPERTY_HEALTH_REGEN_PERCENTAGE, + MODIFIER_PROPERTY_MOVESPEED_BONUS_CONSTANT, + MODIFIER_PROPERTY_ATTACKSPEED_BONUS_CONSTANT + } +end +function modifier_ritual_buff.prototype.GetEffectName(self) + return "particles/econ/items/crystal_maiden/ti9_immortal_staff/cm_ti9_staff_lvlup_globe.vpcf" +end +function modifier_ritual_buff.prototype.OnCreated(self) + local ability = self:GetAbility() + if not ability then + return + end + local parent = self:GetParent() + self.armor = ability:GetSpecialValueFor("bonus_armor") / 100 * parent:GetPhysicalArmorBaseValue() + self.hpRegen = ability:GetSpecialValueFor("bonus_hp_regen") + self.hpRegenPct = ability:GetSpecialValueFor("bonus_hp_regen_pct") + self.ms = ability:GetSpecialValueFor("reduce_ms") + self.as = -(ability:GetSpecialValueFor("reduce_as") / 100 * parent:GetAttackSpeed(true)) * 100 +end +function modifier_ritual_buff.prototype.OnRefresh(self) + self:OnCreated() +end +function modifier_ritual_buff.prototype.GetModifierPhysicalArmorBonus(self) + local parent = self:GetParent() + local caster = self:GetCaster() + if not parent or not caster then + return 0 + end + if parent:GetTeamNumber() ~= caster:GetTeamNumber() then + return 0 + else + return self.armor + end +end +function modifier_ritual_buff.prototype.GetModifierHPRegenAmplify_Percentage(self) + local parent = self:GetParent() + local caster = self:GetCaster() + if not parent or not caster then + return 0 + end + if parent:GetTeamNumber() ~= caster:GetTeamNumber() then + return 0 + else + return self.hpRegen + end +end +function modifier_ritual_buff.prototype.GetModifierHealthRegenPercentage(self) + local parent = self:GetParent() + local caster = self:GetCaster() + if not parent or not caster then + return 0 + end + if parent:GetTeamNumber() ~= caster:GetTeamNumber() then + return 0 + else + return self.hpRegenPct + end +end +function modifier_ritual_buff.prototype.GetModifierMoveSpeedBonus_Constant(self) + local parent = self:GetParent() + local caster = self:GetCaster() + if not parent or not caster then + return 0 + end + if parent:GetTeamNumber() ~= caster:GetTeamNumber() then + return -self.ms + else + return 0 + end +end +function modifier_ritual_buff.prototype.GetModifierAttackSpeedBonus_Constant(self) + local parent = self:GetParent() + local caster = self:GetCaster() + if not parent or not caster then + return 0 + end + if parent:GetTeamNumber() ~= caster:GetTeamNumber() then + return self.as + else + return 0 + end +end +modifier_ritual_buff = __TS__Decorate( + modifier_ritual_buff, + modifier_ritual_buff, + {registerModifier(nil)}, + {kind = "class", name = "modifier_ritual_buff"} +) +____exports.modifier_ritual_buff = modifier_ritual_buff +return ____exports diff --git a/scripts/vscripts/abilities/heroes/yuki-onna/ability_yuki_snowball.lua b/scripts/vscripts/abilities/heroes/yuki-onna/ability_yuki_snowball.lua new file mode 100644 index 0000000..3965e5d --- /dev/null +++ b/scripts/vscripts/abilities/heroes/yuki-onna/ability_yuki_snowball.lua @@ -0,0 +1,158 @@ +local ____lualib = require("lualib_bundle") +local __TS__Class = ____lualib.__TS__Class +local __TS__ClassExtends = ____lualib.__TS__ClassExtends +local __TS__Decorate = ____lualib.__TS__Decorate +local ____exports = {} +local ____dota_ts_adapter = require("lib.dota_ts_adapter") +local BaseAbility = ____dota_ts_adapter.BaseAbility +local BaseModifier = ____dota_ts_adapter.BaseModifier +local registerAbility = ____dota_ts_adapter.registerAbility +local registerModifier = ____dota_ts_adapter.registerModifier +local ____heal_tracker = require("utils.heal_tracker") +local HealWithBattlePass = ____heal_tracker.HealWithBattlePass +____exports.ability_yuki_snowball = __TS__Class() +local ability_yuki_snowball = ____exports.ability_yuki_snowball +ability_yuki_snowball.name = "ability_yuki_snowball" +ability_yuki_snowball.____file_path = "scripts/vscripts/abilities/heroes/yuki-onna/ability_yuki_snowball.lua" +__TS__ClassExtends(ability_yuki_snowball, BaseAbility) +function ability_yuki_snowball.prototype.GetCastPoint(self) + return 0 +end +function ability_yuki_snowball.prototype.GetBackswingTime(self) + return 0 +end +function ability_yuki_snowball.prototype.Precache(self, context) + PrecacheResource("particle", "particles/econ/events/snowball/snowball_projectile.vpcf", context) +end +function ability_yuki_snowball.prototype.OnSpellStart(self) + if not IsServer() then + return + end + local target = self:GetCursorTarget() + local caster = self:GetCaster() + if not target then + return + end + local info = { + Target = target, + Source = caster, + Ability = self, + EffectName = "particles/econ/events/snowball/snowball_projectile.vpcf", + bDodgeable = true, + bProvidesVision = true, + iMoveSpeed = 1600, + iVisionRadius = 450, + iVisionTeamNumber = caster:GetTeamNumber(), + iSourceAttachment = DOTA_PROJECTILE_ATTACHMENT_ATTACK_2 + } + ProjectileManager:CreateTrackingProjectile(info) + EmitSoundOn("FrostivusConsumable.Snowball.Target", caster) +end +function ability_yuki_snowball.prototype.OnProjectileHit(self, target, location) + if not IsServer() then + return false + end + local caster = self:GetCaster() + local ability = self + if not ability then + return false + end + local radius = ability:GetSpecialValueFor("radius") + local heal = ability:GetSpecialValueFor("heal") + if target and not target:IsInvulnerable() then + if target:TriggerSpellAbsorb(ability) then + return false + end + local units = FindUnitsInRadius( + caster:GetTeamNumber(), + target:GetAbsOrigin(), + nil, + radius, + DOTA_UNIT_TARGET_TEAM_BOTH, + bit.bor(DOTA_UNIT_TARGET_HERO, DOTA_UNIT_TARGET_BASIC), + DOTA_UNIT_TARGET_FLAG_INVULNERABLE, + FIND_CLOSEST, + false + ) + for ____, unit in ipairs(units) do + do + if not unit then + goto __continue13 + end + if unit:GetTeamNumber() == caster:GetTeamNumber() then + HealWithBattlePass( + nil, + unit, + heal, + ability, + caster + ) + SendOverheadEventMessage( + nil, + OVERHEAD_ALERT_HEAL, + unit, + heal, + nil + ) + else + unit:AddNewModifier( + caster, + ability, + ____exports.modifier_snowball_debuff.name, + {duration = ability:GetSpecialValueFor("duration")} + ) + end + end + ::__continue13:: + end + end + return true +end +ability_yuki_snowball = __TS__Decorate( + ability_yuki_snowball, + ability_yuki_snowball, + {registerAbility(nil)}, + {kind = "class", name = "ability_yuki_snowball"} +) +____exports.ability_yuki_snowball = ability_yuki_snowball +____exports.modifier_snowball_debuff = __TS__Class() +local modifier_snowball_debuff = ____exports.modifier_snowball_debuff +modifier_snowball_debuff.name = "modifier_snowball_debuff" +modifier_snowball_debuff.____file_path = "scripts/vscripts/abilities/heroes/yuki-onna/ability_yuki_snowball.lua" +__TS__ClassExtends(modifier_snowball_debuff, BaseModifier) +function modifier_snowball_debuff.prototype.IsHidden(self) + return false +end +function modifier_snowball_debuff.prototype.IsPurgable(self) + return true +end +function modifier_snowball_debuff.prototype.IsDebuff(self) + return true +end +function modifier_snowball_debuff.prototype.IsBuff(self) + return false +end +function modifier_snowball_debuff.prototype.RemoveOnDeath(self) + return true +end +function modifier_snowball_debuff.prototype.DeclareFunctions(self) + return {MODIFIER_PROPERTY_ATTACKSPEED_BONUS_CONSTANT} +end +function modifier_snowball_debuff.prototype.GetModifierAttackSpeedBonus_Constant(self) + local ability = self:GetAbility() + if not ability then + return 0 + end + return ability:GetSpecialValueFor("slow_as") +end +function modifier_snowball_debuff.prototype.GetEffectName(self) + return "particles/units/heroes/hero_ancient_apparition/ancient_apparition_ice_blast_debuff.vpcf" +end +modifier_snowball_debuff = __TS__Decorate( + modifier_snowball_debuff, + modifier_snowball_debuff, + {registerModifier(nil)}, + {kind = "class", name = "modifier_snowball_debuff"} +) +____exports.modifier_snowball_debuff = modifier_snowball_debuff +return ____exports diff --git a/scripts/vscripts/abilities/heroes/yuki-onna/ability_yuki_snowman.lua b/scripts/vscripts/abilities/heroes/yuki-onna/ability_yuki_snowman.lua new file mode 100644 index 0000000..e1ab59f --- /dev/null +++ b/scripts/vscripts/abilities/heroes/yuki-onna/ability_yuki_snowman.lua @@ -0,0 +1,73 @@ +local ____lualib = require("lualib_bundle") +local __TS__Class = ____lualib.__TS__Class +local __TS__ClassExtends = ____lualib.__TS__ClassExtends +local __TS__ArraySplice = ____lualib.__TS__ArraySplice +local __TS__Decorate = ____lualib.__TS__Decorate +local ____exports = {} +local ____dota_ts_adapter = require("lib.dota_ts_adapter") +local BaseAbility = ____dota_ts_adapter.BaseAbility +local registerAbility = ____dota_ts_adapter.registerAbility +____exports.ability_yuki_snowman = __TS__Class() +local ability_yuki_snowman = ____exports.ability_yuki_snowman +ability_yuki_snowman.name = "ability_yuki_snowman" +ability_yuki_snowman.____file_path = "scripts/vscripts/abilities/heroes/yuki-onna/ability_yuki_snowman.lua" +__TS__ClassExtends(ability_yuki_snowman, BaseAbility) +function ability_yuki_snowman.prototype.OnSpellStart(self) + if not IsServer() then + return + end + local target = self:GetCursorPosition() + local caster = self:GetCaster() + local bonusHealth = self:GetSpecialValueFor("bonus_health") + local maxSnowman = self:GetSpecialValueFor("max_snowman") + if not caster.snowman then + caster.snowman = {} + else + local snowmanArray = caster.snowman + do + local i = #snowmanArray - 1 + while i >= 0 do + local man = snowmanArray[i + 1] + if not man or not IsValidEntity(man) or not man:IsAlive() then + __TS__ArraySplice(snowmanArray, i, 1) + end + i = i - 1 + end + end + end + local snowmanArray = caster.snowman + if #snowmanArray >= maxSnowman then + local firstSnowman = snowmanArray[1] + if firstSnowman and IsValidEntity(firstSnowman) then + firstSnowman:Destroy() + end + table.remove(snowmanArray, 1) + end + local unit = CreateUnitByName( + "npc_classic_snowman", + target, + true, + nil, + nil, + DOTA_TEAM_GOODGUYS + ) + if not unit then + return + end + snowmanArray[#snowmanArray + 1] = unit + local snowballAbility = unit:FindAbilityByName("ability_yuki_snowball") + if snowballAbility then + snowballAbility:SetLevel(self:GetLevel()) + end + unit:SetOwner(caster) + FindClearSpaceForUnit(unit, target, true) + unit:SetBaseMaxHealth(bonusHealth) +end +ability_yuki_snowman = __TS__Decorate( + ability_yuki_snowman, + ability_yuki_snowman, + {registerAbility(nil)}, + {kind = "class", name = "ability_yuki_snowman"} +) +____exports.ability_yuki_snowman = ability_yuki_snowman +return ____exports diff --git a/scripts/vscripts/abilities/invul_box.lua b/scripts/vscripts/abilities/invul_box.lua new file mode 100644 index 0000000..3ca2721 --- /dev/null +++ b/scripts/vscripts/abilities/invul_box.lua @@ -0,0 +1,218 @@ +local ____lualib = require("lualib_bundle") +local __TS__Class = ____lualib.__TS__Class +local __TS__ClassExtends = ____lualib.__TS__ClassExtends +local __TS__Decorate = ____lualib.__TS__Decorate +local ____exports = {} +local ____dota_ts_adapter = require("lib.dota_ts_adapter") +local BaseAbility = ____dota_ts_adapter.BaseAbility +local BaseModifier = ____dota_ts_adapter.BaseModifier +local registerAbility = ____dota_ts_adapter.registerAbility +local registerModifier = ____dota_ts_adapter.registerModifier +____exports.invul_box = __TS__Class() +local invul_box = ____exports.invul_box +invul_box.name = "invul_box" +invul_box.____file_path = "scripts/vscripts/abilities/invul_box.lua" +__TS__ClassExtends(invul_box, BaseAbility) +function invul_box.prototype.GetIntrinsicModifierName(self) + return "modifier_invul_box" +end +invul_box = __TS__Decorate( + invul_box, + invul_box, + {registerAbility(nil)}, + {kind = "class", name = "invul_box"} +) +____exports.invul_box = invul_box +____exports.modifier_invul_box = __TS__Class() +local modifier_invul_box = ____exports.modifier_invul_box +modifier_invul_box.name = "modifier_invul_box" +modifier_invul_box.____file_path = "scripts/vscripts/abilities/invul_box.lua" +__TS__ClassExtends(modifier_invul_box, BaseModifier) +function modifier_invul_box.prototype.IsHidden(self) + return true +end +function modifier_invul_box.prototype.IsPurgable(self) + return false +end +function modifier_invul_box.prototype.IsDebuff(self) + return false +end +function modifier_invul_box.prototype.CheckState(self) + return {[MODIFIER_STATE_NO_HEALTH_BAR] = true, [MODIFIER_STATE_UNSELECTABLE] = false, [MODIFIER_STATE_SPECIALLY_DENIABLE] = true} +end +function modifier_invul_box.prototype.DeclareFunctions(self) + return {MODIFIER_PROPERTY_MIN_HEALTH, MODIFIER_EVENT_ON_ATTACKED} +end +function modifier_invul_box.prototype.GetMinHealth(self) + return 1 +end +function modifier_invul_box.prototype.OnCreated(self) + if IsServer() then + self:StartIntervalThink(0.1) + end +end +function modifier_invul_box.prototype.OnIntervalThink(self) + if IsServer() then + self:CheckForNearbyHeroes() + end +end +function modifier_invul_box.prototype.CheckForNearbyHeroes(self) + local target = self:GetParent() + local nearbyHeroes = FindUnitsInRadius( + target:GetTeamNumber(), + target:GetAbsOrigin(), + nil, + 40, + DOTA_UNIT_TARGET_TEAM_ENEMY, + DOTA_UNIT_TARGET_HERO, + DOTA_UNIT_TARGET_FLAG_NONE, + FIND_ANY_ORDER, + false + ) + for ____, hero in ipairs(nearbyHeroes) do + if hero and hero:IsAlive() then + local direction = target:GetAbsOrigin() - hero:GetAbsOrigin() + direction.z = 0 + local normalizedDirection = direction:Normalized() + self:StartKnockback(target, normalizedDirection, 30, 0.2) + self:CheckForChainKnockback( + target, + target:GetAbsOrigin(), + 15, + 0.2 + ) + end + end +end +function modifier_invul_box.prototype.OnAttacked(self, event) + if event.target ~= self:GetParent() then + return + end + local attacker = event.attacker + local target = event.target + if not attacker or not target or attacker == target then + return + end + local direction = target:GetAbsOrigin() - attacker:GetAbsOrigin() + direction.z = 0 + local normalizedDirection = direction:Normalized() + local knockbackForce = 50 + local knockbackDuration = 0.3 + local startPosition = target:GetAbsOrigin() + local startTime = GameRules:GetGameTime() + local totalDuration = knockbackDuration + local knockbackTimer = Timers:CreateTimer( + 0.01, + function() + local currentTime = GameRules:GetGameTime() + local elapsed = currentTime - startTime + local progress = elapsed / totalDuration + if progress >= 1 then + return + end + local easeOutProgress = 1 - (1 - progress) ^ 3 + local currentDistance = knockbackForce * easeOutProgress + local newPosition = Vector(startPosition.x + normalizedDirection.x * currentDistance, startPosition.y + normalizedDirection.y * currentDistance, startPosition.z) + local groundPosition = GetGroundPosition(newPosition, target) + local finalPosition = Vector(newPosition.x, newPosition.y, groundPosition.z) + local oldPosition = target:GetAbsOrigin() + target:SetAbsOrigin(finalPosition) + local currentPos = target:GetAbsOrigin() + local distanceFromTarget = currentPos - finalPosition + local distance = distanceFromTarget:Length2D() + if distance > 10 then + target:SetAbsOrigin(oldPosition) + local foundSafePosition = false + local searchRadius = 50 + do + local angle = 0 + while angle < 360 do + local testDirection = Vector( + math.cos(angle * math.pi / 180), + math.sin(angle * math.pi / 180), + 0 + ) + local testPosition = Vector(startPosition.x + testDirection.x * currentDistance * 0.5, startPosition.y + testDirection.y * currentDistance * 0.5, startPosition.z) + local testGroundPosition = GetGroundPosition(testPosition, target) + local testFinalPosition = Vector(testPosition.x, testPosition.y, testGroundPosition.z) + local testOldPos = target:GetAbsOrigin() + target:SetAbsOrigin(testFinalPosition) + local testCurrentPos = target:GetAbsOrigin() + local testDistanceVector = testCurrentPos - testFinalPosition + local testDistance = testDistanceVector:Length2D() + if testDistance <= 10 then + foundSafePosition = true + break + else + target:SetAbsOrigin(testOldPos) + end + angle = angle + 45 + end + end + if not foundSafePosition then + return + end + else + self:CheckForChainKnockback(target, finalPosition, knockbackForce * 0.5, knockbackDuration) + end + return 0.01 + end + ) +end +function modifier_invul_box.prototype.CheckForChainKnockback(self, originalTarget, position, chainForce, chainDuration) + local nearbyUnits = FindUnitsInRadius( + originalTarget:GetTeamNumber(), + position, + nil, + 100, + DOTA_UNIT_TARGET_TEAM_BOTH, + DOTA_UNIT_TARGET_ALL, + DOTA_UNIT_TARGET_FLAG_NONE, + FIND_ANY_ORDER, + false + ) + for ____, unit in ipairs(nearbyUnits) do + do + if unit == originalTarget then + goto __continue29 + end + if unit:HasModifier("modifier_invul_box") then + local direction = unit:GetAbsOrigin() - position + direction.z = 0 + local normalizedDirection = direction:Normalized() + self:StartKnockback(unit, normalizedDirection, chainForce, chainDuration) + end + end + ::__continue29:: + end +end +function modifier_invul_box.prototype.StartKnockback(self, target, direction, force, duration) + local startPosition = target:GetAbsOrigin() + local startTime = GameRules:GetGameTime() + local knockbackTimer = Timers:CreateTimer( + 0.01, + function() + local currentTime = GameRules:GetGameTime() + local elapsed = currentTime - startTime + local progress = elapsed / duration + if progress >= 1 then + return + end + local easeOutProgress = 1 - (1 - progress) ^ 3 + local currentDistance = force * easeOutProgress + local newPosition = Vector(startPosition.x + direction.x * currentDistance, startPosition.y + direction.y * currentDistance, startPosition.z) + local groundPosition = GetGroundPosition(newPosition, target) + local finalPosition = Vector(newPosition.x, newPosition.y, groundPosition.z) + target:SetAbsOrigin(finalPosition) + return 0.01 + end + ) +end +modifier_invul_box = __TS__Decorate( + modifier_invul_box, + modifier_invul_box, + {registerModifier(nil)}, + {kind = "class", name = "modifier_invul_box"} +) +____exports.modifier_invul_box = modifier_invul_box +return ____exports diff --git a/scripts/vscripts/abilities/invul_npc.lua b/scripts/vscripts/abilities/invul_npc.lua new file mode 100644 index 0000000..55a54af --- /dev/null +++ b/scripts/vscripts/abilities/invul_npc.lua @@ -0,0 +1,98 @@ +local ____lualib = require("lualib_bundle") +local __TS__Class = ____lualib.__TS__Class +local __TS__ClassExtends = ____lualib.__TS__ClassExtends +local __TS__Decorate = ____lualib.__TS__Decorate +local ____exports = {} +local ____dota_ts_adapter = require("lib.dota_ts_adapter") +local BaseAbility = ____dota_ts_adapter.BaseAbility +local BaseModifier = ____dota_ts_adapter.BaseModifier +local registerAbility = ____dota_ts_adapter.registerAbility +local registerModifier = ____dota_ts_adapter.registerModifier +____exports.invul_npc = __TS__Class() +local invul_npc = ____exports.invul_npc +invul_npc.name = "invul_npc" +invul_npc.____file_path = "scripts/vscripts/abilities/invul_npc.lua" +__TS__ClassExtends(invul_npc, BaseAbility) +function invul_npc.prototype.GetIntrinsicModifierName(self) + return "modifier_invul_npc" +end +invul_npc = __TS__Decorate( + invul_npc, + invul_npc, + {registerAbility(nil)}, + {kind = "class", name = "invul_npc"} +) +____exports.invul_npc = invul_npc +____exports.modifier_invul_npc = __TS__Class() +local modifier_invul_npc = ____exports.modifier_invul_npc +modifier_invul_npc.name = "modifier_invul_npc" +modifier_invul_npc.____file_path = "scripts/vscripts/abilities/invul_npc.lua" +__TS__ClassExtends(modifier_invul_npc, BaseModifier) +function modifier_invul_npc.prototype.IsHidden(self) + return true +end +function modifier_invul_npc.prototype.IsPurgable(self) + return false +end +function modifier_invul_npc.prototype.IsDebuff(self) + return false +end +function modifier_invul_npc.prototype.CheckState(self) + if self:GetParent():GetUnitName() == "npc_quest_giver_friend" or self:GetParent():GetUnitName() == "npc_mound" or self:GetParent():GetUnitName() == "npc_wisps" then + return {[MODIFIER_STATE_NO_HEALTH_BAR] = true, [MODIFIER_STATE_NOT_ON_MINIMAP] = true} + end + if self:GetParent():GetUnitName() == "npc_capture_point" or self:GetParent():GetUnitName() == "npc_teleport" then + return { + [MODIFIER_STATE_NO_HEALTH_BAR] = true, + [MODIFIER_STATE_NOT_ON_MINIMAP] = true, + [MODIFIER_STATE_INVULNERABLE] = true, + [MODIFIER_STATE_UNSELECTABLE] = true, + [MODIFIER_STATE_NO_UNIT_COLLISION] = true + } + end + if self:GetParent():GetUnitName() == "npc_dota_camera" then + return { + [MODIFIER_STATE_INVULNERABLE] = true, + [MODIFIER_STATE_NOT_ON_MINIMAP] = true, + [MODIFIER_STATE_UNSELECTABLE] = true, + [MODIFIER_STATE_MAGIC_IMMUNE] = true, + [MODIFIER_STATE_NO_UNIT_COLLISION] = true, + [MODIFIER_STATE_FLYING_FOR_PATHING_PURPOSES_ONLY] = true, + [MODIFIER_STATE_NO_HEALTH_BAR] = true + } + else + return {[MODIFIER_STATE_NO_HEALTH_BAR] = true, [MODIFIER_STATE_OUT_OF_GAME] = true, [MODIFIER_STATE_NOT_ON_MINIMAP] = true} + end +end +function modifier_invul_npc.prototype.DeclareFunctions(self) + return {MODIFIER_PROPERTY_MIN_HEALTH} +end +function modifier_invul_npc.prototype.GetEffectName(self) + local unitName = self:GetParent():GetUnitName() + if unitName == "npc_wisps" then + return "particles/units/heroes/hero_wisp/wisp_ambient.vpcf" + end + return "" +end +function modifier_invul_npc.prototype.GetEffectAttachType(self) + local unitName = self:GetParent():GetUnitName() + if unitName == "npc_wisps" then + return PATTACH_ROOTBONE_FOLLOW + end + return PATTACH_ABSORIGIN_FOLLOW +end +function modifier_invul_npc.prototype.GetMinHealth(self) + local unitName = self:GetParent():GetUnitName() + if unitName == "npc_wisps" or unitName == "npc_mound" or unitName == "npc_campfire" then + return 1 + end + return 0 +end +modifier_invul_npc = __TS__Decorate( + modifier_invul_npc, + modifier_invul_npc, + {registerModifier(nil)}, + {kind = "class", name = "modifier_invul_npc"} +) +____exports.modifier_invul_npc = modifier_invul_npc +return ____exports diff --git a/scripts/vscripts/abilities/modifiers/ability_effects.lua b/scripts/vscripts/abilities/modifiers/ability_effects.lua new file mode 100644 index 0000000..6f889cc --- /dev/null +++ b/scripts/vscripts/abilities/modifiers/ability_effects.lua @@ -0,0 +1,214 @@ +local ____lualib = require("lualib_bundle") +local __TS__Class = ____lualib.__TS__Class +local __TS__ClassExtends = ____lualib.__TS__ClassExtends +local __TS__Decorate = ____lualib.__TS__Decorate +local ____exports = {} +local ____dota_ts_adapter = require("lib.dota_ts_adapter") +local BaseAbility = ____dota_ts_adapter.BaseAbility +local BaseModifier = ____dota_ts_adapter.BaseModifier +local registerAbility = ____dota_ts_adapter.registerAbility +local registerModifier = ____dota_ts_adapter.registerModifier +require("utils.store_effects") +____exports.ability_effects = __TS__Class() +local ability_effects = ____exports.ability_effects +ability_effects.name = "ability_effects" +ability_effects.____file_path = "scripts/vscripts/abilities/modifiers/ability_effects.lua" +__TS__ClassExtends(ability_effects, BaseAbility) +function ability_effects.prototype.GetIntrinsicModifierName(self) + return "modifier_effect_manager" +end +function ability_effects.prototype.IsHidden(self) + return false +end +ability_effects = __TS__Decorate( + ability_effects, + ability_effects, + {registerAbility(nil)}, + {kind = "class", name = "ability_effects"} +) +____exports.ability_effects = ability_effects +____exports.modifier_effect_manager = __TS__Class() +local modifier_effect_manager = ____exports.modifier_effect_manager +modifier_effect_manager.name = "modifier_effect_manager" +modifier_effect_manager.____file_path = "scripts/vscripts/abilities/modifiers/ability_effects.lua" +__TS__ClassExtends(modifier_effect_manager, BaseModifier) +function modifier_effect_manager.prototype.IsHidden(self) + return true +end +function modifier_effect_manager.prototype.IsPurgable(self) + return false +end +function modifier_effect_manager.prototype.OnCreated(self, params) + if not IsServer() then + return + end + local parent = self:GetParent() + if not parent or parent:IsNull() or not IsValidEntity(parent) then + return + end + local hero = parent + if not hero or not hero:IsRealHero() then + return + end + local wings = GetEffect(nil, hero, "wings") + if wings then + hero:AddNewModifier( + hero, + self:GetAbility(), + ____exports.modifier_effects_wings.name, + {} + ) + end + local effect = GetEffect(nil, hero, "effect") + if effect then + hero:AddNewModifier( + hero, + self:GetAbility(), + ____exports.modifier_effects_effect.name, + {} + ) + end + local model = GetEffect(nil, hero, "model") + if model then + hero:AddNewModifier( + hero, + self:GetAbility(), + ____exports.modifier_effects_model.name, + {} + ) + end +end +modifier_effect_manager = __TS__Decorate( + modifier_effect_manager, + modifier_effect_manager, + {registerModifier(nil)}, + {kind = "class", name = "modifier_effect_manager"} +) +____exports.modifier_effect_manager = modifier_effect_manager +____exports.modifier_effects_wings = __TS__Class() +local modifier_effects_wings = ____exports.modifier_effects_wings +modifier_effects_wings.name = "modifier_effects_wings" +modifier_effects_wings.____file_path = "scripts/vscripts/abilities/modifiers/ability_effects.lua" +__TS__ClassExtends(modifier_effects_wings, BaseModifier) +function modifier_effects_wings.prototype.IsHidden(self) + return true +end +function modifier_effects_wings.prototype.IsPurgable(self) + return false +end +function modifier_effects_wings.prototype.RemoveOnDeath(self) + return false +end +function modifier_effects_wings.prototype.GetEffectAttachType(self) + return PATTACH_ABSORIGIN_FOLLOW +end +modifier_effects_wings = __TS__Decorate( + modifier_effects_wings, + modifier_effects_wings, + {registerModifier(nil)}, + {kind = "class", name = "modifier_effects_wings"} +) +____exports.modifier_effects_wings = modifier_effects_wings +____exports.modifier_effects_effect = __TS__Class() +local modifier_effects_effect = ____exports.modifier_effects_effect +modifier_effects_effect.name = "modifier_effects_effect" +modifier_effects_effect.____file_path = "scripts/vscripts/abilities/modifiers/ability_effects.lua" +__TS__ClassExtends(modifier_effects_effect, BaseModifier) +function modifier_effects_effect.prototype.IsHidden(self) + return true +end +function modifier_effects_effect.prototype.IsPurgable(self) + return false +end +function modifier_effects_effect.prototype.RemoveOnDeath(self) + return false +end +function modifier_effects_effect.prototype.GetEffectName(self) + local parent = self:GetParent() + if not parent or parent:IsNull() or not IsValidEntity(parent) then + return "" + end + local hero = parent + if not hero or not hero:IsRealHero() then + return "" + end + local current_effect = GetEffect(nil, hero, "effect") + if current_effect == "skin_effect_ice" then + return "particles/econ/items/omniknight/omniknight_fall20_immortal/omniknight_fall20_immortal_degen_aura_debuff.vpcf" + end + if current_effect == "skin_effect_fire" then + return "particles/econ/items/omniknight/omni_crimson_witness_2021/omniknight_crimson_witness_2021_degen_aura_debuff.vpcf" + end + if current_effect == "skin_effect_heaven" then + return "particles/units/heroes/hero_omniknight/omniknight_heavenly_grace_buff.vpcf" + end + if current_effect == "skin_effect_fall_2021_emblem" then + return "particles/econ/events/fall_2021/fall_2021_emblem_game_effect.vpcf" + end + if current_effect == "skin_effect_blue_gems" then + return "particles/blue_gems_effect.vpcf" + end + if current_effect == "skin_effect_sponsor" then + return "particles/sponsor_effect.vpcf" + end + if current_effect == "skin_effect_lotus" then + return "particles/lotus_effect.vpcf" + end + if current_effect == "skin_effect_bp_red" then + return "particles/effect_battlepass_red.vpcf" + end + if current_effect == "skin_effect_bp_garden" then + return "particles/battlepass_garden_effect.vpcf" + end + if current_effect == "skin_effect_bp_golden" then + return "particles/battlepass_golden_effect.vpcf" + end + if current_effect == "skin_effect_bp_diretide_emblem" then + return "particles/econ/events/diretide_2020/emblem/fall20_emblem_effect.vpcf" + end + if current_effect == "skin_effect_bp_diretide_emblem_v1" then + return "particles/econ/events/diretide_2020/emblem/fall20_emblem_v1_effect.vpcf" + end + if current_effect == "skin_effect_bp_diretide_emblem_v3" then + return "particles/econ/events/diretide_2020/emblem/fall20_emblem_v3_effect.vpcf" + end + return "" +end +function modifier_effects_effect.prototype.GetEffectAttachType(self) + return PATTACH_ABSORIGIN_FOLLOW +end +modifier_effects_effect = __TS__Decorate( + modifier_effects_effect, + modifier_effects_effect, + {registerModifier(nil)}, + {kind = "class", name = "modifier_effects_effect"} +) +____exports.modifier_effects_effect = modifier_effects_effect +____exports.modifier_effects_model = __TS__Class() +local modifier_effects_model = ____exports.modifier_effects_model +modifier_effects_model.name = "modifier_effects_model" +modifier_effects_model.____file_path = "scripts/vscripts/abilities/modifiers/ability_effects.lua" +__TS__ClassExtends(modifier_effects_model, BaseModifier) +function modifier_effects_model.prototype.IsHidden(self) + return true +end +function modifier_effects_model.prototype.IsPurgable(self) + return false +end +function modifier_effects_model.prototype.RemoveOnDeath(self) + return false +end +function modifier_effects_model.prototype.GetEffectName(self) + return "particles/units/heroes/hero_omniknight/omniknight_guardian_angel_wings.vpcf" +end +function modifier_effects_model.prototype.GetEffectAttachType(self) + return PATTACH_ABSORIGIN_FOLLOW +end +modifier_effects_model = __TS__Decorate( + modifier_effects_model, + modifier_effects_model, + {registerModifier(nil)}, + {kind = "class", name = "modifier_effects_model"} +) +____exports.modifier_effects_model = modifier_effects_model +return ____exports diff --git a/scripts/vscripts/abilities/modifiers/ability_glyph_custom.lua b/scripts/vscripts/abilities/modifiers/ability_glyph_custom.lua new file mode 100644 index 0000000..401fdf6 --- /dev/null +++ b/scripts/vscripts/abilities/modifiers/ability_glyph_custom.lua @@ -0,0 +1,164 @@ +local ____lualib = require("lualib_bundle") +local __TS__Class = ____lualib.__TS__Class +local __TS__ClassExtends = ____lualib.__TS__ClassExtends +local __TS__Decorate = ____lualib.__TS__Decorate +local ____exports = {} +local ____dota_ts_adapter = require("lib.dota_ts_adapter") +local BaseAbility = ____dota_ts_adapter.BaseAbility +local BaseModifier = ____dota_ts_adapter.BaseModifier +local registerAbility = ____dota_ts_adapter.registerAbility +local registerModifier = ____dota_ts_adapter.registerModifier +____exports.ability_glyph_custom = __TS__Class() +local ability_glyph_custom = ____exports.ability_glyph_custom +ability_glyph_custom.name = "ability_glyph_custom" +ability_glyph_custom.____file_path = "scripts/vscripts/abilities/modifiers/ability_glyph_custom.lua" +__TS__ClassExtends(ability_glyph_custom, BaseAbility) +function ability_glyph_custom.prototype.GetIntrinsicModifierName(self) + return "modifier_glyph_custom_passive" +end +ability_glyph_custom = __TS__Decorate( + ability_glyph_custom, + ability_glyph_custom, + {registerAbility(nil)}, + {kind = "class", name = "ability_glyph_custom"} +) +____exports.ability_glyph_custom = ability_glyph_custom +____exports.modifier_glyph_custom_passive = __TS__Class() +local modifier_glyph_custom_passive = ____exports.modifier_glyph_custom_passive +modifier_glyph_custom_passive.name = "modifier_glyph_custom_passive" +modifier_glyph_custom_passive.____file_path = "scripts/vscripts/abilities/modifiers/ability_glyph_custom.lua" +__TS__ClassExtends(modifier_glyph_custom_passive, BaseModifier) +function modifier_glyph_custom_passive.prototype.____constructor(self, ...) + BaseModifier.prototype.____constructor(self, ...) + self.isActive = true +end +function modifier_glyph_custom_passive.prototype.IsHidden(self) + return false +end +function modifier_glyph_custom_passive.prototype.DeclareFunctions(self) + return {MODIFIER_PROPERTY_MIN_HEALTH, MODIFIER_EVENT_ON_TAKEDAMAGE} +end +function modifier_glyph_custom_passive.prototype.GetMinHealth(self) + if self.isActive then + return 1 + end + return 0 +end +function modifier_glyph_custom_passive.prototype.OnTakeDamage(self, event) + local parent = self:GetParent() + if event.unit == parent then + if parent:GetHealthPercent() <= 10 and self.isActive then + FireGameEvent("dota_glyph_used", {teamnumber = DOTA_TEAM_GOODGUYS}) + self.isActive = false + parent:AddNewModifier( + parent, + self:GetAbility(), + "modifier_glyph_custom", + {duration = 6} + ) + Timers:CreateTimer( + 306, + function() + self.isActive = true + end + ) + parent:RemoveModifierByName("modifier_glyph_custom_passive") + end + end +end +modifier_glyph_custom_passive = __TS__Decorate( + modifier_glyph_custom_passive, + modifier_glyph_custom_passive, + {registerModifier(nil)}, + {kind = "class", name = "modifier_glyph_custom_passive"} +) +____exports.modifier_glyph_custom_passive = modifier_glyph_custom_passive +____exports.modifier_glyph_custom = __TS__Class() +local modifier_glyph_custom = ____exports.modifier_glyph_custom +modifier_glyph_custom.name = "modifier_glyph_custom" +modifier_glyph_custom.____file_path = "scripts/vscripts/abilities/modifiers/ability_glyph_custom.lua" +__TS__ClassExtends(modifier_glyph_custom, BaseModifier) +function modifier_glyph_custom.prototype.IsHidden(self) + return false +end +function modifier_glyph_custom.prototype.IsPurgable(self) + return false +end +function modifier_glyph_custom.prototype.OnCreated(self, params) + local parent = self:GetParent() + local particle = ParticleManager:CreateParticle("particles/items_fx/glyph.vpcf", PATTACH_ABSORIGIN_FOLLOW, parent) + ParticleManager:SetParticleControl( + particle, + 0, + parent:GetAbsOrigin() + ) + ParticleManager:SetParticleControl( + particle, + 1, + Vector(150, 1, 1) + ) + self:AddParticle( + particle, + false, + false, + -1, + false, + false + ) +end +function modifier_glyph_custom.prototype.OnDestroy(self) + if IsClient() then + return + end + local parent = self:GetParent() + local ability = self:GetAbility() + if ability then + parent:AddNewModifier(parent, ability, "modifier_glyph_custom_passive_cd", {duration = 300}) + end +end +function modifier_glyph_custom.prototype.CheckState(self) + return {[MODIFIER_STATE_INVULNERABLE] = true} +end +modifier_glyph_custom = __TS__Decorate( + modifier_glyph_custom, + modifier_glyph_custom, + {registerModifier(nil)}, + {kind = "class", name = "modifier_glyph_custom"} +) +____exports.modifier_glyph_custom = modifier_glyph_custom +____exports.modifier_glyph_custom_passive_cd = __TS__Class() +local modifier_glyph_custom_passive_cd = ____exports.modifier_glyph_custom_passive_cd +modifier_glyph_custom_passive_cd.name = "modifier_glyph_custom_passive_cd" +modifier_glyph_custom_passive_cd.____file_path = "scripts/vscripts/abilities/modifiers/ability_glyph_custom.lua" +__TS__ClassExtends(modifier_glyph_custom_passive_cd, BaseModifier) +function modifier_glyph_custom_passive_cd.prototype.IsHidden(self) + return false +end +function modifier_glyph_custom_passive_cd.prototype.IsDebuff(self) + return true +end +function modifier_glyph_custom_passive_cd.prototype.IsPurgable(self) + return true +end +function modifier_glyph_custom_passive_cd.prototype.OnDestroy(self) + if IsClient() then + return + end + local ability = self:GetAbility() + if ability then + self:GetParent():AddNewModifier( + self:GetParent(), + ability, + "modifier_glyph_custom_passive", + {} + ) + end +end +modifier_glyph_custom_passive_cd = __TS__Decorate( + modifier_glyph_custom_passive_cd, + modifier_glyph_custom_passive_cd, + {registerModifier(nil)}, + {kind = "class", name = "modifier_glyph_custom_passive_cd"} +) +____exports.modifier_glyph_custom_passive_cd = modifier_glyph_custom_passive_cd +return ____exports diff --git a/scripts/vscripts/abilities/modifiers/ability_modifier_source.lua b/scripts/vscripts/abilities/modifiers/ability_modifier_source.lua new file mode 100644 index 0000000..8b84fdd --- /dev/null +++ b/scripts/vscripts/abilities/modifiers/ability_modifier_source.lua @@ -0,0 +1,43 @@ +local ____lualib = require("lualib_bundle") +local __TS__Class = ____lualib.__TS__Class +local __TS__ClassExtends = ____lualib.__TS__ClassExtends +local __TS__Decorate = ____lualib.__TS__Decorate +local ____exports = {} +local ____dota_ts_adapter = require("lib.dota_ts_adapter") +local BaseAbility = ____dota_ts_adapter.BaseAbility +local registerAbility = ____dota_ts_adapter.registerAbility +____exports.MODIFIER_SOURCE_ABILITY_NAME = "ability_modifier_source" +____exports.ability_modifier_source = __TS__Class() +local ability_modifier_source = ____exports.ability_modifier_source +ability_modifier_source.name = "ability_modifier_source" +ability_modifier_source.____file_path = "scripts/vscripts/abilities/modifiers/ability_modifier_source.lua" +__TS__ClassExtends(ability_modifier_source, BaseAbility) +function ability_modifier_source.prototype.IsHidden(self) + return true +end +function ability_modifier_source.prototype.IsInnateAbility(self) + return true +end +ability_modifier_source = __TS__Decorate( + ability_modifier_source, + ability_modifier_source, + {registerAbility(nil)}, + {kind = "class", name = "ability_modifier_source"} +) +____exports.ability_modifier_source = ability_modifier_source +function ____exports.getModifierSourceAbility(self, unit) + if not IsServer() or not unit or not IsValidEntity(unit) or unit:IsNull() then + return nil + end + local ability = unit:FindAbilityByName(____exports.MODIFIER_SOURCE_ABILITY_NAME) + if not ability then + ability = unit:AddAbility(____exports.MODIFIER_SOURCE_ABILITY_NAME) + end + if ability and ability:GetLevel() <= 0 then + ability:SetLevel(1) + end + return ability or nil +end +local g = _G +g.getModifierSourceAbility = ____exports.getModifierSourceAbility +return ____exports diff --git a/scripts/vscripts/abilities/modifiers/ability_stacking_crit.lua b/scripts/vscripts/abilities/modifiers/ability_stacking_crit.lua new file mode 100644 index 0000000..2bc11a1 --- /dev/null +++ b/scripts/vscripts/abilities/modifiers/ability_stacking_crit.lua @@ -0,0 +1,341 @@ +local ____lualib = require("lualib_bundle") +local __TS__Class = ____lualib.__TS__Class +local __TS__ClassExtends = ____lualib.__TS__ClassExtends +local __TS__Decorate = ____lualib.__TS__Decorate +local __TS__ArrayForEach = ____lualib.__TS__ArrayForEach +local __TS__ArrayFindIndex = ____lualib.__TS__ArrayFindIndex +local __TS__ArraySort = ____lualib.__TS__ArraySort +local __TS__ArrayFilter = ____lualib.__TS__ArrayFilter +local ____exports = {} +local ____dota_ts_adapter = require("lib.dota_ts_adapter") +local BaseAbility = ____dota_ts_adapter.BaseAbility +local BaseModifier = ____dota_ts_adapter.BaseModifier +local registerAbility = ____dota_ts_adapter.registerAbility +local registerModifier = ____dota_ts_adapter.registerModifier +local ____crit_mult = require("utils.crit_mult") +local getFinalStackingCritMultiplier = ____crit_mult.getFinalStackingCritMultiplier +local CRIT_LUCK_ADDITIVE_PERCENT_PER_POINT = 1 +____exports.ability_stacking_crit = __TS__Class() +local ability_stacking_crit = ____exports.ability_stacking_crit +ability_stacking_crit.name = "ability_stacking_crit" +ability_stacking_crit.____file_path = "scripts/vscripts/abilities/modifiers/ability_stacking_crit.lua" +__TS__ClassExtends(ability_stacking_crit, BaseAbility) +function ability_stacking_crit.prototype.GetIntrinsicModifierName(self) + return "modifier_stacking_crit" +end +ability_stacking_crit = __TS__Decorate( + ability_stacking_crit, + ability_stacking_crit, + {registerAbility(nil)}, + {kind = "class", name = "ability_stacking_crit"} +) +____exports.ability_stacking_crit = ability_stacking_crit +____exports.modifier_stacking_crit = __TS__Class() +local modifier_stacking_crit = ____exports.modifier_stacking_crit +modifier_stacking_crit.name = "modifier_stacking_crit" +modifier_stacking_crit.____file_path = "scripts/vscripts/abilities/modifiers/ability_stacking_crit.lua" +__TS__ClassExtends(modifier_stacking_crit, BaseModifier) +function modifier_stacking_crit.prototype.____constructor(self, ...) + BaseModifier.prototype.____constructor(self, ...) + self.critModifiers = {} + self.guaranteedCritCount = 0 + self.guaranteedCritUntilDestroy = false + self.wasCrit = false + self.pendingGuaranteedCritConsume = false + self.forcedTargetCritEntityIndex = nil + self.lastCritChance = 0 + self.lastCritMult = 1 +end +function modifier_stacking_crit.GetForUnit(self, unit) + local mod = unit:FindModifierByName("modifier_stacking_crit") + return mod and mod or nil +end +function modifier_stacking_crit.prototype.RemoveOnDeath(self) + return false +end +function modifier_stacking_crit.prototype.IsPurgable(self) + return false +end +function modifier_stacking_crit.prototype.IsHidden(self) + return true +end +function modifier_stacking_crit.prototype.DeclareFunctions(self) + return {MODIFIER_PROPERTY_PREATTACK_CRITICALSTRIKE, MODIFIER_EVENT_ON_ATTACK_LANDED, MODIFIER_PROPERTY_MAGICAL_RESISTANCE_DIRECT_MODIFICATION} +end +function modifier_stacking_crit.prototype.GetModifierMagicalResistanceDirectModification(self) + return self:GetParent():GetIntellect(true) * -0.1 +end +function modifier_stacking_crit.prototype.GetModifierPreAttack_CriticalStrike(self, params) + if not IsServer() then + return 0 + end + self.wasCrit = false + self.lastCritChance = 0 + self.lastCritMult = 1 + local totalBonus = 0 + local parent = self:GetParent() + local target = params and params.target + if self.forcedTargetCritEntityIndex ~= nil and target and IsValidEntity(target) and target:GetEntityIndex() == self.forcedTargetCritEntityIndex then + self.forcedTargetCritEntityIndex = nil + if #self.critModifiers > 0 then + local baseCritMult = 0 + __TS__ArrayForEach( + self.critModifiers, + function(____, critData) + baseCritMult = baseCritMult + critData.mult / 100 + end + ) + local finalMult = getFinalStackingCritMultiplier(nil, parent, baseCritMult) + self.wasCrit = true + self.lastCritMult = finalMult + self.lastCritChance = 100 + return finalMult * 100 + end + end + if self.guaranteedCritUntilDestroy and #self.critModifiers > 0 then + print((("[STACKING_CRIT] guaranteedCritUntilDestroy=TRUE, unit=" .. parent:GetUnitName()) .. ", crits=") .. tostring(#self.critModifiers)) + local baseCritMult = 0 + __TS__ArrayForEach( + self.critModifiers, + function(____, critData) + print((((((("[STACKING_CRIT] source=" .. critData.source) .. " chance=") .. tostring(critData.chance)) .. " mult=") .. tostring(critData.mult)) .. " entityIndex=") .. tostring(critData.entityIndex or -1)) + baseCritMult = baseCritMult + critData.mult / 100 + end + ) + local finalMult = getFinalStackingCritMultiplier(nil, parent, baseCritMult) + print((("[STACKING_CRIT] baseCritMult_sum=" .. tostring(baseCritMult)) .. " finalMult=") .. tostring(finalMult)) + self.wasCrit = true + self.lastCritMult = finalMult + self.lastCritChance = 100 + return finalMult * 100 + end + if self.guaranteedCritCount > 0 and #self.critModifiers > 0 then + local baseCritMult = 0 + __TS__ArrayForEach( + self.critModifiers, + function(____, critData) + baseCritMult = baseCritMult + critData.mult / 100 + end + ) + local finalMult = getFinalStackingCritMultiplier(nil, parent, baseCritMult) + self.pendingGuaranteedCritConsume = true + self.wasCrit = true + self.lastCritMult = finalMult + self.lastCritChance = 100 + return finalMult * 100 + end + local hadAnyCrit = false + local bestProcChance = 0 + __TS__ArrayForEach( + self.critModifiers, + function(____, critData) + local luck = parent and parent:IsHero() and getLuck(nil, parent) or 0 + local effectiveChance = parent and parent:IsHero() and luck > 0 and math.min( + 100, + math.max(0, critData.chance + luck * CRIT_LUCK_ADDITIVE_PERCENT_PER_POINT) + ) or critData.chance + if parent and parent:IsHero() and luck > 0 then + end + if RollPseudoRandomPercentage( + effectiveChance, + 1, + self:GetParent() + ) then + totalBonus = totalBonus + critData.mult / 100 + hadAnyCrit = true + if effectiveChance > bestProcChance then + bestProcChance = effectiveChance + end + end + end + ) + if hadAnyCrit then + local baseCritMult = totalBonus + local finalMult = getFinalStackingCritMultiplier(nil, parent, baseCritMult) + self.wasCrit = true + self.lastCritChance = bestProcChance + self.lastCritMult = finalMult + return finalMult * 100 + end + return 0 +end +function modifier_stacking_crit.prototype.ForceNextCritOnTarget(self, target) + if not IsServer() then + return + end + if not target or not IsValidEntity(target) or target:IsNull() then + self.forcedTargetCritEntityIndex = nil + return + end + self.forcedTargetCritEntityIndex = target:GetEntityIndex() +end +function modifier_stacking_crit.prototype.OnCreated(self) + if not IsServer() then + return + end + self:ClearCritModifiers() +end +function modifier_stacking_crit.prototype.ClearCritModifiers(self) + self.critModifiers = {} +end +function modifier_stacking_crit.prototype.UpdateCrit(self, oldChance, oldMult, newChance, newMult, source) + if source == nil then + source = "ability" + end + if not IsServer() then + return + end + if newChance < 0 and newMult <= 0 then + self:RemoveCrit(source) + return + end + self:UpdateExistingCrit(newChance, newMult, source) +end +function modifier_stacking_crit.prototype.UpdateExistingCrit(self, chance, mult, source, entity) + if not IsServer() then + return + end + if chance < 0 and mult <= 0 then + self:RemoveCrit(source, entity) + return + end + local entityIndex = entity and entity:GetEntityIndex() + local existingIndex = __TS__ArrayFindIndex( + self.critModifiers, + function(____, crit) + if crit.source ~= source then + return false + end + if entityIndex ~= nil then + return crit.entityIndex == entityIndex + end + return true + end + ) + if existingIndex ~= -1 then + self.critModifiers[existingIndex + 1].chance = chance + self.critModifiers[existingIndex + 1].mult = mult + if entityIndex ~= nil then + self.critModifiers[existingIndex + 1].entityIndex = entityIndex + end + else + local ____self_critModifiers_4 = self.critModifiers + ____self_critModifiers_4[#____self_critModifiers_4 + 1] = {chance = chance, mult = mult, source = source, entityIndex = entityIndex} + end + __TS__ArraySort( + self.critModifiers, + function(____, a, b) return b.chance - a.chance end + ) +end +function modifier_stacking_crit.prototype.AddCustomCrit(self, chance, mult, source, entity) + if not IsServer() then + return + end + if chance < 0 and mult <= 0 then + return + end + local entityIndex = entity and entity:GetEntityIndex() + local existingIndex = __TS__ArrayFindIndex( + self.critModifiers, + function(____, crit) return crit.source == source and crit.entityIndex == entityIndex end + ) + if existingIndex ~= -1 then + self.critModifiers[existingIndex + 1] = {chance = chance, mult = mult, source = source, entityIndex = entityIndex} + else + local ____self_critModifiers_7 = self.critModifiers + ____self_critModifiers_7[#____self_critModifiers_7 + 1] = {chance = chance, mult = mult, source = source, entityIndex = entityIndex} + end + __TS__ArraySort( + self.critModifiers, + function(____, a, b) return b.chance - a.chance end + ) +end +function modifier_stacking_crit.prototype.GuaranteeNextCrit(self, count) + if count == nil then + count = 1 + end + if not IsServer() then + return + end + self.guaranteedCritCount = math.max(0, self.guaranteedCritCount + count) +end +function modifier_stacking_crit.prototype.GuaranteeCritUntilDestroy(self, enable) + if enable == nil then + enable = true + end + if not IsServer() then + return + end + self.guaranteedCritUntilDestroy = enable +end +function modifier_stacking_crit.prototype.OnAttackLanded(self, event) + if not IsServer() then + return + end + if event.attacker ~= self:GetParent() then + return + end + if self.pendingGuaranteedCritConsume then + self.guaranteedCritCount = math.max(0, self.guaranteedCritCount - 1) + self.pendingGuaranteedCritConsume = false + end + local parent = self:GetParent() + local target = event.target + if not parent or not target then + return + end + if self.wasCrit then + local avgTrue = parent:GetAverageTrueAttackDamage(target) + local approxDamage = math.floor((avgTrue or 0) * (self.lastCritMult or 1)) + if self:GetParent():GetUnitName() == "npc_dota_hero_phantom_assassin" then + EmitSoundOn("Hero_PhantomAssassin.CoupDeGrace", target) + local effect_cast = ParticleManager:CreateParticle("particles/units/heroes/hero_phantom_assassin/phantom_assassin_crit_impact.vpcf", PATTACH_POINT_FOLLOW, target) + ParticleManager:SetParticleControlEnt( + effect_cast, + 0, + target, + PATTACH_POINT_FOLLOW, + "attach_hitloc", + target:GetOrigin(), + true + ) + ParticleManager:SetParticleControl( + effect_cast, + 1, + target:GetAbsOrigin() + ) + ParticleManager:SetParticleControlForward( + effect_cast, + 1, + (parent:GetOrigin() - target:GetOrigin()):Normalized() + ) + ParticleManager:ReleaseParticleIndex(effect_cast) + end + self.wasCrit = false + end +end +function modifier_stacking_crit.prototype.RemoveCrit(self, source, entity) + if not IsServer() then + return + end + local entityIndex = entity and entity:GetEntityIndex() + self.critModifiers = __TS__ArrayFilter( + self.critModifiers, + function(____, crit) return not (crit.source == source and crit.entityIndex == entityIndex) end + ) +end +function modifier_stacking_crit.prototype.OnDestroy(self) + if not IsServer() then + return + end + self.critModifiers = {} + self.guaranteedCritUntilDestroy = false +end +modifier_stacking_crit = __TS__Decorate( + modifier_stacking_crit, + modifier_stacking_crit, + {registerModifier(nil)}, + {kind = "class", name = "modifier_stacking_crit"} +) +____exports.modifier_stacking_crit = modifier_stacking_crit +return ____exports diff --git a/scripts/vscripts/abilities/modifiers/ability_stacking_spell_crit.lua b/scripts/vscripts/abilities/modifiers/ability_stacking_spell_crit.lua new file mode 100644 index 0000000..1f74717 --- /dev/null +++ b/scripts/vscripts/abilities/modifiers/ability_stacking_spell_crit.lua @@ -0,0 +1,234 @@ +local ____lualib = require("lualib_bundle") +local __TS__Class = ____lualib.__TS__Class +local __TS__ClassExtends = ____lualib.__TS__ClassExtends +local __TS__Decorate = ____lualib.__TS__Decorate +local __TS__ArrayForEach = ____lualib.__TS__ArrayForEach +local __TS__ArrayFindIndex = ____lualib.__TS__ArrayFindIndex +local __TS__ArraySort = ____lualib.__TS__ArraySort +local __TS__ArrayFilter = ____lualib.__TS__ArrayFilter +local ____exports = {} +local ____dota_ts_adapter = require("lib.dota_ts_adapter") +local BaseAbility = ____dota_ts_adapter.BaseAbility +local BaseModifier = ____dota_ts_adapter.BaseModifier +local registerAbility = ____dota_ts_adapter.registerAbility +local registerModifier = ____dota_ts_adapter.registerModifier +local ____crit_mult = require("utils.crit_mult") +local getFinalStackingCritMultiplier = ____crit_mult.getFinalStackingCritMultiplier +local SPELL_CRIT_LUCK_ADDITIVE_PERCENT_PER_POINT = 1 +local isApplyingSpellCritBonus = false +____exports.ability_stacking_spell_crit = __TS__Class() +local ability_stacking_spell_crit = ____exports.ability_stacking_spell_crit +ability_stacking_spell_crit.name = "ability_stacking_spell_crit" +ability_stacking_spell_crit.____file_path = "scripts/vscripts/abilities/modifiers/ability_stacking_spell_crit.lua" +__TS__ClassExtends(ability_stacking_spell_crit, BaseAbility) +function ability_stacking_spell_crit.prototype.GetIntrinsicModifierName(self) + return "modifier_stacking_spell_crit" +end +ability_stacking_spell_crit = __TS__Decorate( + ability_stacking_spell_crit, + ability_stacking_spell_crit, + {registerAbility(nil)}, + {kind = "class", name = "ability_stacking_spell_crit"} +) +____exports.ability_stacking_spell_crit = ability_stacking_spell_crit +____exports.modifier_stacking_spell_crit = __TS__Class() +local modifier_stacking_spell_crit = ____exports.modifier_stacking_spell_crit +modifier_stacking_spell_crit.name = "modifier_stacking_spell_crit" +modifier_stacking_spell_crit.____file_path = "scripts/vscripts/abilities/modifiers/ability_stacking_spell_crit.lua" +__TS__ClassExtends(modifier_stacking_spell_crit, BaseModifier) +function modifier_stacking_spell_crit.prototype.____constructor(self, ...) + BaseModifier.prototype.____constructor(self, ...) + self.critModifiers = {} +end +function modifier_stacking_spell_crit.GetForUnit(self, unit) + local mod = unit:FindModifierByName("modifier_stacking_spell_crit") + return mod and mod or nil +end +function modifier_stacking_spell_crit.prototype.RemoveOnDeath(self) + return false +end +function modifier_stacking_spell_crit.prototype.IsPurgable(self) + return false +end +function modifier_stacking_spell_crit.prototype.IsHidden(self) + return true +end +function modifier_stacking_spell_crit.prototype.DeclareFunctions(self) + return {MODIFIER_EVENT_ON_TAKEDAMAGE} +end +function modifier_stacking_spell_crit.prototype.OnCreated(self) + if not IsServer() then + return + end + self.critModifiers = {} +end +function modifier_stacking_spell_crit.prototype.OnTakeDamage(self, event) + if not IsServer() or isApplyingSpellCritBonus then + return + end + if event.attacker ~= self:GetParent() then + return + end + if #self.critModifiers == 0 then + return + end + local damage = event.damage + if damage <= 0 then + return + end + if event.damage_type ~= DAMAGE_TYPE_MAGICAL then + return + end + if not event.inflictor then + return + end + local damageFlags = event.damage_flags or 0 + if bit.band(damageFlags, DOTA_DAMAGE_FLAG_HPLOSS) == DOTA_DAMAGE_FLAG_HPLOSS then + return + end + if bit.band(damageFlags, DOTA_DAMAGE_FLAG_REFLECTION) == DOTA_DAMAGE_FLAG_REFLECTION then + return + end + local parent = self:GetParent() + local victim = event.unit + local inflictor = event.inflictor + if not victim or not inflictor or victim:IsNull() or inflictor:IsNull() then + return + end + if inflictor == self:GetAbility() then + return + end + local totalBonusMult = 0 + local bestProcChance = 0 + local luck = parent:IsHero() and getLuck(nil, parent) or 0 + __TS__ArrayForEach( + self.critModifiers, + function(____, critData) + local effectiveChance = parent:IsHero() and luck > 0 and math.min( + 100, + math.max(0, critData.chance + luck * SPELL_CRIT_LUCK_ADDITIVE_PERCENT_PER_POINT) + ) or critData.chance + if RollPseudoRandomPercentage(effectiveChance, 1, parent) then + totalBonusMult = totalBonusMult + critData.mult / 100 + if effectiveChance > bestProcChance then + bestProcChance = effectiveChance + end + end + end + ) + if totalBonusMult <= 0 then + return + end + local finalMult = getFinalStackingCritMultiplier(nil, parent, totalBonusMult) + if finalMult <= 0 then + return + end + local bonusDamage = damage * math.max(0, finalMult - 1) + if bonusDamage <= 0 then + return + end + local spellAmpFrac = parent:IsHero() and math.max( + 0, + parent:GetSpellAmplification(false) + ) or 0 + local damageToApply = bonusDamage / math.max(0.000001, 1 + spellAmpFrac) + isApplyingSpellCritBonus = true + ApplyDamage({ + victim = victim, + attacker = parent, + damage = damageToApply, + damage_type = DAMAGE_TYPE_MAGICAL, + damage_flags = DOTA_DAMAGE_FLAG_NO_SPELL_AMPLIFICATION, + ability = inflictor + }) + isApplyingSpellCritBonus = false + SendOverheadEventMessage( + nil, + OVERHEAD_ALERT_DEADLY_BLOW, + victim, + math.floor(bonusDamage), + nil + ) +end +function modifier_stacking_spell_crit.prototype.AddCustomCrit(self, chance, mult, source, entity) + if not IsServer() then + return + end + if chance < 0 and mult <= 0 then + return + end + local entityIndex = entity and entity:GetEntityIndex() + local existingIndex = __TS__ArrayFindIndex( + self.critModifiers, + function(____, crit) return crit.source == source and crit.entityIndex == entityIndex end + ) + if existingIndex ~= -1 then + self.critModifiers[existingIndex + 1] = {chance = chance, mult = mult, source = source, entityIndex = entityIndex} + else + local ____self_critModifiers_2 = self.critModifiers + ____self_critModifiers_2[#____self_critModifiers_2 + 1] = {chance = chance, mult = mult, source = source, entityIndex = entityIndex} + end + __TS__ArraySort( + self.critModifiers, + function(____, a, b) return b.chance - a.chance end + ) +end +function modifier_stacking_spell_crit.prototype.UpdateExistingCrit(self, chance, mult, source, entity) + if not IsServer() then + return + end + if chance < 0 and mult <= 0 then + self:RemoveCrit(source, entity) + return + end + local entityIndex = entity and entity:GetEntityIndex() + local existingIndex = __TS__ArrayFindIndex( + self.critModifiers, + function(____, crit) + if crit.source ~= source then + return false + end + if entityIndex ~= nil then + return crit.entityIndex == entityIndex + end + return true + end + ) + if existingIndex ~= -1 then + self.critModifiers[existingIndex + 1].chance = chance + self.critModifiers[existingIndex + 1].mult = mult + if entityIndex ~= nil then + self.critModifiers[existingIndex + 1].entityIndex = entityIndex + end + else + self:AddCustomCrit(chance, mult, source, entity) + end + __TS__ArraySort( + self.critModifiers, + function(____, a, b) return b.chance - a.chance end + ) +end +function modifier_stacking_spell_crit.prototype.RemoveCrit(self, source, entity) + if not IsServer() then + return + end + local entityIndex = entity and entity:GetEntityIndex() + self.critModifiers = __TS__ArrayFilter( + self.critModifiers, + function(____, crit) return not (crit.source == source and crit.entityIndex == entityIndex) end + ) +end +function modifier_stacking_spell_crit.prototype.OnDestroy(self) + if not IsServer() then + return + end + self.critModifiers = {} +end +modifier_stacking_spell_crit = __TS__Decorate( + modifier_stacking_spell_crit, + modifier_stacking_spell_crit, + {registerModifier(nil)}, + {kind = "class", name = "modifier_stacking_spell_crit"} +) +____exports.modifier_stacking_spell_crit = modifier_stacking_spell_crit +return ____exports diff --git a/scripts/vscripts/abilities/modifiers/ability_unit_less_laggy.lua b/scripts/vscripts/abilities/modifiers/ability_unit_less_laggy.lua new file mode 100644 index 0000000..9ede2c5 --- /dev/null +++ b/scripts/vscripts/abilities/modifiers/ability_unit_less_laggy.lua @@ -0,0 +1,51 @@ +local ____lualib = require("lualib_bundle") +local __TS__Class = ____lualib.__TS__Class +local __TS__ClassExtends = ____lualib.__TS__ClassExtends +local __TS__Decorate = ____lualib.__TS__Decorate +local ____exports = {} +local ____dota_ts_adapter = require("lib.dota_ts_adapter") +local BaseAbility = ____dota_ts_adapter.BaseAbility +local BaseModifier = ____dota_ts_adapter.BaseModifier +local registerAbility = ____dota_ts_adapter.registerAbility +local registerModifier = ____dota_ts_adapter.registerModifier +____exports.ability_unit_less_laggy = __TS__Class() +local ability_unit_less_laggy = ____exports.ability_unit_less_laggy +ability_unit_less_laggy.name = "ability_unit_less_laggy" +ability_unit_less_laggy.____file_path = "scripts/vscripts/abilities/modifiers/ability_unit_less_laggy.lua" +__TS__ClassExtends(ability_unit_less_laggy, BaseAbility) +function ability_unit_less_laggy.prototype.GetIntrinsicModifierName(self) + return "modifier_unit_less_laggy" +end +ability_unit_less_laggy = __TS__Decorate( + ability_unit_less_laggy, + ability_unit_less_laggy, + {registerAbility(nil)}, + {kind = "class", name = "ability_unit_less_laggy"} +) +____exports.ability_unit_less_laggy = ability_unit_less_laggy +____exports.modifier_unit_less_laggy = __TS__Class() +local modifier_unit_less_laggy = ____exports.modifier_unit_less_laggy +modifier_unit_less_laggy.name = "modifier_unit_less_laggy" +modifier_unit_less_laggy.____file_path = "scripts/vscripts/abilities/modifiers/ability_unit_less_laggy.lua" +__TS__ClassExtends(modifier_unit_less_laggy, BaseModifier) +function modifier_unit_less_laggy.prototype.IsHidden(self) + return false +end +function modifier_unit_less_laggy.prototype.IsPurgable(self) + return false +end +function modifier_unit_less_laggy.prototype.CheckState(self) + if self:GetParent():GetHealthPercent() <= 99.9 then + return {[MODIFIER_STATE_NO_HEALTH_BAR] = false} + else + return {[MODIFIER_STATE_NO_HEALTH_BAR] = true} + end +end +modifier_unit_less_laggy = __TS__Decorate( + modifier_unit_less_laggy, + modifier_unit_less_laggy, + {registerModifier(nil)}, + {kind = "class", name = "modifier_unit_less_laggy"} +) +____exports.modifier_unit_less_laggy = modifier_unit_less_laggy +return ____exports diff --git a/scripts/vscripts/abilities/modifiers/dps_tracker.lua b/scripts/vscripts/abilities/modifiers/dps_tracker.lua new file mode 100644 index 0000000..702dcdf --- /dev/null +++ b/scripts/vscripts/abilities/modifiers/dps_tracker.lua @@ -0,0 +1,160 @@ +local ____lualib = require("lualib_bundle") +local __TS__Class = ____lualib.__TS__Class +local __TS__ClassExtends = ____lualib.__TS__ClassExtends +local __TS__Decorate = ____lualib.__TS__Decorate +local __TS__NumberToFixed = ____lualib.__TS__NumberToFixed +local ____exports = {} +local ____dota_ts_adapter = require("lib.dota_ts_adapter") +local BaseAbility = ____dota_ts_adapter.BaseAbility +local BaseModifier = ____dota_ts_adapter.BaseModifier +local registerAbility = ____dota_ts_adapter.registerAbility +local registerModifier = ____dota_ts_adapter.registerModifier +print("[DPS Tracker] ===== Файл dps_tracker.ts загружен ===== ") +--- Модификатор для отслеживания урона, нанесенного юнитом +-- Отображает DPS и последний урон снизу юнита +-- +-- Использование: +-- const modifier = unit.AddNewModifier(unit, getModifierSourceAbility(unit), "modifier_dps_tracker", {}); +____exports.dps_tracker = __TS__Class() +local dps_tracker = ____exports.dps_tracker +dps_tracker.name = "dps_tracker" +dps_tracker.____file_path = "scripts/vscripts/abilities/modifiers/dps_tracker.lua" +__TS__ClassExtends(dps_tracker, BaseAbility) +function dps_tracker.prototype.GetIntrinsicModifierName(self) + print("[DPS Tracker] GetIntrinsicModifierName вызван") + return "modifier_dps_tracker" +end +dps_tracker = __TS__Decorate( + dps_tracker, + dps_tracker, + {registerAbility(nil)}, + {kind = "class", name = "dps_tracker"} +) +____exports.dps_tracker = dps_tracker +____exports.modifier_dps_tracker = __TS__Class() +local modifier_dps_tracker = ____exports.modifier_dps_tracker +modifier_dps_tracker.name = "modifier_dps_tracker" +modifier_dps_tracker.____file_path = "scripts/vscripts/abilities/modifiers/dps_tracker.lua" +__TS__ClassExtends(modifier_dps_tracker, BaseModifier) +function modifier_dps_tracker.prototype.____constructor(self) + BaseModifier.prototype.____constructor(self) + self.totalDamage = 0 + self.lastHitDamage = 0 + self.startTime = 0 + self.lastHitTime = 0 + self.INACTIVE_TIMEOUT = 14 + print("[DPS Tracker] Конструктор modifier_dps_tracker вызван") +end +function modifier_dps_tracker.prototype.IsHidden(self) + return true +end +function modifier_dps_tracker.prototype.IsDebuff(self) + return false +end +function modifier_dps_tracker.prototype.IsPurgable(self) + return false +end +function modifier_dps_tracker.prototype.OnCreated(self, params) + print((("[DPS Tracker] OnCreated вызван, IsServer: " .. tostring(IsServer())) .. ", IsClient: ") .. tostring(IsClient())) + if not IsServer() then + print("[DPS Tracker] OnCreated: не на сервере, выходим") + return + end + self.totalDamage = 0 + self.lastHitDamage = 0 + self.startTime = GameRules:GetGameTime() + self.lastHitTime = self.startTime + local unit = self:GetParent() + if unit and IsValidEntity(unit) then + print((("[DPS Tracker] Модификатор создан для юнита (цели): " .. unit:GetUnitName()) .. ", индекс: ") .. tostring(unit:entindex())) + else + print("[DPS Tracker] ОШИБКА: юнит невалиден при создании модификатора!") + end + self:StartIntervalThink(1) + print("[DPS Tracker] StartIntervalThink установлен на 1.0") +end +function modifier_dps_tracker.prototype.DeclareFunctions(self) + return {MODIFIER_EVENT_ON_TAKEDAMAGE, MODIFIER_PROPERTY_MIN_HEALTH} +end +function modifier_dps_tracker.prototype.GetMinHealth(self) + return 1 +end +function modifier_dps_tracker.prototype.OnTakeDamage(self, event) + if not IsServer() then + print("[DPS Tracker] OnTakeDamage: не на сервере") + return + end + local victim = self:GetParent() + local attacker = event.attacker + if not attacker or not IsValidEntity(attacker) then + return + end + if event.unit ~= self:GetParent() then + return + end + self:GetParent():StartGesture(ACT_DOTA_FLINCH) + local currentTime = GameRules:GetGameTime() + local damage = event.damage + if self.totalDamage == 0 then + self.startTime = currentTime + end + self:GetParent():SetHealth(self:GetParent():GetHealth() + damage) + self.totalDamage = self.totalDamage + damage + self.lastHitDamage = damage + self.lastHitTime = currentTime + print((((((("[DPS Tracker] Урон получен: " .. tostring(math.floor(damage + 0.5))) .. ", всего урона: ") .. tostring(math.floor(self.totalDamage + 0.5))) .. ", от ") .. attacker:GetUnitName()) .. ", цель: ") .. victim:GetUnitName()) + self:updateDPSDisplay(victim:entindex()) +end +function modifier_dps_tracker.prototype.OnIntervalThink(self) + if not IsServer() then + print("[DPS Tracker] OnIntervalThink: не на сервере") + return + end + local currentTime = GameRules:GetGameTime() + local victim = self:GetParent() + local victimIndex = victim:entindex() + local timeSinceLastHit = currentTime - self.lastHitTime + if timeSinceLastHit > self.INACTIVE_TIMEOUT then + print(("[DPS Tracker] Цель не получала урон " .. __TS__NumberToFixed(timeSinceLastHit, 1)) .. " секунд, удаляем отображение") + CustomGameEventManager:Send_ServerToAllClients("unit_dps_remove", {unitIndex = victimIndex}) + else + self:updateDPSDisplay(victimIndex) + end +end +function modifier_dps_tracker.prototype.updateDPSDisplay(self, victimIndex) + if not IsServer() then + return + end + local currentTime = GameRules:GetGameTime() + local elapsedTime = currentTime - self.startTime + local dps = elapsedTime > 0 and self.totalDamage / elapsedTime or 0 + local updateData = { + unitIndex = victimIndex, + attackerIndex = 0, + totalDamage = math.floor(self.totalDamage + 0.5), + dps = math.floor(dps + 0.5), + lastHitDamage = math.floor(self.lastHitDamage + 0.5) + } + CustomGameEventManager:Send_ServerToAllClients("unit_dps_update", updateData) +end +function modifier_dps_tracker.prototype.OnDestroy(self) + if not IsServer() then + return + end + local unit = self:GetParent() + if unit and IsValidEntity(unit) then + local victimIndex = unit:entindex() + print((((("[DPS Tracker] Модификатор уничтожен для юнита: " .. unit:GetUnitName()) .. ", индекс: ") .. tostring(victimIndex)) .. ", финальный урон: ") .. tostring(math.floor(self.totalDamage + 0.5))) + CustomGameEventManager:Send_ServerToAllClients("unit_dps_remove", {unitIndex = victimIndex}) + else + print("[DPS Tracker] Ошибка: юнит невалиден при уничтожении модификатора") + end +end +modifier_dps_tracker = __TS__Decorate( + modifier_dps_tracker, + modifier_dps_tracker, + {registerModifier(nil)}, + {kind = "class", name = "modifier_dps_tracker"} +) +____exports.modifier_dps_tracker = modifier_dps_tracker +return ____exports diff --git a/scripts/vscripts/abilities/modifiers/modifier_boss_hud_health_bar.lua b/scripts/vscripts/abilities/modifiers/modifier_boss_hud_health_bar.lua new file mode 100644 index 0000000..4bf8d65 --- /dev/null +++ b/scripts/vscripts/abilities/modifiers/modifier_boss_hud_health_bar.lua @@ -0,0 +1,99 @@ +local ____lualib = require("lualib_bundle") +local __TS__StringStartsWith = ____lualib.__TS__StringStartsWith +local __TS__Class = ____lualib.__TS__Class +local __TS__ClassExtends = ____lualib.__TS__ClassExtends +local __TS__Decorate = ____lualib.__TS__Decorate +local ____exports = {} +local ____dota_ts_adapter = require("lib.dota_ts_adapter") +local BaseModifier = ____dota_ts_adapter.BaseModifier +local registerModifier = ____dota_ts_adapter.registerModifier +local THINK_INTERVAL = 0.2 +--- Волновые боссы + только npc_boss_nevermore (остальные npc_boss_* — без верхнего бара) +function ____exports.isBossHudHealthBarUnit(self, unitName) + return __TS__StringStartsWith(unitName, "npc_wave_boss_") or unitName == "npc_boss_nevermore" +end +--- Локализованное имя в HUD для Nevermore из spawn_boss_units / кат-сцены +____exports.BOSS_NEVERMORE_NAME_TOKEN = "#npc_boss_nevermore" +function ____exports.applyBossHudHealthBar(self, unit, nameToken) + if not IsServer() or unit == nil or not unit:IsAlive() then + return + end + if unit:HasModifier(____exports.modifier_boss_hud_health_bar.name) then + return + end + unit:AddNewModifier( + unit, + getModifierSourceAbility(nil, unit), + ____exports.modifier_boss_hud_health_bar.name, + {name_token = nameToken} + ) +end +____exports.modifier_boss_hud_health_bar = __TS__Class() +local modifier_boss_hud_health_bar = ____exports.modifier_boss_hud_health_bar +modifier_boss_hud_health_bar.name = "modifier_boss_hud_health_bar" +modifier_boss_hud_health_bar.____file_path = "scripts/vscripts/abilities/modifiers/modifier_boss_hud_health_bar.lua" +__TS__ClassExtends(modifier_boss_hud_health_bar, BaseModifier) +function modifier_boss_hud_health_bar.prototype.____constructor(self, ...) + BaseModifier.prototype.____constructor(self, ...) + self.nameToken = "" +end +function modifier_boss_hud_health_bar.prototype.IsHidden(self) + return true +end +function modifier_boss_hud_health_bar.prototype.IsPurgable(self) + return false +end +function modifier_boss_hud_health_bar.prototype.RemoveOnDeath(self) + return true +end +function modifier_boss_hud_health_bar.prototype.OnCreated(self, params) + self.parent = self:GetParent() + self.nameToken = params.name_token or "#" .. self.parent:GetUnitName() + self:sendUpdate() + self:StartIntervalThink(THINK_INTERVAL) +end +function modifier_boss_hud_health_bar.prototype.DeclareFunctions(self) + return {MODIFIER_EVENT_ON_TAKEDAMAGE} +end +function modifier_boss_hud_health_bar.prototype.OnTakeDamage(self) + self:sendUpdate() +end +function modifier_boss_hud_health_bar.prototype.OnIntervalThink(self) + if not IsValidEntity(self.parent) or not self.parent:IsAlive() then + self:StartIntervalThink(-1) + return + end + self:sendUpdate() +end +function modifier_boss_hud_health_bar.prototype.OnDestroy(self) + if IsServer() then + CustomGameEventManager:Send_ServerToAllClients("boss_health_bar_hide", {}) + end +end +function modifier_boss_hud_health_bar.prototype.sendUpdate(self) + if not IsServer() then + return + end + if not IsValidEntity(self.parent) or not self.parent:IsAlive() then + return + end + CustomGameEventManager:Send_ServerToAllClients( + "boss_health_bar_update", + { + health = math.floor(self.parent:GetHealth()), + maxHealth = math.max( + 1, + math.floor(self.parent:GetMaxHealth()) + ), + nameToken = self.nameToken + } + ) +end +modifier_boss_hud_health_bar = __TS__Decorate( + modifier_boss_hud_health_bar, + modifier_boss_hud_health_bar, + {registerModifier(nil)}, + {kind = "class", name = "modifier_boss_hud_health_bar"} +) +____exports.modifier_boss_hud_health_bar = modifier_boss_hud_health_bar +return ____exports diff --git a/scripts/vscripts/abilities/modifiers/modifier_cutscene_ready_lock.lua b/scripts/vscripts/abilities/modifiers/modifier_cutscene_ready_lock.lua new file mode 100644 index 0000000..17d04f0 --- /dev/null +++ b/scripts/vscripts/abilities/modifiers/modifier_cutscene_ready_lock.lua @@ -0,0 +1,30 @@ +local ____lualib = require("lualib_bundle") +local __TS__Class = ____lualib.__TS__Class +local __TS__ClassExtends = ____lualib.__TS__ClassExtends +local __TS__Decorate = ____lualib.__TS__Decorate +local ____exports = {} +local ____dota_ts_adapter = require("lib.dota_ts_adapter") +local BaseModifier = ____dota_ts_adapter.BaseModifier +local registerModifier = ____dota_ts_adapter.registerModifier +____exports.modifier_cutscene_ready_lock = __TS__Class() +local modifier_cutscene_ready_lock = ____exports.modifier_cutscene_ready_lock +modifier_cutscene_ready_lock.name = "modifier_cutscene_ready_lock" +modifier_cutscene_ready_lock.____file_path = "scripts/vscripts/abilities/modifiers/modifier_cutscene_ready_lock.lua" +__TS__ClassExtends(modifier_cutscene_ready_lock, BaseModifier) +function modifier_cutscene_ready_lock.prototype.IsHidden(self) + return true +end +function modifier_cutscene_ready_lock.prototype.IsPurgable(self) + return false +end +function modifier_cutscene_ready_lock.prototype.CheckState(self) + return {[MODIFIER_STATE_STUNNED] = true, [MODIFIER_STATE_COMMAND_RESTRICTED] = true} +end +modifier_cutscene_ready_lock = __TS__Decorate( + modifier_cutscene_ready_lock, + modifier_cutscene_ready_lock, + {registerModifier(nil)}, + {kind = "class", name = "modifier_cutscene_ready_lock"} +) +____exports.modifier_cutscene_ready_lock = modifier_cutscene_ready_lock +return ____exports diff --git a/scripts/vscripts/abilities/modifiers/modifier_donate_item.lua b/scripts/vscripts/abilities/modifiers/modifier_donate_item.lua new file mode 100644 index 0000000..1546dde --- /dev/null +++ b/scripts/vscripts/abilities/modifiers/modifier_donate_item.lua @@ -0,0 +1,51 @@ +local ____lualib = require("lualib_bundle") +local __TS__Class = ____lualib.__TS__Class +local __TS__ClassExtends = ____lualib.__TS__ClassExtends +local __TS__Decorate = ____lualib.__TS__Decorate +local ____exports = {} +local ____dota_ts_adapter = require("lib.dota_ts_adapter") +local BaseModifier = ____dota_ts_adapter.BaseModifier +local registerModifier = ____dota_ts_adapter.registerModifier +____exports.modifier_donate_item = __TS__Class() +local modifier_donate_item = ____exports.modifier_donate_item +modifier_donate_item.name = "modifier_donate_item" +modifier_donate_item.____file_path = "scripts/vscripts/abilities/modifiers/modifier_donate_item.lua" +__TS__ClassExtends(modifier_donate_item, BaseModifier) +function modifier_donate_item.prototype.IsHidden(self) + return true +end +function modifier_donate_item.prototype.IsPurgable(self) + return false +end +function modifier_donate_item.prototype.IsPurgeException(self) + return false +end +function modifier_donate_item.prototype.RemoveOnDeath(self) + return false +end +function modifier_donate_item.prototype.CheckState(self) + return { + [MODIFIER_STATE_OUT_OF_GAME] = true, + [MODIFIER_STATE_INVULNERABLE] = true, + [MODIFIER_STATE_NOT_ON_MINIMAP] = true, + [MODIFIER_STATE_COMMAND_RESTRICTED] = true, + [MODIFIER_STATE_STUNNED] = true, + [MODIFIER_STATE_FLYING_FOR_PATHING_PURPOSES_ONLY] = true, + [MODIFIER_STATE_NO_HEALTH_BAR] = true, + [MODIFIER_STATE_NO_UNIT_COLLISION] = true, + [MODIFIER_STATE_INVISIBLE] = true + } +end +function modifier_donate_item.prototype.OnDestroy(self) + if not IsServer() then + return + end +end +modifier_donate_item = __TS__Decorate( + modifier_donate_item, + modifier_donate_item, + {registerModifier(nil)}, + {kind = "class", name = "modifier_donate_item"} +) +____exports.modifier_donate_item = modifier_donate_item +return ____exports diff --git a/scripts/vscripts/abilities/modifiers/modifier_equipment_stats.lua b/scripts/vscripts/abilities/modifiers/modifier_equipment_stats.lua new file mode 100644 index 0000000..bf5b19a --- /dev/null +++ b/scripts/vscripts/abilities/modifiers/modifier_equipment_stats.lua @@ -0,0 +1,164 @@ +local ____lualib = require("lualib_bundle") +local __TS__Class = ____lualib.__TS__Class +local __TS__ClassExtends = ____lualib.__TS__ClassExtends +local __TS__Number = ____lualib.__TS__Number +local __TS__NumberIsFinite = ____lualib.__TS__NumberIsFinite +local __TS__Decorate = ____lualib.__TS__Decorate +local ____exports = {} +local ____dota_ts_adapter = require("lib.dota_ts_adapter") +local BaseModifier = ____dota_ts_adapter.BaseModifier +local registerModifier = ____dota_ts_adapter.registerModifier +local ____luck = require("utils.luck") +local addLuck = ____luck.addLuck +local reduceLuck = ____luck.reduceLuck +____exports.modifier_equipment_stats = __TS__Class() +local modifier_equipment_stats = ____exports.modifier_equipment_stats +modifier_equipment_stats.name = "modifier_equipment_stats" +modifier_equipment_stats.____file_path = "scripts/vscripts/abilities/modifiers/modifier_equipment_stats.lua" +__TS__ClassExtends(modifier_equipment_stats, BaseModifier) +function modifier_equipment_stats.prototype.____constructor(self, ...) + BaseModifier.prototype.____constructor(self, ...) + self.appliedLuck = 0 + self.strengthPct = 0 + self.intellectPct = 0 + self.damagePct = 0 + self.damageReductionPct = 0 + self.spellAmpPct = 0 + self.strengthBonus = 0 + self.intellectBonus = 0 +end +function modifier_equipment_stats.prototype.IsHidden(self) + return true +end +function modifier_equipment_stats.prototype.IsPurgable(self) + return false +end +function modifier_equipment_stats.prototype.RemoveOnDeath(self) + return false +end +function modifier_equipment_stats.prototype.OnCreated(self, params) + if not IsServer() then + return + end + self:readParams(params) + self:applyLuck() + self:StartIntervalThink(1) + self:updateDerivedAttributeBonuses() +end +function modifier_equipment_stats.prototype.OnRefresh(self, params) + if not IsServer() then + return + end + self:clearLuck() + self:readParams(params) + self:applyLuck() + self:updateDerivedAttributeBonuses() +end +function modifier_equipment_stats.prototype.OnDestroy(self) + if not IsServer() then + return + end + self:clearLuck() +end +function modifier_equipment_stats.prototype.OnIntervalThink(self) + if not IsServer() then + return + end + self:updateDerivedAttributeBonuses() +end +function modifier_equipment_stats.prototype.DeclareFunctions(self) + return { + MODIFIER_PROPERTY_STATS_STRENGTH_BONUS, + MODIFIER_PROPERTY_STATS_INTELLECT_BONUS, + MODIFIER_PROPERTY_TOTALDAMAGEOUTGOING_PERCENTAGE, + MODIFIER_PROPERTY_INCOMING_DAMAGE_PERCENTAGE, + MODIFIER_PROPERTY_SPELL_AMPLIFY_PERCENTAGE + } +end +function modifier_equipment_stats.prototype.GetModifierBonusStats_Strength(self) + return self.strengthBonus +end +function modifier_equipment_stats.prototype.GetModifierBonusStats_Intellect(self) + return self.intellectBonus +end +function modifier_equipment_stats.prototype.GetModifierTotalDamageOutgoing_Percentage(self) + return self.damagePct +end +function modifier_equipment_stats.prototype.GetModifierIncomingDamage_Percentage(self) + return -self.damageReductionPct +end +function modifier_equipment_stats.prototype.GetModifierSpellAmplify_Percentage(self) + return self.spellAmpPct +end +function modifier_equipment_stats.prototype.readParams(self, params) + self.appliedLuck = 0 + self.strengthPct = __TS__Number(params.strengthPct or "0") + self.intellectPct = __TS__Number(params.intellectPct or "0") + self.damagePct = __TS__Number(params.damagePct or "0") + self.damageReductionPct = __TS__Number(params.damageReductionPct or "0") + self.spellAmpPct = __TS__Number(params.spellAmpPct or "0") + self.appliedLuck = __TS__Number(params.luckPct or "0") + if not __TS__NumberIsFinite(self.appliedLuck) then + self.appliedLuck = 0 + end + if not __TS__NumberIsFinite(self.strengthPct) then + self.strengthPct = 0 + end + if not __TS__NumberIsFinite(self.intellectPct) then + self.intellectPct = 0 + end + if not __TS__NumberIsFinite(self.damagePct) then + self.damagePct = 0 + end + if not __TS__NumberIsFinite(self.damageReductionPct) then + self.damageReductionPct = 0 + end + if not __TS__NumberIsFinite(self.spellAmpPct) then + self.spellAmpPct = 0 + end +end +function modifier_equipment_stats.prototype.updateDerivedAttributeBonuses(self) + local hero = self:GetParent() + if not hero or not hero:IsRealHero() then + self.strengthBonus = 0 + self.intellectBonus = 0 + return + end + local baseStrength = math.max( + 0, + hero:GetStrength() - self.strengthBonus + ) + local baseIntellect = math.max( + 0, + hero:GetIntellect(false) - self.intellectBonus + ) + self.strengthBonus = math.floor(baseStrength * (self.strengthPct / 100)) + self.intellectBonus = math.floor(baseIntellect * (self.intellectPct / 100)) + self:ForceRefresh() +end +function modifier_equipment_stats.prototype.applyLuck(self) + local hero = self:GetParent() + if not hero or not hero:IsRealHero() then + return + end + if self.appliedLuck ~= 0 then + addLuck(nil, hero, self.appliedLuck) + end +end +function modifier_equipment_stats.prototype.clearLuck(self) + local hero = self:GetParent() + if not hero or not hero:IsRealHero() then + return + end + if self.appliedLuck ~= 0 then + reduceLuck(nil, hero, self.appliedLuck) + end +end +modifier_equipment_stats = __TS__Decorate( + modifier_equipment_stats, + modifier_equipment_stats, + {registerModifier(nil)}, + {kind = "class", name = "modifier_equipment_stats"} +) +____exports.modifier_equipment_stats = modifier_equipment_stats +return ____exports diff --git a/scripts/vscripts/abilities/modifiers/modifier_general_arc.lua b/scripts/vscripts/abilities/modifiers/modifier_general_arc.lua new file mode 100644 index 0000000..9fe4dac --- /dev/null +++ b/scripts/vscripts/abilities/modifiers/modifier_general_arc.lua @@ -0,0 +1,133 @@ +local ____lualib = require("lualib_bundle") +local __TS__Class = ____lualib.__TS__Class +local __TS__ClassExtends = ____lualib.__TS__ClassExtends +local __TS__Decorate = ____lualib.__TS__Decorate +local ____exports = {} +local ____dota_ts_adapter = require("lib.dota_ts_adapter") +local BaseModifierMotionBoth = ____dota_ts_adapter.BaseModifierMotionBoth +local registerModifier = ____dota_ts_adapter.registerModifier +____exports.modifier_general_arc = __TS__Class() +local modifier_general_arc = ____exports.modifier_general_arc +modifier_general_arc.name = "modifier_general_arc" +modifier_general_arc.____file_path = "scripts/vscripts/abilities/modifiers/modifier_general_arc.lua" +__TS__ClassExtends(modifier_general_arc, BaseModifierMotionBoth) +function modifier_general_arc.prototype.____constructor(self, ...) + BaseModifierMotionBoth.prototype.____constructor(self, ...) + self.direction = Vector(0, 0, 0) + self.interrupted = false + self.duration = 0 + self.distance = 0 + self.speed = 0 + self.height = 0 + self.const1 = 0 + self.const2 = 0 + self.isFlail = false +end +function modifier_general_arc.prototype.IsHidden(self) + return true +end +function modifier_general_arc.prototype.IsPurgable(self) + return false +end +function modifier_general_arc.prototype.RemoveOnDeath(self) + return false +end +function modifier_general_arc.prototype.OnCreated(self, params) + if IsClient() then + return + end + local parent = self:GetParent() + local origin = parent:GetOrigin() + if not not params.isFlail then + self:SetStackCount(1) + end + if not not params.x then + local targetOrigin = Vector(params.x, params.y, 0) + local dir = targetOrigin - origin + dir = dir:Normalized() + self.direction = dir + self.distance = (targetOrigin - origin):Length() + else + self.direction = parent:GetForwardVector() + self.distance = params.distance + end + self.duration = params.duration + self.height = params.height + self.speed = self.distance / self.duration + if self:ApplyHorizontalMotionController() == false or self:ApplyVerticalMotionController() == false then + self:Destroy() + return + end + local posEnd = origin + self.direction * self.distance + local heightStart = GetGroundHeight(origin, parent) + local heightEnd = GetGroundHeight(posEnd, parent) + local tempmin = heightStart + local tempmax = heightEnd + if tempmin > tempmax then + tempmin = tempmax + tempmax = tempmin + end + local delta = (tempmax - tempmin) * 2 / 3 + local heightMax = tempmin + delta + self.height + heightEnd = heightEnd - heightStart + heightMax = heightMax - heightStart + if heightMax < heightEnd then + heightMax = heightEnd + 0.01 + end + if heightMax <= 0 then + heightMax = 0.01 + end + local durationEnd = (1 + math.sqrt(1 - heightEnd / heightMax)) / 2 + self.const1 = 4 * heightMax * durationEnd / self.duration + self.const2 = 4 * heightMax * durationEnd * durationEnd / (self.duration * self.duration) +end +function modifier_general_arc.prototype.OnDestroy(self) + if IsClient() then + return + end + local parent = self:GetParent() + parent:RemoveHorizontalMotionController(self) + parent:RemoveVerticalMotionController(self) +end +function modifier_general_arc.prototype.GetVerticalSpeed(self, time) + return self.const1 - 2 * self.const2 * time +end +function modifier_general_arc.prototype.UpdateHorizontalMotion(self, me, dt) + local pos = me:GetOrigin() + self.direction * self.speed * dt + me:SetOrigin(pos) +end +function modifier_general_arc.prototype.UpdateVerticalMotion(self, me, dt) + local pos = me:GetOrigin() + local time = self:GetElapsedTime() + local height = pos.z + local speed = self:GetVerticalSpeed(time) + pos.z = height + speed * dt + me:SetOrigin(pos) +end +function modifier_general_arc.prototype.OnHorizontalMotionInterrupted(self) + self.interrupted = true +end +function modifier_general_arc.prototype.OnVerticalMotionInterrupted(self) + self:Destroy() +end +function modifier_general_arc.prototype.DeclareFunctions(self) + return {MODIFIER_PROPERTY_OVERRIDE_ANIMATION} +end +function modifier_general_arc.prototype.CheckState(self) + return {[MODIFIER_STATE_STUNNED] = true} +end +function modifier_general_arc.prototype.GetOverrideAnimation(self) + if self:GetStackCount() == 1 then + return ACT_DOTA_FLAIL + else + return ACT_DOTA_OVERRIDE_ABILITY_2 + end +end +modifier_general_arc = __TS__Decorate( + modifier_general_arc, + modifier_general_arc, + {registerModifier(nil)}, + {kind = "class", name = "modifier_general_arc"} +) +____exports.modifier_general_arc = modifier_general_arc +return ____exports diff --git a/scripts/vscripts/abilities/modifiers/modifier_general_fired.lua b/scripts/vscripts/abilities/modifiers/modifier_general_fired.lua new file mode 100644 index 0000000..fef1719 --- /dev/null +++ b/scripts/vscripts/abilities/modifiers/modifier_general_fired.lua @@ -0,0 +1,126 @@ +local ____lualib = require("lualib_bundle") +local __TS__Class = ____lualib.__TS__Class +local __TS__ClassExtends = ____lualib.__TS__ClassExtends +local __TS__Decorate = ____lualib.__TS__Decorate +local ____exports = {} +local ____dota_ts_adapter = require("lib.dota_ts_adapter") +local BaseModifier = ____dota_ts_adapter.BaseModifier +local registerModifier = ____dota_ts_adapter.registerModifier +____exports.modifier_general_fired = __TS__Class() +local modifier_general_fired = ____exports.modifier_general_fired +modifier_general_fired.name = "modifier_general_fired" +modifier_general_fired.____file_path = "scripts/vscripts/abilities/modifiers/modifier_general_fired.lua" +__TS__ClassExtends(modifier_general_fired, BaseModifier) +function modifier_general_fired.prototype.____constructor(self, ...) + BaseModifier.prototype.____constructor(self, ...) + self.isBurned = false + self.STACKS_TO_BURN = 0 + self.STACKS_TO_ARMOR_REDUCE = 70 +end +function modifier_general_fired.prototype.IsDebuff(self) + return true +end +function modifier_general_fired.prototype.IsPurgable(self) + return true +end +function modifier_general_fired.prototype.OnCreated(self) + if IsServer() then + self:CheckBurnedState() + self:StartIntervalThink(1) + end +end +function modifier_general_fired.prototype.OnRefresh(self) + if IsServer() then + self:CheckBurnedState() + end +end +function modifier_general_fired.prototype.OnDestroy(self) + if IsClient() then + return + end + if self.particleId then + ParticleManager:DestroyParticle(self.particleId, false) + ParticleManager:ReleaseParticleIndex(self.particleId) + end + if self.burnedParticleId then + ParticleManager:DestroyParticle(self.burnedParticleId, false) + ParticleManager:ReleaseParticleIndex(self.burnedParticleId) + end + self.burnedParticleId = nil +end +function modifier_general_fired.prototype.OnIntervalThink(self) + local frozeModifier = self:GetParent():FindModifierByName("modifier_general_froze") + local frozeStacks = frozeModifier and frozeModifier:GetStackCount() or 0 + if self:GetStackCount() > 0 then + local decreaseAmount = math.max( + 1, + math.ceil(self:GetStackCount() * 0.1) + ) + self:SetStackCount(self:GetStackCount() - decreaseAmount) + self:SetDuration(1.01, true) + else + self:GetParent():RemoveModifierByName("modifier_general_fired") + end + if frozeStacks > 0 then + local decreaseAmount = math.min( + self:GetStackCount(), + frozeStacks + ) + self:SetStackCount(self:GetStackCount() - decreaseAmount) + if frozeModifier then + frozeModifier:SetStackCount(frozeStacks - decreaseAmount) + end + end + if self:GetStackCount() > 0 then + ApplyDamage({ + victim = self:GetParent(), + attacker = self:GetCaster() or self:GetParent(), + damage = self:GetStackCount() * 3, + damage_type = DAMAGE_TYPE_MAGICAL, + ability = self:GetAbility() + }) + end +end +function modifier_general_fired.prototype.CheckState(self) + if self.isBurned and self:GetStackCount() >= self.STACKS_TO_ARMOR_REDUCE then + return {[MODIFIER_STATE_PASSIVES_DISABLED] = true} + end + return {} +end +function modifier_general_fired.prototype.DeclareFunctions(self) + return {MODIFIER_PROPERTY_PHYSICAL_ARMOR_BONUS, MODIFIER_PROPERTY_MAGICAL_RESISTANCE_BONUS, MODIFIER_PROPERTY_PHYSICAL_ARMOR_BONUS} +end +function modifier_general_fired.prototype.GetModifierPhysicalArmorBonus(self) + return self:GetStackCount() >= self.STACKS_TO_ARMOR_REDUCE and -self:GetStackCount() * 0.25 or 0 +end +function modifier_general_fired.prototype.GetModifierMagicalResistanceBonus(self) + return self:GetStackCount() >= self.STACKS_TO_ARMOR_REDUCE and -self:GetStackCount() * 0.25 or 0 +end +function modifier_general_fired.prototype.GetEffectName(self) + return "particles/units/heroes/hero_huskar/huskar_burning_spear_debuff.vpcf" +end +function modifier_general_fired.prototype.GetEffectAttachType(self) + return PATTACH_ABSORIGIN_FOLLOW +end +function modifier_general_fired.prototype.CheckBurnedState(self) + local parent = self:GetParent() + if self:GetStackCount() >= self.STACKS_TO_BURN and not self.isBurned then + self.isBurned = true + EmitSoundOn("Hero_Huskar.BurningSpear.Target", parent) + elseif self:GetStackCount() < self.STACKS_TO_BURN and self.isBurned then + self.isBurned = false + if self.burnedParticleId then + ParticleManager:DestroyParticle(self.burnedParticleId, false) + ParticleManager:ReleaseParticleIndex(self.burnedParticleId) + self.burnedParticleId = nil + end + end +end +modifier_general_fired = __TS__Decorate( + modifier_general_fired, + modifier_general_fired, + {registerModifier(nil)}, + {kind = "class", name = "modifier_general_fired"} +) +____exports.modifier_general_fired = modifier_general_fired +return ____exports diff --git a/scripts/vscripts/abilities/modifiers/modifier_general_froze.lua b/scripts/vscripts/abilities/modifiers/modifier_general_froze.lua new file mode 100644 index 0000000..c95a2c1 --- /dev/null +++ b/scripts/vscripts/abilities/modifiers/modifier_general_froze.lua @@ -0,0 +1,133 @@ +local ____lualib = require("lualib_bundle") +local __TS__Class = ____lualib.__TS__Class +local __TS__ClassExtends = ____lualib.__TS__ClassExtends +local __TS__Decorate = ____lualib.__TS__Decorate +local ____exports = {} +local ____dota_ts_adapter = require("lib.dota_ts_adapter") +local BaseModifier = ____dota_ts_adapter.BaseModifier +local registerModifier = ____dota_ts_adapter.registerModifier +____exports.modifier_general_froze = __TS__Class() +local modifier_general_froze = ____exports.modifier_general_froze +modifier_general_froze.name = "modifier_general_froze" +modifier_general_froze.____file_path = "scripts/vscripts/abilities/modifiers/modifier_general_froze.lua" +__TS__ClassExtends(modifier_general_froze, BaseModifier) +function modifier_general_froze.prototype.____constructor(self, ...) + BaseModifier.prototype.____constructor(self, ...) + self.isFrozen = false + self.STACKS_TO_FREEZE = 100 + self.STACKS_TO_DAMAGEUP = 30 +end +function modifier_general_froze.prototype.IsDebuff(self) + return true +end +function modifier_general_froze.prototype.IsPurgable(self) + return true +end +function modifier_general_froze.prototype.OnCreated(self) + if IsServer() then + self:CheckFrozenState() + self:StartIntervalThink(1) + end +end +function modifier_general_froze.prototype.OnRefresh(self) + if IsServer() then + self:CheckFrozenState() + end +end +function modifier_general_froze.prototype.OnDestroy(self) + if IsClient() then + return + end + if self.particleId then + ParticleManager:DestroyParticle(self.particleId, false) + ParticleManager:ReleaseParticleIndex(self.particleId) + end + if self.frozenParticleId then + ParticleManager:DestroyParticle(self.frozenParticleId, false) + ParticleManager:ReleaseParticleIndex(self.frozenParticleId) + end + self.frozenParticleId = nil +end +function modifier_general_froze.prototype.OnIntervalThink(self) + if not self:GetParent() then + return + end + if "modifier_general_fired" ~= nil then + local firedModifier = self:GetParent():FindModifierByName("modifier_general_fired") + local firedStacks = firedModifier and firedModifier:GetStackCount() or 0 + if self:GetStackCount() > 0 then + local decreaseAmount = math.max( + 1, + math.ceil(self:GetStackCount() * 0.1) + ) + self:SetStackCount(self:GetStackCount() - decreaseAmount) + self:SetDuration(1.01, true) + else + self:GetParent():RemoveModifierByName("modifier_general_froze") + end + if firedStacks > 0 then + local decreaseAmount = math.min( + self:GetStackCount(), + firedStacks + ) + self:SetStackCount(self:GetStackCount() - decreaseAmount) + if firedModifier then + firedModifier:SetStackCount(firedStacks - decreaseAmount) + end + end + end +end +function modifier_general_froze.prototype.CheckState(self) + if self.isFrozen and self:GetParent():IsBoss() then + return {[MODIFIER_STATE_FROZEN] = true, [MODIFIER_STATE_ROOTED] = true} + end + return {} +end +function modifier_general_froze.prototype.DeclareFunctions(self) + return {MODIFIER_PROPERTY_MOVESPEED_BONUS_PERCENTAGE, MODIFIER_PROPERTY_ATTACKSPEED_PERCENTAGE, MODIFIER_PROPERTY_INCOMING_DAMAGE_PERCENTAGE, MODIFIER_PROPERTY_HEALTH_REGEN_PERCENTAGE} +end +function modifier_general_froze.prototype.GetModifierMoveSpeedBonus_Percentage(self) + return -self:GetStackCount() +end +function modifier_general_froze.prototype.GetModifierAttackSpeedPercentage(self) + return -self:GetStackCount() +end +function modifier_general_froze.prototype.GetEffectName(self) + return "particles/generic_gameplay/generic_slowed_cold.vpcf" +end +function modifier_general_froze.prototype.GetModifierIncomingDamage_Percentage(self) + return self:GetStackCount() >= self.STACKS_TO_DAMAGEUP and self:GetStackCount() or 0 +end +function modifier_general_froze.prototype.GetModifierHealthRegenPercentage(self) + return -self:GetStackCount() * 0.01 +end +function modifier_general_froze.prototype.GetEffectAttachType(self) + return PATTACH_ABSORIGIN_FOLLOW +end +function modifier_general_froze.prototype.CheckFrozenState(self) + local parent = self:GetParent() + if self:GetStackCount() >= self.STACKS_TO_FREEZE and not self.isFrozen then + self.isFrozen = true + self.frozenParticleId = ParticleManager:CreateParticle("particles/units/heroes/hero_crystalmaiden/maiden_frostbite_buff.vpcf", PATTACH_ABSORIGIN_FOLLOW, parent) + EmitSoundOn("Hero_Ancient_Apparition.ColdFeetCast", parent) + elseif self:GetStackCount() < self.STACKS_TO_FREEZE and self.isFrozen then + self.isFrozen = false + if self.frozenParticleId then + ParticleManager:DestroyParticle(self.frozenParticleId, false) + ParticleManager:ReleaseParticleIndex(self.frozenParticleId) + self.frozenParticleId = nil + end + parent:RemoveModifierByName("modifier_general_silenced") + end +end +function modifier_general_froze.prototype.GetModifierStateImmune(self) + return {[MODIFIER_STATE_FROZEN] = true, [MODIFIER_STATE_ROOTED] = true} +end +modifier_general_froze = __TS__Decorate( + modifier_general_froze, + modifier_general_froze, + {registerModifier(nil)}, + {kind = "class", name = "modifier_general_froze"} +) +____exports.modifier_general_froze = modifier_general_froze +return ____exports diff --git a/scripts/vscripts/abilities/modifiers/modifier_general_hunger.lua b/scripts/vscripts/abilities/modifiers/modifier_general_hunger.lua new file mode 100644 index 0000000..9870c66 --- /dev/null +++ b/scripts/vscripts/abilities/modifiers/modifier_general_hunger.lua @@ -0,0 +1,223 @@ +local ____lualib = require("lualib_bundle") +local __TS__Class = ____lualib.__TS__Class +local __TS__ClassExtends = ____lualib.__TS__ClassExtends +local __TS__Decorate = ____lualib.__TS__Decorate +local ____exports = {} +local ____dota_ts_adapter = require("lib.dota_ts_adapter") +local BaseModifier = ____dota_ts_adapter.BaseModifier +local registerModifier = ____dota_ts_adapter.registerModifier +____exports.modifier_general_hunger = __TS__Class() +local modifier_general_hunger = ____exports.modifier_general_hunger +modifier_general_hunger.name = "modifier_general_hunger" +modifier_general_hunger.____file_path = "scripts/vscripts/abilities/modifiers/modifier_general_hunger.lua" +__TS__ClassExtends(modifier_general_hunger, BaseModifier) +function modifier_general_hunger.prototype.____constructor(self, ...) + BaseModifier.prototype.____constructor(self, ...) + self.maxstack = 100 +end +function modifier_general_hunger.prototype.IsHidden(self) + return false +end +function modifier_general_hunger.prototype.IsDebuff(self) + return false +end +function modifier_general_hunger.prototype.IsPurgable(self) + return false +end +function modifier_general_hunger.prototype.OnCreated(self, params) + if IsServer() then + self:SetDuration(3.01, true) + self:StartIntervalThink(3) + end +end +function modifier_general_hunger.prototype.OnRefresh(self, params) + if IsServer() then + self:SetDuration(3.01, true) + self:StartIntervalThink(3) + end +end +function modifier_general_hunger.prototype.OnIntervalThink(self) + if IsServer() then + self:SetStackCount(self:GetStackCount() - 1) + self:SetDuration(3.01, true) + if self:GetStackCount() < 1 then + self:Destroy() + end + end +end +function modifier_general_hunger.prototype.DeclareFunctions(self) + return {MODIFIER_PROPERTY_STATS_AGILITY_BONUS, MODIFIER_PROPERTY_STATS_INTELLECT_BONUS, MODIFIER_PROPERTY_STATS_STRENGTH_BONUS} +end +function modifier_general_hunger.prototype.GetModifierBonusStats_Strength(self) + if not IsServer() then + return 0 + end + local parent = self:GetParent() + if not parent then + return 0 + end + if not parent:IsRealHero() then + return 0 + end + do + local function ____catch(e) + return true, 0 + end + local ____try, ____hasReturned, ____returnValue = pcall(function() + local hero = parent + local baseStr = hero:GetBaseStrength() or 0 + return true, math.ceil(baseStr * (self:GetStackCount() * 0.01)) + end) + if not ____try then + ____hasReturned, ____returnValue = ____catch(____hasReturned) + end + if ____hasReturned then + return ____returnValue + end + end +end +function modifier_general_hunger.prototype.GetModifierBonusStats_Agility(self) + if not IsServer() then + return 0 + end + local parent = self:GetParent() + if not parent then + return 0 + end + if not parent:IsRealHero() then + return 0 + end + local hero = parent + local baseAgi = hero:GetBaseAgility() + return math.ceil(baseAgi * (self:GetStackCount() * 0.01)) +end +function modifier_general_hunger.prototype.GetModifierBonusStats_Intellect(self) + if not IsServer() then + return 0 + end + local parent = self:GetParent() + if not parent then + return 0 + end + if not parent:IsRealHero() then + return 0 + end + do + local function ____catch(e) + return true, 0 + end + local ____try, ____hasReturned, ____returnValue = pcall(function() + local hero = parent + local baseInt = hero:GetBaseIntellect() or 0 + return true, math.ceil(baseInt * (self:GetStackCount() * 0.01)) + end) + if not ____try then + ____hasReturned, ____returnValue = ____catch(____hasReturned) + end + if ____hasReturned then + return ____returnValue + end + end +end +function modifier_general_hunger.prototype.IncrementStackCount(self) + if self:GetStackCount() >= self.maxstack then + self:SetStackCount(self.maxstack) + else + BaseModifier.prototype.SetStackCount( + self, + self:GetStackCount() + 1 + ) + end +end +function modifier_general_hunger.prototype.GetTexture(self) + return "life_stealer_rage" +end +modifier_general_hunger = __TS__Decorate( + modifier_general_hunger, + modifier_general_hunger, + {registerModifier(nil)}, + {kind = "class", name = "modifier_general_hunger"} +) +____exports.modifier_general_hunger = modifier_general_hunger +--- Несколько экземпляров на юните — у каждого свой таймер (каждый приём пищи живёт 60 сек). +____exports.modifier_general_hunger_fulled = __TS__Class() +local modifier_general_hunger_fulled = ____exports.modifier_general_hunger_fulled +modifier_general_hunger_fulled.name = "modifier_general_hunger_fulled" +modifier_general_hunger_fulled.____file_path = "scripts/vscripts/abilities/modifiers/modifier_general_hunger.lua" +__TS__ClassExtends(modifier_general_hunger_fulled, BaseModifier) +function modifier_general_hunger_fulled.prototype.IsHidden(self) + return false +end +function modifier_general_hunger_fulled.prototype.IsDebuff(self) + return false +end +function modifier_general_hunger_fulled.prototype.IsPurgable(self) + return false +end +function modifier_general_hunger_fulled.prototype.GetAttributes(self) + return MODIFIER_ATTRIBUTE_MULTIPLE +end +function modifier_general_hunger_fulled.prototype.OnCreated(self, params) + if IsServer() then + local duration = params.duration or 60 + self:SetDuration(duration, true) + end +end +modifier_general_hunger_fulled = __TS__Decorate( + modifier_general_hunger_fulled, + modifier_general_hunger_fulled, + {registerModifier(nil)}, + {kind = "class", name = "modifier_general_hunger_fulled"} +) +____exports.modifier_general_hunger_fulled = modifier_general_hunger_fulled +--- Максимум «слотов» сытости для еды с баффами (энерго-напиток и т.п.). Каждый слот = один приём, свой таймер 60 сек. +____exports.BUFF_FOOD_MAX_SLOTS = 3 +--- Слот сытости от еды с баффами. Вешается только такими предметами (energy_drink и т.д.). Лимит BUFF_FOOD_MAX_SLOTS штук. +____exports.modifier_buff_food_slot = __TS__Class() +local modifier_buff_food_slot = ____exports.modifier_buff_food_slot +modifier_buff_food_slot.name = "modifier_buff_food_slot" +modifier_buff_food_slot.____file_path = "scripts/vscripts/abilities/modifiers/modifier_general_hunger.lua" +__TS__ClassExtends(modifier_buff_food_slot, BaseModifier) +function modifier_buff_food_slot.prototype.IsHidden(self) + return true +end +function modifier_buff_food_slot.prototype.IsDebuff(self) + return false +end +function modifier_buff_food_slot.prototype.IsPurgable(self) + return false +end +function modifier_buff_food_slot.prototype.GetAttributes(self) + return MODIFIER_ATTRIBUTE_MULTIPLE +end +function modifier_buff_food_slot.prototype.OnCreated(self, params) + if IsServer() then + local duration = params.duration or 60 + self:SetDuration(duration, true) + end +end +function modifier_buff_food_slot.prototype.GetTexture(self) + return "life_stealer_rage" +end +modifier_buff_food_slot = __TS__Decorate( + modifier_buff_food_slot, + modifier_buff_food_slot, + {registerModifier(nil)}, + {kind = "class", name = "modifier_buff_food_slot"} +) +____exports.modifier_buff_food_slot = modifier_buff_food_slot +--- Считает, сколько слотов бафф-еды сейчас на юните (для проверки лимита). +function ____exports.getBuffFoodSlotCount(self, unit) + local count = 0 + do + local i = 0 + while i < unit:GetModifierCount() do + if unit:GetModifierNameByIndex(i) == ____exports.modifier_buff_food_slot.name then + count = count + 1 + end + i = i + 1 + end + end + return count +end +return ____exports diff --git a/scripts/vscripts/abilities/modifiers/modifier_general_knockback.lua b/scripts/vscripts/abilities/modifiers/modifier_general_knockback.lua new file mode 100644 index 0000000..4a28847 --- /dev/null +++ b/scripts/vscripts/abilities/modifiers/modifier_general_knockback.lua @@ -0,0 +1,154 @@ +local ____lualib = require("lualib_bundle") +local __TS__Class = ____lualib.__TS__Class +local __TS__ClassExtends = ____lualib.__TS__ClassExtends +local __TS__Decorate = ____lualib.__TS__Decorate +local ____exports = {} +local ____dota_ts_adapter = require("lib.dota_ts_adapter") +local BaseModifierMotionBoth = ____dota_ts_adapter.BaseModifierMotionBoth +local registerModifier = ____dota_ts_adapter.registerModifier +____exports.modifier_general_knockback = __TS__Class() +local modifier_general_knockback = ____exports.modifier_general_knockback +modifier_general_knockback.name = "modifier_general_knockback" +modifier_general_knockback.____file_path = "scripts/vscripts/abilities/modifiers/modifier_general_knockback.lua" +__TS__ClassExtends(modifier_general_knockback, BaseModifierMotionBoth) +function modifier_general_knockback.prototype.____constructor(self, ...) + BaseModifierMotionBoth.prototype.____constructor(self, ...) + self.distance = 0 + self.height = 0 + self.duration = 0 + self.anim = 0 + self.direction = 0 + self.tree = 0 + self.hVelocity = 0 + self.vVelocity = 0 + self.gravity = 0 + self.stun = false + self.flail = false + self.interrupted = false +end +function modifier_general_knockback.prototype.IsHidden(self) + return true +end +function modifier_general_knockback.prototype.IsPurgable(self) + return false +end +function modifier_general_knockback.prototype.GetAttributes(self) + return MODIFIER_ATTRIBUTE_MULTIPLE +end +function modifier_general_knockback.prototype.OnCreated(self, params) + if IsServer() then + self.distance = params.distance or 0 + self.height = params.height or 0 + self.duration = params.duration or 0 + if not not params.direction_x and not not params.direction_y then + self.direction = Vector(params.direction_x, params.direction_y, 0):Normalized() + else + self.direction = -self:GetParent():GetForwardVector() + end + self.tree = params.tree_destroy_radius or self:GetParent():GetHullRadius() + self.stun = not not params.isStun + self.flail = not not params.isFlail + if self.duration == 0 then + return self:Destroy() + end + self.parent = self:GetParent() + self.origin = self.parent:GetOrigin() + self.hVelocity = self.distance / self.duration + local half_duration = self.duration / 2 + self.gravity = 2 * self.height / (half_duration * half_duration) + self.vVelocity = self.gravity * half_duration + if self.distance > 0 then + if self:ApplyHorizontalMotionController() == false then + return self:Destroy() + end + end + if self.height >= 0 then + if self:ApplyVerticalMotionController() == false then + return self:Destroy() + end + end + if self.flail then + self:SetStackCount(1) + elseif self.stun then + self:SetStackCount(2) + end + else + self.anim = self:GetStackCount() + self:SetStackCount(0) + end +end +function modifier_general_knockback.prototype.OnDestroy(self) + if IsClient() then + return + end + if not self.interrupted then + if self.tree > 0 then + GridNav:DestroyTreesAroundPoint( + self.parent:GetOrigin(), + self.tree, + true + ) + end + end + self.parent:InterruptMotionControllers(true) +end +function modifier_general_knockback.prototype.DeclareFunctions(self) + return {MODIFIER_PROPERTY_OVERRIDE_ANIMATION} +end +function modifier_general_knockback.prototype.GetOverrideAnimation(self) + if self.anim == 1 then + return ACT_DOTA_FLAIL + elseif self.anim == 2 then + return ACT_DOTA_DISABLED + end + return ACT_DOTA_FLAIL +end +function modifier_general_knockback.prototype.CheckState(self) + return {[MODIFIER_STATE_STUNNED] = self.stun} +end +function modifier_general_knockback.prototype.UpdateHorizontalMotion(self, me, dt) + local target = self.direction * self.distance * (dt / self.duration) + self.parent:SetOrigin(self.parent:GetOrigin() + target) +end +function modifier_general_knockback.prototype.UpdateVerticalMotion(self, me, dt) + local time = dt / self.duration + self.parent:SetOrigin(self.parent:GetOrigin() + Vector(0, 0, self.vVelocity * dt)) + self.vVelocity = self.vVelocity - self.gravity * dt +end +function modifier_general_knockback.prototype.OnHorizontalMotionInterrupted(self) + if IsClient() then + return + end + self.interrupted = true + self:Destroy() +end +function modifier_general_knockback.prototype.OnVerticalMotionInterrupted(self) + if IsClient() then + return + end + self.interrupted = true + self:Destroy() +end +function modifier_general_knockback.prototype.GetEffectName(self) + if IsClient() then + return "" + end + if self.stun then + return "particles/generic_gameplay/generic_stunned.vpcf" + end + return "" +end +function modifier_general_knockback.prototype.GetEffectAttachType(self) + if IsClient() then + return PATTACH_INVALID + end + return PATTACH_OVERHEAD_FOLLOW +end +modifier_general_knockback = __TS__Decorate( + modifier_general_knockback, + modifier_general_knockback, + {registerModifier(nil)}, + {kind = "class", name = "modifier_general_knockback"} +) +____exports.modifier_general_knockback = modifier_general_knockback +return ____exports diff --git a/scripts/vscripts/abilities/modifiers/modifier_general_silenced.lua b/scripts/vscripts/abilities/modifiers/modifier_general_silenced.lua new file mode 100644 index 0000000..0d1c48c --- /dev/null +++ b/scripts/vscripts/abilities/modifiers/modifier_general_silenced.lua @@ -0,0 +1,36 @@ +local ____lualib = require("lualib_bundle") +local __TS__Class = ____lualib.__TS__Class +local __TS__ClassExtends = ____lualib.__TS__ClassExtends +local __TS__Decorate = ____lualib.__TS__Decorate +local ____exports = {} +local ____dota_ts_adapter = require("lib.dota_ts_adapter") +local BaseModifier = ____dota_ts_adapter.BaseModifier +local registerModifier = ____dota_ts_adapter.registerModifier +____exports.modifier_general_silenced = __TS__Class() +local modifier_general_silenced = ____exports.modifier_general_silenced +modifier_general_silenced.name = "modifier_general_silenced" +modifier_general_silenced.____file_path = "scripts/vscripts/abilities/modifiers/modifier_general_silenced.lua" +__TS__ClassExtends(modifier_general_silenced, BaseModifier) +function modifier_general_silenced.prototype.IsDebuff(self) + return true +end +function modifier_general_silenced.prototype.IsPurgable(self) + return true +end +function modifier_general_silenced.prototype.CheckState(self) + return {[MODIFIER_STATE_SILENCED] = true} +end +function modifier_general_silenced.prototype.GetEffectName(self) + return "particles/generic_gameplay/generic_silenced.vpcf" +end +function modifier_general_silenced.prototype.GetEffectAttachType(self) + return PATTACH_OVERHEAD_FOLLOW +end +modifier_general_silenced = __TS__Decorate( + modifier_general_silenced, + modifier_general_silenced, + {registerModifier(nil)}, + {kind = "class", name = "modifier_general_silenced"} +) +____exports.modifier_general_silenced = modifier_general_silenced +return ____exports diff --git a/scripts/vscripts/abilities/modifiers/modifier_kitty_flex_catakeet.lua b/scripts/vscripts/abilities/modifiers/modifier_kitty_flex_catakeet.lua new file mode 100644 index 0000000..85a1431 --- /dev/null +++ b/scripts/vscripts/abilities/modifiers/modifier_kitty_flex_catakeet.lua @@ -0,0 +1,154 @@ +local ____lualib = require("lualib_bundle") +local __TS__Class = ____lualib.__TS__Class +local __TS__ClassExtends = ____lualib.__TS__ClassExtends +local __TS__Decorate = ____lualib.__TS__Decorate +local ____exports = {} +local ____dota_ts_adapter = require("lib.dota_ts_adapter") +local BaseModifier = ____dota_ts_adapter.BaseModifier +local registerModifier = ____dota_ts_adapter.registerModifier +local ____modifier_general_arc = require("abilities.modifiers.modifier_general_arc") +local modifier_general_arc = ____modifier_general_arc.modifier_general_arc +____exports.KITTY_FLEX_CATAKEET_MODEL = "models/items/courier/catakeet/catakeet_head_curious.vmdl" +--- Декоративная головка-курьер для эффекта kitty_flex: без выделения, урона и коллизий. +____exports.modifier_kitty_flex_catakeet = __TS__Class() +local modifier_kitty_flex_catakeet = ____exports.modifier_kitty_flex_catakeet +modifier_kitty_flex_catakeet.name = "modifier_kitty_flex_catakeet" +modifier_kitty_flex_catakeet.____file_path = "scripts/vscripts/abilities/modifiers/modifier_kitty_flex_catakeet.lua" +__TS__ClassExtends(modifier_kitty_flex_catakeet, BaseModifier) +function modifier_kitty_flex_catakeet.prototype.____constructor(self, ...) + BaseModifier.prototype.____constructor(self, ...) + self.bounceRadiusMin = 80 + self.bounceRadiusMax = 360 + self.bounceHeightMin = 110 + self.bounceHeightMax = 220 + self.bounceIntervalMin = 0.55 + self.bounceIntervalMax = 1 + self.lookIntervalMin = 0.35 + self.lookIntervalMax = 0.75 + self.nextLookAt = 0 + self.nextJumpAt = 0 +end +function modifier_kitty_flex_catakeet.prototype.IsHidden(self) + return true +end +function modifier_kitty_flex_catakeet.prototype.IsPurgable(self) + return false +end +function modifier_kitty_flex_catakeet.prototype.RemoveOnDeath(self) + return false +end +function modifier_kitty_flex_catakeet.prototype.OnCreated(self, params) + if not IsServer() then + return + end + self.bounceRadiusMin = params.bounce_radius_min or self.bounceRadiusMin + self.bounceRadiusMax = params.bounce_radius_max or self.bounceRadiusMax + self.bounceHeightMin = params.bounce_height_min or self.bounceHeightMin + self.bounceHeightMax = params.bounce_height_max or self.bounceHeightMax + self.bounceIntervalMin = params.bounce_interval_min or self.bounceIntervalMin + self.bounceIntervalMax = params.bounce_interval_max or self.bounceIntervalMax + self.lookIntervalMin = params.look_interval_min or self.lookIntervalMin + self.lookIntervalMax = params.look_interval_max or self.lookIntervalMax + local parent = self:GetParent() + parent:SetModel(____exports.KITTY_FLEX_CATAKEET_MODEL) + parent:SetOriginalModel(____exports.KITTY_FLEX_CATAKEET_MODEL) + parent:SetModelScale(1.35) + parent:SetHullRadius(1) + parent:SetControllableByPlayer(-1, false) + parent:SetMoveCapability(DOTA_UNIT_CAP_MOVE_GROUND) + parent:SetAttackCapability(DOTA_UNIT_CAP_NO_ATTACK) + local now = GameRules:GetGameTime() + self.nextLookAt = now + RandomFloat(0.05, 0.25) + self.nextJumpAt = now + RandomFloat(self.bounceIntervalMin, self.bounceIntervalMax) + self:StartIntervalThink(0.1) +end +function modifier_kitty_flex_catakeet.prototype.OnIntervalThink(self) + if not IsServer() then + return + end + local parent = self:GetParent() + if not parent or parent:IsNull() or not IsValidEntity(parent) then + return + end + local hero = self:GetCaster() + local now = GameRules:GetGameTime() + if now >= self.nextLookAt and hero and IsValidEntity(hero) and not hero:IsNull() then + parent:FaceTowards(hero:GetAbsOrigin()) + self.nextLookAt = now + RandomFloat(self.lookIntervalMin, self.lookIntervalMax) + end + if now < self.nextJumpAt or parent:HasModifier(modifier_general_arc.name) then + return + end + self:performBounceJumpNearHero(parent, hero) + self.nextJumpAt = now + RandomFloat(self.bounceIntervalMin, self.bounceIntervalMax) +end +function modifier_kitty_flex_catakeet.prototype.performBounceJumpNearHero(self, parent, hero) + local parentOrigin = parent:GetAbsOrigin() + local targetPos = parentOrigin + if hero and IsValidEntity(hero) and not hero:IsNull() then + local heroOrigin = hero:GetAbsOrigin() + local angle = RandomFloat(0, 2 * math.pi) + local distance = RandomFloat(self.bounceRadiusMin, self.bounceRadiusMax) + targetPos = Vector( + heroOrigin.x + math.cos(angle) * distance, + heroOrigin.y + math.sin(angle) * distance, + heroOrigin.z + ) + targetPos = GetGroundPosition(targetPos, parent) + else + local randomOffset = RandomVector(RandomFloat(self.bounceRadiusMin * 0.4, self.bounceRadiusMax * 0.6)) + targetPos = GetGroundPosition( + parentOrigin + Vector(randomOffset.x, randomOffset.y, 0), + parent + ) + end + local jumpDirection = targetPos - parentOrigin + if jumpDirection:Length2D() > 16 then + parent:SetForwardVector(jumpDirection:Normalized()) + end + local jumpDistance = jumpDirection:Length2D() + modifier_general_arc:apply( + parent, + hero or parent, + nil, + { + x = targetPos.x, + y = targetPos.y, + z = targetPos.z, + duration = RandomFloat(0.28, 0.42), + distance = jumpDistance, + height = RandomFloat(self.bounceHeightMin, self.bounceHeightMax), + isFlail = true + } + ) +end +function modifier_kitty_flex_catakeet.prototype.OnDestroy(self) + if not IsServer() then + return + end + local parent = self:GetParent() + if parent and IsValidEntity(parent) and not parent:IsNull() then + UTIL_Remove(parent) + end +end +function modifier_kitty_flex_catakeet.prototype.CheckState(self) + return { + [MODIFIER_STATE_INVULNERABLE] = true, + [MODIFIER_STATE_UNSELECTABLE] = true, + [MODIFIER_STATE_UNTARGETABLE] = true, + [MODIFIER_STATE_NO_HEALTH_BAR] = true, + [MODIFIER_STATE_NOT_ON_MINIMAP] = true, + [MODIFIER_STATE_NO_UNIT_COLLISION] = true, + [MODIFIER_STATE_DISARMED] = true, + [MODIFIER_STATE_COMMAND_RESTRICTED] = true, + [MODIFIER_STATE_IGNORING_MOVE_AND_ATTACK_ORDERS] = true + } +end +modifier_kitty_flex_catakeet = __TS__Decorate( + modifier_kitty_flex_catakeet, + modifier_kitty_flex_catakeet, + {registerModifier(nil)}, + {kind = "class", name = "modifier_kitty_flex_catakeet"} +) +____exports.modifier_kitty_flex_catakeet = modifier_kitty_flex_catakeet +return ____exports diff --git a/scripts/vscripts/abilities/modifiers/modifier_swamp_slow.lua b/scripts/vscripts/abilities/modifiers/modifier_swamp_slow.lua new file mode 100644 index 0000000..f499bce --- /dev/null +++ b/scripts/vscripts/abilities/modifiers/modifier_swamp_slow.lua @@ -0,0 +1,64 @@ +local ____lualib = require("lualib_bundle") +local __TS__Class = ____lualib.__TS__Class +local __TS__ClassExtends = ____lualib.__TS__ClassExtends +local __TS__Decorate = ____lualib.__TS__Decorate +local ____exports = {} +local ____dota_ts_adapter = require("lib.dota_ts_adapter") +local BaseModifier = ____dota_ts_adapter.BaseModifier +local registerModifier = ____dota_ts_adapter.registerModifier +____exports.modifier_swamp_slow = __TS__Class() +local modifier_swamp_slow = ____exports.modifier_swamp_slow +modifier_swamp_slow.name = "modifier_swamp_slow" +modifier_swamp_slow.____file_path = "scripts/vscripts/abilities/modifiers/modifier_swamp_slow.lua" +__TS__ClassExtends(modifier_swamp_slow, BaseModifier) +function modifier_swamp_slow.prototype.OnCreated(self) + self:StartIntervalThink(0.33) +end +function modifier_swamp_slow.prototype.OnIntervalThink(self) + if IsClient() then + return + end + local movespeed = self:GetParent():GetMoveSpeedModifier( + self:GetParent():GetBaseMoveSpeed(), + false + ) + local damagetable = { + victim = self:GetParent(), + attacker = self:GetParent(), + damage = self:GetStackCount() * 1, + damage_type = DAMAGE_TYPE_PURE + } + ApplyDamage({ + victim = damagetable.victim, + attacker = damagetable.attacker, + damage = movespeed <= 100 and damagetable.damage + self:GetParent():GetMaxHealth() * 0.01 or damagetable.damage, + damage_type = damagetable.damage_type + }) + self:IncrementStackCount() +end +function modifier_swamp_slow.prototype.IsHidden(self) + return false +end +function modifier_swamp_slow.prototype.IsDebuff(self) + return true +end +function modifier_swamp_slow.prototype.IsPurgable(self) + return true +end +function modifier_swamp_slow.prototype.DeclareFunctions(self) + return {MODIFIER_PROPERTY_MOVESPEED_BONUS_CONSTANT} +end +function modifier_swamp_slow.prototype.GetModifierMoveSpeedBonus_Constant(self) + return -30 +end +function modifier_swamp_slow.prototype.GetTexture(self) + return "night_stalker_darkness" +end +modifier_swamp_slow = __TS__Decorate( + modifier_swamp_slow, + modifier_swamp_slow, + {registerModifier(nil)}, + {kind = "class", name = "modifier_swamp_slow"} +) +____exports.modifier_swamp_slow = modifier_swamp_slow +return ____exports diff --git a/scripts/vscripts/abilities/modifiers/modifier_vampirism.lua b/scripts/vscripts/abilities/modifiers/modifier_vampirism.lua new file mode 100644 index 0000000..b593b47 --- /dev/null +++ b/scripts/vscripts/abilities/modifiers/modifier_vampirism.lua @@ -0,0 +1,123 @@ +local ____lualib = require("lualib_bundle") +local __TS__Class = ____lualib.__TS__Class +local __TS__ClassExtends = ____lualib.__TS__ClassExtends +local __TS__Decorate = ____lualib.__TS__Decorate +local ____exports = {} +local ____dota_ts_adapter = require("lib.dota_ts_adapter") +local BaseModifier = ____dota_ts_adapter.BaseModifier +local registerModifier = ____dota_ts_adapter.registerModifier +____exports.modifier_vampirism = __TS__Class() +local modifier_vampirism = ____exports.modifier_vampirism +modifier_vampirism.name = "modifier_vampirism" +modifier_vampirism.____file_path = "scripts/vscripts/abilities/modifiers/modifier_vampirism.lua" +__TS__ClassExtends(modifier_vampirism, BaseModifier) +function modifier_vampirism.prototype.IsHidden(self) + return true +end +function modifier_vampirism.prototype.IsDebuff(self) + return false +end +function modifier_vampirism.prototype.IsPurgable(self) + return false +end +function modifier_vampirism.prototype.DeclareFunctions(self) + return {MODIFIER_EVENT_ON_ATTACK_LANDED, MODIFIER_EVENT_ON_TAKEDAMAGE} +end +function modifier_vampirism.prototype.RemoveOnDeath(self) + return false +end +function modifier_vampirism.prototype.OnAttackLanded(self, event) + if not IsServer() then + return + end + if event.attacker ~= self:GetParent() then + return + end + local hero = self:GetParent() + local physicalVampirism = getPhysicalVampirism(nil, hero) + if physicalVampirism <= 0 then + return + end + local attackDamage = event.damage or 0 + if attackDamage <= 0 then + return + end + local healAmount = attackDamage * physicalVampirism / 100 + if healAmount <= 0 then + return + end + hero:HealWithParams( + healAmount, + self:GetAbility(), + false, + true, + hero, + false + ) + SendOverheadEventMessage( + nil, + OVERHEAD_ALERT_HEAL, + hero, + healAmount, + hero:GetPlayerOwner() + ) + self:CreateVampirismEffect(event.target) +end +function modifier_vampirism.prototype.OnTakeDamage(self, event) + if not IsServer() then + return + end + if event.attacker ~= self:GetParent() then + return + end + local hero = self:GetParent() + local magicalVampirism = getMagicalVampirism(nil, hero) + if magicalVampirism <= 0 then + return + end + local damage = event.damage + local healAmount = 0 + if magicalVampirism > 0 and event.inflictor ~= nil then + local magicalHeal = damage * magicalVampirism / 100 + healAmount = healAmount + magicalHeal + end + if healAmount > 0 then + hero:HealWithParams( + healAmount, + self:GetAbility(), + false, + true, + hero, + false + ) + SendOverheadEventMessage( + nil, + OVERHEAD_ALERT_HEAL, + hero, + healAmount, + hero:GetPlayerOwner() + ) + self:CreateVampirismEffect(event.unit) + end +end +function modifier_vampirism.prototype.CreateVampirismEffect(self, target) + local attackerParticle = ParticleManager:CreateParticle( + "particles/units/heroes/hero_bloodseeker/bloodseeker_bloodbath.vpcf", + PATTACH_ABSORIGIN_FOLLOW, + self:GetParent() + ) + ParticleManager:SetParticleControl( + attackerParticle, + 0, + self:GetParent():GetAbsOrigin() + ) + ParticleManager:ReleaseParticleIndex(attackerParticle) +end +modifier_vampirism = __TS__Decorate( + modifier_vampirism, + modifier_vampirism, + {registerModifier(nil)}, + {kind = "class", name = "modifier_vampirism"} +) +____exports.modifier_vampirism = modifier_vampirism +return ____exports diff --git a/scripts/vscripts/abilities/modifiers/pets/modifier_pet_buff_npc_pig_event.lua b/scripts/vscripts/abilities/modifiers/pets/modifier_pet_buff_npc_pig_event.lua new file mode 100644 index 0000000..fde9fd1 --- /dev/null +++ b/scripts/vscripts/abilities/modifiers/pets/modifier_pet_buff_npc_pig_event.lua @@ -0,0 +1,58 @@ +local ____lualib = require("lualib_bundle") +local __TS__Class = ____lualib.__TS__Class +local __TS__ClassExtends = ____lualib.__TS__ClassExtends +local __TS__Decorate = ____lualib.__TS__Decorate +local ____exports = {} +local ____dota_ts_adapter = require("lib.dota_ts_adapter") +local BaseModifier = ____dota_ts_adapter.BaseModifier +local registerModifier = ____dota_ts_adapter.registerModifier +local ____incoming_damage_reduction_combine = require("utils.incoming_damage_reduction_combine") +local removeIncomingDamageReductionSource = ____incoming_damage_reduction_combine.removeIncomingDamageReductionSource +local setIncomingDamageReductionSource = ____incoming_damage_reduction_combine.setIncomingDamageReductionSource +local PET_PIG_EVENT_INCOMING_SOURCE = "modifier_pet_buff_npc_pig_event" +____exports.modifier_pet_buff_npc_pig_event = __TS__Class() +local modifier_pet_buff_npc_pig_event = ____exports.modifier_pet_buff_npc_pig_event +modifier_pet_buff_npc_pig_event.name = "modifier_pet_buff_npc_pig_event" +modifier_pet_buff_npc_pig_event.____file_path = "scripts/vscripts/abilities/modifiers/pets/modifier_pet_buff_npc_pig_event.lua" +__TS__ClassExtends(modifier_pet_buff_npc_pig_event, BaseModifier) +function modifier_pet_buff_npc_pig_event.prototype.IsHidden(self) + return false +end +function modifier_pet_buff_npc_pig_event.prototype.IsDebuff(self) + return false +end +function modifier_pet_buff_npc_pig_event.prototype.RemoveOnDeath(self) + return false +end +function modifier_pet_buff_npc_pig_event.prototype.OnCreated(self) + if not IsServer() then + return + end + setIncomingDamageReductionSource( + nil, + self:GetParent(), + PET_PIG_EVENT_INCOMING_SOURCE, + function() return 10 end + ) +end +function modifier_pet_buff_npc_pig_event.prototype.OnDestroy(self) + if not IsServer() then + return + end + removeIncomingDamageReductionSource( + nil, + self:GetParent(), + PET_PIG_EVENT_INCOMING_SOURCE + ) +end +function modifier_pet_buff_npc_pig_event.prototype.GetTexture(self) + return "primal_beast_onslaught" +end +modifier_pet_buff_npc_pig_event = __TS__Decorate( + modifier_pet_buff_npc_pig_event, + modifier_pet_buff_npc_pig_event, + {registerModifier(nil)}, + {kind = "class", name = "modifier_pet_buff_npc_pig_event"} +) +____exports.modifier_pet_buff_npc_pig_event = modifier_pet_buff_npc_pig_event +return ____exports diff --git a/scripts/vscripts/abilities/modifiers/pets/modifier_pet_buff_npc_squirrel_event.lua b/scripts/vscripts/abilities/modifiers/pets/modifier_pet_buff_npc_squirrel_event.lua new file mode 100644 index 0000000..74dbb57 --- /dev/null +++ b/scripts/vscripts/abilities/modifiers/pets/modifier_pet_buff_npc_squirrel_event.lua @@ -0,0 +1,39 @@ +local ____lualib = require("lualib_bundle") +local __TS__Class = ____lualib.__TS__Class +local __TS__ClassExtends = ____lualib.__TS__ClassExtends +local __TS__Decorate = ____lualib.__TS__Decorate +local ____exports = {} +local ____dota_ts_adapter = require("lib.dota_ts_adapter") +local BaseModifier = ____dota_ts_adapter.BaseModifier +local registerModifier = ____dota_ts_adapter.registerModifier +____exports.modifier_pet_buff_npc_squirrel_event = __TS__Class() +local modifier_pet_buff_npc_squirrel_event = ____exports.modifier_pet_buff_npc_squirrel_event +modifier_pet_buff_npc_squirrel_event.name = "modifier_pet_buff_npc_squirrel_event" +modifier_pet_buff_npc_squirrel_event.____file_path = "scripts/vscripts/abilities/modifiers/pets/modifier_pet_buff_npc_squirrel_event.lua" +__TS__ClassExtends(modifier_pet_buff_npc_squirrel_event, BaseModifier) +function modifier_pet_buff_npc_squirrel_event.prototype.IsHidden(self) + return false +end +function modifier_pet_buff_npc_squirrel_event.prototype.IsDebuff(self) + return false +end +function modifier_pet_buff_npc_squirrel_event.prototype.RemoveOnDeath(self) + return false +end +function modifier_pet_buff_npc_squirrel_event.prototype.DeclareFunctions(self) + return {MODIFIER_PROPERTY_EXP_RATE_BOOST} +end +function modifier_pet_buff_npc_squirrel_event.prototype.GetModifierPercentageExpRateBoost(self) + return 35 +end +function modifier_pet_buff_npc_squirrel_event.prototype.GetTexture(self) + return "default_items/xp" +end +modifier_pet_buff_npc_squirrel_event = __TS__Decorate( + modifier_pet_buff_npc_squirrel_event, + modifier_pet_buff_npc_squirrel_event, + {registerModifier(nil)}, + {kind = "class", name = "modifier_pet_buff_npc_squirrel_event"} +) +____exports.modifier_pet_buff_npc_squirrel_event = modifier_pet_buff_npc_squirrel_event +return ____exports diff --git a/scripts/vscripts/abilities/modifiers/pets/modifier_pet_buff_npc_wolf_event.lua b/scripts/vscripts/abilities/modifiers/pets/modifier_pet_buff_npc_wolf_event.lua new file mode 100644 index 0000000..e1b0bb4 --- /dev/null +++ b/scripts/vscripts/abilities/modifiers/pets/modifier_pet_buff_npc_wolf_event.lua @@ -0,0 +1,52 @@ +local ____lualib = require("lualib_bundle") +local __TS__Class = ____lualib.__TS__Class +local __TS__ClassExtends = ____lualib.__TS__ClassExtends +local __TS__Decorate = ____lualib.__TS__Decorate +local ____exports = {} +local ____dota_ts_adapter = require("lib.dota_ts_adapter") +local BaseModifier = ____dota_ts_adapter.BaseModifier +local registerModifier = ____dota_ts_adapter.registerModifier +____exports.modifier_pet_buff_npc_wolf_event = __TS__Class() +local modifier_pet_buff_npc_wolf_event = ____exports.modifier_pet_buff_npc_wolf_event +modifier_pet_buff_npc_wolf_event.name = "modifier_pet_buff_npc_wolf_event" +modifier_pet_buff_npc_wolf_event.____file_path = "scripts/vscripts/abilities/modifiers/pets/modifier_pet_buff_npc_wolf_event.lua" +__TS__ClassExtends(modifier_pet_buff_npc_wolf_event, BaseModifier) +function modifier_pet_buff_npc_wolf_event.prototype.IsHidden(self) + return false +end +function modifier_pet_buff_npc_wolf_event.prototype.RemoveOnDeath(self) + return false +end +function modifier_pet_buff_npc_wolf_event.prototype.IsDebuff(self) + return false +end +function modifier_pet_buff_npc_wolf_event.prototype.OnCreated(self, params) + if not IsServer() then + return + end + local stackingCritMod = self:GetParent():FindModifierByName("modifier_stacking_crit") + if stackingCritMod then + local ability = self:GetAbility() + stackingCritMod:AddCustomCrit(15, 160, "modifier_pet_buff_npc_wolf_event") + end +end +function modifier_pet_buff_npc_wolf_event.prototype.DeclareFunctions(self) + return {MODIFIER_PROPERTY_DAMAGEOUTGOING_PERCENTAGE, MODIFIER_PROPERTY_SPELL_AMPLIFY_PERCENTAGE} +end +function modifier_pet_buff_npc_wolf_event.prototype.GetModifierDamageOutgoing_Percentage(self, event) + return 15 +end +function modifier_pet_buff_npc_wolf_event.prototype.GetModifierSpellAmplify_Percentage(self, event) + return 15 +end +function modifier_pet_buff_npc_wolf_event.prototype.GetTexture(self) + return "lycan_feral_impulse" +end +modifier_pet_buff_npc_wolf_event = __TS__Decorate( + modifier_pet_buff_npc_wolf_event, + modifier_pet_buff_npc_wolf_event, + {registerModifier(nil)}, + {kind = "class", name = "modifier_pet_buff_npc_wolf_event"} +) +____exports.modifier_pet_buff_npc_wolf_event = modifier_pet_buff_npc_wolf_event +return ____exports diff --git a/scripts/vscripts/abilities/no_healthbar.lua b/scripts/vscripts/abilities/no_healthbar.lua new file mode 100644 index 0000000..ec4a944 --- /dev/null +++ b/scripts/vscripts/abilities/no_healthbar.lua @@ -0,0 +1,50 @@ +local ____lualib = require("lualib_bundle") +local __TS__Class = ____lualib.__TS__Class +local __TS__ClassExtends = ____lualib.__TS__ClassExtends +local __TS__Decorate = ____lualib.__TS__Decorate +local ____exports = {} +local ____dota_ts_adapter = require("lib.dota_ts_adapter") +local BaseAbility = ____dota_ts_adapter.BaseAbility +local BaseModifier = ____dota_ts_adapter.BaseModifier +local registerAbility = ____dota_ts_adapter.registerAbility +local registerModifier = ____dota_ts_adapter.registerModifier +____exports.no_healthbar = __TS__Class() +local no_healthbar = ____exports.no_healthbar +no_healthbar.name = "no_healthbar" +no_healthbar.____file_path = "scripts/vscripts/abilities/no_healthbar.lua" +__TS__ClassExtends(no_healthbar, BaseAbility) +function no_healthbar.prototype.GetIntrinsicModifierName(self) + return "modifier_no_healthbar" +end +no_healthbar = __TS__Decorate( + no_healthbar, + no_healthbar, + {registerAbility(nil)}, + {kind = "class", name = "no_healthbar"} +) +____exports.no_healthbar = no_healthbar +____exports.modifier_no_healthbar = __TS__Class() +local modifier_no_healthbar = ____exports.modifier_no_healthbar +modifier_no_healthbar.name = "modifier_no_healthbar" +modifier_no_healthbar.____file_path = "scripts/vscripts/abilities/no_healthbar.lua" +__TS__ClassExtends(modifier_no_healthbar, BaseModifier) +function modifier_no_healthbar.prototype.IsHidden(self) + return true +end +function modifier_no_healthbar.prototype.IsPurgable(self) + return false +end +function modifier_no_healthbar.prototype.IsDebuff(self) + return false +end +function modifier_no_healthbar.prototype.CheckState(self) + return {[MODIFIER_STATE_NO_HEALTH_BAR] = true, [MODIFIER_STATE_TAUNTED] = false, [MODIFIER_STATE_SILENCED] = false} +end +modifier_no_healthbar = __TS__Decorate( + modifier_no_healthbar, + modifier_no_healthbar, + {registerModifier(nil)}, + {kind = "class", name = "modifier_no_healthbar"} +) +____exports.modifier_no_healthbar = modifier_no_healthbar +return ____exports diff --git a/scripts/vscripts/abilities/spider_snake_passive.lua b/scripts/vscripts/abilities/spider_snake_passive.lua new file mode 100644 index 0000000..9da6dd0 --- /dev/null +++ b/scripts/vscripts/abilities/spider_snake_passive.lua @@ -0,0 +1,50 @@ +local ____lualib = require("lualib_bundle") +local __TS__Class = ____lualib.__TS__Class +local __TS__ClassExtends = ____lualib.__TS__ClassExtends +local __TS__Decorate = ____lualib.__TS__Decorate +local ____exports = {} +local ____dota_ts_adapter = require("lib.dota_ts_adapter") +local BaseAbility = ____dota_ts_adapter.BaseAbility +local BaseModifier = ____dota_ts_adapter.BaseModifier +local registerAbility = ____dota_ts_adapter.registerAbility +local registerModifier = ____dota_ts_adapter.registerModifier +____exports.spider_snake_passive = __TS__Class() +local spider_snake_passive = ____exports.spider_snake_passive +spider_snake_passive.name = "spider_snake_passive" +spider_snake_passive.____file_path = "scripts/vscripts/abilities/spider_snake_passive.lua" +__TS__ClassExtends(spider_snake_passive, BaseAbility) +function spider_snake_passive.prototype.GetIntrinsicModifierName(self) + return "modifier_spider_snake_passive" +end +spider_snake_passive = __TS__Decorate( + spider_snake_passive, + spider_snake_passive, + {registerAbility(nil)}, + {kind = "class", name = "spider_snake_passive"} +) +____exports.spider_snake_passive = spider_snake_passive +____exports.modifier_spider_snake_passive = __TS__Class() +local modifier_spider_snake_passive = ____exports.modifier_spider_snake_passive +modifier_spider_snake_passive.name = "modifier_spider_snake_passive" +modifier_spider_snake_passive.____file_path = "scripts/vscripts/abilities/spider_snake_passive.lua" +__TS__ClassExtends(modifier_spider_snake_passive, BaseModifier) +function modifier_spider_snake_passive.prototype.IsHidden(self) + return true +end +function modifier_spider_snake_passive.prototype.IsPurgable(self) + return false +end +function modifier_spider_snake_passive.prototype.IsDebuff(self) + return false +end +function modifier_spider_snake_passive.prototype.CheckState(self) + return {[MODIFIER_STATE_NO_UNIT_COLLISION] = true} +end +modifier_spider_snake_passive = __TS__Decorate( + modifier_spider_snake_passive, + modifier_spider_snake_passive, + {registerModifier(nil)}, + {kind = "class", name = "modifier_spider_snake_passive"} +) +____exports.modifier_spider_snake_passive = modifier_spider_snake_passive +return ____exports diff --git a/scripts/vscripts/abilities/system/hero_rage.lua b/scripts/vscripts/abilities/system/hero_rage.lua new file mode 100644 index 0000000..e1ed02d --- /dev/null +++ b/scripts/vscripts/abilities/system/hero_rage.lua @@ -0,0 +1,520 @@ +local ____lualib = require("lualib_bundle") +local __TS__Class = ____lualib.__TS__Class +local __TS__ClassExtends = ____lualib.__TS__ClassExtends +local __TS__Decorate = ____lualib.__TS__Decorate +local ____exports = {} +local ____dota_ts_adapter = require("lib.dota_ts_adapter") +local BaseAbility = ____dota_ts_adapter.BaseAbility +local BaseModifier = ____dota_ts_adapter.BaseModifier +local registerAbility = ____dota_ts_adapter.registerAbility +local registerModifier = ____dota_ts_adapter.registerModifier +local ____hero_rage_config = require("abilities.system.hero_rage_config") +local shouldHeroUseRageResource = ____hero_rage_config.shouldHeroUseRageResource +function ____exports.heroRageGetModifier(self, hero) + return hero:FindModifierByName(____exports.modifier_hero_rage.name) +end +function ____exports.heroRageSet(self, hero, value) + if not IsServer() then + return + end + local m = ____exports.heroRageGetModifier(nil, hero) + if not m then + return + end + m:SetStackCount(math.max( + 0, + math.min( + math.floor(value), + m.maxRage + ) + )) + m:pushRageNetClient() +end +--- Списание ярости после успешного каста (стоимость из `rage_cost` в KV или своё число). +function ____exports.heroRageTrySpend(self, hero, cost) + if not IsServer() then + return false + end + if cost <= 0 then + return true + end + local m = ____exports.heroRageGetModifier(nil, hero) + if not m then + return false + end + if m:GetStackCount() < cost then + return false + end + m:addRageInternal(-cost) + return true +end +local function defaultSpawnParams() + return { + max_rage = 100, + rage_per_attack = 6, + rage_per_damage = 1, + attack_speed_per_stack = 0, + time_out_of_combat = 4, + tick = 0.01 + } +end +____exports.ability_hero_rage = __TS__Class() +local ability_hero_rage = ____exports.ability_hero_rage +ability_hero_rage.name = "ability_hero_rage" +ability_hero_rage.____file_path = "scripts/vscripts/abilities/system/hero_rage.lua" +__TS__ClassExtends(ability_hero_rage, BaseAbility) +function ability_hero_rage.prototype.GetIntrinsicModifierName(self) + return ____exports.modifier_hero_rage.name +end +function ability_hero_rage.prototype.OnOwnerDied(self) + if not IsServer() then + return + end + local c = self:GetCaster() + ____exports.heroRageSet(nil, c, 0) +end +ability_hero_rage = __TS__Decorate( + ability_hero_rage, + ability_hero_rage, + {registerAbility(nil)}, + {kind = "class", name = "ability_hero_rage"} +) +____exports.ability_hero_rage = ability_hero_rage +____exports.modifier_hero_rage = __TS__Class() +local modifier_hero_rage = ____exports.modifier_hero_rage +modifier_hero_rage.name = "modifier_hero_rage" +modifier_hero_rage.____file_path = "scripts/vscripts/abilities/system/hero_rage.lua" +__TS__ClassExtends(modifier_hero_rage, BaseModifier) +function modifier_hero_rage.prototype.____constructor(self, ...) + BaseModifier.prototype.____constructor(self, ...) + self.maxRage = 100 + self.ragePerAttack = 0 + self.ragePerDamage = 0 + self.attackSpeedPerStack = 0 + self.timeOutOfCombatThreshold = 4 + self.tick = 0.01 + self.outOfCombatTime = 0 +end +function modifier_hero_rage.prototype.pushRageNetClient(self) + if not IsServer() then + return + end + CustomNetTables:SetTableValue( + "custom_stats", + "rage_ent_" .. tostring(self.parentHero:entindex()), + { + cur = self:GetStackCount(), + max = self.maxRage + } + ) +end +function modifier_hero_rage.prototype.clearRageNetClient(self) + if not IsServer() then + return + end + CustomNetTables:SetTableValue( + "custom_stats", + "rage_ent_" .. tostring(self.parentHero:entindex()), + {off = 1} + ) +end +function modifier_hero_rage.prototype.IsHidden(self) + return true +end +function modifier_hero_rage.prototype.IsPurgable(self) + return false +end +function modifier_hero_rage.prototype.RemoveOnDeath(self) + return false +end +function modifier_hero_rage.prototype.DeclareFunctions(self) + return { + MODIFIER_EVENT_ON_ATTACK_LANDED, + MODIFIER_EVENT_ON_TAKEDAMAGE, + MODIFIER_EVENT_ON_ABILITY_EXECUTED, + MODIFIER_PROPERTY_ATTACKSPEED_BONUS_CONSTANT, + MODIFIER_PROPERTY_MANA_REGEN_TOTAL_PERCENTAGE, + MODIFIER_PROPERTY_FIXED_MANA_REGEN, + MODIFIER_PROPERTY_FORCE_MAX_MANA, + MODIFIER_PROPERTY_DAMAGEOUTGOING_PERCENTAGE, + MODIFIER_PROPERTY_MANACOST_PERCENTAGE_STACKING + } +end +function modifier_hero_rage.prototype.GetModifierPercentageManacostStacking(self, ...) + local args = {...} + local event = args[1] + if event and event.ability and not event.ability:IsNull() and event.ability:IsItem() then + return 100 + end + return 0 +end +function modifier_hero_rage.prototype.GetModifierDamageOutgoing_Percentage(self, event) + return self:GetStackCount() * 0.25 +end +function modifier_hero_rage.prototype.GetModifierTotalPercentageManaRegen(self) + return 0 +end +function modifier_hero_rage.prototype.GetModifierFixedManaRegen(self) + return 0 +end +function modifier_hero_rage.prototype.GetModifierForceMaxMana(self) + return self.maxRage +end +function modifier_hero_rage.prototype.syncManaToRage(self) + if not IsServer() then + return + end + local hero = self.parentHero + local rage = self:GetStackCount() + local maxM = hero:GetMaxMana() + hero:SetMaxMana(100) + hero:SetMana(math.min(rage, maxM)) +end +function modifier_hero_rage.prototype.reapplyManaPoolAfterStatBump(self) + if not IsServer() then + return + end + local hero = self.parentHero + local rage = self:GetStackCount() + hero:SetMaxMana(self.maxRage) + hero:SetMana(math.min( + rage, + hero:GetMaxMana() + )) +end +function modifier_hero_rage.prototype.OnTakeDamage(self, event) + if not IsServer() then + return + end + if event.damage <= 0 then + return + end + local victim = event.unit + local attacker = event.attacker + if attacker == self.parentHero and victim and victim:GetTeamNumber() ~= self.parentHero:GetTeamNumber() then + if self.ragePerAttack ~= 0 then + self:addRageInternal(self.ragePerAttack) + end + self:reapplyManaPoolAfterStatBump() + return + end + if victim == self.parentHero then + if self.ragePerDamage ~= 0 then + self:addRageInternal(self.ragePerDamage) + end + self:reapplyManaPoolAfterStatBump() + end +end +function modifier_hero_rage.prototype.getAbilityRageSpendCost(self, ability) + local customRageCost = math.max( + 0, + math.floor(ability:GetSpecialValueFor("rage_cost")) + ) + if customRageCost > 0 then + return customRageCost + end + return math.max( + 0, + math.floor(ability:GetManaCost(ability:GetLevel())) + ) +end +function modifier_hero_rage.prototype.OnAbilityExecuted(self, event) + if not IsServer() then + return + end + if event.unit ~= self.parentHero then + return + end + local ability = event.ability + if not ability or ability:IsNull() then + return + end + if ability:IsItem() then + return + end + if ability == self:GetAbility() then + return + end + local cost = self:getAbilityRageSpendCost(ability) + if cost <= 0 then + return + end + ____exports.heroRageTrySpend(nil, self.parentHero, cost) +end +function modifier_hero_rage.prototype.OnCreated(self, params) + local parent = self:GetParent() + self.parentHero = parent + local rawAbility = self:GetAbility() + local ability = rawAbility and rawAbility:GetAbilityName() == "ability_hero_rage" and rawAbility or nil + local def = defaultSpawnParams(nil) + if ability then + self.maxRage = math.max( + 1, + math.floor(ability:GetSpecialValueFor("max_rage")) + ) + self.ragePerAttack = ability:GetSpecialValueFor("rage_per_attack") + self.ragePerDamage = ability:GetSpecialValueFor("rage_per_damage") + self.attackSpeedPerStack = ability:GetSpecialValueFor("attack_speed_per_stack") + self.timeOutOfCombatThreshold = math.max( + 0.1, + ability:GetSpecialValueFor("time_out_of_combat") + ) + self.tick = math.max( + 0.03, + ability:GetSpecialValueFor("tick") + ) + else + self.maxRage = math.max( + 1, + math.floor(params.max_rage or def.max_rage) + ) + self.ragePerAttack = params.rage_per_attack or def.rage_per_attack + self.ragePerDamage = params.rage_per_damage or def.rage_per_damage + self.attackSpeedPerStack = params.attack_speed_per_stack or def.attack_speed_per_stack + self.timeOutOfCombatThreshold = math.max(0.1, params.time_out_of_combat or def.time_out_of_combat) + self.tick = math.max(0.03, params.tick or def.tick) + end + if not IsServer() then + return + end + parent.__zHeroRage = self + self:SetStackCount(0) + self.outOfCombatTime = 0 + parent:AddNewModifier( + parent, + self:GetAbility() or nil, + ____exports.modifier_hero_rage_max.name, + {} + ) + self:StartIntervalThink(self.tick) + self:pushRageNetClient() + self:syncManaToRage() + self.levelUpListener = ListenToGameEvent( + "dota_player_gained_level", + function(event) + if not IsServer() then + return + end + if not IsValidEntity(self.parentHero) then + return + end + if not self.parentHero:IsRealHero() or self.parentHero:IsIllusion() then + return + end + local pid = self.parentHero:GetPlayerOwnerID() + if event.player ~= pid then + return + end + self:reapplyManaPoolAfterStatBump() + end, + nil + ) +end +function modifier_hero_rage.prototype.OnDestroy(self) + if not IsServer() then + return + end + if self.levelUpListener ~= nil then + StopListeningToGameEvent(self.levelUpListener) + self.levelUpListener = nil + end + self:clearRageNetClient() + local p = self:GetParent() + if p.__zHeroRage == self then + p.__zHeroRage = nil + end + p:RemoveModifierByName(____exports.modifier_hero_rage_max.name) +end +function modifier_hero_rage.prototype.OnRefresh(self) + local rawAbility = self:GetAbility() + local ability = rawAbility and rawAbility:GetAbilityName() == "ability_hero_rage" and rawAbility or nil + if ability then + self.maxRage = math.max( + 1, + math.floor(ability:GetSpecialValueFor("max_rage")) + ) + self.ragePerAttack = ability:GetSpecialValueFor("rage_per_attack") + self.ragePerDamage = ability:GetSpecialValueFor("rage_per_damage") + self.attackSpeedPerStack = ability:GetSpecialValueFor("attack_speed_per_stack") + self.timeOutOfCombatThreshold = math.max( + 0.1, + ability:GetSpecialValueFor("time_out_of_combat") + ) + self.tick = math.max( + 0.03, + ability:GetSpecialValueFor("tick") + ) + end + if not IsServer() then + return + end + self:clampRageStack() + local maxMod = self.parentHero:FindModifierByName(____exports.modifier_hero_rage_max.name) + if maxMod then + maxMod:SetStackCount(self.maxRage) + end + self:pushRageNetClient() + self:syncManaToRage() +end +function modifier_hero_rage.prototype.OnStackCountChanged(self, _stack) + if not IsServer() then + return + end + self:clampRageStack() + self:pushRageNetClient() + self:syncManaToRage() +end +function modifier_hero_rage.prototype.OnIntervalThink(self) + if not IsServer() then + return + end + self:syncManaToRage() + self.outOfCombatTime = self.outOfCombatTime + self.tick + if self.outOfCombatTime >= self.timeOutOfCombatThreshold then + self:addRageInternal(-1) + end +end +function modifier_hero_rage.prototype.OnAttackLanded(self, event) + if not IsServer() then + return + end +end +function modifier_hero_rage.prototype.GetModifierAttackSpeedBonus_Constant(self) + return self.attackSpeedPerStack * self:GetStackCount() +end +function modifier_hero_rage.prototype.resetOutOfCombatTimer(self) + self.outOfCombatTime = 0 +end +function modifier_hero_rage.prototype.addRageInternal(self, delta) + self:resetOutOfCombatTimer() + local next = self:GetStackCount() + delta + if next > self.maxRage then + next = self.maxRage + end + if next < 0 then + next = 0 + end + self:SetStackCount(next) +end +function modifier_hero_rage.prototype.clampRageStack(self) + local v = self:GetStackCount() + if v < 0 then + v = 0 + end + if v > self.maxRage then + v = self.maxRage + end + if v ~= self:GetStackCount() then + self:SetStackCount(v) + end +end +modifier_hero_rage = __TS__Decorate( + modifier_hero_rage, + modifier_hero_rage, + {registerModifier(nil)}, + {kind = "class", name = "modifier_hero_rage"} +) +____exports.modifier_hero_rage = modifier_hero_rage +____exports.modifier_hero_rage_max = __TS__Class() +local modifier_hero_rage_max = ____exports.modifier_hero_rage_max +modifier_hero_rage_max.name = "modifier_hero_rage_max" +modifier_hero_rage_max.____file_path = "scripts/vscripts/abilities/system/hero_rage.lua" +__TS__ClassExtends(modifier_hero_rage_max, BaseModifier) +function modifier_hero_rage_max.prototype.____constructor(self, ...) + BaseModifier.prototype.____constructor(self, ...) + self.manaBonusLock = false +end +function modifier_hero_rage_max.prototype.IsHidden(self) + return true +end +function modifier_hero_rage_max.prototype.IsPurgable(self) + return false +end +function modifier_hero_rage_max.prototype.RemoveOnDeath(self) + return false +end +function modifier_hero_rage_max.prototype.DeclareFunctions(self) + return {MODIFIER_PROPERTY_MANA_BONUS} +end +function modifier_hero_rage_max.prototype.GetModifierManaBonus(self) + if self.manaBonusLock then + return 0 + end + self.manaBonusLock = true + local maxWithoutThis = self:GetParent():GetMaxMana() + self.manaBonusLock = false + local targetMax = self:GetStackCount() + return targetMax - maxWithoutThis +end +function modifier_hero_rage_max.prototype.OnCreated(self) + if not IsServer() then + return + end + local parent = self:GetParent() + local rage = parent:FindModifierByName(____exports.modifier_hero_rage.name) + self:SetStackCount(rage and rage.maxRage or 0) +end +modifier_hero_rage_max = __TS__Decorate( + modifier_hero_rage_max, + modifier_hero_rage_max, + {registerModifier(nil)}, + {kind = "class", name = "modifier_hero_rage_max"} +) +____exports.modifier_hero_rage_max = modifier_hero_rage_max +function ____exports.heroRageGetCurrent(self, hero) + local m = ____exports.heroRageGetModifier(nil, hero) + return m and m:GetStackCount() or 0 +end +function ____exports.heroRageGetMax(self, hero) + local m = ____exports.heroRageGetModifier(nil, hero) + return m and m.maxRage or 0 +end +function ____exports.heroRageIncrease(self, hero, amount) + if not IsServer() then + return + end + local m = ____exports.heroRageGetModifier(nil, hero) + if not m then + return + end + m:addRageInternal(amount) +end +function ____exports.heroRageDecrement(self, hero, amount) + ____exports.heroRageIncrease( + nil, + hero, + -math.abs(amount) + ) +end +--- Вешает систему ярости на героя из конфига `HERO_RAGE_UNIT_NAMES`. +-- Если в `game/` уже есть `ability_hero_rage` в KV — берётся она, иначе — модификатор с дефолтами. +function ____exports.attachHeroRageSystemForConfiguredHero(self, hero) + if not IsServer() then + return + end + if not hero:IsRealHero() or hero:IsIllusion() then + return + end + if not shouldHeroUseRageResource( + nil, + hero:GetUnitName() + ) then + return + end + if hero:FindModifierByName(____exports.modifier_hero_rage.name) then + return + end + hero:AddAbility("ability_hero_rage") + local rageAb = hero:FindAbilityByName("ability_hero_rage") + if rageAb ~= nil then + rageAb:SetLevel(1) + return + end + hero:AddNewModifier( + hero, + getModifierSourceAbility(nil, hero), + ____exports.modifier_hero_rage.name, + defaultSpawnParams(nil) + ) +end +return ____exports diff --git a/scripts/vscripts/abilities/system/hero_rage_config.lua b/scripts/vscripts/abilities/system/hero_rage_config.lua new file mode 100644 index 0000000..e0c6f7b --- /dev/null +++ b/scripts/vscripts/abilities/system/hero_rage_config.lua @@ -0,0 +1,22 @@ +local ____lualib = require("lualib_bundle") +local Set = ____lualib.Set +local __TS__New = ____lualib.__TS__New +local ____exports = {} +--- Герои, которым при спавне выдаётся система «ярость»: ресурс на стаках модификатора, текущая/макс мана совпадают с яростью, реген маны отключён. +-- Добавь сюда `npc_dota_hero_*` — это не привязано к конкретному кастомному герою в коде. +____exports.HERO_RAGE_UNIT_NAMES = __TS__New(Set, { + "npc_dota_hero_bloodhunter", + "npc_dota_hero_axe", + "npc_dota_hero_juggernaut", + "npc_dota_hero_pudge", + "npc_dota_hero_sven", + "npc_dota_hero_bristleback", + "npc_dota_hero_troll_warlord", + "npc_dota_hero_legion_commander", + "npc_dota_hero_sand_king", + "npc_dota_hero_spectre" +}) +function ____exports.shouldHeroUseRageResource(self, unitName) + return ____exports.HERO_RAGE_UNIT_NAMES:has(unitName) +end +return ____exports diff --git a/scripts/vscripts/abilities/system/rage_ability.lua b/scripts/vscripts/abilities/system/rage_ability.lua new file mode 100644 index 0000000..967dac1 --- /dev/null +++ b/scripts/vscripts/abilities/system/rage_ability.lua @@ -0,0 +1,110 @@ +local ____lualib = require("lualib_bundle") +local __TS__Class = ____lualib.__TS__Class +local __TS__ClassExtends = ____lualib.__TS__ClassExtends +local ____exports = {} +local ____dota_ts_adapter = require("lib.dota_ts_adapter") +local BaseAbility = ____dota_ts_adapter.BaseAbility +local ____hero_rage = require("abilities.system.hero_rage") +local heroRageGetCurrent = ____hero_rage.heroRageGetCurrent +--- Стоимость ярости: приоритет `rage_cost`, иначе берём обычный mana cost способности. +function ____exports.getAbilityRageCost(self, ability) + local customRageCost = math.max( + 0, + math.floor(ability:GetSpecialValueFor("rage_cost")) + ) + if customRageCost > 0 then + return customRageCost + end + return math.max( + 0, + math.floor(ability:GetManaCost(ability:GetLevel())) + ) +end +____exports.canCastRageNoTarget = function(____, ability) + if not IsServer() then + return UF_SUCCESS + end + local caster = ability:GetCaster() + local cost = ____exports.getAbilityRageCost(nil, ability) + if cost <= 0 then + return UF_SUCCESS + end + if ability:IsChanneling() then + return UF_SUCCESS + end + local ____temp_0 + if heroRageGetCurrent(nil, caster) < cost then + ____temp_0 = UF_FAIL_CUSTOM + else + ____temp_0 = UF_SUCCESS + end + return ____temp_0 +end +____exports.canCastRageErrorNoTarget = function(____, ability) + if not IsServer() then + return "" + end + local caster = ability:GetCaster() + local cost = ____exports.getAbilityRageCost(nil, ability) + if cost <= 0 then + return "" + end + if ability:IsChanneling() then + return "" + end + return heroRageGetCurrent(nil, caster) < cost and "#dota_hud_error_havent_charges" or "" +end +____exports.canCastRageTarget = function(____, ability, target) + local rage = ____exports.canCastRageNoTarget(nil, ability) + if rage ~= UF_SUCCESS then + return rage + end + if not IsServer() then + return UF_SUCCESS + end + local caster = ability:GetCaster() + return UnitFilter( + target, + ability:GetAbilityTargetTeam(), + ability:GetAbilityTargetType(), + ability:GetAbilityTargetFlags(), + caster:GetTeamNumber() + ) +end +--- Базовый класс для способностей с расходом ярости (проверка без таргета / точки). Не регистрируется как отдельная ability — только наследование. +____exports.RageAbilityNoTarget = __TS__Class() +local RageAbilityNoTarget = ____exports.RageAbilityNoTarget +RageAbilityNoTarget.name = "RageAbilityNoTarget" +RageAbilityNoTarget.____file_path = "scripts/vscripts/abilities/system/rage_ability.lua" +__TS__ClassExtends(RageAbilityNoTarget, BaseAbility) +function RageAbilityNoTarget.prototype.CastFilterResult(self) + return ____exports.canCastRageNoTarget(nil, self) +end +function RageAbilityNoTarget.prototype.GetCustomCastError(self) + return ____exports.canCastRageErrorNoTarget(nil, self) +end +--- Базовый класс для способностей с расходом ярости по юниту-цели. +____exports.RageAbilityTarget = __TS__Class() +local RageAbilityTarget = ____exports.RageAbilityTarget +RageAbilityTarget.name = "RageAbilityTarget" +RageAbilityTarget.____file_path = "scripts/vscripts/abilities/system/rage_ability.lua" +__TS__ClassExtends(RageAbilityTarget, BaseAbility) +function RageAbilityTarget.prototype.CastFilterResultTarget(self, target) + return ____exports.canCastRageTarget(nil, self, target) +end +function RageAbilityTarget.prototype.GetCustomCastErrorTarget(self, _target) + return ____exports.canCastRageErrorNoTarget(nil, self) +end +--- Базовый класс для способностей с расходом ярости по точке. +____exports.RageAbilityPoint = __TS__Class() +local RageAbilityPoint = ____exports.RageAbilityPoint +RageAbilityPoint.name = "RageAbilityPoint" +RageAbilityPoint.____file_path = "scripts/vscripts/abilities/system/rage_ability.lua" +__TS__ClassExtends(RageAbilityPoint, BaseAbility) +function RageAbilityPoint.prototype.CastFilterResultLocation(self) + return ____exports.canCastRageNoTarget(nil, self) +end +function RageAbilityPoint.prototype.GetCustomCastErrorLocation(self) + return ____exports.canCastRageErrorNoTarget(nil, self) +end +return ____exports diff --git a/scripts/vscripts/abilities/zombie/boss/suicide_boys.lua b/scripts/vscripts/abilities/zombie/boss/suicide_boys.lua new file mode 100644 index 0000000..2a0cf78 --- /dev/null +++ b/scripts/vscripts/abilities/zombie/boss/suicide_boys.lua @@ -0,0 +1,201 @@ +local ____lualib = require("lualib_bundle") +local __TS__Class = ____lualib.__TS__Class +local __TS__ClassExtends = ____lualib.__TS__ClassExtends +local __TS__Decorate = ____lualib.__TS__Decorate +local ____exports = {} +local ____dota_ts_adapter = require("lib.dota_ts_adapter") +local BaseAbility = ____dota_ts_adapter.BaseAbility +local BaseModifier = ____dota_ts_adapter.BaseModifier +local registerAbility = ____dota_ts_adapter.registerAbility +local registerModifier = ____dota_ts_adapter.registerModifier +local HOMER_UNIT_NAME = "npc_homer" +local HOMER_TRIGGER_RADIUS = 200 +local JUMP_DURATION = 5 +local EXPLOSION_DELAY = 2.5 +local EXPLOSION_DAMAGE = 100000000 +____exports.suicide_boys = __TS__Class() +local suicide_boys = ____exports.suicide_boys +suicide_boys.name = "suicide_boys" +suicide_boys.____file_path = "scripts/vscripts/abilities/zombie/Boss/suicide_boys.lua" +__TS__ClassExtends(suicide_boys, BaseAbility) +function suicide_boys.prototype.GetIntrinsicModifierName(self) + return ____exports.modifier_suicide_boys_homer_trigger.name +end +function suicide_boys.prototype.Precache(self, context) + PrecacheResource("particle", "particles/units/heroes/hero_techies/techies_suicide.vpcf", context) + PrecacheResource("particle", "particles/units/heroes/hero_tiny/tiny_toss_blur.vpcf", context) +end +function suicide_boys.prototype.GetBehavior(self) + return DOTA_ABILITY_BEHAVIOR_PASSIVE +end +suicide_boys = __TS__Decorate( + suicide_boys, + suicide_boys, + {registerAbility(nil)}, + {kind = "class", name = "suicide_boys"} +) +____exports.suicide_boys = suicide_boys +____exports.modifier_suicide_boys_homer_trigger = __TS__Class() +local modifier_suicide_boys_homer_trigger = ____exports.modifier_suicide_boys_homer_trigger +modifier_suicide_boys_homer_trigger.name = "modifier_suicide_boys_homer_trigger" +modifier_suicide_boys_homer_trigger.____file_path = "scripts/vscripts/abilities/zombie/Boss/suicide_boys.lua" +__TS__ClassExtends(modifier_suicide_boys_homer_trigger, BaseModifier) +function modifier_suicide_boys_homer_trigger.prototype.IsHidden(self) + return true +end +function modifier_suicide_boys_homer_trigger.prototype.IsPurgable(self) + return false +end +function modifier_suicide_boys_homer_trigger.prototype.OnCreated(self) + if not IsServer() then + return + end + self:StartIntervalThink(0.1) +end +function modifier_suicide_boys_homer_trigger.prototype.OnIntervalThink(self) + if not IsServer() then + return + end + local parent = self:GetParent() + local ability = self:GetAbility() + if not parent or not ability then + return + end + if not parent:IsAlive() then + return + end + if not ability:IsFullyCastable() then + return + end + if parent:HasModifier(____exports.modifier_tiny_toss_lua.name) then + return + end + local nearbyUnits = FindUnitsInRadius( + parent:GetTeamNumber(), + parent:GetAbsOrigin(), + nil, + HOMER_TRIGGER_RADIUS, + DOTA_UNIT_TARGET_TEAM_BOTH, + bit.bor(DOTA_UNIT_TARGET_HERO, DOTA_UNIT_TARGET_BASIC), + DOTA_UNIT_TARGET_FLAG_NONE, + FIND_CLOSEST, + false + ) + for ____, unit in ipairs(nearbyUnits) do + do + if unit:GetUnitName() ~= HOMER_UNIT_NAME then + goto __continue15 + end + self:TriggerPassiveSuicide(parent, ability) + return + end + ::__continue15:: + end +end +function modifier_suicide_boys_homer_trigger.prototype.TriggerPassiveSuicide(self, parent, ability) + parent:AddNewModifier(parent, ability, ____exports.modifier_tiny_toss_lua.name, {duration = JUMP_DURATION}) + ability:UseResources(true, false, true, true) + Timers:CreateTimer( + EXPLOSION_DELAY, + function() + if not IsValidEntity(parent) or not parent:IsAlive() then + return + end + local radius = ability:GetSpecialValueFor("radius") + local origin = parent:GetAbsOrigin() + local units = FindUnitsInRadius( + parent:GetTeamNumber(), + origin, + nil, + radius, + DOTA_UNIT_TARGET_TEAM_BOTH, + bit.bor(DOTA_UNIT_TARGET_HERO, DOTA_UNIT_TARGET_BASIC), + DOTA_UNIT_TARGET_FLAG_NONE, + FIND_ANY_ORDER, + false + ) + local explosionFx = ParticleManager:CreateParticle("particles/units/heroes/hero_techies/techies_suicide.vpcf", PATTACH_CUSTOMORIGIN, parent) + ParticleManager:SetParticleControl(explosionFx, 0, origin) + ParticleManager:SetParticleControl( + explosionFx, + 1, + Vector(radius, radius, radius) + ) + ParticleManager:ReleaseParticleIndex(explosionFx) + for ____, unit in ipairs(units) do + ApplyDamage({ + victim = unit, + attacker = parent, + damage = EXPLOSION_DAMAGE, + damage_type = DAMAGE_TYPE_PURE, + ability = ability + }) + end + end + ) +end +modifier_suicide_boys_homer_trigger = __TS__Decorate( + modifier_suicide_boys_homer_trigger, + modifier_suicide_boys_homer_trigger, + {registerModifier(nil)}, + {kind = "class", name = "modifier_suicide_boys_homer_trigger"} +) +____exports.modifier_suicide_boys_homer_trigger = modifier_suicide_boys_homer_trigger +____exports.modifier_tiny_toss_lua = __TS__Class() +local modifier_tiny_toss_lua = ____exports.modifier_tiny_toss_lua +modifier_tiny_toss_lua.name = "modifier_tiny_toss_lua" +modifier_tiny_toss_lua.____file_path = "scripts/vscripts/abilities/zombie/Boss/suicide_boys.lua" +__TS__ClassExtends(modifier_tiny_toss_lua, BaseModifier) +function modifier_tiny_toss_lua.prototype.IsHidden(self) + return true +end +function modifier_tiny_toss_lua.prototype.IsDebuff(self) + return self:GetCaster():GetTeamNumber() ~= self:GetParent():GetTeamNumber() +end +function modifier_tiny_toss_lua.prototype.IsStunDebuff(self) + return true +end +function modifier_tiny_toss_lua.prototype.IsPurgable(self) + return true +end +function modifier_tiny_toss_lua.prototype.OnCreated(self) + if not IsServer() then + return + end + local caster = self:GetCaster() + local parent = self:GetParent() + local origin = parent:GetAbsOrigin() + parent:AddNewModifier( + caster, + self:GetAbility(), + "modifier_knockback", + { + should_stun = 1, + knockback_duration = JUMP_DURATION, + duration = JUMP_DURATION, + knockback_distance = 0, + knockback_height = 2500, + center_x = origin.x, + center_y = origin.y, + center_z = origin.z + } + ) + EmitSoundOn("balah_babar", caster) +end +function modifier_tiny_toss_lua.prototype.CheckState(self) + return {[MODIFIER_STATE_STUNNED] = true} +end +function modifier_tiny_toss_lua.prototype.GetEffectName(self) + return "particles/units/heroes/hero_tiny/tiny_toss_blur.vpcf" +end +function modifier_tiny_toss_lua.prototype.GetEffectAttachType(self) + return PATTACH_ABSORIGIN_FOLLOW +end +modifier_tiny_toss_lua = __TS__Decorate( + modifier_tiny_toss_lua, + modifier_tiny_toss_lua, + {registerModifier(nil)}, + {kind = "class", name = "modifier_tiny_toss_lua"} +) +____exports.modifier_tiny_toss_lua = modifier_tiny_toss_lua +return ____exports diff --git a/scripts/vscripts/ability_alt_cast_manager.lua b/scripts/vscripts/ability_alt_cast_manager.lua new file mode 100644 index 0000000..7b8804f --- /dev/null +++ b/scripts/vscripts/ability_alt_cast_manager.lua @@ -0,0 +1,87 @@ +local ____lualib = require("lualib_bundle") +local __TS__Class = ____lualib.__TS__Class +local Map = ____lualib.Map +local __TS__New = ____lualib.__TS__New +local __TS__Iterator = ____lualib.__TS__Iterator +local ____exports = {} +--- Менеджер для отслеживания состояний альтернативного каста способностей +____exports.AbilityAltCastManager = __TS__Class() +local AbilityAltCastManager = ____exports.AbilityAltCastManager +AbilityAltCastManager.name = "AbilityAltCastManager" +AbilityAltCastManager.____file_path = "scripts/vscripts/ability_alt_cast_manager.lua" +function AbilityAltCastManager.prototype.____constructor(self) + self.altCastStates = __TS__New(Map) + if IsServer() and type(CustomGameEventManager) ~= "nil" and CustomGameEventManager ~= nil then + CustomGameEventManager:RegisterListener( + "ability_alt_cast_state", + function(_, event) + local playerId = event.PlayerID or event.playerId + local abilityName = event.abilityName + local isAltCast = event.isAltCast == 1 or event.isAltCast == true + self:updateAltCastState(playerId, abilityName, isAltCast) + end + ) + end +end +function AbilityAltCastManager.getInstance(self) + if not ____exports.AbilityAltCastManager.instance then + ____exports.AbilityAltCastManager.instance = __TS__New(____exports.AbilityAltCastManager) + end + return ____exports.AbilityAltCastManager.instance +end +function AbilityAltCastManager.prototype.updateAltCastState(self, playerId, abilityName, isAltCast) + if playerId == nil or playerId == nil or playerId < 0 then + return + end + local playerStates = self.altCastStates:get(playerId) + if not playerStates then + playerStates = __TS__New(Map) + self.altCastStates:set(playerId, playerStates) + end + playerStates:set(abilityName, isAltCast) + self:pushPlayerStatesToNetTable(playerId) +end +function AbilityAltCastManager.prototype.getAltCastState(self, playerId, abilityName) + local playerStates = self.altCastStates:get(playerId) + if not playerStates then + return false + end + if playerStates:has(abilityName) then + local state = playerStates:get(abilityName) or false + return state + end + return false +end +function AbilityAltCastManager.prototype.clearPlayerStates(self, playerId) + self.altCastStates:delete(playerId) + if type(CustomNetTables) ~= "nil" and CustomNetTables ~= nil then + CustomNetTables:SetTableValue( + "ability_alt_cast_states", + tostring(playerId), + {} + ) + end +end +function AbilityAltCastManager.prototype.syncPlayerStatesToClient(self, playerId) + self:pushPlayerStatesToNetTable(playerId) +end +function AbilityAltCastManager.prototype.pushPlayerStatesToNetTable(self, playerId) + if type(CustomNetTables) == "nil" or CustomNetTables == nil then + return + end + local playerStates = self.altCastStates:get(playerId) + local payload = {} + if playerStates then + for ____, ____value in __TS__Iterator(playerStates:entries()) do + local abilityName = ____value[1] + local enabled = ____value[2] + payload[abilityName] = enabled and 1 or 0 + end + end + CustomNetTables:SetTableValue( + "ability_alt_cast_states", + tostring(playerId), + payload + ) +end +return ____exports diff --git a/scripts/vscripts/addon_game_mode.lua b/scripts/vscripts/addon_game_mode.lua new file mode 100644 index 0000000..2266373 --- /dev/null +++ b/scripts/vscripts/addon_game_mode.lua @@ -0,0 +1,23 @@ +local ____lualib = require("lualib_bundle") +local __TS__ObjectAssign = ____lualib.__TS__ObjectAssign +local ____exports = {} +require("lib.timers") +require("lib.pool") +require("utils.vector_ws") +local ____GameMode = require("GameMode") +local GameMode = ____GameMode.GameMode +require("autopickup") +require("utils.luck") +require("utils.vampirism") +require("utils.crit_mult") +require("utils.store_effects") +require("store_manager") +require("store_arcade_packs") +__TS__ObjectAssign( + getfenv(), + {Activate = GameMode.Activate, Precache = GameMode.Precache} +) +if GameRules.Addon ~= nil then + GameRules.Addon:Reload() +end +return ____exports diff --git a/scripts/vscripts/addon_init.lua b/scripts/vscripts/addon_init.lua new file mode 100644 index 0000000..cf2e925 --- /dev/null +++ b/scripts/vscripts/addon_init.lua @@ -0,0 +1,18 @@ +--[[ Generated with https://github.com/TypeScriptToLua/TypeScriptToLua ]] +local ____exports = {} +require("abilities.modifiers.modifier_swamp_slow") +require("triggers.trigger_button") +require("abilities.system.hero_rage") +require("abilities.modifiers.ability_modifier_source") +require("modifiers.modifier_stats_multiplier") +require("utils.incoming_damage_reduction_combine") +LinkLuaModifier("modifier_card_66", "cards/examples/card_66", LUA_MODIFIER_MOTION_NONE) +LinkLuaModifier("modifier_card_67", "cards/examples/card_67", LUA_MODIFIER_MOTION_NONE) +LinkLuaModifier("modifier_card_69", "cards/examples/card_69", LUA_MODIFIER_MOTION_NONE) +LinkLuaModifier("modifier_card_70", "cards/examples/card_70", LUA_MODIFIER_MOTION_NONE) +LinkLuaModifier("modifier_card_71", "cards/examples/card_71", LUA_MODIFIER_MOTION_NONE) +LinkLuaModifier("modifier_card_72", "cards/examples/card_72", LUA_MODIFIER_MOTION_NONE) +LinkLuaModifier("modifier_arsenal_dynamic_stats", "arsenal/items/dynamic_stats", LUA_MODIFIER_MOTION_NONE) +LinkLuaModifier("modifier_incoming_damage_reduction", "utils/incoming_damage_reduction_combine", LUA_MODIFIER_MOTION_NONE) +LinkLuaModifier("modifier_card_74_armor_reduce", "cards/examples/card_74", LUA_MODIFIER_MOTION_NONE) +return ____exports diff --git a/scripts/vscripts/admin_menu.lua b/scripts/vscripts/admin_menu.lua new file mode 100644 index 0000000..93b9841 --- /dev/null +++ b/scripts/vscripts/admin_menu.lua @@ -0,0 +1,1806 @@ +local ____lualib = require("lualib_bundle") +local __TS__Class = ____lualib.__TS__Class +local Map = ____lualib.Map +local __TS__New = ____lualib.__TS__New +local __TS__ArrayIncludes = ____lualib.__TS__ArrayIncludes +local __TS__StringStartsWith = ____lualib.__TS__StringStartsWith +local __TS__ArraySort = ____lualib.__TS__ArraySort +local __TS__ArrayIsArray = ____lualib.__TS__ArrayIsArray +local __TS__ObjectValues = ____lualib.__TS__ObjectValues +local __TS__ArrayFrom = ____lualib.__TS__ArrayFrom +local __TS__ArrayMap = ____lualib.__TS__ArrayMap +local __TS__ArraySome = ____lualib.__TS__ArraySome +local __TS__Decorate = ____lualib.__TS__Decorate +local ____exports = {} +local ____tstl_2Dutils = require("lib.tstl-utils") +local reloadable = ____tstl_2Dutils.reloadable +require("utils.utils") +local ____crystal_currency = require("crystal_currency") +local CrystalCurrency = ____crystal_currency.CrystalCurrency +local ____blackshop = require("blackshop") +local BlackShop = ____blackshop.BlackShop +local ____CardSystem = require("cards.CardSystem") +local ShowCardSelectionToPlayer = ____CardSystem.ShowCardSelectionToPlayer +local ____SpawnManager = require("SpawnManager") +local SpawnManager = ____SpawnManager.SpawnManager +local ____WaveManager = require("WaveManager") +local WaveManager = ____WaveManager.WaveManager +local ____DayNightCycleManager = require("DayNightCycleManager") +local DayNightCycleManager = ____DayNightCycleManager.DayNightCycleManager +local DOTA_ModifyXP_Unspecified = 0 +____exports.AdminMenuManager = __TS__Class() +local AdminMenuManager = ____exports.AdminMenuManager +AdminMenuManager.name = "AdminMenuManager" +AdminMenuManager.____file_path = "scripts/vscripts/admin_menu.lua" +function AdminMenuManager.prototype.____constructor(self) + self.selectedEntities = __TS__New(Map) + self.spawnZoneCounter = 0 +end +function AdminMenuManager.getInstance(self) + if not ____exports.AdminMenuManager.instance then + ____exports.AdminMenuManager.instance = __TS__New(____exports.AdminMenuManager) + end + return ____exports.AdminMenuManager.instance +end +function AdminMenuManager.prototype.initialize(self) + CustomGameEventManager:RegisterListener( + "admin_spawn_hero", + function(source, event) + local playerId = event.PlayerID ~= nil and event.PlayerID or source + self:handleSpawnHero(playerId, event) + end + ) + CustomGameEventManager:RegisterListener( + "admin_spawn_target", + function(source, event) + local playerId = event.PlayerID ~= nil and event.PlayerID or source + self:handleSpawnTarget(playerId, event) + end + ) + CustomGameEventManager:RegisterListener( + "admin_give_item", + function(source, event) + local playerId = event.PlayerID ~= nil and event.PlayerID or source + self:handleGiveItem(playerId, event) + end + ) + CustomGameEventManager:RegisterListener( + "admin_request_items_list", + function(source, event) + local playerId = event.PlayerID ~= nil and event.PlayerID or source + self:handleRequestItemsList(playerId, event) + end + ) + CustomGameEventManager:RegisterListener( + "admin_change_level", + function(source, event) + local playerId = event.PlayerID ~= nil and event.PlayerID or source + self:handleChangeLevel(playerId, event) + end + ) + CustomGameEventManager:RegisterListener( + "admin_toggle_invulnerable", + function(source, event) + local playerId = event.PlayerID ~= nil and event.PlayerID or source + self:handleToggleInvulnerable(playerId, event) + end + ) + CustomGameEventManager:RegisterListener( + "admin_toggle_scepter", + function(source, event) + local playerId = event.PlayerID ~= nil and event.PlayerID or source + self:handleToggleScepter(playerId, event) + end + ) + CustomGameEventManager:RegisterListener( + "admin_toggle_shard", + function(source, event) + local playerId = event.PlayerID ~= nil and event.PlayerID or source + self:handleToggleShard(playerId, event) + end + ) + CustomGameEventManager:RegisterListener( + "admin_change_side", + function(source, event) + local playerId = event.PlayerID ~= nil and event.PlayerID or source + self:handleChangeSide(playerId, event) + end + ) + CustomGameEventManager:RegisterListener( + "admin_reset_hero", + function(source, event) + local playerId = event.PlayerID ~= nil and event.PlayerID or source + self:handleResetHero(playerId, event) + end + ) + CustomGameEventManager:RegisterListener( + "admin_delete_hero", + function(source, event) + local playerId = event.PlayerID ~= nil and event.PlayerID or source + self:handleDeleteHero(playerId, event) + end + ) + CustomGameEventManager:RegisterListener( + "admin_delete_entity", + function(source, event) + local playerId = event.PlayerID ~= nil and event.PlayerID or source + self:handleDeleteEntity(playerId, event) + end + ) + CustomGameEventManager:RegisterListener( + "admin_toggle_setting", + function(source, event) + local playerId = event.PlayerID ~= nil and event.PlayerID or source + self:handleToggleSetting(playerId, event) + end + ) + CustomGameEventManager:RegisterListener( + "admin_refresh_spells", + function(source, event) + local playerId = event.PlayerID ~= nil and event.PlayerID or source + self:handleRefreshSpells(playerId, event) + end + ) + CustomGameEventManager:RegisterListener( + "admin_refresh_health", + function(source, event) + local playerId = event.PlayerID ~= nil and event.PlayerID or source + self:handleRefreshHealth(playerId, event) + end + ) + CustomGameEventManager:RegisterListener( + "admin_give_gold", + function(source, event) + local playerId = event.PlayerID ~= nil and event.PlayerID or source + self:handleGiveGold(playerId, event) + end + ) + CustomGameEventManager:RegisterListener( + "admin_change_luck", + function(source, event) + local playerId = event.PlayerID ~= nil and event.PlayerID or source + self:handleChangeLuck(playerId, event) + end + ) + CustomGameEventManager:RegisterListener( + "admin_change_physical_vampirism", + function(source, event) + local playerId = event.PlayerID ~= nil and event.PlayerID or source + self:handleChangePhysicalVampirism(playerId, event) + end + ) + CustomGameEventManager:RegisterListener( + "admin_change_magical_vampirism", + function(source, event) + local playerId = event.PlayerID ~= nil and event.PlayerID or source + self:handleChangeMagicalVampirism(playerId, event) + end + ) + CustomGameEventManager:RegisterListener( + "admin_give_crystals", + function(source, event) + local playerId = event.PlayerID ~= nil and event.PlayerID or source + self:handleGiveCrystals(playerId, event) + end + ) + CustomGameEventManager:RegisterListener( + "admin_pause_game", + function(source, event) + local playerId = event.PlayerID ~= nil and event.PlayerID or source + self:handlePauseGame(playerId) + end + ) + CustomGameEventManager:RegisterListener( + "admin_exit_game", + function(source, event) + local playerId = event.PlayerID ~= nil and event.PlayerID or source + self:handleExitGame(playerId) + end + ) + CustomGameEventManager:RegisterListener( + "admin_set_timescale", + function(source, event) + local playerId = event.PlayerID ~= nil and event.PlayerID or source + self:handleSetTimeScale(playerId, event) + end + ) + CustomGameEventManager:RegisterListener( + "admin_script_reload", + function(source, event) + local playerId = event.PlayerID ~= nil and event.PlayerID or source + self:handleScriptReload(playerId) + end + ) + CustomGameEventManager:RegisterListener( + "admin_spawn_zone_create", + function(source, event) + local playerId = event.PlayerID ~= nil and event.PlayerID or source + self:handleSpawnZoneCreate(playerId, event) + end + ) + CustomGameEventManager:RegisterListener( + "admin_spawn_zone_delete_here", + function(source, event) + local playerId = event.PlayerID ~= nil and event.PlayerID or source + self:handleSpawnZoneDeleteHere(playerId) + end + ) + CustomGameEventManager:RegisterListener( + "admin_toggle_spawn_debug_zones", + function(source, event) + local playerId = event.PlayerID ~= nil and event.PlayerID or source + self:handleToggleSpawnDebugZones(playerId) + end + ) + CustomGameEventManager:RegisterListener( + "admin_change_hero", + function(source, event) + local playerId = event.PlayerID ~= nil and event.PlayerID or source + self:handleChangeHero(playerId, event) + end + ) + CustomGameEventManager:RegisterListener( + "admin_request_steam_id", + function(source, event) + local playerId = event.PlayerID ~= nil and event.PlayerID or source + self:handleRequestSteamId(playerId) + end + ) + CustomGameEventManager:RegisterListener( + "admin_check_cheat_mode", + function(source, event) + local playerId = event.PlayerID ~= nil and event.PlayerID or source + self:handleCheckCheatMode(playerId) + end + ) + CustomGameEventManager:RegisterListener( + "admin_request_spawn_units_list", + function(source, event) + local playerId = event.PlayerID ~= nil and event.PlayerID or source + self:handleRequestSpawnUnitsList(playerId) + end + ) + CustomGameEventManager:RegisterListener( + "admin_spawn_units_once", + function(source, event) + local playerId = event.PlayerID ~= nil and event.PlayerID or source + self:handleSpawnUnitsOnce(playerId, event) + end + ) + CustomGameEventManager:RegisterListener( + "admin_set_next_wave", + function(source, event) + local playerId = event.PlayerID ~= nil and event.PlayerID or source + self:handleSetNextWave(playerId, event) + end + ) + CustomGameEventManager:RegisterListener( + "admin_force_morning", + function(source, event) + local playerId = event.PlayerID ~= nil and event.PlayerID or source + self:handleForceMorning(playerId) + end + ) + CustomGameEventManager:RegisterListener( + "admin_adjust_daynight_timer", + function(source, event) + local playerId = event.PlayerID ~= nil and event.PlayerID or source + self:handleAdjustDayNightTimer(playerId, event) + end + ) + CustomGameEventManager:RegisterListener( + "admin_set_daynight_timer", + function(source, event) + local playerId = event.PlayerID ~= nil and event.PlayerID or source + self:handleSetDayNightTimer(playerId, event) + end + ) + CustomGameEventManager:RegisterListener( + "admin_show_card_selection", + function(source, event) + local playerId = event.PlayerID ~= nil and event.PlayerID or source + self:handleShowCardSelection(playerId, event) + end + ) + print("[AdminMenu] Инициализирован менеджер админ-меню") +end +function AdminMenuManager.prototype.checkAdminAccess(self, playerId) + if GameRules:IsCheatMode() or Convars:GetBool("sv_cheats") then + return true + end + local steamId = tostring(PlayerResource:GetSteamAccountID(playerId)) + local allowedSteamIds = {"877002179", "453736017", "170695158"} + local hasAccess = __TS__ArrayIncludes(allowedSteamIds, steamId) + if hasAccess then + print(((("[AdminMenu] Доступ разрешен для игрока " .. tostring(playerId)) .. " (Steam ID: ") .. steamId) .. ")") + else + print(((("[AdminMenu] Доступ запрещен для игрока " .. tostring(playerId)) .. " (Steam ID: ") .. steamId) .. ")") + end + return hasAccess +end +function AdminMenuManager.prototype.handleToggleSpawnDebugZones(self, playerId) + if not IsServer() then + return + end + if not self:checkAdminAccess(playerId) then + print("[AdminMenu] Отказано в доступе к debug зонам спавна") + return + end + SpawnManager:getInstance():ToggleDebugZones() +end +function AdminMenuManager.prototype.handleRequestSpawnUnitsList(self, playerId) + if not IsServer() then + return + end + if not self:checkAdminAccess(playerId) then + print("[AdminMenu] Отказано в доступе к списку юнитов спавна") + return + end + local player = PlayerResource:GetPlayer(playerId) + if not player then + return + end + local units = {} + do + local function ____catch(e) + print("[AdminMenu] Ошибка при загрузке npc_units_custom.txt: " .. tostring(e)) + end + local ____try, ____hasReturned = pcall(function() + local kv = LoadKeyValues("scripts/npc/npc_units_custom.txt") + if kv then + for key in pairs(kv) do + if type(key) == "string" and __TS__StringStartsWith(key, "npc_") then + units[#units + 1] = key + end + end + end + end) + if not ____try then + ____catch(____hasReturned) + end + end + __TS__ArraySort(units) + CustomGameEventManager:Send_ServerToPlayer(player, "admin_spawn_units_list", {units = units}) +end +function AdminMenuManager.prototype.handleSpawnUnitsOnce(self, playerId, event) + if not IsServer() then + return + end + if not self:checkAdminAccess(playerId) then + print("[AdminMenu] Отказано в доступе к одноразовому спавну юнитов") + return + end + local hero = PlayerResource:GetSelectedHeroEntity(playerId) + if not hero or not IsValidEntity(hero) then + print("[AdminMenu] Не найден валидный герой для одноразового спавна юнитов") + return + end + local center = hero:GetAbsOrigin() + local units = {} + if event.units then + local rawUnits = __TS__ArrayIsArray(event.units) and event.units or __TS__ObjectValues(event.units) + for ____, u in ipairs(rawUnits) do + if u and u.unitName ~= nil then + local count = u.count and u.count > 0 and u.count or 1 + units[#units + 1] = {unitName = u.unitName, count = count} + end + end + end + if #units == 0 then + print("[AdminMenu] Одноразовый спавн отменён: список юнитов пуст") + return + end + for ____, unitInfo in ipairs(units) do + do + local i = 0 + while i < unitInfo.count do + local angle = math.rad(RandomFloat(0, 360)) + local distance = RandomFloat(0, 200) + local pos = GetGroundPosition( + Vector( + center.x + distance * math.cos(angle), + center.y + distance * math.sin(angle), + center.z + ), + nil + ) + CreateUnitByName( + unitInfo.unitName, + pos, + true, + nil, + nil, + DOTA_TEAM_NEUTRALS + ) + i = i + 1 + end + end + end +end +function AdminMenuManager.prototype.handleSpawnHero(self, playerId, event) + if not IsServer() then + return + end + if not self:checkAdminAccess(playerId) then + print("[AdminMenu] Отказано в доступе") + return + end + local heroName = event.heroName or "npc_dota_hero_crystal_maiden" + local team = event.team or DOTA_TEAM_GOODGUYS + local ____type = event.type or "ally" + local player = PlayerResource:GetPlayer(playerId) + local spawnPos + if player then + local hero = PlayerResource:GetSelectedHeroEntity(playerId) + if hero and IsValidEntity(hero) then + spawnPos = hero:GetAbsOrigin() + else + spawnPos = Vector(0, 0, 256) + end + else + spawnPos = Vector(0, 0, 256) + end + PrecacheUnitByNameAsync( + heroName, + function() + local unit = CreateUnitByName( + heroName, + spawnPos, + true, + nil, + nil, + team + ) + if unit and IsValidEntity(unit) then + FindClearSpaceForUnit(unit, spawnPos, true) + unit:SetControllableByPlayer(playerId, true) + if unit:IsRealHero() then + local hero = unit + while hero:GetLevel() > 1 do + hero:HeroLevelUp(false) + end + hero:SetAbilityPoints(1) + end + self.selectedEntities:set( + unit:GetEntityIndex(), + unit + ) + print((("[AdminMenu] Заспавнен " .. heroName) .. " для команды ") .. tostring(team)) + end + end + ) +end +function AdminMenuManager.prototype.handleSpawnTarget(self, playerId, event) + if not IsServer() then + return + end + if not self:checkAdminAccess(playerId) then + print("[AdminMenu] Отказано в доступе") + return + end + local player = PlayerResource:GetPlayer(playerId) + local spawnPos + if player then + local hero = PlayerResource:GetSelectedHeroEntity(playerId) + if hero and IsValidEntity(hero) then + local heroPos = hero:GetAbsOrigin() + local heroForward = hero:GetForwardVector() + local offset = Vector(heroForward.x * 300, heroForward.y * 300, 0) + spawnPos = Vector(heroPos.x + offset.x, heroPos.y + offset.y, heroPos.z) + spawnPos = GetGroundPosition(spawnPos, nil) + else + spawnPos = Vector(0, 0, 256) + end + else + spawnPos = Vector(0, 0, 256) + end + local unit = CreateUnitByName( + "npc_dummy_test", + spawnPos, + true, + nil, + nil, + DOTA_TEAM_NEUTRALS + ) + if unit and IsValidEntity(unit) then + FindClearSpaceForUnit(unit, spawnPos, true) + unit:SetControllableByPlayer(playerId, true) + if player then + unit:SetOwner(player) + end + local dpsTracker = unit:AddNewModifier( + unit, + getModifierSourceAbility(nil, unit), + "modifier_dps_tracker", + {} + ) + self.selectedEntities:set( + unit:GetEntityIndex(), + unit + ) + print((((((("[AdminMenu] Заспавнена мишень npc_dummy_test на позиции " .. tostring(spawnPos.x)) .. ", ") .. tostring(spawnPos.y)) .. ", ") .. tostring(spawnPos.z)) .. ", управляемая игроком ") .. tostring(playerId)) + else + print("[AdminMenu] Ошибка: не удалось заспавнить мишень npc_dummy_test") + end +end +function AdminMenuManager.prototype.handleGiveItem(self, playerId, event) + if not IsServer() then + return + end + if not self:checkAdminAccess(playerId) then + print("[AdminMenu] Отказано в доступе") + return + end + local itemName = event.itemName + print((("[AdminMenu] handleGiveItem вызван для игрока " .. tostring(playerId)) .. ", предмет: ") .. tostring(itemName)) + if not itemName then + print("[AdminMenu] Ошибка: не указано имя предмета") + return + end + local player = PlayerResource:GetPlayer(playerId) + local targetUnit + if self.selectedEntities.size > 0 then + local firstEntity = __TS__ArrayFrom(self.selectedEntities:values())[1] + if firstEntity and IsValidEntity(firstEntity) then + targetUnit = firstEntity + end + end + if not targetUnit and player then + local hero = PlayerResource:GetSelectedHeroEntity(playerId) + if hero and IsValidEntity(hero) then + targetUnit = hero + end + end + if not targetUnit or not IsValidEntity(targetUnit) then + print("[AdminMenu] Ошибка: не найдена цель для выдачи предмета. Выбранных сущностей: " .. tostring(self.selectedEntities.size)) + return + end + print((("[AdminMenu] Цель найдена: " .. targetUnit:GetUnitName()) .. ", является героем: ") .. tostring(targetUnit:IsRealHero())) + local isBlackshopItem = string.sub(itemName, 1, 16) == "item_blackshop_" + print((("[AdminMenu] Предмет является blackshop: " .. tostring(isBlackshopItem)) .. ", itemName: ") .. itemName) + if isBlackshopItem then + if targetUnit:IsRealHero() then + local hero = targetUnit + print("[AdminMenu] Создание blackshop предмета " .. itemName) + local item = CreateItem(itemName, nil, nil) + if item and IsValidEntity(item) then + print("[AdminMenu] Предмет создан, добавление герою") + local addedItem = hero:AddItem(item) + if addedItem and IsValidEntity(addedItem) then + print("[AdminMenu] Предмет добавлен, использование...") + addedItem:CastAbility() + print((("[AdminMenu] Выдан blackshop предмет " .. itemName) .. " игроку ") .. tostring(playerId)) + else + print(("[AdminMenu] Ошибка: не удалось добавить blackshop предмет " .. itemName) .. ", инвентарь полон?") + UTIL_Remove(item) + end + else + print("[AdminMenu] Ошибка: не удалось создать предмет " .. itemName) + end + else + print("[AdminMenu] Ошибка: цель не является героем для blackshop предмета") + end + else + if targetUnit:IsRealHero() then + local hero = targetUnit + print("[AdminMenu] Выдача обычного предмета " .. itemName) + local addedItem = hero:AddItemByName(itemName) + if addedItem ~= nil and addedItem ~= nil then + print((("[AdminMenu] Выдан предмет " .. itemName) .. " игроку ") .. tostring(playerId)) + else + print(("[AdminMenu] Ошибка: не удалось выдать предмет " .. itemName) .. ", возможно инвентарь полон или предмет не существует") + end + else + print("[AdminMenu] Цель не является героем, создаем предмет на земле рядом с " .. targetUnit:GetUnitName()) + local unitPos = targetUnit:GetAbsOrigin() + local forward = targetUnit:GetForwardVector() + local dropPos = Vector(unitPos.x + forward.x * 100, unitPos.y + forward.y * 100, unitPos.z) + local groundPos = GetGroundPosition(dropPos, nil) + local item = CreateItem(itemName, nil, nil) + if item and IsValidEntity(item) then + CreateItemOnPositionForLaunch(groundPos, item) + item:SetAbsOrigin(groundPos) + print((("[AdminMenu] Предмет " .. itemName) .. " создан на земле рядом с ") .. targetUnit:GetUnitName()) + else + print("[AdminMenu] Ошибка: не удалось создать предмет " .. itemName) + end + end + end +end +function AdminMenuManager.prototype.handleRequestItemsList(self, playerId, event) + if not IsServer() then + return + end + if not self:checkAdminAccess(playerId) then + return + end + local category = event.category or "default" + local player = PlayerResource:GetPlayer(playerId) + if not player then + return + end + local itemsList = {} + if category == "blackshop" then + local blackShop = BlackShop:getInstance() + local shopItems = blackShop.shopItems + if shopItems and __TS__ArrayIsArray(shopItems) then + local itemsWithQuality = __TS__ArrayMap( + shopItems, + function(____, item) return {name = item.name, quality = item.quality} end + ) + CustomGameEventManager:Send_ServerToPlayer(player, "admin_items_list", {category = category, items = itemsWithQuality}) + print((("[AdminMenu] Отправка списка blackshop предметов с качеством для категории " .. category) .. ", количество: ") .. tostring(#itemsWithQuality)) + return + else + print("[AdminMenu] Ошибка: не удалось получить список blackshop предметов") + end + elseif category == "util_items" then + itemsList = self:getItemsFromKvByScriptPrefix({"scripts/npc/items/util_items.kv"}, {"items/util_items/"}) + elseif category == "quest_items" then + itemsList = self:getItemsFromKvByScriptPrefix({"scripts/npc/items/quest_items.kv"}, {"items/quest_items/"}, true) + elseif category == "admin_items" then + itemsList = {"item_blink", "item_test"} + else + itemsList = self:getItemsFromKvByScriptPrefix({"scripts/npc/items/custom_items.kv", "scripts/npc/npc_items_custom.txt"}, {"items/default_items/"}) + end + print((("[AdminMenu] Отправка списка предметов для категории " .. category) .. ", количество: ") .. tostring(#itemsList)) + CustomGameEventManager:Send_ServerToPlayer(player, "admin_items_list", {category = category, items = itemsList}) + print("[AdminMenu] Список предметов отправлен игроку " .. tostring(playerId)) +end +function AdminMenuManager.prototype.getItemsFromKvByScriptPrefix(self, kvPaths, scriptPrefixes, allowItemsWithoutScriptFile) + if allowItemsWithoutScriptFile == nil then + allowItemsWithoutScriptFile = false + end + do + local function ____catch(e) + print("[AdminMenu] Ошибка получения списка предметов из KV: " .. tostring(e)) + return true, {} + end + local ____try, ____hasReturned, ____returnValue = pcall(function() + local uniqueItems = {} + for ____, kvPath in ipairs(kvPaths) do + do + local kv = LoadKeyValues(kvPath) + if not kv then + goto __continue130 + end + local abilitiesRoot = kv.DOTAAbilities or kv + for key in pairs(abilitiesRoot) do + do + if not __TS__StringStartsWith(key, "item_") then + goto __continue132 + end + if __TS__StringStartsWith(key, "item_recipe_") then + goto __continue132 + end + if __TS__StringStartsWith(key, "item_blackshop_") then + goto __continue132 + end + if key == "item_aghanims_shard" or key == "item_aghanims_shard_roshan" then + goto __continue132 + end + local abilityDef = abilitiesRoot[key] + local scriptFile = abilityDef and abilityDef.ScriptFile + if type(scriptFile) == "string" and #tostring(scriptFile) > 0 then + local scriptFilePath = tostring(scriptFile) + local hasMatchedPrefix = __TS__ArraySome( + scriptPrefixes, + function(____, prefix) return __TS__StringStartsWith(scriptFilePath, prefix) end + ) + if not hasMatchedPrefix then + goto __continue132 + end + elseif not allowItemsWithoutScriptFile then + goto __continue132 + end + uniqueItems[key] = true + end + ::__continue132:: + end + end + ::__continue130:: + end + local items = {} + for itemName in pairs(uniqueItems) do + items[#items + 1] = itemName + end + __TS__ArraySort(items) + return true, items + end) + if not ____try then + ____hasReturned, ____returnValue = ____catch(____hasReturned) + end + if ____hasReturned then + return ____returnValue + end + end +end +function AdminMenuManager.prototype.handleChangeLevel(self, playerId, event) + if not IsServer() then + return + end + if not self:checkAdminAccess(playerId) then + return + end + local amount = event.amount or 1 + local entityIndices = {} + if event.entityIndices then + if __TS__ArrayIsArray(event.entityIndices) then + entityIndices = event.entityIndices + else + entityIndices = __TS__ObjectValues(event.entityIndices) + end + end + if #entityIndices == 0 then + print("[AdminMenu] Нет выбранных сущностей для изменения уровня. Выделите существо или используйте кнопки +СОЮЗНИК/+ВРАГ/+МИШЕНЬ") + return + end + for ____, entityIndex in ipairs(entityIndices) do + local unit = self.selectedEntities:get(entityIndex) + if not unit or not IsValidEntity(unit) then + unit = EntIndexToHScript(entityIndex) + end + if unit and IsValidEntity(unit) and unit:IsRealHero() then + local hero = unit + local currentLevel = hero:GetLevel() + local newLevel = math.min( + math.max(currentLevel + amount, 1), + 30 + ) + if newLevel > currentLevel then + while hero:GetLevel() < newLevel do + hero:HeroLevelUp(false) + end + hero:SetAbilityPoints(hero:GetAbilityPoints() + (newLevel - currentLevel)) + elseif newLevel < currentLevel then + local xpNeeded = hero:GetCurrentXP() + hero:AddExperience(-xpNeeded, DOTA_ModifyXP_Unspecified, false, true) + while hero:GetLevel() < newLevel do + hero:HeroLevelUp(false) + end + end + print((((("[AdminMenu] Уровень героя " .. hero:GetUnitName()) .. " изменен: ") .. tostring(currentLevel)) .. " -> ") .. tostring(newLevel)) + end + end +end +function AdminMenuManager.prototype.handleToggleInvulnerable(self, playerId, event) + if not IsServer() then + return + end + if not self:checkAdminAccess(playerId) then + return + end + local entityIndices = {} + if event.entityIndices then + if __TS__ArrayIsArray(event.entityIndices) then + entityIndices = event.entityIndices + else + entityIndices = __TS__ObjectValues(event.entityIndices) + end + end + if #entityIndices == 0 then + local player = PlayerResource:GetPlayer(playerId) + if player then + local currentHero = player:GetAssignedHero() or PlayerResource:GetSelectedHeroEntity(playerId) + if currentHero and IsValidEntity(currentHero) then + entityIndices = {currentHero:GetEntityIndex()} + end + end + end + for ____, entityIndex in ipairs(entityIndices) do + local unit = self.selectedEntities:get(entityIndex) + if not unit or not IsValidEntity(unit) then + unit = EntIndexToHScript(entityIndex) + end + if unit and IsValidEntity(unit) then + local hasModifier = unit:HasModifier("modifier_fountain_glyph") + if hasModifier then + unit:RemoveModifierByName("modifier_fountain_glyph") + print("[AdminMenu] Модификатор modifier_invulnerable удален у " .. unit:GetUnitName()) + else + unit:AddNewModifier( + nil, + getModifierSourceAbility(nil, nil), + "modifier_fountain_glyph", + {} + ) + print("[AdminMenu] Модификатор modifier_invulnerable добавлен " .. unit:GetUnitName()) + end + end + end +end +function AdminMenuManager.prototype.handleToggleScepter(self, playerId, event) + if not IsServer() then + return + end + if not self:checkAdminAccess(playerId) then + return + end + local entityIndices = {} + if event.entityIndices then + if __TS__ArrayIsArray(event.entityIndices) then + entityIndices = event.entityIndices + else + entityIndices = __TS__ObjectValues(event.entityIndices) + end + end + for ____, entityIndex in ipairs(entityIndices) do + local unit = self.selectedEntities:get(entityIndex) + if not unit or not IsValidEntity(unit) then + unit = EntIndexToHScript(entityIndex) + end + if unit and IsValidEntity(unit) and unit:IsRealHero() then + local hero = unit + local existingBlessing = hero:FindItemInInventory("item_ultimate_scepter_2") + if not existingBlessing then + hero:AddItemByName("item_ultimate_scepter_2") + print("[AdminMenu] Aghanim's Blessing добавлен " .. unit:GetUnitName()) + else + print(("[AdminMenu] У " .. unit:GetUnitName()) .. " уже есть Aghanim's Blessing") + end + end + end +end +function AdminMenuManager.prototype.handleToggleShard(self, playerId, event) + if not IsServer() then + return + end + if not self:checkAdminAccess(playerId) then + return + end + local entityIndices = {} + if event.entityIndices then + if __TS__ArrayIsArray(event.entityIndices) then + entityIndices = event.entityIndices + else + entityIndices = __TS__ObjectValues(event.entityIndices) + end + end + if #entityIndices == 0 then + print("[AdminMenu] Нет выбранных сущностей для добавления Shard. Выделите существо или используйте кнопки +СОЮЗНИК/+ВРАГ/+МИШЕНЬ") + return + end + for ____, entityIndex in ipairs(entityIndices) do + local unit = self.selectedEntities:get(entityIndex) + if not unit or not IsValidEntity(unit) then + unit = EntIndexToHScript(entityIndex) + end + if unit and IsValidEntity(unit) and unit:IsRealHero() then + local hero = unit + if not HasShard(nil, hero) then + local existingShard = hero:FindItemInInventory("item_aghanims_shard") + if not existingShard then + hero:AddItemByName("item_aghanims_shard") + print("[AdminMenu] Aghanim's Shard добавлен " .. unit:GetUnitName()) + else + print(("[AdminMenu] У " .. unit:GetUnitName()) .. " уже есть Aghanim's Shard") + end + else + print(("[AdminMenu] У " .. unit:GetUnitName()) .. " уже есть Shard") + end + end + end +end +function AdminMenuManager.prototype.handleChangeSide(self, playerId, event) + if not IsServer() then + return + end + if not self:checkAdminAccess(playerId) then + return + end + local entityIndices = {} + if event.entityIndices then + if __TS__ArrayIsArray(event.entityIndices) then + entityIndices = event.entityIndices + else + entityIndices = __TS__ObjectValues(event.entityIndices) + end + end + if #entityIndices == 0 then + print("[AdminMenu] Нет выбранных сущностей для смены стороны. Выделите существо или используйте кнопки +СОЮЗНИК/+ВРАГ/+МИШЕНЬ") + return + end + for ____, entityIndex in ipairs(entityIndices) do + local unit = self.selectedEntities:get(entityIndex) + if not unit or not IsValidEntity(unit) then + unit = EntIndexToHScript(entityIndex) + end + if unit and IsValidEntity(unit) then + local currentTeam = unit:GetTeamNumber() + local ____temp_2 + if currentTeam == DOTA_TEAM_GOODGUYS then + ____temp_2 = DOTA_TEAM_BADGUYS + else + ____temp_2 = DOTA_TEAM_GOODGUYS + end + local newTeam = ____temp_2 + unit:SetTeam(newTeam) + print((((("[AdminMenu] Команда " .. unit:GetUnitName()) .. " изменена: ") .. tostring(currentTeam)) .. " -> ") .. tostring(newTeam)) + end + end +end +function AdminMenuManager.prototype.handleResetHero(self, playerId, event) + if not IsServer() then + return + end + if not self:checkAdminAccess(playerId) then + return + end + local heroName = event.heroName or "npc_dota_hero_crystal_maiden" + local team = event.team or DOTA_TEAM_GOODGUYS + local player = PlayerResource:GetPlayer(playerId) + if not player then + print(("[AdminMenu] Игрок " .. tostring(playerId)) .. " не найден") + return + end + local currentHero = player:GetAssignedHero() or PlayerResource:GetSelectedHeroEntity(playerId) + if not currentHero or not IsValidEntity(currentHero) then + print(("[AdminMenu] У игрока " .. tostring(playerId)) .. " нет героя для замены") + return + end + local spawnPos = currentHero:GetAbsOrigin() + local currentTeam = currentHero:GetTeam() + local finalTeam = team or currentTeam + local entityIndex = currentHero:GetEntityIndex() + self.selectedEntities:delete(entityIndex) + print((((("[AdminMenu] Начинаем замену героя для игрока " .. tostring(playerId)) .. ": ") .. currentHero:GetUnitName()) .. " -> ") .. heroName) + PrecacheUnitByNameAsync( + heroName, + function() + local addon = GameRules.Addon + if addon ~= nil then + addon:resetWorldLightFixSpawnForPlayer(playerId) + end + local newHero = CreateUnitByName( + heroName, + spawnPos, + false, + player, + player, + finalTeam + ) + if not newHero or not IsValidEntity(newHero) then + print("[AdminMenu] Ошибка: не удалось создать нового героя " .. heroName) + return + end + print((("[AdminMenu] Новый герой " .. heroName) .. " создан, EntityIndex: ") .. tostring(newHero:GetEntityIndex())) + FindClearSpaceForUnit(newHero, spawnPos, true) + newHero:SetControllableByPlayer(playerId, true) + newHero:SetOwner(player) + if newHero:IsRealHero() then + while newHero:GetLevel() > 1 do + newHero:HeroLevelUp(false) + end + newHero:SetAbilityPoints(1) + end + player:SetAssignedHeroEntity(newHero) + self.selectedEntities:set( + newHero:GetEntityIndex(), + newHero + ) + print((("[AdminMenu] Новый герой " .. heroName) .. " назначен игроку ") .. tostring(playerId)) + Timers:CreateTimer( + 0.1, + function() + if currentHero and IsValidEntity(currentHero) then + currentHero:RemoveSelf() + print(("[AdminMenu] Старый герой " .. currentHero:GetUnitName()) .. " удален") + end + end + ) + print((("[AdminMenu] Герой игрока " .. tostring(playerId)) .. " успешно заменен на ") .. heroName) + end + ) +end +function AdminMenuManager.prototype.handleDeleteHero(self, playerId, event) + if not IsServer() then + return + end + if not self:checkAdminAccess(playerId) then + return + end + local entityIndices = {} + if event.entityIndices then + if __TS__ArrayIsArray(event.entityIndices) then + entityIndices = event.entityIndices + else + entityIndices = __TS__ObjectValues(event.entityIndices) + end + end + for ____, entityIndex in ipairs(entityIndices) do + do + local unit = self.selectedEntities:get(entityIndex) + if not unit or not IsValidEntity(unit) then + unit = EntIndexToHScript(entityIndex) + end + if not unit or not IsValidEntity(unit) then + goto __continue231 + end + local isAssignedHero = false + if unit:IsRealHero() then + local hero = unit + do + local i = 0 + while i < DOTA_MAX_TEAM_PLAYERS do + if PlayerResource:IsValidPlayerID(i) then + local player = PlayerResource:GetPlayer(i) + if player then + local assignedHero = player:GetAssignedHero() + if assignedHero and assignedHero == hero then + isAssignedHero = true + print((("[AdminMenu] Нельзя удалить основного героя игрока " .. tostring(i)) .. ": ") .. hero:GetUnitName()) + break + end + end + end + i = i + 1 + end + end + end + if not isAssignedHero then + self.selectedEntities:delete(entityIndex) + unit:RemoveSelf() + print("[AdminMenu] Удален " .. unit:GetUnitName()) + end + end + ::__continue231:: + end +end +function AdminMenuManager.prototype.handleDeleteEntity(self, playerId, event) + if not IsServer() then + return + end + if not self:checkAdminAccess(playerId) then + print("[AdminMenu] Отказано в доступе для удаления существ") + return + end + local entityIndices = {} + if event.entityIndices then + if __TS__ArrayIsArray(event.entityIndices) then + entityIndices = event.entityIndices + else + entityIndices = __TS__ObjectValues(event.entityIndices) + end + end + if #entityIndices <= 1 and self.selectedEntities.size > 0 then + local serverEntities = __TS__ArrayFrom(self.selectedEntities:keys()) + if #serverEntities > #entityIndices then + entityIndices = serverEntities + print(((("[AdminMenu] Используем " .. tostring(#entityIndices)) .. " существ из selectedEntities на сервере (было передано ") .. tostring(#entityIndices == #serverEntities and 0 or 1)) .. ")") + end + end + if #entityIndices == 0 then + print("[AdminMenu] Нет выбранных существ для удаления. Выделите существо или используйте кнопки +СОЮЗНИК/+ВРАГ/+МИШЕНЬ") + return + end + print( + ("[AdminMenu] Начало удаления группы из " .. tostring(#entityIndices)) .. " существ. EntityIndices:", + entityIndices + ) + local deletedCount = 0 + local skippedCount = 0 + for ____, entityIndex in ipairs(entityIndices) do + do + local unit = self.selectedEntities:get(entityIndex) + if not unit or not IsValidEntity(unit) then + unit = EntIndexToHScript(entityIndex) + end + if not unit or not IsValidEntity(unit) then + skippedCount = skippedCount + 1 + goto __continue250 + end + local unitPlayerId = unit:GetPlayerOwnerID() + local hasPlayerId = unitPlayerId ~= -1 and PlayerResource:IsValidPlayerID(unitPlayerId) + if not hasPlayerId then + local isAssignedHero = false + if unit:IsRealHero() then + local hero = unit + do + local i = 0 + while i < DOTA_MAX_TEAM_PLAYERS do + if PlayerResource:IsValidPlayerID(i) then + local player = PlayerResource:GetPlayer(i) + if player then + local assignedHero = player:GetAssignedHero() + if assignedHero and assignedHero == hero then + isAssignedHero = true + print((("[AdminMenu] Нельзя удалить основного героя игрока " .. tostring(i)) .. ": ") .. hero:GetUnitName()) + skippedCount = skippedCount + 1 + break + end + end + end + i = i + 1 + end + end + end + if not isAssignedHero then + self.selectedEntities:delete(entityIndex) + unit:RemoveSelf() + deletedCount = deletedCount + 1 + print(((("[AdminMenu] Удалено существо без playerId: " .. unit:GetUnitName()) .. " (EntityIndex: ") .. tostring(entityIndex)) .. ")") + end + else + skippedCount = skippedCount + 1 + print((("[AdminMenu] Пропущено существо с playerId " .. tostring(unitPlayerId)) .. ": ") .. unit:GetUnitName()) + end + end + ::__continue250:: + end + print(((((("[AdminMenu] Удаление группы завершено: удалено " .. tostring(deletedCount)) .. ", пропущено ") .. tostring(skippedCount)) .. " из ") .. tostring(#entityIndices)) .. " существ") +end +function AdminMenuManager.prototype.handleChangeHero(self, playerId, event) + if not IsServer() then + return + end + if not self:checkAdminAccess(playerId) then + return + end + local heroName = event.heroName or "npc_dota_hero_crystal_maiden" + local team = event.team or DOTA_TEAM_GOODGUYS + local player = PlayerResource:GetPlayer(playerId) + if not player then + print(("[AdminMenu] Игрок " .. tostring(playerId)) .. " не найден") + return + end + local currentHero = player:GetAssignedHero() or PlayerResource:GetSelectedHeroEntity(playerId) + if not currentHero or not IsValidEntity(currentHero) then + print(("[AdminMenu] У игрока " .. tostring(playerId)) .. " нет героя для замены") + return + end + local spawnPos = currentHero:GetAbsOrigin() + local currentTeam = currentHero:GetTeam() + local finalTeam = team or currentTeam + local entityIndex = currentHero:GetEntityIndex() + self.selectedEntities:delete(entityIndex) + print((((("[AdminMenu] Начинаем замену героя для игрока " .. tostring(playerId)) .. ": ") .. currentHero:GetUnitName()) .. " -> ") .. heroName) + PrecacheUnitByNameAsync( + heroName, + function() + local addon = GameRules.Addon + if addon ~= nil then + addon:resetWorldLightFixSpawnForPlayer(playerId) + end + local newHero = CreateUnitByName( + heroName, + spawnPos, + false, + player, + player, + finalTeam + ) + if not newHero or not IsValidEntity(newHero) then + print("[AdminMenu] Ошибка: не удалось создать нового героя " .. heroName) + return + end + print((("[AdminMenu] Новый герой " .. heroName) .. " создан, EntityIndex: ") .. tostring(newHero:GetEntityIndex())) + FindClearSpaceForUnit(newHero, spawnPos, true) + newHero:SetControllableByPlayer(playerId, true) + newHero:SetOwner(player) + if newHero:IsRealHero() then + while newHero:GetLevel() > 1 do + newHero:HeroLevelUp(false) + end + newHero:SetAbilityPoints(1) + end + player:SetAssignedHeroEntity(newHero) + self.selectedEntities:set( + newHero:GetEntityIndex(), + newHero + ) + print((("[AdminMenu] Новый герой " .. heroName) .. " назначен игроку ") .. tostring(playerId)) + Timers:CreateTimer( + 0.1, + function() + if currentHero and IsValidEntity(currentHero) then + currentHero:RemoveSelf() + print(("[AdminMenu] Старый герой " .. currentHero:GetUnitName()) .. " удален") + end + end + ) + print((("[AdminMenu] Герой игрока " .. tostring(playerId)) .. " успешно заменен на ") .. heroName) + end + ) +end +function AdminMenuManager.prototype.handleToggleSetting(self, playerId, event) + if not IsServer() then + return + end + if not self:checkAdminAccess(playerId) then + return + end + local setting = event.setting or "" + local value = event.value or false + repeat + local ____switch276 = setting + do + print("[AdminMenu] Неизвестная настройка: " .. setting) + break + end + until true +end +function AdminMenuManager.prototype.handleRefreshSpells(self, playerId, event) + if not IsServer() then + return + end + if not self:checkAdminAccess(playerId) then + return + end + local entityIndices = {} + if event.entityIndices then + if __TS__ArrayIsArray(event.entityIndices) then + entityIndices = event.entityIndices + else + entityIndices = __TS__ObjectValues(event.entityIndices) + end + end + if #entityIndices == 0 then + local player = PlayerResource:GetPlayer(playerId) + if player then + local currentHero = player:GetAssignedHero() or PlayerResource:GetSelectedHeroEntity(playerId) + if currentHero and IsValidEntity(currentHero) then + entityIndices = {currentHero:GetEntityIndex()} + end + end + end + for ____, entityIndex in ipairs(entityIndices) do + local unit = self.selectedEntities:get(entityIndex) + if not unit or not IsValidEntity(unit) then + unit = EntIndexToHScript(entityIndex) + end + if unit and IsValidEntity(unit) then + do + local i = 0 + while i < unit:GetAbilityCount() do + local ability = unit:GetAbilityByIndex(i) + if ability and IsValidEntity(ability) then + ability:EndCooldown() + end + i = i + 1 + end + end + if unit:IsRealHero() then + local hero = unit + hero:SetMana(hero:GetMaxMana()) + end + print("[AdminMenu] Заклинания обновлены и мана восстановлена для " .. unit:GetUnitName()) + end + end +end +function AdminMenuManager.prototype.handleRefreshHealth(self, playerId, event) + if not IsServer() then + return + end + if not self:checkAdminAccess(playerId) then + return + end + local entityIndices = {} + if event.entityIndices then + if __TS__ArrayIsArray(event.entityIndices) then + entityIndices = event.entityIndices + else + entityIndices = __TS__ObjectValues(event.entityIndices) + end + end + if #entityIndices == 0 then + local player = PlayerResource:GetPlayer(playerId) + if player then + local currentHero = player:GetAssignedHero() or PlayerResource:GetSelectedHeroEntity(playerId) + if currentHero and IsValidEntity(currentHero) then + entityIndices = {currentHero:GetEntityIndex()} + end + end + end + for ____, entityIndex in ipairs(entityIndices) do + local unit = self.selectedEntities:get(entityIndex) + if not unit or not IsValidEntity(unit) then + unit = EntIndexToHScript(entityIndex) + end + if unit and IsValidEntity(unit) then + unit:SetHealth(unit:GetMaxHealth()) + print("[AdminMenu] Здоровье восстановлено для " .. unit:GetUnitName()) + end + end +end +function AdminMenuManager.prototype.handlePauseGame(self, playerId) + if not IsServer() then + return + end + if not self:checkAdminAccess(playerId) then + return + end + local isPaused = GameRules:IsGamePaused() + if isPaused then + SendToServerConsole("dota_pause") + print("[AdminMenu] Игра возобновлена") + else + SendToServerConsole("dota_pause") + print("[AdminMenu] Игра приостановлена") + end +end +function AdminMenuManager.prototype.handleExitGame(self, playerId) + if not IsServer() then + return + end + if not self:checkAdminAccess(playerId) then + return + end + print("[AdminMenu] Выход из игры") +end +function AdminMenuManager.prototype.handleScriptReload(self, playerId) + if not IsServer() then + return + end + if not self:checkAdminAccess(playerId) then + print("[AdminMenu] Отказано в доступе к script_reload") + return + end + SendToServerConsole("script_reload") + print("[AdminMenu] script_reload выполнен по запросу игрока " .. tostring(playerId)) +end +function AdminMenuManager.prototype.handleSpawnZoneCreate(self, playerId, event) + if not IsServer() then + return + end + if not self:checkAdminAccess(playerId) then + print("[AdminMenu] Отказано в доступе к созданию зоны спавна") + return + end + local hero = PlayerResource:GetSelectedHeroEntity(playerId) + if not hero or not IsValidEntity(hero) then + print("[AdminMenu] Не найден валидный герой для создания зоны спавна") + return + end + local center = hero:GetAbsOrigin() + local shape = event.shape == "square" and "square" or "circle" + local radius = event.radius and event.radius > 0 and event.radius or 600 + local width = event.width and event.width > 0 and event.width or radius + local height = event.height and event.height > 0 and event.height or radius + local units = {} + if event.units then + local rawUnits = __TS__ArrayIsArray(event.units) and event.units or __TS__ObjectValues(event.units) + for ____, u in ipairs(rawUnits) do + if u and u.unitName ~= nil then + local count = u.count and u.count > 0 and u.count or 1 + units[#units + 1] = {unitName = u.unitName, count = count} + end + end + end + if #units == 0 then + units[#units + 1] = {unitName = "npc_pig", count = 1} + end + local behaviorRaw = event.behavior + local behavior = "aggressive" + if behaviorRaw == "friendly" or behaviorRaw == "aggressive" or behaviorRaw == "neutral" then + behavior = behaviorRaw + end + local spawnAtPointRaw = event.spawnAtPointHeight + local spawnAtPointHeight = spawnAtPointRaw == true or spawnAtPointRaw == 1 + local config = { + units = units, + position = center, + radius = radius, + interval = 10, + team = DOTA_TEAM_NEUTRALS, + behavior = behavior, + shape = shape + } + if spawnAtPointHeight then + config.spawnAtPointHeight = true + end + if shape == "square" then + config.width = width + config.height = height + end + local ____self_7, ____spawnZoneCounter_8 = self, "spawnZoneCounter" + local ____self_spawnZoneCounter_9 = ____self_7[____spawnZoneCounter_8] + ____self_7[____spawnZoneCounter_8] = ____self_spawnZoneCounter_9 + 1 + local zoneId = "zone_admin_" .. tostring(____self_spawnZoneCounter_9) + SpawnManager:getInstance():CreateZoneFromAdmin(zoneId, config) + print((((((((((("[AdminMenu] Создана зона спавна '" .. zoneId) .. "' shape=") .. shape) .. ", radius=") .. tostring(radius)) .. ", width=") .. tostring(width)) .. ", height=") .. tostring(height)) .. ", units=") .. table.concat( + __TS__ArrayMap( + units, + function(____, u) return (u.unitName .. "x") .. tostring(u.count) end + ), + ", " + )) +end +function AdminMenuManager.prototype.handleSpawnZoneDeleteHere(self, playerId) + if not IsServer() then + return + end + if not self:checkAdminAccess(playerId) then + print("[AdminMenu] Отказано в доступе к удалению зоны спавна") + return + end + local hero = PlayerResource:GetSelectedHeroEntity(playerId) + if not hero or not IsValidEntity(hero) then + print("[AdminMenu] Не найден валидный герой для удаления зоны спавна") + return + end + local pos = hero:GetAbsOrigin() + local removedId = SpawnManager:getInstance():RemoveZoneAtPosition(pos) + if removedId then + print((("[AdminMenu] Зона спавна '" .. removedId) .. "' удалена под героем игрока ") .. tostring(playerId)) + else + print("[AdminMenu] Зона спавна под героем не найдена") + end +end +function AdminMenuManager.prototype.getAllHeroes(self) + local heroes = {} + do + local i = 0 + while i < DOTA_MAX_TEAM_PLAYERS do + if PlayerResource:IsValidPlayerID(i) then + local hero = PlayerResource:GetSelectedHeroEntity(i) + if hero and IsValidEntity(hero) then + heroes[#heroes + 1] = hero + end + end + i = i + 1 + end + end + return heroes +end +function AdminMenuManager.prototype.handleGiveGold(self, playerId, event) + if not IsServer() then + return + end + if not self:checkAdminAccess(playerId) then + return + end + local amount = event.amount or 1000 + local goldAmount = math.min( + 99999, + math.max( + 0, + math.floor(amount) + ) + ) + local entityIndices = {} + if event.entityIndices then + if __TS__ArrayIsArray(event.entityIndices) then + entityIndices = event.entityIndices + else + entityIndices = __TS__ObjectValues(event.entityIndices) + end + end + if #entityIndices == 0 then + local player = PlayerResource:GetPlayer(playerId) + if player then + local currentHero = player:GetAssignedHero() or PlayerResource:GetSelectedHeroEntity(playerId) + if currentHero and IsValidEntity(currentHero) then + entityIndices = {currentHero:GetEntityIndex()} + end + end + end + for ____, entityIndex in ipairs(entityIndices) do + local unit = self.selectedEntities:get(entityIndex) + if not unit or not IsValidEntity(unit) then + unit = EntIndexToHScript(entityIndex) + end + if unit and IsValidEntity(unit) and unit:IsRealHero() then + local hero = unit + local ownerPlayerId = hero:GetPlayerOwnerID() + if ownerPlayerId >= 0 and PlayerResource:IsValidPlayerID(ownerPlayerId) then + PlayerResource:ModifyGold(ownerPlayerId, goldAmount, false, DOTA_ModifyGold_Unspecified) + print(((((("[AdminMenu] Выдано " .. tostring(goldAmount)) .. " золота игроку ") .. tostring(ownerPlayerId)) .. " (") .. hero:GetUnitName()) .. ")") + end + end + end +end +function AdminMenuManager.prototype.handleChangeLuck(self, playerId, event) + if not IsServer() then + return + end + if not self:checkAdminAccess(playerId) then + return + end + local amount = event.amount or 0 + local entityIndices = {} + if event.entityIndices then + if __TS__ArrayIsArray(event.entityIndices) then + entityIndices = event.entityIndices + else + entityIndices = __TS__ObjectValues(event.entityIndices) + end + end + if #entityIndices == 0 then + local player = PlayerResource:GetPlayer(playerId) + if player then + local currentHero = player:GetAssignedHero() or PlayerResource:GetSelectedHeroEntity(playerId) + if currentHero and IsValidEntity(currentHero) then + entityIndices = {currentHero:GetEntityIndex()} + end + end + end + for ____, entityIndex in ipairs(entityIndices) do + local unit = self.selectedEntities:get(entityIndex) + if not unit or not IsValidEntity(unit) then + unit = EntIndexToHScript(entityIndex) + end + if unit and IsValidEntity(unit) and unit:IsRealHero() then + local hero = unit + addLuck(nil, hero, amount) + print((("[AdminMenu] Добавлено " .. tostring(amount)) .. " удачи герою ") .. hero:GetUnitName()) + end + end +end +function AdminMenuManager.prototype.handleChangePhysicalVampirism(self, playerId, event) + if not IsServer() then + return + end + if not self:checkAdminAccess(playerId) then + return + end + local amount = event.amount or 0 + local entityIndices = {} + if event.entityIndices then + if __TS__ArrayIsArray(event.entityIndices) then + entityIndices = event.entityIndices + else + entityIndices = __TS__ObjectValues(event.entityIndices) + end + end + if #entityIndices == 0 then + local player = PlayerResource:GetPlayer(playerId) + if player then + local currentHero = player:GetAssignedHero() or PlayerResource:GetSelectedHeroEntity(playerId) + if currentHero and IsValidEntity(currentHero) then + entityIndices = {currentHero:GetEntityIndex()} + end + end + end + for ____, entityIndex in ipairs(entityIndices) do + local unit = self.selectedEntities:get(entityIndex) + if not unit or not IsValidEntity(unit) then + unit = EntIndexToHScript(entityIndex) + end + if unit and IsValidEntity(unit) and unit:IsRealHero() then + local hero = unit + addPhysicalVampirism(nil, hero, amount) + print((("[AdminMenu] Добавлено " .. tostring(amount)) .. " физического вампиризма герою ") .. hero:GetUnitName()) + end + end +end +function AdminMenuManager.prototype.handleChangeMagicalVampirism(self, playerId, event) + if not IsServer() then + return + end + if not self:checkAdminAccess(playerId) then + return + end + local amount = event.amount or 0 + local entityIndices = {} + if event.entityIndices then + if __TS__ArrayIsArray(event.entityIndices) then + entityIndices = event.entityIndices + else + entityIndices = __TS__ObjectValues(event.entityIndices) + end + end + if #entityIndices == 0 then + local player = PlayerResource:GetPlayer(playerId) + if player then + local currentHero = player:GetAssignedHero() or PlayerResource:GetSelectedHeroEntity(playerId) + if currentHero and IsValidEntity(currentHero) then + entityIndices = {currentHero:GetEntityIndex()} + end + end + end + for ____, entityIndex in ipairs(entityIndices) do + local unit = self.selectedEntities:get(entityIndex) + if not unit or not IsValidEntity(unit) then + unit = EntIndexToHScript(entityIndex) + end + if unit and IsValidEntity(unit) and unit:IsRealHero() then + local hero = unit + addMagicalVampirism(nil, hero, amount) + print((("[AdminMenu] Добавлено " .. tostring(amount)) .. " магического вампиризма герою ") .. hero:GetUnitName()) + end + end +end +function AdminMenuManager.prototype.handleGiveCrystals(self, playerId, event) + if not IsServer() then + return + end + if not self:checkAdminAccess(playerId) then + return + end + local amount = event.amount or 1000 + local crystalAmount = math.min( + 999999, + math.max( + 0, + math.floor(amount) + ) + ) + local entityIndices = {} + if event.entityIndices then + if __TS__ArrayIsArray(event.entityIndices) then + entityIndices = event.entityIndices + else + entityIndices = __TS__ObjectValues(event.entityIndices) + end + end + if #entityIndices == 0 then + local player = PlayerResource:GetPlayer(playerId) + if player then + local currentHero = player:GetAssignedHero() or PlayerResource:GetSelectedHeroEntity(playerId) + if currentHero and IsValidEntity(currentHero) then + entityIndices = {currentHero:GetEntityIndex()} + end + end + end + for ____, entityIndex in ipairs(entityIndices) do + local unit = self.selectedEntities:get(entityIndex) + if not unit or not IsValidEntity(unit) then + unit = EntIndexToHScript(entityIndex) + end + if unit and IsValidEntity(unit) and unit:IsRealHero() then + local hero = unit + local ownerPlayerId = hero:GetPlayerOwnerID() + if ownerPlayerId >= 0 and PlayerResource:IsValidPlayerID(ownerPlayerId) then + local crystalCurrency = CrystalCurrency:getInstance() + crystalCurrency:addCrystals(ownerPlayerId, crystalAmount) + print(((((("[AdminMenu] Выдано " .. tostring(crystalAmount)) .. " кристаллов игроку ") .. tostring(ownerPlayerId)) .. " (") .. hero:GetUnitName()) .. ")") + end + end + end +end +function AdminMenuManager.prototype.handleSetNextWave(self, playerId, event) + if not IsServer() then + return + end + if not self:checkAdminAccess(playerId) then + print("[AdminMenu] Отказано в доступе к управлению волнами") + return + end + local waveManager = WaveManager:getInstance() + local dayNightManager = DayNightCycleManager:getInstance() + local rawNight = event.night + local rawWave = event.wave + if rawNight == nil and rawWave == nil then + print("[AdminMenu] admin_set_next_wave: не переданы night / wave") + return + end + if rawWave ~= nil then + local targetWave = math.max( + 1, + math.floor(rawWave) + ) + waveManager:SetNextWaveIndex(targetWave) + end + local currentNight = waveManager:GetCurrentNight() + local currentWave = waveManager:GetCurrentWave() + 1 + if rawNight ~= nil then + local targetNight = math.max( + 1, + math.floor(rawNight) + ) + print(((("[AdminMenu] Запрошена ночь " .. tostring(targetNight)) .. " через админ-меню (фактическая текущая ночь сейчас ") .. tostring(currentNight)) .. ", управление ночью оставлено циклу дня/ночи)") + end + dayNightManager:ForceNightIn(3) + print(((("[AdminMenu] Установлена следующая волна через админ-меню: текущая ночь=" .. tostring(currentNight)) .. ", следующая волна=") .. tostring(currentWave)) .. ", ночь начнётся через 3 секунды (если сейчас день)") +end +function AdminMenuManager.prototype.handleForceMorning(self, playerId) + if not IsServer() then + return + end + if not self:checkAdminAccess(playerId) then + print("[AdminMenu] Отказано в доступе к 'сделать утро'") + return + end + local dayNightManager = DayNightCycleManager:getInstance() + dayNightManager:ForceDay() + print("[AdminMenu] Утро принудительно установлено через админ-меню") +end +function AdminMenuManager.prototype.handleAdjustDayNightTimer(self, playerId, event) + if not IsServer() then + return + end + if not self:checkAdminAccess(playerId) then + print("[AdminMenu] Отказано в доступе к изменению таймера дня/ночи") + return + end + local rawDelta = event.delta + if rawDelta == nil or rawDelta == 0 then + return + end + local delta = math.floor(rawDelta) + local dayNightManager = DayNightCycleManager:getInstance() + dayNightManager:AdjustTimeLeft(delta) + print(((((("[AdminMenu] Таймер дня/ночи изменён на " .. tostring(delta)) .. " сек, новое значение: ") .. tostring(dayNightManager:GetTimeLeft())) .. " (isDay=") .. tostring(dayNightManager:IsDaytime())) .. ")") +end +function AdminMenuManager.prototype.handleSetDayNightTimer(self, playerId, event) + if not IsServer() then + return + end + if not self:checkAdminAccess(playerId) then + print("[AdminMenu] Отказано в доступе к установке таймера дня/ночи") + return + end + local rawSeconds = event.seconds + if rawSeconds == nil then + return + end + local seconds = math.floor(rawSeconds) + local dayNightManager = DayNightCycleManager:getInstance() + dayNightManager:SetTimeLeft(seconds) + print(((((("[AdminMenu] Таймер дня/ночи установлен в " .. tostring(seconds)) .. " сек, текущее значение: ") .. tostring(dayNightManager:GetTimeLeft())) .. " (isDay=") .. tostring(dayNightManager:IsDaytime())) .. ")") +end +function AdminMenuManager.prototype.handleRequestSteamId(self, playerId) + if not IsServer() then + return + end + local player = PlayerResource:GetPlayer(playerId) + if not player then + print("[AdminMenu] Не удалось получить контроллер игрока " .. tostring(playerId)) + return + end + local steamId = tostring(PlayerResource:GetSteamAccountID(playerId)) + CustomGameEventManager:Send_ServerToPlayer(player, "admin_menu_steam_id", {steamId = steamId}) + print((("[AdminMenu] Отправлен Steam ID " .. steamId) .. " игроку ") .. tostring(playerId)) +end +function AdminMenuManager.prototype.handleCheckCheatMode(self, playerId) + if not IsServer() then + return + end + local player = PlayerResource:GetPlayer(playerId) + if not player then + return + end + local isCheatMode = GameRules:IsCheatMode() or Convars:GetBool("sv_cheats") + CustomGameEventManager:Send_ServerToPlayer(player, "admin_menu_cheat_mode", {isCheatMode = isCheatMode}) +end +function AdminMenuManager.prototype.handleShowCardSelection(self, playerId, event) + if not IsServer() then + return + end + if not self:checkAdminAccess(playerId) then + print("[AdminMenu] Отказано в доступе для показа выбора карт") + return + end + local targetPlayerId = playerId + if event.entityIndices then + local entityArray = {} + if __TS__ArrayIsArray(event.entityIndices) then + entityArray = event.entityIndices + else + entityArray = __TS__ObjectValues(event.entityIndices) + end + if #entityArray > 0 then + local firstEntityIndex = entityArray[1] + local firstEntity = EntIndexToHScript(firstEntityIndex) + if firstEntity and IsValidEntity(firstEntity) and firstEntity:IsRealHero() then + targetPlayerId = firstEntity:GetPlayerOwnerID() + end + end + else + local hero = PlayerResource:GetSelectedHeroEntity(playerId) + if hero and IsValidEntity(hero) and hero:IsRealHero() then + targetPlayerId = hero:GetPlayerOwnerID() + end + end + ShowCardSelectionToPlayer(nil, targetPlayerId, 3, "admin_menu") + print("[AdminMenu] Показан выбор карт для игрока " .. tostring(targetPlayerId)) +end +function AdminMenuManager.prototype.handleSetTimeScale(self, playerId, event) + if not IsServer() then + return + end + if not self:checkAdminAccess(playerId) then + return + end + local timescale = event.timescale or 1 + local clampedTimescale = math.min( + 10, + math.max(0, timescale) + ) + Convars:SetFloat("host_timescale", clampedTimescale) + print("[AdminMenu] Скорость времени установлена: " .. tostring(clampedTimescale)) +end +AdminMenuManager.instance = nil +AdminMenuManager = __TS__Decorate(AdminMenuManager, AdminMenuManager, {reloadable}, {kind = "class", name = "AdminMenuManager"}) +____exports.AdminMenuManager = AdminMenuManager +return ____exports diff --git a/scripts/vscripts/ai/ai_demon_dragon_satyr.lua b/scripts/vscripts/ai/ai_demon_dragon_satyr.lua new file mode 100644 index 0000000..fe0d1b1 --- /dev/null +++ b/scripts/vscripts/ai/ai_demon_dragon_satyr.lua @@ -0,0 +1,309 @@ +--[[ Generated with https://github.com/TypeScriptToLua/TypeScriptToLua ]] +local ____exports = {} +local ____dota_ts_adapter = require("lib.dota_ts_adapter") +local registerEntityFunction = ____dota_ts_adapter.registerEntityFunction +local THINK_INTERVAL = 0.25 +local TARGET_SEARCH_RADIUS = 2600 +local FEAR_CAST_RADIUS = 900 +local GRAB_CAST_RANGE = 170 +local FACE_CAST_RANGE = 420 +local BACK_CAST_RANGE = 720 +local abilityGrab +local abilityFear +local abilityBackStun +local abilityFaceStun +local lastOrderGameTime = -999 +local lastAttackOrderGameTime = -999 +local lastAttackTargetIndex = -1 +local function refreshAbilities(self) + if not thisEntity or thisEntity:IsNull() then + return + end + abilityGrab = thisEntity:FindAbilityByName("ability_grab") or nil + abilityFear = thisEntity:FindAbilityByName("fear") or nil + abilityBackStun = thisEntity:FindAbilityByName("back_stun") or nil + abilityFaceStun = thisEntity:FindAbilityByName("face_stun") or nil +end +local function findClosestEnemy(self) + local enemies = FindUnitsInRadius( + thisEntity:GetTeamNumber(), + thisEntity:GetAbsOrigin(), + nil, + TARGET_SEARCH_RADIUS, + DOTA_UNIT_TARGET_TEAM_ENEMY, + bit.bor(DOTA_UNIT_TARGET_HERO, DOTA_UNIT_TARGET_BASIC), + DOTA_UNIT_TARGET_FLAG_FOW_VISIBLE, + FIND_CLOSEST, + false + ) + for ____, unit in ipairs(enemies) do + do + if not unit or unit:IsNull() or not unit:IsAlive() then + goto __continue5 + end + return unit + end + ::__continue5:: + end + return nil +end +local function getDistance2D(self, from, to) + return (to - from):Length2D() +end +local function isTargetBehind(self, target) + local toTarget = (target:GetAbsOrigin() - thisEntity:GetAbsOrigin()):Normalized() + local forward = thisEntity:GetForwardVector() + return forward:Dot(toTarget) < -0.25 +end +local function isTargetInFront(self, target) + local toTarget = (target:GetAbsOrigin() - thisEntity:GetAbsOrigin()):Normalized() + local forward = thisEntity:GetForwardVector() + return forward:Dot(toTarget) > 0.25 +end +local function isPointInFront(self, point) + local toPoint = (point - thisEntity:GetAbsOrigin()):Normalized() + local forward = thisEntity:GetForwardVector() + return forward:Dot(toPoint) > 0.25 +end +local function isPointBehind(self, point) + local toPoint = (point - thisEntity:GetAbsOrigin()):Normalized() + local forward = thisEntity:GetForwardVector() + return forward:Dot(toPoint) < -0.25 +end +local function issueCastPosition(self, ability, position) + local now = GameRules:GetGameTime() + if now - lastOrderGameTime < 0.15 then + return false + end + ExecuteOrderFromTable({ + UnitIndex = thisEntity:entindex(), + OrderType = DOTA_UNIT_ORDER_CAST_POSITION, + AbilityIndex = ability:entindex(), + Position = position, + Queue = false + }) + lastOrderGameTime = now + return true +end +local function issueCastNoTarget(self, ability) + local now = GameRules:GetGameTime() + if now - lastOrderGameTime < 0.15 then + return false + end + ExecuteOrderFromTable({ + UnitIndex = thisEntity:entindex(), + OrderType = DOTA_UNIT_ORDER_CAST_NO_TARGET, + AbilityIndex = ability:entindex(), + Queue = false + }) + lastOrderGameTime = now + return true +end +local function issueMove(self, target) + local now = GameRules:GetGameTime() + if now - lastOrderGameTime < 0.25 then + return + end + if not target or target:IsNull() or not target:IsAlive() then + return + end + local targetPosition = target:GetAbsOrigin() + ExecuteOrderFromTable({ + UnitIndex = thisEntity:entindex(), + OrderType = DOTA_UNIT_ORDER_MOVE_TO_POSITION, + Position = targetPosition, + Queue = false + }) + lastOrderGameTime = now +end +local function issueAttackTarget(self, target) + local now = GameRules:GetGameTime() + if now - lastOrderGameTime < 0.25 then + return + end + if not target or target:IsNull() or not target:IsAlive() then + return + end + local targetIndex = target:entindex() + if targetIndex == nil or targetIndex < 0 then + return + end + if lastAttackTargetIndex == targetIndex and now - lastAttackOrderGameTime < 1 then + return + end + ExecuteOrderFromTable({ + UnitIndex = thisEntity:entindex(), + OrderType = DOTA_UNIT_ORDER_ATTACK_TARGET, + TargetIndex = targetIndex, + Queue = false + }) + lastOrderGameTime = now + lastAttackOrderGameTime = now + lastAttackTargetIndex = targetIndex +end +local function areAllAbilitiesOnCooldown(self) + if not abilityGrab or not abilityFear or not abilityBackStun or not abilityFaceStun then + return false + end + return abilityGrab:GetCooldownTimeRemaining() > 0 and abilityFear:GetCooldownTimeRemaining() > 0 and abilityBackStun:GetCooldownTimeRemaining() > 0 and abilityFaceStun:GetCooldownTimeRemaining() > 0 +end +local function findStunnedEnemyPoint(self) + local enemies = FindUnitsInRadius( + thisEntity:GetTeamNumber(), + thisEntity:GetAbsOrigin(), + nil, + BACK_CAST_RANGE, + DOTA_UNIT_TARGET_TEAM_ENEMY, + bit.bor(DOTA_UNIT_TARGET_HERO, DOTA_UNIT_TARGET_BASIC), + DOTA_UNIT_TARGET_FLAG_FOW_VISIBLE, + FIND_CLOSEST, + false + ) + for ____, enemy in ipairs(enemies) do + do + if not enemy or enemy:IsNull() or not enemy:IsAlive() then + goto __continue28 + end + if enemy:HasModifier("modifier_face_stunned") or enemy:HasModifier("modifier_back_stunned") then + return enemy:GetAbsOrigin() + end + end + ::__continue28:: + end + return nil +end +local function tryChainStunAtPoint(self, point) + local distance = getDistance2D( + nil, + thisEntity:GetAbsOrigin(), + point + ) + if abilityFaceStun and not abilityFaceStun:IsNull() and abilityFaceStun:IsFullyCastable() and distance <= FACE_CAST_RANGE then + thisEntity:FaceTowards(point) + return issueCastPosition(nil, abilityFaceStun, point) + end + if abilityBackStun and not abilityBackStun:IsNull() and abilityBackStun:IsFullyCastable() and distance <= BACK_CAST_RANGE then + local casterOrigin = thisEntity:GetAbsOrigin() + local faceAwayPoint = casterOrigin:__mul(2):__sub(point) + thisEntity:FaceTowards(faceAwayPoint) + return issueCastNoTarget(nil, abilityBackStun) + end + return false +end +local function tryCastFear(self) + if not abilityFear or abilityFear:IsNull() or not abilityFear:IsFullyCastable() then + return false + end + local enemies = FindUnitsInRadius( + thisEntity:GetTeamNumber(), + thisEntity:GetAbsOrigin(), + nil, + FEAR_CAST_RADIUS, + DOTA_UNIT_TARGET_TEAM_ENEMY, + bit.bor(DOTA_UNIT_TARGET_HERO, DOTA_UNIT_TARGET_BASIC), + DOTA_UNIT_TARGET_FLAG_NONE, + FIND_ANY_ORDER, + false + ) + if #enemies < 2 then + return false + end + return issueCastNoTarget(nil, abilityFear) +end +local function tryCastGrab(self, target, distance) + if not abilityGrab or abilityGrab:IsNull() or not abilityGrab:IsFullyCastable() then + return false + end + if distance > GRAB_CAST_RANGE then + return false + end + return issueCastPosition( + nil, + abilityGrab, + target:GetAbsOrigin() + ) +end +local function tryCastFaceStun(self, target, distance) + if not abilityFaceStun or abilityFaceStun:IsNull() or not abilityFaceStun:IsFullyCastable() then + return false + end + if distance > FACE_CAST_RANGE or not isTargetInFront(nil, target) then + return false + end + return issueCastPosition( + nil, + abilityFaceStun, + target:GetAbsOrigin() + ) +end +local function tryCastBackStun(self, target, distance) + if not abilityBackStun or abilityBackStun:IsNull() or not abilityBackStun:IsFullyCastable() then + return false + end + if distance > BACK_CAST_RANGE or not isTargetBehind(nil, target) then + return false + end + return issueCastNoTarget(nil, abilityBackStun) +end +local function DemonDragonSatyrThink(self) + if not IsServer() or not thisEntity or thisEntity:IsNull() or not thisEntity:IsAlive() then + return THINK_INTERVAL + end + if GameRules:IsGamePaused() or thisEntity:IsChanneling() then + return THINK_INTERVAL + end + if not abilityGrab or not abilityFear or not abilityBackStun or not abilityFaceStun then + refreshAbilities(nil) + end + local target = findClosestEnemy(nil) + if not target then + return THINK_INTERVAL + end + local distance = getDistance2D( + nil, + thisEntity:GetAbsOrigin(), + target:GetAbsOrigin() + ) + local stunnedEnemyPoint = findStunnedEnemyPoint(nil) + if stunnedEnemyPoint then + if tryChainStunAtPoint(nil, stunnedEnemyPoint) then + return THINK_INTERVAL + end + if isPointInFront(nil, stunnedEnemyPoint) and tryCastFaceStun(nil, target, distance) then + return THINK_INTERVAL + end + if isPointBehind(nil, stunnedEnemyPoint) and tryCastBackStun(nil, target, distance) then + return THINK_INTERVAL + end + end + if tryCastFear(nil) then + return THINK_INTERVAL + end + if tryCastGrab(nil, target, distance) then + return THINK_INTERVAL + end + if tryCastFaceStun(nil, target, distance) then + return THINK_INTERVAL + end + if tryCastBackStun(nil, target, distance) then + return THINK_INTERVAL + end + if areAllAbilitiesOnCooldown(nil) then + issueAttackTarget(nil, target) + return THINK_INTERVAL + end + issueMove(nil, target) + return THINK_INTERVAL +end +registerEntityFunction( + nil, + "Spawn", + function() + if not IsServer() or not thisEntity or thisEntity:IsNull() then + return + end + refreshAbilities(nil) + thisEntity:SetContextThink("DemonDragonSatyrThink", DemonDragonSatyrThink, THINK_INTERVAL) + end +) +return ____exports diff --git a/scripts/vscripts/ai/ai_snowman.lua b/scripts/vscripts/ai/ai_snowman.lua new file mode 100644 index 0000000..a146419 --- /dev/null +++ b/scripts/vscripts/ai/ai_snowman.lua @@ -0,0 +1,181 @@ +--[[ Generated with https://github.com/TypeScriptToLua/TypeScriptToLua ]] +local ____exports = {} +local ____dota_ts_adapter = require("lib.dota_ts_adapter") +local registerEntityFunction = ____dota_ts_adapter.registerEntityFunction +--- Как в npc_classic_snowman Ability1 / KV abilities. +local SNOWBALL_ABILITY_NAME = "ability_yuki_snowball" +--- Широкий поиск союзных героев вокруг снеговика. +local SEARCH_RADIUS = 1100 +--- Поиск героя для преследования, если в радиусе каста снежка никого нет. +local CHASE_SCAN_RADIUS = 12000 +--- Частые тики — пока цель подходит под HP и кулдаун 0, касты идут подряд. +local THINK_INTERVAL = 0.05 +--- Не спамить приказ движения при преследовании. +local CHASE_MOVE_INTERVAL = 0.35 +--- Кидать снежок только если у героя строго меньше этого % HP. +local SNOWBALL_ONLY_IF_TARGET_HP_PCT_BELOW = 98 +local snowballAbility +local lastChaseMoveGameTime = -999 +local function resolveSnowballAbility(self) + if not thisEntity or thisEntity:IsNull() then + return nil + end + return thisEntity:FindAbilityByName(SNOWBALL_ABILITY_NAME) or thisEntity:GetAbilityByIndex(0) or nil +end +local function tryCastSnowballOnAlly(self, target) + if not snowballAbility or snowballAbility:IsNull() or not thisEntity or thisEntity:IsNull() then + return + end + local pid = thisEntity:GetPlayerOwnerID() + if pid >= 0 then + thisEntity:CastAbilityOnTarget(target, snowballAbility, pid) + return + end + ExecuteOrderFromTable({ + UnitIndex = thisEntity:entindex(), + OrderType = DOTA_UNIT_ORDER_CAST_TARGET, + TargetIndex = target:entindex(), + AbilityIndex = snowballAbility:entindex(), + Queue = false + }) +end +--- Союзные герои в радиусе; без других снеговиков (тот же unit name). +local function getFriendlyHeroesInRadius(self, radius) + if not thisEntity or thisEntity:IsNull() then + return {} + end + local units = FindUnitsInRadius( + thisEntity:GetTeamNumber(), + thisEntity:GetAbsOrigin(), + nil, + radius, + DOTA_UNIT_TARGET_TEAM_FRIENDLY, + DOTA_UNIT_TARGET_HERO, + bit.bor(DOTA_UNIT_TARGET_FLAG_MAGIC_IMMUNE_ENEMIES, DOTA_UNIT_TARGET_FLAG_FOW_VISIBLE), + FIND_CLOSEST, + false + ) + local selfName = thisEntity:GetUnitName() + local out = {} + for ____, u in ipairs(units) do + do + if not u or u:IsNull() or not u:IsAlive() then + goto __continue9 + end + if u:GetUnitName() == selfName then + goto __continue9 + end + out[#out + 1] = u + end + ::__continue9:: + end + return out +end +--- Ближайший союзный герой в большом радиусе (порядок FIND_CLOSEST). +local function findClosestFriendlyHero(self, maxRadius) + local list = getFriendlyHeroesInRadius(nil, maxRadius) + return #list > 0 and list[1] or nil +end +local function tryChaseNearestHero(self) + if not thisEntity or thisEntity:IsNull() then + return + end + local hero = findClosestFriendlyHero(nil, CHASE_SCAN_RADIUS) + if not hero then + return + end + local now = GameRules:GetGameTime() + if now - lastChaseMoveGameTime < CHASE_MOVE_INTERVAL then + return + end + local dest = GetGroundPosition( + hero:GetAbsOrigin(), + hero + ) + thisEntity:MoveToPosition(dest) + lastChaseMoveGameTime = now +end +local function snowmanThink(self) + if not IsServer() or not thisEntity or thisEntity:IsNull() or not thisEntity:IsAlive() then + return THINK_INTERVAL + end + if GameRules:IsGamePaused() or GameRules:State_Get() == DOTA_GAMERULES_STATE_POST_GAME then + return THINK_INTERVAL + end + if not snowballAbility or snowballAbility:IsNull() then + snowballAbility = resolveSnowballAbility(nil) + if not snowballAbility then + return THINK_INTERVAL + end + end + local closeHeroes = getFriendlyHeroesInRadius(nil, SEARCH_RADIUS) + local selfName = thisEntity:GetUnitName() + local origin = thisEntity:GetAbsOrigin() + local ab = snowballAbility + --- В радиусе каста снежка (AbilityCastRange), не считая порог HP. + local heroesInSnowballRange = {} + for ____, ally in ipairs(closeHeroes) do + do + if not ally or ally:IsNull() or not ally:IsAlive() then + goto __continue23 + end + if ally:GetUnitName() == selfName then + goto __continue23 + end + local maxCast = ab:GetCastRange(origin, ally) + local dist = (ally:GetAbsOrigin() - origin):Length2D() + if dist <= maxCast + 32 then + heroesInSnowballRange[#heroesInSnowballRange + 1] = ally + end + end + ::__continue23:: + end + if #heroesInSnowballRange == 0 then + tryChaseNearestHero(nil) + return THINK_INTERVAL + end + local lowestAlly + local lowestHpPct = 101 + for ____, ally in ipairs(heroesInSnowballRange) do + do + if not ally or ally:IsNull() or not ally:IsAlive() then + goto __continue29 + end + local hpPct = ally:GetHealth() / math.max( + 1, + ally:GetMaxHealth() + ) * 100 + if hpPct >= SNOWBALL_ONLY_IF_TARGET_HP_PCT_BELOW then + goto __continue29 + end + if hpPct < lowestHpPct then + lowestHpPct = hpPct + lowestAlly = ally + end + end + ::__continue29:: + end + if lowestAlly and ab:IsFullyCastable() then + tryCastSnowballOnAlly(nil, lowestAlly) + end + return THINK_INTERVAL +end +registerEntityFunction( + nil, + "Spawn", + function() + if not IsServer() then + return + end + if not thisEntity or thisEntity:IsNull() then + return + end + snowballAbility = resolveSnowballAbility(nil) + if not snowballAbility then + print(("[ai_snowman] не найдена способность " .. SNOWBALL_ABILITY_NAME) .. " — ИИ не запущен") + return + end + thisEntity:SetContextThink("SnowmanThink", snowmanThink, THINK_INTERVAL) + end +) +return ____exports diff --git a/scripts/vscripts/ai/boss_100lvl/ai_demon_dragon_satyr.lua b/scripts/vscripts/ai/boss_100lvl/ai_demon_dragon_satyr.lua new file mode 100644 index 0000000..9c10b4f --- /dev/null +++ b/scripts/vscripts/ai/boss_100lvl/ai_demon_dragon_satyr.lua @@ -0,0 +1 @@ +--[[ Generated with https://github.com/TypeScriptToLua/TypeScriptToLua ]] diff --git a/scripts/vscripts/ai/nevermore_ai.lua b/scripts/vscripts/ai/nevermore_ai.lua new file mode 100644 index 0000000..001ff32 --- /dev/null +++ b/scripts/vscripts/ai/nevermore_ai.lua @@ -0,0 +1,980 @@ +local ____lualib = require("lualib_bundle") +local __TS__NumberToFixed = ____lualib.__TS__NumberToFixed +local __TS__ArrayFind = ____lualib.__TS__ArrayFind +local ____exports = {} +local getPredictedGroundPosition, getTargetHorizontalForward, getAimInFrontOfPredictedTarget, getBossForwardAimPoint, addCoilWaveAimJitter, getNevermoreCoilWavePhase, canCoilWaveHitAnyEnemy, canCoilBeamHitAnyEnemy, canHubCrossburstHitAnyEnemy, debugLog, NevermoreBossThink, tryIdleTripleCoilForward, purgeDebugPrint, removeAllModifiersFromUnit, purgeAndPositionNevermoreForRequiem, tryRequiemFromCenterPattern, tryCastPointAbility, tryComputeTimeWalkJump, tryTimeWalkIntoCoilPattern, trySpellSeries, tryExecuteQueuedCoil, abilities, cachedTarget, nextTargetSearchAt, comboLockUntil, queuedCoilAt, queuedCoilCount, queuedCoilInitial, queuedCoilSkipStreak, lastOrderAt, spellSeries, seriesCooldownUntil, SERIES_ROTATION, seriesRotationIndex, SERIES_TRIPLE_MIN, SERIES_TRIPLE_MAX, SERIES_WAVE_MIN, SERIES_WAVE_MAX, SERIES_BEAM_MIN, SERIES_BEAM_MAX, SERIES_CROSS_MIN, SERIES_CROSS_MAX, BETWEEN_SERIES_PAUSE, BETWEEN_CAST_GAP, WITHIN_SERIES_GAP, TIME_WALK_MIN_DISTANCE, PREDICT_LEAD_SPELLS, PREDICT_LEAD_TIME_WALK, PREDICT_MAX_LEAD_UNITS, HERO_ACQUIRE_RADIUS, COIL_JITTER_DIST_MIN, COIL_JITTER_DIST_MAX, COIL_JITTER_FOLLOW_MIN, COIL_JITTER_FOLLOW_MAX, AIM_FORWARD_FIRST_CAST, AIM_FORWARD_FOLLOW_CAST, MAX_QUEUED_COIL_SKIP_STREAK, TIME_WALK_STANDOFF_MIN, TIME_WALK_STANDOFF_MAX, aiNextCastAt, nextIdleTripleAt, requiemCommitPending, DEBUG_AI, nextDebugStateAt, debugTagNextAt, REQUIEM_RARE_ROLL_MAX, DEBUG_NEVERMORE_PURGE, PURGE_LOG_MAX_PER_CALL, NEVERMORE_PURGE_SKIP_MODIFIER_NAMES +local ____modifier_boss_nevermore_debuff_immune = require("abilities.creep.modifier_boss_nevermore_debuff_immune") +local modifier_boss_nevermore_debuff_immune = ____modifier_boss_nevermore_debuff_immune.modifier_boss_nevermore_debuff_immune +local ____modifier_boss_nevermore_phase_terror_wave = require("abilities.creep.modifier_boss_nevermore_phase_terror_wave") +local applyNevermorePhaseTerrorWave = ____modifier_boss_nevermore_phase_terror_wave.applyNevermorePhaseTerrorWave +local modifier_boss_nevermore_phase_terror_wave = ____modifier_boss_nevermore_phase_terror_wave.modifier_boss_nevermore_phase_terror_wave +local ____modifier_boss_nevermore_requiem_gate = require("abilities.creep.modifier_boss_nevermore_requiem_gate") +local applyNevermoreRequiemGate = ____modifier_boss_nevermore_requiem_gate.applyNevermoreRequiemGate +local modifier_boss_nevermore_requiem_gate = ____modifier_boss_nevermore_requiem_gate.modifier_boss_nevermore_requiem_gate +local ____modifier_boss_hud_health_bar = require("abilities.modifiers.modifier_boss_hud_health_bar") +local BOSS_NEVERMORE_NAME_TOKEN = ____modifier_boss_hud_health_bar.BOSS_NEVERMORE_NAME_TOKEN +local applyBossHudHealthBar = ____modifier_boss_hud_health_bar.applyBossHudHealthBar +local ____nevermore_boss_requiem_bridge = require("ai.nevermore_boss_requiem_bridge") +local nevermoreBumpRequiemAiCooldown = ____nevermore_boss_requiem_bridge.nevermoreBumpRequiemAiCooldown +local nevermoreGetRequiemNextAiTime = ____nevermore_boss_requiem_bridge.nevermoreGetRequiemNextAiTime +local nevermoreNeedsMandatoryRequiem = ____nevermore_boss_requiem_bridge.nevermoreNeedsMandatoryRequiem +local nevermoreRegisterPhaseRequiemHook = ____nevermore_boss_requiem_bridge.nevermoreRegisterPhaseRequiemHook +local ____dota_ts_adapter = require("lib.dota_ts_adapter") +local registerEntityFunction = ____dota_ts_adapter.registerEntityFunction +function getPredictedGroundPosition(self, target, leadSeconds) + local cur = GetGroundPosition( + target:GetAbsOrigin(), + nil + ) + local vx = target:GetVelocity().x + local vy = target:GetVelocity().y + local speed2d = math.sqrt(vx * vx + vy * vy) + if speed2d < 25 then + local spd = math.max( + 1, + target:GetIdealSpeed() + ) + local fwd = target:GetForwardVector() + vx = fwd.x * spd + vy = fwd.y * spd + end + local dx = vx * leadSeconds + local dy = vy * leadSeconds + local leadLen = math.sqrt(dx * dx + dy * dy) + if leadLen > PREDICT_MAX_LEAD_UNITS and leadLen > 0 then + local scale = PREDICT_MAX_LEAD_UNITS / leadLen + dx = dx * scale + dy = dy * scale + end + return GetGroundPosition( + cur + Vector(dx, dy, 0), + nil + ) +end +function getTargetHorizontalForward(self, target) + local f = target:GetForwardVector() + local len2d = math.sqrt(f.x * f.x + f.y * f.y) + if len2d < 0.01 then + return Vector(1, 0, 0) + end + return Vector(f.x / len2d, f.y / len2d, 0) +end +function getAimInFrontOfPredictedTarget(self, target, leadSeconds, forwardUnits) + local pred = getPredictedGroundPosition(nil, target, leadSeconds) + local fwd = getTargetHorizontalForward(nil, target) + return GetGroundPosition(pred + fwd * forwardUnits, nil) +end +function getBossForwardAimPoint(self, boss, distance) + local o = GetGroundPosition( + boss:GetAbsOrigin(), + nil + ) + local f = boss:GetForwardVector() + local len2d = math.sqrt(f.x * f.x + f.y * f.y) + local dir = len2d < 0.01 and Vector(1, 0, 0) or Vector(f.x / len2d, f.y / len2d, 0) + return GetGroundPosition(o + dir * distance, nil) +end +function addCoilWaveAimJitter(self, anchor, castIndexInSeries) + local sector = castIndexInSeries * 149 % 360 + local yaw = sector + RandomInt(0, 89) + local distMin = castIndexInSeries <= 0 and COIL_JITTER_DIST_MIN or COIL_JITTER_FOLLOW_MIN + local distMax = castIndexInSeries <= 0 and COIL_JITTER_DIST_MAX or COIL_JITTER_FOLLOW_MAX + local dist = RandomInt(distMin, distMax) + local offset = RotatePosition( + Vector(0, 0, 0), + QAngle(0, yaw, 0), + Vector(dist, 0, 0) + ) + return GetGroundPosition(anchor + offset, nil) +end +function getNevermoreCoilWavePhase(self, boss) + local hp = boss:GetHealthPercent() + if hp <= 25 then + return 4 + end + if hp <= 50 then + return 3 + end + if hp <= 75 then + return 2 + end + return 1 +end +function canCoilWaveHitAnyEnemy(self, boss, aimPoint) + local ab = abilities.coilWave + if not ab or ab:IsNull() then + return false + end + local phase = getNevermoreCoilWavePhase(nil, boss) + local origin = boss:GetAbsOrigin() + local dir = aimPoint - origin + dir.z = 0 + if dir:Length2D() < 1 then + local f = boss:GetForwardVector() + dir = Vector(f.x, f.y, 0) + end + dir = dir:Normalized() + local radius = ab:GetSpecialValueFor("radius") + if radius <= 0 then + return false + end + local baseSlotsKv = ab:GetSpecialValueFor("lane_slot_count") + local baseSlots = baseSlotsKv > 0 and math.floor(baseSlotsKv) or 5 + local slotCount = baseSlots + (phase - 1) + local forwardKv = ab:GetSpecialValueFor("lane_forward_dist") + local forwardDist = (forwardKv > 0 and forwardKv or 360) + (phase - 1) * 35 + local spacingKv = ab:GetSpecialValueFor("lane_slot_spacing") + local spacingFactor = spacingKv > 0 and spacingKv or 1.32 + local spacing = math.max(radius * spacingFactor, 185) + local right = Vector(-dir.y, dir.x, 0):Normalized() + local centerRow = GetGroundPosition(origin + dir * forwardDist, nil) + local mid = (slotCount - 1) / 2 + local searchR = forwardDist + radius + spacing * slotCount + 450 + local enemies = FindUnitsInRadius( + boss:GetTeamNumber(), + origin, + nil, + searchR, + DOTA_UNIT_TARGET_TEAM_ENEMY, + bit.bor(DOTA_UNIT_TARGET_HERO, DOTA_UNIT_TARGET_BASIC), + DOTA_UNIT_TARGET_FLAG_NONE, + FIND_ANY_ORDER, + false + ) + local hullFudge = 28 + for ____, u in ipairs(enemies) do + do + if not u or u:IsNull() or not u:IsAlive() then + goto __continue18 + end + local ep = GetGroundPosition( + u:GetAbsOrigin(), + nil + ) + do + local i = 0 + while i < slotCount do + local lateral = (i - mid) * spacing + local slotPos = GetGroundPosition(centerRow + right * lateral, nil) + if (ep - slotPos):Length2D() <= radius + hullFudge then + return true + end + i = i + 1 + end + end + end + ::__continue18:: + end + return false +end +function canCoilBeamHitAnyEnemy(self, boss, aimPoint) + local ab = abilities.coilBeam + if not ab or ab:IsNull() then + return false + end + local phase = getNevermoreCoilWavePhase(nil, boss) + local origin = boss:GetAbsOrigin() + local dir = aimPoint - origin + dir.z = 0 + if dir:Length2D() < 1 then + local f = boss:GetForwardVector() + dir = Vector(f.x, f.y, 0) + end + dir = dir:Normalized() + local radius = ab:GetSpecialValueFor("radius") + if radius <= 0 then + return false + end + local baseSlotsKv = ab:GetSpecialValueFor("lane_slot_count") + local baseSlots = baseSlotsKv > 0 and math.floor(baseSlotsKv) or 12 + local bonusKv = ab:GetSpecialValueFor("lane_slot_phase_bonus") + local perPhase = bonusKv > 0 and math.floor(bonusKv) or 2 + local slotCount = baseSlots + (phase - 1) * perPhase + local startKv = ab:GetSpecialValueFor("beam_start_dist") + local startDist = startKv > 0 and startKv or 140 + local stepKv = ab:GetSpecialValueFor("beam_step") + local step = stepKv > 0 and stepKv or math.max(radius * 1.22, 195) + local endAlong = startDist + (slotCount - 1) * step + local searchR = endAlong + radius + 200 + local enemies = FindUnitsInRadius( + boss:GetTeamNumber(), + origin, + nil, + searchR, + DOTA_UNIT_TARGET_TEAM_ENEMY, + bit.bor(DOTA_UNIT_TARGET_HERO, DOTA_UNIT_TARGET_BASIC), + DOTA_UNIT_TARGET_FLAG_NONE, + FIND_ANY_ORDER, + false + ) + local hullFudge = 28 + for ____, u in ipairs(enemies) do + do + if not u or u:IsNull() or not u:IsAlive() then + goto __continue27 + end + local ep = GetGroundPosition( + u:GetAbsOrigin(), + nil + ) + do + local i = 0 + while i < slotCount do + local along = startDist + i * step + local slotPos = GetGroundPosition(origin + dir * along, nil) + if (ep - slotPos):Length2D() <= radius + hullFudge then + return true + end + i = i + 1 + end + end + end + ::__continue27:: + end + return false +end +function canHubCrossburstHitAnyEnemy(self, boss) + local ab = abilities.hubCrossburst + if not ab or ab:IsNull() then + return false + end + local pickKv = ab:GetSpecialValueFor("spawn_pick_radius") + local pick = pickKv > 0 and pickKv or 1500 + local startKv = ab:GetSpecialValueFor("ring_start_dist") + local stepKv = ab:GetSpecialValueFor("ring_step") + local countKv = ab:GetSpecialValueFor("ring_count") + local countBonusKv = ab:GetSpecialValueFor("ring_count_phase_bonus") + local radius = ab:GetSpecialValueFor("radius") + local start = startKv > 0 and startKv or 90 + local step = stepKv > 0 and stepKv or 190 + local baseCnt = countKv > 0 and math.floor(countKv) or 7 + local perPh = countBonusKv > 0 and math.floor(countBonusKv) or 1 + local phase = getNevermoreCoilWavePhase(nil, boss) + local cnt = math.max(1, baseCnt + (phase - 1) * perPh) + local r = radius > 0 and radius or 165 + local armReach = start + math.max(0, cnt - 1) * step + r + local searchR = pick + armReach + 250 + local enemies = FindUnitsInRadius( + boss:GetTeamNumber(), + boss:GetAbsOrigin(), + nil, + searchR, + DOTA_UNIT_TARGET_TEAM_ENEMY, + bit.bor(DOTA_UNIT_TARGET_HERO, DOTA_UNIT_TARGET_BASIC), + DOTA_UNIT_TARGET_FLAG_NONE, + FIND_ANY_ORDER, + false + ) + for ____, u in ipairs(enemies) do + if u and not u:IsNull() and u:IsAlive() then + return true + end + end + return false +end +function debugLog(self, tag, message, throttle) + if throttle == nil then + throttle = 0.35 + end + if not DEBUG_AI then + return + end + local now = GameRules:GetGameTime() + local nextAt = debugTagNextAt[tag] or 0 + if now < nextAt then + return + end + debugTagNextAt[tag] = now + throttle + print((("[NevermoreAI][" .. tag) .. "] ") .. message) +end +function NevermoreBossThink(self) + if not IsServer() or not thisEntity or thisEntity:IsNull() then + return 0.5 + end + if not thisEntity:IsAlive() then + return 0.5 + end + if thisEntity:IsChanneling() then + return 0.5 + end + if thisEntity:GetOwner() ~= nil and thisEntity:GetOwner():IsRealHero() then + return 0.5 + end + local now = GameRules:GetGameTime() + if now >= nextDebugStateAt then + nextDebugStateAt = now + 1 + local ser = spellSeries and (spellSeries.kind .. "x") .. tostring(spellSeries.remaining) or "idle" + debugLog( + nil, + "state", + (((((("lock=" .. __TS__NumberToFixed(comboLockUntil - now, 2)) .. " target=") .. (cachedTarget and cachedTarget:GetUnitName() or "none")) .. " q=") .. tostring(queuedCoilCount)) .. " series=") .. ser, + 0.95 + ) + end + if tryExecuteQueuedCoil(nil, now) then + return 0.14 + end + if now < comboLockUntil then + return 0.14 + end + local shouldRefreshTarget = now >= nextTargetSearchAt or not cachedTarget or cachedTarget:IsNull() or not cachedTarget:IsAlive() or (cachedTarget:GetAbsOrigin() - thisEntity:GetAbsOrigin()):Length2D() > HERO_ACQUIRE_RADIUS + if shouldRefreshTarget then + local enemies = FindUnitsInRadius( + thisEntity:GetTeamNumber(), + thisEntity:GetAbsOrigin(), + nil, + HERO_ACQUIRE_RADIUS, + DOTA_UNIT_TARGET_TEAM_ENEMY, + DOTA_UNIT_TARGET_HERO, + DOTA_UNIT_TARGET_FLAG_NO_INVIS, + FIND_CLOSEST, + false + ) + if #enemies == 0 then + enemies = FindUnitsInRadius( + thisEntity:GetTeamNumber(), + thisEntity:GetAbsOrigin(), + nil, + HERO_ACQUIRE_RADIUS, + DOTA_UNIT_TARGET_TEAM_ENEMY, + DOTA_UNIT_TARGET_BASIC, + DOTA_UNIT_TARGET_FLAG_NO_INVIS, + FIND_CLOSEST, + false + ) + end + local enemyHero = __TS__ArrayFind( + enemies, + function(____, unit) return unit:IsRealHero() and unit:IsAlive() end + ) + cachedTarget = enemyHero or enemies[1] + nextTargetSearchAt = now + (cachedTarget ~= nil and 0.55 or 1) + debugLog( + nil, + "target", + (("refresh enemies=" .. tostring(#enemies)) .. " chosen=") .. (cachedTarget ~= nil and cachedTarget:GetUnitName() or "none"), + 0.35 + ) + end + local primaryTarget = cachedTarget + if not primaryTarget then + requiemCommitPending = false + spellSeries = nil + queuedCoilCount = 0 + queuedCoilInitial = 0 + queuedCoilSkipStreak = 0 + if tryIdleTripleCoilForward(nil, now) then + return 0.14 + end + return 0.25 + end + if tryTimeWalkIntoCoilPattern(nil, primaryTarget, now) then + return 0.14 + end + if trySpellSeries(nil, primaryTarget, now) then + return 0.14 + end + if tryRequiemFromCenterPattern(nil, primaryTarget, now) then + return 0.14 + end + comboLockUntil = now + 0.18 + return 0.18 +end +function tryIdleTripleCoilForward(self, now) + if now < nextIdleTripleAt then + return false + end + if not abilities.tripleCoil or not abilities.tripleCoil:IsFullyCastable() then + return false + end + if thisEntity:HasModifier("modifier_boss_nevermore_time_walk") then + return false + end + local pos = getBossForwardAimPoint( + nil, + thisEntity, + RandomInt(280, 520) + ) + if not tryCastPointAbility(nil, abilities.tripleCoil, pos, now) then + return false + end + nextIdleTripleAt = now + 3.2 + comboLockUntil = now + 1 + debugLog(nil, "idle_triple", "no enemies — triple forward", 0.4) + return true +end +function purgeDebugPrint(self, message) + if DEBUG_NEVERMORE_PURGE then + print("[NevermorePurge] " .. message) + end +end +function removeAllModifiersFromUnit(self, unit) + if not unit or unit:IsNull() then + purgeDebugPrint(nil, "removeAllModifiersFromUnit: unit пустой") + return + end + local unitLabel = "" + do + pcall(function() + unitLabel = unit:GetUnitName() + end) + end + local startCount = unit:GetModifierCount() + purgeDebugPrint( + nil, + (((("старт: " .. unitLabel) .. " ent=") .. tostring(unit:entindex())) .. " modifiers=") .. tostring(startCount) + ) + local guard = 0 + local maxIterations = 400 + local logged = 0 + while unit:GetModifierCount() > 0 and guard < maxIterations do + do + guard = guard + 1 + local beforeCount = unit:GetModifierCount() + local lastIdx = beforeCount - 1 + local name = unit:GetModifierNameByIndex(lastIdx) + if name == nil or name == "" then + purgeDebugPrint( + nil, + (("прервали: пустое имя по индексу " .. tostring(lastIdx)) .. ", count=") .. tostring(beforeCount) + ) + break + end + if NEVERMORE_PURGE_SKIP_MODIFIER_NAMES[name] then + local removedSkipped = false + do + local i = lastIdx - 1 + while i >= 0 do + do + local altName = unit:GetModifierNameByIndex(i) + if not altName or NEVERMORE_PURGE_SKIP_MODIFIER_NAMES[altName] then + goto __continue72 + end + unit:RemoveModifierByName(altName) + removedSkipped = true + break + end + ::__continue72:: + i = i - 1 + end + end + if not removedSkipped then + purgeDebugPrint( + nil, + "стоп whitelist: остались только защищённые модификаторы, count=" .. tostring(beforeCount) + ) + break + end + goto __continue69 + end + if logged < PURGE_LOG_MAX_PER_CALL then + purgeDebugPrint( + nil, + ((((((" #" .. tostring(guard)) .. " снятие: idx=") .. tostring(lastIdx)) .. " name=\"") .. name) .. "\" count до=") .. tostring(beforeCount) + ) + logged = logged + 1 + elseif logged == PURGE_LOG_MAX_PER_CALL then + purgeDebugPrint( + nil, + (" … дальше без логов каждого шага (лимит " .. tostring(PURGE_LOG_MAX_PER_CALL)) .. ")" + ) + logged = logged + 1 + end + unit:RemoveModifierByName(name) + local afterCount = unit:GetModifierCount() + if afterCount >= beforeCount then + purgeDebugPrint( + nil, + (((("СТОП: RemoveModifierByName не уменьшил список (движок не снял?) name=\"" .. name) .. "\" до=") .. tostring(beforeCount)) .. " после=") .. tostring(afterCount) + ) + break + end + end + ::__continue69:: + end + local endCount = unit:GetModifierCount() + if guard >= maxIterations then + purgeDebugPrint( + nil, + (("ВНИМАНИЕ: достигнут лимит итераций " .. tostring(maxIterations)) .. ", осталось модификаторов: ") .. tostring(endCount) + ) + end + purgeDebugPrint( + nil, + ((((("конец цикла: снятий≈" .. tostring(guard)) .. ", осталось modifiers=") .. tostring(endCount)) .. " (") .. unitLabel) .. ")" + ) +end +function purgeAndPositionNevermoreForRequiem(self) + local t0 = IsServer() and GameRules:GetGameTime() or 0 + local n0 = thisEntity:GetModifierCount() + purgeDebugPrint( + nil, + (((("purgeAndPosition: до снятия time=" .. __TS__NumberToFixed(t0, 2)) .. " modifiers=") .. tostring(n0)) .. " requiemPending=") .. tostring(requiemCommitPending) + ) + removeAllModifiersFromUnit(nil, thisEntity) + local n1 = thisEntity:GetModifierCount() + purgeDebugPrint( + nil, + "purgeAndPosition: после removeAll modifiers=" .. tostring(n1) + ) + thisEntity:Purge( + true, + true, + false, + true, + true + ) + local n2 = thisEntity:GetModifierCount() + purgeDebugPrint( + nil, + "purgeAndPosition: после Purge modifiers=" .. tostring(n2) + ) + applyNevermoreRequiemGate(nil, thisEntity) + local center = Entities:FindByName(nil, "nevermore_center_point") + if center then + debugLog(nil, "requiem", "teleport to nevermore_center_point", 0.2) + FindClearSpaceForUnit( + thisEntity, + center:GetAbsOrigin(), + true + ) + local cp = center:GetAbsOrigin() + purgeDebugPrint( + nil, + ((((("телепорт: nevermore_center_point ok pos=(" .. __TS__NumberToFixed(cp.x, 0)) .. ",") .. __TS__NumberToFixed(cp.y, 0)) .. ",") .. __TS__NumberToFixed(cp.z, 0)) .. ")" + ) + else + purgeDebugPrint(nil, "телепорт: сущность nevermore_center_point НЕ НАЙДЕНА на карте") + debugLog(nil, "requiem", "center point not found", 1) + end + local req = abilities.requiem + if req and not req:IsNull() then + purgeDebugPrint( + nil, + (((((((("requiem ability: castable=" .. tostring(req:IsFullyCastable())) .. " channel=") .. tostring(req:GetChannelTime())) .. " cd=") .. __TS__NumberToFixed( + req:GetCooldownTimeRemaining(), + 2 + )) .. " mana=") .. __TS__NumberToFixed( + thisEntity:GetMana(), + 0 + )) .. "/") .. __TS__NumberToFixed( + thisEntity:GetMaxMana(), + 0 + ) + ) + else + purgeDebugPrint(nil, "requiem ability: отсутствует") + end +end +function tryRequiemFromCenterPattern(self, target, now) + if not abilities.requiem then + return false + end + if not target or target:IsNull() or not target:IsAlive() then + requiemCommitPending = false + return false + end + local mandatoryRequiem = nevermoreNeedsMandatoryRequiem(nil, thisEntity) + if not requiemCommitPending then + if not mandatoryRequiem and now < nevermoreGetRequiemNextAiTime(nil) then + return false + end + if not abilities.requiem:IsFullyCastable() then + return false + end + local hpPct = thisEntity:GetHealthPercent() + local allowByHp = hpPct <= 38 + local allowByRareRoll = RandomInt(1, 100) <= REQUIEM_RARE_ROLL_MAX + if not mandatoryRequiem and not allowByHp and not allowByRareRoll then + debugLog( + nil, + "requiem", + ("skip rare ult (hp=" .. __TS__NumberToFixed(hpPct, 0)) .. "%)", + 2 + ) + return false + end + requiemCommitPending = true + if mandatoryRequiem then + debugLog( + nil, + "requiem", + ("mandatory ult hp=" .. __TS__NumberToFixed(hpPct, 0)) .. "%", + 0.5 + ) + end + end + purgeAndPositionNevermoreForRequiem(nil) + local castPosition = getAimInFrontOfPredictedTarget(nil, target, PREDICT_LEAD_SPELLS + 0.15, AIM_FORWARD_FIRST_CAST) + if not tryCastPointAbility(nil, abilities.requiem, castPosition, now) then + comboLockUntil = now + 0.12 + return true + end + requiemCommitPending = false + debugLog( + nil, + "requiem", + (("cast at " .. __TS__NumberToFixed(castPosition.x, 0)) .. ",") .. __TS__NumberToFixed(castPosition.y, 0), + 0.2 + ) + nevermoreBumpRequiemAiCooldown(nil) + comboLockUntil = now + 1 + queuedCoilCount = 0 + queuedCoilInitial = 0 + spellSeries = nil + seriesCooldownUntil = now + 2 + return true +end +function tryCastPointAbility(self, ability, position, now) + if not ability or ability:IsNull() then + debugLog(nil, "cast_fail", "ability missing", 0.5) + return false + end + if not ability:IsFullyCastable() then + debugLog( + nil, + "cast_fail", + ability:GetAbilityName() .. " not castable", + 0.25 + ) + return false + end + if now < lastOrderAt + 0.12 then + debugLog( + nil, + "cast_fail", + ability:GetAbilityName() .. " blocked by order throttle", + 0.25 + ) + return false + end + ExecuteOrderFromTable({ + UnitIndex = thisEntity:entindex(), + OrderType = DOTA_UNIT_ORDER_CAST_POSITION, + AbilityIndex = ability:entindex(), + Position = position + }) + lastOrderAt = now + debugLog( + nil, + "cast_ok", + (((ability:GetAbilityName() .. " -> ") .. __TS__NumberToFixed(position.x, 0)) .. ",") .. __TS__NumberToFixed(position.y, 0), + 0.12 + ) + return true +end +function tryComputeTimeWalkJump(self, target) + if not abilities.timeWalk then + return nil + end + local bossPos = thisEntity:GetAbsOrigin() + local predicted = getPredictedGroundPosition(nil, target, PREDICT_LEAD_TIME_WALK) + local toPred = predicted - bossPos + local distanceToPredicted = toPred:Length2D() + if distanceToPredicted <= TIME_WALK_MIN_DISTANCE then + return nil + end + local dir = distanceToPredicted < 1 and thisEntity:GetForwardVector() or toPred:Normalized() + local standoff = RandomInt(TIME_WALK_STANDOFF_MIN, TIME_WALK_STANDOFF_MAX) + local jumpPos = GetGroundPosition(predicted - dir * standoff, nil) + local maxRange = abilities.timeWalk:GetCastRange(bossPos, nil) + local toJump = jumpPos - bossPos + local jumpLen = toJump:Length2D() + if jumpLen > maxRange - 30 then + jumpPos = GetGroundPosition( + bossPos + toJump:Normalized() * math.max(50, maxRange - 40), + nil + ) + end + return jumpPos +end +function tryTimeWalkIntoCoilPattern(self, target, now) + if not abilities.timeWalk or not abilities.coilWave then + return false + end + if now < aiNextCastAt.timeWalk or now < aiNextCastAt.coilWave then + return false + end + if not abilities.timeWalk:IsFullyCastable() or not abilities.coilWave:IsFullyCastable() then + return false + end + if thisEntity:HasModifier("modifier_boss_nevermore_time_walk") then + return false + end + local jumpPos = tryComputeTimeWalkJump(nil, target) + if not jumpPos then + return false + end + local predictedForLog = getPredictedGroundPosition(nil, target, PREDICT_LEAD_TIME_WALK) + local distBossToPred = (predictedForLog - thisEntity:GetAbsOrigin()):Length2D() + if not tryCastPointAbility(nil, abilities.timeWalk, jumpPos, now) then + return false + end + debugLog( + nil, + "time_walk", + (("distToPred=" .. __TS__NumberToFixed(distBossToPred, 0)) .. " queuedCoils=") .. tostring(queuedCoilCount), + 0.2 + ) + aiNextCastAt.timeWalk = now + 3 + aiNextCastAt.coilWave = now + BETWEEN_CAST_GAP + comboLockUntil = now + 0.7 + spellSeries = nil + seriesCooldownUntil = now + 4 + queuedCoilCount = RandomInt(2, 3) + queuedCoilInitial = queuedCoilCount + queuedCoilAt = now + 0.55 + queuedCoilSkipStreak = 0 + return true +end +function trySpellSeries(self, target, now) + if thisEntity:HasModifier("modifier_boss_nevermore_time_walk") then + return false + end + if spellSeries == nil then + if now < seriesCooldownUntil then + return false + end + local kind = SERIES_ROTATION[seriesRotationIndex % #SERIES_ROTATION + 1] + seriesRotationIndex = seriesRotationIndex + 1 + local count = kind == "triple" and RandomInt(SERIES_TRIPLE_MIN, SERIES_TRIPLE_MAX) or (kind == "wave" and RandomInt(SERIES_WAVE_MIN, SERIES_WAVE_MAX) or (kind == "beam" and RandomInt(SERIES_BEAM_MIN, SERIES_BEAM_MAX) or RandomInt(SERIES_CROSS_MIN, SERIES_CROSS_MAX))) + local ____temp_2 + if kind == "triple" then + ____temp_2 = abilities.tripleCoil + else + local ____temp_1 + if kind == "wave" then + ____temp_1 = abilities.coilWave + else + local ____temp_0 + if kind == "beam" then + ____temp_0 = abilities.coilBeam + else + ____temp_0 = abilities.hubCrossburst + end + ____temp_1 = ____temp_0 + end + ____temp_2 = ____temp_1 + end + local ab = ____temp_2 + if not ab then + return false + end + spellSeries = {kind = kind, remaining = count, totalInSeries = count} + debugLog( + nil, + "series", + (("start " .. kind) .. " x") .. tostring(count), + 0.2 + ) + end + local series = spellSeries + if not series then + return false + end + local ____temp_5 + if series.kind == "triple" then + ____temp_5 = abilities.tripleCoil + else + local ____temp_4 + if series.kind == "wave" then + ____temp_4 = abilities.coilWave + else + local ____temp_3 + if series.kind == "beam" then + ____temp_3 = abilities.coilBeam + else + ____temp_3 = abilities.hubCrossburst + end + ____temp_4 = ____temp_3 + end + ____temp_5 = ____temp_4 + end + local ability = ____temp_5 + if not ability or not ability:IsFullyCastable() then + debugLog(nil, "series", series.kind .. " not castable, wait", 0.3) + comboLockUntil = now + 0.25 + return true + end + local castIndex = series.totalInSeries - series.remaining + local forwardUnits = castIndex == 0 and AIM_FORWARD_FIRST_CAST or AIM_FORWARD_FOLLOW_CAST + local anchor = getAimInFrontOfPredictedTarget(nil, target, PREDICT_LEAD_SPELLS, forwardUnits) + local castPos = addCoilWaveAimJitter(nil, anchor, castIndex) + if series.kind == "wave" and not canCoilWaveHitAnyEnemy(nil, thisEntity, castPos) then + debugLog(nil, "series", "wave abort — никого в полосе, сбрасываем серию", 0.28) + spellSeries = nil + seriesCooldownUntil = now + 0.45 + comboLockUntil = now + 0.22 + return true + end + if series.kind == "beam" and not canCoilBeamHitAnyEnemy(nil, thisEntity, castPos) then + debugLog(nil, "series", "beam abort — никого на луче, сбрасываем серию", 0.28) + spellSeries = nil + seriesCooldownUntil = now + 0.45 + comboLockUntil = now + 0.22 + return true + end + if series.kind == "cross" and not canHubCrossburstHitAnyEnemy(nil, thisEntity) then + debugLog(nil, "series", "crossburst abort — врагов слишком далеко для зоны хаба", 0.28) + spellSeries = nil + seriesCooldownUntil = now + 0.45 + comboLockUntil = now + 0.22 + return true + end + if not tryCastPointAbility(nil, ability, castPos, now) then + return false + end + series.remaining = series.remaining - 1 + debugLog( + nil, + "series", + (series.kind .. " cast, left=") .. tostring(series.remaining), + 0.15 + ) + if series.remaining <= 0 then + spellSeries = nil + seriesCooldownUntil = now + BETWEEN_SERIES_PAUSE + comboLockUntil = now + WITHIN_SERIES_GAP + debugLog( + nil, + "series", + ("end -> pause " .. tostring(BETWEEN_SERIES_PAUSE)) .. "s", + 0.2 + ) + else + comboLockUntil = now + WITHIN_SERIES_GAP + end + return true +end +function tryExecuteQueuedCoil(self, now) + if queuedCoilCount <= 0 or now < queuedCoilAt then + return false + end + if not cachedTarget or cachedTarget:IsNull() or not cachedTarget:IsAlive() then + debugLog(nil, "queued", "target invalid -> clear queue", 0.5) + queuedCoilCount = 0 + queuedCoilInitial = 0 + queuedCoilSkipStreak = 0 + return false + end + if not abilities.coilWave or not abilities.coilWave:IsFullyCastable() then + debugLog(nil, "queued", "coil not castable -> postpone", 0.35) + queuedCoilAt = now + 0.2 + return false + end + local castIndex = queuedCoilInitial - queuedCoilCount + local forwardUnits = castIndex == 0 and AIM_FORWARD_FIRST_CAST or AIM_FORWARD_FOLLOW_CAST + local anchor = getAimInFrontOfPredictedTarget(nil, cachedTarget, PREDICT_LEAD_SPELLS, forwardUnits) + local pos = addCoilWaveAimJitter(nil, anchor, castIndex) + if not canCoilWaveHitAnyEnemy(nil, thisEntity, pos) then + queuedCoilSkipStreak = queuedCoilSkipStreak + 1 + if queuedCoilSkipStreak >= MAX_QUEUED_COIL_SKIP_STREAK then + debugLog(nil, "queued", "coil abort queue — нет целей в полосе слишком долго", 0.35) + queuedCoilCount = 0 + queuedCoilInitial = 0 + queuedCoilSkipStreak = 0 + return false + end + debugLog(nil, "queued", "coil skip — никого в полосе, откладываем", 0.3) + queuedCoilAt = now + 0.35 + return true + end + if not tryCastPointAbility(nil, abilities.coilWave, pos, now) then + return false + end + queuedCoilSkipStreak = 0 + queuedCoilCount = queuedCoilCount - 1 + queuedCoilAt = now + BETWEEN_CAST_GAP + aiNextCastAt.coilWave = math.max(aiNextCastAt.coilWave, now + BETWEEN_CAST_GAP) + comboLockUntil = now + BETWEEN_CAST_GAP + debugLog( + nil, + "queued", + "coil fired, left=" .. tostring(queuedCoilCount), + 0.2 + ) + return true +end +abilities = {} +nextTargetSearchAt = 0 +comboLockUntil = 0 +queuedCoilAt = 0 +queuedCoilCount = 0 +queuedCoilInitial = 0 +queuedCoilSkipStreak = 0 +lastOrderAt = 0 +seriesCooldownUntil = 0 +SERIES_ROTATION = {"triple", "wave", "beam", "cross"} +seriesRotationIndex = 0 +SERIES_TRIPLE_MIN = 2 +SERIES_TRIPLE_MAX = 4 +SERIES_WAVE_MIN = 2 +SERIES_WAVE_MAX = 4 +SERIES_BEAM_MIN = 2 +SERIES_BEAM_MAX = 4 +SERIES_CROSS_MIN = 1 +SERIES_CROSS_MAX = 2 +BETWEEN_SERIES_PAUSE = 1.6 +BETWEEN_CAST_GAP = 1 +WITHIN_SERIES_GAP = BETWEEN_CAST_GAP +TIME_WALK_MIN_DISTANCE = 2000 +PREDICT_LEAD_SPELLS = 0.42 +PREDICT_LEAD_TIME_WALK = 0.55 +PREDICT_MAX_LEAD_UNITS = 480 +HERO_ACQUIRE_RADIUS = 12000 +COIL_JITTER_DIST_MIN = 60 +COIL_JITTER_DIST_MAX = 200 +COIL_JITTER_FOLLOW_MIN = 90 +COIL_JITTER_FOLLOW_MAX = 260 +AIM_FORWARD_FIRST_CAST = 100 +AIM_FORWARD_FOLLOW_CAST = 50 +MAX_QUEUED_COIL_SKIP_STREAK = 5 +TIME_WALK_STANDOFF_MIN = 380 +TIME_WALK_STANDOFF_MAX = 560 +aiNextCastAt = {timeWalk = 0, tripleCoil = 0, coilWave = 0} +nextIdleTripleAt = 0 +requiemCommitPending = false +DEBUG_AI = true +nextDebugStateAt = 0 +debugTagNextAt = {} +registerEntityFunction( + nil, + "Spawn", + function() + if not IsServer() or not thisEntity then + return + end + applyBossHudHealthBar(nil, thisEntity, BOSS_NEVERMORE_NAME_TOKEN) + applyNevermorePhaseTerrorWave(nil, thisEntity) + applyNevermoreRequiemGate(nil, thisEntity) + thisEntity:AddNewModifier( + thisEntity, + getModifierSourceAbility(nil, thisEntity), + modifier_boss_nevermore_debuff_immune.name, + {} + ) + nevermoreRegisterPhaseRequiemHook( + nil, + function() + requiemCommitPending = false + spellSeries = nil + queuedCoilCount = 0 + queuedCoilInitial = 0 + queuedCoilSkipStreak = 0 + seriesCooldownUntil = GameRules:GetGameTime() + 2 + end + ) + abilities.timeWalk = thisEntity:FindAbilityByName("boss_nevermore_time_walk") or nil + abilities.requiem = thisEntity:FindAbilityByName("boss_nevermore_requiem_barrage") or nil + abilities.tripleCoil = thisEntity:FindAbilityByName("boss_nevermore_triple_coil_aoe") or nil + abilities.coilWave = thisEntity:FindAbilityByName("boss_nevermore_coil_wave") or nil + abilities.coilBeam = thisEntity:FindAbilityByName("boss_nevermore_coil_beam") or nil + abilities.hubCrossburst = thisEntity:FindAbilityByName("boss_nevermore_hub_crossburst") or nil + thisEntity:SetContextThink("NevermoreBossThink", NevermoreBossThink, 0.25) + end +) +REQUIEM_RARE_ROLL_MAX = 4 +DEBUG_NEVERMORE_PURGE = true +PURGE_LOG_MAX_PER_CALL = 40 +NEVERMORE_PURGE_SKIP_MODIFIER_NAMES = {[modifier_boss_nevermore_debuff_immune.name] = true, [modifier_boss_nevermore_phase_terror_wave.name] = true, [modifier_boss_nevermore_requiem_gate.name] = true, modifier_no_healthbar = true} +return ____exports diff --git a/scripts/vscripts/ai/nevermore_boss_requiem_bridge.lua b/scripts/vscripts/ai/nevermore_boss_requiem_bridge.lua new file mode 100644 index 0000000..33d8820 --- /dev/null +++ b/scripts/vscripts/ai/nevermore_boss_requiem_bridge.lua @@ -0,0 +1,128 @@ +--[[ Generated with https://github.com/TypeScriptToLua/TypeScriptToLua ]] +local ____exports = {} +local requiemNextAiAt, onScriptedPhaseRequiem +function ____exports.nevermoreBumpRequiemAiCooldown(self) + requiemNextAiAt = GameRules:GetGameTime() + RandomInt(____exports.NEVERMORE_REQUIEM_AI_CD_MIN, ____exports.NEVERMORE_REQUIEM_AI_CD_MAX) +end +--- Перед кастом реквиема после Terror Wave: очистка очередей серий ИИ + откладываем ульту у ИИ. +function ____exports.nevermoreNotifyScriptedPhaseRequiem(self) + if onScriptedPhaseRequiem ~= nil then + onScriptedPhaseRequiem(nil) + end + ____exports.nevermoreBumpRequiemAiCooldown(nil) +end +--- Кулдаун реквиема для ИИ, счётчик обязательных кастов ульты, принудительный каст. +____exports.NEVERMORE_REQUIEM_AI_CD_MIN = 72 +____exports.NEVERMORE_REQUIEM_AI_CD_MAX = 96 +--- Сколько раз босс должен скастовать реквием (ульт), чтобы снять «броню». +____exports.NEVERMORE_REQUIEM_REQUIRED_CASTS = 3 +--- HP% строго ниже — включается −80% входящего, пока не набрано 3 каста. +____exports.NEVERMORE_REQUIEM_HP_THRESHOLD = 50 +____exports.NEVERMORE_REQUIEM_DAMAGE_REDUCTION_PCT = 80 +local REQUIEM_ABILITY = "boss_nevermore_requiem_barrage" +requiemNextAiAt = 0 +local requiemCastCountByEnt = {} +function ____exports.nevermoreRegisterPhaseRequiemHook(self, fn) + onScriptedPhaseRequiem = fn +end +function ____exports.nevermoreGetRequiemNextAiTime(self) + return requiemNextAiAt +end +function ____exports.nevermoreGetRequiemCastCount(self, boss) + return requiemCastCountByEnt[boss:entindex()] or 0 +end +function ____exports.nevermoreIncrementRequiemCastCount(self, boss) + local ent = boss:entindex() + local next = math.min(____exports.NEVERMORE_REQUIEM_REQUIRED_CASTS, (requiemCastCountByEnt[ent] or 0) + 1) + requiemCastCountByEnt[ent] = next +end +function ____exports.nevermoreClearRequiemCastCount(self, boss) + requiemCastCountByEnt[boss:entindex()] = nil +end +--- Нужно добить касты ульты: уже ниже порога HP, а реквиемов < 3. +function ____exports.nevermoreNeedsMandatoryRequiem(self, boss) + if not boss or boss:IsNull() or not boss:IsAlive() then + return false + end + return boss:GetHealthPercent() < ____exports.NEVERMORE_REQUIEM_HP_THRESHOLD and ____exports.nevermoreGetRequiemCastCount(nil, boss) < ____exports.NEVERMORE_REQUIEM_REQUIRED_CASTS +end +local function getRequiemCastPositionForBoss(self, boss) + local origin = GetGroundPosition( + boss:GetAbsOrigin(), + nil + ) + local enemies = FindUnitsInRadius( + boss:GetTeamNumber(), + origin, + nil, + 12000, + DOTA_UNIT_TARGET_TEAM_ENEMY, + bit.bor(DOTA_UNIT_TARGET_HERO, DOTA_UNIT_TARGET_BASIC), + DOTA_UNIT_TARGET_FLAG_NONE, + FIND_CLOSEST, + false + ) + for ____, e in ipairs(enemies) do + do + if not e or e:IsNull() or not e:IsAlive() then + goto __continue11 + end + local ep = GetGroundPosition( + e:GetAbsOrigin(), + nil + ) + local to = ep - origin + if to:Length2D() < 1 then + goto __continue11 + end + local fwd = to:Normalized() + return GetGroundPosition(ep + fwd * 100, nil) + end + ::__continue11:: + end + local f = boss:GetForwardVector() + local len2d = math.sqrt(f.x * f.x + f.y * f.y) + local dir = len2d < 0.01 and Vector(1, 0, 0) or Vector(f.x / len2d, f.y / len2d, 0) + return GetGroundPosition(origin + dir * 400, nil) +end +--- Телепорт в центр (если есть) и каст реквиема. +function ____exports.tryForceNevermoreRequiemCast(self, boss) + if not IsServer() then + return false + end + if not boss or boss:IsNull() or not boss:IsAlive() then + return false + end + if boss:IsChanneling() then + return false + end + local req = boss:FindAbilityByName(REQUIEM_ABILITY) + if not req or req:IsNull() then + return false + end + if req:GetLevel() < 1 then + req:SetLevel(1) + end + req:EndCooldown() + if not req:IsFullyCastable() then + return false + end + ____exports.nevermoreNotifyScriptedPhaseRequiem(nil) + local center = Entities:FindByName(nil, "nevermore_center_point") + if center and not center:IsNull() then + FindClearSpaceForUnit( + boss, + center:GetAbsOrigin(), + true + ) + end + local aim = getRequiemCastPositionForBoss(nil, boss) + ExecuteOrderFromTable({ + UnitIndex = boss:entindex(), + OrderType = DOTA_UNIT_ORDER_CAST_POSITION, + AbilityIndex = req:entindex(), + Position = aim + }) + return true +end +return ____exports diff --git a/scripts/vscripts/api_helper.lua b/scripts/vscripts/api_helper.lua new file mode 100644 index 0000000..5f7927f --- /dev/null +++ b/scripts/vscripts/api_helper.lua @@ -0,0 +1,63 @@ +local ____lualib = require("lualib_bundle") +local __TS__StringTrim = ____lualib.__TS__StringTrim +local ____exports = {} +local ____server_config = require("server_config") +local SERVER_CONFIG = ____server_config.SERVER_CONFIG +local SERVER_KEY_SCOPE = "zombie_invasion" +local LOCAL_OVERRIDE_SERVER_KEY = "" +local cachedServerKey = nil +local didPrintServerKeyWarning = false +local function resolveServerKey(self) + if cachedServerKey ~= nil then + return cachedServerKey + end + local localOverrideKey = __TS__StringTrim(LOCAL_OVERRIDE_SERVER_KEY) + if #localOverrideKey > 0 then + cachedServerKey = localOverrideKey + return cachedServerKey + end + if GameRules:IsCheatMode() then + return "menya_ebut_negry_tolpoy" + end + if type(GetDedicatedServerKeyV3) == "function" then + local rawKey = GetDedicatedServerKeyV3(SERVER_KEY_SCOPE) + if rawKey ~= nil and rawKey ~= nil then + local serverKey = tostring(rawKey) + if #serverKey > 0 then + cachedServerKey = serverKey + return cachedServerKey + end + end + end + if not didPrintServerKeyWarning then + didPrintServerKeyWarning = true + print(("[api_helper] Некорректный DedicatedServerKey для scope='" .. SERVER_KEY_SCOPE) .. "'. Запросы API будут без x-custom-key.") + end + cachedServerKey = "" + return cachedServerKey +end +--- Устанавливает стандартные заголовки для HTTP запроса +function ____exports.setApiHeaders(self, request) + local serverKey = resolveServerKey(nil) + request:SetHTTPRequestHeaderValue("Content-Type", "application/json") + if #serverKey > 0 then + request:SetHTTPRequestHeaderValue("x-custom-key", serverKey) + end + request:SetHTTPRequestNetworkActivityTimeout(SERVER_CONFIG.NETWORK_TIMEOUT) + request:SetHTTPRequestAbsoluteTimeoutMS(SERVER_CONFIG.ABSOLUTE_TIMEOUT) +end +--- Устанавливает заголовки для длительных запросов (например, сохранение игры) +function ____exports.setApiHeadersLong(self, request) + local serverKey = resolveServerKey(nil) + request:SetHTTPRequestHeaderValue("Content-Type", "application/json") + if #serverKey > 0 then + request:SetHTTPRequestHeaderValue("x-custom-key", serverKey) + end + request:SetHTTPRequestNetworkActivityTimeout(SERVER_CONFIG.NETWORK_TIMEOUT * 3) + request:SetHTTPRequestAbsoluteTimeoutMS(SERVER_CONFIG.ABSOLUTE_TIMEOUT * 3) +end +--- Тело POST как JSON (единая точка; совпадает с заголовком Content-Type в setApiHeaders). +function ____exports.encodeApiBody(self, data) + return json.encode(data) +end +return ____exports diff --git a/scripts/vscripts/arsenal/arsenalcatalog.lua b/scripts/vscripts/arsenal/arsenalcatalog.lua new file mode 100644 index 0000000..23d9c9c --- /dev/null +++ b/scripts/vscripts/arsenal/arsenalcatalog.lua @@ -0,0 +1,197 @@ +--[[ Generated with https://github.com/TypeScriptToLua/TypeScriptToLua ]] +local ____exports = {} +--- Каталог арсенала: реестр всех экип-предметов и список играбельных героев. +-- +-- Раскладывается в две NetTable: +-- - "arsenal_catalog" / "items" → ArsenalItemDef[] +-- - "arsenal_catalog" / "heroes" → string[] (npc_dota_hero_*) +local DEFAULT_ITEM_ICON = "s2r://panorama/images/items/recipe_psd.vtex" +local ARSENAL_ICON = { + weapon = {ironBlade = "file://{images}/custom_game/arsenal/ironblade.png", stormEdge = "file://{images}/custom_game/arsenal/stormedge.png", sunfangSaber = "file://{images}/custom_game/arsenal/sunfangsaber.png"}, + armor = {chainMail = "file://{images}/custom_game/arsenal/chainmail.png", dragonPlate = "file://{images}/custom_game/arsenal/dragonplate.png", vanguardShell = "file://{images}/custom_game/arsenal/vanguardshell.png"}, + helmet = {leatherCap = "file://{images}/custom_game/arsenal/leathercap.png", arcaneCirclet = "file://{images}/custom_game/arsenal/arcanecirclet.png", warcrownMask = "file://{images}/custom_game/arsenal/warcrownmask.png"}, + boots = {swiftBoots = "file://{images}/custom_game/arsenal/swiftboots.png", phaseTreads = "file://{images}/custom_game/arsenal/phasetreads.png", hermesGreaves = "file://{images}/custom_game/arsenal/hermesgreaves.png"}, + necklace = {amberPendant = "file://{images}/custom_game/arsenal/amberpendant.png", soulLocket = "file://{images}/custom_game/arsenal/soullocket.png", prismChoker = "file://{images}/custom_game/arsenal/prismchoker.png"}, + ring = {copperBand = "file://{images}/custom_game/arsenal/copperband.png", voidSignet = "file://{images}/custom_game/arsenal/voidsignet.png", titanLoop = "file://{images}/custom_game/arsenal/titancircle.png"} +} +--- Перечень слотов в порядке отображения в UI. +____exports.ARSENAL_SLOTS = { + "weapon", + "armor", + "helmet", + "boots", + "necklace", + "ring" +} +--- Полный список экип-предметов. Эффекты — отдельные модификаторы в +-- `src/vscripts/arsenal/items//...` (имена согласованы с полем `modifier`). +-- +-- Это базовый набор-каркас (по 2 предмета на слот). Расширять отдельной задачей. +____exports.ARSENAL_ITEMS = { + { + itemName = "item_arsenal_weapon_iron_blade", + slot = "weapon", + quality = "epic", + modifier = "modifier_arsenal_weapon_iron_blade", + icon = ARSENAL_ICON.weapon.ironBlade + }, + { + itemName = "item_arsenal_weapon_storm_edge", + slot = "weapon", + quality = "epic", + modifier = "modifier_arsenal_weapon_storm_edge", + icon = ARSENAL_ICON.weapon.stormEdge + }, + { + itemName = "item_arsenal_weapon_sunfang_saber", + slot = "weapon", + quality = "epic", + modifier = "modifier_arsenal_weapon_storm_edge", + icon = ARSENAL_ICON.weapon.sunfangSaber + }, + { + itemName = "item_arsenal_armor_chain_mail", + slot = "armor", + quality = "epic", + modifier = "modifier_arsenal_armor_chain_mail", + icon = ARSENAL_ICON.armor.chainMail + }, + { + itemName = "item_arsenal_armor_dragon_plate", + slot = "armor", + quality = "epic", + modifier = "modifier_arsenal_armor_dragon_plate", + icon = ARSENAL_ICON.armor.dragonPlate + }, + { + itemName = "item_arsenal_armor_vanguard_shell", + slot = "armor", + quality = "epic", + modifier = "modifier_arsenal_armor_dragon_plate", + icon = ARSENAL_ICON.armor.vanguardShell + }, + { + itemName = "item_arsenal_helmet_leather_cap", + slot = "helmet", + quality = "rare", + modifier = "modifier_arsenal_helmet_leather_cap", + icon = ARSENAL_ICON.helmet.leatherCap + }, + { + itemName = "item_arsenal_helmet_arcane_circlet", + slot = "helmet", + quality = "rare", + modifier = "modifier_arsenal_helmet_arcane_circlet", + icon = ARSENAL_ICON.helmet.arcaneCirclet + }, + { + itemName = "item_arsenal_helmet_warcrown_mask", + slot = "helmet", + quality = "rare", + modifier = "modifier_arsenal_helmet_arcane_circlet", + icon = ARSENAL_ICON.helmet.warcrownMask + }, + { + itemName = "item_arsenal_boots_swift_boots", + slot = "boots", + quality = "legendary", + modifier = "modifier_arsenal_boots_swift_boots", + icon = ARSENAL_ICON.boots.swiftBoots + }, + { + itemName = "item_arsenal_boots_phase_treads", + slot = "boots", + quality = "legendary", + modifier = "modifier_arsenal_boots_phase_treads", + icon = ARSENAL_ICON.boots.phaseTreads + }, + { + itemName = "item_arsenal_boots_hermes_greaves", + slot = "boots", + quality = "legendary", + modifier = "modifier_arsenal_boots_phase_treads", + icon = ARSENAL_ICON.boots.hermesGreaves + }, + { + itemName = "item_arsenal_necklace_amber_pendant", + slot = "necklace", + quality = "legendary", + modifier = "modifier_arsenal_necklace_amber_pendant", + icon = ARSENAL_ICON.necklace.amberPendant + }, + { + itemName = "item_arsenal_necklace_soul_locket", + slot = "necklace", + quality = "legendary", + modifier = "modifier_arsenal_necklace_soul_locket", + icon = ARSENAL_ICON.necklace.soulLocket + }, + { + itemName = "item_arsenal_necklace_prism_choker", + slot = "necklace", + quality = "legendary", + modifier = "modifier_arsenal_necklace_soul_locket", + icon = ARSENAL_ICON.necklace.prismChoker + }, + { + itemName = "item_arsenal_ring_copper_band", + slot = "ring", + quality = "mythic", + modifier = "modifier_arsenal_ring_copper_band", + icon = ARSENAL_ICON.ring.copperBand + }, + { + itemName = "item_arsenal_ring_void_signet", + slot = "ring", + quality = "mythic", + modifier = "modifier_arsenal_ring_void_signet", + icon = ARSENAL_ICON.ring.voidSignet + }, + { + itemName = "item_arsenal_ring_titan_loop", + slot = "ring", + quality = "mythic", + modifier = "modifier_arsenal_ring_void_signet", + icon = ARSENAL_ICON.ring.titanLoop + } +} +--- Карта itemName → ArsenalItemDef (для быстрого поиска по имени). +____exports.ARSENAL_ITEMS_MAP = (function() + local map = {} + for ____, item in ipairs(____exports.ARSENAL_ITEMS) do + map[item.itemName] = item + end + return map +end)(nil) +--- Парс `scripts/npc/herolist.txt` → имена героев со значением `>0` или `-1` (включённые). +-- Тот же фильтр, что в `GameMode.getEnabledHeroesFromHeroList`. +function ____exports.getEnabledHeroes(self) + local rawKv = LoadKeyValues("scripts/npc/herolist.txt") + local ____temp_2 = rawKv and rawKv.CustomHeroList + if ____temp_2 == nil then + ____temp_2 = rawKv + end + local heroList = ____temp_2 + if not heroList then + return {} + end + local result = {} + for heroName in pairs(heroList) do + local value = tonumber(tostring(heroList[heroName])) or 0 + if value > 0 or value == -1 then + result[#result + 1] = tostring(heroName) + end + end + return result +end +--- Опубликовать каталог и список героев в NetTable. Вызывать один раз при старте сервера. +function ____exports.publishArsenalCatalog(self) + CustomNetTables:SetTableValue("arsenal_catalog", "items", ____exports.ARSENAL_ITEMS) + CustomNetTables:SetTableValue( + "arsenal_catalog", + "heroes", + {list = ____exports.getEnabledHeroes(nil)} + ) + CustomNetTables:SetTableValue("arsenal_catalog", "slots", {list = ____exports.ARSENAL_SLOTS}) +end +return ____exports diff --git a/scripts/vscripts/arsenal/arsenalmanager.lua b/scripts/vscripts/arsenal/arsenalmanager.lua new file mode 100644 index 0000000..8a0137b --- /dev/null +++ b/scripts/vscripts/arsenal/arsenalmanager.lua @@ -0,0 +1,1568 @@ +local ____lualib = require("lualib_bundle") +local __TS__Class = ____lualib.__TS__Class +local __TS__New = ____lualib.__TS__New +local __TS__StringTrim = ____lualib.__TS__StringTrim +local __TS__ObjectKeys = ____lualib.__TS__ObjectKeys +local __TS__NumberIsFinite = ____lualib.__TS__NumberIsFinite +local __TS__Delete = ____lualib.__TS__Delete +local __TS__ArraySort = ____lualib.__TS__ArraySort +local Set = ____lualib.Set +local __TS__ObjectAssign = ____lualib.__TS__ObjectAssign +local __TS__ArrayIsArray = ____lualib.__TS__ArrayIsArray +local __TS__Decorate = ____lualib.__TS__Decorate +local ____exports = {} +local ____tstl_2Dutils = require("lib.tstl-utils") +local reloadable = ____tstl_2Dutils.reloadable +local ____api_helper = require("api_helper") +local setApiHeaders = ____api_helper.setApiHeaders +local ____server_config = require("server_config") +local SERVER_CONFIG = ____server_config.SERVER_CONFIG +local ____ArsenalCatalog = require("arsenal.ArsenalCatalog") +local ARSENAL_ITEMS = ____ArsenalCatalog.ARSENAL_ITEMS +local ARSENAL_ITEMS_MAP = ____ArsenalCatalog.ARSENAL_ITEMS_MAP +local ARSENAL_SLOTS = ____ArsenalCatalog.ARSENAL_SLOTS +local publishArsenalCatalog = ____ArsenalCatalog.publishArsenalCatalog +local ____ArsenalStats = require("arsenal.ArsenalStats") +local createArsenalItemInstance = ____ArsenalStats.createArsenalItemInstance +local getEffectiveItemStats = ____ArsenalStats.getEffectiveItemStats +local ____ArsenalStatRuntime = require("arsenal.ArsenalStatRuntime") +local setArsenalTotalsProvider = ____ArsenalStatRuntime.setArsenalTotalsProvider +local ____store_manager = require("store_manager") +local StoreManager = ____store_manager.StoreManager +local ____real_lobby_player = require("utils.real_lobby_player") +local isRealLobbyPlayer = ____real_lobby_player.isRealLobbyPlayer +require("arsenal.items.weapon") +require("arsenal.items.armor") +require("arsenal.items.helmet") +require("arsenal.items.boots") +require("arsenal.items.necklace") +require("arsenal.items.ring") +require("arsenal.items.dynamic_stats") +local LOG_PREFIX = "[ArsenalManager]" +local ARSENAL_ROLL_VERSION = 2 +local ARSENAL_INVENTORY_PUT_DEBOUNCE = 0.12 +--- Включить тяжёлый лог пересчёта тоталов (json.encode на каждое изменение) — только для отладки. +local ARSENAL_VERBOSE_TOTALS_LOG = false +--- Максимум экземпляров предметов в инвентаре (синхрон с UI `MIN_CATALOG_CELLS` и PUT API). +local ARSENAL_INVENTORY_MAX_INSTANCES = 207 +____exports.ArsenalManagerClass = __TS__Class() +local ArsenalManagerClass = ____exports.ArsenalManagerClass +ArsenalManagerClass.name = "ArsenalManagerClass" +ArsenalManagerClass.____file_path = "scripts/vscripts/arsenal/ArsenalManager.lua" +function ArsenalManagerClass.prototype.____constructor(self) + self.loadouts = {} + self.instances = {} + self.selectedHero = {} + self.appliedFor = {} + self.lastPrintedTotals = {} + self.inventorySaveGeneration = {} +end +function ArsenalManagerClass.getInstance(self) + if not ____exports.ArsenalManagerClass._instance then + ____exports.ArsenalManagerClass._instance = __TS__New(____exports.ArsenalManagerClass) + end + return ____exports.ArsenalManagerClass._instance +end +function ArsenalManagerClass.prototype.initialize(self) + publishArsenalCatalog(nil) + setArsenalTotalsProvider( + nil, + function(____, hero) return self:getHeroStatTotals(hero) end + ) + self:registerListeners() + print(LOG_PREFIX .. " initialized") +end +function ArsenalManagerClass.prototype.registerListeners(self) + CustomGameEventManager:RegisterListener( + "arsenal_equip_item", + function(_src, data) + local playerId = self:resolvePlayerId(data) + if playerId == nil then + return + end + self:handleEquip( + playerId, + data.hero, + data.slot, + tostring(data.instanceId or "") + ) + end + ) + CustomGameEventManager:RegisterListener( + "arsenal_unequip_item", + function(_src, data) + local playerId = self:resolvePlayerId(data) + if playerId == nil then + return + end + self:handleUnequip(playerId, data.hero, data.slot) + end + ) + CustomGameEventManager:RegisterListener( + "arsenal_select_hero", + function(_src, data) + local playerId = self:resolvePlayerId(data) + if playerId == nil then + return + end + self.selectedHero[playerId] = tostring(data.hero or "") + self:syncToClient(playerId) + if self.selectedHero[playerId] and #self.selectedHero[playerId] > 0 then + self:refreshHeroArsenalStats(playerId, self.selectedHero[playerId]) + self:scheduleArsenalStatRefresh(playerId) + end + end + ) + CustomGameEventManager:RegisterListener( + "arsenal_request_sync", + function(_src, data) + local playerId = self:resolvePlayerId(data) + if playerId == nil then + return + end + self:loadFromServer(playerId) + end + ) + CustomGameEventManager:RegisterListener( + "arsenal_upgrade_item", + function(_src, data) + local playerId = self:resolvePlayerId(data) + if playerId == nil then + return + end + self:handleUpgradeItem( + playerId, + tostring(data.instanceId or "") + ) + end + ) + CustomGameEventManager:RegisterListener( + "arsenal_toggle_pin", + function(_src, data) + local playerId = self:resolvePlayerId(data) + if playerId == nil then + return + end + self:handleTogglePin( + playerId, + tostring(data.instanceId or "") + ) + end + ) + CustomGameEventManager:RegisterListener( + "arsenal_toggle_favorite", + function(_src, data) + local playerId = self:resolvePlayerId(data) + if playerId == nil then + return + end + self:handleToggleFavorite( + playerId, + tostring(data.instanceId or "") + ) + end + ) + CustomGameEventManager:RegisterListener( + "arsenal_disassemble_item", + function(_src, data) + local playerId = self:resolvePlayerId(data) + if playerId == nil then + return + end + self:handleDisassembleItem( + playerId, + tostring(data.instanceId or "") + ) + end + ) + ListenToGameEvent( + "game_rules_state_change", + function() + if not IsServer() then + return + end + local s = GameRules:State_Get() + if s == DOTA_GAMERULES_STATE_PRE_GAME or s == DOTA_GAMERULES_STATE_GAME_IN_PROGRESS then + do + local pid = 0 + while pid < DOTA_MAX_PLAYERS do + do + if not isRealLobbyPlayer(nil, pid) then + goto __continue28 + end + self:refreshHeroArsenalStats(pid, "") + self:scheduleArsenalStatRefresh(pid) + end + ::__continue28:: + pid = pid + 1 + end + end + end + end, + nil + ) +end +function ArsenalManagerClass.prototype.resolvePlayerId(self, data) + local ____opt_result_2 + if data ~= nil then + ____opt_result_2 = data.PlayerID + end + local ____opt_result_2_6 = ____opt_result_2 + if ____opt_result_2_6 == nil then + local ____opt_result_5 + if data ~= nil then + ____opt_result_5 = data.playerId + end + ____opt_result_2_6 = ____opt_result_5 + end + local ____opt_result_2_6_10 = ____opt_result_2_6 + if ____opt_result_2_6_10 == nil then + local ____opt_result_9 + if data ~= nil then + ____opt_result_9 = data.playerID + end + ____opt_result_2_6_10 = ____opt_result_9 + end + local raw = ____opt_result_2_6_10 + if raw == nil or raw == nil then + return nil + end + local n = tonumber(tostring(raw)) + if n == nil or n < 0 then + return nil + end + return n +end +function ArsenalManagerClass.prototype.normalizeArsenalStatKey(self, rawKey) + local key = string.lower(tostring(rawKey or "")) + local alias = { + battle_level = "battle_level", + bonus_damage = "bonus_damage", + attack_speed = "attack_speed", + bonus_armor = "bonus_armor", + max_health = "max_health", + max_mana = "max_mana", + health_regen = "health_regen", + mana_regen = "mana_regen", + bonus_health_regen = "health_regen", + bonus_mana_regen = "mana_regen", + hp_regen = "health_regen", + mp_regen = "mana_regen", + move_speed = "move_speed", + magic_resist = "magic_resist", + spell_amp = "spell_amp", + all_stats = "all_stats", + all_stats_pct = "all_stats_pct", + bonus_strength = "bonus_strength", + bonus_agility = "bonus_agility", + bonus_intellect = "bonus_intellect", + strength_pct = "strength_pct", + agility_pct = "agility_pct", + intellect_pct = "intellect_pct", + luck = "luck", + outgoing_damage_pct = "outgoing_damage_pct", + outgoing_damage = "outgoing_damage_pct", + damage_out_pct = "outgoing_damage_pct", + damage_outgoing_pct = "outgoing_damage_pct", + bonus_outgoing_damage_pct = "outgoing_damage_pct", + incoming_damage_reduction_pct = "incoming_damage_reduction_pct", + incoming_reduction_pct = "incoming_damage_reduction_pct", + damage_reduction_pct = "incoming_damage_reduction_pct", + attack_speed_pct = "attack_speed_pct", + attackspeed_pct = "attack_speed_pct", + attack_speed_percent = "attack_speed_pct", + move_speed_pct = "move_speed_pct", + movespeed_pct = "move_speed_pct", + move_speed_percent = "move_speed_pct", + base_damage_pct = "base_damage_pct", + bonus_damage_pct = "base_damage_pct", + attack_damage_pct = "base_damage_pct", + crit_mult = "crit_mult", + crit_multiplier = "crit_mult", + critical_multiplier = "crit_mult", + damage = "bonus_damage", + bonus_attack_damage = "bonus_damage", + attack_damage = "bonus_damage", + attackspeed = "attack_speed", + attack_speed_bonus = "attack_speed", + armor = "bonus_armor", + armor_bonus = "bonus_armor", + physical_armor = "bonus_armor", + health = "max_health", + bonus_health = "max_health", + mana = "max_mana", + bonus_mana = "max_mana", + movespeed = "move_speed", + movespeed_bonus = "move_speed", + move_speed_bonus = "move_speed", + magic_resistance = "magic_resist", + magical_resistance = "magic_resist", + magic_resistance_bonus = "magic_resist", + spell_amplify = "spell_amp", + spell_amplification = "spell_amp", + stats_all = "all_stats", + all_attributes_pct = "all_stats_pct", + attributes_pct = "all_stats_pct", + stats_pct = "all_stats_pct", + strength = "bonus_strength", + bonus_str = "bonus_strength", + str_bonus = "bonus_strength", + agility = "bonus_agility", + bonus_agi = "bonus_agility", + agi_bonus = "bonus_agility", + intellect = "bonus_intellect", + bonus_int = "bonus_intellect", + int_bonus = "bonus_intellect", + str_pct = "strength_pct", + strength_percent = "strength_pct", + bonus_strength_pct = "strength_pct", + agi_pct = "agility_pct", + agility_percent = "agility_pct", + bonus_agility_pct = "agility_pct", + int_pct = "intellect_pct", + intellect_percent = "intellect_pct", + bonus_intellect_pct = "intellect_pct" + } + return alias[key] +end +function ArsenalManagerClass.prototype.refreshHeroArsenalStats(self, playerId, _heroName) + local player = PlayerResource:GetPlayer(playerId) + if not player then + return + end + local assigned = player.GetAssignedHero and player:GetAssignedHero() + local hero = assigned and IsValidEntity(assigned) and assigned:IsRealHero() and assigned or PlayerResource:GetSelectedHeroEntity(playerId) + if not hero or not IsValidEntity(hero) or not hero:IsRealHero() then + return + end + local heroMemo = hero + heroMemo.__arsenalHeroStatTotalsMemoTime = nil + heroMemo.__arsenalHeroStatTotalsMemo = nil + if not hero:HasModifier(____exports.ArsenalManagerClass.DYNAMIC_MODIFIER_NAME) then + hero:AddNewModifier( + hero, + getModifierSourceAbility(nil, hero), + ____exports.ArsenalManagerClass.DYNAMIC_MODIFIER_NAME, + {} + ) + end + local mod = hero:FindModifierByName(____exports.ArsenalManagerClass.DYNAMIC_MODIFIER_NAME) + if mod then + mod:ForceRefresh() + end + hero:CalculateStatBonus(true) +end +function ArsenalManagerClass.prototype.scheduleArsenalStatRefresh(self, playerId) + if not IsServer() then + return + end + local function run() + self:refreshHeroArsenalStats(playerId, "") + return nil + end + Timers:CreateTimer(0, run) + Timers:CreateTimer(0.15, run) +end +function ArsenalManagerClass.prototype.isArsenalEditPhase(self) + return GameRules:State_Get() == DOTA_GAMERULES_STATE_CUSTOM_GAME_SETUP +end +function ArsenalManagerClass.prototype.sendError(self, playerId, token) + local player = PlayerResource:GetPlayer(playerId) + if not player then + return + end + CustomGameEventManager:Send_ServerToPlayer(player, "create_error_message", {message = token}) +end +function ArsenalManagerClass.prototype.ensureInstances(self, playerId) + if not self.instances[playerId] then + self.instances[playerId] = {} + end + return self.instances[playerId] +end +function ArsenalManagerClass.prototype.getInstance(self, playerId, instanceId) + local bag = self.instances[playerId] + if not bag then + return nil + end + local direct = bag[instanceId] + if direct ~= nil then + return direct + end + for key in pairs(bag) do + do + local row = bag[key] + if not row then + goto __continue50 + end + if row.instanceId == instanceId or row.instance_id == instanceId then + return row + end + end + ::__continue50:: + end + return nil +end +function ArsenalManagerClass.prototype.countStatLines(self, stats) + if not stats or type(stats) ~= "table" then + return 0 + end + local n = 0 + local row = stats + for k in pairs(row) do + local idx = tonumber(k) + if idx ~= nil and idx > 0 and row[k] ~= nil then + n = n + 1 + end + end + return n +end +function ArsenalManagerClass.prototype.getInstanceByCatalogItem(self, playerId, itemName) + local bag = self.instances[playerId] + if not bag then + return nil + end + for id in pairs(bag) do + local inst = bag[id] + if inst ~= nil and inst.itemName == itemName then + return inst + end + end + return nil +end +function ArsenalManagerClass.prototype.nextSerialForPlayer(self, playerId) + local maxS = 0 + local bag = self.instances[playerId] + if bag ~= nil then + for id in pairs(bag) do + local ____opt_11 = bag[id] + local s = ____opt_11 and ____opt_11.serial or 0 + if s > maxS then + maxS = s + end + end + end + return maxS + 1 +end +function ArsenalManagerClass.prototype.ensureInstanceIdentityMeta(self, _playerId, _inst) + return false +end +function ArsenalManagerClass.prototype.resolveInventoryOwnerDisplayName(self, playerId) + local pid = playerId + if not PlayerResource:IsValidPlayerID(pid) then + return "Unknown" + end + local nm = PlayerResource:GetPlayerName(pid) + local s = __TS__StringTrim(tostring(nm or "")) + return #s > 0 and s or "Unknown" +end +function ArsenalManagerClass.prototype.createNewInstance(self, playerId, itemName, rollQuality, rollContext) + local def = ARSENAL_ITEMS_MAP[itemName] + if not def then + return nil + end + local quality = rollQuality or def.quality + local bag = self:ensureInstances(playerId) + if #__TS__ObjectKeys(bag) >= ARSENAL_INVENTORY_MAX_INSTANCES then + print((((LOG_PREFIX .. " createNewInstance: inventory cap ") .. tostring(ARSENAL_INVENTORY_MAX_INSTANCES)) .. " player=") .. tostring(playerId)) + return nil + end + local instanceId = "ars_" .. DoUniqueString("i") + local serial = self:nextSerialForPlayer(playerId) + local created = createArsenalItemInstance( + nil, + instanceId, + serial, + itemName, + quality, + rollContext + ) + self:setRollVersion(created, ARSENAL_ROLL_VERSION) + created.ownerName = self:resolveInventoryOwnerDisplayName(playerId) + bag[instanceId] = created + return instanceId +end +function ArsenalManagerClass.prototype.repairBrokenInstance(self, playerId, instanceId) + local ____opt_13 = self.instances[playerId] + local inst = ____opt_13 and ____opt_13[instanceId] + if not inst then + return false + end + if self:countStatLines(inst.stats) >= 7 then + return false + end + local def = ARSENAL_ITEMS_MAP[inst.itemName] + if not def then + return false + end + local oldFlags = inst + local wasPinned = not not oldFlags.pinned + local wasFavorite = not not oldFlags.favorite + local rerolled = createArsenalItemInstance( + nil, + instanceId, + inst.serial, + inst.itemName, + inst.quality or def.quality + ) + rerolled.upgradeLevel = math.max( + 0, + math.min( + 5, + math.floor(inst.upgradeLevel or 0) + ) + ) + if wasPinned then + rerolled.pinned = true + end + if wasFavorite then + rerolled.favorite = true + end + self:setRollVersion(rerolled, ARSENAL_ROLL_VERSION) + local oldMeta = inst + rerolled.globalSerial = oldMeta.globalSerial + rerolled.ownerName = oldMeta.ownerName + self:ensureInstances(playerId)[instanceId] = rerolled + return true +end +function ArsenalManagerClass.prototype.normalizePinned(self, inst) + local raw = inst.pinned + inst.pinned = raw == true or raw == 1 or raw == "1" +end +function ArsenalManagerClass.prototype.normalizeFavorite(self, inst) + local raw = inst.favorite + inst.favorite = raw == true or raw == 1 or raw == "1" +end +function ArsenalManagerClass.prototype.getRollVersion(self, inst) + local raw = inst.rollVersion + if type(raw) ~= "number" or not __TS__NumberIsFinite(raw) then + return 0 + end + return math.floor(raw) +end +function ArsenalManagerClass.prototype.setRollVersion(self, inst, version) + inst.rollVersion = version +end +function ArsenalManagerClass.prototype.rerollInstanceForCurrentVersion(self, playerId, instanceId) + local ____opt_15 = self.instances[playerId] + local inst = ____opt_15 and ____opt_15[instanceId] + if not inst then + return false + end + if self:getRollVersion(inst) >= ARSENAL_ROLL_VERSION then + return false + end + local def = ARSENAL_ITEMS_MAP[inst.itemName] + if not def then + return false + end + local oldFlags = inst + local wasPinned = not not oldFlags.pinned + local wasFavorite = not not oldFlags.favorite + local rerolled = createArsenalItemInstance( + nil, + instanceId, + inst.serial, + inst.itemName, + inst.quality or def.quality + ) + rerolled.upgradeLevel = math.max( + 0, + math.min( + 5, + math.floor(inst.upgradeLevel or 0) + ) + ) + if wasPinned then + rerolled.pinned = true + end + if wasFavorite then + rerolled.favorite = true + end + self:setRollVersion(rerolled, ARSENAL_ROLL_VERSION) + local oldMeta = inst + rerolled.globalSerial = oldMeta.globalSerial + rerolled.ownerName = oldMeta.ownerName + self:ensureInstances(playerId)[instanceId] = rerolled + return true +end +function ArsenalManagerClass.prototype.normalizeInventoryFlags(self, playerId) + local bag = self.instances[playerId] + if not bag then + return + end + for id in pairs(bag) do + local inst = bag[id] + if inst ~= nil then + self:normalizePinned(inst) + self:normalizeFavorite(inst) + if not not inst.favorite then + inst.pinned = true + end + end + end +end +function ArsenalManagerClass.prototype.debugPrintInventoryOwnerMeta(self, playerId) + local bag = self.instances[playerId] + if not bag then + print(((LOG_PREFIX .. " owner-meta player=") .. tostring(playerId)) .. ": inventory empty") + return + end + for instanceId in pairs(bag) do + local inst = bag[instanceId] + local ownerName = tostring(inst and inst.ownerName or "") + local globalSerial = inst and inst.globalSerial or 0 + local markMissing = (not ownerName or #ownerName <= 0 or ownerName == "Unknown") and "MISSING_OWNER" or "OK" + print((((((((((((LOG_PREFIX .. " owner-meta player=") .. tostring(playerId)) .. " instance=") .. instanceId) .. " item=") .. (inst and inst.itemName or "?")) .. " globalSerial=") .. tostring(globalSerial)) .. " ownerName=\"") .. ownerName) .. "\" status=") .. markMissing) + end +end +function ArsenalManagerClass.prototype.getArsenalUpgradeCost(self, quality, currentLevelBeforeUpgrade) + local lv = math.max( + 0, + math.min( + 4, + math.floor(currentLevelBeforeUpgrade) + ) + ) + local commonCurve = { + 100, + 200, + 400, + 800, + 1600 + } + local rarityMult = { + common = 1, + rare = 2, + epic = 4, + legendary = 8, + mythic = 16 + } + local base = commonCurve[lv + 1] or commonCurve[1] + local mult = rarityMult[quality] or 1 + return base * mult +end +function ArsenalManagerClass.prototype.getDisassembleShardReward(self, quality, upgradeLevel) + local base = { + common = 15, + rare = 35, + epic = 70, + legendary = 120, + mythic = 200 + } + local b = base[quality] or 15 + local lv = math.max( + 0, + math.min( + 5, + math.floor(upgradeLevel) + ) + ) + return b + lv * 20 +end +function ArsenalManagerClass.prototype.isInstanceEquippedInAnyLoadout(self, playerId, instanceId) + local all = self.loadouts[playerId] + if not all then + return false + end + for heroName in pairs(all) do + do + local hl = all[heroName] + if not hl then + goto __continue106 + end + for ____, slot in ipairs(ARSENAL_SLOTS) do + if hl[slot] == instanceId then + return true + end + end + end + ::__continue106:: + end + return false +end +function ArsenalManagerClass.prototype.removeInstanceFromAllLoadouts(self, playerId, instanceId) + local all = self.loadouts[playerId] + if not all then + return + end + for heroName in pairs(all) do + do + local hl = all[heroName] + if not hl then + goto __continue114 + end + for ____, slot in ipairs(ARSENAL_SLOTS) do + if hl[slot] == instanceId then + __TS__Delete(hl, slot) + end + end + end + ::__continue114:: + end +end +function ArsenalManagerClass.prototype.removeInstanceFromHeroLoadoutExceptSlot(self, playerId, heroName, keepSlot, instanceId) + local ____opt_23 = self.loadouts[playerId] + local hl = ____opt_23 and ____opt_23[heroName] + if not hl then + return + end + for ____, slot in ipairs(ARSENAL_SLOTS) do + do + if slot == keepSlot then + goto __continue122 + end + if hl[slot] == instanceId then + __TS__Delete(hl, slot) + end + end + ::__continue122:: + end +end +function ArsenalManagerClass.prototype.migrateLoadoutsFromLegacyItemNames(self, playerId) + local all = self.loadouts[playerId] + if not all then + return + end + for heroName in pairs(all) do + do + local hl = all[heroName] + if not hl then + goto __continue128 + end + for ____, slot in ipairs(ARSENAL_SLOTS) do + do + local v = hl[slot] + if not v then + goto __continue130 + end + if self:getInstance(playerId, v) then + goto __continue130 + end + if ARSENAL_ITEMS_MAP[v] then + local inst = self:getInstanceByCatalogItem(playerId, v) + if inst then + hl[slot] = inst.instanceId + end + end + end + ::__continue130:: + end + end + ::__continue128:: + end +end +function ArsenalManagerClass.prototype.ingestInventoryPayload(self, playerId, payload) + if not payload or type(payload) ~= "table" then + self.instances[playerId] = {} + return + end + if payload.instances and type(payload.instances) == "table" then + local raw = payload.instances + local deduped = {} + for rawKey in pairs(raw) do + do + local row = raw[rawKey] + if not row or type(row) ~= "table" then + goto __continue140 + end + local ____tostring_27 = tostring + local ____row_instanceId_25 = row.instanceId + if ____row_instanceId_25 == nil then + ____row_instanceId_25 = row.instance_id + end + local ____row_instanceId_25_26 = ____row_instanceId_25 + if ____row_instanceId_25_26 == nil then + ____row_instanceId_25_26 = rawKey + end + local canonicalId = ____tostring_27(____row_instanceId_25_26) + if not canonicalId or #canonicalId <= 0 then + goto __continue140 + end + local normalizedRow = row + normalizedRow.instanceId = canonicalId + deduped[canonicalId] = normalizedRow + end + ::__continue140:: + end + self:trimInstancesBagToMax(deduped, ARSENAL_INVENTORY_MAX_INSTANCES) + self.instances[playerId] = deduped + return + end + self.instances[playerId] = {} +end +function ArsenalManagerClass.prototype.countCatalogInstances(self, playerId) + local n = 0 + local bag = self.instances[playerId] + if not bag then + return 0 + end + for id in pairs(bag) do + local it = bag[id] + if it ~= nil and ARSENAL_ITEMS_MAP[it.itemName] ~= nil then + n = n + 1 + end + end + return n +end +function ArsenalManagerClass.prototype.trimInstancesBagToMax(self, bag, max) + local keys = __TS__ObjectKeys(bag) + if #keys <= max then + return + end + __TS__ArraySort( + keys, + function(____, a, b) + local ____opt_28 = bag[a] + local sa = ____opt_28 and ____opt_28.serial or 0 + local ____opt_30 = bag[b] + local sb = ____opt_30 and ____opt_30.serial or 0 + return sb - sa + end + ) + do + local i = max + while i < #keys do + local k = keys[i + 1] + if k ~= nil then + __TS__Delete(bag, k) + end + i = i + 1 + end + end + print(((((LOG_PREFIX .. " trimInstancesBagToMax: removed ") .. tostring(#keys - max)) .. " oldest instances (cap=") .. tostring(max)) .. ")") +end +function ArsenalManagerClass.prototype.handleEquip(self, playerId, heroName, slot, instanceId) + if not self:isArsenalEditPhase() then + self:sendError(playerId, "#arsenal_locked_in_game") + return + end + if not heroName or not slot or not instanceId then + print((((((((LOG_PREFIX .. " equip: bad args player=") .. tostring(playerId)) .. " hero=") .. heroName) .. " slot=") .. slot) .. " id=") .. instanceId) + return + end + local inst = self:getInstance(playerId, instanceId) + if not inst then + self:sendError(playerId, "#arsenal_item_not_owned") + return + end + local def = ARSENAL_ITEMS_MAP[inst.itemName] + if not def or def.slot ~= slot then + print((((LOG_PREFIX .. " equip: slot mismatch item=") .. inst.itemName) .. " slot=") .. slot) + return + end + self:removeInstanceFromHeroLoadoutExceptSlot(playerId, heroName, slot, instanceId) + if not self.loadouts[playerId] then + self.loadouts[playerId] = {} + end + if not self.loadouts[playerId][heroName] then + self.loadouts[playerId][heroName] = {} + end + self.loadouts[playerId][heroName][slot] = instanceId + self:syncToClient(playerId) + self:saveLoadoutsToServer(playerId) + self:refreshHeroArsenalStats(playerId, heroName) + self:scheduleArsenalStatRefresh(playerId) + print((((((((LOG_PREFIX .. " equip player=") .. tostring(playerId)) .. " hero=") .. heroName) .. " slot=") .. slot) .. " instance=") .. instanceId) +end +function ArsenalManagerClass.prototype.handleUnequip(self, playerId, heroName, slot) + if not self:isArsenalEditPhase() then + self:sendError(playerId, "#arsenal_locked_in_game") + return + end + if not heroName or not slot then + return + end + local ____opt_32 = self.loadouts[playerId] + local heroLoadout = ____opt_32 and ____opt_32[heroName] + if not heroLoadout or heroLoadout[slot] == nil then + return + end + __TS__Delete(heroLoadout, slot) + self:syncToClient(playerId) + self:saveLoadoutsToServer(playerId) + self:refreshHeroArsenalStats(playerId, heroName) + self:scheduleArsenalStatRefresh(playerId) + print((((((LOG_PREFIX .. " unequip player=") .. tostring(playerId)) .. " hero=") .. heroName) .. " slot=") .. slot) +end +function ArsenalManagerClass.prototype.handleUpgradeItem(self, playerId, instanceId) + if not instanceId then + return + end + if not self:isArsenalEditPhase() then + self:sendError(playerId, "#arsenal_locked_in_game") + return + end + local inst = self:getInstance(playerId, instanceId) + if not inst then + self:sendError(playerId, "#arsenal_item_not_owned") + return + end + if inst.upgradeLevel >= 5 then + return + end + local cost = self:getArsenalUpgradeCost(inst.quality, inst.upgradeLevel) + local store = StoreManager:getInstance() + if cost > 0 then + if store:getDustCurrency(playerId) < cost then + self:sendError(playerId, "Недостаточно пыли для улучшения") + return + end + if not store:removeDustCurrency(playerId, cost) then + self:sendError(playerId, "Недостаточно пыли для улучшения") + return + end + store:saveCurrencyToServer(playerId) + end + inst.upgradeLevel = inst.upgradeLevel + 1 + self:syncToClient(playerId) + self:saveInventoryToServer(playerId) + local needStats = false + for heroName in pairs(self.loadouts[playerId] or ({})) do + do + local loadout = self.loadouts[playerId][heroName] + if not loadout then + goto __continue173 + end + for ____, slot in ipairs(ARSENAL_SLOTS) do + if loadout[slot] == instanceId then + needStats = true + break + end + end + if needStats then + break + end + end + ::__continue173:: + end + if needStats then + self:refreshHeroArsenalStats(playerId, "") + self:scheduleArsenalStatRefresh(playerId) + end + print((((((((LOG_PREFIX .. " upgrade player=") .. tostring(playerId)) .. " instance=") .. instanceId) .. " level=") .. tostring(inst.upgradeLevel)) .. " cost=") .. tostring(cost)) +end +function ArsenalManagerClass.prototype.handleTogglePin(self, playerId, instanceId) + if not instanceId then + return + end + local inst = self:getInstance(playerId, instanceId) + if not inst then + self:sendError(playerId, "#arsenal_item_not_owned") + return + end + local cur = not not inst.pinned + inst.pinned = not cur + self:syncToClient(playerId) + self:saveInventoryToServer(playerId) + print((((((LOG_PREFIX .. " pin=") .. tostring(not cur)) .. " player=") .. tostring(playerId)) .. " instance=") .. instanceId) +end +function ArsenalManagerClass.prototype.handleToggleFavorite(self, playerId, instanceId) + if not instanceId then + return + end + local inst = self:getInstance(playerId, instanceId) + if not inst then + self:sendError(playerId, "#arsenal_item_not_owned") + return + end + local cur = not not inst.favorite + local next = not cur + inst.favorite = next + if next then + inst.pinned = true + end + self:syncToClient(playerId) + self:saveInventoryToServer(playerId) + print((((((((LOG_PREFIX .. " favorite=") .. tostring(next)) .. " pin=") .. tostring(not not inst.pinned)) .. " player=") .. tostring(playerId)) .. " instance=") .. instanceId) +end +function ArsenalManagerClass.prototype.handleDisassembleItem(self, playerId, instanceId) + if not instanceId then + return + end + if not self:isArsenalEditPhase() then + self:sendError(playerId, "#arsenal_locked_in_game") + return + end + local inst = self:getInstance(playerId, instanceId) + if not inst then + self:sendError(playerId, "#arsenal_item_not_owned") + return + end + if not not inst.pinned then + self:sendError(playerId, "#arsenal_disassemble_pinned") + return + end + if self:isInstanceEquippedInAnyLoadout(playerId, instanceId) then + self:sendError(playerId, "#arsenal_disassemble_equipped") + return + end + local shards = self:getDisassembleShardReward(inst.quality, inst.upgradeLevel or 0) + __TS__Delete( + self:ensureInstances(playerId), + instanceId + ) + self:removeInstanceFromAllLoadouts(playerId, instanceId) + StoreManager:getInstance():addDustCurrency(playerId, shards) + self:syncToClient(playerId) + self:saveInventoryToServer(playerId) + self:saveLoadoutsToServer(playerId) + self:refreshHeroArsenalStats(playerId, "") + self:scheduleArsenalStatRefresh(playerId) + print((((((LOG_PREFIX .. " disassemble player=") .. tostring(playerId)) .. " instance=") .. instanceId) .. " shards=") .. tostring(shards)) +end +function ArsenalManagerClass.prototype.applyLoadout(self, player, hero) + if not player or not hero then + return + end + local playerId = player:GetPlayerID() + if playerId < 0 then + return + end + local heroName = hero:GetUnitName() + local ____opt_34 = self.loadouts[playerId] + local heroLoadout = ____opt_34 and ____opt_34[heroName] + if not heroLoadout then + return + end + for ____, slot in ipairs(ARSENAL_SLOTS) do + do + local instanceId = heroLoadout[slot] + if not instanceId then + goto __continue198 + end + if not self:getInstance(playerId, instanceId) then + goto __continue198 + end + end + ::__continue198:: + end + if not hero:HasModifier(____exports.ArsenalManagerClass.DYNAMIC_MODIFIER_NAME) then + hero:AddNewModifier( + hero, + getModifierSourceAbility(nil, hero), + ____exports.ArsenalManagerClass.DYNAMIC_MODIFIER_NAME, + {} + ) + end + local mod = hero:FindModifierByName(____exports.ArsenalManagerClass.DYNAMIC_MODIFIER_NAME) + if mod then + mod:ForceRefresh() + end + self:scheduleArsenalStatRefresh(playerId) + if not self.appliedFor[playerId] then + self.appliedFor[playerId] = __TS__New(Set) + end + self.appliedFor[playerId]:add(heroName) + print((((LOG_PREFIX .. " applyLoadout player=") .. tostring(playerId)) .. " hero=") .. heroName) +end +function ArsenalManagerClass.prototype.clearLoadout(self, player, hero) + if not player or not hero then + return + end + local playerId = player:GetPlayerID() + if playerId < 0 then + return + end + local heroName = hero:GetUnitName() + local ____opt_36 = self.loadouts[playerId] + local heroLoadout = ____opt_36 and ____opt_36[heroName] + if not heroLoadout then + return + end + hero:RemoveModifierByName(____exports.ArsenalManagerClass.DYNAMIC_MODIFIER_NAME) + local ____opt_38 = self.appliedFor[playerId] + if ____opt_38 ~= nil then + ____opt_38:delete(heroName) + end + if self.lastPrintedTotals[playerId] then + self.lastPrintedTotals[playerId][heroName] = "" + end +end +function ArsenalManagerClass.prototype.tryGrantStarterPackIfEmpty(self, playerId) + local changed = false + for ____, def in ipairs(ARSENAL_ITEMS) do + if not self:getInstanceByCatalogItem(playerId, def.itemName) then + changed = self:createNewInstance(playerId, def.itemName) ~= nil or changed + end + end + local bag = self.instances[playerId] + if bag ~= nil then + for instanceId in pairs(bag) do + changed = self:repairBrokenInstance(playerId, instanceId) or changed + end + end + if not changed then + print(((LOG_PREFIX .. " starter/test grant skipped: player=") .. tostring(playerId)) .. ", inventory already valid") + return + end + self:syncToClient(playerId) + self:saveInventoryToServer(playerId) + print((LOG_PREFIX .. " starter/test grant applied player=") .. tostring(playerId)) +end +function ArsenalManagerClass.prototype.regenerateAllCatalogItemsForTesting(self, playerId) + self.instances[playerId] = {} + self.loadouts[playerId] = {} + self.appliedFor[playerId] = __TS__New(Set) + for ____, def in ipairs(ARSENAL_ITEMS) do + self:createNewInstance(playerId, def.itemName) + end + self:syncToClient(playerId) + self:saveInventoryToServer(playerId) + self:saveLoadoutsToServer(playerId) + print((LOG_PREFIX .. " regenerated full random arsenal for player=") .. tostring(playerId)) +end +function ArsenalManagerClass.prototype.grantArsenalItem(self, playerId, itemName, count) + if count == nil then + count = 1 + end + if not ARSENAL_ITEMS_MAP[itemName] then + print((LOG_PREFIX .. " grantArsenalItem: unknown item ") .. itemName) + return false + end + local n = math.max( + 1, + math.floor(count) + ) + do + local i = 0 + while i < n do + self:createNewInstance(playerId, itemName, nil) + i = i + 1 + end + end + self:syncToClient(playerId) + self:saveInventoryToServer(playerId) + print((((((LOG_PREFIX .. " grant player=") .. tostring(playerId)) .. " item=") .. itemName) .. " x") .. tostring(n)) + return true +end +function ArsenalManagerClass.prototype.grantArsenalItemDetailed(self, playerId, itemName, count, rollQuality, rollContext, options) + if count == nil then + count = 1 + end + if not ARSENAL_ITEMS_MAP[itemName] then + print((LOG_PREFIX .. " grantArsenalItemDetailed: unknown item ") .. itemName) + return {} + end + local n = math.max( + 1, + math.floor(count) + ) + local created = {} + do + local i = 0 + while i < n do + do + local instanceId = self:createNewInstance(playerId, itemName, rollQuality, rollContext) + if not instanceId then + goto __continue226 + end + local inst = self:getInstance(playerId, instanceId) + local defQ = ARSENAL_ITEMS_MAP[itemName].quality + created[#created + 1] = {instanceId = instanceId, itemName = itemName, quality = inst and inst.quality or rollQuality or defQ} + end + ::__continue226:: + i = i + 1 + end + end + if not (options and options.deferPersistence) then + self:syncToClient(playerId) + self:saveInventoryToServer(playerId) + end + print((((((LOG_PREFIX .. " grant detailed player=") .. tostring(playerId)) .. " item=") .. itemName) .. " x=") .. tostring(#created)) + return created +end +function ArsenalManagerClass.prototype.flushArsenalInventoryToClientAndApi(self, playerId) + local pid = playerId + self.inventorySaveGeneration[pid] = (self.inventorySaveGeneration[pid] or 0) + 1 + self:syncToClient(pid) + self:sendInventoryPutRequest(pid) +end +function ArsenalManagerClass.prototype.getOwnedCount(self, playerId, itemName) + local bag = self.instances[playerId] + if not bag then + return 0 + end + local n = 0 + for id in pairs(bag) do + local it = bag[id] + if it ~= nil and it.itemName == itemName then + n = n + 1 + end + end + return n +end +function ArsenalManagerClass.prototype.getHeroLoadout(self, playerId, heroName) + local ____opt_44 = self.loadouts[playerId] + return ____opt_44 and ____opt_44[heroName] or ({}) +end +function ArsenalManagerClass.prototype.getInventoryInstance(self, playerId, instanceId) + return self:getInstance(playerId, instanceId) +end +function ArsenalManagerClass.prototype.isInstanceTradeLocked(self, playerId, instanceId) + local inst = self:getInstance(playerId, instanceId) + if not inst then + return true + end + local rawPinned = inst.pinned + local rawFavorite = inst.favorite + local pinned = rawPinned == true or rawPinned == 1 or rawPinned == "1" + local favorite = rawFavorite == true or rawFavorite == 1 or rawFavorite == "1" + return pinned or favorite +end +function ArsenalManagerClass.prototype.removeInstanceFromAllLoadoutsForMarket(self, playerId, instanceId) + self:removeInstanceFromAllLoadouts(playerId, instanceId) + self:syncToClient(playerId) + self:saveLoadoutsToServer(playerId) + self:refreshHeroArsenalStats(playerId, "") + self:scheduleArsenalStatRefresh(playerId) +end +function ArsenalManagerClass.prototype.buildInventoryPayloadForMarket(self, playerId) + local source = self.instances[playerId] or ({}) + local normalized = {} + for key in pairs(source) do + do + local inst = source[key] + if not inst then + goto __continue241 + end + normalized[key] = __TS__ObjectAssign({}, inst, { + instanceId = inst.instanceId, + instance_id = inst.instanceId, + itemName = inst.itemName, + item_name = inst.itemName, + upgradeLevel = inst.upgradeLevel, + upgrade_level = inst.upgradeLevel, + globalSerial = inst.globalSerial, + global_serial = inst.globalSerial, + ownerName = inst.ownerName, + owner_name = inst.ownerName + }) + end + ::__continue241:: + end + return {instances = normalized} +end +function ArsenalManagerClass.prototype.removeInventoryInstanceForMarket(self, playerId, instanceId) + local bag = self:ensureInstances(playerId) + local inst = bag[instanceId] + if not inst then + return nil + end + __TS__Delete(bag, instanceId) + self:removeInstanceFromAllLoadouts(playerId, instanceId) + self:syncToClient(playerId) + self:saveInventoryToServer(playerId) + self:saveLoadoutsToServer(playerId) + self:refreshHeroArsenalStats(playerId, "") + self:scheduleArsenalStatRefresh(playerId) + return inst +end +function ArsenalManagerClass.prototype.upsertInventoryInstanceFromMarket(self, playerId, instance) + if not instance or not instance.instanceId then + return + end + self:ensureInstances(playerId)[instance.instanceId] = instance + self:syncToClient(playerId) + self:saveInventoryToServer(playerId) + self:refreshHeroArsenalStats(playerId, "") + self:scheduleArsenalStatRefresh(playerId) +end +function ArsenalManagerClass.prototype.getHeroStatTotals(self, hero) + local empty = { + battle_level = 0, + bonus_damage = 0, + attack_speed = 0, + bonus_armor = 0, + max_health = 0, + max_mana = 0, + health_regen = 0, + mana_regen = 0, + move_speed = 0, + magic_resist = 0, + spell_amp = 0, + all_stats = 0, + all_stats_pct = 0, + bonus_strength = 0, + bonus_agility = 0, + bonus_intellect = 0, + strength_pct = 0, + agility_pct = 0, + intellect_pct = 0, + luck = 0, + outgoing_damage_pct = 0, + incoming_damage_reduction_pct = 0, + attack_speed_pct = 0, + move_speed_pct = 0, + base_damage_pct = 0, + crit_mult = 0 + } + if not hero then + return empty + end + local playerId = hero.GetPlayerID ~= nil and hero:GetPlayerID() or hero:GetPlayerOwnerID() + if playerId < 0 then + return empty + end + local gt = GameRules:GetGameTime() + local heroMemo = hero + local memo = heroMemo.__arsenalHeroStatTotalsMemo + if memo ~= nil and heroMemo.__arsenalHeroStatTotalsMemoTime == gt then + return memo + end + local heroName = hero:GetUnitName() + local ____opt_46 = self.loadouts[playerId] + local heroLoadout = ____opt_46 and ____opt_46[heroName] + if not heroLoadout then + heroMemo.__arsenalHeroStatTotalsMemoTime = gt + heroMemo.__arsenalHeroStatTotalsMemo = empty + return empty + end + for ____, slot in ipairs(ARSENAL_SLOTS) do + do + local instanceId = heroLoadout[slot] + if not instanceId then + goto __continue253 + end + local inst = self:getInstance(playerId, instanceId) + if not inst then + goto __continue253 + end + local lines = getEffectiveItemStats(nil, inst) + for ____, line in ipairs(lines) do + do + local normalizedKey = self:normalizeArsenalStatKey(tostring(line.key)) + if not normalizedKey then + print((((((LOG_PREFIX .. " unknown stat key dropped: \"") .. tostring(line.key)) .. "\" item=") .. inst.itemName) .. " instance=") .. instanceId) + goto __continue256 + end + empty[normalizedKey] = (empty[normalizedKey] or 0) + line.value + end + ::__continue256:: + end + end + ::__continue253:: + end + if ARSENAL_VERBOSE_TOTALS_LOG then + local totalsHash = json.encode(empty) + if not self.lastPrintedTotals[playerId] then + self.lastPrintedTotals[playerId] = {} + end + local prevHash = self.lastPrintedTotals[playerId][heroName] + if prevHash ~= totalsHash then + self.lastPrintedTotals[playerId][heroName] = totalsHash + print((((((((((LOG_PREFIX .. " totals player=") .. tostring(playerId)) .. " hero=") .. heroName) .. " ") .. ((((((("dmg=" .. tostring(empty.bonus_damage)) .. " as=") .. tostring(empty.attack_speed)) .. " aspct=") .. tostring(empty.attack_speed_pct)) .. " armor=") .. tostring(empty.bonus_armor)) .. " ") .. ((("hp=" .. tostring(empty.max_health)) .. " mp=") .. tostring(empty.max_mana)) .. " ") .. ((((((((("ms=" .. tostring(empty.move_speed)) .. " mspct=") .. tostring(empty.move_speed_pct)) .. " mr=") .. tostring(empty.magic_resist)) .. " amp=") .. tostring(empty.spell_amp)) .. " all=") .. tostring(empty.all_stats)) .. " ") .. ((((((("all%=" .. tostring(empty.all_stats_pct)) .. " str%=") .. tostring(empty.strength_pct)) .. " agi%=") .. tostring(empty.agility_pct)) .. " int%=") .. tostring(empty.intellect_pct)) .. " ") .. (((((((("luck=" .. tostring(empty.luck)) .. " out%=") .. tostring(empty.outgoing_damage_pct)) .. " in_red%=") .. tostring(empty.incoming_damage_reduction_pct)) .. " bdmg%=") .. tostring(empty.base_damage_pct)) .. " crit%=") .. tostring(empty.crit_mult)) + end + end + heroMemo.__arsenalHeroStatTotalsMemoTime = gt + heroMemo.__arsenalHeroStatTotalsMemo = empty + return empty +end +function ArsenalManagerClass.prototype.syncToClient(self, playerId) + CustomNetTables:SetTableValue( + "arsenal_loadouts", + tostring(playerId), + {loadouts = self.loadouts[playerId] or ({}), selectedHero = self.selectedHero[playerId] or ""} + ) + CustomNetTables:SetTableValue( + "arsenal_inventory", + tostring(playerId), + {instances = self.instances[playerId] or ({})} + ) +end +function ArsenalManagerClass.prototype.syncLoadoutsToClientOnly(self, playerId) + CustomNetTables:SetTableValue( + "arsenal_loadouts", + tostring(playerId), + {loadouts = self.loadouts[playerId] or ({}), selectedHero = self.selectedHero[playerId] or ""} + ) +end +function ArsenalManagerClass.prototype.saveLoadoutsToServer(self, playerId) + local steamId = PlayerResource:GetSteamAccountID(playerId) + if not steamId then + return + end + local data = self.loadouts[playerId] or ({}) + local request = CreateHTTPRequest( + "PUT", + ((SERVER_CONFIG.API_URL .. "/player/") .. tostring(steamId)) .. "/arsenal_loadouts" + ) + setApiHeaders(nil, request) + request:SetHTTPRequestRawPostBody( + "application/json", + json.encode({arsenal_loadouts = data}) + ) + request:Send(function(result) + if result.StatusCode >= 200 and result.StatusCode < 300 then + print(((LOG_PREFIX .. " arsenal_loadouts PUT ok (player=") .. tostring(playerId)) .. ")") + else + print((LOG_PREFIX .. " arsenal_loadouts PUT fail: StatusCode=") .. tostring(result.StatusCode)) + end + end) +end +function ArsenalManagerClass.prototype.saveInventoryToServer(self, playerId) + local pid = playerId + local g = (self.inventorySaveGeneration[pid] or 0) + 1 + self.inventorySaveGeneration[pid] = g + Timers:CreateTimer( + ARSENAL_INVENTORY_PUT_DEBOUNCE, + function() + if self.inventorySaveGeneration[pid] ~= g then + return nil + end + self:sendInventoryPutRequest(pid) + return nil + end + ) +end +function ArsenalManagerClass.prototype.sendInventoryPutRequest(self, playerId) + local steamId = PlayerResource:GetSteamAccountID(playerId) + if not steamId then + return + end + local data = {instances = self.instances[playerId] or ({})} + local request = CreateHTTPRequest( + "PUT", + ((SERVER_CONFIG.API_URL .. "/player/") .. tostring(steamId)) .. "/arsenal_inventory" + ) + setApiHeaders(nil, request) + request:SetHTTPRequestRawPostBody( + "application/json", + json.encode({arsenal_inventory = data}) + ) + request:Send(function(result) + if result.StatusCode >= 200 and result.StatusCode < 300 then + print(((LOG_PREFIX .. " arsenal_inventory PUT ok (player=") .. tostring(playerId)) .. ")") + else + print((LOG_PREFIX .. " arsenal_inventory PUT fail: StatusCode=") .. tostring(result.StatusCode)) + end + end) +end +function ArsenalManagerClass.prototype.loadFromServer(self, playerId) + local steamId = PlayerResource:GetSteamAccountID(playerId) + if not steamId then + return + end + local loadoutsReq = CreateHTTPRequest( + "GET", + ((SERVER_CONFIG.API_URL .. "/player/") .. tostring(steamId)) .. "/arsenal_loadouts" + ) + setApiHeaders(nil, loadoutsReq) + loadoutsReq:Send(function(result) + if result.StatusCode >= 200 and result.StatusCode < 300 then + do + local function ____catch(e) + print((LOG_PREFIX .. " loadouts decode err: ") .. tostring(e)) + end + local ____try, ____hasReturned = pcall(function() + local decoded = {json.decode(result.Body)} + local resp = nil + if __TS__ArrayIsArray(decoded) and #decoded > 0 then + resp = decoded[1] + elseif decoded and type(decoded) == "table" then + resp = decoded + end + if resp and type(resp) == "table" and resp.arsenal_loadouts ~= nil then + self.loadouts[playerId] = resp.arsenal_loadouts or ({}) + else + self.loadouts[playerId] = self.loadouts[playerId] or ({}) + end + self:syncLoadoutsToClientOnly(playerId) + local selected = self.selectedHero[playerId] + if selected and #selected > 0 then + self:refreshHeroArsenalStats(playerId, selected) + self:scheduleArsenalStatRefresh(playerId) + end + print((LOG_PREFIX .. " loaded arsenal_loadouts for player=") .. tostring(playerId)) + end) + if not ____try then + ____catch(____hasReturned) + end + end + else + print((LOG_PREFIX .. " arsenal_loadouts GET fail: StatusCode=") .. tostring(result.StatusCode)) + end + end) + local invReq = CreateHTTPRequest( + "GET", + ((SERVER_CONFIG.API_URL .. "/player/") .. tostring(steamId)) .. "/arsenal_inventory" + ) + setApiHeaders(nil, invReq) + invReq:Send(function(result) + if result.StatusCode >= 200 and result.StatusCode < 300 then + do + local function ____catch(e) + print((LOG_PREFIX .. " inventory decode err: ") .. tostring(e)) + end + local ____try, ____hasReturned = pcall(function() + local decoded = {json.decode(result.Body)} + local resp = nil + if __TS__ArrayIsArray(decoded) and #decoded > 0 then + resp = decoded[1] + elseif decoded and type(decoded) == "table" then + resp = decoded + end + if resp and type(resp) == "table" and resp.arsenal_inventory ~= nil then + self:ingestInventoryPayload(playerId, resp.arsenal_inventory) + else + self.instances[playerId] = self.instances[playerId] or ({}) + end + self:normalizeInventoryFlags(playerId) + self:debugPrintInventoryOwnerMeta(playerId) + local repaired = false + local rerolledByVersion = false + local bag = self.instances[playerId] + if bag ~= nil then + for instanceId in pairs(bag) do + repaired = self:repairBrokenInstance(playerId, instanceId) or repaired + rerolledByVersion = self:rerollInstanceForCurrentVersion(playerId, instanceId) or rerolledByVersion + end + end + if repaired or rerolledByVersion then + self.inventorySaveGeneration[playerId] = (self.inventorySaveGeneration[playerId] or 0) + 1 + self:sendInventoryPutRequest(playerId) + end + self:migrateLoadoutsFromLegacyItemNames(playerId) + self:syncToClient(playerId) + local selected = self.selectedHero[playerId] + if selected and #selected > 0 then + self:refreshHeroArsenalStats(playerId, selected) + self:scheduleArsenalStatRefresh(playerId) + end + print((((((LOG_PREFIX .. " loaded arsenal_inventory for player=") .. tostring(playerId)) .. " repaired=") .. tostring(repaired)) .. " rerolledByVersion=") .. tostring(rerolledByVersion)) + end) + if not ____try then + ____catch(____hasReturned) + end + end + else + print((LOG_PREFIX .. " arsenal_inventory GET fail: StatusCode=") .. tostring(result.StatusCode)) + end + end) +end +ArsenalManagerClass.DYNAMIC_MODIFIER_NAME = "modifier_arsenal_dynamic_stats" +ArsenalManagerClass = __TS__Decorate(ArsenalManagerClass, ArsenalManagerClass, {reloadable}, {kind = "class", name = "ArsenalManagerClass"}) +____exports.ArsenalManagerClass = ArsenalManagerClass +____exports.ArsenalManager = ____exports.ArsenalManagerClass:getInstance() +function ____exports.grantArsenalItem(self, playerId, itemName, count) + if count == nil then + count = 1 + end + return ____exports.ArsenalManager:grantArsenalItem(playerId, itemName, count) +end +function ____exports.grantArsenalItemDetailed(self, playerId, itemName, count, rollQuality, rollContext, options) + if count == nil then + count = 1 + end + return ____exports.ArsenalManager:grantArsenalItemDetailed( + playerId, + itemName, + count, + rollQuality, + rollContext, + options + ) +end +function ____exports.flushArsenalInventoryToClientAndApi(self, playerId) + ____exports.ArsenalManager:flushArsenalInventoryToClientAndApi(playerId) +end +return ____exports diff --git a/scripts/vscripts/arsenal/arsenalstatruntime.lua b/scripts/vscripts/arsenal/arsenalstatruntime.lua new file mode 100644 index 0000000..0d308fa --- /dev/null +++ b/scripts/vscripts/arsenal/arsenalstatruntime.lua @@ -0,0 +1,41 @@ +--[[ Generated with https://github.com/TypeScriptToLua/TypeScriptToLua ]] +local ____exports = {} +local EMPTY_TOTALS = { + battle_level = 0, + bonus_damage = 0, + attack_speed = 0, + bonus_armor = 0, + max_health = 0, + max_mana = 0, + health_regen = 0, + mana_regen = 0, + move_speed = 0, + magic_resist = 0, + spell_amp = 0, + all_stats = 0, + all_stats_pct = 0, + bonus_strength = 0, + bonus_agility = 0, + bonus_intellect = 0, + strength_pct = 0, + agility_pct = 0, + intellect_pct = 0, + luck = 0, + outgoing_damage_pct = 0, + incoming_damage_reduction_pct = 0, + attack_speed_pct = 0, + move_speed_pct = 0, + base_damage_pct = 0, + crit_mult = 0 +} +local provider +function ____exports.setArsenalTotalsProvider(self, nextProvider) + provider = nextProvider +end +function ____exports.getArsenalTotals(self, hero) + if not provider then + return EMPTY_TOTALS + end + return provider(nil, hero) +end +return ____exports diff --git a/scripts/vscripts/arsenal/arsenalstats.lua b/scripts/vscripts/arsenal/arsenalstats.lua new file mode 100644 index 0000000..737474b --- /dev/null +++ b/scripts/vscripts/arsenal/arsenalstats.lua @@ -0,0 +1,727 @@ +local ____lualib = require("lualib_bundle") +local __TS__NumberIsFinite = ____lualib.__TS__NumberIsFinite +local ____exports = {} +local ARSENAL_RARITY_ORDER = { + "common", + "rare", + "epic", + "legendary", + "mythic" +} +local ASCENDED_STAT_QUALITY = "ascended" +local ARSENAL_RARITY_MULTIPLIER = { + common = 1, + rare = 1.2, + epic = 1.45, + legendary = 1.75, + mythic = 2.1, + ascended = 2.55 +} +local BASE_STAT_COUNT = 2 +local ADDITIONAL_STAT_COUNT = 4 +local HIDDEN_FINAL_STAT_INDEX = 6 +local HIDDEN_FINAL_UNLOCK_LEVEL = 1 +local MAX_UPGRADE_LEVEL = 5 +local PER_LEVEL_GROWTH = 0.12 +local MYTHIC_BEST_LINE_CHANCE_PCT = 24 +local MYTHIC_WEAK_LINE_CHANCE_PCT = 10 +--- С вероятностью N% новая строка копирует ключ уже существующего (ниже — реже дубли одного и того же стата на предмете). +local ARSENAL_REPEAT_EXISTING_STAT_CHANCE_PCT = 26 +local MAX_RARITY_INDEX = #ARSENAL_RARITY_ORDER - 1 +local MAX_RARITY_CAP_APPROACH_PER_STEP = 0.22 +local ADDITIONAL_SLOTS_BY_ITEM_QUALITY = { + common = 0, + rare = 1, + epic = 2, + legendary = 3, + mythic = 4 +} +--- Диапазоны с учётом 6 слотов экипировки и роста от редкости/уровня (см. Panorama `getTemplateForStatKey`). +-- +-- `upgradeGrowthMin` / `upgradeGrowthMax` — диапазон для **`k`** в формуле итога: +-- `значение ∝ effectiveBase × rarityMult × (1 + k × lineBonus)`. +-- Это **не** «+N к тултипу» и **не** проценты вида «15 = 15%»; типичные **`k`** — **доли 0.08…0.14** +-- (при `lineBonus = 5` множитель прокачки около **1.4…1.7**). +local ARSENAL_STAT_POOL = { + { + key = "bonus_damage", + min = 12, + max = 64, + upgradeGrowthMin = 0.1, + upgradeGrowthMax = 0.14 + }, + { + key = "attack_speed", + min = 5, + max = 22, + upgradeGrowthMin = 0.09, + upgradeGrowthMax = 0.13 + }, + { + key = "bonus_armor", + min = 2, + max = 8, + upgradeGrowthMin = 0.1, + upgradeGrowthMax = 0.14 + }, + { + key = "max_health", + min = 70, + max = 240, + upgradeGrowthMin = 0.09, + upgradeGrowthMax = 0.12 + }, + { + key = "max_mana", + min = 50, + max = 180, + upgradeGrowthMin = 0.09, + upgradeGrowthMax = 0.12 + }, + { + key = "health_regen", + min = 0.4, + max = 3.2, + decimals = 1, + upgradeGrowthMin = 0.09, + upgradeGrowthMax = 0.12 + }, + { + key = "mana_regen", + min = 0.2, + max = 1.8, + decimals = 2, + upgradeGrowthMin = 0.09, + upgradeGrowthMax = 0.12 + }, + { + key = "move_speed", + min = 6, + max = 12, + upgradeGrowthMin = 0.08, + upgradeGrowthMax = 0.12 + }, + { + key = "magic_resist", + min = 0.2, + max = 1.2, + decimals = 1, + upgradeGrowthMin = 0.04, + upgradeGrowthMax = 0.1 + }, + { + key = "spell_amp", + min = 1, + max = 5, + decimals = 1, + upgradeGrowthMin = 0.08, + upgradeGrowthMax = 0.13 + }, + { + key = "all_stats", + min = 2, + max = 5, + upgradeGrowthMin = 0.045, + upgradeGrowthMax = 0.07 + }, + { + key = "bonus_strength", + min = 4, + max = 16, + upgradeGrowthMin = 0.09, + upgradeGrowthMax = 0.13 + }, + { + key = "bonus_agility", + min = 4, + max = 16, + upgradeGrowthMin = 0.09, + upgradeGrowthMax = 0.13 + }, + { + key = "bonus_intellect", + min = 4, + max = 16, + upgradeGrowthMin = 0.09, + upgradeGrowthMax = 0.13 + }, + { + key = "all_stats_pct", + min = 0.2, + max = 1.1, + decimals = 1, + upgradeGrowthMin = 0.04, + upgradeGrowthMax = 0.065 + }, + { + key = "strength_pct", + min = 0.8, + max = 10, + decimals = 1, + upgradeGrowthMin = 0.05, + upgradeGrowthMax = 0.08 + }, + { + key = "agility_pct", + min = 0.8, + max = 10, + decimals = 1, + upgradeGrowthMin = 0.05, + upgradeGrowthMax = 0.08 + }, + { + key = "intellect_pct", + min = 0.8, + max = 10, + decimals = 1, + upgradeGrowthMin = 0.05, + upgradeGrowthMax = 0.08 + }, + { + key = "luck", + min = 0.5, + max = 2.5, + decimals = 1, + upgradeGrowthMin = 0.08, + upgradeGrowthMax = 0.14 + }, + { + key = "outgoing_damage_pct", + min = 0.8, + max = 3.5, + decimals = 1, + upgradeGrowthMin = 0.06, + upgradeGrowthMax = 0.1 + }, + { + key = "incoming_damage_reduction_pct", + min = 0.5, + max = 2, + decimals = 1, + upgradeGrowthMin = 0.06, + upgradeGrowthMax = 0.1 + }, + { + key = "attack_speed_pct", + min = 1.5, + max = 6, + decimals = 1, + upgradeGrowthMin = 0.07, + upgradeGrowthMax = 0.11 + }, + { + key = "move_speed_pct", + min = 1, + max = 4, + decimals = 1, + upgradeGrowthMin = 0.07, + upgradeGrowthMax = 0.11 + }, + { + key = "base_damage_pct", + min = 0.8, + max = 3.5, + decimals = 1, + upgradeGrowthMin = 0.07, + upgradeGrowthMax = 0.11 + }, + { + key = "crit_mult", + min = 1, + max = 8, + decimals = 1, + upgradeGrowthMin = 0.07, + upgradeGrowthMax = 0.11 + }, + {key = "battle_level", min = 1, max = 1, decimals = 0} +} +local function roundTo(self, value, decimals) + if decimals == nil then + decimals = 0 + end + if decimals <= 0 then + return math.floor(value + 0.5) + end + local mul = math.pow(10, decimals) + return math.floor(value * mul + 0.5) / mul +end +local function randomTemplateIndex(self) + return RandomInt(1, #ARSENAL_STAT_POOL) - 1 +end +local function inferSlotFromItemName(self, itemName) + local n = string.lower(itemName) + if (string.find(n, "_weapon_", nil, true) or 0) - 1 >= 0 then + return "weapon" + end + if (string.find(n, "_armor_", nil, true) or 0) - 1 >= 0 then + return "armor" + end + if (string.find(n, "_helmet_", nil, true) or 0) - 1 >= 0 then + return "helmet" + end + if (string.find(n, "_boots_", nil, true) or 0) - 1 >= 0 then + return "boots" + end + if (string.find(n, "_necklace_", nil, true) or 0) - 1 >= 0 then + return "necklace" + end + return "ring" +end +local function getSlotWeightForStat(self, slot, key) + if slot == "ring" then + if key == "max_mana" or key == "magic_resist" or key == "spell_amp" then + return 2.5 + end + if key == "mana_regen" then + return 2.2 + end + if key == "max_health" then + return 1.4 + end + if key == "all_stats_pct" then + return 1.55 + end + if key == "all_stats" then + return 1.15 + end + if key == "intellect_pct" or key == "strength_pct" or key == "agility_pct" or key == "bonus_intellect" or key == "bonus_strength" or key == "bonus_agility" then + return 1.3 + end + elseif slot == "weapon" then + if key == "bonus_damage" or key == "attack_speed" or key == "base_damage_pct" then + return 2.6 + end + if key == "attack_speed_pct" or key == "outgoing_damage_pct" or key == "crit_mult" then + return 1.8 + end + if key == "strength_pct" or key == "agility_pct" or key == "bonus_strength" or key == "bonus_agility" then + return 1.5 + end + if key == "all_stats_pct" then + return 1.25 + end + elseif slot == "armor" then + if key == "health_regen" or key == "max_health" then + return 1.85 + end + if key == "mana_regen" or key == "bonus_armor" then + return 1.55 + end + if key == "all_stats_pct" then + return 1.55 + end + if key == "all_stats" then + return 1.2 + end + if key == "strength_pct" or key == "bonus_strength" then + return 1.6 + end + elseif slot == "helmet" then + if key == "health_regen" then + return 1.65 + end + if key == "all_stats_pct" then + return 1.72 + end + if key == "all_stats" then + return 1.25 + end + if key == "intellect_pct" or key == "bonus_intellect" then + return 1.8 + end + elseif slot == "boots" then + if key == "all_stats_pct" then + return 1.38 + end + if key == "agility_pct" or key == "bonus_agility" then + return 1.6 + end + elseif slot == "necklace" then + if key == "mana_regen" or key == "spell_amp" then + return 1.75 + end + if key == "all_stats_pct" then + return 1.62 + end + if key == "all_stats" then + return 1.2 + end + if key == "intellect_pct" or key == "bonus_intellect" then + return 1.5 + end + end + return 1 +end +local function randomTemplateIndexForSlot(self, slot) + local totalWeight = 0 + for ____, t in ipairs(ARSENAL_STAT_POOL) do + totalWeight = totalWeight + getSlotWeightForStat(nil, slot, t.key) + end + if totalWeight <= 0 then + return randomTemplateIndex(nil) + end + local roll = RandomFloat(0, totalWeight) + local passed = 0 + do + local i = 0 + while i < #ARSENAL_STAT_POOL do + passed = passed + getSlotWeightForStat(nil, slot, ARSENAL_STAT_POOL[i + 1].key) + if roll <= passed then + return i + end + i = i + 1 + end + end + return #ARSENAL_STAT_POOL - 1 +end +local function findTemplateIndexByKey(self, key) + do + local i = 0 + while i < #ARSENAL_STAT_POOL do + if ARSENAL_STAT_POOL[i + 1].key == key then + return i + end + i = i + 1 + end + end + return nil +end +--- Реже, чем раньше: часть строк может взять тот же стат, что уже выпал (случайная из набранных). +-- Иначе — обычный взвешенный ролл по слоту. +local function randomTemplateIndexForSlotWithStackBias(self, slot, existingLines) + if #existingLines == 0 or RandomInt(1, 100) > ARSENAL_REPEAT_EXISTING_STAT_CHANCE_PCT then + return randomTemplateIndexForSlot(nil, slot) + end + local linePick = RandomInt(1, #existingLines) - 1 + local key = existingLines[linePick + 1].key + local idx = findTemplateIndexByKey(nil, key) + if idx == nil then + return randomTemplateIndexForSlot(nil, slot) + end + return idx +end +local function rollStatBase(self, template, slotIndex, contractDropStatBias) + if contractDropStatBias == nil then + contractDropStatBias = 0 + end + local roll01 = RandomFloat(0, 1) + local exponent = slotIndex < BASE_STAT_COUNT and 1.35 or 1.75 + if contractDropStatBias > 0 then + exponent = exponent * (1 - 0.45 * contractDropStatBias) + end + local biased = math.pow(roll01, exponent) + local raw = template.min + (template.max - template.min) * biased + return roundTo(nil, raw, template.decimals or 0) +end +local function rollStatBaseInBand(self, template, fromRatio, toRatio) + local clampedFrom = math.max( + 0, + math.min(1, fromRatio) + ) + local clampedTo = math.max( + clampedFrom, + math.min(1, toRatio) + ) + local ratio = RandomFloat(clampedFrom, clampedTo) + local raw = template.min + (template.max - template.min) * ratio + return roundTo(nil, raw, template.decimals or 0) +end +--- Случайный множитель роста строки по шаблону; без min/max в шаблоне — undefined (берётся PER_LEVEL_GROWTH). +local function rollUpgradeGrowthForTemplate(self, template) + local minG = template.upgradeGrowthMin + local maxG = template.upgradeGrowthMax + if minG == nil or maxG == nil then + return nil + end + if not __TS__NumberIsFinite(minG) or not __TS__NumberIsFinite(maxG) then + return nil + end + local lo = math.min(minG, maxG) + local hi = math.max(minG, maxG) + return roundTo( + nil, + RandomFloat(lo, hi), + 4 + ) +end +local function clampRarityIndex(self, index) + if index < 0 then + return 0 + end + if index >= #ARSENAL_RARITY_ORDER then + return #ARSENAL_RARITY_ORDER - 1 + end + return index +end +local function getRarityIndex(self, quality) + do + local i = 0 + while i < #ARSENAL_RARITY_ORDER do + if ARSENAL_RARITY_ORDER[i + 1] == quality then + return i + end + i = i + 1 + end + end + return 0 +end +local function elevateRarityBySteps(self, baseQuality, steps) + if steps <= 0 then + return baseQuality + end + local nextIndex = clampRarityIndex( + nil, + getRarityIndex(nil, baseQuality) + steps + ) + return ARSENAL_RARITY_ORDER[nextIndex + 1] +end +local function capStatQualityByItemQuality(self, statQuality, itemQuality) + local statIndex = getRarityIndex(nil, statQuality) + local itemIndex = getRarityIndex(nil, itemQuality) + return ARSENAL_RARITY_ORDER[math.min(statIndex, itemIndex) + 1] +end +local function getTemplateByKey(self, key) + for ____, t in ipairs(ARSENAL_STAT_POOL) do + if t.key == key then + return t + end + end + return nil +end +local function getStatQualityByBaseValue(self, stat) + local tpl = getTemplateByKey(nil, stat.key) + if not tpl then + return "common" + end + local span = tpl.max - tpl.min + if span <= 0 then + return "common" + end + local ratio = (stat.base - tpl.min) / span + local clamped = math.max( + 0, + math.min(1, ratio) + ) + local rawTier = math.floor(clamped * #ARSENAL_RARITY_ORDER) + return ARSENAL_RARITY_ORDER[clampRarityIndex(nil, rawTier) + 1] +end +local function getEffectiveStatQuality(self, item, statIndex, baseQuality) + if item.quality == "mythic" and statIndex == HIDDEN_FINAL_STAT_INDEX and math.floor(item.upgradeLevel) >= MAX_UPGRADE_LEVEL then + return ASCENDED_STAT_QUALITY + end + return capStatQualityByItemQuality(nil, baseQuality, item.quality) +end +local function getBaseAfterCapApproach(self, stat, extraMaxRaritySteps) + if extraMaxRaritySteps <= 0 then + return stat.base + end + local tpl = getTemplateByKey(nil, stat.key) + if not tpl or tpl.max <= stat.base then + return stat.base + end + local approachFactor = 1 - math.pow(1 - MAX_RARITY_CAP_APPROACH_PER_STEP, extraMaxRaritySteps) + local shifted = stat.base + (tpl.max - stat.base) * approachFactor + return roundTo(nil, shifted, tpl.decimals or 0) +end +local function getActiveAdditionalCount(self, itemQuality) + return ADDITIONAL_SLOTS_BY_ITEM_QUALITY[itemQuality] or 0 +end +local function getAdditionalUpgradeTargetIndex(self, item, levelStep, activeAdditional) + if levelStep <= 0 then + return nil + end + if levelStep == HIDDEN_FINAL_UNLOCK_LEVEL then + return HIDDEN_FINAL_STAT_INDEX + end + local poolSize = math.min(ADDITIONAL_STAT_COUNT, activeAdditional) + 1 + if poolSize <= 0 then + return nil + end + local seed = levelStep * 97 + poolSize * 31 + do + local offset = 0 + while offset < poolSize do + local idx = offset == poolSize - 1 and HIDDEN_FINAL_STAT_INDEX or BASE_STAT_COUNT + offset + local line = item.stats[idx + 1] + local baseInt = math.floor((line and line.base or 0) * 100 + 0.5) + seed = seed + ((idx + 1) * 17 + baseInt) + offset = offset + 1 + end + end + local offset = math.floor(math.abs(seed)) % poolSize + return offset == poolSize - 1 and HIDDEN_FINAL_STAT_INDEX or BASE_STAT_COUNT + offset +end +function ____exports.contractRewardMultToDropStatBias01(self, rewardMultiplier) + local minM = 3 + local maxM = 10 + local span = maxM - minM + if span <= 0 then + return 0 + end + return math.max( + 0, + math.min(1, (rewardMultiplier - minM) / span) + ) +end +function ____exports.createInitialItemState(self, itemName, quality, context) + local bias = math.max( + 0, + math.min(1, context and context.contractDropStatBias or 0) + ) + local mythicBandLift = bias * 0.035 + local slot = inferSlotFromItemName(nil, itemName) + local stats = {} + local totalStatLines = BASE_STAT_COUNT + ADDITIONAL_STAT_COUNT + 1 + local mythicBestLines = 0 + if quality == "mythic" then + mythicBestLines = 1 + if RandomInt(1, 100) <= MYTHIC_BEST_LINE_CHANCE_PCT then + mythicBestLines = 2 + end + end + do + local i = 0 + while i < BASE_STAT_COUNT + ADDITIONAL_STAT_COUNT + 1 do + local templateIndex = randomTemplateIndexForSlotWithStackBias(nil, slot, stats) + local template = ARSENAL_STAT_POOL[templateIndex + 1] + local rolledBase + if quality == "mythic" then + local guaranteedBestCut = totalStatLines - mythicBestLines + if i >= guaranteedBestCut then + rolledBase = rollStatBaseInBand( + nil, + template, + 0.8, + math.min(1, 0.97 + mythicBandLift) + ) + elseif RandomInt(1, 100) <= MYTHIC_WEAK_LINE_CHANCE_PCT then + rolledBase = rollStatBaseInBand( + nil, + template, + 0.05, + math.min(1, 0.59 + mythicBandLift * 0.5) + ) + else + rolledBase = rollStatBaseInBand( + nil, + template, + 0.6, + math.min(1, 0.79 + mythicBandLift) + ) + end + else + rolledBase = rollStatBase(nil, template, i, bias) + end + local rolledGrowth = rollUpgradeGrowthForTemplate(nil, template) + local row = {key = template.key, base = rolledBase} + if rolledGrowth ~= nil then + row.upgradeGrowth = rolledGrowth + end + stats[#stats + 1] = row + i = i + 1 + end + end + return {itemName = itemName, quality = quality, upgradeLevel = 0, stats = stats} +end +function ____exports.createArsenalItemInstance(self, instanceId, serial, itemName, quality, rollContext) + local core = ____exports.createInitialItemState(nil, itemName, quality, rollContext) + return { + instanceId = instanceId, + serial = serial, + itemName = core.itemName, + quality = core.quality, + upgradeLevel = core.upgradeLevel, + stats = core.stats + } +end +function ____exports.getEffectiveItemStats(self, item) + local result = {} + local level = math.max( + 0, + math.min( + MAX_UPGRADE_LEVEL, + math.floor(item.upgradeLevel) + ) + ) + local activeAdditional = getActiveAdditionalCount(nil, item.quality) + local lineLevelBonus = {} + do + local i = 0 + while i < #item.stats do + lineLevelBonus[i + 1] = 0 + i = i + 1 + end + end + do + local i = 0 + while i < BASE_STAT_COUNT and i < #item.stats do + lineLevelBonus[i + 1] = level + i = i + 1 + end + end + do + local step = 1 + while step <= level do + local target = getAdditionalUpgradeTargetIndex(nil, item, step, activeAdditional) + if target ~= nil and target < #item.stats then + lineLevelBonus[target + 1] = (lineLevelBonus[target + 1] or 0) + 1 + end + step = step + 1 + end + end + do + local i = 0 + while i < #item.stats do + do + if i >= BASE_STAT_COUNT and i < BASE_STAT_COUNT + ADDITIONAL_STAT_COUNT then + local localIndex = i - BASE_STAT_COUNT + if localIndex >= activeAdditional then + goto __continue106 + end + end + if i == HIDDEN_FINAL_STAT_INDEX and level < HIDDEN_FINAL_UNLOCK_LEVEL then + goto __continue106 + end + local rolledQuality = getStatQualityByBaseValue(nil, item.stats[i + 1]) + local rarityBonusSteps = lineLevelBonus[i + 1] or 0 + local upgradedQuality = elevateRarityBySteps(nil, rolledQuality, rarityBonusSteps) + local maxRarityStepsSpent = math.max( + 0, + rarityBonusSteps - (MAX_RARITY_INDEX - getRarityIndex(nil, rolledQuality)) + ) + local statQuality = getEffectiveStatQuality(nil, item, i, upgradedQuality) + local effectiveBase = getBaseAfterCapApproach(nil, item.stats[i + 1], maxRarityStepsSpent) + local qualityMult = ARSENAL_RARITY_MULTIPLIER[statQuality] + local lineBonus = lineLevelBonus[i + 1] or 0 + local statKey = item.stats[i + 1].key + if statKey == "battle_level" then + result[#result + 1] = { + key = statKey, + value = math.max( + 0, + math.floor(item.stats[i + 1].base) + lineBonus + ) + } + goto __continue106 + end + local tpl = getTemplateByKey(nil, statKey) + local outDecimals = tpl and tpl.decimals or 0 + local perLevelGrowth = item.stats[i + 1].upgradeGrowth or PER_LEVEL_GROWTH + local growthMult = 1 + perLevelGrowth * lineBonus + result[#result + 1] = { + key = statKey, + value = roundTo(nil, effectiveBase * qualityMult * growthMult, outDecimals) + } + end + ::__continue106:: + i = i + 1 + end + end + return result +end +function ____exports.getItemVisibleStatsCount(self, item) + local activeAdditional = getActiveAdditionalCount(nil, item.quality) + local hasHidden = math.floor(item.upgradeLevel) >= HIDDEN_FINAL_UNLOCK_LEVEL and 1 or 0 + return BASE_STAT_COUNT + activeAdditional + hasHidden +end +return ____exports diff --git a/scripts/vscripts/arsenal/items/armor.lua b/scripts/vscripts/arsenal/items/armor.lua new file mode 100644 index 0000000..f2f8697 --- /dev/null +++ b/scripts/vscripts/arsenal/items/armor.lua @@ -0,0 +1,75 @@ +local ____lualib = require("lualib_bundle") +local __TS__Class = ____lualib.__TS__Class +local __TS__ClassExtends = ____lualib.__TS__ClassExtends +local __TS__Decorate = ____lualib.__TS__Decorate +local ____exports = {} +local ____dota_ts_adapter = require("lib.dota_ts_adapter") +local BaseModifier = ____dota_ts_adapter.BaseModifier +local registerModifier = ____dota_ts_adapter.registerModifier +____exports.modifier_arsenal_armor_chain_mail = __TS__Class() +local modifier_arsenal_armor_chain_mail = ____exports.modifier_arsenal_armor_chain_mail +modifier_arsenal_armor_chain_mail.name = "modifier_arsenal_armor_chain_mail" +modifier_arsenal_armor_chain_mail.____file_path = "scripts/vscripts/arsenal/items/armor.lua" +__TS__ClassExtends(modifier_arsenal_armor_chain_mail, BaseModifier) +function modifier_arsenal_armor_chain_mail.prototype.IsHidden(self) + return false +end +function modifier_arsenal_armor_chain_mail.prototype.IsPurgable(self) + return false +end +function modifier_arsenal_armor_chain_mail.prototype.RemoveOnDeath(self) + return false +end +function modifier_arsenal_armor_chain_mail.prototype.GetTexture(self) + return "item_chainmail" +end +function modifier_arsenal_armor_chain_mail.prototype.DeclareFunctions(self) + return {MODIFIER_PROPERTY_PHYSICAL_ARMOR_BONUS} +end +function modifier_arsenal_armor_chain_mail.prototype.GetModifierPhysicalArmorBonus(self) + return 5 +end +modifier_arsenal_armor_chain_mail = __TS__Decorate( + modifier_arsenal_armor_chain_mail, + modifier_arsenal_armor_chain_mail, + {registerModifier(nil)}, + {kind = "class", name = "modifier_arsenal_armor_chain_mail"} +) +____exports.modifier_arsenal_armor_chain_mail = modifier_arsenal_armor_chain_mail +____exports.modifier_arsenal_armor_dragon_plate = __TS__Class() +local modifier_arsenal_armor_dragon_plate = ____exports.modifier_arsenal_armor_dragon_plate +modifier_arsenal_armor_dragon_plate.name = "modifier_arsenal_armor_dragon_plate" +modifier_arsenal_armor_dragon_plate.____file_path = "scripts/vscripts/arsenal/items/armor.lua" +__TS__ClassExtends(modifier_arsenal_armor_dragon_plate, BaseModifier) +function modifier_arsenal_armor_dragon_plate.prototype.IsHidden(self) + return false +end +function modifier_arsenal_armor_dragon_plate.prototype.IsPurgable(self) + return false +end +function modifier_arsenal_armor_dragon_plate.prototype.RemoveOnDeath(self) + return false +end +function modifier_arsenal_armor_dragon_plate.prototype.GetTexture(self) + return "item_platemail" +end +function modifier_arsenal_armor_dragon_plate.prototype.DeclareFunctions(self) + return {MODIFIER_PROPERTY_PHYSICAL_ARMOR_BONUS, MODIFIER_PROPERTY_HEALTH_BONUS, MODIFIER_PROPERTY_MAGICAL_RESISTANCE_BONUS} +end +function modifier_arsenal_armor_dragon_plate.prototype.GetModifierPhysicalArmorBonus(self) + return 12 +end +function modifier_arsenal_armor_dragon_plate.prototype.GetModifierHealthBonus(self) + return 350 +end +function modifier_arsenal_armor_dragon_plate.prototype.GetModifierMagicalResistanceBonus(self) + return 10 +end +modifier_arsenal_armor_dragon_plate = __TS__Decorate( + modifier_arsenal_armor_dragon_plate, + modifier_arsenal_armor_dragon_plate, + {registerModifier(nil)}, + {kind = "class", name = "modifier_arsenal_armor_dragon_plate"} +) +____exports.modifier_arsenal_armor_dragon_plate = modifier_arsenal_armor_dragon_plate +return ____exports diff --git a/scripts/vscripts/arsenal/items/battle_level.lua b/scripts/vscripts/arsenal/items/battle_level.lua new file mode 100644 index 0000000..6d79712 --- /dev/null +++ b/scripts/vscripts/arsenal/items/battle_level.lua @@ -0,0 +1,62 @@ +local ____lualib = require("lualib_bundle") +local __TS__Class = ____lualib.__TS__Class +local __TS__ClassExtends = ____lualib.__TS__ClassExtends +local __TS__Decorate = ____lualib.__TS__Decorate +local ____exports = {} +local ____dota_ts_adapter = require("lib.dota_ts_adapter") +local BaseModifier = ____dota_ts_adapter.BaseModifier +local registerModifier = ____dota_ts_adapter.registerModifier +____exports.modifier_arsenal_battle_level = __TS__Class() +local modifier_arsenal_battle_level = ____exports.modifier_arsenal_battle_level +modifier_arsenal_battle_level.name = "modifier_arsenal_battle_level" +modifier_arsenal_battle_level.____file_path = "scripts/vscripts/arsenal/items/battle_level.lua" +__TS__ClassExtends(modifier_arsenal_battle_level, BaseModifier) +function modifier_arsenal_battle_level.prototype.IsHidden(self) + return true +end +function modifier_arsenal_battle_level.prototype.RemoveOnDeath(self) + return false +end +function modifier_arsenal_battle_level.prototype.OnCreated(self) + if not IsServer() then + return + end + self:applyBattleLevel() +end +function modifier_arsenal_battle_level.prototype.OnRefresh(self) + if not IsServer() then + return + end + self:applyBattleLevel() +end +function modifier_arsenal_battle_level.prototype.applyBattleLevel(self) + local hero = self:GetParent() + if not hero or not IsValidEntity(hero) or not hero:IsRealHero() then + return + end + local bonus = math.max( + 0, + math.floor(self:GetStackCount() or 0) + ) + if bonus <= 0 then + return + end + local targetLevel = 1 + bonus + local cur = hero:GetLevel() + if cur >= targetLevel then + return + end + local prevAbilityPoints = hero:GetAbilityPoints() + while hero:GetLevel() < targetLevel do + hero:HeroLevelUp(false) + hero:SetAbilityPoints(prevAbilityPoints) + end +end +modifier_arsenal_battle_level = __TS__Decorate( + modifier_arsenal_battle_level, + modifier_arsenal_battle_level, + {registerModifier(nil)}, + {kind = "class", name = "modifier_arsenal_battle_level"} +) +____exports.modifier_arsenal_battle_level = modifier_arsenal_battle_level +return ____exports diff --git a/scripts/vscripts/arsenal/items/boots.lua b/scripts/vscripts/arsenal/items/boots.lua new file mode 100644 index 0000000..03b5f2b --- /dev/null +++ b/scripts/vscripts/arsenal/items/boots.lua @@ -0,0 +1,72 @@ +local ____lualib = require("lualib_bundle") +local __TS__Class = ____lualib.__TS__Class +local __TS__ClassExtends = ____lualib.__TS__ClassExtends +local __TS__Decorate = ____lualib.__TS__Decorate +local ____exports = {} +local ____dota_ts_adapter = require("lib.dota_ts_adapter") +local BaseModifier = ____dota_ts_adapter.BaseModifier +local registerModifier = ____dota_ts_adapter.registerModifier +____exports.modifier_arsenal_boots_swift_boots = __TS__Class() +local modifier_arsenal_boots_swift_boots = ____exports.modifier_arsenal_boots_swift_boots +modifier_arsenal_boots_swift_boots.name = "modifier_arsenal_boots_swift_boots" +modifier_arsenal_boots_swift_boots.____file_path = "scripts/vscripts/arsenal/items/boots.lua" +__TS__ClassExtends(modifier_arsenal_boots_swift_boots, BaseModifier) +function modifier_arsenal_boots_swift_boots.prototype.IsHidden(self) + return false +end +function modifier_arsenal_boots_swift_boots.prototype.IsPurgable(self) + return false +end +function modifier_arsenal_boots_swift_boots.prototype.RemoveOnDeath(self) + return false +end +function modifier_arsenal_boots_swift_boots.prototype.GetTexture(self) + return "item_boots" +end +function modifier_arsenal_boots_swift_boots.prototype.DeclareFunctions(self) + return {MODIFIER_PROPERTY_MOVESPEED_BONUS_CONSTANT} +end +function modifier_arsenal_boots_swift_boots.prototype.GetModifierMoveSpeedBonus_Constant(self) + return 35 +end +modifier_arsenal_boots_swift_boots = __TS__Decorate( + modifier_arsenal_boots_swift_boots, + modifier_arsenal_boots_swift_boots, + {registerModifier(nil)}, + {kind = "class", name = "modifier_arsenal_boots_swift_boots"} +) +____exports.modifier_arsenal_boots_swift_boots = modifier_arsenal_boots_swift_boots +____exports.modifier_arsenal_boots_phase_treads = __TS__Class() +local modifier_arsenal_boots_phase_treads = ____exports.modifier_arsenal_boots_phase_treads +modifier_arsenal_boots_phase_treads.name = "modifier_arsenal_boots_phase_treads" +modifier_arsenal_boots_phase_treads.____file_path = "scripts/vscripts/arsenal/items/boots.lua" +__TS__ClassExtends(modifier_arsenal_boots_phase_treads, BaseModifier) +function modifier_arsenal_boots_phase_treads.prototype.IsHidden(self) + return false +end +function modifier_arsenal_boots_phase_treads.prototype.IsPurgable(self) + return false +end +function modifier_arsenal_boots_phase_treads.prototype.RemoveOnDeath(self) + return false +end +function modifier_arsenal_boots_phase_treads.prototype.GetTexture(self) + return "item_phase_boots" +end +function modifier_arsenal_boots_phase_treads.prototype.DeclareFunctions(self) + return {MODIFIER_PROPERTY_MOVESPEED_BONUS_CONSTANT, MODIFIER_PROPERTY_PREATTACK_BONUS_DAMAGE} +end +function modifier_arsenal_boots_phase_treads.prototype.GetModifierMoveSpeedBonus_Constant(self) + return 50 +end +function modifier_arsenal_boots_phase_treads.prototype.GetModifierPreAttack_BonusDamage(self) + return 15 +end +modifier_arsenal_boots_phase_treads = __TS__Decorate( + modifier_arsenal_boots_phase_treads, + modifier_arsenal_boots_phase_treads, + {registerModifier(nil)}, + {kind = "class", name = "modifier_arsenal_boots_phase_treads"} +) +____exports.modifier_arsenal_boots_phase_treads = modifier_arsenal_boots_phase_treads +return ____exports diff --git a/scripts/vscripts/arsenal/items/dynamic_stats.lua b/scripts/vscripts/arsenal/items/dynamic_stats.lua new file mode 100644 index 0000000..0746e33 --- /dev/null +++ b/scripts/vscripts/arsenal/items/dynamic_stats.lua @@ -0,0 +1,429 @@ +local ____lualib = require("lualib_bundle") +local __TS__ObjectValues = ____lualib.__TS__ObjectValues +local __TS__ObjectAssign = ____lualib.__TS__ObjectAssign +local __TS__Class = ____lualib.__TS__Class +local __TS__ClassExtends = ____lualib.__TS__ClassExtends +local __TS__Decorate = ____lualib.__TS__Decorate +local ____exports = {} +local ____dota_ts_adapter = require("lib.dota_ts_adapter") +local BaseModifier = ____dota_ts_adapter.BaseModifier +local registerModifier = ____dota_ts_adapter.registerModifier +local ____ArsenalStatRuntime = require("arsenal.ArsenalStatRuntime") +local getArsenalTotals = ____ArsenalStatRuntime.getArsenalTotals +local ____modifier_stats_multiplier = require("modifiers.modifier_stats_multiplier") +local ARSENAL_STATS_MULTIPLIER_SOURCE_ID = ____modifier_stats_multiplier.ARSENAL_STATS_MULTIPLIER_SOURCE_ID +local invalidateStatsMultiplierSumCache = ____modifier_stats_multiplier.invalidateStatsMultiplierSumCache +local removeStatsMultiplierSource = ____modifier_stats_multiplier.removeStatsMultiplierSource +local setStatsMultiplierSource = ____modifier_stats_multiplier.setStatsMultiplierSource +local ____incoming_damage_reduction_combine = require("utils.incoming_damage_reduction_combine") +local removeIncomingDamageReductionSource = ____incoming_damage_reduction_combine.removeIncomingDamageReductionSource +local setIncomingDamageReductionSource = ____incoming_damage_reduction_combine.setIncomingDamageReductionSource +local ____luck = require("utils.luck") +local addLuck = ____luck.addLuck +local reduceLuck = ____luck.reduceLuck +local ____crit_mult = require("utils.crit_mult") +local addCritMult = ____crit_mult.addCritMult +local reduceCritMult = ____crit_mult.reduceCritMult +local ZERO_TOTALS = { + battle_level = 0, + bonus_damage = 0, + attack_speed = 0, + bonus_armor = 0, + max_health = 0, + max_mana = 0, + health_regen = 0, + mana_regen = 0, + move_speed = 0, + magic_resist = 0, + spell_amp = 0, + all_stats = 0, + all_stats_pct = 0, + bonus_strength = 0, + bonus_agility = 0, + bonus_intellect = 0, + strength_pct = 0, + agility_pct = 0, + intellect_pct = 0, + luck = 0, + outgoing_damage_pct = 0, + incoming_damage_reduction_pct = 0, + attack_speed_pct = 0, + move_speed_pct = 0, + base_damage_pct = 0, + crit_mult = 0 +} +--- % атрибутов — через stats multiplier (база × %). Боевые % — прямо в DeclareFunctions ниже. +local ARSENAL_ATTRIBUTE_PCT_KINDS = {"all_stats_pct", "strength_pct", "agility_pct", "intellect_pct"} +local ALL_ARSENAL_MULTIPLIER_SOURCE_IDS = __TS__ObjectValues(ARSENAL_STATS_MULTIPLIER_SOURCE_ID) +local ARSENAL_INCOMING_REDUCTION_SOURCE_ID = "arsenal_dynamic_stats_incoming" +--- Раньше вешались на stats_multiplier — снимаем, чтобы не дублировать с прямым DeclareFunctions. +local ARSENAL_LEGACY_COMBAT_PCT_KINDS = { + "outgoing_damage_pct", + "incoming_damage_reduction_pct", + "attack_speed_pct", + "move_speed_pct", + "base_damage_pct" +} +local function coerceTotals(self, next) + if not next then + return __TS__ObjectAssign({}, ZERO_TOTALS) + end + return { + battle_level = next.battle_level or 0, + bonus_damage = next.bonus_damage or 0, + attack_speed = next.attack_speed or 0, + bonus_armor = next.bonus_armor or 0, + max_health = next.max_health or 0, + max_mana = next.max_mana or 0, + health_regen = next.health_regen or 0, + mana_regen = next.mana_regen or 0, + move_speed = next.move_speed or 0, + magic_resist = next.magic_resist or 0, + spell_amp = next.spell_amp or 0, + all_stats = next.all_stats or 0, + all_stats_pct = next.all_stats_pct or 0, + bonus_strength = next.bonus_strength or 0, + bonus_agility = next.bonus_agility or 0, + bonus_intellect = next.bonus_intellect or 0, + strength_pct = next.strength_pct or 0, + agility_pct = next.agility_pct or 0, + intellect_pct = next.intellect_pct or 0, + luck = next.luck or 0, + outgoing_damage_pct = next.outgoing_damage_pct or 0, + incoming_damage_reduction_pct = next.incoming_damage_reduction_pct or 0, + attack_speed_pct = next.attack_speed_pct or 0, + move_speed_pct = next.move_speed_pct or 0, + base_damage_pct = next.base_damage_pct or 0, + crit_mult = next.crit_mult or 0 + } +end +____exports.modifier_arsenal_dynamic_stats = __TS__Class() +local modifier_arsenal_dynamic_stats = ____exports.modifier_arsenal_dynamic_stats +modifier_arsenal_dynamic_stats.name = "modifier_arsenal_dynamic_stats" +modifier_arsenal_dynamic_stats.____file_path = "scripts/vscripts/arsenal/items/dynamic_stats.lua" +__TS__ClassExtends(modifier_arsenal_dynamic_stats, BaseModifier) +function modifier_arsenal_dynamic_stats.prototype.____constructor(self, ...) + BaseModifier.prototype.____constructor(self, ...) + self.totals = __TS__ObjectAssign({}, ZERO_TOTALS) + self.appliedLuckFromArsenal = 0 + self.appliedCritMultFromArsenal = 0 +end +function modifier_arsenal_dynamic_stats.prototype.OnCreated(self) + if not IsServer() then + return + end + self:SetHasCustomTransmitterData(true) + self:refreshTotalsFromServer() + self:applyBattleLevelFromTotals() + self:syncLuckFromTotals() + self:syncCritMultFromTotals() + self:wireArsenalAttributePctSources() + self:wireArsenalIncomingReductionSource() +end +function modifier_arsenal_dynamic_stats.prototype.OnRefresh(self) + if not IsServer() then + return + end + self:refreshTotalsFromServer() + self:applyBattleLevelFromTotals() + self:syncLuckFromTotals() + self:syncCritMultFromTotals() + self:wireArsenalAttributePctSources() + self:wireArsenalIncomingReductionSource() +end +function modifier_arsenal_dynamic_stats.prototype.OnDestroy(self) + if not IsServer() then + return + end + self:clearLuckFromTotals() + self:clearCritMultFromTotals() + self:unwireAllArsenalMultiplierSources() + self:unwireArsenalIncomingReductionSource() +end +function modifier_arsenal_dynamic_stats.prototype.wireArsenalIncomingReductionSource(self) + local hero = self:GetParent() + if not hero or not IsValidEntity(hero) or not hero:IsRealHero() then + return + end + setIncomingDamageReductionSource( + nil, + hero, + ARSENAL_INCOMING_REDUCTION_SOURCE_ID, + function() + if not hero or not IsValidEntity(hero) then + return 0 + end + local totals = coerceTotals( + nil, + getArsenalTotals(nil, hero) + ) + return math.max(0, totals.incoming_damage_reduction_pct or 0) + end + ) +end +function modifier_arsenal_dynamic_stats.prototype.unwireArsenalIncomingReductionSource(self) + local hero = self:GetParent() + if not hero or not IsValidEntity(hero) then + return + end + removeIncomingDamageReductionSource(nil, hero, ARSENAL_INCOMING_REDUCTION_SOURCE_ID) +end +function modifier_arsenal_dynamic_stats.prototype.unwireLegacyArsenalCombatPctSources(self, hero) + for ____, k in ipairs(ARSENAL_LEGACY_COMBAT_PCT_KINDS) do + removeStatsMultiplierSource(nil, hero, ARSENAL_STATS_MULTIPLIER_SOURCE_ID[k]) + end +end +function modifier_arsenal_dynamic_stats.prototype.wireArsenalAttributePctSources(self) + local hero = self:GetParent() + if not hero or not IsValidEntity(hero) or not hero:IsRealHero() then + return + end + self:unwireLegacyArsenalCombatPctSources(hero) + for ____, k in ipairs(ARSENAL_ATTRIBUTE_PCT_KINDS) do + local sourceId = ARSENAL_STATS_MULTIPLIER_SOURCE_ID[k] + setStatsMultiplierSource( + nil, + hero, + sourceId, + function() + if not hero or not IsValidEntity(hero) then + return 0 + end + local t = coerceTotals( + nil, + getArsenalTotals(nil, hero) + ) + if k == "strength_pct" then + return t.strength_pct + end + if k == "agility_pct" then + return t.agility_pct + end + if k == "intellect_pct" then + return t.intellect_pct + end + if k == "all_stats_pct" then + return t.all_stats_pct + end + return 0 + end, + k + ) + end +end +function modifier_arsenal_dynamic_stats.prototype.unwireAllArsenalMultiplierSources(self) + local hero = self:GetParent() + if not hero or not IsValidEntity(hero) then + return + end + for ____, sourceId in ipairs(ALL_ARSENAL_MULTIPLIER_SOURCE_IDS) do + removeStatsMultiplierSource(nil, hero, sourceId) + end +end +function modifier_arsenal_dynamic_stats.prototype.AddCustomTransmitterData(self) + return self.totals +end +function modifier_arsenal_dynamic_stats.prototype.HandleCustomTransmitterData(self, data) + self.totals = coerceTotals(nil, data) +end +function modifier_arsenal_dynamic_stats.prototype.refreshTotalsFromServer(self) + local parent = self:GetParent() + local hero = parent + local next = getArsenalTotals(nil, hero) + self.totals = coerceTotals(nil, next) + self:SendBuffRefreshToClients() + if hero and IsValidEntity(hero) and hero:IsRealHero() then + invalidateStatsMultiplierSumCache(nil, hero) + end +end +function modifier_arsenal_dynamic_stats.prototype.syncLuckFromTotals(self) + local hero = self:GetParent() + if not hero or not IsValidEntity(hero) or not hero:IsRealHero() then + return + end + local targetLuck = self:getTotals().luck or 0 + local delta = targetLuck - self.appliedLuckFromArsenal + if math.abs(delta) <= 0.0001 then + return + end + if delta > 0 then + addLuck(nil, hero, delta) + else + reduceLuck(nil, hero, -delta) + end + self.appliedLuckFromArsenal = targetLuck +end +function modifier_arsenal_dynamic_stats.prototype.clearLuckFromTotals(self) + local hero = self:GetParent() + if not hero or not IsValidEntity(hero) or not hero:IsRealHero() then + return + end + if math.abs(self.appliedLuckFromArsenal) <= 0.0001 then + return + end + if self.appliedLuckFromArsenal > 0 then + reduceLuck(nil, hero, self.appliedLuckFromArsenal) + else + addLuck(nil, hero, -self.appliedLuckFromArsenal) + end + self.appliedLuckFromArsenal = 0 +end +function modifier_arsenal_dynamic_stats.prototype.syncCritMultFromTotals(self) + local hero = self:GetParent() + if not hero or not IsValidEntity(hero) or not hero:IsRealHero() then + return + end + local targetCrit = self:getTotals().crit_mult or 0 + local delta = targetCrit - self.appliedCritMultFromArsenal + if math.abs(delta) <= 0.0001 then + return + end + if delta > 0 then + addCritMult(nil, hero, delta) + else + reduceCritMult(nil, hero, -delta) + end + self.appliedCritMultFromArsenal = targetCrit +end +function modifier_arsenal_dynamic_stats.prototype.clearCritMultFromTotals(self) + local hero = self:GetParent() + if not hero or not IsValidEntity(hero) or not hero:IsRealHero() then + return + end + if math.abs(self.appliedCritMultFromArsenal) <= 0.0001 then + return + end + if self.appliedCritMultFromArsenal > 0 then + reduceCritMult(nil, hero, self.appliedCritMultFromArsenal) + else + addCritMult(nil, hero, -self.appliedCritMultFromArsenal) + end + self.appliedCritMultFromArsenal = 0 +end +function modifier_arsenal_dynamic_stats.prototype.applyBattleLevelFromTotals(self) + local hero = self:GetParent() + if not hero or not IsValidEntity(hero) or not hero:IsRealHero() then + return + end + local bonus = math.max( + 0, + math.floor(self:getTotals().battle_level or 0) + ) + if bonus <= 0 then + return + end + local targetLevel = 1 + bonus + if hero:GetLevel() >= targetLevel then + return + end + local prevAbilityPoints = hero:GetAbilityPoints() + while hero:GetLevel() < targetLevel do + hero:HeroLevelUp(false) + hero:SetAbilityPoints(prevAbilityPoints) + end +end +function modifier_arsenal_dynamic_stats.prototype.IsHidden(self) + return true +end +function modifier_arsenal_dynamic_stats.prototype.IsPurgable(self) + return false +end +function modifier_arsenal_dynamic_stats.prototype.RemoveOnDeath(self) + return false +end +function modifier_arsenal_dynamic_stats.prototype.GetTexture(self) + return "item_mantle" +end +function modifier_arsenal_dynamic_stats.prototype.DeclareFunctions(self) + return { + MODIFIER_PROPERTY_PREATTACK_BONUS_DAMAGE, + MODIFIER_PROPERTY_ATTACKSPEED_BONUS_CONSTANT, + MODIFIER_PROPERTY_PHYSICAL_ARMOR_BONUS, + MODIFIER_PROPERTY_HEALTH_BONUS, + MODIFIER_PROPERTY_MANA_BONUS, + MODIFIER_PROPERTY_HEALTH_REGEN_CONSTANT, + MODIFIER_PROPERTY_MANA_REGEN_CONSTANT, + MODIFIER_PROPERTY_MOVESPEED_BONUS_CONSTANT, + MODIFIER_PROPERTY_MAGICAL_RESISTANCE_BONUS, + MODIFIER_PROPERTY_SPELL_AMPLIFY_PERCENTAGE, + MODIFIER_PROPERTY_STATS_STRENGTH_BONUS, + MODIFIER_PROPERTY_STATS_AGILITY_BONUS, + MODIFIER_PROPERTY_STATS_INTELLECT_BONUS, + MODIFIER_PROPERTY_DAMAGEOUTGOING_PERCENTAGE, + MODIFIER_PROPERTY_ATTACKSPEED_PERCENTAGE, + MODIFIER_PROPERTY_MOVESPEED_BONUS_PERCENTAGE, + MODIFIER_PROPERTY_BASEDAMAGEOUTGOING_PERCENTAGE + } +end +function modifier_arsenal_dynamic_stats.prototype.getTotals(self) + if IsServer() then + local parent = self:GetParent() + return coerceTotals( + nil, + getArsenalTotals(nil, parent) + ) + end + return self.totals +end +function modifier_arsenal_dynamic_stats.prototype.GetModifierPreAttack_BonusDamage(self) + return self:getTotals().bonus_damage +end +function modifier_arsenal_dynamic_stats.prototype.GetModifierAttackSpeedBonus_Constant(self) + return self:getTotals().attack_speed +end +function modifier_arsenal_dynamic_stats.prototype.GetModifierPhysicalArmorBonus(self) + return self:getTotals().bonus_armor +end +function modifier_arsenal_dynamic_stats.prototype.GetModifierHealthBonus(self) + return self:getTotals().max_health +end +function modifier_arsenal_dynamic_stats.prototype.GetModifierManaBonus(self) + return self:getTotals().max_mana +end +function modifier_arsenal_dynamic_stats.prototype.GetModifierConstantHealthRegen(self) + return self:getTotals().health_regen +end +function modifier_arsenal_dynamic_stats.prototype.GetModifierConstantManaRegen(self) + return self:getTotals().mana_regen +end +function modifier_arsenal_dynamic_stats.prototype.GetModifierMoveSpeedBonus_Constant(self) + return self:getTotals().move_speed +end +function modifier_arsenal_dynamic_stats.prototype.GetModifierMagicalResistanceBonus(self) + return self:getTotals().magic_resist +end +function modifier_arsenal_dynamic_stats.prototype.GetModifierSpellAmplify_Percentage(self) + return self:getTotals().spell_amp +end +function modifier_arsenal_dynamic_stats.prototype.GetModifierBonusStats_Strength(self) + local t = self:getTotals() + return t.all_stats + t.bonus_strength +end +function modifier_arsenal_dynamic_stats.prototype.GetModifierBonusStats_Agility(self) + local t = self:getTotals() + return t.all_stats + t.bonus_agility +end +function modifier_arsenal_dynamic_stats.prototype.GetModifierBonusStats_Intellect(self) + local t = self:getTotals() + return t.all_stats + t.bonus_intellect +end +function modifier_arsenal_dynamic_stats.prototype.GetModifierDamageOutgoing_Percentage(self) + return self:getTotals().outgoing_damage_pct +end +function modifier_arsenal_dynamic_stats.prototype.GetModifierAttackSpeedPercentage(self) + return self:getTotals().attack_speed_pct +end +function modifier_arsenal_dynamic_stats.prototype.GetModifierMoveSpeedBonus_Percentage(self) + return self:getTotals().move_speed_pct +end +function modifier_arsenal_dynamic_stats.prototype.GetModifierBaseDamageOutgoing_Percentage(self) + return self:getTotals().base_damage_pct +end +modifier_arsenal_dynamic_stats = __TS__Decorate( + modifier_arsenal_dynamic_stats, + modifier_arsenal_dynamic_stats, + {registerModifier(nil)}, + {kind = "class", name = "modifier_arsenal_dynamic_stats"} +) +____exports.modifier_arsenal_dynamic_stats = modifier_arsenal_dynamic_stats +return ____exports diff --git a/scripts/vscripts/arsenal/items/helmet.lua b/scripts/vscripts/arsenal/items/helmet.lua new file mode 100644 index 0000000..c953934 --- /dev/null +++ b/scripts/vscripts/arsenal/items/helmet.lua @@ -0,0 +1,72 @@ +local ____lualib = require("lualib_bundle") +local __TS__Class = ____lualib.__TS__Class +local __TS__ClassExtends = ____lualib.__TS__ClassExtends +local __TS__Decorate = ____lualib.__TS__Decorate +local ____exports = {} +local ____dota_ts_adapter = require("lib.dota_ts_adapter") +local BaseModifier = ____dota_ts_adapter.BaseModifier +local registerModifier = ____dota_ts_adapter.registerModifier +____exports.modifier_arsenal_helmet_leather_cap = __TS__Class() +local modifier_arsenal_helmet_leather_cap = ____exports.modifier_arsenal_helmet_leather_cap +modifier_arsenal_helmet_leather_cap.name = "modifier_arsenal_helmet_leather_cap" +modifier_arsenal_helmet_leather_cap.____file_path = "scripts/vscripts/arsenal/items/helmet.lua" +__TS__ClassExtends(modifier_arsenal_helmet_leather_cap, BaseModifier) +function modifier_arsenal_helmet_leather_cap.prototype.IsHidden(self) + return false +end +function modifier_arsenal_helmet_leather_cap.prototype.IsPurgable(self) + return false +end +function modifier_arsenal_helmet_leather_cap.prototype.RemoveOnDeath(self) + return false +end +function modifier_arsenal_helmet_leather_cap.prototype.GetTexture(self) + return "item_helm_of_iron_will" +end +function modifier_arsenal_helmet_leather_cap.prototype.DeclareFunctions(self) + return {MODIFIER_PROPERTY_HEALTH_REGEN_CONSTANT} +end +function modifier_arsenal_helmet_leather_cap.prototype.GetModifierConstantHealthRegen(self) + return 3 +end +modifier_arsenal_helmet_leather_cap = __TS__Decorate( + modifier_arsenal_helmet_leather_cap, + modifier_arsenal_helmet_leather_cap, + {registerModifier(nil)}, + {kind = "class", name = "modifier_arsenal_helmet_leather_cap"} +) +____exports.modifier_arsenal_helmet_leather_cap = modifier_arsenal_helmet_leather_cap +____exports.modifier_arsenal_helmet_arcane_circlet = __TS__Class() +local modifier_arsenal_helmet_arcane_circlet = ____exports.modifier_arsenal_helmet_arcane_circlet +modifier_arsenal_helmet_arcane_circlet.name = "modifier_arsenal_helmet_arcane_circlet" +modifier_arsenal_helmet_arcane_circlet.____file_path = "scripts/vscripts/arsenal/items/helmet.lua" +__TS__ClassExtends(modifier_arsenal_helmet_arcane_circlet, BaseModifier) +function modifier_arsenal_helmet_arcane_circlet.prototype.IsHidden(self) + return false +end +function modifier_arsenal_helmet_arcane_circlet.prototype.IsPurgable(self) + return false +end +function modifier_arsenal_helmet_arcane_circlet.prototype.RemoveOnDeath(self) + return false +end +function modifier_arsenal_helmet_arcane_circlet.prototype.GetTexture(self) + return "item_helm_of_the_dominator" +end +function modifier_arsenal_helmet_arcane_circlet.prototype.DeclareFunctions(self) + return {MODIFIER_PROPERTY_MANA_BONUS, MODIFIER_PROPERTY_MANA_REGEN_CONSTANT} +end +function modifier_arsenal_helmet_arcane_circlet.prototype.GetModifierManaBonus(self) + return 250 +end +function modifier_arsenal_helmet_arcane_circlet.prototype.GetModifierConstantManaRegen(self) + return 3 +end +modifier_arsenal_helmet_arcane_circlet = __TS__Decorate( + modifier_arsenal_helmet_arcane_circlet, + modifier_arsenal_helmet_arcane_circlet, + {registerModifier(nil)}, + {kind = "class", name = "modifier_arsenal_helmet_arcane_circlet"} +) +____exports.modifier_arsenal_helmet_arcane_circlet = modifier_arsenal_helmet_arcane_circlet +return ____exports diff --git a/scripts/vscripts/arsenal/items/necklace.lua b/scripts/vscripts/arsenal/items/necklace.lua new file mode 100644 index 0000000..666501b --- /dev/null +++ b/scripts/vscripts/arsenal/items/necklace.lua @@ -0,0 +1,75 @@ +local ____lualib = require("lualib_bundle") +local __TS__Class = ____lualib.__TS__Class +local __TS__ClassExtends = ____lualib.__TS__ClassExtends +local __TS__Decorate = ____lualib.__TS__Decorate +local ____exports = {} +local ____dota_ts_adapter = require("lib.dota_ts_adapter") +local BaseModifier = ____dota_ts_adapter.BaseModifier +local registerModifier = ____dota_ts_adapter.registerModifier +____exports.modifier_arsenal_necklace_amber_pendant = __TS__Class() +local modifier_arsenal_necklace_amber_pendant = ____exports.modifier_arsenal_necklace_amber_pendant +modifier_arsenal_necklace_amber_pendant.name = "modifier_arsenal_necklace_amber_pendant" +modifier_arsenal_necklace_amber_pendant.____file_path = "scripts/vscripts/arsenal/items/necklace.lua" +__TS__ClassExtends(modifier_arsenal_necklace_amber_pendant, BaseModifier) +function modifier_arsenal_necklace_amber_pendant.prototype.IsHidden(self) + return false +end +function modifier_arsenal_necklace_amber_pendant.prototype.IsPurgable(self) + return false +end +function modifier_arsenal_necklace_amber_pendant.prototype.RemoveOnDeath(self) + return false +end +function modifier_arsenal_necklace_amber_pendant.prototype.GetTexture(self) + return "item_pendant" +end +function modifier_arsenal_necklace_amber_pendant.prototype.DeclareFunctions(self) + return {MODIFIER_PROPERTY_MAGICAL_RESISTANCE_BONUS} +end +function modifier_arsenal_necklace_amber_pendant.prototype.GetModifierMagicalResistanceBonus(self) + return 8 +end +modifier_arsenal_necklace_amber_pendant = __TS__Decorate( + modifier_arsenal_necklace_amber_pendant, + modifier_arsenal_necklace_amber_pendant, + {registerModifier(nil)}, + {kind = "class", name = "modifier_arsenal_necklace_amber_pendant"} +) +____exports.modifier_arsenal_necklace_amber_pendant = modifier_arsenal_necklace_amber_pendant +____exports.modifier_arsenal_necklace_soul_locket = __TS__Class() +local modifier_arsenal_necklace_soul_locket = ____exports.modifier_arsenal_necklace_soul_locket +modifier_arsenal_necklace_soul_locket.name = "modifier_arsenal_necklace_soul_locket" +modifier_arsenal_necklace_soul_locket.____file_path = "scripts/vscripts/arsenal/items/necklace.lua" +__TS__ClassExtends(modifier_arsenal_necklace_soul_locket, BaseModifier) +function modifier_arsenal_necklace_soul_locket.prototype.IsHidden(self) + return false +end +function modifier_arsenal_necklace_soul_locket.prototype.IsPurgable(self) + return false +end +function modifier_arsenal_necklace_soul_locket.prototype.RemoveOnDeath(self) + return false +end +function modifier_arsenal_necklace_soul_locket.prototype.GetTexture(self) + return "item_pipe" +end +function modifier_arsenal_necklace_soul_locket.prototype.DeclareFunctions(self) + return {MODIFIER_PROPERTY_MAGICAL_RESISTANCE_BONUS, MODIFIER_PROPERTY_SPELL_AMPLIFY_PERCENTAGE, MODIFIER_PROPERTY_HEALTH_BONUS} +end +function modifier_arsenal_necklace_soul_locket.prototype.GetModifierMagicalResistanceBonus(self) + return 18 +end +function modifier_arsenal_necklace_soul_locket.prototype.GetModifierSpellAmplify_Percentage(self) + return 12 +end +function modifier_arsenal_necklace_soul_locket.prototype.GetModifierHealthBonus(self) + return 200 +end +modifier_arsenal_necklace_soul_locket = __TS__Decorate( + modifier_arsenal_necklace_soul_locket, + modifier_arsenal_necklace_soul_locket, + {registerModifier(nil)}, + {kind = "class", name = "modifier_arsenal_necklace_soul_locket"} +) +____exports.modifier_arsenal_necklace_soul_locket = modifier_arsenal_necklace_soul_locket +return ____exports diff --git a/scripts/vscripts/arsenal/items/ring.lua b/scripts/vscripts/arsenal/items/ring.lua new file mode 100644 index 0000000..9c7c551 --- /dev/null +++ b/scripts/vscripts/arsenal/items/ring.lua @@ -0,0 +1,81 @@ +local ____lualib = require("lualib_bundle") +local __TS__Class = ____lualib.__TS__Class +local __TS__ClassExtends = ____lualib.__TS__ClassExtends +local __TS__Decorate = ____lualib.__TS__Decorate +local ____exports = {} +local ____dota_ts_adapter = require("lib.dota_ts_adapter") +local BaseModifier = ____dota_ts_adapter.BaseModifier +local registerModifier = ____dota_ts_adapter.registerModifier +____exports.modifier_arsenal_ring_copper_band = __TS__Class() +local modifier_arsenal_ring_copper_band = ____exports.modifier_arsenal_ring_copper_band +modifier_arsenal_ring_copper_band.name = "modifier_arsenal_ring_copper_band" +modifier_arsenal_ring_copper_band.____file_path = "scripts/vscripts/arsenal/items/ring.lua" +__TS__ClassExtends(modifier_arsenal_ring_copper_band, BaseModifier) +function modifier_arsenal_ring_copper_band.prototype.IsHidden(self) + return false +end +function modifier_arsenal_ring_copper_band.prototype.IsPurgable(self) + return false +end +function modifier_arsenal_ring_copper_band.prototype.RemoveOnDeath(self) + return false +end +function modifier_arsenal_ring_copper_band.prototype.GetTexture(self) + return "item_ring_of_basilius" +end +function modifier_arsenal_ring_copper_band.prototype.DeclareFunctions(self) + return {MODIFIER_PROPERTY_MANA_REGEN_CONSTANT, MODIFIER_PROPERTY_MANA_BONUS} +end +function modifier_arsenal_ring_copper_band.prototype.GetModifierConstantManaRegen(self) + return 1 +end +function modifier_arsenal_ring_copper_band.prototype.GetModifierManaBonus(self) + return 100 +end +modifier_arsenal_ring_copper_band = __TS__Decorate( + modifier_arsenal_ring_copper_band, + modifier_arsenal_ring_copper_band, + {registerModifier(nil)}, + {kind = "class", name = "modifier_arsenal_ring_copper_band"} +) +____exports.modifier_arsenal_ring_copper_band = modifier_arsenal_ring_copper_band +____exports.modifier_arsenal_ring_void_signet = __TS__Class() +local modifier_arsenal_ring_void_signet = ____exports.modifier_arsenal_ring_void_signet +modifier_arsenal_ring_void_signet.name = "modifier_arsenal_ring_void_signet" +modifier_arsenal_ring_void_signet.____file_path = "scripts/vscripts/arsenal/items/ring.lua" +__TS__ClassExtends(modifier_arsenal_ring_void_signet, BaseModifier) +function modifier_arsenal_ring_void_signet.prototype.IsHidden(self) + return false +end +function modifier_arsenal_ring_void_signet.prototype.IsPurgable(self) + return false +end +function modifier_arsenal_ring_void_signet.prototype.RemoveOnDeath(self) + return false +end +function modifier_arsenal_ring_void_signet.prototype.GetTexture(self) + return "item_void_stone" +end +function modifier_arsenal_ring_void_signet.prototype.DeclareFunctions(self) + return {MODIFIER_PROPERTY_STATS_INTELLECT_BONUS, MODIFIER_PROPERTY_STATS_STRENGTH_BONUS, MODIFIER_PROPERTY_STATS_AGILITY_BONUS, MODIFIER_PROPERTY_SPELL_AMPLIFY_PERCENTAGE} +end +function modifier_arsenal_ring_void_signet.prototype.GetModifierBonusStats_Intellect(self) + return 18 +end +function modifier_arsenal_ring_void_signet.prototype.GetModifierBonusStats_Strength(self) + return 18 +end +function modifier_arsenal_ring_void_signet.prototype.GetModifierBonusStats_Agility(self) + return 18 +end +function modifier_arsenal_ring_void_signet.prototype.GetModifierSpellAmplify_Percentage(self) + return 8 +end +modifier_arsenal_ring_void_signet = __TS__Decorate( + modifier_arsenal_ring_void_signet, + modifier_arsenal_ring_void_signet, + {registerModifier(nil)}, + {kind = "class", name = "modifier_arsenal_ring_void_signet"} +) +____exports.modifier_arsenal_ring_void_signet = modifier_arsenal_ring_void_signet +return ____exports diff --git a/scripts/vscripts/arsenal/items/weapon.lua b/scripts/vscripts/arsenal/items/weapon.lua new file mode 100644 index 0000000..367fbd0 --- /dev/null +++ b/scripts/vscripts/arsenal/items/weapon.lua @@ -0,0 +1,72 @@ +local ____lualib = require("lualib_bundle") +local __TS__Class = ____lualib.__TS__Class +local __TS__ClassExtends = ____lualib.__TS__ClassExtends +local __TS__Decorate = ____lualib.__TS__Decorate +local ____exports = {} +local ____dota_ts_adapter = require("lib.dota_ts_adapter") +local BaseModifier = ____dota_ts_adapter.BaseModifier +local registerModifier = ____dota_ts_adapter.registerModifier +____exports.modifier_arsenal_weapon_iron_blade = __TS__Class() +local modifier_arsenal_weapon_iron_blade = ____exports.modifier_arsenal_weapon_iron_blade +modifier_arsenal_weapon_iron_blade.name = "modifier_arsenal_weapon_iron_blade" +modifier_arsenal_weapon_iron_blade.____file_path = "scripts/vscripts/arsenal/items/weapon.lua" +__TS__ClassExtends(modifier_arsenal_weapon_iron_blade, BaseModifier) +function modifier_arsenal_weapon_iron_blade.prototype.IsHidden(self) + return false +end +function modifier_arsenal_weapon_iron_blade.prototype.IsPurgable(self) + return false +end +function modifier_arsenal_weapon_iron_blade.prototype.RemoveOnDeath(self) + return false +end +function modifier_arsenal_weapon_iron_blade.prototype.GetTexture(self) + return "item_blade_of_alacrity" +end +function modifier_arsenal_weapon_iron_blade.prototype.DeclareFunctions(self) + return {MODIFIER_PROPERTY_PREATTACK_BONUS_DAMAGE} +end +function modifier_arsenal_weapon_iron_blade.prototype.GetModifierPreAttack_BonusDamage(self) + return 8 +end +modifier_arsenal_weapon_iron_blade = __TS__Decorate( + modifier_arsenal_weapon_iron_blade, + modifier_arsenal_weapon_iron_blade, + {registerModifier(nil)}, + {kind = "class", name = "modifier_arsenal_weapon_iron_blade"} +) +____exports.modifier_arsenal_weapon_iron_blade = modifier_arsenal_weapon_iron_blade +____exports.modifier_arsenal_weapon_storm_edge = __TS__Class() +local modifier_arsenal_weapon_storm_edge = ____exports.modifier_arsenal_weapon_storm_edge +modifier_arsenal_weapon_storm_edge.name = "modifier_arsenal_weapon_storm_edge" +modifier_arsenal_weapon_storm_edge.____file_path = "scripts/vscripts/arsenal/items/weapon.lua" +__TS__ClassExtends(modifier_arsenal_weapon_storm_edge, BaseModifier) +function modifier_arsenal_weapon_storm_edge.prototype.IsHidden(self) + return false +end +function modifier_arsenal_weapon_storm_edge.prototype.IsPurgable(self) + return false +end +function modifier_arsenal_weapon_storm_edge.prototype.RemoveOnDeath(self) + return false +end +function modifier_arsenal_weapon_storm_edge.prototype.GetTexture(self) + return "item_maelstrom" +end +function modifier_arsenal_weapon_storm_edge.prototype.DeclareFunctions(self) + return {MODIFIER_PROPERTY_PREATTACK_BONUS_DAMAGE, MODIFIER_PROPERTY_ATTACKSPEED_BONUS_CONSTANT} +end +function modifier_arsenal_weapon_storm_edge.prototype.GetModifierPreAttack_BonusDamage(self) + return 18 +end +function modifier_arsenal_weapon_storm_edge.prototype.GetModifierAttackSpeedBonus_Constant(self) + return 30 +end +modifier_arsenal_weapon_storm_edge = __TS__Decorate( + modifier_arsenal_weapon_storm_edge, + modifier_arsenal_weapon_storm_edge, + {registerModifier(nil)}, + {kind = "class", name = "modifier_arsenal_weapon_storm_edge"} +) +____exports.modifier_arsenal_weapon_storm_edge = modifier_arsenal_weapon_storm_edge +return ____exports diff --git a/scripts/vscripts/arsenal/marketplacemanager.lua b/scripts/vscripts/arsenal/marketplacemanager.lua new file mode 100644 index 0000000..a11d9df --- /dev/null +++ b/scripts/vscripts/arsenal/marketplacemanager.lua @@ -0,0 +1,1161 @@ +local ____lualib = require("lualib_bundle") +local __TS__Class = ____lualib.__TS__Class +local __TS__New = ____lualib.__TS__New +local __TS__ArrayIsArray = ____lualib.__TS__ArrayIsArray +local __TS__ArrayMap = ____lualib.__TS__ArrayMap +local __TS__StringTrim = ____lualib.__TS__StringTrim +local __TS__StringSplit = ____lualib.__TS__StringSplit +local __TS__ArrayFilter = ____lualib.__TS__ArrayFilter +local __TS__ObjectValues = ____lualib.__TS__ObjectValues +local __TS__StringSubstring = ____lualib.__TS__StringSubstring +local __TS__ObjectKeys = ____lualib.__TS__ObjectKeys +local __TS__TypeOf = ____lualib.__TS__TypeOf +local __TS__Number = ____lualib.__TS__Number +local Set = ____lualib.Set +local __TS__ArraySome = ____lualib.__TS__ArraySome +local __TS__Decorate = ____lualib.__TS__Decorate +local ____exports = {} +local ____tstl_2Dutils = require("lib.tstl-utils") +local reloadable = ____tstl_2Dutils.reloadable +local ____api_helper = require("api_helper") +local setApiHeaders = ____api_helper.setApiHeaders +local ____server_config = require("server_config") +local SERVER_CONFIG = ____server_config.SERVER_CONFIG +local ____ArsenalManager = require("arsenal.ArsenalManager") +local ArsenalManager = ____ArsenalManager.ArsenalManager +local ____store_manager = require("store_manager") +local StoreManager = ____store_manager.StoreManager +local LOG_PREFIX = "[MarketplaceManager]" +--- Вкл. шумные print (API, рефреш, слоты). По умолчанию выкл. +local MARKETPLACE_VERBOSE_LOG = false +--- Отдельно: разбор истории продаж / NetTable (вкл. при отладке пустой истории). +local MARKETPLACE_SALES_DEBUG_LOG = false +local function marketplaceLog(self, message) + if MARKETPLACE_VERBOSE_LOG then + print(message) + end +end +local function salesHistoryDebugLog(self, message) + if MARKETPLACE_SALES_DEBUG_LOG then + print((LOG_PREFIX .. "[sales] ") .. message) + end +end +local MARKET_MIN_PRICE_FREE = 5000 +--- Слоты активных лотов на игрока (без донат-расширения). +local MARKET_BASE_SLOTS_LIMIT = 8 +local MARKET_MAX_SLOTS_LIMIT = 8 +____exports.MarketplaceManagerClass = __TS__Class() +local MarketplaceManagerClass = ____exports.MarketplaceManagerClass +MarketplaceManagerClass.name = "MarketplaceManagerClass" +MarketplaceManagerClass.____file_path = "scripts/vscripts/arsenal/MarketplaceManager.lua" +function MarketplaceManagerClass.prototype.____constructor(self) + self.marketBuyInFlightByPlayer = {} + self.marketCreateInFlightByPlayer = {} +end +function MarketplaceManagerClass.getInstance(self) + if not ____exports.MarketplaceManagerClass._instance then + ____exports.MarketplaceManagerClass._instance = __TS__New(____exports.MarketplaceManagerClass) + end + return ____exports.MarketplaceManagerClass._instance +end +function MarketplaceManagerClass.prototype.initialize(self) + if not IsServer() then + return + end + self:registerListeners() + marketplaceLog(nil, LOG_PREFIX .. " initialized") +end +function MarketplaceManagerClass.prototype.registerListeners(self) + CustomGameEventManager:RegisterListener( + "arsenal_market_refresh", + function(_src, data) + local playerId = self:resolvePlayerId(data) + if playerId == nil then + return + end + local stats = self:parseStatKeys(data.stats) + self:refreshForPlayer(playerId, stats, false) + end + ) + CustomGameEventManager:RegisterListener( + "arsenal_market_create", + function(_src, data) + local playerId = self:resolvePlayerId(data) + if playerId == nil then + return + end + local instanceId = tostring(data.instance_id or "") + local priceFree = math.max( + 1, + math.floor(tonumber(data.price_free) or 0) + ) + self:createListing(playerId, instanceId, priceFree) + end + ) + CustomGameEventManager:RegisterListener( + "arsenal_market_buy", + function(_src, data) + local playerId = self:resolvePlayerId(data) + if playerId == nil then + return + end + local listingId = tostring(data.listing_id or "") + self:buyListing(playerId, listingId) + end + ) + CustomGameEventManager:RegisterListener( + "arsenal_market_cancel", + function(_src, data) + local playerId = self:resolvePlayerId(data) + if playerId == nil then + return + end + local listingId = tostring(data.listing_id or "") + self:cancelListing(playerId, listingId) + end + ) +end +function MarketplaceManagerClass.prototype.resolvePlayerId(self, data) + local ____opt_result_2 + if data ~= nil then + ____opt_result_2 = data.PlayerID + end + local ____opt_result_2_6 = ____opt_result_2 + if ____opt_result_2_6 == nil then + local ____opt_result_5 + if data ~= nil then + ____opt_result_5 = data.playerId + end + ____opt_result_2_6 = ____opt_result_5 + end + local ____opt_result_2_6_10 = ____opt_result_2_6 + if ____opt_result_2_6_10 == nil then + local ____opt_result_9 + if data ~= nil then + ____opt_result_9 = data.playerID + end + ____opt_result_2_6_10 = ____opt_result_9 + end + local raw = ____opt_result_2_6_10 + if raw == nil or raw == nil then + return nil + end + local n = tonumber(tostring(raw)) + if n == nil or n < 0 then + return nil + end + return n +end +function MarketplaceManagerClass.prototype.parseStatKeys(self, raw) + if not raw then + return {} + end + if __TS__ArrayIsArray(raw) then + return __TS__ArrayMap( + raw, + function(____, x) return tostring(x) end + ) + end + if type(raw) == "string" then + return __TS__ArrayFilter( + __TS__ArrayMap( + __TS__StringSplit(raw, ","), + function(____, x) return __TS__StringTrim(tostring(x)) end + ), + function(____, x) return #x > 0 end + ) + end + if type(raw) == "table" then + return __TS__ArrayMap( + __TS__ObjectValues(raw), + function(____, x) return tostring(x) end + ) + end + return {} +end +function MarketplaceManagerClass.prototype.getSteamId(self, playerId) + local steamId = PlayerResource:GetSteamAccountID(playerId) + if not steamId or steamId <= 0 then + return nil + end + return steamId +end +function MarketplaceManagerClass.prototype.sendResult(self, playerId, success, messageToken, op) + marketplaceLog( + nil, + (((((((LOG_PREFIX .. " result player=") .. tostring(playerId)) .. " ok=") .. tostring(success)) .. " msg=") .. messageToken) .. " op=") .. (op or "none") + ) + local player = PlayerResource:GetPlayer(playerId) + if not player then + return + end + local payload = {success = success, message = messageToken} + if op ~= nil then + payload.op = op + end + CustomGameEventManager:Send_ServerToPlayer(player, "arsenal_market_result", payload) +end +function MarketplaceManagerClass.prototype.callApi(self, method, url, body, cb) + marketplaceLog( + nil, + (((((LOG_PREFIX .. " api -> ") .. method) .. " ") .. url) .. " body=") .. (body and json.encode(body) or "{}") + ) + local req = CreateHTTPRequest(method, url) + setApiHeaders(nil, req) + if method == "POST" or method == "PUT" then + req:SetHTTPRequestRawPostBody( + "application/json", + json.encode(body or ({})) + ) + end + req:Send(function(result) + local ok = result.StatusCode >= 200 and result.StatusCode < 300 + local payload = nil + if result.Body and #result.Body > 0 then + local decoded = {json.decode(result.Body)} + if decoded and type(decoded) == "table" then + payload = decoded + end + end + local bodySize = type(result.Body) == "string" and #result.Body or 0 + marketplaceLog( + nil, + (((((((((LOG_PREFIX .. " api <- ") .. method) .. " ") .. url) .. " code=") .. tostring(result.StatusCode)) .. " ok=") .. tostring(ok)) .. " bodySize=") .. tostring(bodySize) + ) + if result.Body and #result.Body > 0 then + local preview = #result.Body > 220 and __TS__StringSubstring(result.Body, 0, 220) .. "..." or result.Body + marketplaceLog(nil, (LOG_PREFIX .. " api body preview=") .. preview) + end + if payload and type(payload) == "table" then + local sampleItems = payload.items + if sampleItems and __TS__ArrayIsArray(sampleItems) then + marketplaceLog( + nil, + (LOG_PREFIX .. " api payload items=") .. tostring(#sampleItems) + ) + else + local keys = table.concat( + __TS__ObjectKeys(payload), + "," + ) + marketplaceLog(nil, ((LOG_PREFIX .. " api payload keys=[") .. keys) .. "]") + end + end + if MARKETPLACE_SALES_DEBUG_LOG and (string.find(url, "arsenal_market/sales", nil, true) or 0) - 1 >= 0 then + local rawBody = type(result.Body) == "string" and result.Body or "" + local preview = #rawBody <= 0 and "(empty body)" or (#rawBody > 380 and __TS__StringSubstring(rawBody, 0, 380) .. "..." or rawBody) + salesHistoryDebugLog( + nil, + (((((("HTTP GET sales code=" .. tostring(result.StatusCode)) .. " ok=") .. tostring(ok)) .. " bodyLen=") .. tostring(bodySize)) .. " preview=") .. preview + ) + end + if cb ~= nil then + cb(nil, ok, payload) + end + end) +end +function MarketplaceManagerClass.prototype.unwrapPayload(self, payload) + if not payload or type(payload) ~= "table" then + return payload + end + if __TS__ArrayIsArray(payload) then + for ____, part in ipairs(payload) do + do + if not part or type(part) ~= "table" then + goto __continue50 + end + if __TS__ArrayIsArray(part) then + return part + end + if part.items ~= nil or part.listings ~= nil or part.data ~= nil then + return part + end + end + ::__continue50:: + end + end + return payload +end +function MarketplaceManagerClass.prototype.extractListingsFromPayload(self, payload) + local p = self:unwrapPayload(payload) + if not p or type(p) ~= "table" then + return {} + end + if __TS__ArrayIsArray(p) then + return p + end + local direct = p.items + if __TS__ArrayIsArray(direct) then + return direct + end + local listings = p.listings + if __TS__ArrayIsArray(listings) then + return listings + end + local data = p.data + if __TS__ArrayIsArray(data) then + return data + end + if data and type(data) == "table" then + local di = data.items + if __TS__ArrayIsArray(di) then + return di + end + local dl = data.listings + if __TS__ArrayIsArray(dl) then + return dl + end + end + return {} +end +function MarketplaceManagerClass.prototype.extractSalesHistoryFromPayload(self, payload) + local function pickRowsArray(____, node) + if not node or type(node) ~= "table" then + return {rows = {}, source = "empty_node"} + end + if __TS__ArrayIsArray(node) then + return {rows = node, source = "nested_array"} + end + local o = node + for ____, k in ipairs({ + "items", + "sales", + "sales_history", + "history", + "rows" + }) do + local v = o[k] + if __TS__ArrayIsArray(v) then + return {rows = v, source = "object." .. k} + end + end + local data = o.data + if __TS__ArrayIsArray(data) then + return {rows = data, source = "object.data_array"} + end + if data and type(data) == "table" then + local ____data_items_13 = data.items + if ____data_items_13 == nil then + ____data_items_13 = data.sales + end + local di = ____data_items_13 + if __TS__ArrayIsArray(di) then + return {rows = di, source = "object.data.items_or_sales"} + end + end + return {rows = {}, source = "object_no_array"} + end + local rawType = (payload == nil or payload == nil) and "null" or __TS__TypeOf(payload) + local p = self:unwrapPayload(payload) + local unwrappedType = (p == nil or p == nil) and "null" or __TS__TypeOf(p) + local shape = "none" + local rows = {} + if not p then + salesHistoryDebugLog(nil, ("extract: rawType=" .. rawType) .. " unwrapped=null → 0 rows") + return {} + end + if __TS__ArrayIsArray(p) then + rows = p + shape = "unwrap_array" + elseif type(p) == "table" then + local picked = pickRowsArray(nil, p) + rows = picked.rows + shape = picked.source + else + salesHistoryDebugLog(nil, ((("extract: rawType=" .. rawType) .. " unwrapped=") .. unwrappedType) .. " → 0 rows (not object/array)") + return {} + end + local out = {} + for ____, row in ipairs(rows) do + do + if not row or type(row) ~= "table" then + goto __continue78 + end + local ____math_max_17 = math.max + local ____math_floor_16 = math.floor + local ____row_price_free_14 = row.price_free + if ____row_price_free_14 == nil then + ____row_price_free_14 = row.priceFree + end + local ____row_price_free_14_15 = ____row_price_free_14 + if ____row_price_free_14_15 == nil then + ____row_price_free_14_15 = 0 + end + local price = ____math_max_17( + 0, + ____math_floor_16(__TS__Number(____row_price_free_14_15)) + ) + local ____math_max_21 = math.max + local ____math_floor_20 = math.floor + local ____row_commission_free_18 = row.commission_free + if ____row_commission_free_18 == nil then + ____row_commission_free_18 = row.commissionFree + end + local ____row_commission_free_18_19 = ____row_commission_free_18 + if ____row_commission_free_18_19 == nil then + ____row_commission_free_18_19 = 0 + end + local commission = ____math_max_21( + 0, + ____math_floor_20(__TS__Number(____row_commission_free_18_19)) + ) + local ____math_max_25 = math.max + local ____math_floor_24 = math.floor + local ____row_seller_received_free_22 = row.seller_received_free + if ____row_seller_received_free_22 == nil then + ____row_seller_received_free_22 = row.sellerReceivedFree + end + local ____row_seller_received_free_22_23 = ____row_seller_received_free_22 + if ____row_seller_received_free_22_23 == nil then + ____row_seller_received_free_22_23 = 0 + end + local received = ____math_max_25( + 0, + ____math_floor_24(__TS__Number(____row_seller_received_free_22_23)) + ) + local ____tostring_28 = tostring + local ____row_listing_id_26 = row.listing_id + if ____row_listing_id_26 == nil then + ____row_listing_id_26 = row.listingId + end + local ____row_listing_id_26_27 = ____row_listing_id_26 + if ____row_listing_id_26_27 == nil then + ____row_listing_id_26_27 = "" + end + local ____tostring_28_result_42 = ____tostring_28(____row_listing_id_26_27) + local ____tostring_31 = tostring + local ____row_item_name_29 = row.item_name + if ____row_item_name_29 == nil then + ____row_item_name_29 = row.itemName + end + local ____row_item_name_29_30 = ____row_item_name_29 + if ____row_item_name_29_30 == nil then + ____row_item_name_29_30 = "" + end + local ____tostring_31_result_43 = ____tostring_31(____row_item_name_29_30) + local ____tostring_33 = tostring + local ____row_quality_32 = row.quality + if ____row_quality_32 == nil then + ____row_quality_32 = "common" + end + local ____tostring_33_result_44 = ____tostring_33(____row_quality_32) + local ____math_floor_36 = math.floor + local ____row_buyer_steam_id_34 = row.buyer_steam_id + if ____row_buyer_steam_id_34 == nil then + ____row_buyer_steam_id_34 = row.buyerSteamId + end + local ____row_buyer_steam_id_34_35 = ____row_buyer_steam_id_34 + if ____row_buyer_steam_id_34_35 == nil then + ____row_buyer_steam_id_34_35 = 0 + end + local ____math_floor_36_result_45 = ____math_floor_36(__TS__Number(____row_buyer_steam_id_34_35)) + local ____tostring_39 = tostring + local ____row_buyer_name_37 = row.buyer_name + if ____row_buyer_name_37 == nil then + ____row_buyer_name_37 = row.buyerName + end + local ____row_buyer_name_37_38 = ____row_buyer_name_37 + if ____row_buyer_name_37_38 == nil then + ____row_buyer_name_37_38 = "" + end + local ____tostring_39_result_46 = ____tostring_39(____row_buyer_name_37_38) + local ____temp_47 = received > 0 and received or math.max(0, price - commission) + local ____row_sold_at_40 = row.sold_at + if ____row_sold_at_40 == nil then + ____row_sold_at_40 = row.soldAt + end + local ____row_sold_at_40_41 = ____row_sold_at_40 + if ____row_sold_at_40_41 == nil then + ____row_sold_at_40_41 = row.updated_at + end + out[#out + 1] = { + listing_id = ____tostring_28_result_42, + item_name = ____tostring_31_result_43, + quality = ____tostring_33_result_44, + price_free = price, + buyer_steam_id = ____math_floor_36_result_45, + buyer_name = ____tostring_39_result_46, + commission_free = commission, + seller_received_free = ____temp_47, + sold_at = ____row_sold_at_40_41 + } + end + ::__continue78:: + end + local first = #out > 0 and out[1] or nil + salesHistoryDebugLog( + nil, + (((((((((("extract: rawType=" .. rawType) .. " unwrapped=") .. unwrappedType) .. " shape=") .. shape) .. " rawRows=") .. tostring(#rows)) .. " normalized=") .. tostring(#out)) .. " firstItem=") .. (first and tostring(first.item_name) or "-") + ) + return out +end +function MarketplaceManagerClass.prototype.extractInventoryInstancesFromPayload(self, payload) + local visited = __TS__New(Set) + local walk + walk = function(____, node, depth) + if not node or type(node) ~= "table" then + return nil + end + if visited:has(node) then + return nil + end + visited:add(node) + if depth > 6 then + return nil + end + local directInstances = node.instances + if directInstances and type(directInstances) == "table" then + return directInstances + end + local ai = node.arsenal_inventory + if ai and type(ai) == "table" and ai.instances and type(ai.instances) == "table" then + return ai.instances + end + local d = node.data + if d and type(d) == "table" then + local fromData = walk(nil, d, depth + 1) + if fromData then + return fromData + end + end + if __TS__ArrayIsArray(node) then + for ____, part in ipairs(node) do + local found = walk(nil, part, depth + 1) + if found then + return found + end + end + return nil + end + for k in pairs(node) do + do + local v = node[k] + if not v or type(v) ~= "table" then + goto __continue94 + end + local found = walk(nil, v, depth + 1) + if found then + return found + end + end + ::__continue94:: + end + return nil + end + return walk(nil, payload, 0) or ({}) +end +function MarketplaceManagerClass.prototype.collectCreateCandidateIds(self, localInstanceId, owned, localInstances, apiInstances) + local out = {} + local seen = {} + local function push(____, v) + if v == nil or v == nil then + return + end + local s = tostring(v) + if not s or #s <= 0 then + return + end + if seen[s] then + return + end + seen[s] = true + out[#out + 1] = type(v) == "number" and v or s + end + push( + nil, + __TS__Number(owned.globalSerial or 0) > 0 and __TS__Number(owned.globalSerial or 0) or nil + ) + push( + nil, + __TS__Number(owned.serial or 0) > 0 and __TS__Number(owned.serial or 0) or nil + ) + push(nil, localInstanceId) + push(nil, owned.instanceId) + push(nil, owned.instance_id) + local function scanBag(____, bag) + for key in pairs(bag) do + local row = bag[key] + local ____tostring_56 = tostring + local ____opt_result_50 + if row ~= nil then + ____opt_result_50 = row.instanceId + end + local ____opt_result_50_54 = ____opt_result_50 + if ____opt_result_50_54 == nil then + local ____opt_result_53 + if row ~= nil then + ____opt_result_53 = row.instance_id + end + ____opt_result_50_54 = ____opt_result_53 + end + local ____opt_result_50_54_55 = ____opt_result_50_54 + if ____opt_result_50_54_55 == nil then + ____opt_result_50_54_55 = "" + end + local rowInstance = ____tostring_56(____opt_result_50_54_55) + local ____opt_result_59 + if row ~= nil then + ____opt_result_59 = row.globalSerial + end + local ____opt_result_59_63 = ____opt_result_59 + if ____opt_result_59_63 == nil then + local ____opt_result_62 + if row ~= nil then + ____opt_result_62 = row.global_serial + end + ____opt_result_59_63 = ____opt_result_62 + end + local ____opt_result_59_63_64 = ____opt_result_59_63 + if ____opt_result_59_63_64 == nil then + ____opt_result_59_63_64 = 0 + end + local rowGlobal = __TS__Number(____opt_result_59_63_64) + local ____opt_result_67 + if row ~= nil then + ____opt_result_67 = row.serial + end + local ____opt_result_67_68 = ____opt_result_67 + if ____opt_result_67_68 == nil then + ____opt_result_67_68 = 0 + end + local rowSerial = __TS__Number(____opt_result_67_68) + local sameInstance = #rowInstance > 0 and rowInstance == localInstanceId + local sameGlobal = __TS__Number(owned.globalSerial or 0) > 0 and rowGlobal > 0 and rowGlobal == __TS__Number(owned.globalSerial or 0) + local sameSerial = __TS__Number(owned.serial or 0) > 0 and rowSerial > 0 and rowSerial == __TS__Number(owned.serial or 0) + if sameInstance or sameGlobal or sameSerial then + push(nil, key) + push(nil, rowInstance) + if rowGlobal > 0 then + push(nil, rowGlobal) + end + if rowSerial > 0 then + push(nil, rowSerial) + end + end + end + end + scanBag(nil, localInstances) + scanBag(nil, apiInstances) + return out +end +function MarketplaceManagerClass.prototype.extractSlotsFromPayload(self, payload, myListingsCount) + local p = self:unwrapPayload(payload) + if not p or type(p) ~= "table" then + return {slots_limit = MARKET_BASE_SLOTS_LIMIT, slots_used = myListingsCount} + end + local ____temp_69 + if p.data and type(p.data) == "table" then + ____temp_69 = p.data + else + ____temp_69 = p + end + local data = ____temp_69 + local ____data_slots_limit_70 = data.slots_limit + if ____data_slots_limit_70 == nil then + ____data_slots_limit_70 = data.market_slots_limit + end + local ____data_slots_limit_70_71 = ____data_slots_limit_70 + if ____data_slots_limit_70_71 == nil then + ____data_slots_limit_70_71 = MARKET_BASE_SLOTS_LIMIT + end + local rawLimit = __TS__Number(____data_slots_limit_70_71) + local ____data_slots_used_72 = data.slots_used + if ____data_slots_used_72 == nil then + ____data_slots_used_72 = data.market_slots_used + end + local ____data_slots_used_72_73 = ____data_slots_used_72 + if ____data_slots_used_72_73 == nil then + ____data_slots_used_72_73 = myListingsCount + end + local rawUsed = __TS__Number(____data_slots_used_72_73) + return { + slots_limit = math.max( + MARKET_BASE_SLOTS_LIMIT, + math.min( + MARKET_MAX_SLOTS_LIMIT, + math.floor(rawLimit or MARKET_BASE_SLOTS_LIMIT) + ) + ), + slots_used = math.max( + 0, + math.floor(rawUsed or myListingsCount) + ) + } +end +function MarketplaceManagerClass.prototype.setMarketTables(self, playerId, publicListings, myListings, slots, selectedStats, salesHistory) + marketplaceLog( + nil, + ((((((((((((((LOG_PREFIX .. " set tables player=") .. tostring(playerId)) .. " public=") .. tostring(#publicListings)) .. " my=") .. tostring(#myListings)) .. " sales=") .. tostring(#salesHistory)) .. " slots=") .. tostring(slots.slots_used or #myListings)) .. "/") .. tostring(slots.slots_limit or MARKET_BASE_SLOTS_LIMIT)) .. " stats=[") .. table.concat(selectedStats, ",")) .. "]" + ) + local statKeysSet = {} + for ____, row in ipairs(publicListings) do + for ____, k in ipairs(row.stat_keys or ({})) do + if k and #k > 0 then + statKeysSet[k] = true + end + end + for ____, p in ipairs(row.stats_snapshot or ({})) do + local k = tostring(p.key or "") + if #k > 0 then + statKeysSet[k] = true + end + end + end + CustomNetTables:SetTableValue( + "arsenal_market", + "listings_public", + { + listings = publicListings, + stat_keys = __TS__ObjectKeys(statKeysSet), + selected_stats = selectedStats, + ts = GameRules:GetGameTime() + } + ) + local salesJsonEncoded = json.encode(salesHistory) + salesHistoryDebugLog( + nil, + (((((("setMarketTables pid=" .. tostring(playerId)) .. " sales=") .. tostring(#salesHistory)) .. " sales_json_chars=") .. tostring(#salesJsonEncoded)) .. " my=") .. tostring(#myListings) + ) + CustomNetTables:SetTableValue( + "arsenal_market", + tostring(playerId), + { + my_listings = myListings, + sales_history_json = salesJsonEncoded, + slots_limit = slots.slots_limit or MARKET_BASE_SLOTS_LIMIT, + slots_used = slots.slots_used or #myListings, + ts = GameRules:GetGameTime() + } + ) +end +function MarketplaceManagerClass.prototype.refreshForAllConnectedPlayers(self) + do + local pid = 0 + while pid < DOTA_MAX_PLAYERS do + do + if not PlayerResource:IsValidPlayerID(pid) then + goto __continue121 + end + local playerId = pid + local player = PlayerResource:GetPlayer(playerId) + if not player then + goto __continue121 + end + self:refreshForPlayer(playerId, {}, true) + end + ::__continue121:: + pid = pid + 1 + end + end +end +function MarketplaceManagerClass.prototype.refreshForPlayer(self, playerId, selectedStats, silentOnError) + local steamId = self:getSteamId(playerId) + if not steamId then + return + end + marketplaceLog( + nil, + (((((((LOG_PREFIX .. " refresh player=") .. tostring(playerId)) .. " steam=") .. tostring(steamId)) .. " stats=[") .. table.concat(selectedStats, ",")) .. "] silent=") .. tostring(silentOnError) + ) + local statsQuery = #selectedStats > 0 and "?stats=" .. table.concat(selectedStats, ",") or "" + self:callApi( + "GET", + (SERVER_CONFIG.API_URL .. "/arsenal_market/listings") .. statsQuery, + nil, + function(____, okPub, pubPayload) + self:callApi( + "GET", + ((SERVER_CONFIG.API_URL .. "/player/") .. tostring(steamId)) .. "/arsenal_market/my_listings", + nil, + function(____, okMine, minePayload) + self:callApi( + "GET", + ((SERVER_CONFIG.API_URL .. "/player/") .. tostring(steamId)) .. "/arsenal_market/slots", + nil, + function(____, okSlots, slotsPayload) + self:callApi( + "GET", + ((SERVER_CONFIG.API_URL .. "/player/") .. tostring(steamId)) .. "/arsenal_market/sales", + nil, + function(____, okSales, salesPayload) + local publicListings = okPub and self:extractListingsFromPayload(pubPayload) or ({}) + local myListings = okMine and self:extractListingsFromPayload(minePayload) or ({}) + local slots = okSlots and self:extractSlotsFromPayload(slotsPayload, #myListings) or ({slots_limit = MARKET_BASE_SLOTS_LIMIT, slots_used = #myListings}) + salesHistoryDebugLog( + nil, + (((((("GET sales player=" .. tostring(playerId)) .. " steam=") .. tostring(steamId)) .. " ok=") .. tostring(okSales)) .. " payloadType=") .. ((salesPayload == nil or salesPayload == nil) and "null" or __TS__TypeOf(salesPayload)) + ) + local salesHistory = okSales and self:extractSalesHistoryFromPayload(salesPayload) or ({}) + salesHistoryDebugLog( + nil, + "GET sales → normalized=" .. tostring(#salesHistory) + ) + self:setMarketTables( + playerId, + publicListings, + myListings, + slots, + selectedStats, + salesHistory + ) + if (not okPub or not okMine or not okSlots) and not silentOnError then + self:sendResult(playerId, false, "#store_marketplace_error_fetch", "refresh") + end + end + ) + end + ) + end + ) + end + ) +end +function MarketplaceManagerClass.prototype.createListing(self, playerId, instanceId, priceFree) + marketplaceLog( + nil, + (((((LOG_PREFIX .. " create req player=") .. tostring(playerId)) .. " instance=") .. instanceId) .. " price=") .. tostring(priceFree) + ) + if not instanceId or priceFree < MARKET_MIN_PRICE_FREE then + self:sendResult(playerId, false, "#store_marketplace_error_min_price", "create") + return + end + local steamId = self:getSteamId(playerId) + if not steamId then + return + end + local owned = ArsenalManager:getInventoryInstance(playerId, instanceId) + if not owned then + self:sendResult(playerId, false, "#arsenal_item_not_owned", "create") + return + end + marketplaceLog( + nil, + (((((((((((LOG_PREFIX .. " create owned local instanceId=") .. tostring(owned.instanceId or "")) .. " serial=") .. tostring(owned.serial or 0)) .. " globalSerial=") .. tostring(owned.globalSerial or 0)) .. " item=") .. tostring(owned.itemName or "")) .. " quality=") .. tostring(owned.quality or "")) .. " lvl=") .. tostring(owned.upgradeLevel or 0) + ) + if ArsenalManager:isInstanceTradeLocked(playerId, instanceId) then + self:sendResult(playerId, false, "#store_marketplace_error_locked_item", "create") + return + end + local itemLevel = math.floor(__TS__Number(owned.upgradeLevel or 0)) + if itemLevel < 1 then + self:sendResult(playerId, false, "#store_marketplace_error_min_level_1", "create") + return + end + local pidKey = playerId + if self.marketCreateInFlightByPlayer[pidKey] then + self:sendResult(playerId, false, "#store_marketplace_error_create_in_progress", "create") + return + end + self.marketCreateInFlightByPlayer[pidKey] = true + local function clearCreateFlight() + self.marketCreateInFlightByPlayer[pidKey] = false + end + local inventoryPayload = ArsenalManager:buildInventoryPayloadForMarket(playerId) + local payloadInstances = inventoryPayload.instances or ({}) + marketplaceLog( + nil, + (LOG_PREFIX .. " create local payload instancesCount=") .. tostring(#__TS__ObjectKeys(payloadInstances)) + ) + local localRow = payloadInstances[instanceId] + if localRow then + marketplaceLog( + nil, + (((((((((LOG_PREFIX .. " create local payload hit key=") .. instanceId) .. " row.instanceId=") .. tostring(localRow.instanceId or "")) .. " row.instance_id=") .. tostring(localRow.instance_id or "")) .. " row.global_serial=") .. tostring(localRow.global_serial or 0)) .. " row.serial=") .. tostring(localRow.serial or 0) + ) + else + marketplaceLog(nil, (LOG_PREFIX .. " create local payload miss key=") .. instanceId) + end + self:callApi( + "PUT", + ((SERVER_CONFIG.API_URL .. "/player/") .. tostring(steamId)) .. "/arsenal_inventory", + {arsenal_inventory = inventoryPayload}, + function(____, _okSync) + self:callApi( + "GET", + ((SERVER_CONFIG.API_URL .. "/player/") .. tostring(steamId)) .. "/arsenal_inventory", + nil, + function(____, _okInv, invPayload) + local apiInstances = self:extractInventoryInstancesFromPayload(invPayload) + marketplaceLog( + nil, + (LOG_PREFIX .. " create api inventory instancesCount=") .. tostring(#__TS__ObjectKeys(apiInstances)) + ) + local byLocalKey = apiInstances[instanceId] + if byLocalKey then + marketplaceLog( + nil, + (((((((((LOG_PREFIX .. " create api hit localKey=") .. instanceId) .. " row.instanceId=") .. tostring(byLocalKey.instanceId or "")) .. " row.instance_id=") .. tostring(byLocalKey.instance_id or "")) .. " row.global_serial=") .. tostring(byLocalKey.global_serial or 0)) .. " row.serial=") .. tostring(byLocalKey.serial or 0) + ) + else + marketplaceLog(nil, (LOG_PREFIX .. " create api miss localKey=") .. instanceId) + end + local candidateIds = self:collectCreateCandidateIds(instanceId, owned, payloadInstances, apiInstances) + marketplaceLog( + nil, + ((LOG_PREFIX .. " create candidates=[") .. table.concat( + __TS__ArrayMap( + candidateIds, + function(____, x) return tostring(x) end + ), + "," + )) .. "]" + ) + self:callApi( + "GET", + ((SERVER_CONFIG.API_URL .. "/player/") .. tostring(steamId)) .. "/arsenal_market/slots", + nil, + function(____, okSlots, slotsPayload) + if not okSlots then + clearCreateFlight(nil) + self:sendResult(playerId, false, "#store_marketplace_error_fetch", "create") + return + end + local ____math_max_79 = math.max + local ____math_min_78 = math.min + local ____math_floor_77 = math.floor + local ____opt_result_76 + if slotsPayload ~= nil then + ____opt_result_76 = slotsPayload.slots_limit + end + local slotsLimit = ____math_max_79( + MARKET_BASE_SLOTS_LIMIT, + ____math_min_78( + MARKET_MAX_SLOTS_LIMIT, + ____math_floor_77(__TS__Number(____opt_result_76 or MARKET_BASE_SLOTS_LIMIT)) + ) + ) + local ____math_max_84 = math.max + local ____math_floor_83 = math.floor + local ____opt_result_82 + if slotsPayload ~= nil then + ____opt_result_82 = slotsPayload.slots_used + end + local slotsUsed = ____math_max_84( + 0, + ____math_floor_83(__TS__Number(____opt_result_82 or 0)) + ) + if slotsUsed >= slotsLimit then + clearCreateFlight(nil) + self:sendResult(playerId, false, "#store_marketplace_error_slots_full", "create") + return + end + local createUrl = ((SERVER_CONFIG.API_URL .. "/player/") .. tostring(steamId)) .. "/arsenal_market/create" + local function buildCreateBody(____, primaryInstanceId) + return { + instance_id = primaryInstanceId, + instanceId = instanceId, + item_instance_id = primaryInstanceId, + itemInstanceId = instanceId, + serial = __TS__Number(owned.serial or 0), + global_serial = __TS__Number(owned.globalSerial or 0), + globalSerial = __TS__Number(owned.globalSerial or 0), + item_name = tostring(owned.itemName or ""), + itemName = tostring(owned.itemName or ""), + quality = tostring(owned.quality or ""), + upgrade_level = __TS__Number(owned.upgradeLevel or 0), + upgradeLevel = __TS__Number(owned.upgradeLevel or 0), + price_free = priceFree, + priceFree = priceFree, + request_id = tostring(DoUniqueString("market_create")), + requestId = tostring(DoUniqueString("market_create_alt")) + } + end + local function onCreateSuccess() + ArsenalManager:removeInventoryInstanceForMarket(playerId, instanceId) + clearCreateFlight(nil) + ArsenalManager:loadFromServer(playerId) + self:refreshForPlayer(playerId, {}, true) + self:sendResult(playerId, true, "#store_marketplace_success_create", "create") + end + local tryCreateByIndex + tryCreateByIndex = function(____, idx) + if idx >= #candidateIds then + clearCreateFlight(nil) + self:sendResult(playerId, false, "#store_marketplace_error_create", "create") + return + end + local currentId = candidateIds[idx + 1] + marketplaceLog( + nil, + (((LOG_PREFIX .. " create try idx=") .. tostring(idx)) .. " candidate=") .. tostring(currentId) + ) + self:callApi( + "POST", + createUrl, + buildCreateBody(nil, currentId), + function(____, okTry, payloadTry) + if not okTry then + local ____tostring_93 = tostring + local ____opt_result_87 + if payloadTry ~= nil then + ____opt_result_87 = payloadTry.error + end + local ____opt_result_87_91 = ____opt_result_87 + if ____opt_result_87_91 == nil then + local ____opt_result_90 + if payloadTry ~= nil then + ____opt_result_90 = payloadTry.message + end + ____opt_result_87_91 = ____opt_result_90 + end + local ____opt_result_87_91_92 = ____opt_result_87_91 + if ____opt_result_87_91_92 == nil then + ____opt_result_87_91_92 = "" + end + local err = ____tostring_93(____opt_result_87_91_92) + marketplaceLog( + nil, + ((((((LOG_PREFIX .. " create try fail idx=") .. tostring(idx)) .. " candidate=") .. tostring(currentId)) .. " err=\"") .. err) .. "\"" + ) + if idx + 1 < #candidateIds then + tryCreateByIndex(nil, idx + 1) + return + end + clearCreateFlight(nil) + self:sendResult(playerId, false, "#store_marketplace_error_create", "create") + return + end + marketplaceLog( + nil, + (((LOG_PREFIX .. " create try success idx=") .. tostring(idx)) .. " candidate=") .. tostring(currentId) + ) + onCreateSuccess(nil) + end + ) + end + tryCreateByIndex(nil, 0) + end + ) + end + ) + end + ) +end +function MarketplaceManagerClass.prototype.buyListing(self, playerId, listingId) + marketplaceLog( + nil, + (((LOG_PREFIX .. " buy req player=") .. tostring(playerId)) .. " listing=") .. listingId + ) + if not listingId then + self:sendResult(playerId, false, "#store_marketplace_error_invalid_data", "buy") + return + end + local steamId = self:getSteamId(playerId) + if not steamId then + return + end + local pidKey = playerId + if self.marketBuyInFlightByPlayer[pidKey] then + self:sendResult(playerId, false, "#store_marketplace_error_buy_in_progress", "buy") + return + end + self.marketBuyInFlightByPlayer[pidKey] = true + local function clearBuyFlight() + self.marketBuyInFlightByPlayer[pidKey] = false + end + self:callApi( + "GET", + ((SERVER_CONFIG.API_URL .. "/player/") .. tostring(steamId)) .. "/arsenal_market/my_listings", + nil, + function(____, okMine, minePayload) + if okMine then + local ownListings = self:extractListingsFromPayload(minePayload) + local isOwn = __TS__ArraySome( + ownListings, + function(____, x) return tostring(x.listing_id or "") == listingId end + ) + if isOwn then + clearBuyFlight(nil) + self:sendResult(playerId, false, "#store_marketplace_error_buy", "buy") + return + end + end + self:callApi( + "POST", + ((SERVER_CONFIG.API_URL .. "/player/") .. tostring(steamId)) .. "/arsenal_market/buy", + { + listing_id = listingId, + request_id = tostring(DoUniqueString("market_buy")) + }, + function(____, ok, payload) + clearBuyFlight(nil) + if not ok then + self:sendResult(playerId, false, "#store_marketplace_error_buy", "buy") + return + end + ArsenalManager:loadFromServer(playerId) + StoreManager:getInstance():loadCurrencyFromServer(playerId) + self:refreshForPlayer(playerId, {}, true) + local ____opt_result_96 + if payload ~= nil then + ____opt_result_96 = payload.seller_steam_id + end + local sellerSteamId = __TS__Number(____opt_result_96 or 0) + if sellerSteamId > 0 then + local sellerPid = self:findConnectedPlayerBySteamId(sellerSteamId) + if sellerPid ~= nil then + ArsenalManager:loadFromServer(sellerPid) + StoreManager:getInstance():loadCurrencyFromServer(sellerPid) + self:refreshForPlayer(sellerPid, {}, true) + end + end + self:refreshForAllConnectedPlayers() + self:sendResult(playerId, true, "#store_marketplace_success_buy", "buy") + end + ) + end + ) +end +function MarketplaceManagerClass.prototype.cancelListing(self, playerId, listingId) + marketplaceLog( + nil, + (((LOG_PREFIX .. " cancel req player=") .. tostring(playerId)) .. " listing=") .. listingId + ) + if not listingId then + self:sendResult(playerId, false, "#store_marketplace_error_invalid_data", "cancel") + return + end + local steamId = self:getSteamId(playerId) + if not steamId then + return + end + self:callApi( + "POST", + ((SERVER_CONFIG.API_URL .. "/player/") .. tostring(steamId)) .. "/arsenal_market/cancel", + { + listing_id = listingId, + request_id = tostring(DoUniqueString("market_cancel")) + }, + function(____, ok) + if not ok then + self:sendResult(playerId, false, "#store_marketplace_error_cancel", "cancel") + return + end + ArsenalManager:loadFromServer(playerId) + self:refreshForPlayer(playerId, {}, true) + self:sendResult(playerId, true, "#store_marketplace_success_cancel", "cancel") + end + ) +end +function MarketplaceManagerClass.prototype.findConnectedPlayerBySteamId(self, steamId) + do + local pid = 0 + while pid < DOTA_MAX_PLAYERS do + do + if not PlayerResource:IsValidPlayerID(pid) then + goto __continue175 + end + if PlayerResource:GetSteamAccountID(pid) == steamId then + return pid + end + end + ::__continue175:: + pid = pid + 1 + end + end + return nil +end +MarketplaceManagerClass = __TS__Decorate(MarketplaceManagerClass, MarketplaceManagerClass, {reloadable}, {kind = "class", name = "MarketplaceManagerClass"}) +____exports.MarketplaceManagerClass = MarketplaceManagerClass +____exports.MarketplaceManager = ____exports.MarketplaceManagerClass:getInstance() +return ____exports diff --git a/scripts/vscripts/autopickup.lua b/scripts/vscripts/autopickup.lua new file mode 100644 index 0000000..84ae696 --- /dev/null +++ b/scripts/vscripts/autopickup.lua @@ -0,0 +1,47 @@ +--[[ Generated with https://github.com/TypeScriptToLua/TypeScriptToLua ]] +local ____exports = {} +local ____entity_radius = require("utils.entity_radius") +local findAllByClassnameInRadius = ____entity_radius.findAllByClassnameInRadius +CustomGameEventManager:RegisterListener( + "autopickup_ctrl_pressed", + function(_source, event) + local playerID = event.PlayerID + local player = PlayerResource:GetPlayer(playerID) + if not player then + return + end + local hero = player:GetAssignedHero() + if not hero or not hero:IsRealHero() then + return + end + local heroPos = hero:GetAbsOrigin() + local detectionRadius = 400 + local nearbyItems = findAllByClassnameInRadius("dota_item_drop", heroPos, detectionRadius) + local closestItem + local minDist = detectionRadius + 1 + for ____, item in ipairs(nearbyItems) do + do + if not IsValidEntity(item) then + goto __continue5 + end + local itemEntity = item + local itemPos = itemEntity:GetAbsOrigin() + local dist = (heroPos - itemPos):Length2D() + if dist < minDist then + minDist = dist + closestItem = itemEntity + end + end + ::__continue5:: + end + if closestItem then + ExecuteOrderFromTable({ + UnitIndex = hero:entindex(), + OrderType = DOTA_UNIT_ORDER_PICKUP_ITEM, + TargetIndex = closestItem:entindex(), + Queue = false + }) + end + end +) +return ____exports diff --git a/scripts/vscripts/battle_pass_duplicate_compensation.lua b/scripts/vscripts/battle_pass_duplicate_compensation.lua new file mode 100644 index 0000000..c07e670 --- /dev/null +++ b/scripts/vscripts/battle_pass_duplicate_compensation.lua @@ -0,0 +1,88 @@ +local ____lualib = require("lualib_bundle") +local __TS__ObjectAssign = ____lualib.__TS__ObjectAssign +local ____exports = {} +local ____card_catalog = require("card_catalog") +local buildCardPurchaseCompensationMap = ____card_catalog.buildCardPurchaseCompensationMap +local BP_DUPLICATE_COMPENSATION_BY_STORE_ITEM_ID = __TS__ObjectAssign( + { + hero_bloodhunter = {price_free = 260000, price_donate = 500}, + hero_sand_king = {price_free = 260000, price_donate = 500}, + hero_mirana = {price_free = 260000, price_donate = 500}, + hero_spectre = {price_free = 260000, price_donate = 500}, + hero_vengefulspirit = {price_free = 250000, price_donate = 250}, + skin_effect_fire = {price_donate = 120}, + skin_effect_ice = {price_donate = 600}, + skin_effect_bp_red = {price_donate = 120}, + skin_effect_bp_garden = {price_donate = 120}, + skin_effect_bp_golden = {price_donate = 120}, + skin_effect_bp_diretide_emblem = {price_donate = 120}, + skin_effect_bp_diretide_emblem_v1 = {price_donate = 120}, + skin_effect_bp_diretide_emblem_v3 = {price_donate = 120}, + skin_effect_fall_2021_emblem = {price_donate = 300}, + skin_effect_blue_gems = {price_donate = 300}, + skin_effect_sponsor = {price_donate = 1500}, + skin_effect_lotus = {price_donate = 300} + }, + buildCardPurchaseCompensationMap(nil), + { + chat_wheel_sound_i_am_sad = {price_donate = 10000}, + chat_wheel_sound_jump = {price_donate = 10000}, + chat_wheel_sound_agent_gabena = {price_free = 25000, price_donate = 50}, + chat_wheel_sound_byd_dobr_idi = {price_free = 25000, price_donate = 50}, + chat_wheel_sound_cat_shnapy = {price_free = 250000, price_donate = 150}, + chat_wheel_sound_dobro_pozhalovat_v_club = {price_free = 25000, price_donate = 50}, + chat_wheel_sound_get_out = {price_free = 25000, price_donate = 50}, + chat_wheel_sound_eto_prosto_okhueno = {price_free = 25000, price_donate = 50}, + chat_wheel_sound_ia_vas_unichtozhu = {price_free = 25000, price_donate = 50}, + chat_wheel_sound_kak_rulit = {price_free = 25000, price_donate = 50}, + chat_wheel_sound_kto_myaukaet = {price_free = 25000, price_donate = 50}, + chat_wheel_sound_Muhehehehe = {price_free = 25000, price_donate = 50}, + chat_wheel_sound_ne_tvoy_uroven_dorogoy = {price_free = 25000, price_donate = 50}, + chat_wheel_sound_ne_ponimaiu_karina_strimersha_slozhno_slozhno = {price_free = 25000, price_donate = 50}, + chat_wheel_sound_nikhuia_ne_ponial_no_ochen_interesno = {price_free = 25000, price_donate = 50}, + chat_wheel_sound_oi_tak_nravitsa = {price_free = 500000, price_donate = 500}, + chat_wheel_sound_ou_mai = {price_free = 25000, price_donate = 50}, + chat_wheel_sound_olyhi_bezdari_ogyzki = {price_free = 25000, price_donate = 50}, + chat_wheel_sound_po_syobam = {price_free = 25000, price_donate = 50}, + chat_wheel_sound_poshel_process = {price_free = 25000, price_donate = 50}, + chat_wheel_sound_posledniy_ponedelnik_zivesh = {price_free = 25000, price_donate = 50}, + chat_wheel_sound_rot_etogo_kazino = {price_free = 25000, price_donate = 50}, + chat_wheel_sound_s_kakoy_stati = {price_free = 25000, price_donate = 50}, + chat_wheel_sound_sir_no_sir = {price_free = 25000, price_donate = 50}, + chat_wheel_sound_sir_yes_sir = {price_free = 25000, price_donate = 50}, + chat_wheel_sound_stop_mne_ne_priyatno = {price_free = 25000, price_donate = 50}, + chat_wheel_sound_stoyat_ya_yzhe_eto_sosal = {price_free = 25000, price_donate = 50}, + chat_wheel_sound_ura_pobeda = {price_free = 25000, price_donate = 50}, + chat_wheel_sound_uvorot_ot_spelov = {price_free = 25000, price_donate = 50}, + chat_wheel_sound_v_komp_igri_igral = {price_free = 25000, price_donate = 50}, + chat_wheel_sound_vot_eto_nikhuia_sebe = {price_free = 25000, price_donate = 50}, + chat_wheel_sound_zachem_ya_suda_prishel = {price_free = 25000, price_donate = 50}, + chat_wheel_sound_kitty_flex = {price_free = 25000, price_donate = 50}, + chat_wheel_sound_eblo_razraba = {price_free = 25000, price_donate = 50}, + chat_wheel_sound_kuda = {price_free = 25000, price_donate = 50}, + chat_wheel_sound_bruh = {price_free = 25000, price_donate = 50}, + chat_wheel_sound_shizofreniya = {price_free = 25000, price_donate = 50}, + chat_wheel_sound_vot_eto_povorot = {price_free = 25000, price_donate = 50}, + chat_wheel_sound_fbi_open_up = {price_free = 25000, price_donate = 50}, + chat_wheel_sound_nepravilno_poprobuy_esche_raz = {price_free = 25000, price_donate = 50}, + chat_wheel_sound_dobro_pozhalovat_na_server_shizofreniya = {price_free = 25000, price_donate = 50}, + chat_wheel_sound_nya = {price_free = 25000, price_donate = 50}, + chat_wheel_sound_na_nas_napali = {price_free = 25000, price_donate = 50}, + chat_wheel_sound_murlok = {price_free = 25000, price_donate = 50}, + chat_wheel_sound_kak_zhit_to_a = {price_free = 25000, price_donate = 50}, + chat_wheel_sound_zaika = {price_free = 250000, price_donate = 500} + } +) +function ____exports.getBpDuplicateCompensationByStoreItemId(self, itemId) + local row = BP_DUPLICATE_COMPENSATION_BY_STORE_ITEM_ID[itemId] + if not row then + return {free_currency = 0, donate_currency = 0} + end + local pf = row.price_free + if pf ~= nil and pf > 0 then + return {free_currency = pf, donate_currency = 0} + end + local pd = row.price_donate or 0 + return {free_currency = 0, donate_currency = pd > 0 and pd or 0} +end +return ____exports diff --git a/scripts/vscripts/battle_pass_server.lua b/scripts/vscripts/battle_pass_server.lua new file mode 100644 index 0000000..3bd1a72 --- /dev/null +++ b/scripts/vscripts/battle_pass_server.lua @@ -0,0 +1,2191 @@ +local ____lualib = require("lualib_bundle") +local __TS__Class = ____lualib.__TS__Class +local Map = ____lualib.Map +local __TS__New = ____lualib.__TS__New +local Set = ____lualib.Set +local __TS__Iterator = ____lualib.__TS__Iterator +local __TS__ArrayForEach = ____lualib.__TS__ArrayForEach +local __TS__ArrayFind = ____lualib.__TS__ArrayFind +local __TS__Number = ____lualib.__TS__Number +local __TS__NumberIsFinite = ____lualib.__TS__NumberIsFinite +local __TS__ArrayIsArray = ____lualib.__TS__ArrayIsArray +local __TS__ObjectKeys = ____lualib.__TS__ObjectKeys +local __TS__ArrayMap = ____lualib.__TS__ArrayMap +local __TS__ArrayFilter = ____lualib.__TS__ArrayFilter +local ____exports = {} +local ____match_end_combat_stats = require("match_end_combat_stats") +local MatchEndCombatStats = ____match_end_combat_stats.MatchEndCombatStats +local ____server_config = require("server_config") +local SERVER_CONFIG = ____server_config.SERVER_CONFIG +local ____api_helper = require("api_helper") +local encodeApiBody = ____api_helper.encodeApiBody +local setApiHeaders = ____api_helper.setApiHeaders +local setApiHeadersLong = ____api_helper.setApiHeadersLong +local ____chat_wheel_grant = require("chat_wheel_grant") +local grantChatWheelSoundFromBattlePass = ____chat_wheel_grant.grantChatWheelSoundFromBattlePass +local ____store_manager = require("store_manager") +local StoreManager = ____store_manager.StoreManager +local ____player_info = require("player_info") +local PlayerInfo = ____player_info.PlayerInfo +local ____battle_pass_duplicate_compensation = require("battle_pass_duplicate_compensation") +local getBpDuplicateCompensationByStoreItemId = ____battle_pass_duplicate_compensation.getBpDuplicateCompensationByStoreItemId +local ____WaveManager = require("WaveManager") +local WaveManager = ____WaveManager.WaveManager +local ____QuestSystem = require("quests.QuestSystem") +local QuestEvents = ____QuestSystem.QuestEvents +local ENABLE_VERBOSE_BATTLE_PASS_SERVER_LOGS = true +local function rawPrintFn(____, ...) + _G:print(...) +end +local ____print = ENABLE_VERBOSE_BATTLE_PASS_SERVER_LOGS and rawPrintFn or (function(____, ...) return nil end) +--- Число из ответа API / тела события (VScript). +local function parseApiNumber(self, v) + if v == nil or v == nil then + return 0 + end + local n = tonumber(tostring(v)) + return n ~= nil and n ~= nil and n or 0 +end +--- Булевы поля из API (1 / "true" и т.д.) +local function apiJsonBool(self, v) + return v == true or v == 1 or v == "1" or v == "true" +end +____exports.BattlePassServer = __TS__Class() +local BattlePassServer = ____exports.BattlePassServer +BattlePassServer.name = "BattlePassServer" +BattlePassServer.____file_path = "scripts/vscripts/battle_pass_server.lua" +function BattlePassServer.prototype.____constructor(self) + self.serverUrl = SERVER_CONFIG.API_URL + self.playerQuestState = __TS__New(Map) + self.bpServerSnapshotByPlayer = __TS__New(Map) + self:setupEventListeners() + self:setupQuestTracking() + self:setupPlayerConnectionHandlers() + ____print(nil, "[BattlePassServer] Инициализирован") +end +function BattlePassServer.getInstance(self) + if not ____exports.BattlePassServer.instance then + ____exports.BattlePassServer.instance = __TS__New(____exports.BattlePassServer) + end + return ____exports.BattlePassServer.instance +end +function BattlePassServer.prototype.rememberBattlePassSnapshot(self, playerId, level, experience, hasPremium) + local prev = self.bpServerSnapshotByPlayer:get(playerId) + local ____temp_3 + if hasPremium ~= nil then + ____temp_3 = hasPremium + else + local ____temp_2 = prev and prev.has_premium + if ____temp_2 == nil then + ____temp_2 = false + end + ____temp_3 = ____temp_2 + end + local hp = ____temp_3 + self.bpServerSnapshotByPlayer:set(playerId, {level = level, experience = experience, has_premium = hp}) +end +function BattlePassServer.prototype.getBattlePassSnapshotForMatchEnd(self, playerId) + local s = self.bpServerSnapshotByPlayer:get(playerId) + if s then + return {level = s.level, experience = s.experience, has_premium = s.has_premium} + end + return {level = 1, experience = 0, has_premium = false} +end +function BattlePassServer.prototype.getNpcQuestsCompletedForPlayer(self, playerId) + local state = self.playerQuestState:get(playerId) + return state and state.npcQuestsCompleted or 0 +end +function BattlePassServer.prototype.getTotalHealToAlliesForPlayer(self, playerId) + return MatchEndCombatStats:getInstance():getHealToAlliesSum(playerId) +end +function BattlePassServer.prototype.pickDecodedJsonRoot(self, decoded, rootKeys) + if not decoded or type(decoded) ~= "table" then + return decoded + end + for ____, key in ipairs(rootKeys) do + local v = decoded[key] + if v ~= nil and v ~= nil then + return decoded + end + end + for ____, idx in ipairs({1, 0}) do + local inner = decoded[idx] + if inner and type(inner) == "table" then + for ____, key in ipairs(rootKeys) do + local v = inner[key] + if v ~= nil and v ~= nil then + return inner + end + end + end + end + return decoded +end +function BattlePassServer.prototype.setupPlayerConnectionHandlers(self) + ListenToGameEvent( + "player_connect_full", + function(event) + local playerId = event.PlayerID + if playerId ~= nil and playerId >= 0 then + Timers:CreateTimer( + 2, + function() + if PlayerResource:IsValidPlayer(playerId) and not PlayerResource:IsFakeClient(playerId) then + local steamId = tostring(PlayerResource:GetSteamAccountID(playerId)) + ____print( + nil, + ((("[BattlePassServer] Автоматическая загрузка квестов для игрока " .. tostring(playerId)) .. " (Steam ID: ") .. steamId) .. ")" + ) + self:loadQuests(playerId, steamId) + ____print( + nil, + "[BattlePassServer] Предзагрузка данных Battle Pass для игрока " .. tostring(playerId) + ) + self:loadBattlePassData(playerId, steamId) + end + return nil + end + ) + end + end, + nil + ) + ListenToGameEvent( + "npc_spawned", + function(event) + local unitIndex = event.entindex + if unitIndex == nil or unitIndex == nil then + return + end + local unit = EntIndexToHScript(unitIndex) + if unit and unit:IsRealHero() then + local playerId = unit:GetPlayerOwnerID() + if playerId >= 0 and not PlayerResource:IsFakeClient(playerId) then + self:onHeroSelected( + playerId, + unit:GetUnitName() + ) + Timers:CreateTimer( + 1, + function() + local state = self.playerQuestState:get(playerId) + local steamId = tostring(PlayerResource:GetSteamAccountID(playerId)) + if not state or not state.questsLoaded then + ____print( + nil, + ((("[BattlePassServer] Автоматическая загрузка квестов при спавне героя для игрока " .. tostring(playerId)) .. " (Steam ID: ") .. steamId) .. ")" + ) + self:loadQuests(playerId, steamId) + end + if not self.bpServerSnapshotByPlayer:has(playerId) then + ____print( + nil, + "[BattlePassServer] Догрузка Battle Pass при спавне героя для игрока " .. tostring(playerId) + ) + self:loadBattlePassData(playerId, steamId) + end + return nil + end + ) + end + end + end, + nil + ) +end +function BattlePassServer.prototype.setupQuestTracking(self) + ListenToGameEvent( + "entity_killed", + function(event) + local killedIndex = event.entindex_killed + if killedIndex == nil or killedIndex == nil then + return + end + local killedUnit = EntIndexToHScript(killedIndex) + if not killedUnit then + return + end + local killerIndex = event.entindex_attacker + local ____temp_6 + if killerIndex == nil or killerIndex == nil then + ____temp_6 = nil + else + ____temp_6 = EntIndexToHScript(killerIndex) + end + local killerEnt = ____temp_6 + if not killerEnt then + return + end + local hero + local killerHasIsRealHero = killerEnt.IsRealHero ~= nil + if killerHasIsRealHero and killerEnt:IsRealHero() then + hero = killerEnt + else + local ____opt_7 = killerEnt.GetOwner + local owner = ____opt_7 and ____opt_7(killerEnt) + local ____opt_result_11 + if owner ~= nil then + ____opt_result_11 = owner.IsRealHero + end + local ownerHasIsRealHero = ____opt_result_11 ~= nil + if owner and ownerHasIsRealHero and owner:IsRealHero() then + hero = owner + end + end + if not hero then + return + end + local playerId = hero:GetPlayerOwnerID() + if playerId < 0 then + return + end + local team = killedUnit:GetTeamNumber() + if team == DOTA_TEAM_NEUTRALS or team == DOTA_TEAM_BADGUYS then + local state = self:getOrCreateQuestState(playerId) + state.killCount = state.killCount + 1 + self:syncPlayerQuestProgress(playerId) + end + end, + nil + ) + Timers:CreateTimer( + 5, + function() + do + local function ____catch(e) + ____print( + nil, + "[BattlePassServer] Не удалось подписаться на OnWaveEnd: " .. tostring(e) + ) + end + local ____try, ____hasReturned = pcall(function() + WaveManager:getInstance():OnWaveEnd(function(____, info) + do + local i = 0 + while i < PlayerResource:GetPlayerCount() do + do + if not PlayerResource:IsValidPlayer(i) or PlayerResource:IsFakeClient(i) then + goto __continue52 + end + local state = self:getOrCreateQuestState(i) + state.wavesCompleted = info.waveIndex + self:syncPlayerQuestProgress(i) + end + ::__continue52:: + i = i + 1 + end + end + end) + ____print(nil, "[BattlePassServer] Зарегистрирован OnWaveEnd для квестов") + end) + if not ____try then + ____catch(____hasReturned) + end + end + return nil + end + ) + QuestEvents:OnQuestCompleted(function(____, data) + local questId = data.questId or "" + ____print( + nil, + "[BattlePassServer] OnQuestCompleted: questId=" .. tostring(questId) + ) + do + local i = 0 + while i < PlayerResource:GetPlayerCount() do + do + if not PlayerResource:IsValidPlayer(i) or PlayerResource:IsFakeClient(i) then + goto __continue56 + end + local state = self:getOrCreateQuestState(i) + state.npcQuestsCompleted = state.npcQuestsCompleted + 1 + local canSync = state.questsLoaded and #state.quests > 0 + if not canSync then + ____print( + nil, + ((("[BattlePassServer] Квесты ещё не загружены для игрока " .. tostring(i)) .. ", прогресс сохранён (npcQuestsCompleted=") .. tostring(state.npcQuestsCompleted)) .. ")" + ) + Timers:CreateTimer( + 3, + function() + self:syncPlayerQuestProgress(i) + return nil + end + ) + end + self:syncPlayerQuestProgress(i) + end + ::__continue56:: + i = i + 1 + end + end + end) + ListenToGameEvent( + "entity_killed", + function(event) + local killedIndex = event.entindex_killed + if killedIndex == nil or killedIndex == nil then + return + end + local killed = EntIndexToHScript(killedIndex) + local ____temp_14 = not killed + if not ____temp_14 then + local ____this_13 + ____this_13 = killed + local ____opt_12 = ____this_13.IsRealHero + if ____opt_12 ~= nil then + ____opt_12 = ____opt_12(____this_13) + end + ____temp_14 = not ____opt_12 + end + if ____temp_14 then + return + end + local playerId = killed:GetPlayerOwnerID() + if playerId < 0 then + return + end + local state = self:getOrCreateQuestState(playerId) + state.lastDeathTime = GameRules:GetGameTime() + self:syncPlayerQuestProgress(playerId) + end, + nil + ) + ListenToGameEvent( + "dota_item_picked_up", + function(event) + local heroIndex = event.HeroEntityIndex + local itemIndex = event.ItemEntityIndex + if heroIndex == nil or heroIndex == nil then + return + end + if itemIndex == nil or itemIndex == nil then + return + end + local hero = EntIndexToHScript(heroIndex) + local item = EntIndexToHScript(itemIndex) + if not hero or not item or not hero:IsRealHero() then + return + end + local playerId = hero:GetPlayerOwnerID() + if playerId < 0 then + return + end + local itemName = item:GetAbilityName() + local state = self:getOrCreateQuestState(playerId) + state.collectedItems[itemName] = (state.collectedItems[itemName] or 0) + 1 + self:syncPlayerQuestProgress(playerId) + end, + nil + ) + CustomGameEventManager:RegisterListener( + "cooking_claim_dish", + function(_, event) + local ____event_playerID_15 = event.playerID + if ____event_playerID_15 == nil then + ____event_playerID_15 = event.PlayerID + end + local playerId = ____event_playerID_15 + if playerId == nil or playerId < 0 then + return + end + local result = event.result + local state = self:getOrCreateQuestState(playerId) + state.cookingCompleted = state.cookingCompleted + 1 + if result == "item_grilled_meat" then + state.cookGrilledMeat = 1 + end + self:syncPlayerQuestProgress(playerId) + end + ) + CustomGameEventManager:RegisterListener( + "cooking_start", + function(_, event) + local ____event_PlayerID_16 = event.PlayerID + if ____event_PlayerID_16 == nil then + ____event_PlayerID_16 = event.playerID + end + local playerId = ____event_PlayerID_16 + if playerId == nil or playerId < 0 then + return + end + local state = self:getOrCreateQuestState(playerId) + state.campfireUses = state.campfireUses + 1 + self:syncPlayerQuestProgress(playerId) + end + ) + Timers:CreateTimer( + 15, + function() + self:syncAllQuestProgress() + return 30 + end + ) + ____print(nil, "[BattlePassServer] Квест-трекинг инициализирован") +end +function BattlePassServer.prototype.getOrCreateQuestState(self, playerId) + local state = self.playerQuestState:get(playerId) + if not state then + state = { + quests = {}, + killCount = 0, + blackShopBuys = 0, + blackShopBuysByQuality = {}, + npcQuestsCompleted = 0, + npcQuestsByNpc = {}, + wavesCompleted = 0, + gameStartTime = GameRules:GetGameTime(), + lastSyncTime = 0, + questsLoaded = false, + cookingCompleted = 0, + cookGrilledMeat = 0, + campfireUses = 0, + tipsGiven = 0, + lastDeathTime = 0, + collectedItems = {}, + heroesPlayed = __TS__New(Set) + } + self.playerQuestState:set(playerId, state) + end + return state +end +function BattlePassServer.prototype.onTipped(self, playerId) + local state = self:getOrCreateQuestState(playerId) + state.tipsGiven = state.tipsGiven + 1 + self:syncPlayerQuestProgress(playerId) +end +function BattlePassServer.prototype.onHealAlly(self, healerPlayerId, amount) + MatchEndCombatStats:getInstance():addHealToAllies(healerPlayerId, amount) + self:syncPlayerQuestProgress(healerPlayerId) +end +function BattlePassServer.prototype.onHeroSelected(self, playerId, heroName) + local state = self:getOrCreateQuestState(playerId) + state.heroesPlayed:add(heroName) + local steamId = tostring(PlayerResource:GetSteamAccountID(playerId)) + local request = CreateHTTPRequest("POST", ((self.serverUrl .. "/battlepass/") .. steamId) .. "/hero-played") + setApiHeaders(nil, request) + request:SetHTTPRequestRawPostBody( + "application/json", + json.encode({hero_name = heroName}) + ) + request:Send(function() + end) + self:syncPlayerQuestProgress(playerId) +end +function BattlePassServer.prototype.onBlackShopPurchase(self, playerId, quality) + local state = self:getOrCreateQuestState(playerId) + state.blackShopBuys = state.blackShopBuys + 1 + if quality then + local q = string.lower(quality) + state.blackShopBuysByQuality[q] = (state.blackShopBuysByQuality[q] or 0) + 1 + end + self:syncPlayerQuestProgress(playerId) +end +function BattlePassServer.prototype.getQuestProgress(self, playerId, quest) + local state = self.playerQuestState:get(playerId) + if not state then + return 0 + end + repeat + local ____switch85 = quest.type + local ____cond85 = ____switch85 == "kill_zombies" + if ____cond85 then + return state.killCount + end + ____cond85 = ____cond85 or ____switch85 == "survive_time" + if ____cond85 then + return math.floor(GameRules:GetGameTime() - state.gameStartTime) + end + ____cond85 = ____cond85 or ____switch85 == "buy_black_shop" + if ____cond85 then + return state.blackShopBuys + end + ____cond85 = ____cond85 or ____switch85 == "buy_black_shop_quality" + if ____cond85 then + do + local q = string.lower(quest.quality or "") + return q ~= "" and (state.blackShopBuysByQuality[q] or 0) or 0 + end + end + ____cond85 = ____cond85 or ____switch85 == "complete_npc_quest" + if ____cond85 then + return state.npcQuestsCompleted + end + ____cond85 = ____cond85 or ____switch85 == "complete_npc_quest_npc" + if ____cond85 then + do + local npc = quest.npc or "" + return npc ~= "" and (state.npcQuestsByNpc[npc] or 0) or 0 + end + end + ____cond85 = ____cond85 or ____switch85 == "earn_gold" + if ____cond85 then + do + do + local function ____catch(e) + return true, 0 + end + local ____try, ____hasReturned, ____returnValue = pcall(function() + return true, PlayerResource:GetTotalEarnedGold(playerId) + end) + if not ____try then + ____hasReturned, ____returnValue = ____catch(____hasReturned) + end + if ____hasReturned then + return ____returnValue + end + end + end + end + ____cond85 = ____cond85 or ____switch85 == "hero_level" + if ____cond85 then + do + local hero = PlayerResource:GetSelectedHeroEntity(playerId) + if hero then + return hero:GetLevel() + end + return 0 + end + end + ____cond85 = ____cond85 or ____switch85 == "survive_waves" + if ____cond85 then + return state.wavesCompleted + end + ____cond85 = ____cond85 or ____switch85 == "complete_cooking" + if ____cond85 then + return state.cookingCompleted + end + ____cond85 = ____cond85 or ____switch85 == "cook_grilled_meat" + if ____cond85 then + return state.cookGrilledMeat + end + ____cond85 = ____cond85 or ____switch85 == "use_campfire" + if ____cond85 then + return state.campfireUses + end + ____cond85 = ____cond85 or ____switch85 == "tip_teammate" + if ____cond85 then + return state.tipsGiven + end + ____cond85 = ____cond85 or ____switch85 == "no_death" + if ____cond85 then + do + if state.lastDeathTime > 0 then + return 0 + end + return math.floor(GameRules:GetGameTime() - state.gameStartTime) + end + end + ____cond85 = ____cond85 or ____switch85 == "heal_ally" + if ____cond85 then + return MatchEndCombatStats:getInstance():getHealToAlliesSum(playerId) + end + ____cond85 = ____cond85 or ____switch85 == "deal_damage" + if ____cond85 then + return MatchEndCombatStats:getInstance():getOutgoingDamageSum(playerId) + end + ____cond85 = ____cond85 or ____switch85 == "collect_item" + if ____cond85 then + do + local item = quest.target_item or "" + return item ~= "" and (state.collectedItems[item] or 0) or 0 + end + end + ____cond85 = ____cond85 or ____switch85 == "play_different_hero" + if ____cond85 then + return math.max(state.heroesPlayed.size, quest.progress or 0) + end + do + return 0 + end + until true +end +function BattlePassServer.prototype.syncPlayerQuestProgress(self, playerId) + local state = self.playerQuestState:get(playerId) + if not state or not state.questsLoaded or #state.quests == 0 then + return + end + if not PlayerResource:IsValidPlayer(playerId) or PlayerResource:IsFakeClient(playerId) then + return + end + local steamId = tostring(PlayerResource:GetSteamAccountID(playerId)) + local anyUpdated = false + for ____, quest in ipairs(state.quests) do + do + if quest.claimed then + goto __continue99 + end + local currentProgress = self:getQuestProgress(playerId, quest) + local newProgress = math.min(currentProgress, quest.target) + local newCompleted = newProgress >= quest.target + local wasCompleted = quest.completed + if newProgress ~= quest.progress or newCompleted ~= quest.completed then + quest.progress = newProgress + quest.completed = newCompleted + anyUpdated = true + self:syncQuestProgressToApi(steamId, quest.quest_id, newProgress) + if quest.completed and not wasCompleted then + ____print(nil, ("[BattlePassServer] Квест " .. quest.quest_id) .. " выполнен, автоматически забираем...") + self:claimQuest(playerId, steamId, quest.quest_id) + end + end + end + ::__continue99:: + end + if anyUpdated then + self:sendQuestsToClient(playerId, state.quests) + end +end +function BattlePassServer.prototype.syncAllQuestProgress(self) + for ____, ____value in __TS__Iterator(self.playerQuestState) do + local playerId = ____value[1] + local state = ____value[2] + do + if not state.questsLoaded or #state.quests == 0 then + goto __continue106 + end + if not PlayerResource:IsValidPlayer(playerId) or PlayerResource:IsFakeClient(playerId) then + goto __continue106 + end + local steamId = tostring(PlayerResource:GetSteamAccountID(playerId)) + local anyUpdated = false + for ____, quest in ipairs(state.quests) do + do + if quest.claimed then + goto __continue109 + end + local currentProgress = self:getQuestProgress(playerId, quest) + local newProgress = math.min(currentProgress, quest.target) + local newCompleted = newProgress >= quest.target + local wasCompleted = quest.completed + if newProgress ~= quest.progress or newCompleted ~= quest.completed then + quest.progress = newProgress + quest.completed = newCompleted + anyUpdated = true + self:syncQuestProgressToApi(steamId, quest.quest_id, newProgress) + if quest.completed and not wasCompleted then + ____print(nil, ("[BattlePassServer] Квест " .. quest.quest_id) .. " выполнен, автоматически забираем...") + self:claimQuest(playerId, steamId, quest.quest_id) + end + end + end + ::__continue109:: + end + if anyUpdated then + self:sendQuestsToClient(playerId, state.quests) + end + end + ::__continue106:: + end +end +function BattlePassServer.prototype.syncQuestProgressToApi(self, steamId, questId, progress) + local request = CreateHTTPRequest("POST", ((self.serverUrl .. "/battlepass/") .. steamId) .. "/quests/progress") + setApiHeaders(nil, request) + request:SetHTTPRequestRawPostBody( + "application/json", + json.encode({quest_id = questId, progress = progress}) + ) + request:Send(function(result) + if result.StatusCode >= 200 and result.StatusCode < 300 then + do + pcall(function() + local decoded = {json.decode(result.Body)} + local data = self:pickDecodedJsonRoot(decoded, {"success", "completed", "progress"}) + if data and apiJsonBool(nil, data.completed) then + ____print( + nil, + ((("[BattlePassServer] Квест " .. questId) .. " стал выполненным (прогресс: ") .. tostring(progress)) .. ")" + ) + end + end) + end + else + ____print( + nil, + (("[BattlePassServer] Ошибка синхронизации квеста " .. questId) .. ": ") .. tostring(result.StatusCode) + ) + end + end) +end +function BattlePassServer.prototype.sendQuestsToClient(self, playerId, quests) + local player = PlayerResource:GetPlayer(playerId) + if not player then + return + end + local questsObject = {} + __TS__ArrayForEach( + quests, + function(____, q, idx) + questsObject[tostring(idx)] = { + quest_id = q.quest_id, + type = q.type, + name = q.name, + description = q.description, + progress = q.progress, + target = q.target, + completed = q.completed and 1 or 0, + claimed = q.claimed and 1 or 0, + reward_exp = q.reward_exp, + reward_free_currency = q.reward_free_currency or 0 + } + end + ) + CustomGameEventManager:Send_ServerToPlayer(player, "battle_pass_quests_data", {quests = questsObject}) +end +function BattlePassServer.prototype.loadQuests(self, playerId, steamId) + ____print( + nil, + ((("[BattlePassServer] Запрос квестов для " .. steamId) .. " (playerId: ") .. tostring(playerId)) .. ")" + ) + local request = CreateHTTPRequest("GET", ((self.serverUrl .. "/battlepass/") .. steamId) .. "/quests") + setApiHeaders(nil, request) + request:Send(function(result) + local player = PlayerResource:GetPlayer(playerId) + if not player then + ____print( + nil, + ("[BattlePassServer] Игрок " .. tostring(playerId)) .. " не найден при получении квестов" + ) + return + end + if result.StatusCode == 0 then + ____print(nil, "[BattlePassServer] Сервер недоступен (StatusCode=0) для квестов") + return + end + if result.StatusCode >= 200 and result.StatusCode < 300 then + do + local function ____catch(e) + ____print( + nil, + "[BattlePassServer] Ошибка парсинга квестов: " .. tostring(e) + ) + ____print( + nil, + "[BattlePassServer] Тело ответа: " .. tostring(result.Body) + ) + end + local ____try, ____hasReturned = pcall(function() + local decoded = {json.decode(result.Body)} + ____print( + nil, + "[BattlePassServer] Ответ API квестов (raw): " .. tostring(result.Body) + ) + local data = self:pickDecodedJsonRoot(decoded, {"quests"}) + ____print( + nil, + "[BattlePassServer] Финальные данные: " .. json.encode(data) + ) + local questsData = nil + if data and type(data) == "table" then + if data.quests ~= nil and data.quests ~= nil then + questsData = data.quests + ____print(nil, "[BattlePassServer] ✓ Найдено через .quests") + elseif data.quests ~= nil and data.quests ~= nil then + questsData = data.quests + ____print(nil, "[BattlePassServer] ✓ Найдено через [\"quests\"]") + end + end + if questsData == nil or questsData == nil then + ____print(nil, "[BattlePassServer] ⚠️ quests не найдено в data") + end + local questsArray = {} + if questsData ~= nil and questsData ~= nil then + local rawQuests = {} + local index = 1 + while true do + do + local item = questsData[index] + if item == nil or item == nil then + if index == 1 then + index = 0 + goto __continue136 + end + break + end + rawQuests[#rawQuests + 1] = item + index = index + 1 + end + ::__continue136:: + end + if #rawQuests == 0 and type(questsData) == "table" then + for key in pairs(questsData) do + local item = questsData[key] + if item and type(item) == "table" then + rawQuests[#rawQuests + 1] = item + end + end + end + ____print( + nil, + ("[BattlePassServer] Получено " .. tostring(#rawQuests)) .. " квестов из API" + ) + for ____, q in ipairs(rawQuests) do + if q and type(q) == "table" then + local questObj = { + quest_id = q.quest_id or "", + type = q.type or "unknown", + name = q.name or q.quest_id or "", + description = q.description or "", + progress = q.progress or 0, + target = q.target or 1, + completed = apiJsonBool(nil, q.completed), + claimed = apiJsonBool(nil, q.claimed), + reward_exp = q.reward_exp or 0, + reward_free_currency = q.reward_free_currency or 0 + } + if q.quality then + questObj.quality = q.quality + end + if q.npc then + questObj.npc = q.npc + end + if q.target_item then + questObj.target_item = q.target_item + end + questsArray[#questsArray + 1] = questObj + end + end + else + ____print(nil, "[BattlePassServer] ⚠️ questsData пусто или null после всех попыток") + end + local state = self:getOrCreateQuestState(playerId) + state.quests = questsArray + state.questsLoaded = true + for ____, quest in ipairs(state.quests) do + do + if quest.claimed then + goto __continue150 + end + local currentProgress = self:getQuestProgress(playerId, quest) + local newProgress = math.min(currentProgress, quest.target) + local newCompleted = newProgress >= quest.target + local wasCompleted = quest.completed + if newProgress > quest.progress or newCompleted ~= quest.completed then + quest.progress = newProgress + quest.completed = newCompleted + self:syncQuestProgressToApi(steamId, quest.quest_id, newProgress) + end + if quest.completed and not quest.claimed then + ____print(nil, ("[BattlePassServer] Квест " .. quest.quest_id) .. " уже выполнен, автоматически забираем...") + self:claimQuest(playerId, steamId, quest.quest_id) + end + end + ::__continue150:: + end + ____print( + nil, + (("[BattlePassServer] Загружено " .. tostring(#questsArray)) .. " квестов для ") .. steamId + ) + self:sendQuestsToClient(playerId, state.quests) + Timers:CreateTimer( + 0.5, + function() + self:syncPlayerQuestProgress(playerId) + return nil + end + ) + end) + if not ____try then + ____catch(____hasReturned) + end + end + else + ____print( + nil, + "[BattlePassServer] Ошибка загрузки квестов: " .. tostring(result.StatusCode) + ) + ____print( + nil, + "[BattlePassServer] Тело ответа: " .. tostring(result.Body) + ) + end + end) +end +function BattlePassServer.prototype.claimQuest(self, playerId, steamId, questId) + ____print( + nil, + (((("[BattlePassServer] claimQuest: playerId=" .. tostring(playerId)) .. ", steamId=") .. steamId) .. ", questId=") .. questId + ) + local state = self.playerQuestState:get(playerId) + if not state then + ____print( + nil, + "[BattlePassServer] ⚠️ Состояние квестов не найдено для playerId=" .. tostring(playerId) + ) + return + end + local quest = __TS__ArrayFind( + state.quests, + function(____, q) return q.quest_id == questId end + ) + if not quest then + ____print(nil, ("[BattlePassServer] ⚠️ Квест " .. questId) .. " не найден в локальном состоянии") + local player = PlayerResource:GetPlayer(playerId) + if player then + CustomGameEventManager:Send_ServerToPlayer(player, "battle_pass_quest_claim_result", {success = false, error = "Задание не найдено"}) + end + return + end + ____print( + nil, + (((((("[BattlePassServer] Квест найден: completed=" .. tostring(quest.completed)) .. ", claimed=") .. tostring(quest.claimed)) .. ", progress=") .. tostring(quest.progress)) .. "/") .. tostring(quest.target) + ) + if not quest.completed or quest.claimed then + ____print( + nil, + (("[BattlePassServer] ⚠️ Квест не может быть забран: completed=" .. tostring(quest.completed)) .. ", claimed=") .. tostring(quest.claimed) + ) + local player = PlayerResource:GetPlayer(playerId) + if player then + CustomGameEventManager:Send_ServerToPlayer(player, "battle_pass_quest_claim_result", {success = false, error = "Задание не выполнено или уже забрано"}) + end + return + end + ____print(nil, "[BattlePassServer] Отправляем запрос на клейм квеста " .. questId) + local request = CreateHTTPRequest("POST", ((self.serverUrl .. "/battlepass/") .. steamId) .. "/quests/claim") + setApiHeadersLong(nil, request) + request:SetHTTPRequestRawPostBody( + "application/json", + json.encode({quest_id = questId}) + ) + request:Send(function(result) + local player = PlayerResource:GetPlayer(playerId) + if not player then + return + end + if result.StatusCode >= 200 and result.StatusCode < 300 then + do + local function ____catch(e) + ____print( + nil, + "[BattlePassServer] Ошибка парсинга claim квеста: " .. tostring(e) + ) + ____print( + nil, + "[BattlePassServer] Тело ответа: " .. tostring(result.Body) + ) + end + local ____try, ____hasReturned = pcall(function() + local decoded = {json.decode(result.Body)} + ____print( + nil, + "[BattlePassServer] Ответ claim квеста (raw): " .. tostring(result.Body) + ) + local data = self:pickDecodedJsonRoot(decoded, {"success", "reward_exp", "quest_id", "new_level"}) + ____print( + nil, + "[BattlePassServer] Данные claim: " .. json.encode(data) + ) + quest.claimed = true + local rewardExpNum = __TS__Number(data.reward_exp) + local rewardExp = __TS__NumberIsFinite(rewardExpNum) and rewardExpNum or 0 + local rewardFreeNum = __TS__Number(data.reward_free_currency) + local rewardFreeCurrency = __TS__NumberIsFinite(rewardFreeNum) and rewardFreeNum or 0 + local newLevelNum = __TS__Number(data.new_level) + local newLevel = __TS__NumberIsFinite(newLevelNum) and newLevelNum or 0 + local newExpNum = __TS__Number(data.new_experience) + local newExp = __TS__NumberIsFinite(newExpNum) and newExpNum or 0 + ____print( + nil, + (((((((("[BattlePassServer] Квест " .. questId) .. " забран для ") .. steamId) .. ", +") .. tostring(rewardExp)) .. " BP XP, новый уровень: ") .. tostring(newLevel)) .. ", новый опыт: ") .. tostring(newExp) + ) + CustomGameEventManager:Send_ServerToPlayer(player, "battle_pass_quest_claim_result", { + success = true, + quest_id = questId, + reward_exp = rewardExp, + reward_free_currency = rewardFreeCurrency, + new_level = newLevel, + new_experience = newExp + }) + self:sendQuestsToClient(playerId, state.quests) + ____print(nil, "[BattlePassServer] Перезагружаем данные BP после клейма квеста...") + self:loadBattlePassData(playerId, steamId) + end) + if not ____try then + ____catch(____hasReturned) + end + end + else + local errorMsg = "Ошибка: " .. tostring(result.StatusCode) + ____print( + nil, + "[BattlePassServer] Ошибка клейма квеста: StatusCode=" .. tostring(result.StatusCode) + ) + ____print( + nil, + "[BattlePassServer] Тело ответа: " .. tostring(result.Body) + ) + do + local function ____catch(e) + ____print( + nil, + "[BattlePassServer] Ошибка парсинга ответа об ошибке: " .. tostring(e) + ) + end + local ____try, ____hasReturned = pcall(function() + local body = {json.decode(result.Body)} + local errorBody = body + if body and type(body) == "table" and body[1] ~= nil then + errorBody = body[1] + end + if errorBody and errorBody.error then + errorMsg = errorBody.error + ____print(nil, "[BattlePassServer] Сообщение об ошибке: " .. errorMsg) + end + end) + if not ____try then + ____catch(____hasReturned) + end + end + CustomGameEventManager:Send_ServerToPlayer(player, "battle_pass_quest_claim_result", {success = false, error = errorMsg}) + end + end) +end +function BattlePassServer.prototype.setupEventListeners(self) + CustomGameEventManager:RegisterListener( + "request_battle_pass", + function(source, event) + local playerId = event.PlayerID + local steamId = tostring(PlayerResource:GetSteamAccountID(playerId)) + ____print( + nil, + (("[BattlePassServer] Запрос данных Battle Pass для игрока " .. tostring(playerId)) .. ", Steam ID: ") .. steamId + ) + self:loadBattlePassData(playerId, steamId) + end + ) + CustomGameEventManager:RegisterListener( + "battle_pass_claim_reward", + function(source, event) + local playerId = event.PlayerID + local level = event.level + local rawPrem = event.is_premium + local isPremium = rawPrem == 1 or rawPrem == true or rawPrem == "1" + local steamId = tostring(PlayerResource:GetSteamAccountID(playerId)) + ____print( + nil, + (((("[BattlePassServer] Запрос на забор награды уровня " .. tostring(level)) .. " (premium: ") .. tostring(isPremium)) .. ") для игрока ") .. tostring(playerId) + ) + self:claimReward(playerId, steamId, level, isPremium) + end + ) + CustomGameEventManager:RegisterListener( + "buy_battle_pass_premium", + function(source, event) + local playerId = event.PlayerID + local steamId = tostring(PlayerResource:GetSteamAccountID(playerId)) + ____print( + nil, + "[BattlePassServer] Запрос на покупку Battle Pass Premium для игрока " .. tostring(playerId) + ) + self:buyPremium(playerId, steamId) + end + ) + CustomGameEventManager:RegisterListener( + "claim_all_battle_pass_rewards", + function(source, event) + local playerId = event.PlayerID + local steamId = tostring(PlayerResource:GetSteamAccountID(playerId)) + ____print( + nil, + "[BattlePassServer] Запрос на забор ВСЕХ наград для игрока " .. tostring(playerId) + ) + self:claimAllRewards(playerId, steamId) + end + ) + CustomGameEventManager:RegisterListener( + "request_battle_pass_quests", + function(source, event) + local playerId = event.PlayerID + local steamId = tostring(PlayerResource:GetSteamAccountID(playerId)) + ____print( + nil, + "[BattlePassServer] Запрос квестов для игрока " .. tostring(playerId) + ) + self:loadQuests(playerId, steamId) + end + ) + CustomGameEventManager:RegisterListener( + "claim_battle_pass_quest", + function(source, event) + local playerId = event.PlayerID + local questId = event.quest_id + local steamId = tostring(PlayerResource:GetSteamAccountID(playerId)) + ____print( + nil, + (("[BattlePassServer] Клейм квеста " .. questId) .. " для игрока ") .. tostring(playerId) + ) + self:claimQuest(playerId, steamId, questId) + end + ) +end +function BattlePassServer.prototype.loadBattlePassData(self, playerId, steamId) + local request = CreateHTTPRequest("GET", (self.serverUrl .. "/battlepass/") .. steamId) + setApiHeaders(nil, request) + request:Send(function(result) + do + local function ____catch(____error) + ____print( + nil, + "[BattlePassServer] Ошибка обработки ответа: " .. tostring(____error) + ) + local player = PlayerResource:GetPlayer(playerId) + if player then + CustomGameEventManager:Send_ServerToPlayer(player, "battle_pass_data", {error = "Ошибка обработки данных"}) + end + end + local ____try, ____hasReturned, ____returnValue = pcall(function() + local player = PlayerResource:GetPlayer(playerId) + if not player then + return true + end + if result.StatusCode == 0 then + ____print(nil, "[BattlePassServer] Сервер недоступен (StatusCode=0)") + CustomGameEventManager:Send_ServerToPlayer(player, "battle_pass_data", {error = "Сервер недоступен"}) + return true + end + if result.StatusCode >= 200 and result.StatusCode < 300 then + do + local function ____catch(e) + ____print( + nil, + "[BattlePassServer] Ошибка парсинга JSON: " .. tostring(e) + ) + CustomGameEventManager:Send_ServerToPlayer(player, "battle_pass_data", {error = "Ошибка парсинга данных"}) + end + local ____try, ____hasReturned = pcall(function() + local responseData = {json.decode(result.Body)} + local data = nil + if __TS__ArrayIsArray(responseData) and #responseData > 0 then + data = responseData[1] + elseif responseData and type(responseData) == "table" then + data = responseData + end + if data then + local claimedRewards = data.claimed_rewards or ({}) + local claimedObject = {} + if __TS__ArrayIsArray(claimedRewards) then + __TS__ArrayForEach( + claimedRewards, + function(____, level, index) + claimedObject[tostring(index)] = level + end + ) + elseif type(claimedRewards) == "table" then + for key in pairs(claimedRewards) do + if rawget(claimedRewards, key) ~= nil then + claimedObject[key] = claimedRewards[key] + end + end + end + local claimedPremiumRewards = data.claimed_premium_rewards or ({}) + local claimedPremiumObject = {} + if __TS__ArrayIsArray(claimedPremiumRewards) then + __TS__ArrayForEach( + claimedPremiumRewards, + function(____, level, index) + claimedPremiumObject[tostring(index)] = level + end + ) + elseif type(claimedPremiumRewards) == "table" then + for key in pairs(claimedPremiumRewards) do + if rawget(claimedPremiumRewards, key) ~= nil then + claimedPremiumObject[key] = claimedPremiumRewards[key] + end + end + end + ____print( + nil, + (((((((("[BattlePassServer] Данные загружены: level=" .. tostring(data.level)) .. ", exp=") .. tostring(data.experience)) .. ", claimed=") .. tostring(#__TS__ObjectKeys(claimedObject))) .. ", premium=") .. tostring(data.has_premium)) .. ", claimed_premium=") .. tostring(#__TS__ObjectKeys(claimedPremiumObject)) + ) + self:rememberBattlePassSnapshot( + playerId, + parseApiNumber(nil, data.level), + parseApiNumber(nil, data.experience), + apiJsonBool(nil, data.has_premium) + ) + CustomGameEventManager:Send_ServerToPlayer(player, "battle_pass_data", { + level = data.level or 0, + experience = data.experience or 0, + claimed_rewards = claimedObject, + has_premium = data.has_premium and 1 or 0, + claimed_premium_rewards = claimedPremiumObject + }) + else + ____print(nil, "[BattlePassServer] Данные не найдены, создаем новый Battle Pass для " .. steamId) + self:createBattlePass(playerId, steamId) + end + end) + if not ____try then + ____catch(____hasReturned) + end + end + elseif result.StatusCode == 404 then + ____print(nil, "[BattlePassServer] 404 - создаем новый Battle Pass для " .. steamId) + self:createBattlePass(playerId, steamId) + else + ____print( + nil, + "[BattlePassServer] Ошибка сервера: " .. tostring(result.StatusCode) + ) + CustomGameEventManager:Send_ServerToPlayer( + player, + "battle_pass_data", + {error = "Ошибка сервера: " .. tostring(result.StatusCode)} + ) + end + end) + if not ____try then + ____hasReturned, ____returnValue = ____catch(____hasReturned) + end + if ____hasReturned then + return ____returnValue + end + end + end) +end +function BattlePassServer.prototype.createBattlePass(self, playerId, steamId, afterCreate) + local request = CreateHTTPRequest("POST", self.serverUrl .. "/battlepass") + setApiHeadersLong(nil, request) + request:SetHTTPRequestRawPostBody( + "application/json", + encodeApiBody(nil, { + steam_id = steamId, + level = 0, + experience = 0, + claimed_rewards = {}, + has_premium = false, + claimed_premium_rewards = {} + }) + ) + request:Send(function(result) + local player = PlayerResource:GetPlayer(playerId) + if not player then + if afterCreate then + afterCreate(nil, false) + end + return + end + if result.StatusCode >= 200 and result.StatusCode < 300 then + ____print(nil, "[BattlePassServer] Battle Pass создан на сервере для " .. steamId) + self:rememberBattlePassSnapshot(playerId, 0, 0, false) + CustomGameEventManager:Send_ServerToPlayer(player, "battle_pass_data", { + level = 0, + experience = 0, + claimed_rewards = {}, + has_premium = 0, + claimed_premium_rewards = {} + }) + if afterCreate then + afterCreate(nil, true) + end + else + ____print( + nil, + "[BattlePassServer] Ошибка создания Battle Pass: " .. tostring(result.StatusCode) + ) + CustomGameEventManager:Send_ServerToPlayer( + player, + "battle_pass_data", + {error = "Ошибка создания Battle Pass: " .. tostring(result.StatusCode)} + ) + if afterCreate then + afterCreate(nil, false) + end + end + end) +end +function BattlePassServer.prototype.claimReward(self, playerId, steamId, level, isPremium) + if isPremium == nil then + isPremium = false + end + local endpoint = isPremium and "claim-premium" or "claim" + local request = CreateHTTPRequest("POST", (((self.serverUrl .. "/battlepass/") .. steamId) .. "/") .. endpoint) + setApiHeadersLong(nil, request) + request:SetHTTPRequestRawPostBody( + "application/json", + json.encode({steam_id = steamId, level = level, is_premium = isPremium}) + ) + request:Send(function(result) + local player = PlayerResource:GetPlayer(playerId) + if not player then + return + end + if result.StatusCode >= 200 and result.StatusCode < 300 then + ____print( + nil, + (((("[BattlePassServer] Награда уровня " .. tostring(level)) .. " (premium: ") .. tostring(isPremium)) .. ") забрана для ") .. steamId + ) + local rCfg = self:getRewardForLevel(level, isPremium) + local expF = 0 + local expD = 0 + if (rCfg and rCfg.type) == "free_currency" then + expF = rCfg.amount or 0 + end + if (rCfg and rCfg.type) == "donate_currency" then + expD = rCfg.amount or 0 + end + local ____temp_21 = self:parseCurrencyGrantedFromClaimResponse(tostring(result.Body)) + local apiFree = ____temp_21.apiFree + local apiDonate = ____temp_21.apiDonate + local hasField = ____temp_21.hasField + if hasField and (expF > 0 or expD > 0) then + self:syncBattlePassShopCurrencyIfUnderGranted( + playerId, + apiFree, + apiDonate, + expF, + expD, + (("одиночный claim ур." .. tostring(level)) .. " prem=") .. tostring(isPremium) + ) + elseif not hasField and (expF > 0 or expD > 0) then + rawPrintFn( + nil, + ((((("[BattlePassServer] В ответе claim нет currency_granted — начисляю осколки через /currency/give: free=" .. tostring(expF)) .. " donate=") .. tostring(expD)) .. " (ур.") .. tostring(level)) .. ")" + ) + self:grantShopCurrencyViaApi( + playerId, + expF, + expD, + ((("[BattlePassServer] Валюта БП (нет поля API): +" .. tostring(expF)) .. " free, +") .. tostring(expD)) .. " donate" + ) + end + self:grantRewardInGame(playerId, level, isPremium) + CustomGameEventManager:Send_ServerToPlayer(player, "battle_pass_claim_result", {success = true, level = level, is_premium = isPremium and 1 or 0}) + else + local errorMsg = "Ошибка: " .. tostring(result.StatusCode) + do + pcall(function() + local body = {json.decode(result.Body)} + if body and body.error then + errorMsg = body.error + end + end) + end + ____print(nil, "[BattlePassServer] Ошибка забора награды: " .. errorMsg) + CustomGameEventManager:Send_ServerToPlayer(player, "battle_pass_claim_result", {success = false, level = level, error = errorMsg}) + end + end) +end +function BattlePassServer.battlePassFreeShardsPerLevel(self, level) + return level * 250 +end +function BattlePassServer.battlePassDustForLevel(self, level) + return level * 200 +end +function BattlePassServer.isDustLevel(self, level, isPremium) + local list = isPremium and ____exports.BattlePassServer.BP_PREMIUM_DUST_LEVELS or ____exports.BattlePassServer.BP_FREE_DUST_LEVELS + for ____, l in ipairs(list) do + if l == level then + return true + end + end + return false +end +function BattlePassServer.isPackLevel(self, level, isPremium) + local list = isPremium and ____exports.BattlePassServer.BP_PREMIUM_PACK_LEVELS or ____exports.BattlePassServer.BP_FREE_PACK_LEVELS + for ____, l in ipairs(list) do + if l == level then + return true + end + end + return false +end +function BattlePassServer.buildFreeRewardsConfig(self) + local cfg = {} + do + local lvl = 1 + while lvl <= 50 do + if ____exports.BattlePassServer:isDustLevel(lvl, false) then + cfg[lvl] = { + type = "dust_currency", + amount = ____exports.BattlePassServer:battlePassDustForLevel(lvl) + } + elseif ____exports.BattlePassServer:isPackLevel(lvl, false) then + cfg[lvl] = {type = "arcade_pack_standard", amount = 1} + else + cfg[lvl] = { + type = "free_currency", + amount = ____exports.BattlePassServer:battlePassFreeShardsPerLevel(lvl) + } + end + lvl = lvl + 1 + end + end + return cfg +end +function BattlePassServer.prototype.getRewardForLevel(self, lvl, isPremium) + if isPremium == nil then + isPremium = false + end + local config = isPremium and ____exports.BattlePassServer.PREMIUM_REWARDS_CONFIG or ____exports.BattlePassServer.REWARDS_CONFIG + local reward = config[lvl] + if reward == nil or reward == nil then + return nil + end + return reward +end +function BattlePassServer.prototype.findCurrencyGrantedInDecoded(self, decoded) + if not decoded or type(decoded) ~= "table" then + return nil + end + local candidates = {decoded} + local z = decoded + if z[0] and type(z[0]) == "table" then + candidates[#candidates + 1] = z[0] + end + if z[1] and type(z[1]) == "table" then + candidates[#candidates + 1] = z[1] + end + for ____, obj in ipairs(candidates) do + local cg = obj.currency_granted + if cg ~= nil and cg ~= nil and type(cg) == "table" then + return { + free = parseApiNumber(nil, cg.free_currency), + donate = parseApiNumber(nil, cg.donate_currency) + } + end + end + return nil +end +function BattlePassServer.prototype.parseCurrencyGrantedFromClaimResponse(self, body) + do + local ____try, ____hasReturned, ____returnValue = pcall(function() + local decoded = {json.decode(body)} + local found = self:findCurrencyGrantedInDecoded(decoded) + if found then + return true, {apiFree = found.free, apiDonate = found.donate, hasField = true} + end + end) + if ____try and ____hasReturned then + return ____returnValue + end + end + return {apiFree = 0, apiDonate = 0, hasField = false} +end +function BattlePassServer.prototype.sumExpectedShopCurrencyForLevels(self, freeLevels, premiumLevels) + local free = 0 + local donate = 0 + for ____, lvl in ipairs(freeLevels) do + local r = self:getRewardForLevel(lvl, false) + if (r and r.type) == "free_currency" then + free = free + (r.amount or 0) + end + end + for ____, lvl in ipairs(premiumLevels) do + local r = self:getRewardForLevel(lvl, true) + if (r and r.type) == "free_currency" then + free = free + (r.amount or 0) + end + if (r and r.type) == "donate_currency" then + donate = donate + (r.amount or 0) + end + end + return {free = free, donate = donate} +end +function BattlePassServer.prototype.syncBattlePassShopCurrencyIfUnderGranted(self, playerId, apiFree, apiDonate, expectedFree, expectedDonate, logCtx) + local needFree = math.max(0, expectedFree - (apiFree or 0)) + local needDonate = math.max(0, expectedDonate - (apiDonate or 0)) + if needFree <= 0 and needDonate <= 0 then + return false + end + rawPrintFn( + nil, + (((((((((((("[BattlePassServer] " .. logCtx) .. ": API free=") .. tostring(apiFree)) .. " donate=") .. tostring(apiDonate)) .. ", ожидалось free=") .. tostring(expectedFree)) .. " donate=") .. tostring(expectedDonate)) .. " → доначисление give +") .. tostring(needFree)) .. "/+") .. tostring(needDonate) + ) + self:grantShopCurrencyViaApi( + playerId, + needFree, + needDonate, + ((((("[BattlePassServer] Доначисление валюты БП (" .. logCtx) .. "): +") .. tostring(needFree)) .. " free, +") .. tostring(needDonate)) .. " donate" + ) + return true +end +function BattlePassServer.prototype.grantShopCurrencyViaApi(self, playerId, freeAmount, donateAmount, logSuccess, dustAmount, arcadePackStandard, arcadePackPremium) + if dustAmount == nil then + dustAmount = 0 + end + if arcadePackStandard == nil then + arcadePackStandard = 0 + end + if arcadePackPremium == nil then + arcadePackPremium = 0 + end + StoreManager:getInstance():grantShopExtrasViaApi(playerId, { + freeAmount = freeAmount, + donateAmount = donateAmount, + dustAmount = dustAmount, + arcadePackStandard = arcadePackStandard, + arcadePackPremium = arcadePackPremium + }, logSuccess) +end +function BattlePassServer.prototype.playerAlreadyOwnsChatWheelSound(self, playerId, soundId) + local info = PlayerInfo:GetPlayerInfo(playerId) + local sw = info and info.sounds_wheel + if sw and sw[soundId] then + return true + end + return StoreManager:getInstance():hasPurchasedItem(playerId, "chat_wheel_sound_" .. soundId) +end +function BattlePassServer.prototype.grantRewardInGame(self, playerId, level, isPremium) + if isPremium == nil then + isPremium = false + end + local reward = self:getRewardForLevel(level, isPremium) + if not reward then + ____print( + nil, + ((("[BattlePassServer] Уровень " .. tostring(level)) .. " (premium: ") .. tostring(isPremium)) .. ") не имеет награды" + ) + return + end + if not PlayerResource:IsValidPlayer(playerId) or not PlayerResource:GetPlayer(playerId) then + ____print( + nil, + (("[BattlePassServer] Слот " .. tostring(playerId)) .. " не валиден — пропуск выдачи награды уровня ") .. tostring(level) + ) + return + end + local storeMgr = StoreManager:getInstance() + repeat + local ____switch272 = reward.type + local cardId, cardStoreId, itemName, itemSteamId, purchaseRequest, effectId + local ____cond272 = ____switch272 == "free_currency" or ____switch272 == "donate_currency" or ____switch272 == "dust_currency" or ____switch272 == "arcade_pack_standard" or ____switch272 == "arcade_pack_premium" + if ____cond272 then + ____print( + nil, + ((((("[BattlePassServer] Пропуск " .. reward.type) .. " уровня ") .. tostring(level)) .. " (premium=") .. tostring(isPremium)) .. ") — уже начислено на сервере при клейме" + ) + break + end + ____cond272 = ____cond272 or ____switch272 == "card" + if ____cond272 then + cardId = reward.card_id + if cardId == nil or cardId == nil then + ____print( + nil, + "[BattlePassServer] Ошибка: card_id не указан для карты на уровне " .. tostring(level) + ) + break + end + cardStoreId = "card_data_" .. tostring(cardId) + if storeMgr:hasPurchasedCardById(playerId, cardId) then + local comp = getBpDuplicateCompensationByStoreItemId(nil, cardStoreId) + if comp.free_currency > 0 or comp.donate_currency > 0 then + self:grantShopCurrencyViaApi( + playerId, + comp.free_currency, + comp.donate_currency, + ((((((("[BattlePassServer] Дубликат карты " .. cardStoreId) .. " (ур. ") .. tostring(level)) .. "): компенсация +") .. tostring(comp.free_currency)) .. " free, +") .. tostring(comp.donate_currency)) .. " donate" + ) + else + ____print(nil, ("[BattlePassServer] Дубликат " .. cardStoreId) .. ", нет компенсации в battle_pass_duplicate_compensation.ts") + end + break + end + storeMgr:registerCardFromBattlePass(playerId, cardId) + ____print( + nil, + (("[BattlePassServer] Карта " .. tostring(cardId)) .. " добавлена через StoreManager для игрока ") .. tostring(playerId) + ) + break + end + ____cond272 = ____cond272 or ____switch272 == "item" + if ____cond272 then + itemName = reward.item_name + if not itemName then + ____print( + nil, + "[BattlePassServer] Ошибка: item_name не указан для предмета на уровне " .. tostring(level) + ) + break + end + if storeMgr:hasPurchasedItem(playerId, itemName) then + local comp = getBpDuplicateCompensationByStoreItemId(nil, itemName) + if comp.free_currency > 0 or comp.donate_currency > 0 then + self:grantShopCurrencyViaApi( + playerId, + comp.free_currency, + comp.donate_currency, + ((((((("[BattlePassServer] Дубликат предмета " .. itemName) .. " (ур. ") .. tostring(level)) .. "): компенсация +") .. tostring(comp.free_currency)) .. " free, +") .. tostring(comp.donate_currency)) .. " donate" + ) + else + ____print(nil, ("[BattlePassServer] Дубликат " .. itemName) .. ", нет компенсации в battle_pass_duplicate_compensation.ts") + end + break + end + itemSteamId = tostring(PlayerResource:GetSteamAccountID(playerId)) + purchaseRequest = CreateHTTPRequest("POST", ((self.serverUrl .. "/player/") .. itemSteamId) .. "/purchases") + setApiHeaders(nil, purchaseRequest) + purchaseRequest:SetHTTPRequestRawPostBody( + "application/json", + json.encode({item_id = itemName, item_category = "items", card_id = nil}) + ) + purchaseRequest:Send(function(purchaseResult) + if purchaseResult.StatusCode >= 200 and purchaseResult.StatusCode < 300 then + ____print( + nil, + ((((("[BattlePassServer] Предмет " .. itemName) .. " добавлен в покупки игрока ") .. tostring(playerId)) .. " (Steam ID: ") .. itemSteamId) .. ")" + ) + else + ____print( + nil, + "[BattlePassServer] Ошибка добавления предмета в покупки: " .. tostring(purchaseResult.StatusCode) + ) + ____print( + nil, + "[BattlePassServer] Тело ответа: " .. tostring(purchaseResult.Body) + ) + end + end) + break + end + ____cond272 = ____cond272 or ____switch272 == "effect" + if ____cond272 then + effectId = reward.effect_id + if effectId then + if storeMgr:hasPurchasedItem(playerId, effectId) then + local comp = getBpDuplicateCompensationByStoreItemId(nil, effectId) + if comp.free_currency > 0 or comp.donate_currency > 0 then + self:grantShopCurrencyViaApi( + playerId, + comp.free_currency, + comp.donate_currency, + ((((((("[BattlePassServer] Дубликат эффекта " .. effectId) .. " (ур. ") .. tostring(level)) .. "): компенсация +") .. tostring(comp.free_currency)) .. " free, +") .. tostring(comp.donate_currency)) .. " donate" + ) + else + ____print(nil, ("[BattlePassServer] Дубликат " .. effectId) .. ", нет компенсации в battle_pass_duplicate_compensation.ts") + end + break + end + local effectSteamId = tostring(PlayerResource:GetSteamAccountID(playerId)) + local effectPurchaseRequest = CreateHTTPRequest("POST", ((self.serverUrl .. "/player/") .. effectSteamId) .. "/purchases") + setApiHeaders(nil, effectPurchaseRequest) + effectPurchaseRequest:SetHTTPRequestRawPostBody( + "application/json", + json.encode({item_id = effectId, item_category = "effects", card_id = nil}) + ) + effectPurchaseRequest:Send(function(effectPurchaseResult) + if effectPurchaseResult.StatusCode >= 200 and effectPurchaseResult.StatusCode < 300 then + ____print( + nil, + ((((("[BattlePassServer] Эффект " .. effectId) .. " добавлен в покупки игрока ") .. tostring(playerId)) .. " (Steam ID: ") .. effectSteamId) .. ")" + ) + else + ____print( + nil, + "[BattlePassServer] Ошибка добавления эффекта в покупки: " .. tostring(effectPurchaseResult.StatusCode) + ) + ____print( + nil, + "[BattlePassServer] Тело ответа: " .. tostring(effectPurchaseResult.Body) + ) + end + end) + end + break + end + ____cond272 = ____cond272 or ____switch272 == "chat_wheel_sound" + if ____cond272 then + do + local sid = reward.sound_id + if not sid then + ____print( + nil, + "[BattlePassServer] Ошибка: sound_id не указан для chat_wheel_sound на уровне " .. tostring(level) + ) + break + end + if self:playerAlreadyOwnsChatWheelSound(playerId, sid) then + local cwItemId = "chat_wheel_sound_" .. sid + local comp = getBpDuplicateCompensationByStoreItemId(nil, cwItemId) + if comp.free_currency > 0 or comp.donate_currency > 0 then + self:grantShopCurrencyViaApi( + playerId, + comp.free_currency, + comp.donate_currency, + ((((((("[BattlePassServer] Дубликат звука " .. cwItemId) .. " (ур. ") .. tostring(level)) .. "): компенсация +") .. tostring(comp.free_currency)) .. " free, +") .. tostring(comp.donate_currency)) .. " donate" + ) + else + ____print(nil, ("[BattlePassServer] Дубликат " .. cwItemId) .. ", нет компенсации в battle_pass_duplicate_compensation.ts") + end + break + end + grantChatWheelSoundFromBattlePass(nil, playerId, sid) + break + end + end + until true +end +function BattlePassServer.prototype.claimAllRewards(self, playerId, steamId) + local request = CreateHTTPRequest("POST", ((self.serverUrl .. "/battlepass/") .. steamId) .. "/claim-all") + setApiHeadersLong(nil, request) + request:SetHTTPRequestRawPostBody( + "application/json", + json.encode({steam_id = steamId}) + ) + request:Send(function(result) + local player = PlayerResource:GetPlayer(playerId) + if not player then + return + end + if result.StatusCode >= 200 and result.StatusCode < 300 then + do + local function ____catch(e) + ____print( + nil, + "[BattlePassServer] Ошибка парсинга claim-all: " .. tostring(e) + ) + CustomGameEventManager:Send_ServerToPlayer(player, "battle_pass_claim_result", {success = false, error = "Ошибка обработки данных"}) + end + local ____try, ____hasReturned = pcall(function() + local decoded = {json.decode(result.Body)} + local responseData = self:pickDecodedJsonRoot(decoded, {"success", "free_levels", "premium_levels"}) + local ____opt_result_30 + if responseData ~= nil then + ____opt_result_30 = responseData.free_levels + end + local flRaw = ____opt_result_30 + local ____opt_result_33 + if responseData ~= nil then + ____opt_result_33 = responseData.premium_levels + end + local plRaw = ____opt_result_33 + local freeLevels = __TS__ArrayIsArray(flRaw) and __TS__ArrayFilter( + __TS__ArrayMap( + flRaw, + function(____, v) return parseApiNumber(nil, v) end + ), + function(____, n) return n >= 1 end + ) or ({}) + local premiumLevels = __TS__ArrayIsArray(plRaw) and __TS__ArrayFilter( + __TS__ArrayMap( + plRaw, + function(____, v) return parseApiNumber(nil, v) end + ), + function(____, n) return n >= 1 end + ) or ({}) + ____print( + nil, + (((("[BattlePassServer] Забрано " .. tostring(#freeLevels)) .. " бесплатных + ") .. tostring(#premiumLevels)) .. " премиум наград для ") .. steamId + ) + local ____temp_34 = self:sumExpectedShopCurrencyForLevels(freeLevels, premiumLevels) + local expF = ____temp_34.free + local expD = ____temp_34.donate + local ____temp_35 = self:parseCurrencyGrantedFromClaimResponse(tostring(result.Body)) + local apiFree = ____temp_35.apiFree + local apiDonate = ____temp_35.apiDonate + local hasField = ____temp_35.hasField + if hasField and (expF > 0 or expD > 0) then + self:syncBattlePassShopCurrencyIfUnderGranted( + playerId, + apiFree, + apiDonate, + expF, + expD, + ((("claim-all (" .. tostring(#freeLevels)) .. "+") .. tostring(#premiumLevels)) .. " ур.)" + ) + elseif not hasField and (expF > 0 or expD > 0) then + rawPrintFn( + nil, + (("[BattlePassServer] claim-all: нет currency_granted в ответе — give free=" .. tostring(expF)) .. " donate=") .. tostring(expD) + ) + self:grantShopCurrencyViaApi( + playerId, + expF, + expD, + ((("[BattlePassServer] Валюта БП claim-all (нет поля API): +" .. tostring(expF)) .. " free, +") .. tostring(expD)) .. " donate" + ) + end + for ____, lvl in ipairs(freeLevels) do + self:grantRewardInGame(playerId, lvl, false) + end + for ____, lvl in ipairs(premiumLevels) do + self:grantRewardInGame(playerId, lvl, true) + end + local freeObject = {} + __TS__ArrayForEach( + freeLevels, + function(____, lvl, idx) + freeObject[tostring(idx)] = lvl + end + ) + local premiumObject = {} + __TS__ArrayForEach( + premiumLevels, + function(____, lvl, idx) + premiumObject[tostring(idx)] = lvl + end + ) + CustomGameEventManager:Send_ServerToPlayer(player, "battle_pass_claim_result", {success = true, claimed_levels = freeObject, claimed_premium_levels = premiumObject}) + end) + if not ____try then + ____catch(____hasReturned) + end + end + else + local errorMsg = "Ошибка: " .. tostring(result.StatusCode) + do + pcall(function() + local body = {json.decode(result.Body)} + if body and body.error then + errorMsg = body.error + end + end) + end + ____print(nil, "[BattlePassServer] Ошибка забора всех наград: " .. errorMsg) + CustomGameEventManager:Send_ServerToPlayer(player, "battle_pass_claim_result", {success = false, error = errorMsg}) + end + end) +end +function BattlePassServer.prototype.buyPremium(self, playerId, steamId) + local request = CreateHTTPRequest("POST", ((self.serverUrl .. "/battlepass/") .. steamId) .. "/buy-premium") + setApiHeadersLong(nil, request) + request:SetHTTPRequestRawPostBody( + "application/json", + json.encode({steam_id = steamId, cost = 1000}) + ) + request:Send(function(result) + local player = PlayerResource:GetPlayer(playerId) + if not player then + return + end + if result.StatusCode >= 200 and result.StatusCode < 300 then + ____print(nil, "[BattlePassServer] Battle Pass Premium куплен для " .. steamId) + CustomGameEventManager:Send_ServerToPlayer(player, "battle_pass_buy_result", {success = true}) + self:loadBattlePassData(playerId, steamId) + else + local code = result.StatusCode + local errorMsg = "Ошибка: " .. tostring(code) + local ____tostring_37 = tostring + local ____result_Body_36 = result.Body + if ____result_Body_36 == nil then + ____result_Body_36 = "" + end + local rawBody = ____tostring_37(____result_Body_36) + if rawBody ~= "" then + do + pcall(function() + local decoded = {json.decode(rawBody)} + local body = self:pickDecodedJsonRoot(decoded, {"error"}) + if body and body.error ~= nil and body.error ~= nil and tostring(body.error) ~= "" then + errorMsg = tostring(body.error) + end + end) + end + end + ____print( + nil, + ((("[BattlePassServer] Ошибка покупки Premium: " .. errorMsg) .. " (HTTP ") .. tostring(code)) .. ")" + ) + CustomGameEventManager:Send_ServerToPlayer(player, "battle_pass_buy_result", {success = false, error = errorMsg}) + end + end) +end +function BattlePassServer.prototype.addExperience(self, playerId, amount) + self:sendAddExpRequest(playerId, amount, false) +end +function BattlePassServer.prototype.sendAddExpRequest(self, playerId, amount, alreadyRetriedAfterCreate) + local expAdd = math.max( + 0, + math.floor(amount) + ) + if expAdd <= 0 then + return + end + local steamId = tostring(PlayerResource:GetSteamAccountID(playerId)) + ____print( + nil, + ((((("[BattlePassServer] Добавляем " .. tostring(expAdd)) .. " опыта Battle Pass игроку ") .. tostring(playerId)) .. " (Steam ID: ") .. steamId) .. ")" + ) + local request = CreateHTTPRequest("POST", ((self.serverUrl .. "/battlepass/") .. steamId) .. "/addexp") + setApiHeadersLong(nil, request) + request:SetHTTPRequestRawPostBody( + "application/json", + encodeApiBody(nil, {steam_id = steamId, experience = expAdd}) + ) + request:Send(function(result) + if result.StatusCode >= 200 and result.StatusCode < 300 then + do + local function ____catch(e) + ____print(nil, "[BattlePassServer] Опыт добавлен для " .. steamId) + end + local ____try, ____hasReturned = pcall(function() + local responseData = {json.decode(result.Body)} + local levelUp = responseData.level_up == true + ____print( + nil, + (((((("[BattlePassServer] Опыт добавлен для " .. steamId) .. ". Новый уровень: ") .. tostring(responseData.level)) .. ", опыт: ") .. tostring(responseData.experience)) .. ", level_up: ") .. tostring(levelUp) + ) + if responseData.level ~= nil and responseData.experience ~= nil then + self:rememberBattlePassSnapshot( + playerId, + parseApiNumber(nil, responseData.level), + parseApiNumber(nil, responseData.experience) + ) + end + end) + if not ____try then + ____catch(____hasReturned) + end + end + self:loadBattlePassData(playerId, steamId) + elseif result.StatusCode == 404 and not alreadyRetriedAfterCreate then + ____print(nil, "[BattlePassServer] addexp 404 (нет battle_pass) — создаём запись и повторяем начисление") + self:createBattlePass( + playerId, + steamId, + function(____, ok) + if ok then + self:sendAddExpRequest(playerId, expAdd, true) + else + ____print(nil, "[BattlePassServer] Не удалось создать Battle Pass — опыт за матч не начислен") + end + end + ) + else + local bodyStr = result.Body ~= nil and tostring(result.Body) or "" + ____print( + nil, + (("[BattlePassServer] Ошибка добавления опыта: HTTP " .. tostring(result.StatusCode)) .. " body=") .. bodyStr + ) + end + end) +end +BattlePassServer.REWARD_AMOUNTS = {DONATE_CURRENCY = { + LEVEL_1 = 100, + LEVEL_11 = 100, + LEVEL_21 = 100, + LEVEL_31 = 100, + LEVEL_41 = 100 +}, ITEM_AMOUNT = 1, EFFECT_AMOUNT = 1, CARD_AMOUNT = 1} +BattlePassServer.BP_FREE_DUST_LEVELS = { + 3, + 8, + 13, + 18, + 23, + 28, + 33, + 38, + 43, + 48 +} +BattlePassServer.BP_FREE_PACK_LEVELS = { + 10, + 20, + 30, + 40, + 50 +} +BattlePassServer.BP_PREMIUM_DUST_LEVELS = { + 2, + 6, + 13, + 16, + 22, + 26, + 32, + 36, + 42, + 47 +} +BattlePassServer.BP_PREMIUM_PACK_LEVELS = { + 3, + 9, + 23, + 33, + 48 +} +BattlePassServer.REWARDS_CONFIG = ____exports.BattlePassServer:buildFreeRewardsConfig() +BattlePassServer.PREMIUM_REWARDS_CONFIG = { + [1] = {type = "donate_currency", amount = ____exports.BattlePassServer.REWARD_AMOUNTS.DONATE_CURRENCY.LEVEL_1}, + [2] = { + type = "dust_currency", + amount = ____exports.BattlePassServer:battlePassDustForLevel(2) + }, + [3] = {type = "arcade_pack_premium", amount = 1}, + [4] = { + type = "free_currency", + amount = ____exports.BattlePassServer:battlePassFreeShardsPerLevel(4) + }, + [5] = { + type = "free_currency", + amount = ____exports.BattlePassServer:battlePassFreeShardsPerLevel(5) + }, + [6] = { + type = "dust_currency", + amount = ____exports.BattlePassServer:battlePassDustForLevel(6) + }, + [7] = { + type = "free_currency", + amount = ____exports.BattlePassServer:battlePassFreeShardsPerLevel(7) + }, + [8] = { + type = "free_currency", + amount = ____exports.BattlePassServer:battlePassFreeShardsPerLevel(8) + }, + [9] = {type = "arcade_pack_premium", amount = 1}, + [10] = { + type = "dust_currency", + amount = ____exports.BattlePassServer:battlePassDustForLevel(10) + }, + [11] = {type = "donate_currency", amount = ____exports.BattlePassServer.REWARD_AMOUNTS.DONATE_CURRENCY.LEVEL_11}, + [12] = {type = "effect", amount = ____exports.BattlePassServer.REWARD_AMOUNTS.EFFECT_AMOUNT, effect_id = "skin_effect_bp_diretide_emblem"}, + [13] = { + type = "dust_currency", + amount = ____exports.BattlePassServer:battlePassDustForLevel(13) + }, + [14] = { + type = "free_currency", + amount = ____exports.BattlePassServer:battlePassFreeShardsPerLevel(14) + }, + [15] = { + type = "free_currency", + amount = ____exports.BattlePassServer:battlePassFreeShardsPerLevel(15) + }, + [16] = { + type = "dust_currency", + amount = ____exports.BattlePassServer:battlePassDustForLevel(16) + }, + [17] = { + type = "free_currency", + amount = ____exports.BattlePassServer:battlePassFreeShardsPerLevel(17) + }, + [18] = { + type = "free_currency", + amount = ____exports.BattlePassServer:battlePassFreeShardsPerLevel(18) + }, + [19] = { + type = "free_currency", + amount = ____exports.BattlePassServer:battlePassFreeShardsPerLevel(19) + }, + [20] = {type = "effect", amount = ____exports.BattlePassServer.REWARD_AMOUNTS.EFFECT_AMOUNT, effect_id = "skin_effect_bp_diretide_emblem_v1"}, + [21] = {type = "donate_currency", amount = ____exports.BattlePassServer.REWARD_AMOUNTS.DONATE_CURRENCY.LEVEL_21}, + [22] = { + type = "dust_currency", + amount = ____exports.BattlePassServer:battlePassDustForLevel(22) + }, + [23] = {type = "arcade_pack_premium", amount = 1}, + [24] = { + type = "free_currency", + amount = ____exports.BattlePassServer:battlePassFreeShardsPerLevel(24) + }, + [25] = { + type = "dust_currency", + amount = ____exports.BattlePassServer:battlePassDustForLevel(25) + }, + [26] = { + type = "dust_currency", + amount = ____exports.BattlePassServer:battlePassDustForLevel(26) + }, + [27] = { + type = "free_currency", + amount = ____exports.BattlePassServer:battlePassFreeShardsPerLevel(27) + }, + [28] = { + type = "free_currency", + amount = ____exports.BattlePassServer:battlePassFreeShardsPerLevel(28) + }, + [29] = { + type = "free_currency", + amount = ____exports.BattlePassServer:battlePassFreeShardsPerLevel(29) + }, + [30] = { + type = "free_currency", + amount = ____exports.BattlePassServer:battlePassFreeShardsPerLevel(30) + }, + [31] = {type = "donate_currency", amount = ____exports.BattlePassServer.REWARD_AMOUNTS.DONATE_CURRENCY.LEVEL_31}, + [32] = { + type = "dust_currency", + amount = ____exports.BattlePassServer:battlePassDustForLevel(32) + }, + [33] = {type = "arcade_pack_premium", amount = 1}, + [34] = { + type = "free_currency", + amount = ____exports.BattlePassServer:battlePassFreeShardsPerLevel(34) + }, + [35] = { + type = "free_currency", + amount = ____exports.BattlePassServer:battlePassFreeShardsPerLevel(35) + }, + [36] = { + type = "dust_currency", + amount = ____exports.BattlePassServer:battlePassDustForLevel(36) + }, + [37] = { + type = "free_currency", + amount = ____exports.BattlePassServer:battlePassFreeShardsPerLevel(37) + }, + [38] = { + type = "free_currency", + amount = ____exports.BattlePassServer:battlePassFreeShardsPerLevel(38) + }, + [39] = { + type = "free_currency", + amount = ____exports.BattlePassServer:battlePassFreeShardsPerLevel(39) + }, + [40] = {type = "item", amount = ____exports.BattlePassServer.REWARD_AMOUNTS.ITEM_AMOUNT, item_name = "hero_spectre"}, + [41] = {type = "donate_currency", amount = ____exports.BattlePassServer.REWARD_AMOUNTS.DONATE_CURRENCY.LEVEL_41}, + [42] = { + type = "dust_currency", + amount = ____exports.BattlePassServer:battlePassDustForLevel(42) + }, + [43] = { + type = "free_currency", + amount = ____exports.BattlePassServer:battlePassFreeShardsPerLevel(43) + }, + [44] = { + type = "free_currency", + amount = ____exports.BattlePassServer:battlePassFreeShardsPerLevel(44) + }, + [45] = {type = "effect", amount = ____exports.BattlePassServer.REWARD_AMOUNTS.EFFECT_AMOUNT, effect_id = "skin_effect_bp_diretide_emblem_v3"}, + [46] = { + type = "dust_currency", + amount = ____exports.BattlePassServer:battlePassDustForLevel(46) + }, + [47] = { + type = "dust_currency", + amount = ____exports.BattlePassServer:battlePassDustForLevel(47) + }, + [48] = {type = "arcade_pack_premium", amount = 1}, + [49] = { + type = "dust_currency", + amount = ____exports.BattlePassServer:battlePassDustForLevel(49) + }, + [50] = { + type = "free_currency", + amount = ____exports.BattlePassServer:battlePassFreeShardsPerLevel(50) + } +} +if IsServer() then + (function() + Timers:CreateTimer( + 2, + function() + do + local function ____catch(____error) + ____print( + nil, + "[BattlePassServer] Ошибка инициализации: " .. tostring(____error) + ) + return true, nil + end + local ____try, ____hasReturned, ____returnValue = pcall(function() + local battlePassServer = ____exports.BattlePassServer:getInstance() + ____print(nil, "[BattlePassServer] Успешно инициализирован") + return true, nil + end) + if not ____try then + ____hasReturned, ____returnValue = ____catch(____hasReturned) + end + if ____hasReturned then + return ____returnValue + end + end + end + ) + end)(nil) +end +return ____exports diff --git a/scripts/vscripts/black_shop_teleport.lua b/scripts/vscripts/black_shop_teleport.lua new file mode 100644 index 0000000..1c00671 --- /dev/null +++ b/scripts/vscripts/black_shop_teleport.lua @@ -0,0 +1,26 @@ +--[[ Generated with https://github.com/TypeScriptToLua/TypeScriptToLua ]] +local ____exports = {} +--- Телепорт с чёрного рынка: в Hammer ставь info_target / path_corner с этими targetname. +____exports.BLACK_SHOP_TELEPORT_TARGETNAMES = {grove = "point_blackshop_tp_grove", two_sisters = "point_blackshop_tp_two_sisters", village_waterfall = "point_blackshop_tp_village_waterfall", ancient_ridge = "point_blackshop_tp_ancient_ridge"} +--- Если энтити с именем ещё нет на карте — подставляются эти координаты (подправь под свою карту). +local BLACK_SHOP_TELEPORT_FALLBACK = { + grove = Vector(-4200, 3600, 384), + two_sisters = Vector(-1800, 4100, 256), + village_waterfall = Vector(1400, -2800, 128), + ancient_ridge = Vector(5200, -1600, 512) +} +function ____exports.isBlackShopTeleportDestination(self, dest) + return dest ~= nil and ____exports.BLACK_SHOP_TELEPORT_TARGETNAMES[dest] ~= nil +end +function ____exports.resolveBlackShopTeleportPosition(self, destination) + if not ____exports.isBlackShopTeleportDestination(nil, destination) then + return nil + end + local targetname = ____exports.BLACK_SHOP_TELEPORT_TARGETNAMES[destination] + local marker = Entities:FindByName(nil, targetname) + if marker then + return marker:GetAbsOrigin() + end + return BLACK_SHOP_TELEPORT_FALLBACK[destination] +end +return ____exports diff --git a/scripts/vscripts/blackshop.lua b/scripts/vscripts/blackshop.lua new file mode 100644 index 0000000..4a7d0ba --- /dev/null +++ b/scripts/vscripts/blackshop.lua @@ -0,0 +1,599 @@ +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 diff --git a/scripts/vscripts/card_catalog.lua b/scripts/vscripts/card_catalog.lua new file mode 100644 index 0000000..d7af978 --- /dev/null +++ b/scripts/vscripts/card_catalog.lua @@ -0,0 +1,908 @@ +local ____lualib = require("lualib_bundle") +local __TS__Number = ____lualib.__TS__Number +local __TS__ArrayFind = ____lualib.__TS__ArrayFind +local __TS__NumberIsFinite = ____lualib.__TS__NumberIsFinite +local __TS__ArrayFilter = ____lualib.__TS__ArrayFilter +local __TS__ArrayMap = ____lualib.__TS__ArrayMap +local __TS__ArraySort = ____lualib.__TS__ArraySort +local ____exports = {} +local function cards1to13(self) + return { + { + id = 1, + quality = 4, + defaultCard = false, + inherent = true, + deck_slots = 3, + price_free = 20800, + max_copies = 1, + values = {vampirism_bonus = {7, 9, 11}, damage_multiplier = {0.5, 1.25, 2}, max_health_penalty_pct = {25, 20, 15}} + }, + { + id = 2, + inherent = true, + deck_slots = 3, + quality = 4, + defaultCard = false, + price_free = 20800, + max_copies = 1, + values = {speed_bonus = {45, 60, 75}, speed_limit = {0, 1100, 1350}, pass_through_units = {0, 0, 1}, attacktime = {0, 0, 50}} + }, + { + id = 3, + quality = 1, + defaultCard = true, + price_free = 0, + max_copies = 2, + values = {damage_pct = {2.5, 3.5, 4.5}} + }, + { + id = 4, + quality = 2, + defaultCard = true, + price_free = 0, + max_copies = 2, + values = {card_bonus = {1, 2, 3}} + }, + { + id = 5, + quality = 2, + defaultCard = true, + price_free = 0, + max_copies = 2, + values = {card_bonus_pct = {1.15, 1.35, 1.67}} + }, + { + id = 6, + quality = 5, + defaultCard = false, + price_free = 83200, + max_copies = 1, + values = {card_show_count = {3}, min_card_choice = {4}, free_reroll_charges = {0, 2, 2}} + }, + { + id = 7, + quality = 2, + defaultCard = true, + price_free = 0, + max_copies = 2, + values = {daynight_bonus_pct = {35, 40, 45}} + }, + { + id = 8, + quality = 2, + defaultCard = true, + price_free = 0, + max_copies = 2, + values = {damage_bonus_health_decress = {2.5, 3.5, 4.5}, stack_health_threshold = {900, 1200, 1500}} + }, + { + id = 9, + quality = 4, + defaultCard = false, + price_free = 20800, + max_copies = 1, + values = {greed_net_worth_per_stack = {100, 100, 100}, kill_gold_bonus_pct = {15, 20, 25}} + }, + { + id = 10, + quality = 1, + defaultCard = true, + price_free = 0, + max_copies = 2, + values = {debuff_duration = {3.5, 4, 4.5}, incoming_damage_per_fired_stack_pct = {1, 1.35, 1.7}, max_incoming_damage_bonus_pct = {30, 45, 60}} + }, + { + id = 11, + quality = 1, + defaultCard = true, + price_free = 0, + max_copies = 2, + values = {primary_attribute_bonus_per_level = {2, 3, 4}, universal_all_bonus_per_level = {1, 1.5, 2}} + }, + { + id = 12, + quality = 4, + defaultCard = false, + price_free = 20800, + max_copies = 1, + values = { + scan_interval = {8, 6, 4.5}, + scan_radius = {900, 1200, 1500}, + mark_duration = {5, 7, 9}, + bonus_damage_pct = {22, 30, 40}, + on_kill_heal_pct = {6, 8, 10}, + on_kill_mana = {35, 50, 70}, + boss_penalty_pct = {50, 40, 30} + } + }, + { + id = 13, + quality = 3, + defaultCard = false, + price_free = 10400, + max_copies = 2, + values = {fired_stacks_on_hit = {1, 2, 3}, explosion_chance_pct = {12, 18, 24}, explosion_radius = {175, 240, 300}, explosion_damage_per_stack = {3, 4.5, 6}} + }, + { + id = 14, + quality = 3, + defaultCard = false, + price_free = 10400, + max_copies = 2, + values = { + proc_chance_pct = {18, 24, 30}, + main_target_bonus_damage_pct = {25, 35, 45}, + fired_stacks = {1, 2, 3}, + ricochet_radius = {300, 420, 540}, + ricochet_damage_pct = {60, 80, 100} + } + }, + { + id = 15, + quality = 2, + defaultCard = false, + price_free = 5200, + max_copies = 2, + values = {base_crit_chance_pct = {15, 20, 25}, crit_multiplier_pct = {200, 240, 280}, crit_multiplier_bonus_pct_per_luck = {0.5, 1, 1.5}} + }, + { + id = 16, + quality = 2, + defaultCard = false, + price_free = 5200, + max_copies = 2, + values = {max_stacks = {200, 260, 320}, spell_amp_per_stack_pct = {0.1, 0.15, 0.2}} + }, + { + id = 17, + quality = 3, + defaultCard = false, + price_free = 10400, + max_copies = 2, + values = {min_health_pct_to_activate = {30}, health_cost_pct = {5}, curse_damage_pct_per_curse_stack = {10, 20, 30}} + }, + { + id = 18, + quality = 2, + defaultCard = false, + price_free = 5200, + max_copies = 2, + values = {base_luck_bonus = {3, 5, 7}, luck_per_card_taken = {0.25, 0.5, 0.75}} + }, + { + id = 19, + quality = 3, + defaultCard = false, + price_free = 10400, + max_copies = 2, + values = {ghost_step_chance_pct = {12, 18, 24}, ghost_step_duration = {2.5, 3.2, 4}, ghost_step_move_speed_pct = {15, 22, 30}, ghost_step_evasion_pct = {15, 22, 30}} + }, + { + id = 20, + quality = 2, + defaultCard = false, + price_free = 5200, + max_copies = 2, + values = {gold_per_step = {100, 70, 40}, move_speed_pct_per_step = {0.25, 0.35, 0.45}, speed_limit = {10000, 12000, 14000}} + }, + { + id = 21, + quality = 4, + defaultCard = false, + price_free = 20800, + inherent = false, + deck_slots = 4, + max_copies = 1, + values = {wave_pure_damage_base = {125, 170, 215}, wave_pure_damage_base_hand = {0.15, 0.25, 0.35}} + }, + { + id = 22, + quality = 2, + defaultCard = true, + price_free = 0, + max_copies = 2, + values = {morning_gold_base = {700, 700, 700}, morning_gold_decay_per_night = {100, 100, 100}, pickup_gold_pct = {0, 50, 50}, pre_night_gold_pct = {0, 0, 50}} + }, + { + id = 23, + quality = 4, + defaultCard = false, + price_free = 20800, + deck_slots = 3, + max_copies = 1, + values = {gold_income_pct_per_minute = {2, 3.5, 5}, gold_tick_interval_sec = {60}, min_earned_gold_pct_of_dividend_required = {25}} + }, + { + id = 24, + quality = 2, + defaultCard = false, + price_free = 5200, + max_copies = 2, + values = {gold_per_step = {100, 80, 60}, attack_damage_per_step = {1.5, 1.75, 2}, spell_amp_per_step_pct = {1.5, 1.75, 2}} + }, + { + id = 25, + quality = 2, + defaultCard = false, + price_free = 5200, + max_copies = 2, + inherent = true, + values = {mana_per_step = {50}, spell_amp_per_step_pct = {0.5, 0.75, 1}, mana_regen_per_step = {0.1, 0.15, 0.2}, max_mana_bonus_pct = {0, 0, 25}} + }, + { + id = 26, + quality = 4, + defaultCard = true, + price_free = 0, + max_copies = 1, + values = {trigger_health_threshold = {100}, heal_pct_per_second = {10, 14, 18}, cooldown_seconds = {120, 100, 80}, invisibility_duration = {10, 12, 14}} + }, + { + id = 27, + quality = 2, + defaultCard = true, + price_free = 0, + max_copies = 2, + values = {mana_to_damage_pct = {1.5, 2, 2.5}} + }, + { + id = 28, + quality = 2, + defaultCard = true, + price_free = 0, + max_copies = 2, + values = {health_to_damage_pct = {1.5, 2, 2.5}} + }, + { + id = 29, + quality = 3, + defaultCard = false, + price_free = 10400, + max_copies = 2, + values = {night_vampirism_pct = {10, 14, 18}, night_incoming_damage_reduce_pct = {5, 7, 9}} + }, + { + id = 30, + quality = 3, + defaultCard = false, + price_free = 10400, + max_copies = 2, + values = {strength_per_level = {0.5, 1, 1.5}, agility_penalty_per_level = {1, 0.5, 0.25}, intellect_penalty_per_level = {1, 0.5, 0.25}} + }, + { + id = 31, + quality = 3, + defaultCard = false, + price_free = 10400, + max_copies = 2, + values = {agility_per_level = {0.5, 1, 1.5}, strength_penalty_per_level = {1, 0.5, 0.25}, intellect_penalty_per_level = {1, 0.5, 0.25}} + }, + { + id = 32, + quality = 2, + defaultCard = false, + price_free = 5200, + max_copies = 2, + values = {base_cooldown_reduction_pct = {6, 9, 11}, cooldown_loss_per_death_pct = {3}} + }, + { + id = 33, + quality = 2, + defaultCard = false, + price_free = 5200, + max_copies = 2, + values = {mana_increase_pct = {15, 25, 35}} + }, + { + id = 34, + quality = 3, + defaultCard = true, + price_free = 0, + max_copies = 2, + values = {heal_from_enemy_max_hp_pct = {15, 22, 30}, heal_from_attack_damage_pct = {100, 130, 160}} + }, + { + id = 35, + quality = 3, + defaultCard = false, + price_free = 10400, + max_copies = 2, + values = {poison_duration = {1, 2, 3}, poison_tick_interval = {1, 0.8, 0.6}, damage_current_hp_pct = {5, 7, 9}, boss_damage_current_hp_pct = {1, 1.4, 1.8}} + }, + { + id = 36, + quality = 3, + defaultCard = false, + price_free = 10400, + max_copies = 2, + values = {crystals_per_step = {2}, damage_pct_per_step = {1, 2, 3}, max_bonus_pct = {80, 160, 320}} + }, + { + id = 37, + quality = 1, + defaultCard = true, + price_free = 0, + max_copies = 2, + values = {base_crystals = {10, 15, 20}, crystals_per_night = {5, 7, 9}} + }, + { + id = 38, + quality = 5, + defaultCard = false, + price_free = 83200, + max_copies = 1, + deck_slots = 5, + values = {multiplier = {1.25, 1.5, 1.75}} + }, + { + id = 39, + quality = 2, + defaultCard = false, + price_free = 5200, + max_copies = 2, + values = {gold_per_crystal = {50, 75, 100}} + }, + { + id = 40, + quality = 2, + defaultCard = false, + price_free = 5200, + max_copies = 2, + values = {gold_per_crystal = {75, 50, 25}} + }, + { + id = 41, + quality = 3, + defaultCard = false, + price_free = 10400, + canupgrade = true, + max_copies = 2, + values = {pool_pairs = {1, 1, 2}} + }, + { + id = 42, + quality = 2, + defaultCard = true, + price_free = 0, + max_copies = 2, + values = {exp_bonus_pct = {10, 15, 20}} + }, + { + id = 43, + quality = 1, + defaultCard = true, + price_free = 0, + max_copies = 2, + values = {bat_reduction = {0.1, 0.15, 0.2}, projectile_speed_bonus = {0, 300, 0}, attack_anim_reduction_pct = {0, 0, 33}} + }, + { + id = 44, + quality = 2, + defaultCard = false, + price_free = 5200, + max_copies = 2, + values = {bonus_move_speed_pct = {5, 7, 9}, bonus_evasion_pct = {5, 7, 9}, luck_bonus_pct_per_point = {0.5, 0.7, 0.9}, max_evasion_pct = {35, 45, 55}} + }, + { + id = 45, + quality = 4, + defaultCard = false, + inherent = true, + price_free = 20800, + deck_slots = 2, + max_copies = 1, + values = {max_bonus_pct = {30, 40, 50}, decay_pct_per_minute = {5}} + }, + { + id = 46, + quality = 4, + defaultCard = false, + price_free = 20800, + max_copies = 1, + values = {damage_to_mana_pct = {20, 25, 30}} + }, + { + id = 47, + quality = 4, + defaultCard = false, + price_free = 20800, + canupgrade = true, + max_copies = 1, + inherent = true, + deck_slots = 4, + values = {exp_bonus_pct = {35, 50, 65}, exp_boost_duration_sec = {180, 180, 180}} + }, + { + id = 48, + quality = 3, + defaultCard = true, + price_free = 0, + max_copies = 1, + values = {trigger_hp_pct = {25}, shield_heal_max_hp_pct = {30, 45, 60}, insurance_duration = {3.5, 4.5, 5.5}, incoming_damage_reduction_pct = {100}} + }, + { + id = 49, + quality = 3, + defaultCard = false, + price_free = 10400, + canupgrade = false, + max_copies = 1, + values = {} + }, + { + id = 50, + quality = 3, + defaultCard = false, + price_free = 10400, + canupgrade = false, + max_copies = 1, + values = {} + }, + { + id = 51, + quality = 4, + defaultCard = false, + price_free = 20800, + max_copies = 1, + values = {gold_from_damage_taken_pct = {11, 16, 21}, incoming_damage_base_pct = {25, 20, 15}, incoming_damage_per_100_taken = {0.1, 0.1, 0.1}, morning_gold_cap = {250, 400, 550}} + }, + { + id = 52, + quality = 3, + defaultCard = false, + price_free = 10400, + max_copies = 2, + values = {night_duration_reduce_sec = {15, 20, 25}, enemy_stats_bonus_pct = {15, 20, 25}} + }, + { + id = 53, + quality = 4, + defaultCard = false, + price_free = 20800, + max_copies = 1, + values = {trigger_card_id = {3, 3, 3}, extra_card_choices = {1, 2, 3}} + }, + { + id = 54, + quality = 3, + defaultCard = false, + price_free = 10400, + max_copies = 2, + values = {intellect_per_level = {0.5, 1, 1.5}, strength_penalty_per_level = {1, 0.5, 0.25}, agility_penalty_per_level = {1, 0.5, 0.25}} + }, + { + id = 55, + quality = 1, + defaultCard = true, + price_free = 0, + max_copies = 2, + values = {max_health_bonus_pct = {25, 50, 75}} + }, + { + id = 56, + quality = 3, + defaultCard = false, + purchasable = false, + obtainable = false, + price_free = 10400, + max_copies = 2, + values = {creep_reflect_pct = {15, 22, 30}} + }, + { + id = 57, + quality = 5, + defaultCard = false, + price_free = 83200, + canupgrade = false, + max_copies = 1, + deck_slots = 5, + values = {} + }, + { + id = 58, + quality = 5, + defaultCard = false, + inherent = true, + price_free = 83200, + max_copies = 1, + deck_slots = 2, + values = {extra_selections_on_start = {3, 4, 5}, cards_per_selection = {3, 4, 5}} + }, + { + id = 59, + quality = 3, + defaultCard = false, + purchasable = false, + obtainable = false, + canupgrade = true, + deck_builder_non_deckable = true, + deck_builder_unlock_card_id = 62, + price_free = 10400, + max_copies = 2, + values = {xp_reward = {500, 800, 1200}} + }, + { + id = 60, + quality = 3, + defaultCard = false, + purchasable = false, + obtainable = false, + canupgrade = true, + deck_builder_non_deckable = true, + deck_builder_unlock_card_id = 62, + price_free = 10400, + max_copies = 2, + values = {gold_reward = {250, 500, 750}} + }, + { + id = 61, + quality = 3, + defaultCard = false, + purchasable = false, + obtainable = false, + canupgrade = true, + deck_builder_non_deckable = true, + deck_builder_unlock_card_id = 62, + price_free = 10400, + max_copies = 2, + values = {crystals_reward = {12, 18, 24}} + }, + { + id = 62, + quality = 4, + defaultCard = false, + price_free = 20800, + canupgrade = false, + max_copies = 1, + values = {} + }, + { + id = 63, + quality = 3, + defaultCard = false, + price_free = 10400, + max_copies = 2, + values = {bonus_card_choices = {3, 4, 5}} + }, + { + id = 64, + quality = 4, + defaultCard = false, + price_free = 20800, + max_copies = 1, + values = {hp_threshold_pct = {20, 25, 30}, crit_multiplier_pct = {200, 250, 300}} + }, + { + id = 65, + quality = 3, + defaultCard = false, + price_free = 10400, + max_copies = 2, + values = {target_hp_full_pct = {100, 100, 100}, crit_multiplier_pct = {300, 450, 500}} + }, + { + id = 66, + quality = 5, + defaultCard = false, + price_free = 83200, + max_copies = 1, + values = {bonus_from_health_pct = {50, 65, 80}, bonus_from_mana_pct = {50, 65, 80}} + }, + { + id = 67, + quality = 5, + defaultCard = false, + price_free = 83200, + max_copies = 1, + inherent = true, + deck_slots = 3, + values = { + hp_per_strength = {0.1}, + outgoing_pct_per_strength = {0.05}, + proc_chance_pct = {40, 45, 50}, + proc_cooldown_sec = {3}, + bonus_damage_from_max_hp_pct = {60, 80, 100} + } + }, + { + id = 68, + quality = 3, + defaultCard = false, + price_free = 10400, + max_copies = 2, + canupgrade = false, + values = {curse_stacks = {1, 2, 3}, cursed_offer_slots = {3, 3, 3}} + }, + { + id = 69, + quality = 4, + defaultCard = false, + price_free = 20800, + max_copies = 1, + values = {outgoing_damage_per_curse_stack_pct = {1, 2.5, 4}, other_cards_effectiveness_pct = {50, 35, 20}} + }, + { + id = 70, + quality = 4, + defaultCard = false, + price_free = 20800, + max_copies = 1, + values = {incoming_reduction_per_rage_pct = {0.1, 0.15, 0.2}} + }, + { + id = 71, + quality = 2, + defaultCard = false, + price_free = 5200, + max_copies = 2, + values = {max_health_pct_per_curse_stack = {1}, self_damage_max_hp_pct_per_sec = {2.5, 2, 1.5}} + }, + { + id = 72, + quality = 4, + defaultCard = false, + price_free = 20800, + max_copies = 1, + values = {cooldown_reduction_pct = {15, 25, 35}} + }, + { + id = 73, + quality = 4, + defaultCard = false, + price_free = 10400, + deck_slots = 3, + max_copies = 1, + values = {deferred_damage_pct = {30, 40, 50}, deferred_dot_duration_sec = {3, 3.5, 4}} + }, + { + id = 74, + quality = 5, + defaultCard = false, + price_free = 83200, + max_copies = 1, + inherent = true, + deck_slots = 3, + values = {crit_mult_bonus_pct = {30, 60, 90}, enemy_armor_reduction_pct = {20, 25, 30}, aura_radius = {600, 900, 1200}} + }, + { + id = 75, + quality = 5, + defaultCard = false, + price_free = 83200, + max_copies = 1, + values = { + mana_cost_pct = {15}, + base_damage = {90, 120, 150}, + damage_from_max_mana_pct = {5, 7, 11}, + blast_radius = {175}, + search_radius = {1000}, + blast_interval_sec = {5, 4, 3}, + blast_delay_sec = {1.2, 1, 0.8} + } + }, + { + id = 76, + quality = 4, + defaultCard = false, + price_free = 20800, + max_copies = 1, + values = {target_count = {1, 2, 3}, radius = {250, 300, 350}, mana_damage_pct = {20, 40, 60}} + }, + { + id = 77, + quality = 5, + defaultCard = false, + price_free = 83200, + max_copies = 1, + values = {damage_reduction_pct = {60, 70, 80}, mana_per_mitigated_damage = {1, 0.8, 0.67}} + }, + { + id = 78, + quality = 4, + defaultCard = false, + price_free = 20800, + max_copies = 1, + values = {stack_duration_sec = {10, 12, 14}, max_mana_pct_per_stack = {0.5, 1, 1.5}} + }, + { + id = 79, + quality = 3, + deck_slots = 4, + defaultCard = false, + price_free = 10400, + canupgrade = false, + max_copies = 2, + values = {} + }, + { + id = 80, + quality = 5, + defaultCard = false, + inherent = true, + deck_slots = 3, + price_free = 83200, + max_copies = 1, + values = {outgoing_damage_per_curse_stack_pct = {5, 7, 9}} + }, + { + id = 81, + quality = 5, + defaultCard = false, + purchasable = false, + canupgrade = true, + deck_builder_non_deckable = true, + deck_builder_unlock_card_id = 80, + price_free = 0, + max_copies = 1, + values = {outgoing_damage_pct = {15, 15, 15}, spell_amp_pct = {15, 15, 15}} + }, + { + id = 82, + quality = 5, + defaultCard = false, + purchasable = false, + canupgrade = true, + deck_builder_non_deckable = true, + deck_builder_unlock_card_id = 80, + price_free = 0, + max_copies = 1, + values = {max_health_pct = {20, 20, 20}, max_mana_pct = {20, 20, 20}} + }, + { + id = 83, + quality = 5, + defaultCard = false, + purchasable = false, + canupgrade = true, + deck_builder_non_deckable = true, + deck_builder_unlock_card_id = 80, + price_free = 0, + max_copies = 1, + values = {model_scale_pct = {20, 20, 20}, physical_vampirism_pct = {10, 10, 10}, magical_vampirism_pct = {10, 10, 10}} + }, + { + id = 84, + quality = 2, + defaultCard = false, + purchasable = false, + obtainable = false, + canupgrade = true, + deck_builder_non_deckable = true, + deck_builder_unlock_card_id = 33, + price_free = 5200, + max_copies = 2, + values = {spell_amp_from_mana_pct = {15, 20, 25}, spell_amp_mana_scale = 0.1} + }, + { + id = 85, + quality = 3, + defaultCard = false, + price_free = 10400, + max_copies = 2, + values = {bonus_card_choices = {1, 2, 3}, slag_pool_copies = {2}} + }, + { + id = 86, + quality = 1, + defaultCard = false, + purchasable = false, + obtainable = false, + canupgrade = false, + deck_builder_non_deckable = true, + price_free = 0, + max_copies = 0, + values = {} + }, + { + id = 87, + quality = 4, + defaultCard = false, + price_free = 20800, + deck_slots = 5, + max_copies = 1, + canupgrade = false, + values = {attr_pct_per_slag = {1.25, 1.5, 1.75}} + }, + { + id = 88, + quality = 2, + defaultCard = false, + price_free = 5200, + max_copies = 2, + canupgrade = false, + deck_slots = 2, + values = {free_rerolls_on_slag = {1}} + } + } +end +local function buildAll(self) + return {unpack(cards1to13(nil))} +end +____exports.ALL_CARD_CATALOG_DEFS = buildAll(nil) +--- Лимит слотов в deck builder и при нормализации колоды на сервере. +____exports.DECK_BUILDER_SLOT_CAPACITY = 30 +____exports.FROSTMOURNE_CARD_ID = 80 +____exports.FROSTMOURNE_SHARD_CARD_IDS = {81, 82, 83} +--- Карта желания и её дары (59–61): только через выбор, не в колоду. +____exports.WISH_CARD_ID = 62 +____exports.WISH_GIFT_CARD_IDS = {59, 60, 61} +____exports.MANA_SURGE_CARD_ID = 33 +____exports.SMALL_MANA_SURGE_CARD_ID = 84 +____exports.FISHING_CARD_ID = 85 +--- Пустая карта-шлак в пуле (только через «Рыбаловство»). +____exports.SLAG_CARD_ID = 86 +function ____exports.isWishGiftCardId(self, cardId) + local id = math.floor(__TS__Number(cardId)) + for ____, giftId in ipairs(____exports.WISH_GIFT_CARD_IDS) do + if giftId == id then + return true + end + end + return false +end +function ____exports.getDeckBuilderUnlockRequiredMessage(self, unlockCardId) + local id = math.floor(__TS__Number(unlockCardId)) + if id == ____exports.FROSTMOURNE_CARD_ID then + return "Нужна купленная карта «Фростморн»" + end + if id == ____exports.WISH_CARD_ID then + return "Нужна купленная карта «Карта желания»" + end + if id == ____exports.MANA_SURGE_CARD_ID then + return "Нужна купленная карта «Буйство маны»" + end + return "Нужна купленная родительская карта" +end +function ____exports.isFrostmourneShardCardId(self, cardId) + local id = math.floor(__TS__Number(cardId)) + for ____, shardId in ipairs(____exports.FROSTMOURNE_SHARD_CARD_IDS) do + if shardId == id then + return true + end + end + return false +end +--- Сколько слотов колоды занимает карта по каталогу (по умолчанию 1). +function ____exports.getCardDeckSlotsFromCatalog(self, cardId) + local row = __TS__ArrayFind( + ____exports.ALL_CARD_CATALOG_DEFS, + function(____, c) return c.id == cardId end + ) + local configured = __TS__Number(row and row.deck_slots) + if __TS__NumberIsFinite(configured) and configured > 0 then + return math.floor(configured) + end + return 1 +end +____exports.DEFAULT_DECK_CARD_IDS = __TS__ArraySort( + __TS__ArrayMap( + __TS__ArrayFilter( + ____exports.ALL_CARD_CATALOG_DEFS, + function(____, c) return c.defaultCard end + ), + function(____, c) return c.id end + ), + function(____, a, b) return a - b end +) +--- Строки для витрины магазина (только нестартовые карты). +function ____exports.getCardShopRows(self) + return __TS__ArrayMap( + __TS__ArrayFilter( + ____exports.ALL_CARD_CATALOG_DEFS, + function(____, c) return not c.defaultCard end + ), + function(____, c) return {id = c.id, price_free = c.price_free, quality = c.quality} end + ) +end +--- Компенсация дубликата БП по id вида card_data_N. +function ____exports.buildCardPurchaseCompensationMap(self) + local r = {} + for ____, c in ipairs(____exports.ALL_CARD_CATALOG_DEFS) do + if not c.defaultCard and c.price_free > 0 then + r["card_data_" .. tostring(c.id)] = {price_free = c.price_free} + end + end + return r +end +return ____exports diff --git a/scripts/vscripts/cards/card_52_effects.lua b/scripts/vscripts/cards/card_52_effects.lua new file mode 100644 index 0000000..3784218 --- /dev/null +++ b/scripts/vscripts/cards/card_52_effects.lua @@ -0,0 +1,126 @@ +local ____lualib = require("lualib_bundle") +local __TS__Number = ____lualib.__TS__Number +local __TS__NumberIsFinite = ____lualib.__TS__NumberIsFinite +local ____exports = {} +local ____card_data = require("cards.card_data") +local CARD_DATABASE = ____card_data.CARD_DATABASE +local CARD_ID = 52 +--- Минимальная длительность ночи (сек.) после суммарного сокращения от карты 52. +____exports.CARD_52_MIN_NIGHT_DURATION_SEC = 30 +local function resolveNumeric(self, value, fallback) + local numeric = __TS__Number(value) + if not __TS__NumberIsFinite(numeric) then + return fallback + end + return numeric +end +local function getCard52LevelValue(self, playerId, key, fallback) + local ____opt_2 = CARD_DATABASE[CARD_ID] + local ____opt_0 = ____opt_2 and ____opt_2.values + local raw = ____opt_0 and ____opt_0[key] + if raw == nil or raw == nil then + return fallback + end + if type(raw) ~= "table" then + return resolveNumeric(nil, raw, fallback) + end + local player = PlayerResource:GetPlayer(playerId) + local ____opt_4 = player and player.cardSystem + local levelRaw = __TS__Number(____opt_4 and ____opt_4:GetCardLevel(CARD_ID) or 1) + local level = __TS__NumberIsFinite(levelRaw) and levelRaw > 0 and math.floor(levelRaw) or 1 + local tableRaw = raw + local byLevel = resolveNumeric(nil, tableRaw[level], 0 / 0) + if __TS__NumberIsFinite(byLevel) then + return byLevel + end + local byLevelString = resolveNumeric( + nil, + tableRaw[tostring(level)], + 0 / 0 + ) + if __TS__NumberIsFinite(byLevelString) then + return byLevelString + end + local levelOne = resolveNumeric(nil, tableRaw[1], 0 / 0) + if __TS__NumberIsFinite(levelOne) then + return levelOne + end + local levelOneString = resolveNumeric(nil, tableRaw["1"], 0 / 0) + if __TS__NumberIsFinite(levelOneString) then + return levelOneString + end + return fallback +end +--- Сумма копий карты 52 у всех игроков. +function ____exports.getTotalCard52Copies(self) + if not IsServer() then + return 0 + end + local totalCopies = 0 + do + local i = 0 + while i < DOTA_MAX_PLAYERS do + do + local player = PlayerResource:GetPlayer(i) + if not player or not player.cardSystem then + goto __continue13 + end + totalCopies = totalCopies + player.cardSystem:GetActiveCardCopies(CARD_ID) + end + ::__continue13:: + i = i + 1 + end + end + return totalCopies +end +local function forEachPlayerCard52(self, handler) + if not IsServer() then + return + end + do + local i = 0 + while i < DOTA_MAX_PLAYERS do + do + local player = PlayerResource:GetPlayer(i) + if not player or not player.cardSystem then + goto __continue17 + end + local copies = player.cardSystem:GetActiveCardCopies(CARD_ID) + if copies <= 0 then + goto __continue17 + end + handler(nil, i, copies) + end + ::__continue17:: + i = i + 1 + end + end +end +--- Множитель статов врагов от карты 52: 1 + X% за каждую копию. +function ____exports.getEnemyStatsMultiplierFromCard52(self) + local totalBonusPct = 0 + forEachPlayerCard52( + nil, + function(____, playerId, copies) + local bonusPctPerCopy = getCard52LevelValue(nil, playerId, "enemy_stats_bonus_pct", 8) + totalBonusPct = totalBonusPct + bonusPctPerCopy * copies + end + ) + return 1 + totalBonusPct / 100 +end +--- На сколько секунд сокращается ночь от карты 52 (суммарно по всем копиям). +function ____exports.getNightDurationReductionSecFromCard52(self) + local totalReduceSec = 0 + forEachPlayerCard52( + nil, + function(____, playerId, copies) + local reduceSecPerCopy = getCard52LevelValue(nil, playerId, "night_duration_reduce_sec", 30) + totalReduceSec = totalReduceSec + reduceSecPerCopy * copies + end + ) + return math.max( + 0, + math.floor(totalReduceSec) + ) +end +return ____exports diff --git a/scripts/vscripts/cards/card_bat_stacking.lua b/scripts/vscripts/cards/card_bat_stacking.lua new file mode 100644 index 0000000..fb706d2 --- /dev/null +++ b/scripts/vscripts/cards/card_bat_stacking.lua @@ -0,0 +1,67 @@ +local ____lualib = require("lualib_bundle") +local __TS__Number = ____lualib.__TS__Number +local __TS__NumberIsFinite = ____lualib.__TS__NumberIsFinite +local ____exports = {} +local ____card_value_resolver = require("cards.card_value_resolver") +local getCardValueByLevel = ____card_value_resolver.getCardValueByLevel +____exports.MODIFIER_CARD_2 = "modifier_card_2" +____exports.MODIFIER_CARD_43 = "modifier_card_43" +local CARD_2_ID = 2 +local MIN_BAT = 0.35 +local DEFAULT_BAT = 1.7 +--- BAT из KV юнита (AttackRate). GetBaseAttackTime() в рантайме — уже модифицированный интервал (~0.24), не подходит. +function ____exports.readHeroKVBaseAttackTime(self, parent) + if not parent or not IsValidEntity(parent) then + return DEFAULT_BAT + end + local unitName = parent:GetUnitName() + if unitName ~= nil then + local kv = GetUnitKeyValuesByName(unitName) + local rateRaw = kv and kv.AttackRate + local rate = tonumber(rateRaw) or __TS__Number(rateRaw) + if __TS__NumberIsFinite(rate) and rate > 0 then + return rate + end + end + return DEFAULT_BAT +end +function ____exports.getCard2BatReductionPercent(self, hero) + if not hero or not IsValidEntity(hero) then + return 0 + end + return math.max( + 0, + getCardValueByLevel( + nil, + CARD_2_ID, + hero, + "attacktime", + 0 + ) + ) +end +--- Целевой BAT после карт 2 (%) и суммарного flat с копий карты 43. +function ____exports.computeTargetBat(self, parent, card43FlatReduction) + local baseBat = ____exports.readHeroKVBaseAttackTime(nil, parent) + local card2Pct = ____exports.getCard2BatReductionPercent(nil, parent) + local flat = math.max(0, card43FlatReduction) + local targetBat = baseBat * (1 - card2Pct * 0.01) - flat + return math.max(MIN_BAT, targetBat) +end +--- Прямое применение BAT на сервере (главный путь для карты 43). +function ____exports.applyHeroTargetBat(self, parent, card43FlatReduction) + if not IsServer() or not parent or not IsValidEntity(parent) then + return + end + parent:SetBaseAttackTime(____exports.computeTargetBat(nil, parent, card43FlatReduction)) +end +--- Запасной IAS, если SetBaseAttackTime перезаписали. +function ____exports.computeAttackSpeedBonusForTargetBat(self, parent, card43FlatReduction) + local currentBat = parent:GetBaseAttackTime() + local targetBat = ____exports.computeTargetBat(nil, parent, card43FlatReduction) + if not __TS__NumberIsFinite(currentBat) or currentBat <= 0 or targetBat >= currentBat - 0.001 then + return 0 + end + return 100 * (currentBat / targetBat - 1) +end +return ____exports diff --git a/scripts/vscripts/cards/card_data.lua b/scripts/vscripts/cards/card_data.lua new file mode 100644 index 0000000..cb83fce --- /dev/null +++ b/scripts/vscripts/cards/card_data.lua @@ -0,0 +1,78 @@ +local ____lualib = require("lualib_bundle") +local __TS__ObjectAssign = ____lualib.__TS__ObjectAssign +local __TS__ObjectEntries = ____lualib.__TS__ObjectEntries +local ____exports = {} +local ____card_catalog = require("card_catalog") +local ALL_CARD_CATALOG_DEFS = ____card_catalog.ALL_CARD_CATALOG_DEFS +local ____CardSystem = require("cards.CardSystem") +local CardQuality = ____CardSystem.CardQuality +local CardSystem = ____CardSystem.CardSystem +local function iconForId(self, id) + return ("file://{images}/custom_game/cards/card_" .. tostring(id)) .. ".png" +end +local function catalogRowToCardData(self, row) + return { + id = row.id, + name = ("card_" .. tostring(row.id)) .. "_name", + description = ("card_" .. tostring(row.id)) .. "_description", + quality = row.quality, + icon = iconForId(nil, row.id), + default = row.defaultCard, + inherent = row.inherent == true, + purchasable = row.purchasable ~= false, + obtainable = row.obtainable ~= false, + canupgrade = row.canupgrade ~= false, + disabled = row.obtainable == false and row.deck_builder_non_deckable ~= true, + values = row.values, + max_copies = row.max_copies, + deck_slots = row.deck_slots or 1, + deck_builder_non_deckable = row.deck_builder_non_deckable == true, + deck_builder_unlock_card_id = row.deck_builder_unlock_card_id, + template = row.template, + title = row.title, + body = row.body + } +end +____exports.CARD_DATABASE = {} +for ____, row in ipairs(ALL_CARD_CATALOG_DEFS) do + ____exports.CARD_DATABASE[row.id] = catalogRowToCardData(nil, row) +end +____exports.CARD_DATABASE[404] = { + id = 404, + name = "card_404_name", + description = "card_404_description", + quality = CardQuality.COMMON, + values = {}, + disabled = true, + inherent = false, + purchasable = false, + obtainable = false, + icon = "file://{images}/custom_game/cards/card_404.png" +} +____exports.CARD_DATABASE[86] = { + id = 86, + name = "card_86_name", + description = "card_86_description", + quality = CardQuality.COMMON, + values = {}, + disabled = true, + inherent = false, + purchasable = false, + obtainable = false, + deck_builder_non_deckable = true, + icon = "file://{images}/custom_game/cards/card_86.png" +} +--- Инициализация базы данных карт +function ____exports.InitializeCardDatabase(self) + local result = {} + for ____, ____value in ipairs(__TS__ObjectEntries(____exports.CARD_DATABASE)) do + local idRaw = ____value[1] + local data = ____value[2] + local id = tonumber(idRaw) + local hasCustomCardClass = CardSystem.cardTypes["card_" .. tostring(id)] ~= nil + local disabledByMissingCustomClass = id ~= 404 and id ~= 86 and not hasCustomCardClass + result[id] = __TS__ObjectAssign({}, data, {disabled = data.disabled == true or disabledByMissingCustomClass}) + end + CardSystem.cardData = result +end +return ____exports diff --git a/scripts/vscripts/cards/card_morning_hooks.lua b/scripts/vscripts/cards/card_morning_hooks.lua new file mode 100644 index 0000000..1e344ff --- /dev/null +++ b/scripts/vscripts/cards/card_morning_hooks.lua @@ -0,0 +1,29 @@ +--[[ Generated with https://github.com/TypeScriptToLua/TypeScriptToLua ]] +local ____exports = {} +local ____custom_game_events = require("custom_game_events") +local ensurePlayerCardSystem = ____custom_game_events.ensurePlayerCardSystem +local ____card_51 = require("cards.examples.card_51") +local notifyCard51MorningStarted = ____card_51.notifyCard51MorningStarted +____exports.notifyCard51MorningStarted = notifyCard51MorningStarted +--- Рассвет: убрать шлак (86) из пула у всех игроков с CardSystem. +function ____exports.notifySlagMorningStarted(self, _morningSequence) + if not IsServer() then + return + end + do + local i = 0 + while i < DOTA_MAX_PLAYERS do + do + local pid = i + local cardSystem = ensurePlayerCardSystem(nil, pid) + if not cardSystem then + goto __continue4 + end + cardSystem:ClearSlagFromPool() + end + ::__continue4:: + i = i + 1 + end + end +end +return ____exports diff --git a/scripts/vscripts/cards/card_slag.lua b/scripts/vscripts/cards/card_slag.lua new file mode 100644 index 0000000..3b7d740 --- /dev/null +++ b/scripts/vscripts/cards/card_slag.lua @@ -0,0 +1,15 @@ +local ____lualib = require("lualib_bundle") +local __TS__Number = ____lualib.__TS__Number +local ____exports = {} +--- Карта «Рыбаловство» и служебная «Шлак» в пуле выбора карт. +____exports.FISHING_CARD_ID = 85 +____exports.SLAG_CARD_ID = 86 +function ____exports.isSlagCardId(self, cardId) + return math.floor(__TS__Number(cardId)) == ____exports.SLAG_CARD_ID +end +--- Пустая карта-заглушка в окне выбора (404 — недобор слотов, 86 — шлак из пула). +function ____exports.isEmptySelectionCardId(self, cardId) + local id = math.floor(__TS__Number(cardId)) + return id == 404 or id == ____exports.SLAG_CARD_ID +end +return ____exports diff --git a/scripts/vscripts/cards/card_value_resolver.lua b/scripts/vscripts/cards/card_value_resolver.lua new file mode 100644 index 0000000..39a6819 --- /dev/null +++ b/scripts/vscripts/cards/card_value_resolver.lua @@ -0,0 +1,152 @@ +local ____lualib = require("lualib_bundle") +local __TS__Number = ____lualib.__TS__Number +local __TS__NumberIsFinite = ____lualib.__TS__NumberIsFinite +local __TS__ObjectEntries = ____lualib.__TS__ObjectEntries +local ____exports = {} +local applyCard69OtherCardsEfficiencyMul, asLeveledValues, getCardLevel, CARD_GLOBAL_EFFICIENCY_DEBUFF_ID +local ____card_data = require("cards.card_data") +local CARD_DATABASE = ____card_data.CARD_DATABASE +function applyCard69OtherCardsEfficiencyMul(self, value, cardId, hero) + if cardId == CARD_GLOBAL_EFFICIENCY_DEBUFF_ID then + return value + end + if not hero or not IsValidEntity(hero) or not hero:IsRealHero() then + return value + end + if not hero:HasModifier("modifier_card_69") then + return value + end + local weakerPct = ____exports.getCardValueByLevel( + nil, + CARD_GLOBAL_EFFICIENCY_DEBUFF_ID, + hero, + "other_cards_effectiveness_pct", + 50 + ) + local clamped = math.max( + 0, + math.min(100, weakerPct) + ) + local mult = 1 - clamped / 100 + return value * mult +end +function asLeveledValues(self, raw) + if raw == nil or raw == nil then + return nil + end + if type(raw) ~= "table" then + return nil + end + local result = {} + local tableRaw = raw + do + local i = 1 + while i <= 32 do + local value = tableRaw[i] + local numeric = __TS__Number(value) + if value ~= nil and value ~= nil and __TS__NumberIsFinite(numeric) then + result[i] = numeric + end + i = i + 1 + end + end + for ____, ____value in ipairs(__TS__ObjectEntries(tableRaw)) do + local keyRaw = ____value[1] + local value = ____value[2] + do + local level = __TS__Number(keyRaw) + if not __TS__NumberIsFinite(level) then + goto __continue11 + end + local levelInt = math.floor(level) + if levelInt < 1 then + goto __continue11 + end + local numeric = __TS__Number(value) + if not __TS__NumberIsFinite(numeric) then + goto __continue11 + end + result[levelInt] = numeric + end + ::__continue11:: + end + return result +end +function getCardLevel(self, hero, cardId) + local level = 1 + if hero and IsValidEntity(hero) then + local heroUnsafe = hero + if type(heroUnsafe.IsRealHero) == "function" and not heroUnsafe:IsRealHero() then + return 1 + end + local owner = type(heroUnsafe.GetPlayerOwner) == "function" and heroUnsafe:GetPlayerOwner() or nil + if owner and owner.cardSystem then + level = owner.cardSystem:GetCardLevel(cardId) + else + local playerOwnerId = type(heroUnsafe.GetPlayerOwnerID) == "function" and heroUnsafe:GetPlayerOwnerID() or -1 + local directPlayerId = type(heroUnsafe.GetPlayerID) == "function" and heroUnsafe:GetPlayerID() or -1 + local playerId = playerOwnerId >= 0 and playerOwnerId or directPlayerId + local safePlayerResource = _G.PlayerResource + if playerId >= 0 and safePlayerResource then + local player = safePlayerResource:GetPlayer(playerId) + local ____opt_2 = player and player.cardSystem + local levelFromSystem = ____opt_2 and ____opt_2:GetCardLevel(cardId) + if __TS__NumberIsFinite(levelFromSystem) then + level = levelFromSystem + end + end + if level <= 1 and playerId >= 0 then + local levelsData = CustomNetTables:GetTableValue( + "cards", + "card_levels_" .. tostring(playerId) + ) + local levelEntry = levelsData and levelsData[tostring(cardId)] + local levelFromNettable = __TS__Number(levelEntry and levelEntry.level or 1) + if __TS__NumberIsFinite(levelFromNettable) and levelFromNettable > 0 then + level = levelFromNettable + end + end + end + end + local safeLevel = __TS__NumberIsFinite(level) and level or 1 + return math.max( + 1, + math.floor(safeLevel) + ) +end +--- Значение карты с учетом уровня (если в каталоге указан массив значений по уровням). +function ____exports.getCardValueByLevel(self, cardId, hero, key, fallback, explicitLevel) + local safeFallback = __TS__NumberIsFinite(fallback) and fallback or 0 + local ____opt_12 = CARD_DATABASE[cardId] + local ____opt_10 = ____opt_12 and ____opt_12.values + local raw = ____opt_10 and ____opt_10[key] + if raw == nil or raw == nil then + return applyCard69OtherCardsEfficiencyMul(nil, safeFallback, cardId, hero) + end + local level = explicitLevel ~= nil and __TS__NumberIsFinite(explicitLevel) and math.max( + 1, + math.floor(explicitLevel) + ) or getCardLevel(nil, hero, cardId) + if type(raw) ~= "table" then + local numeric = __TS__Number(raw) + local resolved = __TS__NumberIsFinite(numeric) and numeric or safeFallback + return applyCard69OtherCardsEfficiencyMul(nil, resolved, cardId, hero) + end + local leveled = asLeveledValues(nil, raw) + if not leveled then + return applyCard69OtherCardsEfficiencyMul(nil, safeFallback, cardId, hero) + end + do + local current = level + while current >= 1 do + local value = leveled[current] + if value ~= nil and value ~= nil and __TS__NumberIsFinite(value) then + return applyCard69OtherCardsEfficiencyMul(nil, value, cardId, hero) + end + current = current - 1 + end + end + return applyCard69OtherCardsEfficiencyMul(nil, safeFallback, cardId, hero) +end +CARD_GLOBAL_EFFICIENCY_DEBUFF_ID = 69 +return ____exports diff --git a/scripts/vscripts/cards/cardbasemodifier.lua b/scripts/vscripts/cards/cardbasemodifier.lua new file mode 100644 index 0000000..ea3c21f --- /dev/null +++ b/scripts/vscripts/cards/cardbasemodifier.lua @@ -0,0 +1,143 @@ +local ____lualib = require("lualib_bundle") +local __TS__Class = ____lualib.__TS__Class +local __TS__ClassExtends = ____lualib.__TS__ClassExtends +local __TS__StringStartsWith = ____lualib.__TS__StringStartsWith +local __TS__StringSubstring = ____lualib.__TS__StringSubstring +local __TS__Number = ____lualib.__TS__Number +local __TS__NumberIsFinite = ____lualib.__TS__NumberIsFinite +local __TS__StringReplace = ____lualib.__TS__StringReplace +local ____exports = {} +local ____dota_ts_adapter = require("lib.dota_ts_adapter") +local BaseModifier = ____dota_ts_adapter.BaseModifier +local ____card_value_resolver = require("cards.card_value_resolver") +local getCardValueByLevel = ____card_value_resolver.getCardValueByLevel +--- Базовый модификатор для всех карт +____exports.CardBaseModifier = __TS__Class() +local CardBaseModifier = ____exports.CardBaseModifier +CardBaseModifier.name = "CardBaseModifier" +CardBaseModifier.____file_path = "scripts/vscripts/cards/CardBaseModifier.lua" +__TS__ClassExtends(CardBaseModifier, BaseModifier) +function CardBaseModifier.prototype.getCardValue(self, key, fallback, cardId) + local resolvedCardId = cardId or self:getCardIdFromModifierName() + if not resolvedCardId or resolvedCardId <= 0 then + return fallback + end + local parent = self:GetParent() + local hero = parent and IsValidEntity(parent) and parent:IsRealHero() and parent or nil + return getCardValueByLevel( + nil, + resolvedCardId, + hero, + key, + fallback, + self.cardLevelSnapshot + ) +end +function CardBaseModifier.prototype.getCardCopies(self) + return math.max( + 1, + math.floor(self:GetStackCount() or 0) + ) +end +function CardBaseModifier.prototype.getScaledCardValue(self, key, fallback, cardId) + return self:getCardValue(key, fallback, cardId) * self:getCardCopies() +end +function CardBaseModifier.prototype.getCardIdFromModifierName(self) + local name = self:GetName() + local prefix = "modifier_card_" + if not __TS__StringStartsWith(name, prefix) then + return 0 + end + local idRaw = __TS__StringSubstring(name, #prefix) + if #idRaw <= 0 then + return 0 + end + local value = __TS__Number(idRaw) + return __TS__NumberIsFinite(value) and math.floor(value) or 0 +end +function CardBaseModifier.prototype.OnCreated(self, params) + local ____opt_result_2 + if params ~= nil then + ____opt_result_2 = params.card_level + end + local levelRaw = __TS__Number(____opt_result_2) + if __TS__NumberIsFinite(levelRaw) and levelRaw > 0 then + self.cardLevelSnapshot = math.floor(levelRaw) + else + self.cardLevelSnapshot = nil + end + if IsClient() then + self:OnCustomCreated(params) + return + end + if not IsServer() then + return + end + if self:GetStackCount() <= 0 then + self:SetStackCount(1) + end + self:SetHasCustomTransmitterData(true) + self:SendBuffRefreshToClients() + self:OnCustomCreated(params) +end +function CardBaseModifier.prototype.OnRefresh(self) + if not IsServer() then + return + end + self:OnCustomRefresh({}) + self:SendBuffRefreshToClients() + local parent = self:GetParent() + if parent and IsValidEntity(parent) and parent:IsRealHero() then + parent:CalculateStatBonus(true) + end +end +function CardBaseModifier.prototype.OnCustomRefresh(self, _params) +end +function CardBaseModifier.prototype.IsPermanent(self) + return true +end +function CardBaseModifier.prototype.IsHidden(self) + return false +end +function CardBaseModifier.prototype.IsDebuff(self) + return false +end +function CardBaseModifier.prototype.RemoveOnDeath(self) + return false +end +function CardBaseModifier.prototype.IsPurgable(self) + return false +end +function CardBaseModifier.prototype.GetAttributes(self) + return MODIFIER_ATTRIBUTE_MULTIPLE +end +function CardBaseModifier.prototype.OnCustomCreated(self, params) +end +function CardBaseModifier.prototype.HandleCustomTransmitterData(self, data) + local ____opt_result_5 + if data ~= nil then + ____opt_result_5 = data.card_level_snapshot + end + local levelRaw = __TS__Number(____opt_result_5) + if __TS__NumberIsFinite(levelRaw) and levelRaw > 0 then + self.cardLevelSnapshot = math.floor(levelRaw) + end +end +function CardBaseModifier.prototype.AddCustomTransmitterData(self) + return {card_level_snapshot = self.cardLevelSnapshot or 1} +end +function CardBaseModifier.prototype.GetTexture(self) + local modifierName = self:GetName() + local cardId = __TS__StringReplace(modifierName, "modifier_card_", "") + return "cards/card_" .. cardId +end +function CardBaseModifier.prototype.GetTooltip(self) + return ("dota_tooltip_modifier_" .. self:GetName()) .. "_Description" +end +function CardBaseModifier.prototype.DeclareFunctions(self) + return {MODIFIER_PROPERTY_TOOLTIP} +end +function CardBaseModifier.prototype.OnTooltip(self) + return self:GetStackCount() +end +return ____exports diff --git a/scripts/vscripts/cards/cards_init.lua b/scripts/vscripts/cards/cards_init.lua new file mode 100644 index 0000000..7d77249 --- /dev/null +++ b/scripts/vscripts/cards/cards_init.lua @@ -0,0 +1,102 @@ +--[[ Generated with https://github.com/TypeScriptToLua/TypeScriptToLua ]] +local ____exports = {} +require("cards.examples.card_49_modifier") +require("cards.modifier_card_greed") +local ____CardSystem = require("cards.CardSystem") +local InitCardSystem = ____CardSystem.InitCardSystem +local ____card_data = require("cards.card_data") +local InitializeCardDatabase = ____card_data.InitializeCardDatabase +require("cards.examples.card_1") +require("cards.examples.card_2") +require("cards.examples.card_3") +require("cards.examples.card_4") +require("cards.examples.card_5") +require("cards.examples.card_6") +require("cards.examples.card_7") +require("cards.examples.card_8") +require("cards.examples.card_9") +require("cards.examples.card_10") +require("cards.examples.card_11") +require("cards.examples.card_12") +require("cards.examples.card_13") +require("cards.examples.card_14") +require("cards.examples.card_15") +require("cards.examples.card_16") +require("cards.examples.card_17") +require("cards.examples.card_18") +require("cards.examples.card_19") +require("cards.examples.card_20") +require("cards.examples.card_21") +require("cards.examples.card_22") +require("cards.examples.card_23") +require("cards.examples.card_24") +require("cards.examples.card_25") +require("cards.examples.card_26") +require("cards.examples.card_27") +require("cards.examples.card_28") +require("cards.examples.card_29") +require("cards.examples.card_30") +require("cards.examples.card_31") +require("cards.examples.card_32") +require("cards.examples.card_33") +require("cards.examples.card_34") +require("cards.examples.card_35") +require("cards.examples.card_36") +require("cards.examples.card_37") +require("cards.examples.card_38") +require("cards.examples.card_39") +require("cards.examples.card_40") +require("cards.examples.card_41") +require("cards.examples.card_42") +require("cards.examples.card_43") +require("cards.examples.card_44") +require("cards.examples.card_45") +require("cards.examples.card_46") +require("cards.examples.card_47") +require("cards.examples.card_48") +require("cards.examples.card_49") +require("cards.examples.card_50") +require("cards.examples.card_51") +require("cards.examples.card_52") +require("cards.examples.card_53") +require("cards.examples.card_54") +require("cards.examples.card_55") +require("cards.examples.card_56") +require("cards.examples.card_57") +require("cards.examples.card_58") +require("cards.examples.card_59") +require("cards.examples.card_60") +require("cards.examples.card_61") +require("cards.examples.card_62") +require("cards.examples.card_63") +require("cards.examples.card_64") +require("cards.examples.card_65") +require("cards.examples.card_66") +require("cards.examples.card_67") +require("cards.examples.card_68") +require("cards.examples.card_69") +require("cards.examples.card_70") +require("cards.examples.card_71") +require("cards.examples.card_72") +require("cards.examples.card_73") +require("cards.examples.card_74") +require("cards.examples.card_75") +require("cards.examples.card_76") +require("cards.examples.card_77") +require("cards.examples.card_78") +require("cards.examples.card_79") +require("cards.examples.card_80") +require("cards.examples.card_81") +require("cards.examples.card_82") +require("cards.examples.card_83") +require("cards.examples.card_84") +require("cards.examples.card_85") +require("cards.examples.card_86") +require("cards.examples.card_87") +require("cards.examples.card_88") +--- Инициализация Card System +function ____exports.InitCards(self) + InitializeCardDatabase(nil) + InitCardSystem(nil) +end +return ____exports diff --git a/scripts/vscripts/cards/cardsystem.lua b/scripts/vscripts/cards/cardsystem.lua new file mode 100644 index 0000000..69faae0 --- /dev/null +++ b/scripts/vscripts/cards/cardsystem.lua @@ -0,0 +1,4202 @@ +local ____lualib = require("lualib_bundle") +local __TS__Number = ____lualib.__TS__Number +local __TS__NumberIsFinite = ____lualib.__TS__NumberIsFinite +local __TS__Class = ____lualib.__TS__Class +local __TS__New = ____lualib.__TS__New +local __TS__StringReplace = ____lualib.__TS__StringReplace +local __TS__ObjectEntries = ____lualib.__TS__ObjectEntries +local __TS__ArrayIsArray = ____lualib.__TS__ArrayIsArray +local __TS__StringTrim = ____lualib.__TS__StringTrim +local __TS__Spread = ____lualib.__TS__Spread +local __TS__StringIncludes = ____lualib.__TS__StringIncludes +local __TS__StringSplit = ____lualib.__TS__StringSplit +local __TS__Delete = ____lualib.__TS__Delete +local __TS__ArrayFindIndex = ____lualib.__TS__ArrayFindIndex +local __TS__ArrayFind = ____lualib.__TS__ArrayFind +local __TS__ArrayIncludes = ____lualib.__TS__ArrayIncludes +local Set = ____lualib.Set +local __TS__Iterator = ____lualib.__TS__Iterator +local __TS__ParseInt = ____lualib.__TS__ParseInt +local __TS__NumberIsNaN = ____lualib.__TS__NumberIsNaN +local __TS__ObjectKeys = ____lualib.__TS__ObjectKeys +local __TS__ArrayFilter = ____lualib.__TS__ArrayFilter +local __TS__ArrayIndexOf = ____lualib.__TS__ArrayIndexOf +local __TS__ArraySplice = ____lualib.__TS__ArraySplice +local __TS__SparseArrayNew = ____lualib.__TS__SparseArrayNew +local __TS__SparseArrayPush = ____lualib.__TS__SparseArrayPush +local __TS__SparseArraySpread = ____lualib.__TS__SparseArraySpread +local __TS__ObjectAssign = ____lualib.__TS__ObjectAssign +local __TS__ArraySort = ____lualib.__TS__ArraySort +local __TS__ArrayMap = ____lualib.__TS__ArrayMap +local __TS__ArraySlice = ____lualib.__TS__ArraySlice +local __TS__ObjectValues = ____lualib.__TS__ObjectValues +local __TS__ArrayFrom = ____lualib.__TS__ArrayFrom +local __TS__TypeOf = ____lualib.__TS__TypeOf +local ____exports = {} +local ____crystal_currency = require("crystal_currency") +local CrystalCurrency = ____crystal_currency.CrystalCurrency +local ____modifier_stats_multiplier = require("modifiers.modifier_stats_multiplier") +local invalidateStatsMultiplierSumCache = ____modifier_stats_multiplier.invalidateStatsMultiplierSumCache +local ____server_config = require("server_config") +local SERVER_CONFIG = ____server_config.SERVER_CONFIG +local ____api_helper = require("api_helper") +local setApiHeaders = ____api_helper.setApiHeaders +local ____card_catalog = require("card_catalog") +local DEFAULT_DECK_CARD_IDS = ____card_catalog.DEFAULT_DECK_CARD_IDS +local DECK_BUILDER_SLOT_CAPACITY = ____card_catalog.DECK_BUILDER_SLOT_CAPACITY +local getDeckBuilderUnlockRequiredMessage = ____card_catalog.getDeckBuilderUnlockRequiredMessage +local ____real_lobby_player = require("utils.real_lobby_player") +local isRealLobbyPlayer = ____real_lobby_player.isRealLobbyPlayer +local ____card_slag = require("cards.card_slag") +local isEmptySelectionCardId = ____card_slag.isEmptySelectionCardId +local isSlagCardId = ____card_slag.isSlagCardId +local SLAG_CARD_ID = ____card_slag.SLAG_CARD_ID +local ____modifier_card_cursed = require("cards.modifier_card_cursed") +local addCursedStack = ____modifier_card_cursed.addCursedStack +local ____modifier_card_greed = require("cards.modifier_card_greed") +local isGreedPoolCardId = ____modifier_card_greed.isGreedPoolCardId +local registerHiddenGreedCard = ____modifier_card_greed.registerHiddenGreedCard +local updateGreedForHero = ____modifier_card_greed.updateGreedForHero +local DEFAULT_DECK_CARD_WEIGHT = 1 +local CARD_UPGRADE_MAX_LEVEL = 3 +local function isMirrorProtectedCardId(self, cardId) + do + local function ____catch() + return true, false + end + local ____try, ____hasReturned, ____returnValue = pcall(function() + local card80 = require("cards.examples.card_80") + local ____opt_0 = card80.isFrostmourneMirrorProtectedCardId + return true, (____opt_0 and ____opt_0( + card80, + math.floor(__TS__Number(cardId)) + )) == true + end) + if not ____try then + ____hasReturned, ____returnValue = ____catch() + end + if ____hasReturned then + return ____returnValue + end + end +end +--- Качество карт +____exports.CardQuality = CardQuality or ({}) +____exports.CardQuality.COMMON = 1 +____exports.CardQuality[____exports.CardQuality.COMMON] = "COMMON" +____exports.CardQuality.RARE = 2 +____exports.CardQuality[____exports.CardQuality.RARE] = "RARE" +____exports.CardQuality.EPIC = 3 +____exports.CardQuality[____exports.CardQuality.EPIC] = "EPIC" +____exports.CardQuality.LEGENDARY = 4 +____exports.CardQuality[____exports.CardQuality.LEGENDARY] = "LEGENDARY" +____exports.CardQuality.MYTHIC = 5 +____exports.CardQuality[____exports.CardQuality.MYTHIC] = "MYTHIC" +--- Карта 79 «Эхо выбора»: нельзя дублировать пустые, Фростморн/осколки и мифические карты. +local function isCard79RepeatBlockedCardId(self, cardId) + local id = math.floor(__TS__Number(cardId)) + if not __TS__NumberIsFinite(id) or id <= 0 then + return true + end + if isEmptySelectionCardId(nil, id) or isMirrorProtectedCardId(nil, id) then + return true + end + local cardData = ____exports.CardSystem.cardData[id] + return (cardData and cardData.quality) == ____exports.CardQuality.MYTHIC +end +--- Базовый класс для карт +____exports.CardBase = __TS__Class() +local CardBase = ____exports.CardBase +CardBase.name = "CardBase" +CardBase.____file_path = "scripts/vscripts/cards/CardSystem.lua" +function CardBase.prototype.____constructor(self, hero, cardKey) + self.modifiers = nil + self.id = hero:GetPlayerID() + self.cardKey = cardKey + self:OnCreated() + self:ModifierRefresh() +end +function CardBase.prototype.OnCreated(self) +end +function CardBase.prototype.OnDestroy(self) + if self.modifiers and not self.modifiers:IsNull() then + self.modifiers:Destroy() + end +end +function CardBase.prototype.OnTransfer(self) +end +function CardBase.prototype.GetHero(self) + local player = PlayerResource:GetPlayer(self.id) + return player:GetAssignedHero() +end +function CardBase.prototype.GetPlayer(self) + return PlayerResource:GetPlayer(self.id) +end +function CardBase.prototype.GetCardLevelSnapshot(self) + local player = self:GetPlayer() + local cardId = __TS__Number(self.cardKey) + if not player or not __TS__NumberIsFinite(cardId) or cardId <= 0 then + return 1 + end + local ____opt_4 = player.cardSystem + local level = ____opt_4 and ____opt_4:GetCardLevel(math.floor(cardId)) + return __TS__NumberIsFinite(level) and math.max( + 1, + math.floor(level) + ) or 1 +end +function CardBase.prototype.ModifierRefresh(self) + local hero = self:GetHero() + local modName = self:GetModifierName() + local reusedExistingModifier = false + if self.modifiers and not self.modifiers:IsNull() then + self.modifiers:Destroy() + self.modifiers = nil + end + if modName then + if IsServer() then + local existing = hero:FindModifierByName(modName) + if existing and not existing:IsNull() then + local currentStacks = math.max( + 0, + math.floor(existing:GetStackCount() or 0) + ) + existing:SetStackCount(currentStacks + 1) + existing:ForceRefresh() + hero:CalculateStatBonus(true) + self.modifiers = nil + reusedExistingModifier = true + end + end + if not reusedExistingModifier then + self.modifiers = hero:AddNewModifier( + hero, + getModifierSourceAbility(nil, hero), + modName, + {card_level = self:GetCardLevelSnapshot()} + ) + end + end + if IsServer() then + local cardId = __TS__Number(self.cardKey) + if isGreedPoolCardId(nil, cardId) then + local player = self:GetPlayer() + updateGreedForHero(nil, hero, player and player.cardSystem) + end + end +end +function CardBase.prototype.IsHidden(self) + return false +end +--- Регистрация класса карты +function ____exports.RegisterCard(self, target) + local className = target.name + ____exports.CardSystem.cardTypes[className] = target +end +--- Парсинг значений в описании карты +function ____exports.ParseCardDescription(self, description, values) + if not values then + return description + end + local parsedDescription = description + for ____, ____value in ipairs(__TS__ObjectEntries(values)) do + local key = ____value[1] + local value = ____value[2] + local pattern = __TS__New(RegExp, ("%" .. key) .. "%", "g") + parsedDescription = __TS__StringReplace( + parsedDescription, + pattern, + tostring(value) + ) + end + return parsedDescription +end +--- Основная система карт +____exports.CardSystem = __TS__Class() +local CardSystem = ____exports.CardSystem +CardSystem.name = "CardSystem" +CardSystem.____file_path = "scripts/vscripts/cards/CardSystem.lua" +function CardSystem.prototype.____constructor(self, playerId) + self.playerItems = {} + self.items = {} + self.itemsId = {} + self.decks = {} + self.activeDeckIndex = 0 + self.cardPieces = {} + self.newCardPieces = {} + self.rerollCost = 0 + self.cardsTaken = 0 + self.cardSelectionQueue = {} + self.isShowingCardSelection = false + self.currentSelectionSource = "unknown" + self.currentSelectionSourceChain = {} + self.currentSelectionToken = 0 + self.poolEntriesById = {} + self.poolNextEntrySeq = 1 + self.rerollQualityFilter = nil + self.guaranteedQualityRerolls = {} + self.card49FreeRerollCharges = 0 + self.card6FreeRerollCharges = 0 + self.card88FreeRerollCharges = 0 + self.card6FreeRerollLevelGranted = false + self.card51MorningGoldEarned = 0 + self.repeatNextSelectedCardOnce = false + self.cardLevelsLoaded = false + self.cardLevelsWaitTimerStarted = false + self.playerId = playerId + self.pool = pool(nil) + self:Init() + Timers:CreateTimer( + 1, + function() + self:SyncPool() + return nil + end + ) +end +function CardSystem.getCardDeckSlots(self, cardId) + local cardData = ____exports.CardSystem.cardData[cardId] + local configured = __TS__Number(cardData and cardData.deck_slots) + if __TS__NumberIsFinite(configured) and configured > 0 then + return math.floor(configured) + end + return 1 +end +function CardSystem.getDeckUsedSlots(self, cards) + local total = 0 + for ____, id in ipairs(cards) do + total = total + ____exports.CardSystem:getCardDeckSlots(id) + end + return total +end +function CardSystem.canAddCardToDeckSlots(self, cards, cardId, capacity) + return ____exports.CardSystem:getDeckUsedSlots(cards) + ____exports.CardSystem:getCardDeckSlots(cardId) <= capacity +end +function CardSystem.prototype.ForceDeckBuilderSync(self) + self:LoadDecks() + self:SyncDecks() + self:SyncCardData() + self:SyncPool() +end +function CardSystem.prototype.Init(self) + local restoredFromRuntimeState = self:RestoreRuntimeStateIfPresent() + if not restoredFromRuntimeState then + Timers:CreateTimer( + 1, + function() + self:LoadDecks() + self:loadCardLevelsFromServer() + self:InitCardPool() + end + ) + else + self:loadCardLevelsFromServer() + self:SyncDecks() + self:SyncPool() + self:SyncActiveCards() + self:SyncRerollCost() + end + self:SyncDecks() + self:SyncCardData() + self:SyncRerollCost() + CustomGameEventManager:RegisterListener( + "card_selected", + function(_, event) + if self.playerId ~= event.PlayerID then + return + end + self:OnChooseCard(event.PlayerID, event.index, event.selection_token) + end + ) + CustomGameEventManager:RegisterListener( + "card_reroll", + function(_, event) + if self.playerId ~= event.PlayerID then + return + end + self:RerollCards(event.PlayerID) + end + ) + CustomGameEventManager:RegisterListener( + "deck_new", + function(_, event) + if self.playerId ~= event.PlayerID then + return + end + self:CreateNewDeck(event.name) + end + ) + CustomGameEventManager:RegisterListener( + "deck_save", + function(_, event) + if self.playerId ~= event.PlayerID then + return + end + if event.index == nil or event.data == nil and event.cards == nil then + return + end + local cardsData = event.cards or event.data + if not cardsData then + return + end + self:SaveDeck(event.index, cardsData, event.name) + end + ) + CustomGameEventManager:RegisterListener( + "deck_delete", + function(_, event) + if self.playerId ~= event.PlayerID then + return + end + self:DeleteDeck(event.index) + end + ) + CustomGameEventManager:RegisterListener( + "deck_activate", + function(_, event) + if self.playerId ~= event.PlayerID then + return + end + self:ActivateDeck(event.index) + end + ) + CustomGameEventManager:RegisterListener( + "reset_card_pool", + function(_, event) + if self.playerId ~= event.PlayerID then + return + end + self:ResetCardPool() + end + ) + CustomGameEventManager:RegisterListener( + "hide_card_selection", + function(_, event) + if self.playerId ~= event.PlayerID then + return + end + self:HideCardSelection() + end + ) + CustomGameEventManager:RegisterListener( + "init_card_system", + function(_, event) + if self.playerId ~= event.PlayerID then + return + end + self:SyncDecks() + self:SyncPool() + end + ) + CustomGameEventManager:RegisterListener( + "request_pool_sync", + function(_, event) + if self.playerId ~= event.PlayerID then + return + end + self:SyncPool() + end + ) + CustomGameEventManager:RegisterListener( + "request_available_cards", + function(_, event) + if self.playerId ~= event.PlayerID then + return + end + self:LoadDecks() + self:loadCardLevelsFromServer() + self:SyncDecks() + self:SyncCardData() + self:SyncPool() + end + ) + CustomGameEventManager:RegisterListener( + "card_craft", + function(_, event) + if self.playerId ~= event.PlayerID then + return + end + self:CraftOrUpgradeCard(event.cardId, event.allowDebris) + end + ) + ListenToGameEvent( + "game_rules_state_change", + function() + local state = GameRules:State_Get() + if state == DOTA_GAMERULES_STATE_GAME_IN_PROGRESS then + self:InitCardPool() + end + end, + nil + ) +end +function CardSystem.prototype.RestoreRuntimeStateIfPresent(self) + local key = tostring(self.playerId) + local snapshot = ____exports.CardSystem.runtimeStateByPlayerId[key] + if not snapshot then + return false + end + self.decks = snapshot.decks or ({}) + self.activeDeckIndex = snapshot.activeDeckIndex or 0 + self.itemsId = __TS__ArrayIsArray(snapshot.itemsId) and ({unpack(snapshot.itemsId)}) or ({}) + self.poolEntriesById = {} + self.poolNextEntrySeq = math.max( + 1, + math.floor(__TS__Number(snapshot.poolNextEntrySeq or 1)) + ) + self.cardsTaken = math.max( + 0, + math.floor(__TS__Number(snapshot.cardsTaken) or 0) + ) + self.rerollCost = math.max( + 0, + math.floor(__TS__Number(snapshot.rerollCost) or 0) + ) + self.card49FreeRerollCharges = math.max( + 0, + math.floor(__TS__Number(snapshot.card49FreeRerollCharges) or 0) + ) + self.card6FreeRerollCharges = math.max( + 0, + math.floor(__TS__Number(snapshot.card6FreeRerollCharges) or 0) + ) + self.card88FreeRerollCharges = math.max( + 0, + math.floor(__TS__Number(snapshot.card88FreeRerollCharges) or 0) + ) + self.card6FreeRerollLevelGranted = snapshot.card6FreeRerollLevelGranted == true + self.card51MorningGoldEarned = math.max( + 0, + math.floor(__TS__Number(snapshot.card51MorningGoldEarned) or 0) + ) + self.pool:clear() + if __TS__ArrayIsArray(snapshot.poolData) then + for ____, item in ipairs(snapshot.poolData) do + do + local entryId = __TS__StringTrim(tostring(item.entry_id or "")) + local id = math.floor(__TS__Number(item.index)) + local weight = math.floor(__TS__Number(item.weight)) + if #entryId <= 0 or not __TS__NumberIsFinite(id) or id <= 0 or not __TS__NumberIsFinite(weight) or weight <= 0 then + goto __continue75 + end + self.poolEntriesById[entryId] = { + entryId = entryId, + cardId = id, + weight = weight, + sourceChain = __TS__ArrayIsArray(item.source_chain) and ({__TS__Spread(item.source_chain)}) or ({}) + } + end + ::__continue75:: + end + end + self:RebuildPoolFromEntries() + return true +end +function CardSystem.prototype.CaptureRuntimeState(self) + local poolData = {} + for entryId in pairs(self.poolEntriesById) do + do + local entry = self.poolEntriesById[entryId] + if not entry or entry.weight <= 0 then + goto __continue79 + end + poolData[#poolData + 1] = { + entry_id = entry.entryId, + index = entry.cardId, + weight = math.floor(entry.weight), + source_chain = entry.sourceChain and #entry.sourceChain > 0 and ({unpack(entry.sourceChain)}) or nil + } + end + ::__continue79:: + end + local state = { + poolData = poolData, + poolNextEntrySeq = self.poolNextEntrySeq, + itemsId = {unpack(self.itemsId)}, + cardsTaken = self.cardsTaken, + rerollCost = self.rerollCost, + card49FreeRerollCharges = self.card49FreeRerollCharges, + card6FreeRerollCharges = self.card6FreeRerollCharges, + card88FreeRerollCharges = self.card88FreeRerollCharges, + card6FreeRerollLevelGranted = self.card6FreeRerollLevelGranted, + card51MorningGoldEarned = self.card51MorningGoldEarned, + decks = self.decks, + activeDeckIndex = self.activeDeckIndex + } + ____exports.CardSystem.runtimeStateByPlayerId[tostring(self.playerId)] = state +end +function CardSystem.prototype.NormalizeSourceToken(self, sourceRaw) + local source = __TS__StringTrim(tostring(sourceRaw or "unknown")) + return #source > 0 and source or "unknown" +end +function CardSystem.prototype.IsNonGameplaySourceToken(self, tokenRaw) + local token = string.lower(self:NormalizeSourceToken(tokenRaw)) + return token == "unknown" or token == "deck_initial_pool" or token == "manual_pool_add" or token == "store_purchase_card" or token == "duplicate_current_pool" or token == "duplicate_unselected_current_options" or token == "threshold_empty_card_bonus" +end +function CardSystem.prototype.BuildSourceChain(self, sourceRaw, baseChain) + local chain = {} + local function pushToken(____, tokenRaw) + local token = self:NormalizeSourceToken(tokenRaw) + if self:IsNonGameplaySourceToken(token) then + return + end + local prev = #chain > 0 and chain[#chain] or nil + if prev ~= token then + chain[#chain + 1] = token + end + end + if baseChain and #baseChain > 0 then + for ____, token in ipairs(baseChain) do + pushToken(nil, token) + end + end + local normalizedSource = self:NormalizeSourceToken(sourceRaw) + if __TS__StringIncludes(normalizedSource, "->") then + local tokens = __TS__StringSplit(normalizedSource, "->") + for ____, token in ipairs(tokens) do + pushToken(nil, token) + end + else + pushToken(nil, normalizedSource) + end + return chain +end +function CardSystem.prototype.SerializeSourceChain(self, chain) + if not chain or #chain <= 0 then + return "" + end + return table.concat(chain, " -> ") +end +function CardSystem.prototype.SetCurrentSelectionSource(self, sourceRaw, sourceChain) + local source = self:NormalizeSourceToken(sourceRaw) + self.currentSelectionSource = source + self.currentSelectionSourceChain = sourceChain and #sourceChain > 0 and ({unpack(sourceChain)}) or self:BuildSourceChain(source) + self.currentSelectionToken = self.currentSelectionToken + 1 +end +function CardSystem.prototype.BuildPoolEntrySourceChain(self, sourceRaw, sourceChain) + local chainBase = sourceChain and #sourceChain > 0 and sourceChain or self.effectSourceChainContext + return self:BuildSourceChain(sourceRaw, chainBase) +end +function CardSystem.prototype.CreatePoolEntry(self, cardId, weight, sourceRaw, sourceChain) + local normalizedCardId = math.floor(__TS__Number(cardId)) + local normalizedWeight = math.floor(__TS__Number(weight)) + if not __TS__NumberIsFinite(normalizedCardId) or normalizedCardId <= 0 or not __TS__NumberIsFinite(normalizedWeight) or normalizedWeight <= 0 then + return nil + end + local ____self_10, ____poolNextEntrySeq_11 = self, "poolNextEntrySeq" + local ____self_poolNextEntrySeq_12 = ____self_10[____poolNextEntrySeq_11] + ____self_10[____poolNextEntrySeq_11] = ____self_poolNextEntrySeq_12 + 1 + local entryId = "e" .. tostring(____self_poolNextEntrySeq_12) + local chain = self:BuildPoolEntrySourceChain(sourceRaw, sourceChain) + self.poolEntriesById[entryId] = {entryId = entryId, cardId = normalizedCardId, weight = normalizedWeight, sourceChain = chain} + return entryId +end +function CardSystem.prototype.RebuildPoolFromEntries(self) + self.pool:clear() + for entryId in pairs(self.poolEntriesById) do + do + local entry = self.poolEntriesById[entryId] + if not entry or entry.weight <= 0 then + goto __continue102 + end + self.pool:add(entry.cardId, entry.weight) + end + ::__continue102:: + end +end +function CardSystem.prototype.GetPoolEntrySourceChain(self, entryId) + if not entryId then + return nil + end + local entry = self.poolEntriesById[entryId] + if not entry or not entry.sourceChain or #entry.sourceChain <= 0 then + return nil + end + return {unpack(entry.sourceChain)} +end +function CardSystem.prototype.PickPoolEntryIdForCardId(self, cardId) + local candidates = {} + local total = 0 + for entryId in pairs(self.poolEntriesById) do + do + local entry = self.poolEntriesById[entryId] + if not entry or entry.cardId ~= cardId or entry.weight <= 0 then + goto __continue109 + end + candidates[#candidates + 1] = entry + total = total + entry.weight + end + ::__continue109:: + end + if #candidates <= 0 or total <= 0 then + return nil + end + local roll = RandomInt(1, total) + local cursor = 0 + for ____, entry in ipairs(candidates) do + cursor = cursor + entry.weight + if roll <= cursor then + return entry.entryId + end + end + local ____opt_13 = candidates[1] + return ____opt_13 and ____opt_13.entryId +end +function CardSystem.prototype.SelectEntryIdsForCards(self, cardIds) + local remainingByEntryId = {} + for entryId in pairs(self.poolEntriesById) do + local entry = self.poolEntriesById[entryId] + if entry and entry.weight > 0 then + remainingByEntryId[entryId] = entry.weight + end + end + local selected = {} + for ____, cardId in ipairs(cardIds) do + do + if not __TS__NumberIsFinite(cardId) or cardId <= 0 or cardId == 404 then + selected[#selected + 1] = nil + goto __continue120 + end + local candidates = {} + local total = 0 + for entryId in pairs(remainingByEntryId) do + do + local left = remainingByEntryId[entryId] + local entry = self.poolEntriesById[entryId] + if not entry or entry.cardId ~= cardId or left <= 0 then + goto __continue122 + end + candidates[#candidates + 1] = {entryId = entryId, weight = left} + total = total + left + end + ::__continue122:: + end + if #candidates <= 0 or total <= 0 then + selected[#selected + 1] = nil + goto __continue120 + end + local roll = RandomInt(1, total) + local cursor = 0 + local picked = candidates[1].entryId + for ____, candidate in ipairs(candidates) do + cursor = cursor + candidate.weight + if roll <= cursor then + picked = candidate.entryId + break + end + end + selected[#selected + 1] = picked + remainingByEntryId[picked] = math.max(0, (remainingByEntryId[picked] or 0) - 1) + end + ::__continue120:: + end + return selected +end +function CardSystem.prototype.ConsumePoolEntryWeight(self, entryId, amount) + if amount == nil then + amount = 1 + end + if not entryId then + return + end + local entry = self.poolEntriesById[entryId] + if not entry then + return + end + entry.weight = math.max( + 0, + entry.weight - math.max( + 1, + math.floor(amount) + ) + ) + if entry.weight <= 0 then + __TS__Delete(self.poolEntriesById, entryId) + end +end +function CardSystem.prototype.ReinitCardPool(self) + self:InitCardPool() +end +function CardSystem.prototype.GetCardsTaken(self) + return self.cardsTaken +end +function CardSystem.prototype.GetActiveCardCount(self) + return #self.itemsId +end +function CardSystem.prototype.GetActiveCardCountExcludingInherent(self) + local n = 0 + for ____, id in ipairs(self.itemsId) do + do + local cardData = ____exports.CardSystem.cardData[id] + if cardData and cardData.inherent == true then + goto __continue138 + end + n = n + 1 + end + ::__continue138:: + end + return n +end +function CardSystem.prototype.GetActiveCardCopies(self, cardId) + local fromItems = 0 + for ____, id in ipairs(self.itemsId) do + if id == cardId then + fromItems = fromItems + 1 + end + end + local fromModifiers = 0 + local ____opt_15 = self:GetPlayer() + local hero = ____opt_15 and ____opt_15:GetAssignedHero() + if hero and IsValidEntity(hero) and hero:IsRealHero() then + local modName = "modifier_card_" .. tostring(cardId) + do + local i = 0 + while i < hero:GetModifierCount() do + if hero:GetModifierNameByIndex(i) == modName then + fromModifiers = fromModifiers + 1 + end + i = i + 1 + end + end + end + return math.max(fromItems, fromModifiers) +end +function CardSystem.prototype.GetCardLevel(self, cardId) + local key = tostring(cardId) + local ____opt_17 = self.cardPieces[key] + local levelRaw = __TS__Number(____opt_17 and ____opt_17.level or 1) + return math.max( + 1, + math.min( + CARD_UPGRADE_MAX_LEVEL, + math.floor(levelRaw) + ) + ) +end +function CardSystem.prototype.ResetCardPool(self) + self.cardsTaken = 0 + self:InitCardPool() +end +function CardSystem.prototype.InitCardPool(self) + self.pool:clear() + self.poolEntriesById = {} + self.poolNextEntrySeq = 1 + self.items = {} + self.itemsId = {} + self.rerollCost = 0 + self.cardsTaken = 0 + local activeDeckKey = tostring(self.activeDeckIndex) + local activeDeck = self.decks[activeDeckKey] + local normalizedCards = self:normalizeDeckCards(activeDeck) + if #normalizedCards > 0 then + self.decks[activeDeckKey] = { + name = self:resolveDeckName(activeDeck, self.activeDeckIndex), + cards = {unpack(normalizedCards)} + } + self:SyncDecks() + end + do + local i = 0 + while i < #normalizedCards do + local cardId = normalizedCards[i + 1] + local cardData = ____exports.CardSystem.cardData[cardId] + if cardData and not cardData.disabled then + if cardData.inherent then + self:AddCard( + tostring(cardId), + nil, + {skipCurseStack = true} + ) + else + self:CreatePoolEntry(cardId, 1, "deck_initial_pool") + end + end + i = i + 1 + end + end + self:RebuildPoolFromEntries() + self:SyncPool() + self:SyncActiveCards() + self:SyncRerollCost() + Timers:CreateTimer( + 0.5, + function() + self:SyncPool() + return nil + end + ) +end +function CardSystem.prototype.HideCardSelection(self) + self.playerItems = {} + self:SyncPlayerItems() + self:SyncRerollCost() + self.isShowingCardSelection = false + self:SyncSelectionQueue() + self:SyncSelectionQueue() + self.cardSelectionQueue = {} + self:SyncSelectionQueue() +end +function CardSystem.prototype.ScheduleCard68CursedPick(self, excludeOptionIds, attempt) + if attempt == nil then + attempt = 0 + end + local maxAttempts = 48 + local delay = attempt == 0 and 0.12 or 0.05 + Timers:CreateTimer( + delay, + function() + if not IsServer() then + return nil + end + if self.isShowingCardSelection or #self.cardSelectionQueue > 0 then + if attempt < maxAttempts then + self:ScheduleCard68CursedPick(excludeOptionIds, attempt + 1) + end + return nil + end + do + local function ____catch(err) + print("[CardSystem] card_68 cursed pick: " .. tostring(err)) + end + local ____try, ____hasReturned, ____returnValue = pcall(function() + local card68 = require("cards.examples.card_68") + local slots = card68.CARD_68_OPTION_SLOTS or 3 + local poolCards = self:GetPoolCards() + local ____opt_19 = self:GetPlayer() + local hero = ____opt_19 and ____opt_19:GetAssignedHero() + local hasCard69 = hero and IsValidEntity(hero) and hero:IsRealHero() and hero:HasModifier("modifier_card_69") + local ____hasCard69_25 + if hasCard69 then + local ____opt_21 = card68.buildAnyOfferIdsFromPool + ____hasCard69_25 = ____opt_21 and ____opt_21(card68, poolCards, slots, excludeOptionIds) or ({}) + else + local ____opt_23 = card68.buildCursedOfferIdsFromPool + ____hasCard69_25 = ____opt_23 and ____opt_23(card68, poolCards, slots, excludeOptionIds) or ({}) + end + local offerIds = ____hasCard69_25 + if #offerIds <= 0 then + print(((("[CardSystem] card_68: в колоде нет подходящих карт для выбора (игрок " .. tostring(self.playerId)) .. ", card69=") .. (hasCard69 and "yes" or "no")) .. ").") + return true, nil + end + self:ShowCardSelectionExactOptions(offerIds, hasCard69 and "card_68_pool_any_pick" or "card_68_cursed_pick") + end) + if not ____try then + ____hasReturned, ____returnValue = ____catch(____hasReturned) + end + if ____hasReturned then + return ____returnValue + end + end + return nil + end + ) +end +function CardSystem.prototype.ProcessNextCardSelection(self) + if not self.cardLevelsLoaded then + self:StartWaitingForCardLevelsIfNeeded() + return + end + if #self.cardSelectionQueue == 0 then + return + end + local nextSelection = table.remove(self.cardSelectionQueue, 1) + self:SyncSelectionQueue() + if nextSelection.exactOptionIds ~= nil and #nextSelection.exactOptionIds > 0 then + self:ProcessCardSelectionExactIds(nextSelection.exactOptionIds, nextSelection.source, nextSelection.sourceChain, nextSelection.baseCount) + elseif nextSelection.forcedCardId ~= nil then + self:ProcessCardSelectionWithForcedCard( + nextSelection.count, + nextSelection.source, + nextSelection.forcedCardId, + nextSelection.sourceChain, + nextSelection.baseCount + ) + else + self:ProcessCardSelection( + nextSelection.count, + nextSelection.source, + nextSelection.qualityFilter, + nextSelection.sourceChain, + nextSelection.baseCount + ) + end +end +function CardSystem.prototype.GetCardSelectionQueueSize(self) + return #self.cardSelectionQueue +end +function CardSystem.prototype.ClearCardSelectionQueue(self) + self.cardSelectionQueue = {} +end +function CardSystem.prototype.IsShowingCardSelection(self) + return self.isShowingCardSelection +end +function CardSystem.prototype.SetRerollQualityFilter(self, qualityFilter) + self.rerollQualityFilter = qualityFilter + if qualityFilter then + else + end +end +function CardSystem.prototype.GetRerollQualityFilter(self) + return self.rerollQualityFilter +end +function CardSystem.prototype.AddGuaranteedQualityRerolls(self, quality, rerollsCount, cardsPerReroll, fallbackBehavior) + if fallbackBehavior == nil then + fallbackBehavior = "skip" + end + print("[CardSystem.AddGuaranteedQualityRerolls] ===== ДОБАВЛЕНИЕ В СИСТЕМУ =====") + print("[CardSystem.AddGuaranteedQualityRerolls] Игрок: " .. tostring(self.playerId)) + print("[CardSystem.AddGuaranteedQualityRerolls] Качество: " .. tostring(quality)) + print("[CardSystem.AddGuaranteedQualityRerolls] Количество реролов: " .. tostring(rerollsCount)) + print("[CardSystem.AddGuaranteedQualityRerolls] Карт за рерол: " .. tostring(cardsPerReroll)) + print("[CardSystem.AddGuaranteedQualityRerolls] Поведение при отсутствии: " .. fallbackBehavior) + print("[CardSystem.AddGuaranteedQualityRerolls] Текущее количество гарантированных реролов: " .. tostring(#self.guaranteedQualityRerolls)) + local existingIndex = __TS__ArrayFindIndex( + self.guaranteedQualityRerolls, + function(____, item) return item.quality == quality end + ) + if existingIndex ~= -1 then + print("[CardSystem.AddGuaranteedQualityRerolls] ✅ Найдены существующие реролы для качества " .. tostring(quality)) + print("[CardSystem.AddGuaranteedQualityRerolls] Было реролов: " .. tostring(self.guaranteedQualityRerolls[existingIndex + 1].rerollsLeft)) + local ____self_guaranteedQualityRerolls_index_26, ____rerollsLeft_27 = self.guaranteedQualityRerolls[existingIndex + 1], "rerollsLeft" + ____self_guaranteedQualityRerolls_index_26[____rerollsLeft_27] = ____self_guaranteedQualityRerolls_index_26[____rerollsLeft_27] + rerollsCount + print("[CardSystem.AddGuaranteedQualityRerolls] Стало реролов: " .. tostring(self.guaranteedQualityRerolls[existingIndex + 1].rerollsLeft)) + else + print("[CardSystem.AddGuaranteedQualityRerolls] ✅ Создаем новые реролы для качества " .. tostring(quality)) + local ____self_guaranteedQualityRerolls_28 = self.guaranteedQualityRerolls + ____self_guaranteedQualityRerolls_28[#____self_guaranteedQualityRerolls_28 + 1] = {quality = quality, rerollsLeft = rerollsCount, cardsPerReroll = cardsPerReroll, fallbackBehavior = fallbackBehavior} + print("[CardSystem.AddGuaranteedQualityRerolls] Добавлен новый элемент в очередь") + end + print("[CardSystem.AddGuaranteedQualityRerolls] Итоговое количество гарантированных реролов: " .. tostring(#self.guaranteedQualityRerolls)) + print("[CardSystem.AddGuaranteedQualityRerolls] ==========================================") +end +function CardSystem.prototype.GetGuaranteedQualityRerolls(self, quality) + local item = __TS__ArrayFind( + self.guaranteedQualityRerolls, + function(____, item) return item.quality == quality end + ) + return item and item.rerollsLeft or 0 +end +function CardSystem.prototype.GetGuaranteedCardsPerReroll(self, quality) + local item = __TS__ArrayFind( + self.guaranteedQualityRerolls, + function(____, item) return item.quality == quality end + ) + return item and item.cardsPerReroll or 0 +end +function CardSystem.prototype.GetAllGuaranteedQualityRerolls(self) + return {unpack(self.guaranteedQualityRerolls)} +end +function CardSystem.prototype.ClearGuaranteedQualityRerolls(self) + self.guaranteedQualityRerolls = {} +end +function CardSystem.prototype.RefreshCard49DailyFreeReroll(self) + if self:HasActiveCard(49) then + self.card49FreeRerollCharges = math.min(____exports.CardSystem.CARD_49_FREE_STACK_CAP, self.card49FreeRerollCharges + 1) + self:SyncRerollCost() + end +end +function CardSystem.prototype.TryGrantCard6Level2FreeRerolls(self) + if self.card6FreeRerollLevelGranted then + return + end + if self:GetCardLevel(____exports.CardSystem.CARD_6_ID) < 2 then + return + end + local grant = math.max( + 0, + math.floor(self:getCardValueByLevel(____exports.CardSystem.CARD_6_ID, "free_reroll_charges", 2)) + ) + if grant <= 0 then + return + end + self.card6FreeRerollLevelGranted = true + self.card6FreeRerollCharges = math.min(____exports.CardSystem.CARD_6_FREE_STACK_CAP, self.card6FreeRerollCharges + grant) + self:SyncRerollCost() +end +function CardSystem.prototype.GetCard6MinSelectionCount(self) + if not self:HasActiveCard(____exports.CardSystem.CARD_6_ID) then + return 0 + end + return math.max( + 0, + math.floor(self:getCardValueByLevel(____exports.CardSystem.CARD_6_ID, "min_card_choice", 0)) + ) +end +function CardSystem.prototype.getCardValueByLevel(self, cardId, key, fallback) + local ____opt_29 = self:GetPlayer() + local hero = ____opt_29 and ____opt_29:GetAssignedHero() + local resolver = require("cards.card_value_resolver") + local ____opt_31 = resolver.getCardValueByLevel + return ____opt_31 and ____opt_31( + resolver, + cardId, + hero, + key, + fallback + ) or fallback +end +function CardSystem.prototype.GetCard6FreeRerollChargesForUse(self) + if not self:HasActiveCard(____exports.CardSystem.CARD_6_ID) or self:GetCardLevel(____exports.CardSystem.CARD_6_ID) < 2 then + return 0 + end + return math.max( + 0, + math.floor(self.card6FreeRerollCharges) + ) +end +function CardSystem.prototype.GetCard88FreeRerollChargesForUse(self) + if not self:HasActiveCard(____exports.CardSystem.CARD_88_ID) then + return 0 + end + return math.max( + 0, + math.floor(self.card88FreeRerollCharges) + ) +end +function CardSystem.prototype.TryGrantCard88FreeRerollsFromSlag(self, slagCount) + if slagCount == nil then + slagCount = 1 + end + if not self:HasActiveCard(____exports.CardSystem.CARD_88_ID) then + return + end + local safeSlagCount = math.max( + 0, + math.floor(slagCount) + ) + if safeSlagCount <= 0 then + return + end + local grantPerSlag = math.max( + 0, + math.floor(self:getCardValueByLevel(____exports.CardSystem.CARD_88_ID, "free_rerolls_on_slag", 2)) + ) + local grant = grantPerSlag * safeSlagCount + if grant <= 0 then + return + end + self.card88FreeRerollCharges = math.min(____exports.CardSystem.CARD_88_FREE_STACK_CAP, self.card88FreeRerollCharges + grant) + self:SyncRerollCost() +end +function CardSystem.prototype.ResolveSelectionQualityFilter(self, source, qualityFilter) + if source == "modifier_card_6_bonus_selection" and self:HasActiveCard(____exports.CardSystem.CARD_6_ID) and self:GetCardLevel(____exports.CardSystem.CARD_6_ID) >= 3 then + return {____exports.CardQuality.LEGENDARY, ____exports.CardQuality.MYTHIC} + end + return qualityFilter +end +function CardSystem.prototype.ShowCardSelection(self, count, source, qualityFilter) + if source == nil then + source = "unknown" + end + local resolvedQualityFilter = self:ResolveSelectionQualityFilter(source, qualityFilter) + local baseCount = math.max( + 1, + math.floor(count) + ) + local resolvedCount = self:ResolveCardSelectionCount(baseCount) + local sourceChain = self:BuildSourceChain(source, self.effectSourceChainContext) + print("[CardSystem.ShowCardSelection] ===== ПОКАЗ ВЫБОРА КАРТ =====") + print("[CardSystem.ShowCardSelection] Игрок: " .. tostring(self.playerId)) + print("[CardSystem.ShowCardSelection] Запрошено карт: " .. tostring(count)) + print("[CardSystem.ShowCardSelection] Итоговое количество карт: " .. tostring(resolvedCount)) + print("[CardSystem.ShowCardSelection] Источник: " .. source) + print("[CardSystem.ShowCardSelection] Фильтр качества: " .. (resolvedQualityFilter and table.concat(resolvedQualityFilter, ", ") or "нет")) + print("[CardSystem.ShowCardSelection] Уже показываем выбор: " .. tostring(self.isShowingCardSelection)) + print("[CardSystem.ShowCardSelection] Размер пула карт: " .. tostring(self.pool.len)) + if not self.cardLevelsLoaded then + print("[CardSystem.ShowCardSelection] ⏳ Уровни карт ещё не загружены, откладываем показ в очередь") + self:AddToCardSelectionQueue( + resolvedCount, + baseCount, + source, + resolvedQualityFilter, + nil, + nil, + sourceChain + ) + self:StartWaitingForCardLevelsIfNeeded() + return + end + if resolvedQualityFilter then + end + if self.isShowingCardSelection then + print(("[CardSystem.ShowCardSelection] ⚠️ Добавляем в очередь выбор из " .. tostring(resolvedCount)) .. " карт") + self:AddToCardSelectionQueue( + resolvedCount, + baseCount, + source, + resolvedQualityFilter, + nil, + nil, + sourceChain + ) + return + end + print(("[CardSystem.ShowCardSelection] ✅ Показываем выбор из " .. tostring(resolvedCount)) .. " карт") + self:ProcessCardSelection( + resolvedCount, + source, + resolvedQualityFilter, + sourceChain, + baseCount + ) + print("[CardSystem.ShowCardSelection] ======================================") +end +function CardSystem.prototype.ShowCardSelectionWithExtraChoices(self, baseCount, extraCount, source, qualityFilter) + if source == nil then + source = "unknown" + end + local normalizedBase = math.max( + 1, + math.floor(baseCount) + ) + local normalizedExtra = math.max( + 0, + math.floor(extraCount) + ) + self:ShowCardSelection(normalizedBase + normalizedExtra, source, qualityFilter) +end +function CardSystem.prototype.GetPassiveCardSelectionExtraChoices(self) + local extra = 0 + if self:HasActiveCard(6) then + extra = extra + 1 + end + return extra +end +function CardSystem.prototype.BuildOptionSourceChainWithCard6Bonus(self, baseChain, optionIndex, baseCount, source) + local chain = baseChain and ({unpack(baseChain)}) or ({}) + local normalizedBase = math.max( + 1, + math.floor(baseCount) + ) + local isCard6TriggeredSelection = source == "modifier_card_6_bonus_selection" + local shouldAppendCard6 = self:HasActiveCard(6) and (isCard6TriggeredSelection or optionIndex >= normalizedBase) + if shouldAppendCard6 then + return self:BuildSourceChain("modifier_card_6_bonus_selection", chain) + end + return #chain > 0 and chain or nil +end +function CardSystem.prototype.ResolveCardSelectionCount(self, count) + local baseCount = math.max( + 1, + math.floor(count) + ) + local desiredCount = baseCount + self:GetPassiveCardSelectionExtraChoices() + local minByCard6 = self:GetCard6MinSelectionCount() + if minByCard6 > 0 then + desiredCount = math.max(desiredCount, minByCard6) + end + local maxByPool = self:GetMaxSelectableOptionsByPool() + if maxByPool > 0 then + return math.max( + 1, + math.min(desiredCount, maxByPool) + ) + end + return desiredCount +end +function CardSystem.prototype.GetMaxSelectableOptionsByPool(self) + if not self.pool then + return 0 + end + local total = __TS__Number(self.pool.totalProportion or 0) + if not __TS__NumberIsFinite(total) or total <= 0 then + return 0 + end + return math.max( + 1, + math.floor(total) + ) +end +function CardSystem.prototype.SkipsDawnCardSelection(self) + return self:HasActiveCard(58) +end +function CardSystem.prototype.HasActiveCard(self, cardId) + if __TS__ArrayIncludes(self.itemsId, cardId) then + return true + end + local player = self:GetPlayer() + local hero = player and player:GetAssignedHero() + if not hero then + return false + end + return hero:HasModifier("modifier_card_" .. tostring(cardId)) +end +function CardSystem.prototype.DuplicateCurrentPool(self) + local snapshot = {} + if self.pool and self.pool.mappingTable ~= nil then + for key in pairs(self.pool.mappingTable) do + local item = self.pool.mappingTable[key] + local cardId = tonumber(key) + local weight = item and item.proportion or 0 + if cardId and weight > 0 and not isMirrorProtectedCardId(nil, cardId) then + snapshot[#snapshot + 1] = {cardId = cardId, weight = weight} + end + end + end + for ____, entry in ipairs(snapshot) do + self:CreatePoolEntry(entry.cardId, entry.weight, "duplicate_current_pool") + end + self:RebuildPoolFromEntries() + self:SyncPool() +end +function CardSystem.prototype.ScheduleRepeatNextSelectedCardOnce(self) + self.repeatNextSelectedCardOnce = true +end +function CardSystem.prototype.HasRepeatNextSelectedCardScheduled(self) + return self.repeatNextSelectedCardOnce +end +function CardSystem.prototype.DuplicateUnselectedCurrentOptions(self, selectedCardId, optionsSnapshot) + local options = optionsSnapshot or self.playerItems + if not options then + return + end + local selected = math.floor(__TS__Number(selectedCardId)) + local uniqueOptionIds = __TS__New(Set) + for key in pairs(options) do + do + local ____math_floor_39 = math.floor + local ____opt_37 = options[key] + local optionId = ____math_floor_39(__TS__Number(____opt_37 and ____opt_37.index)) + if not __TS__NumberIsFinite(optionId) or optionId <= 0 then + goto __continue238 + end + if isEmptySelectionCardId(nil, optionId) or optionId == selected or isMirrorProtectedCardId(nil, optionId) then + goto __continue238 + end + uniqueOptionIds:add(optionId) + end + ::__continue238:: + end + for ____, optionId in __TS__Iterator(uniqueOptionIds) do + local currentWeight = self.pool:getWeightPrize(optionId) + if currentWeight > 0 then + self:CreatePoolEntry(optionId, 1, "duplicate_unselected_current_options") + end + end + self:RebuildPoolFromEntries() + self:SyncPool() +end +function CardSystem.prototype.AddToCardSelectionQueue(self, count, baseCount, source, qualityFilter, forcedCardId, exactOptionIds, sourceChain) + local priority = #self.cardSelectionQueue + local queueItem = { + count = exactOptionIds ~= nil and #exactOptionIds > 0 and #exactOptionIds or count, + baseCount = math.max( + 1, + math.floor(baseCount) + ), + priority = priority, + source = source, + sourceChain = sourceChain, + qualityFilter = qualityFilter, + forcedCardId = forcedCardId, + exactOptionIds = exactOptionIds + } + local ____self_cardSelectionQueue_40 = self.cardSelectionQueue + ____self_cardSelectionQueue_40[#____self_cardSelectionQueue_40 + 1] = queueItem + self:SyncSelectionQueue() + if qualityFilter then + end +end +function CardSystem.prototype.StartWaitingForCardLevelsIfNeeded(self) + if self.cardLevelsWaitTimerStarted then + return + end + self.cardLevelsWaitTimerStarted = true + Timers:CreateTimer( + 0.25, + function() + if not self.cardLevelsLoaded then + return 0.25 + end + self.cardLevelsWaitTimerStarted = false + self:ProcessNextCardSelection() + return nil + end + ) +end +function CardSystem.prototype.ShowCardSelectionWithForcedCard(self, forcedCardId, count, source) + if count == nil then + count = 3 + end + if source == nil then + source = "forced_card" + end + local sourceChain = self:BuildSourceChain(source, self.effectSourceChainContext) + local data = ____exports.CardSystem.cardData[forcedCardId] + if data == nil then + print("[CardSystem.ShowCardSelectionWithForcedCard] неизвестный id " .. tostring(forcedCardId)) + return + end + if data.disabled == true and forcedCardId ~= 404 then + print(("[CardSystem.ShowCardSelectionWithForcedCard] карта " .. tostring(forcedCardId)) .. " отключена") + return + end + local resolvedCount = self:ResolveCardSelectionCount(count) + if not self.cardLevelsLoaded then + self:AddToCardSelectionQueue( + resolvedCount, + math.max( + 1, + math.floor(count) + ), + source, + nil, + forcedCardId, + nil, + sourceChain + ) + self:StartWaitingForCardLevelsIfNeeded() + return + end + if self.isShowingCardSelection then + self:AddToCardSelectionQueue( + resolvedCount, + math.max( + 1, + math.floor(count) + ), + source, + nil, + forcedCardId, + nil, + sourceChain + ) + return + end + self:ProcessCardSelectionWithForcedCard( + resolvedCount, + source, + forcedCardId, + sourceChain, + math.max( + 1, + math.floor(count) + ) + ) +end +function CardSystem.prototype.BuildRandomNonInherentCatalogIds(self, count, excludeIds, qualityFilter) + local slots = math.max( + 1, + math.floor(count) + ) + local exclude = __TS__New(Set, excludeIds) + local candidates = {} + for ____, key in ipairs(__TS__ObjectKeys(____exports.CardSystem.cardData)) do + do + local id = __TS__ParseInt(key, 10) + if __TS__NumberIsNaN(id) or id == 404 then + goto __continue257 + end + if exclude:has(id) then + goto __continue257 + end + local def = ____exports.CardSystem.cardData[id] + if not def or def.disabled == true then + goto __continue257 + end + if qualityFilter ~= nil and def.quality ~= qualityFilter then + goto __continue257 + end + if def.inherent == true then + goto __continue257 + end + candidates[#candidates + 1] = id + end + ::__continue257:: + end + local shuffled = {unpack(candidates)} + do + local i = #shuffled - 1 + while i > 0 do + local j = math.floor(math.random() * (i + 1)) + local tmp = shuffled[i + 1] + shuffled[i + 1] = shuffled[j + 1] + shuffled[j + 1] = tmp + i = i - 1 + end + end + local result = {} + local uniqueTake = math.min(slots, #shuffled) + do + local i = 0 + while i < uniqueTake do + result[#result + 1] = shuffled[i + 1] + i = i + 1 + end + end + while #result < slots do + if #candidates == 0 then + result[#result + 1] = 404 + else + local r = candidates[math.floor(math.random() * #candidates) + 1] + result[#result + 1] = r + end + end + return result +end +function CardSystem.prototype.BuildRandomLegendaryNonInherentIds(self, count, excludeIds) + return self:BuildRandomNonInherentCatalogIds(count, excludeIds, ____exports.CardQuality.LEGENDARY) +end +function CardSystem.prototype.ProcessCardSelectionExactIds(self, ids, source, sourceChain, baseCount) + if baseCount == nil then + baseCount = #ids + end + self.isShowingCardSelection = true + self:SetCurrentSelectionSource(source, sourceChain) + self:SyncSelectionQueue() + self:SyncPool() + local cardSelection = {} + local optionEntryIds = self:SelectEntryIdsForCards(ids) + do + local i = 0 + while i < #ids do + local rawId = ids[i + 1] + local def = ____exports.CardSystem.cardData[rawId] + local safeId = def ~= nil and def.disabled ~= true and rawId or 404 + local ____temp_41 + if safeId ~= 404 then + ____temp_41 = optionEntryIds[i + 1] + else + ____temp_41 = nil + end + local entryId = ____temp_41 + local optionSourceChain = self:BuildOptionSourceChainWithCard6Bonus( + self:GetPoolEntrySourceChain(entryId), + i, + baseCount, + source + ) + cardSelection[tostring(i + 1)] = { + index = safeId, + entry_id = entryId, + source_chain = optionSourceChain and self:SerializeSourceChain(optionSourceChain) or nil, + source_chain_items = optionSourceChain and ({unpack(optionSourceChain)}) or nil, + selection_token = self.currentSelectionToken + } + i = i + 1 + end + end + self.playerItems = cardSelection + self:SyncPlayerItems() + self:SyncSelectionSource() + self:SyncRerollCost() +end +function CardSystem.prototype.ShowLegendaryNonInherentCatalogSelection(self, requestedSlots, source, excludeOptionIds) + if requestedSlots == nil then + requestedSlots = 3 + end + if source == nil then + source = "legendary_catalog_pick" + end + if excludeOptionIds == nil then + excludeOptionIds = {} + end + local resolved = self:ResolveCardSelectionCount(requestedSlots) + local ids = self:BuildRandomLegendaryNonInherentIds(resolved, excludeOptionIds) + if not self.cardLevelsLoaded then + self:AddToCardSelectionQueue( + resolved, + math.max( + 1, + math.floor(requestedSlots) + ), + source, + nil, + nil, + ids, + self:BuildSourceChain(source, self.effectSourceChainContext) + ) + self:StartWaitingForCardLevelsIfNeeded() + return + end + if self.isShowingCardSelection then + self:AddToCardSelectionQueue( + resolved, + math.max( + 1, + math.floor(requestedSlots) + ), + source, + nil, + nil, + ids, + self:BuildSourceChain(source, self.effectSourceChainContext) + ) + return + end + self:ProcessCardSelectionExactIds( + ids, + source, + self:BuildSourceChain(source, self.effectSourceChainContext), + math.max( + 1, + math.floor(requestedSlots) + ) + ) +end +function CardSystem.prototype.ShowAnyNonInherentCatalogSelection(self, requestedSlots, source, excludeOptionIds) + if requestedSlots == nil then + requestedSlots = 3 + end + if source == nil then + source = "any_catalog_pick" + end + if excludeOptionIds == nil then + excludeOptionIds = {} + end + local resolved = self:ResolveCardSelectionCount(requestedSlots) + local ids = self:BuildRandomNonInherentCatalogIds(resolved, excludeOptionIds) + if not self.cardLevelsLoaded then + self:AddToCardSelectionQueue( + resolved, + math.max( + 1, + math.floor(requestedSlots) + ), + source, + nil, + nil, + ids, + self:BuildSourceChain(source, self.effectSourceChainContext) + ) + self:StartWaitingForCardLevelsIfNeeded() + return + end + if self.isShowingCardSelection then + self:AddToCardSelectionQueue( + resolved, + math.max( + 1, + math.floor(requestedSlots) + ), + source, + nil, + nil, + ids, + self:BuildSourceChain(source, self.effectSourceChainContext) + ) + return + end + self:ProcessCardSelectionExactIds( + ids, + source, + self:BuildSourceChain(source, self.effectSourceChainContext), + math.max( + 1, + math.floor(requestedSlots) + ) + ) +end +function CardSystem.prototype.ShowCardSelectionExactOptions(self, optionIds, source) + if source == nil then + source = "exact_options" + end + local normalized = {} + for ____, raw in ipairs(optionIds) do + do + local id = math.floor(__TS__Number(raw)) + if not __TS__NumberIsFinite(id) or id <= 0 then + goto __continue279 + end + if id == 404 then + normalized[#normalized + 1] = 404 + goto __continue279 + end + local def = ____exports.CardSystem.cardData[id] + if def == nil or def.disabled == true then + goto __continue279 + end + normalized[#normalized + 1] = id + end + ::__continue279:: + end + if #normalized <= 0 then + return + end + if not self.cardLevelsLoaded then + self:AddToCardSelectionQueue( + #normalized, + #normalized, + source, + nil, + nil, + normalized, + self:BuildSourceChain(source, self.effectSourceChainContext) + ) + self:StartWaitingForCardLevelsIfNeeded() + return + end + if self.isShowingCardSelection then + self:AddToCardSelectionQueue( + #normalized, + #normalized, + source, + nil, + nil, + normalized, + self:BuildSourceChain(source, self.effectSourceChainContext) + ) + return + end + self:ProcessCardSelectionExactIds( + normalized, + source, + self:BuildSourceChain(source, self.effectSourceChainContext), + #normalized + ) +end +function CardSystem.prototype.BuildForcedCardOptionIds(self, forcedCardId, totalSlots) + local slots = math.max( + 1, + math.floor(totalSlots) + ) + local fromPool = __TS__ArrayFilter( + self:GetPoolCards(), + function(____, id) return id ~= forcedCardId end + ) + local result = {forcedCardId} + local needMore = slots - 1 + if needMore > 0 then + if #fromPool == 0 then + do + local i = 0 + while i < needMore do + result[#result + 1] = 404 + i = i + 1 + end + end + else + local extra = self:DrawRandomFromSubsetRespectingWeights(fromPool, needMore) + for ____, id in ipairs(extra) do + result[#result + 1] = id + end + end + end + do + local i = #result - 1 + while i > 0 do + local j = math.floor(math.random() * (i + 1)) + local tmp = result[i + 1] + result[i + 1] = result[j + 1] + result[j + 1] = tmp + i = i - 1 + end + end + return result +end +function CardSystem.prototype.ProcessCardSelectionWithForcedCard(self, count, source, forcedCardId, sourceChain, baseCount) + if baseCount == nil then + baseCount = count + end + self.isShowingCardSelection = true + self:SetCurrentSelectionSource(source, sourceChain) + self:SyncSelectionQueue() + self:SyncPool() + local ids = self:BuildForcedCardOptionIds(forcedCardId, count) + local cardSelection = {} + local optionEntryIds = self:SelectEntryIdsForCards(ids) + do + local i = 0 + while i < #ids do + local safeId = ids[i + 1] + local ____temp_42 + if safeId ~= 404 then + ____temp_42 = optionEntryIds[i + 1] + else + ____temp_42 = nil + end + local entryId = ____temp_42 + local optionSourceChain = self:BuildOptionSourceChainWithCard6Bonus( + self:GetPoolEntrySourceChain(entryId), + i, + baseCount, + source + ) + cardSelection[tostring(i + 1)] = { + index = safeId, + entry_id = entryId, + source_chain = optionSourceChain and self:SerializeSourceChain(optionSourceChain) or nil, + source_chain_items = optionSourceChain and ({unpack(optionSourceChain)}) or nil, + selection_token = self.currentSelectionToken + } + i = i + 1 + end + end + self.playerItems = cardSelection + self:SyncPlayerItems() + self:SyncSelectionSource() + self:SyncRerollCost() +end +function CardSystem.prototype.ProcessCardSelection(self, count, source, qualityFilter, sourceChain, baseCount) + if baseCount == nil then + baseCount = count + end + print("[CardSystem.ProcessCardSelection] ===== ОБРАБОТКА ВЫБОРА КАРТ =====") + print("[CardSystem.ProcessCardSelection] Игрок: " .. tostring(self.playerId)) + print("[CardSystem.ProcessCardSelection] Запрошено карт: " .. tostring(count)) + print("[CardSystem.ProcessCardSelection] Источник: " .. source) + print("[CardSystem.ProcessCardSelection] Фильтр качества: " .. (qualityFilter and table.concat(qualityFilter, ", ") or "нет")) + if qualityFilter then + end + self.isShowingCardSelection = true + self:SetCurrentSelectionSource(source, sourceChain) + print("[CardSystem.ProcessCardSelection] Флаг isShowingCardSelection установлен в true") + self:SyncSelectionQueue() + self:SyncPool() + print("[CardSystem.ProcessCardSelection] Размер пула после синхронизации: " .. tostring(self.pool.len)) + if self.pool.len == 0 then + print(("[CardSystem.ProcessCardSelection] ⚠️ Пул пустой, показываем " .. tostring(count)) .. " карт 404") + local cardSelection = {} + do + local i = 0 + while i < count do + cardSelection[tostring(i + 1)] = {index = 404} + i = i + 1 + end + end + self.playerItems = cardSelection + self:SyncPlayerItems() + self:SyncSelectionSource() + self:SyncRerollCost() + self.isShowingCardSelection = false + self:SyncSelectionQueue() + print("[CardSystem.ProcessCardSelection] ==========================================") + return + end + print("[CardSystem.ProcessCardSelection] Получаем карты с фильтром качества...") + local isReroll = source == "reroll" + local ____isReroll_43 + if isReroll then + ____isReroll_43 = nil + else + ____isReroll_43 = qualityFilter + end + local effectiveQualityFilter = ____isReroll_43 + local cards = self:GetCardsWithQualityFilter(count, effectiveQualityFilter, isReroll) + print(((("[CardSystem.ProcessCardSelection] Получено карт: " .. tostring(#cards)) .. " (запрошено: ") .. tostring(count)) .. ")") + local cardSelection = {} + local optionEntryIds = self:SelectEntryIdsForCards(cards) + do + local i = 0 + while i < #cards do + local cardId = cards[i + 1] + local ____temp_44 + if cardId ~= 404 then + ____temp_44 = optionEntryIds[i + 1] + else + ____temp_44 = nil + end + local entryId = ____temp_44 + local optionSourceChain = self:BuildOptionSourceChainWithCard6Bonus( + self:GetPoolEntrySourceChain(entryId), + i, + baseCount, + source + ) + cardSelection[tostring(i + 1)] = { + index = cardId, + entry_id = entryId, + source_chain = optionSourceChain and self:SerializeSourceChain(optionSourceChain) or nil, + source_chain_items = optionSourceChain and ({unpack(optionSourceChain)}) or nil, + selection_token = self.currentSelectionToken + } + i = i + 1 + end + end + self.playerItems = cardSelection + self:SyncPlayerItems() + self:SyncSelectionSource() + if source ~= "reroll" then + self:SetRerollQualityFilter(qualityFilter) + end + self:SyncRerollCost() +end +function CardSystem.prototype.DrawRandomCardIdsRespectingPoolWeights(self, count, excludedIds, strictExcludeIds) + if strictExcludeIds == nil then + strictExcludeIds = false + end + if count <= 0 then + return {} + end + if self.pool.len == 0 then + local out = {} + do + local i = 0 + while i < count do + out[#out + 1] = 404 + i = i + 1 + end + end + return out + end + local raw = self:DrawBiasedCardIdsFromPool(count, nil, excludedIds, strictExcludeIds) + while #raw < count do + raw[#raw + 1] = 404 + end + return raw +end +function CardSystem.prototype.DrawRandomFromSubsetRespectingWeights(self, allowed, count, excludedIds, strictExcludeIds) + if strictExcludeIds == nil then + strictExcludeIds = false + end + if count <= 0 then + return {} + end + local raw = self:DrawBiasedCardIdsFromPool(count, allowed, excludedIds, strictExcludeIds) + if #raw <= 0 then + local out = {} + do + local i = 0 + while i < count do + out[#out + 1] = 404 + i = i + 1 + end + end + return out + end + while #raw < count do + raw[#raw + 1] = 404 + end + return raw +end +function CardSystem.prototype.BuildPoolRemainingWeights(self, allowed, excludedIds) + local allowedSet = allowed and #allowed > 0 and __TS__New(Set, allowed) or nil + local remaining = {} + if self.pool and self.pool.mappingTable ~= nil then + for key in pairs(self.pool.mappingTable) do + do + local cardId = __TS__ParseInt(key) + if not __TS__NumberIsFinite(cardId) or cardId <= 0 then + goto __continue316 + end + if allowedSet and not allowedSet:has(cardId) then + goto __continue316 + end + local weight = self.pool:getWeightPrize(cardId) + if weight > 0 then + remaining[tostring(cardId)] = math.floor(weight) + end + end + ::__continue316:: + end + end + return remaining +end +function CardSystem.prototype.DrawCardIdsFromRemaining(self, count, remaining, allowed, excludedIds, strictExcludeIds) + if strictExcludeIds == nil then + strictExcludeIds = false + end + if count <= 0 then + return {} + end + local allowedSet = allowed and #allowed > 0 and __TS__New(Set, allowed) or nil + local batchExcluded = {} + if excludedIds then + for ____, raw in ipairs(excludedIds) do + local id = math.floor(__TS__Number(raw)) + if __TS__NumberIsFinite(id) and id > 0 then + batchExcluded[id] = true + end + end + end + local result = {} + do + local pick = 0 + while pick < count do + local totalWeight = 0 + local entries = {} + local hasNonExcludedCandidates = false + for ____, ____value in ipairs(__TS__ObjectEntries(remaining)) do + local cardIdRaw = ____value[1] + local leftRaw = ____value[2] + do + local cardId = __TS__Number(cardIdRaw) + local left = __TS__Number(leftRaw) + if not __TS__NumberIsFinite(cardId) or not __TS__NumberIsFinite(left) or left <= 0 then + goto __continue328 + end + if allowedSet and not allowedSet:has(cardId) then + goto __continue328 + end + if not batchExcluded[cardId] then + hasNonExcludedCandidates = true + end + end + ::__continue328:: + end + for ____, ____value in ipairs(__TS__ObjectEntries(remaining)) do + local cardIdRaw = ____value[1] + local leftRaw = ____value[2] + do + local cardId = __TS__Number(cardIdRaw) + local left = __TS__Number(leftRaw) + if not __TS__NumberIsFinite(cardId) or not __TS__NumberIsFinite(left) or left <= 0 then + goto __continue333 + end + if allowedSet and not allowedSet:has(cardId) then + goto __continue333 + end + if batchExcluded[cardId] and (strictExcludeIds or hasNonExcludedCandidates) then + goto __continue333 + end + local pickWeight = math.max( + 1, + math.floor(left) + ) + entries[#entries + 1] = {cardId = cardId, pickWeight = pickWeight} + totalWeight = totalWeight + pickWeight + end + ::__continue333:: + end + if totalWeight <= 0 or #entries <= 0 then + break + end + local roll = RandomInt(1, totalWeight) + local cursor = 0 + local selectedCardId = entries[1].cardId + for ____, entry in ipairs(entries) do + cursor = cursor + entry.pickWeight + if roll <= cursor then + selectedCardId = entry.cardId + break + end + end + result[#result + 1] = selectedCardId + local key = tostring(selectedCardId) + remaining[key] = math.max(0, (remaining[key] or 0) - 1) + pick = pick + 1 + end + end + return result +end +function CardSystem.prototype.DrawBiasedCardIdsFromPool(self, count, allowed, excludedIds, strictExcludeIds) + if strictExcludeIds == nil then + strictExcludeIds = false + end + if count <= 0 then + return {} + end + local remaining = self:BuildPoolRemainingWeights(allowed, excludedIds) + return self:DrawCardIdsFromRemaining( + count, + remaining, + allowed, + excludedIds, + strictExcludeIds + ) +end +function CardSystem.prototype.GetCardsWithQualityFilter(self, count, qualityFilter, isReroll, excludedIds, strictExcludeIds) + if isReroll == nil then + isReroll = false + end + if strictExcludeIds == nil then + strictExcludeIds = false + end + print("[CardSystem.GetCardsWithQualityFilter] ===== ПОЛУЧЕНИЕ КАРТ С ФИЛЬТРОМ =====") + print("[CardSystem.GetCardsWithQualityFilter] Игрок: " .. tostring(self.playerId)) + print("[CardSystem.GetCardsWithQualityFilter] Запрошено карт: " .. tostring(count)) + print("[CardSystem.GetCardsWithQualityFilter] Фильтр качества: " .. (qualityFilter and table.concat(qualityFilter, ", ") or "нет")) + print("[CardSystem.GetCardsWithQualityFilter] Это рерол: " .. tostring(isReroll)) + print("[CardSystem.GetCardsWithQualityFilter] Гарантированных реролов: " .. tostring(#self.guaranteedQualityRerolls)) + if isReroll and #self.guaranteedQualityRerolls > 0 then + print("[CardSystem.GetCardsWithQualityFilter] ✅ Используем гарантированные карты качества") + local result = self:GetRerollCardsWithGuaranteedQuality(count, qualityFilter, excludedIds, strictExcludeIds) + print(("[CardSystem.GetCardsWithQualityFilter] Возвращаем " .. tostring(#result)) .. " карт из гарантированных") + return result + end + if not qualityFilter or #qualityFilter == 0 then + print("[CardSystem.GetCardsWithQualityFilter] ✅ Случайный выбор с учётом весов в пуле (randomCard)") + local result = self:DrawRandomCardIdsRespectingPoolWeights(count, excludedIds, strictExcludeIds) + print(("[CardSystem.GetCardsWithQualityFilter] Возвращаем " .. tostring(#result)) .. " карт") + print(("[CardSystem.GetCardsWithQualityFilter] ID карт: [" .. table.concat(result, ", ")) .. "]") + return result + end + local filteredCards = self:GetPoolCardIdsMatchingQualities(qualityFilter) + if #filteredCards == 0 then + local fallbackQuality = self:GetNextAvailableQuality(self:GetHighestQualityInFilter(qualityFilter)) + if fallbackQuality ~= nil then + print((("[CardSystem.GetCardsWithQualityFilter] Нет карт по фильтру [" .. table.concat(qualityFilter, ", ")) .. "] — даунгрейд до ") .. tostring(fallbackQuality)) + return self:GetCardsWithQualityFilter( + count, + {fallbackQuality}, + isReroll, + excludedIds, + strictExcludeIds + ) + end + print("[CardSystem.GetCardsWithQualityFilter] Нет карт по фильтру и даунгрейда — общий пул") + return self:DrawRandomCardIdsRespectingPoolWeights(count, excludedIds, strictExcludeIds) + end + local remaining = self:BuildPoolRemainingWeights(nil, excludedIds) + local result = self:DrawCardIdsFromRemaining( + count, + remaining, + filteredCards, + excludedIds, + strictExcludeIds + ) + while #result < count do + result[#result + 1] = 404 + end + result = self:FillSelectionGapsWithLowerQuality(result, qualityFilter, excludedIds, remaining) + print(("[CardSystem.GetCardsWithQualityFilter] По фильтру качества: [" .. table.concat(result, ", ")) .. "]") + return result +end +function CardSystem.prototype.GetPoolCardIdsMatchingQualities(self, qualities) + local allowed = __TS__New(Set, qualities) + local filteredCards = {} + for ____, cardId in ipairs(self:GetPoolCards()) do + local cardData = ____exports.CardSystem.cardData[cardId] + if cardData and allowed:has(cardData.quality) then + filteredCards[#filteredCards + 1] = cardId + end + end + return filteredCards +end +function CardSystem.prototype.GetHighestQualityInFilter(self, qualityFilter) + local best = qualityFilter[1] + for ____, quality in ipairs(qualityFilter) do + local bestIndex = __TS__ArrayIndexOf(____exports.CardSystem.QUALITY_FALLBACK_ORDER, best) + local qualityIndex = __TS__ArrayIndexOf(____exports.CardSystem.QUALITY_FALLBACK_ORDER, quality) + if qualityIndex >= 0 and (bestIndex < 0 or qualityIndex < bestIndex) then + best = quality + end + end + return best +end +function CardSystem.prototype.GetLowestRarityInFilter(self, qualityFilter) + local lowest = qualityFilter[1] + for ____, quality in ipairs(qualityFilter) do + local lowestIndex = __TS__ArrayIndexOf(____exports.CardSystem.QUALITY_FALLBACK_ORDER, lowest) + local qualityIndex = __TS__ArrayIndexOf(____exports.CardSystem.QUALITY_FALLBACK_ORDER, quality) + if qualityIndex >= 0 and (lowestIndex < 0 or qualityIndex > lowestIndex) then + lowest = quality + end + end + return lowest +end +function CardSystem.prototype.FillSelectionGapsWithLowerQuality(self, selection, qualityFilter, excludedIds, remaining) + local stillMissing = #__TS__ArrayFilter( + selection, + function(____, id) return id == 404 end + ) + if stillMissing <= 0 then + return selection + end + local filled = {unpack(selection)} + local poolRemaining = remaining or self:BuildPoolRemainingWeights(nil, excludedIds) + local fallbackQuality = self:GetNextAvailableQuality(self:GetLowestRarityInFilter(qualityFilter)) + while stillMissing > 0 and fallbackQuality ~= nil do + do + local fallbackPool = self:GetPoolCardIdsMatchingQualities({fallbackQuality}) + if #fallbackPool <= 0 then + fallbackQuality = self:GetNextAvailableQuality(fallbackQuality) + goto __continue365 + end + local replacements = self:DrawCardIdsFromRemaining(stillMissing, poolRemaining, fallbackPool, excludedIds) + local replaceIndex = 0 + do + local i = 0 + while i < #filled do + if filled[i + 1] == 404 and replaceIndex < #replacements and replacements[replaceIndex + 1] ~= 404 then + filled[i + 1] = replacements[replaceIndex + 1] + replaceIndex = replaceIndex + 1 + end + i = i + 1 + end + end + stillMissing = #__TS__ArrayFilter( + filled, + function(____, id) return id == 404 end + ) + if stillMissing <= 0 then + break + end + fallbackQuality = self:GetNextAvailableQuality(fallbackQuality) + end + ::__continue365:: + end + return filled +end +function CardSystem.prototype.GetNextAvailableQuality(self, targetQuality) + local targetIndex = __TS__ArrayIndexOf(____exports.CardSystem.QUALITY_FALLBACK_ORDER, targetQuality) + if targetIndex == -1 then + return nil + end + do + local i = targetIndex + 1 + while i < #____exports.CardSystem.QUALITY_FALLBACK_ORDER do + local quality = ____exports.CardSystem.QUALITY_FALLBACK_ORDER[i + 1] + if #self:GetPoolCardIdsMatchingQualities({quality}) > 0 then + return quality + end + i = i + 1 + end + end + return nil +end +function CardSystem.prototype.GetRerollCardsWithGuaranteedQuality(self, count, qualityFilter, excludedIds, strictExcludeIds) + if strictExcludeIds == nil then + strictExcludeIds = false + end + print("[CardSystem.GetRerollCardsWithGuaranteedQuality] ===== ОБРАБОТКА РЕРОЛА =====") + print("[CardSystem.GetRerollCardsWithGuaranteedQuality] Игрок: " .. tostring(self.playerId)) + print("[CardSystem.GetRerollCardsWithGuaranteedQuality] Запрошено карт: " .. tostring(count)) + print("[CardSystem.GetRerollCardsWithGuaranteedQuality] Гарантированных реролов: " .. tostring(#self.guaranteedQualityRerolls)) + do + local i = 0 + while i < #self.guaranteedQualityRerolls do + local reroll = self.guaranteedQualityRerolls[i + 1] + print((((((("[CardSystem.GetRerollCardsWithGuaranteedQuality] Рерол " .. tostring(i)) .. ": качество=") .. tostring(reroll.quality)) .. ", осталось=") .. tostring(reroll.rerollsLeft)) .. ", карт=") .. tostring(reroll.cardsPerReroll)) + i = i + 1 + end + end + local priorityQuality = __TS__ArrayFind( + self.guaranteedQualityRerolls, + function(____, item) return item.rerollsLeft > 0 end + ) + if not priorityQuality then + print("[CardSystem.GetRerollCardsWithGuaranteedQuality] ❌ Нет доступных гарантированных реролов") + print("[CardSystem.GetRerollCardsWithGuaranteedQuality] Возвращаем обычные случайные карты (веса пула)") + local result = self:DrawRandomCardIdsRespectingPoolWeights(count, excludedIds, strictExcludeIds) + print(("[CardSystem.GetRerollCardsWithGuaranteedQuality] Возвращаем " .. tostring(#result)) .. " случайных карт") + print(("[CardSystem.GetRerollCardsWithGuaranteedQuality] ID карт: [" .. table.concat(result, ", ")) .. "]") + return result + end + print("[CardSystem.GetRerollCardsWithGuaranteedQuality] ✅ Найден приоритетный рерол:") + print("[CardSystem.GetRerollCardsWithGuaranteedQuality] - Качество: " .. tostring(priorityQuality.quality)) + print("[CardSystem.GetRerollCardsWithGuaranteedQuality] - Осталось реролов: " .. tostring(priorityQuality.rerollsLeft)) + print("[CardSystem.GetRerollCardsWithGuaranteedQuality] - Карт за рерол: " .. tostring(priorityQuality.cardsPerReroll)) + print("[CardSystem.GetRerollCardsWithGuaranteedQuality] - Поведение при отсутствии: " .. priorityQuality.fallbackBehavior) + priorityQuality.rerollsLeft = priorityQuality.rerollsLeft - 1 + print("[CardSystem.GetRerollCardsWithGuaranteedQuality] Счетчик реролов уменьшен до: " .. tostring(priorityQuality.rerollsLeft)) + if priorityQuality.rerollsLeft <= 0 then + local index = __TS__ArrayIndexOf(self.guaranteedQualityRerolls, priorityQuality) + if index > -1 then + __TS__ArraySplice(self.guaranteedQualityRerolls, index, 1) + print("[CardSystem.GetRerollCardsWithGuaranteedQuality] ✅ Удален использованный рерол из массива") + print("[CardSystem.GetRerollCardsWithGuaranteedQuality] Осталось гарантированных реролов: " .. tostring(#self.guaranteedQualityRerolls)) + end + end + local allCards = self:GetPoolCards() + local qualityCards = {} + for ____, cardId in ipairs(allCards) do + local cardData = ____exports.CardSystem.cardData[cardId] + if cardData and cardData.quality == priorityQuality.quality then + qualityCards[#qualityCards + 1] = cardId + end + end + if #qualityCards == 0 then + repeat + local ____switch385 = priorityQuality.fallbackBehavior + local skipResult, delayResult, fallbackQuality, replaceResult, defaultResult + local ____cond385 = ____switch385 == "skip" + if ____cond385 then + print("[CardSystem.GetRerollCardsWithGuaranteedQuality] Пропускаем рерол (skip)") + skipResult = self:DrawRandomCardIdsRespectingPoolWeights(count, excludedIds, strictExcludeIds) + print(("[CardSystem.GetRerollCardsWithGuaranteedQuality] Возвращаем " .. tostring(#skipResult)) .. " случайных карт (skip)") + return skipResult + end + ____cond385 = ____cond385 or ____switch385 == "delay" + if ____cond385 then + print("[CardSystem.GetRerollCardsWithGuaranteedQuality] Откладываем рерол (delay)") + delayResult = self:DrawRandomCardIdsRespectingPoolWeights(count, excludedIds, strictExcludeIds) + print(("[CardSystem.GetRerollCardsWithGuaranteedQuality] Возвращаем " .. tostring(#delayResult)) .. " случайных карт (delay)") + return delayResult + end + ____cond385 = ____cond385 or ____switch385 == "replace" + if ____cond385 then + fallbackQuality = self:GetNextAvailableQuality(priorityQuality.quality) + if fallbackQuality ~= nil then + local fallbackCards = {} + for ____, cardId in ipairs(allCards) do + local cardData = ____exports.CardSystem.cardData[cardId] + if cardData and cardData.quality == fallbackQuality then + fallbackCards[#fallbackCards + 1] = cardId + end + end + if #fallbackCards > 0 then + print("[CardSystem.GetRerollCardsWithGuaranteedQuality] Заменяем на качество " .. tostring(fallbackQuality)) + local takeFromFallback = math.min(priorityQuality.cardsPerReroll, count) + local selectedCards = self:DrawRandomFromSubsetRespectingWeights(fallbackCards, takeFromFallback, excludedIds, strictExcludeIds) + local remainingCards = self:DrawRandomCardIdsRespectingPoolWeights(count - #selectedCards, excludedIds, strictExcludeIds) + local ____array_45 = __TS__SparseArrayNew(unpack(selectedCards)) + __TS__SparseArrayPush( + ____array_45, + unpack(remainingCards) + ) + local result = {__TS__SparseArraySpread(____array_45)} + priorityQuality.rerollsLeft = priorityQuality.rerollsLeft - 1 + print(("[CardSystem.GetRerollCardsWithGuaranteedQuality] Возвращаем " .. tostring(#result)) .. " карт (replace)") + return result + end + end + print("[CardSystem.GetRerollCardsWithGuaranteedQuality] Нет доступных карт для замены, используем обычный выбор") + replaceResult = self:DrawRandomCardIdsRespectingPoolWeights(count, excludedIds, strictExcludeIds) + print(("[CardSystem.GetRerollCardsWithGuaranteedQuality] Возвращаем " .. tostring(#replaceResult)) .. " случайных карт (replace fallback)") + return replaceResult + end + do + print("[CardSystem.GetRerollCardsWithGuaranteedQuality] Неизвестное поведение, используем обычный выбор") + defaultResult = self:DrawRandomCardIdsRespectingPoolWeights(count, excludedIds, strictExcludeIds) + print(("[CardSystem.GetRerollCardsWithGuaranteedQuality] Возвращаем " .. tostring(#defaultResult)) .. " случайных карт (default)") + return defaultResult + end + until true + end + local fromQualityCount = math.min(priorityQuality.cardsPerReroll, count) + local fromQuality = self:DrawRandomFromSubsetRespectingWeights(qualityCards, fromQualityCount, excludedIds, strictExcludeIds) + local restCount = count - #fromQuality + local rest = restCount > 0 and self:DrawRandomCardIdsRespectingPoolWeights(restCount, excludedIds, strictExcludeIds) or ({}) + local ____array_46 = __TS__SparseArrayNew(unpack(fromQuality)) + __TS__SparseArrayPush( + ____array_46, + unpack(rest) + ) + local guaranteedCards = {__TS__SparseArraySpread(____array_46)} + do + local i = #guaranteedCards - 1 + while i > 0 do + local j = math.floor(math.random() * (i + 1)) + local ____temp_47 = {guaranteedCards[j + 1], guaranteedCards[i + 1]} + guaranteedCards[i + 1] = ____temp_47[1] + guaranteedCards[j + 1] = ____temp_47[2] + i = i - 1 + end + end + print("[CardSystem.GetRerollCardsWithGuaranteedQuality] Итого возвращаем карт: " .. tostring(#guaranteedCards)) + print(("[CardSystem.GetRerollCardsWithGuaranteedQuality] ID карт: [" .. table.concat(guaranteedCards, ", ")) .. "]") + print("[CardSystem.GetRerollCardsWithGuaranteedQuality] ================================") + return guaranteedCards +end +function CardSystem.prototype.OnChooseCard(self, playerId, index, selectionToken) + local player = PlayerResource:GetPlayer(playerId) + local hero = player and player:GetAssignedHero() + if not hero or not IsValidEntity(hero) or not hero:IsAlive() then + return + end + local normalizedToken = math.floor(__TS__Number(selectionToken)) + if __TS__NumberIsFinite(normalizedToken) and normalizedToken > 0 and normalizedToken ~= self.currentSelectionToken then + print((((("[CardSystem.OnChooseCard] Игнорируем устаревший выбор: player=" .. tostring(playerId)) .. ", token=") .. tostring(normalizedToken)) .. ", current=") .. tostring(self.currentSelectionToken)) + return + end + local selected = self.playerItems[tostring(index)] + if not selected then + return + end + local cardId = selected.index + local selectedEntryId = selected.entry_id + local card68ExcludeOptionIds + if cardId == 68 then + card68ExcludeOptionIds = {} + for key in pairs(self.playerItems) do + local option = self.playerItems[key] + if option and option.index ~= nil and option.index ~= 404 and not isSlagCardId(nil, option.index) then + card68ExcludeOptionIds[#card68ExcludeOptionIds + 1] = option.index + end + end + end + if isSlagCardId(nil, cardId) then + if selectedEntryId then + self:ConsumePoolEntryWeight(selectedEntryId, 1) + else + self:ConsumePoolEntryWeight( + self:PickPoolEntryIdForCardId(cardId), + 1 + ) + end + self:RebuildPoolFromEntries() + self:SyncPool() + self:registerTakenCardChoice() + self.playerItems = {} + self:SyncPlayerItems() + self.isShowingCardSelection = false + self:ProcessNextCardSelection() + return + end + if cardId == 404 then + self:registerTakenCardChoice() + self.playerItems = {} + self:SyncPlayerItems() + self:SyncPool() + self.isShowingCardSelection = false + self:ProcessNextCardSelection() + return + end + if selectedEntryId then + self:ConsumePoolEntryWeight(selectedEntryId, 1) + else + local fallbackEntryId = self:PickPoolEntryIdForCardId(cardId) + self:ConsumePoolEntryWeight(fallbackEntryId, 1) + end + self:RebuildPoolFromEntries() + self:SyncPool() + self:registerTakenCardChoice() + local optionChain = __TS__ArrayIsArray(selected.source_chain_items) and selected.source_chain_items or self.currentSelectionSourceChain + local pickChain = self:BuildSourceChain( + "selected_card_" .. tostring(cardId), + optionChain + ) + self:AddCard( + tostring(cardId), + pickChain + ) + if self.repeatNextSelectedCardOnce then + self.repeatNextSelectedCardOnce = false + if not isCard79RepeatBlockedCardId(nil, cardId) then + local repeatChain = self:BuildSourceChain( + "repeat_selected_card_" .. tostring(cardId), + pickChain + ) + self:AddCard( + tostring(cardId), + repeatChain + ) + end + end + self.playerItems = {} + self:SyncPlayerItems() + self:SyncPool() + self.isShowingCardSelection = false + self:ProcessNextCardSelection() + if card68ExcludeOptionIds ~= nil then + self:ScheduleCard68CursedPick(card68ExcludeOptionIds) + end +end +function CardSystem.prototype.registerTakenCardChoice(self) + self.cardsTaken = self.cardsTaken + 1 + self:SyncRemainingCards() + local taken = self.cardsTaken + if taken % 10 == 0 or taken % 15 == 0 or taken % 20 == 0 then + self:CreatePoolEntry(404, 1, "threshold_empty_card_bonus") + self:RebuildPoolFromEntries() + self:SyncPool() + self:SyncRemainingCards() + end +end +function CardSystem.prototype.RerollCards(self, playerId) + if not self.playerItems or #__TS__ObjectKeys(self.playerItems) == 0 then + return + end + if self.rerollCost > 0 then + local useCard49Free = self:HasActiveCard(49) and self.card49FreeRerollCharges > 0 + local useCard6Free = not useCard49Free and self:GetCard6FreeRerollChargesForUse() > 0 + local useCard88Free = not useCard49Free and not useCard6Free and self:GetCard88FreeRerollChargesForUse() > 0 + if useCard49Free then + self.card49FreeRerollCharges = self.card49FreeRerollCharges - 1 + elseif useCard6Free then + self.card6FreeRerollCharges = self.card6FreeRerollCharges - 1 + elseif useCard88Free then + self.card88FreeRerollCharges = self.card88FreeRerollCharges - 1 + else + local crystalSystem = CrystalCurrency:getInstance() + local currentCrystals = crystalSystem:getCrystals(self.playerId) + if currentCrystals < self.rerollCost then + return + end + local success = crystalSystem:removeCrystals(self.playerId, self.rerollCost) + if not success then + return + end + end + end + if self.rerollCost < 50 then + self.rerollCost = self.rerollCost + 5 + end + self:SyncRerollCost() + self:SyncRemainingCards() + local count = #__TS__ObjectKeys(self.playerItems) + self:ProcessCardSelection(count, "reroll") +end +function CardSystem.prototype.AddCard(self, cardId, sourceChain, options) + local skipCurseStack = (options and options.skipCurseStack) == true + Timers:CreateTimer( + 0.1, + function() + local player = self:GetPlayer() + if not player then + return nil + end + local hero = player:GetAssignedHero() + if not hero or not hero:IsAlive() then + return nil + end + local className = "card_" .. cardId + local CardClass = ____exports.CardSystem.cardTypes[className] + if not CardClass then + return nil + end + local parsedId = __TS__ParseInt(cardId) + local previousEffectContext = self.effectSourceChainContext + self.effectSourceChainContext = sourceChain and #sourceChain > 0 and ({unpack(sourceChain)}) or previousEffectContext + if not skipCurseStack and hero:HasModifier("modifier_card_69") then + addCursedStack(nil, hero) + end + local ____self_itemsId_52 = self.itemsId + ____self_itemsId_52[#____self_itemsId_52 + 1] = parsedId + invalidateStatsMultiplierSumCache(nil, hero) + do + pcall(function() + local card80 = require("cards.examples.card_80") + local ____opt_53 = card80.notifyCard80PlayerTookCard + if ____opt_53 ~= nil then + ____opt_53(card80, hero, parsedId) + end + end) + end + local card = __TS__New(CardClass, hero, cardId) + if isGreedPoolCardId(nil, parsedId) and card:GetModifierName() == nil then + registerHiddenGreedCard(nil, hero, parsedId) + end + if not card:IsHidden() then + local ____self_items_55 = self.items + ____self_items_55[#____self_items_55 + 1] = card + self:SyncActiveCards() + else + table.remove(self.itemsId) + invalidateStatsMultiplierSumCache(nil, hero) + end + if isGreedPoolCardId(nil, parsedId) then + updateGreedForHero(nil, hero, self) + end + self.effectSourceChainContext = previousEffectContext + return nil + end + ) +end +function CardSystem.prototype.CraftOrUpgradeCard(self, cardId, _allowDebris) + local normalizedCardId = math.floor(__TS__Number(cardId)) + if not __TS__NumberIsFinite(normalizedCardId) or normalizedCardId <= 0 then + self:sendCardUpgradeResult(false, "Некорректный id карты") + return + end + local cardData = ____exports.CardSystem.cardData[normalizedCardId] + if not cardData or cardData.disabled == true then + self:sendCardUpgradeResult(false, "Карта недоступна для улучшения") + return + end + if cardData.canupgrade == false then + self:sendCardUpgradeResult(false, "Эту карту нельзя улучшать") + return + end + local key = tostring(normalizedCardId) + local currentState = self.cardPieces[key] or ({num = 0, level = 1}) + local currentLevel = math.max( + 1, + math.floor(__TS__Number(currentState.level or 1)) + ) + if currentLevel >= CARD_UPGRADE_MAX_LEVEL then + self:sendCardUpgradeResult( + false, + "Максимальный уровень: " .. tostring(CARD_UPGRADE_MAX_LEVEL) + ) + return + end + local nextLevel = currentLevel + 1 + local upgradeCost = self:getCardUpgradeCost(cardData.quality, currentLevel) + if upgradeCost <= 0 then + self:sendCardUpgradeResult(false, "Ошибка стоимости улучшения") + return + end + local storeModule = require("store_manager") + local ____opt_58 = storeModule.StoreManager + local ____opt_56 = ____opt_58 and ____opt_58.getInstance + local store = ____opt_56 and ____opt_56(____opt_58) + if not store then + self:sendCardUpgradeResult(false, "Магазин недоступен") + return + end + local isDefaultCard = cardData.default == true + local hasPurchasedCardById = store.hasPurchasedCardById + local ____hasPurchasedCardById_60 + if hasPurchasedCardById then + ____hasPurchasedCardById_60 = store:hasPurchasedCardById(self.playerId, normalizedCardId) == true + else + ____hasPurchasedCardById_60 = false + end + local hasPurchasedCard = ____hasPurchasedCardById_60 + local unlockCardId = math.floor(__TS__Number(cardData.deck_builder_unlock_card_id or 0)) + if cardData.deck_builder_non_deckable == true and unlockCardId > 0 then + local ____hasPurchasedCardById_61 + if hasPurchasedCardById then + ____hasPurchasedCardById_61 = store:hasPurchasedCardById(self.playerId, unlockCardId) == true + else + ____hasPurchasedCardById_61 = false + end + hasPurchasedCard = ____hasPurchasedCardById_61 + end + if not isDefaultCard and not hasPurchasedCard then + self:sendCardUpgradeResult( + false, + unlockCardId > 0 and cardData.deck_builder_non_deckable == true and getDeckBuilderUnlockRequiredMessage(nil, unlockCardId) or "Нельзя улучшить некупленную карту" + ) + return + end + local currentDustCurrency = store:getDustCurrency(self.playerId) + if currentDustCurrency < upgradeCost then + self:sendCardUpgradeResult( + false, + "Недостаточно пыли: нужно " .. tostring(upgradeCost) + ) + return + end + local removed = store:removeDustCurrency(self.playerId, upgradeCost) + if not removed then + self:sendCardUpgradeResult(false, "Не удалось списать пыль") + return + end + local ____this_63 + ____this_63 = store + local ____opt_62 = ____this_63.saveCurrencyToServer + if ____opt_62 ~= nil then + ____opt_62(____this_63, self.playerId) + end + self.cardPieces[key] = __TS__ObjectAssign({}, currentState, {level = nextLevel}) + self:SyncCardPieces() + self:RefreshActiveCardModifiersById(normalizedCardId) + if normalizedCardId == ____exports.CardSystem.CARD_6_ID then + self:TryGrantCard6Level2FreeRerolls() + end + self:saveCardLevelsToServer() + self:sendCardUpgradeResult( + true, + ("Карта улучшена до " .. tostring(nextLevel)) .. " уровня", + normalizedCardId, + nextLevel, + upgradeCost + ) +end +function CardSystem.prototype.getCardUpgradeCost(self, quality, currentLevel) + return ____exports.CardSystem:getCardUpgradeDustCost(quality, currentLevel) +end +function CardSystem.getCardUpgradeDustCost(self, quality, currentLevel) + local baseCost = ____exports.CardSystem.CARD_UPGRADE_BASE_COST_BY_QUALITY[quality] or 2000 + return math.max( + 1, + math.floor(baseCost * math.max(1, currentLevel)) + ) +end +function CardSystem.getDuplicateCardDustCompensation(self, quality, cardLevel) + local maxLevel = CARD_UPGRADE_MAX_LEVEL + local levelForCost = math.max( + 1, + math.floor(cardLevel) + ) + if levelForCost >= maxLevel then + levelForCost = maxLevel - 1 + end + return ____exports.CardSystem:getCardUpgradeDustCost(quality, levelForCost) +end +function CardSystem.prototype.sendCardUpgradeResult(self, success, message, cardId, newLevel, spentFreeCurrency) + local player = self:GetPlayer() + if not player then + return + end + CustomGameEventManager:Send_ServerToPlayer(player, "card_upgrade_result", { + success = success and 1 or 0, + message = message, + card_id = cardId, + new_level = newLevel, + spent_free_currency = spentFreeCurrency, + spent_dust_currency = spentFreeCurrency + }) +end +function CardSystem.prototype.RefreshActiveCardModifiersById(self, cardId) + for ____, card in ipairs(self.items) do + do + local cardUnsafe = card + local activeCardId = __TS__Number(cardUnsafe.cardKey or "0") + if not __TS__NumberIsFinite(activeCardId) or activeCardId ~= cardId then + goto __continue451 + end + if type(cardUnsafe.ModifierRefresh) == "function" then + cardUnsafe:ModifierRefresh() + end + end + ::__continue451:: + end +end +function CardSystem.prototype.buildCardLevelsSyncPayload(self) + local payload = {} + for ____, ____value in ipairs(__TS__ObjectEntries(____exports.CardSystem.cardData)) do + local idRaw = ____value[1] + local cardData = ____value[2] + do + local id = __TS__Number(idRaw) + if not __TS__NumberIsFinite(id) or id <= 0 or id == 404 or cardData.disabled == true then + goto __continue456 + end + local key = tostring(id) + local ____math_max_67 = math.max + local ____math_floor_66 = math.floor + local ____opt_64 = self.cardPieces[key] + local level = ____math_max_67( + 1, + ____math_floor_66(__TS__Number(____opt_64 and ____opt_64.level or 1)) + ) + local canUpgradeByRule = cardData.canupgrade ~= false + local canUpgrade = canUpgradeByRule and level < CARD_UPGRADE_MAX_LEVEL + payload[key] = { + level = level, + max_level = CARD_UPGRADE_MAX_LEVEL, + next_upgrade_cost = canUpgrade and self:getCardUpgradeCost(cardData.quality, level) or 0, + can_upgrade = canUpgradeByRule and 1 or 0 + } + end + ::__continue456:: + end + return payload +end +function CardSystem.prototype.SyncCardPieces(self) + CustomNetTables:SetTableValue( + "cards", + "card_pieces_" .. tostring(self.playerId), + self.cardPieces + ) + CustomNetTables:SetTableValue( + "cards", + "card_levels_" .. tostring(self.playerId), + self:buildCardLevelsSyncPayload() + ) +end +function CardSystem.prototype.buildPersistedCardLevelsPayload(self) + local payload = {} + for ____, ____value in ipairs(__TS__ObjectEntries(____exports.CardSystem.cardData)) do + local idRaw = ____value[1] + local cardData = ____value[2] + do + local id = __TS__Number(idRaw) + if not __TS__NumberIsFinite(id) or id <= 0 or id == 404 or cardData.disabled == true then + goto __continue461 + end + local key = tostring(id) + local ____math_max_71 = math.max + local ____math_floor_70 = math.floor + local ____opt_68 = self.cardPieces[key] + local level = ____math_max_71( + 1, + ____math_floor_70(__TS__Number(____opt_68 and ____opt_68.level or 1)) + ) + payload[key] = level + end + ::__continue461:: + end + return payload +end +function CardSystem.prototype.saveCardLevelsToServer(self) + local steamId = PlayerResource:GetSteamAccountID(self.playerId) + if not steamId then + return + end + local request = CreateHTTPRequest( + "PUT", + ((SERVER_CONFIG.API_URL .. "/player/") .. tostring(steamId)) .. "/card-levels" + ) + setApiHeaders(nil, request) + request:SetHTTPRequestRawPostBody( + "application/json", + json.encode({card_levels = self:buildPersistedCardLevelsPayload()}) + ) + request:Send(function(_result) + end) +end +function CardSystem.prototype.loadCardLevelsFromServer(self) + local steamId = PlayerResource:GetSteamAccountID(self.playerId) + if not steamId then + self.cardLevelsLoaded = true + return + end + local request = CreateHTTPRequest( + "GET", + ((SERVER_CONFIG.API_URL .. "/player/") .. tostring(steamId)) .. "/card-levels" + ) + setApiHeaders(nil, request) + request:Send(function(result) + if result.StatusCode < 200 or result.StatusCode >= 300 then + self.cardLevelsLoaded = true + return + end + do + local function ____catch(_error) + self:SyncCardPieces() + self.cardLevelsLoaded = true + end + local ____try, ____hasReturned, ____returnValue = pcall(function() + local decoded = {json.decode(result.Body)} + local ____temp_72 + if __TS__ArrayIsArray(decoded) and #decoded > 0 then + ____temp_72 = decoded[1] + else + ____temp_72 = decoded + end + local responseData = ____temp_72 + local ____opt_result_75 + if responseData ~= nil then + ____opt_result_75 = responseData.card_levels + end + local cardLevels = ____opt_result_75 + if not cardLevels or type(cardLevels) ~= "table" then + self:SyncCardPieces() + self.cardLevelsLoaded = true + return true + end + for ____, ____value in ipairs(__TS__ObjectEntries(cardLevels)) do + local cardIdRaw = ____value[1] + local levelRaw = ____value[2] + do + local cardIdNum = __TS__Number(cardIdRaw) + local levelNum = __TS__Number(levelRaw) + if not __TS__NumberIsFinite(cardIdNum) or cardIdNum <= 0 or not __TS__NumberIsFinite(levelNum) then + goto __continue473 + end + local cardId = math.floor(cardIdNum) + local level = math.max( + 1, + math.min( + CARD_UPGRADE_MAX_LEVEL, + math.floor(levelNum) + ) + ) + local key = tostring(cardId) + local current = self.cardPieces[key] or ({num = 0, level = 1}) + self.cardPieces[key] = __TS__ObjectAssign({}, current, {level = level}) + end + ::__continue473:: + end + self:SyncCardPieces() + self.cardLevelsLoaded = true + end) + if not ____try then + ____hasReturned, ____returnValue = ____catch(____hasReturned) + end + if ____hasReturned then + return ____returnValue + end + end + end) +end +function CardSystem.prototype.CreateNewDeck(self, name) + local player = self:GetPlayer() + if not player then + return + end + do + local i = 1 + while i <= 10 do + if not self.decks[tostring(i)] then + self.decks[tostring(i)] = {name = name, cards = {}} + self:SyncDecks() + CustomGameEventManager:Send_ServerToPlayer(player, "deck_selected", {index = i}) + return + end + i = i + 1 + end + end +end +function CardSystem.prototype.SaveDeck(self, index, cards, deckName) + if not cards then + return + end + local cardsArray + if __TS__ArrayIsArray(cards) then + cardsArray = cards + elseif type(cards) == "table" and cards ~= nil then + local keys = __TS__ArraySort( + __TS__ObjectKeys(cards), + function(____, a, b) return __TS__ParseInt(a) - __TS__ParseInt(b) end + ) + cardsArray = __TS__ArrayFilter( + __TS__ArrayMap( + keys, + function(____, key) return cards[key] end + ), + function(____, card) return type(card) == "number" end + ) + else + return + end + local normalizedCards = self:normalizeDeckCards(cardsArray) + local finalDeckName = self:resolveDeckName(deckName, index) + self.decks[tostring(index)] = {name = finalDeckName, cards = normalizedCards} + self:SyncDecks() + self:SaveDeckToServer(index, normalizedCards, finalDeckName) + if index == self.activeDeckIndex then + self:ReinitCardPool() + end +end +function CardSystem.prototype.SaveDeckToServer(self, index, cards, deckName) + local player = self:GetPlayer() + if not player then + return + end + local steamId = PlayerResource:GetSteamAccountID(self.playerId) + if not steamId then + return + end + local finalDeckName + if deckName and __TS__StringTrim(deckName) ~= "" then + finalDeckName = __TS__StringTrim(deckName) + else + local deckData = self.decks[tostring(index)] + if deckData and type(deckData) == "table" and deckData.name ~= nil then + finalDeckName = deckData.name or "Колода " .. tostring(index) + else + finalDeckName = "Колода " .. tostring(index) + end + end + local request = CreateHTTPRequest( + "PUT", + (((SERVER_CONFIG.API_URL .. "/player/") .. tostring(steamId)) .. "/decks/") .. tostring(index) + ) + setApiHeaders(nil, request) + local dataToSend = {name = finalDeckName, cards = cards, active = index == self.activeDeckIndex} + request:SetHTTPRequestRawPostBody( + "application/json", + json.encode(dataToSend) + ) + request:Send(function(result) + if result.StatusCode >= 200 and result.StatusCode < 300 then + else + end + end) +end +function CardSystem.prototype.DeleteDeck(self, index) + __TS__Delete( + self.decks, + tostring(index) + ) + self:SyncDecks() + self:DeleteDeckFromServer(index) +end +function CardSystem.prototype.DeleteDeckFromServer(self, index) + local player = self:GetPlayer() + if not player then + return + end + local steamId = PlayerResource:GetSteamAccountID(self.playerId) + if not steamId then + return + end + local request = CreateHTTPRequest( + "DELETE", + (((SERVER_CONFIG.API_URL .. "/player/") .. tostring(steamId)) .. "/decks/") .. tostring(index) + ) + setApiHeaders(nil, request) + request:Send(function(result) + if result.StatusCode >= 200 and result.StatusCode < 300 then + else + end + end) +end +function CardSystem.prototype.ActivateDeck(self, index) + self.activeDeckIndex = index + self:SyncDecks() + local player = self:GetPlayer() + if player then + CustomGameEventManager:Send_ServerToPlayer(player, "deck_selected", {index = index}) + end + self:UpdateActiveDeckOnServer() + self:ReinitCardPool() +end +function CardSystem.prototype.UpdateActiveDeckOnServer(self) + local player = self:GetPlayer() + if not player then + return + end + local steamId = PlayerResource:GetSteamAccountID(self.playerId) + if not steamId then + return + end + for ____, ____value in ipairs(__TS__ObjectEntries(self.decks)) do + local deckIndex = ____value[1] + local deckData = ____value[2] + local index = __TS__ParseInt(deckIndex) + local isActive = index == self.activeDeckIndex + local deckName + local deckCards + if __TS__ArrayIsArray(deckData) then + deckName = deckData[1] + deckCards = __TS__ArraySlice(deckData, 1) + elseif deckData and type(deckData) == "table" and deckData.name ~= nil then + deckName = deckData.name or "Колода " .. tostring(index) + deckCards = deckData.cards or ({}) + else + local keys = __TS__ArraySort( + __TS__ObjectKeys(deckData), + function(____, a, b) return __TS__ParseInt(a) - __TS__ParseInt(b) end + ) + deckName = deckData[keys[1]] or "Колода " .. tostring(index) + deckCards = __TS__ArrayFilter( + __TS__ArrayMap( + __TS__ArraySlice(keys, 1), + function(____, key) return deckData[key] end + ), + function(____, card) return type(card) == "number" end + ) + end + local request = CreateHTTPRequest( + "PUT", + (((SERVER_CONFIG.API_URL .. "/player/") .. tostring(steamId)) .. "/decks/") .. tostring(index) + ) + setApiHeaders(nil, request) + request:SetHTTPRequestHeaderValue("Content-Type", "application/json") + request:SetHTTPRequestNetworkActivityTimeout(10) + request:SetHTTPRequestAbsoluteTimeoutMS(10000) + local dataToSend = {name = deckName, cards = deckCards, active = isActive} + request:SetHTTPRequestRawPostBody( + "application/json", + json.encode(dataToSend) + ) + request:Send(function(result) + if result.StatusCode >= 200 and result.StatusCode < 300 then + else + end + end) + end +end +function CardSystem.prototype.LoadDecks(self) + local player = self:GetPlayer() + if not player then + return + end + local steamId = PlayerResource:GetSteamAccountID(self.playerId) + if not steamId then + return + end + local request = CreateHTTPRequest( + "GET", + ((SERVER_CONFIG.API_URL .. "/player/") .. tostring(steamId)) .. "/decks" + ) + setApiHeaders(nil, request) + request:Send(function(result) + if result.StatusCode >= 200 and result.StatusCode < 300 then + do + pcall(function() + local data = {json.decode(result.Body)} + if data then + for key in pairs(data) do + end + else + end + local success = true + local decks = data and data[1] + local activeDeckIndex = 0 + if data and success and decks then + self.decks = decks.decks or decks + self.activeDeckIndex = decks.activeDeckIndex or activeDeckIndex or 0 + self:normalizeAllDecks() + for key in pairs(self.decks) do + end + for ____, ____value in ipairs(__TS__ObjectEntries(self.decks)) do + local deckIndex = ____value[1] + local deckData = ____value[2] + do + if type(deckData) == "boolean" then + goto __continue536 + end + if type(deckData) == "number" then + goto __continue536 + end + local isLegacy = __TS__ArrayIsArray(deckData) + local deckName = isLegacy and deckData[1] or deckData.name + local cardCount = isLegacy and #deckData - 1 or (deckData.cards ~= nil and deckData.cards ~= nil and #deckData.cards or 0) + end + ::__continue536:: + end + self:SyncDecks() + else + self:SyncDecks() + end + end) + end + else + end + end) +end +function CardSystem.prototype.GetPlayer(self) + return PlayerResource:GetPlayer(self.playerId) +end +function CardSystem.prototype.resolveDeckName(self, rawName, index) + if type(rawName) == "string" and __TS__StringTrim(rawName) ~= "" then + return __TS__StringTrim(rawName) + end + local existingDeck = self.decks[tostring(index)] + if existingDeck ~= nil and existingDeck ~= nil then + if __TS__ArrayIsArray(existingDeck) then + local legacyName = __TS__StringTrim(tostring(existingDeck[1] or "")) + if legacyName ~= "" then + return legacyName + end + elseif type(existingDeck) == "table" and existingDeck.name ~= nil then + local existingName = __TS__StringTrim(tostring(existingDeck.name or "")) + if existingName ~= "" then + return existingName + end + end + end + return "Колода " .. tostring(index) +end +function CardSystem.prototype.getDeckCardsList(self, deckData) + if not deckData then + return {} + end + if __TS__ArrayIsArray(deckData) then + return __TS__ArrayMap( + __TS__ArrayFilter( + __TS__ArrayMap( + __TS__ArraySlice(deckData, 1), + function(____, card) return __TS__Number(card) end + ), + function(____, card) return __TS__NumberIsFinite(card) and card > 0 end + ), + function(____, card) return math.floor(card) end + ) + end + local cards = deckData.cards + if __TS__ArrayIsArray(cards) then + return __TS__ArrayMap( + __TS__ArrayFilter( + __TS__ArrayMap( + cards, + function(____, card) return __TS__Number(card) end + ), + function(____, card) return __TS__NumberIsFinite(card) and card > 0 end + ), + function(____, card) return math.floor(card) end + ) + end + if cards and type(cards) == "table" then + return __TS__ArrayMap( + __TS__ArrayFilter( + __TS__ArrayMap( + __TS__ObjectValues(cards), + function(____, card) return __TS__Number(card) end + ), + function(____, card) return __TS__NumberIsFinite(card) and card > 0 end + ), + function(____, card) return math.floor(card) end + ) + end + return {} +end +function CardSystem.prototype.getDefaultDeckSeed(self) + local seed = {} + local seen = __TS__New(Set) + local function pushIfValid(____, rawId) + local id = math.floor(__TS__Number(rawId)) + if not __TS__NumberIsFinite(id) or id <= 0 or seen:has(id) then + return + end + local cardData = ____exports.CardSystem.cardData[id] + if not cardData or cardData.disabled == true then + return + end + seen:add(id) + seed[#seed + 1] = id + end + for ____, cardId in ipairs(DEFAULT_DECK_CARD_IDS) do + pushIfValid(nil, cardId) + end + if #seed > 0 then + return seed + end + for ____, ____value in ipairs(__TS__ObjectEntries(____exports.CardSystem.cardData)) do + local cardIdRaw = ____value[1] + local cardData = ____value[2] + do + if not cardData or cardData.disabled == true then + goto __continue571 + end + pushIfValid( + nil, + __TS__Number(cardIdRaw) + ) + end + ::__continue571:: + end + return seed +end +function CardSystem.prototype.normalizeDeckCards(self, rawDeck) + local requiredSize = DECK_BUILDER_SLOT_CAPACITY + local normalized = {} + local copiesByCardId = {} + local sourceCards = __TS__ArrayIsArray(rawDeck) and rawDeck or self:getDeckCardsList(rawDeck) + local function getCardMaxCopiesForDeck(____, cardData) + local configured = __TS__Number(cardData.max_copies) + if __TS__NumberIsFinite(configured) and configured > 0 then + return math.floor(configured) + end + if cardData.quality >= ____exports.CardQuality.LEGENDARY then + return 1 + end + return 2 + end + local function getCardMaxCopiesForDeckById(____, cardId) + local cardData = ____exports.CardSystem.cardData[cardId] + if not cardData then + return 2 + end + return getCardMaxCopiesForDeck(nil, cardData) + end + for ____, rawId in ipairs(sourceCards) do + do + local id = math.floor(__TS__Number(rawId)) + if not __TS__NumberIsFinite(id) or id <= 0 then + goto __continue580 + end + local cardData = ____exports.CardSystem.cardData[id] + if not cardData then + if not ____exports.CardSystem:canAddCardToDeckSlots(normalized, id, requiredSize) then + goto __continue580 + end + normalized[#normalized + 1] = id + copiesByCardId[id] = (copiesByCardId[id] or 0) + 1 + if ____exports.CardSystem:getDeckUsedSlots(normalized) >= requiredSize then + break + end + goto __continue580 + end + if cardData.disabled == true then + goto __continue580 + end + if cardData.deck_builder_non_deckable == true then + goto __continue580 + end + local maxCopies = getCardMaxCopiesForDeck(nil, cardData) + local currentCopies = copiesByCardId[id] or 0 + if currentCopies >= maxCopies then + goto __continue580 + end + if not ____exports.CardSystem:canAddCardToDeckSlots(normalized, id, requiredSize) then + goto __continue580 + end + normalized[#normalized + 1] = id + copiesByCardId[id] = currentCopies + 1 + if ____exports.CardSystem:getDeckUsedSlots(normalized) >= requiredSize then + break + end + end + ::__continue580:: + end + if ____exports.CardSystem:getDeckUsedSlots(normalized) >= requiredSize then + return normalized + end + local seed = self:getDefaultDeckSeed() + if #seed <= 0 then + local emergencySeed = __TS__ArrayFilter( + __TS__ArrayFrom(__TS__New(Set, normalized)), + function(____, id) return __TS__NumberIsFinite(id) and id > 0 end + ) + if #emergencySeed > 0 then + seed = emergencySeed + else + local sourceSeed = __TS__ArrayFilter( + __TS__ArrayMap( + __TS__ArrayFrom(__TS__New(Set, sourceCards)), + function(____, id) return math.floor(__TS__Number(id)) end + ), + function(____, id) return __TS__NumberIsFinite(id) and id > 0 end + ) + if #sourceSeed > 0 then + seed = sourceSeed + else + return normalized + end + end + end + while ____exports.CardSystem:getDeckUsedSlots(normalized) < requiredSize do + local addedInPass = 0 + for ____, nextId in ipairs(seed) do + do + local cardData = ____exports.CardSystem.cardData[nextId] + if cardData and cardData.disabled == true then + goto __continue601 + end + if cardData and cardData.deck_builder_non_deckable == true then + goto __continue601 + end + if (copiesByCardId[nextId] or 0) > 0 then + goto __continue601 + end + local maxCopies = getCardMaxCopiesForDeckById(nil, nextId) + local currentCopies = copiesByCardId[nextId] or 0 + if currentCopies >= maxCopies then + goto __continue601 + end + if not ____exports.CardSystem:canAddCardToDeckSlots(normalized, nextId, requiredSize) then + goto __continue601 + end + normalized[#normalized + 1] = nextId + copiesByCardId[nextId] = currentCopies + 1 + addedInPass = addedInPass + 1 + if ____exports.CardSystem:getDeckUsedSlots(normalized) >= requiredSize then + break + end + end + ::__continue601:: + end + if addedInPass <= 0 then + break + end + end + if ____exports.CardSystem:getDeckUsedSlots(normalized) < requiredSize and #seed > 0 then + while ____exports.CardSystem:getDeckUsedSlots(normalized) < requiredSize do + local addedInPass = 0 + for ____, nextId in ipairs(seed) do + do + local cardData = ____exports.CardSystem.cardData[nextId] + if cardData and cardData.disabled == true then + goto __continue612 + end + if cardData and cardData.deck_builder_non_deckable == true then + goto __continue612 + end + local maxCopies = getCardMaxCopiesForDeckById(nil, nextId) + local currentCopies = copiesByCardId[nextId] or 0 + if currentCopies >= maxCopies then + goto __continue612 + end + if not ____exports.CardSystem:canAddCardToDeckSlots(normalized, nextId, requiredSize) then + goto __continue612 + end + normalized[#normalized + 1] = nextId + copiesByCardId[nextId] = currentCopies + 1 + addedInPass = addedInPass + 1 + if ____exports.CardSystem:getDeckUsedSlots(normalized) >= requiredSize then + break + end + end + ::__continue612:: + end + if addedInPass <= 0 then + break + end + end + end + return normalized +end +function CardSystem.prototype.normalizeAllDecks(self) + local normalizedDecks = {} + for ____, ____value in ipairs(__TS__ObjectEntries(self.decks)) do + local deckIndexRaw = ____value[1] + local deckData = ____value[2] + do + local index = __TS__Number(deckIndexRaw) + if not __TS__NumberIsFinite(index) or index <= 0 then + goto __continue621 + end + normalizedDecks[deckIndexRaw] = { + name = self:resolveDeckName( + deckData, + math.floor(index) + ), + cards = self:normalizeDeckCards(deckData) + } + end + ::__continue621:: + end + self.decks = normalizedDecks +end +function CardSystem.prototype.SyncDecks(self) + for ____, ____value in ipairs(__TS__ObjectEntries(self.decks)) do + local deckIndex = ____value[1] + local deckData = ____value[2] + do + if not deckData then + goto __continue625 + end + local isLegacy = __TS__ArrayIsArray(deckData) + local deckName + local cardCount + if isLegacy then + deckName = deckData[1] + cardCount = #deckData - 1 + else + deckName = deckData.name or "Колода " .. deckIndex + local cards = deckData.cards + cardCount = cards ~= nil and cards ~= nil and __TS__ArrayIsArray(cards) and #cards or 0 + end + end + ::__continue625:: + end + CustomNetTables:SetTableValue( + "cards", + "decks_" .. tostring(self.playerId), + self.decks + ) + CustomNetTables:SetTableValue( + "cards", + "active_deck_" .. tostring(self.playerId), + {index = self.activeDeckIndex} + ) + self:SyncCardPieces() + self:CaptureRuntimeState() +end +function CardSystem.prototype.SyncPlayerItems(self) + CustomNetTables:SetTableValue( + "cards", + "selection_" .. tostring(self.playerId), + self.playerItems + ) +end +function CardSystem.prototype.SyncSelectionQueue(self) + local data = {size = #self.cardSelectionQueue, showing = self.isShowingCardSelection} + CustomNetTables:SetTableValue( + "cards", + "selection_queue_" .. tostring(self.playerId), + data + ) +end +function CardSystem.prototype.SyncSelectionSource(self) + CustomNetTables:SetTableValue( + "cards", + "selection_source_" .. tostring(self.playerId), + { + source = self.currentSelectionSource, + chain = self:SerializeSourceChain(self.currentSelectionSourceChain), + chain_items = self.currentSelectionSourceChain, + selection_token = self.currentSelectionToken + } + ) +end +function CardSystem.prototype.SyncPoolSourceChains(self) + local data = {} + for entryId in pairs(self.poolEntriesById) do + do + local entry = self.poolEntriesById[entryId] + local chain = entry and entry.sourceChain or ({}) + if #chain <= 0 then + goto __continue634 + end + data[entryId] = { + index = entry.cardId, + source = chain[#chain] or "unknown", + chain = self:SerializeSourceChain(chain), + chain_items = chain + } + end + ::__continue634:: + end + CustomNetTables:SetTableValue( + "cards", + "pool_source_" .. tostring(self.playerId), + data + ) +end +function CardSystem.prototype.SyncActiveCards(self) + CustomNetTables:SetTableValue( + "cards", + "active_cards_" .. tostring(self.playerId), + self.itemsId + ) + self:CaptureRuntimeState() +end +function CardSystem.prototype.SyncPool(self) + local poolData = {} + for entryId in pairs(self.poolEntriesById) do + do + local entry = self.poolEntriesById[entryId] + if not entry or entry.weight <= 0 then + goto __continue639 + end + poolData[#poolData + 1] = {entry_id = entry.entryId, index = entry.cardId, weight = entry.weight} + end + ::__continue639:: + end + CustomNetTables:SetTableValue( + "cards", + "pool_" .. tostring(self.playerId), + poolData + ) + self:SyncPoolSourceChains() + self:SyncRemainingCards() + self:CaptureRuntimeState() +end +function CardSystem.prototype.SyncRemainingCards(self) + local remainingCount = self.pool ~= nil and self.pool.totalProportion or 0 + CustomNetTables:SetTableValue( + "cards", + "remaining_cards_" .. tostring(self.playerId), + {count = remainingCount} + ) +end +function CardSystem.prototype.SyncCardData(self) + if ____exports.CardSystem.clientCardCatalogSynced then + return + end + ____exports.CardSystem.clientCardCatalogSynced = true + local function serializeValuesForNetTable(____, values) + if not values then + return nil + end + local out = {} + for ____, ____value in ipairs(__TS__ObjectEntries(values)) do + local key = ____value[1] + local raw = ____value[2] + do + if raw == nil or raw == nil then + goto __continue647 + end + if type(raw) == "table" then + local leveled = {} + local hasLevel = false + local tableRaw = raw + do + local i = 1 + while i <= 32 do + local ____tableRaw_i_78 = tableRaw[i] + if ____tableRaw_i_78 == nil then + ____tableRaw_i_78 = tableRaw[tostring(i)] + end + local v = __TS__Number(____tableRaw_i_78) + if __TS__NumberIsFinite(v) then + leveled[tostring(i)] = v + hasLevel = true + end + i = i + 1 + end + end + if hasLevel then + out[key] = leveled + goto __continue647 + end + end + local numeric = __TS__Number(raw) + if __TS__NumberIsFinite(numeric) then + out[key] = numeric + end + end + ::__continue647:: + end + return out + end + local cardDataForClient = {} + for cardId in pairs(____exports.CardSystem.cardData) do + local card = ____exports.CardSystem.cardData[cardId] + cardDataForClient[cardId] = __TS__ObjectAssign( + {}, + card, + { + values = serializeValuesForNetTable(nil, card.values), + disabled = card.disabled == true and "true" or "false", + inherent = card.inherent == true and "true" or "false", + default = card.default == true and "true" or "false", + purchasable = card.purchasable == false and "false" or "true", + obtainable = card.obtainable == false and "false" or "true", + deck_builder_non_deckable = card.deck_builder_non_deckable == true and "true" or "false", + deck_builder_unlock_card_id = card.deck_builder_unlock_card_id + } + ) + end + CustomNetTables:SetTableValue("cards", "card_data", cardDataForClient) + for cardId in pairs(____exports.CardSystem.cardData) do + local card = ____exports.CardSystem.cardData[cardId] + if card.disabled == true then + end + end +end +function CardSystem.prototype.SyncRerollCost(self) + local key = "reroll_cost_" .. tostring(self.playerId) + local card49Charges = self:HasActiveCard(49) and self.card49FreeRerollCharges or 0 + local card6Charges = self:GetCard6FreeRerollChargesForUse() + local card88Charges = self:GetCard88FreeRerollChargesForUse() + local totalFreeCharges = card49Charges + card6Charges + card88Charges + local hasFreeReroll = self.rerollCost > 0 and totalFreeCharges > 0 + local data = { + cost = self.rerollCost, + card49_free_reroll = hasFreeReroll and "1" or "0", + card49_free_charges = tostring(card49Charges), + card6_free_charges = tostring(card6Charges), + card88_free_charges = tostring(card88Charges), + free_reroll_charges_total = tostring(totalFreeCharges) + } + CustomNetTables:SetTableValue("cards", key, data) + self:CaptureRuntimeState() +end +function CardSystem.prototype.PushRerollCostToClient(self) + self:SyncRerollCost() +end +function CardSystem.prototype.ResetCard51MorningGold(self) + self.card51MorningGoldEarned = 0 + self:CaptureRuntimeState() +end +function CardSystem.prototype.GetCard51MorningGoldEarned(self) + return math.max( + 0, + math.floor(self.card51MorningGoldEarned) + ) +end +function CardSystem.prototype.TryGrantCard51GoldFromDamage(self, hero, goldGain) + if not hero or not IsValidEntity(hero) or not hero:IsRealHero() then + return 0 + end + local baseCap = math.max( + 0, + math.floor(self:getCardValueByLevel(51, "morning_gold_cap", 400)) + ) + local copies = math.max( + 1, + self:GetActiveCardCopies(51) + ) + local cap = baseCap * copies + if cap <= 0 or goldGain <= 0 then + return 0 + end + local earned = self:GetCard51MorningGoldEarned() + local remaining = math.max(0, cap - earned) + local actualGain = math.min(goldGain, remaining) + if actualGain <= 0 then + return 0 + end + self.card51MorningGoldEarned = earned + actualGain + PlayerResource:ModifyGold(self.playerId, actualGain, true, DOTA_ModifyGold_Unspecified) + SendOverheadEventMessage( + nil, + OVERHEAD_ALERT_GOLD, + hero, + actualGain, + hero:GetPlayerOwner() + ) + self:CaptureRuntimeState() + return actualGain +end +function CardSystem.prototype.AddSlagToPool(self, weight, count, source, sourceChain) + if weight == nil then + weight = 1 + end + if count == nil then + count = 1 + end + if source == nil then + source = "card_85_fishing_slag" + end + local safeWeight = math.max( + 1, + math.floor(weight) + ) + local safeCount = math.max( + 1, + math.floor(count) + ) + if safeCount <= 1 then + self:CreatePoolEntry(SLAG_CARD_ID, safeWeight, source, sourceChain) + else + self:CreatePoolEntry(SLAG_CARD_ID, safeWeight * safeCount, source, sourceChain) + end + self:TryGrantCard88FreeRerollsFromSlag(safeCount) + self:RebuildPoolFromEntries() + self:SyncPool() +end +function CardSystem.prototype.removePoolEntriesForCardWithSourceToken(self, cardId, sourceToken) + local normalizedCardId = math.floor(__TS__Number(cardId)) + local normalizedSource = self:NormalizeSourceToken(sourceToken) + if not __TS__NumberIsFinite(normalizedCardId) or normalizedCardId <= 0 or #normalizedSource <= 0 then + return + end + for entryId in pairs(self.poolEntriesById) do + do + local entry = self.poolEntriesById[entryId] + if not entry or entry.cardId ~= normalizedCardId then + goto __continue673 + end + local chain = entry.sourceChain or ({}) + local matches = false + for ____, token in ipairs(chain) do + if self:NormalizeSourceToken(token) == normalizedSource then + matches = true + break + end + end + if matches then + __TS__Delete(self.poolEntriesById, entryId) + end + end + ::__continue673:: + end +end +function CardSystem.prototype.SetPoolCardFromSource(self, cardId, weight, source, sourceChain) + if weight == nil then + weight = 1 + end + if source == nil then + source = "manual_pool_add" + end + if isSlagCardId(nil, cardId) then + self:AddSlagToPool(weight, 1, source, sourceChain) + return + end + local cardData = ____exports.CardSystem.cardData[cardId] + if cardData and (cardData.disabled == true or cardData.obtainable == false) then + return + end + self:removePoolEntriesForCardWithSourceToken(cardId, source) + self:CreatePoolEntry( + cardId, + math.max( + 1, + math.floor(weight) + ), + source, + sourceChain + ) + self:RebuildPoolFromEntries() + self:SyncPool() +end +function CardSystem.prototype.AddCardToPool(self, cardId, weight, source, sourceChain) + if weight == nil then + weight = 5 + end + if source == nil then + source = "manual_pool_add" + end + if isSlagCardId(nil, cardId) then + self:AddSlagToPool(weight, 1, source, sourceChain) + return + end + local cardData = ____exports.CardSystem.cardData[cardId] + if cardData and (cardData.disabled == true or cardData.obtainable == false) then + return + end + self:CreatePoolEntry(cardId, weight, source, sourceChain) + self:RebuildPoolFromEntries() + self:SyncPool() +end +function CardSystem.prototype.AddCardToPoolCount(self, cardId, weight, count, source, sourceChain) + if weight == nil then + weight = 5 + end + if count == nil then + count = 1 + end + if source == nil then + source = "manual_pool_add" + end + if isSlagCardId(nil, cardId) then + self:AddSlagToPool(weight, count, source, sourceChain) + return + end + local cardData = ____exports.CardSystem.cardData[cardId] + if cardData and (cardData.disabled == true or cardData.obtainable == false) then + return + end + local safeCount = math.max( + 0, + math.floor(count) + ) + if safeCount <= 1 then + self:CreatePoolEntry(cardId, weight, source, sourceChain) + else + self:CreatePoolEntry(cardId, weight * safeCount, source, sourceChain) + end + self:RebuildPoolFromEntries() + self:SyncPool() +end +function CardSystem.prototype.RemoveCardFromPool(self, cardId) + for entryId in pairs(self.poolEntriesById) do + local entry = self.poolEntriesById[entryId] + if entry and entry.cardId == cardId then + __TS__Delete(self.poolEntriesById, entryId) + end + end + self:RebuildPoolFromEntries() + self:SyncPool() +end +function CardSystem.prototype.HasCardInPool(self, cardId) + for entryId in pairs(self.poolEntriesById) do + local entry = self.poolEntriesById[entryId] + if entry and entry.cardId == cardId and entry.weight > 0 then + return true + end + end + return false +end +function CardSystem.prototype.GetPoolSize(self) + return self.pool.len +end +function CardSystem.prototype.GetPoolCards(self) + local set = __TS__New(Set) + for entryId in pairs(self.poolEntriesById) do + local entry = self.poolEntriesById[entryId] + if entry and entry.weight > 0 then + set:add(entry.cardId) + end + end + return __TS__ArrayFrom(set:values()) +end +function CardSystem.prototype.GetPoolCardWeightSum(self, cardId) + local normalizedCardId = math.floor(__TS__Number(cardId)) + if not __TS__NumberIsFinite(normalizedCardId) or normalizedCardId <= 0 then + return 0 + end + local total = 0 + for entryId in pairs(self.poolEntriesById) do + local entry = self.poolEntriesById[entryId] + if entry and entry.cardId == normalizedCardId and entry.weight > 0 then + total = total + entry.weight + end + end + return total +end +function CardSystem.prototype.ClearPool(self) + self.poolEntriesById = {} + self.pool:clear() + self:SyncPool() +end +function CardSystem.prototype.RemoveRandomCardsFromPool(self, count) + local removedCards = {} + local allCards = self:GetPoolCards() + if #allCards == 0 then + return removedCards + end + local cardsToRemove = math.min(count, #allCards) + local shuffledCards = {unpack(allCards)} + do + local i = #shuffledCards - 1 + while i > 0 do + local j = math.floor(math.random() * (i + 1)) + local ____temp_79 = {shuffledCards[j + 1], shuffledCards[i + 1]} + shuffledCards[i + 1] = ____temp_79[1] + shuffledCards[j + 1] = ____temp_79[2] + i = i - 1 + end + end + do + local i = 0 + while i < cardsToRemove do + local cardId = shuffledCards[i + 1] + for entryId in pairs(self.poolEntriesById) do + local entry = self.poolEntriesById[entryId] + if entry and entry.cardId == cardId then + __TS__Delete(self.poolEntriesById, entryId) + end + end + removedCards[#removedCards + 1] = cardId + i = i + 1 + end + end + self:RebuildPoolFromEntries() + self:SyncPool() + return removedCards +end +CardSystem.CARD_49_FREE_STACK_CAP = 99 +CardSystem.CARD_6_ID = 6 +CardSystem.CARD_6_FREE_STACK_CAP = 99 +CardSystem.CARD_88_ID = 88 +CardSystem.CARD_88_FREE_STACK_CAP = 99 +CardSystem.CARD_UPGRADE_BASE_COST_BY_QUALITY = { + [____exports.CardQuality.COMMON] = 500, + [____exports.CardQuality.RARE] = 1000, + [____exports.CardQuality.EPIC] = 2000, + [____exports.CardQuality.LEGENDARY] = 7000, + [____exports.CardQuality.MYTHIC] = 15000 +} +CardSystem.cardTypes = {} +CardSystem.cardData = {} +CardSystem.clientCardCatalogSynced = false +CardSystem.runtimeStateByPlayerId = {} +CardSystem.QUALITY_FALLBACK_ORDER = { + ____exports.CardQuality.MYTHIC, + ____exports.CardQuality.LEGENDARY, + ____exports.CardQuality.EPIC, + ____exports.CardQuality.RARE, + ____exports.CardQuality.COMMON +} +--- Инициализация Card System для всех игроков +function ____exports.InitCardSystem(self) + local function initPlayerCardSystem(____, playerId) + local player = PlayerResource:GetPlayer(playerId) + if player and not player.cardSystem then + player.cardSystem = __TS__New(____exports.CardSystem, playerId) + end + end + local function initAllConnectedLobbyPlayers() + do + local i = 0 + while i < DOTA_MAX_PLAYERS do + do + local playerId = i + if not isRealLobbyPlayer(nil, playerId) then + goto __continue721 + end + initPlayerCardSystem(nil, playerId) + end + ::__continue721:: + i = i + 1 + end + end + end + CustomGameEventManager:RegisterListener( + "init_card_system", + function(source, _event) + local playerId = source + if playerId == nil or playerId < 0 then + return + end + initPlayerCardSystem(nil, playerId) + local player = PlayerResource:GetPlayer(playerId) + local ____opt_80 = player and player.cardSystem + if ____opt_80 ~= nil then + ____opt_80:ForceDeckBuilderSync() + end + end + ) + CustomGameEventManager:RegisterListener( + "request_available_cards", + function(source, _event) + local playerId = source + if playerId == nil or playerId < 0 then + return + end + initPlayerCardSystem(nil, playerId) + local player = PlayerResource:GetPlayer(playerId) + local ____opt_84 = player and player.cardSystem + if ____opt_84 ~= nil then + ____opt_84:ForceDeckBuilderSync() + end + end + ) + ListenToGameEvent( + "npc_spawned", + function(event) + local unit = EntIndexToHScript(event.entindex) + if unit and unit:IsRealHero() then + local playerId = unit:GetPlayerOwnerID() + if playerId >= 0 then + Timers:CreateTimer( + 0.1, + function() + initPlayerCardSystem(nil, playerId) + local player = PlayerResource:GetPlayer(playerId) + local hero = player and player:GetAssignedHero() + if hero and IsValidEntity(hero) and hero:IsRealHero() then + updateGreedForHero(nil, hero, player and player.cardSystem) + end + return nil + end + ) + end + end + end, + nil + ) + ListenToGameEvent( + "game_rules_state_change", + function() + local state = GameRules:State_Get() + if state == DOTA_GAMERULES_STATE_CUSTOM_GAME_SETUP then + initAllConnectedLobbyPlayers(nil) + end + end, + nil + ) +end +--- Утилитарная функция: получить количество взятых карт игрока +function ____exports.GetPlayerCardsTaken(self, playerId) + local player = PlayerResource:GetPlayer(playerId) + if player and player.cardSystem then + return player.cardSystem:GetCardsTaken() + end + return 0 +end +--- Сколько карт активно у игрока (в т.ч. стартовая колода) — для масштабирования эффектов вроде карты 5. +function ____exports.GetPlayerActiveCardCount(self, playerId) + local player = PlayerResource:GetPlayer(playerId) + if player and player.cardSystem then + return player.cardSystem:GetActiveCardCount() + end + return 0 +end +--- Активные карты игрока без врождённых из колоды. +function ____exports.GetPlayerActiveCardCountExcludingInherent(self, playerId) + local player = PlayerResource:GetPlayer(playerId) + if player and player.cardSystem then + return player.cardSystem:GetActiveCardCountExcludingInherent() + end + return 0 +end +--- Добавить карту в пул игрока +function ____exports.AddCardToPlayerPool(self, playerId, cardId, weight, count, source) + if weight == nil then + weight = 5 + end + if count == nil then + count = 1 + end + if source == nil then + source = "manual_pool_add" + end + local player = PlayerResource:GetPlayer(playerId) + if player and player.cardSystem then + player.cardSystem:AddCardToPoolCount(cardId, weight, count, source) + else + end +end +--- Замешать шлак (86) в пул игрока. +function ____exports.AddSlagToPlayerPool(self, playerId, weight, count, source) + if weight == nil then + weight = 1 + end + if count == nil then + count = 1 + end + if source == nil then + source = "card_85_fishing_slag" + end + local player = PlayerResource:GetPlayer(playerId) + if player and player.cardSystem then + player.cardSystem:AddSlagToPool(weight, count, source) + end +end +--- Удалить карту из пула игрока +function ____exports.RemoveCardFromPlayerPool(self, playerId, cardId) + local player = PlayerResource:GetPlayer(playerId) + if player and player.cardSystem then + player.cardSystem:RemoveCardFromPool(cardId) + else + end +end +--- Проверить, есть ли карта в пуле игрока +function ____exports.HasCardInPlayerPool(self, playerId, cardId) + local player = PlayerResource:GetPlayer(playerId) + if player and player.cardSystem then + return player.cardSystem:HasCardInPool(cardId) + end + return false +end +--- Получить размер пула игрока +function ____exports.GetPlayerPoolSize(self, playerId) + local player = PlayerResource:GetPlayer(playerId) + if player and player.cardSystem then + return player.cardSystem:GetPoolSize() + end + return 0 +end +--- Получить все карты в пуле игрока +function ____exports.GetPlayerPoolCards(self, playerId) + local player = PlayerResource:GetPlayer(playerId) + if player and player.cardSystem then + return player.cardSystem:GetPoolCards() + end + return {} +end +--- Очистить пул игрока +function ____exports.ClearPlayerPool(self, playerId) + local player = PlayerResource:GetPlayer(playerId) + if player and player.cardSystem then + player.cardSystem:ClearPool() + else + end +end +--- Удалить случайные карты из пула игрока +function ____exports.RemoveRandomCardsFromPlayerPool(self, playerId, count) + local player = PlayerResource:GetPlayer(playerId) + if player and player.cardSystem then + return player.cardSystem:RemoveRandomCardsFromPool(count) + else + return {} + end +end +--- Добавить гарантированные карты определенного качества в реролы для игрока +function ____exports.AddPlayerGuaranteedQualityRerolls(self, playerId, quality, rerollsCount, cardsPerReroll, fallbackBehavior) + if fallbackBehavior == nil then + fallbackBehavior = "skip" + end + print("[AddPlayerGuaranteedQualityRerolls] ===== ДОБАВЛЕНИЕ ГАРАНТИРОВАННЫХ КАРТ =====") + print("[AddPlayerGuaranteedQualityRerolls] Игрок: " .. tostring(playerId)) + print("[AddPlayerGuaranteedQualityRerolls] Качество: " .. tostring(quality)) + print("[AddPlayerGuaranteedQualityRerolls] Количество реролов: " .. tostring(rerollsCount)) + print("[AddPlayerGuaranteedQualityRerolls] Карт за рерол: " .. tostring(cardsPerReroll)) + print("[AddPlayerGuaranteedQualityRerolls] Поведение при отсутствии: " .. fallbackBehavior) + local player = PlayerResource:GetPlayer(playerId) + if not player then + print(("[AddPlayerGuaranteedQualityRerolls] ❌ Игрок " .. tostring(playerId)) .. " не найден") + return + end + local hero = player:GetAssignedHero() + if not hero then + print(("[AddPlayerGuaranteedQualityRerolls] ❌ Герой игрока " .. tostring(playerId)) .. " не найден") + return + end + if not player.cardSystem then + print(("[AddPlayerGuaranteedQualityRerolls] ⚠️ CardSystem не найден для игрока " .. tostring(playerId)) .. ", создаю...") + print("[AddPlayerGuaranteedQualityRerolls] Тип player: " .. __TS__TypeOf(player)) + print("[AddPlayerGuaranteedQualityRerolls] player.cardSystem до создания: " .. tostring(player.cardSystem)) + player.cardSystem = __TS__New(____exports.CardSystem, playerId) + print(("[AddPlayerGuaranteedQualityRerolls] ✅ CardSystem создан для игрока " .. tostring(playerId)) .. "!") + print("[AddPlayerGuaranteedQualityRerolls] player.cardSystem после создания: " .. tostring(player.cardSystem)) + else + print("[AddPlayerGuaranteedQualityRerolls] ✅ CardSystem уже существует для игрока " .. tostring(playerId)) + end + print("[AddPlayerGuaranteedQualityRerolls] ✅ Все проверки пройдены, добавляем гарантированные карты") + player.cardSystem:AddGuaranteedQualityRerolls(quality, rerollsCount, cardsPerReroll, fallbackBehavior) + print("[AddPlayerGuaranteedQualityRerolls] ================================================") +end +--- Получить количество оставшихся реролов с гарантированными картами определенного качества для игрока +function ____exports.GetPlayerGuaranteedQualityRerolls(self, playerId, quality) + local player = PlayerResource:GetPlayer(playerId) + if not player then + return 0 + end + local hero = player:GetAssignedHero() + if not hero then + return 0 + end + if not player.cardSystem then + return 0 + end + return player.cardSystem:GetGuaranteedQualityRerolls(quality) +end +--- Получить количество карт в каждом рероле с гарантированными картами определенного качества для игрока +function ____exports.GetPlayerGuaranteedCardsPerReroll(self, playerId, quality) + local player = PlayerResource:GetPlayer(playerId) + if not player then + return 0 + end + local hero = player:GetAssignedHero() + if not hero then + return 0 + end + if not player.cardSystem then + return 0 + end + return player.cardSystem:GetGuaranteedCardsPerReroll(quality) +end +--- Получить все гарантированные карты качества для игрока +function ____exports.GetPlayerAllGuaranteedQualityRerolls(self, playerId) + local player = PlayerResource:GetPlayer(playerId) + if not player then + return {} + end + local hero = player:GetAssignedHero() + if not hero then + return {} + end + if not player.cardSystem then + return {} + end + return player.cardSystem:GetAllGuaranteedQualityRerolls() +end +--- Очистить все гарантированные карты качества для игрока +function ____exports.ClearPlayerGuaranteedQualityRerolls(self, playerId) + local player = PlayerResource:GetPlayer(playerId) + if not player then + return + end + local hero = player:GetAssignedHero() + if not hero then + return + end + if not player.cardSystem then + return + end + player.cardSystem:ClearGuaranteedQualityRerolls() +end +--- Игрок с «Буйным ростом» (58) не получает стандартный выбор карт на рассвете. +function ____exports.shouldPlayerSkipDawnCardSelection(self, playerId) + local player = PlayerResource:GetPlayer(playerId) + if not (player and player.cardSystem) then + return false + end + return player.cardSystem:SkipsDawnCardSelection() +end +--- Показать выбор карт игроку с источником +function ____exports.ShowCardSelectionToPlayer(self, playerId, count, source, qualityFilter) + if source == nil then + source = "unknown" + end + local player = PlayerResource:GetPlayer(playerId) + if not player then + return + end + local hero = player:GetAssignedHero() + if not hero then + return + end + if not player.cardSystem then + player.cardSystem = __TS__New(____exports.CardSystem, playerId) + end + player.cardSystem:ShowCardSelection(count, source, qualityFilter) +end +--- Выбор карт, среди которых гарантированно есть `forcedCardId` (остальные слоты — из пула / 404). +function ____exports.ShowCardSelectionWithForcedCardToPlayer(self, playerId, forcedCardId, count, source) + if count == nil then + count = 3 + end + if source == nil then + source = "forced_card" + end + local player = PlayerResource:GetPlayer(playerId) + if not player then + return + end + local hero = player:GetAssignedHero() + if not hero then + return + end + if not player.cardSystem then + player.cardSystem = __TS__New(____exports.CardSystem, playerId) + end + player.cardSystem:ShowCardSelectionWithForcedCard(forcedCardId, count, source) +end +--- Показ выбора карт из фиксированного списка id. +function ____exports.ShowCardSelectionExactOptionsToPlayer(self, playerId, optionIds, source) + if source == nil then + source = "exact_options" + end + local player = PlayerResource:GetPlayer(playerId) + if not player or not player.cardSystem then + print(("[ShowCardSelectionExactOptionsToPlayer] Игрок " .. tostring(playerId)) .. " не найден или не имеет cardSystem") + return + end + player.cardSystem:ShowCardSelectionExactOptions(optionIds, source) +end +--- Получить размер очереди выборов карт игрока +function ____exports.GetPlayerCardSelectionQueueSize(self, playerId) + local player = PlayerResource:GetPlayer(playerId) + if player and player.cardSystem then + return player.cardSystem:GetCardSelectionQueueSize() + end + return 0 +end +--- Очистить очередь выборов карт игрока +function ____exports.ClearPlayerCardSelectionQueue(self, playerId) + local player = PlayerResource:GetPlayer(playerId) + if player and player.cardSystem then + player.cardSystem:ClearCardSelectionQueue() + else + end +end +--- Проверить, показывается ли выбор карт игроку +function ____exports.IsPlayerShowingCardSelection(self, playerId) + local player = PlayerResource:GetPlayer(playerId) + if player and player.cardSystem then + return player.cardSystem:IsShowingCardSelection() + end + return false +end +--- Принудительно выполнить рерол карт для игрока (для тестирования) +function ____exports.ForceRerollCards(self, playerId) + local player = PlayerResource:GetPlayer(playerId) + if player and player.cardSystem then + player.cardSystem:RerollCards(playerId) + else + end +end +--- Установить фильтр качества для рерола игрока +function ____exports.SetPlayerRerollQualityFilter(self, playerId, qualityFilter) + local player = PlayerResource:GetPlayer(playerId) + if player and player.cardSystem then + player.cardSystem:SetRerollQualityFilter(qualityFilter) + else + end +end +--- Получить фильтр качества для рерола игрока +function ____exports.GetPlayerRerollQualityFilter(self, playerId) + local player = PlayerResource:GetPlayer(playerId) + if player and player.cardSystem then + return player.cardSystem:GetRerollQualityFilter() + end + return nil +end +return ____exports diff --git a/scripts/vscripts/cards/examples/card_1.lua b/scripts/vscripts/cards/examples/card_1.lua new file mode 100644 index 0000000..a1157ad --- /dev/null +++ b/scripts/vscripts/cards/examples/card_1.lua @@ -0,0 +1,84 @@ +local ____lualib = require("lualib_bundle") +local __TS__Class = ____lualib.__TS__Class +local __TS__ClassExtends = ____lualib.__TS__ClassExtends +local __TS__Decorate = ____lualib.__TS__Decorate +local ____exports = {} +local ____CardSystem = require("cards.CardSystem") +local CardBase = ____CardSystem.CardBase +local RegisterCard = ____CardSystem.RegisterCard +local ____CardBaseModifier = require("cards.CardBaseModifier") +local CardBaseModifier = ____CardBaseModifier.CardBaseModifier +local ____dota_ts_adapter = require("lib.dota_ts_adapter") +local registerModifier = ____dota_ts_adapter.registerModifier +local ____vampirism = require("utils.vampirism") +local getPhysicalVampirism = ____vampirism.getPhysicalVampirism +local addPhysicalVampirism = ____vampirism.addPhysicalVampirism +local CARD_ID = 1 +____exports.card_1 = __TS__Class() +local card_1 = ____exports.card_1 +card_1.name = "card_1" +card_1.____file_path = "scripts/vscripts/cards/examples/card_1.lua" +__TS__ClassExtends(card_1, CardBase) +function card_1.prototype.GetModifierName(self) + return "modifier_card_1" +end +card_1 = __TS__Decorate(card_1, card_1, {RegisterCard}, {kind = "class", name = "card_1"}) +____exports.card_1 = card_1 +____exports.modifier_card_1 = __TS__Class() +local modifier_card_1 = ____exports.modifier_card_1 +modifier_card_1.name = "modifier_card_1" +modifier_card_1.____file_path = "scripts/vscripts/cards/examples/card_1.lua" +__TS__ClassExtends(modifier_card_1, CardBaseModifier) +function modifier_card_1.prototype.DeclareFunctions(self) + return {MODIFIER_PROPERTY_EXTRA_HEALTH_PERCENTAGE, MODIFIER_PROPERTY_DAMAGEOUTGOING_PERCENTAGE, MODIFIER_PROPERTY_TOOLTIP} +end +function modifier_card_1.prototype.OnCustomCreated(self, params) + if not IsServer() then + return + end + local attacker = self:GetCaster() + if not attacker or not IsValidEntity(attacker) then + return + end + local vampirismBonus = self:getScaledCardValue("vampirism_bonus", 10, CARD_ID) + addPhysicalVampirism(nil, attacker, vampirismBonus) +end +function modifier_card_1.prototype.GetModifierExtraHealthPercentage(self) + return -self:getScaledCardValue("max_health_penalty_pct", 25, CARD_ID) +end +function modifier_card_1.prototype.GetModifierDamageOutgoing_Percentage(self, event) + local attacker = self:GetCaster() + if not attacker or not IsValidEntity(attacker) then + return 0 + end + local vampirism = getPhysicalVampirism(nil, attacker) + if vampirism <= 0 then + return 0 + end + return vampirism * self:getScaledCardValue("damage_multiplier", 2.5, CARD_ID) +end +function modifier_card_1.prototype.OnTooltip(self) + return self:GetModifierDamageOutgoing_Percentage({}) +end +function modifier_card_1.prototype.OnCustomRefresh(self, _params) + if not IsServer() then + return + end + local attacker = self:GetParent() + if not attacker or not IsValidEntity(attacker) then + return + end + if not attacker or not IsValidEntity(attacker) then + return + end + local vampirismBonus = self:getScaledCardValue("vampirism_bonus", 10, CARD_ID) + addPhysicalVampirism(nil, attacker, vampirismBonus) +end +modifier_card_1 = __TS__Decorate( + modifier_card_1, + modifier_card_1, + {registerModifier(nil)}, + {kind = "class", name = "modifier_card_1"} +) +____exports.modifier_card_1 = modifier_card_1 +return ____exports diff --git a/scripts/vscripts/cards/examples/card_10.lua b/scripts/vscripts/cards/examples/card_10.lua new file mode 100644 index 0000000..d7ede55 --- /dev/null +++ b/scripts/vscripts/cards/examples/card_10.lua @@ -0,0 +1,116 @@ +local ____lualib = require("lualib_bundle") +local __TS__Class = ____lualib.__TS__Class +local __TS__ClassExtends = ____lualib.__TS__ClassExtends +local __TS__Decorate = ____lualib.__TS__Decorate +local ____exports = {} +local ____CardSystem = require("cards.CardSystem") +local CardBase = ____CardSystem.CardBase +local RegisterCard = ____CardSystem.RegisterCard +local ____CardBaseModifier = require("cards.CardBaseModifier") +local CardBaseModifier = ____CardBaseModifier.CardBaseModifier +local ____dota_ts_adapter = require("lib.dota_ts_adapter") +local registerModifier = ____dota_ts_adapter.registerModifier +local ____modifier_general_fired = require("abilities.modifiers.modifier_general_fired") +local modifier_general_fired = ____modifier_general_fired.modifier_general_fired +local CARD_ID = 10 +local DEBUFF_NAME = "modifier_card_10_debuff" +____exports.card_10 = __TS__Class() +local card_10 = ____exports.card_10 +card_10.name = "card_10" +card_10.____file_path = "scripts/vscripts/cards/examples/card_10.lua" +__TS__ClassExtends(card_10, CardBase) +function card_10.prototype.GetModifierName(self) + return "modifier_card_10" +end +card_10 = __TS__Decorate(card_10, card_10, {RegisterCard}, {kind = "class", name = "card_10"}) +____exports.card_10 = card_10 +____exports.modifier_card_10 = __TS__Class() +local modifier_card_10 = ____exports.modifier_card_10 +modifier_card_10.name = "modifier_card_10" +modifier_card_10.____file_path = "scripts/vscripts/cards/examples/card_10.lua" +__TS__ClassExtends(modifier_card_10, CardBaseModifier) +function modifier_card_10.prototype.getValue(self, key, fallback) + return self:getCardValue(key, fallback, CARD_ID) +end +function modifier_card_10.prototype.DeclareFunctions(self) + return {MODIFIER_EVENT_ON_ATTACK_LANDED, MODIFIER_PROPERTY_TOOLTIP} +end +function modifier_card_10.prototype.OnAttackLanded(self, event) + if not IsServer() then + return + end + local attacker = self:GetParent() + if not attacker or not IsValidEntity(attacker) or event.attacker ~= attacker then + return + end + local target = event.target + if not target or not IsValidEntity(target) or not target:IsAlive() then + return + end + if target:GetTeamNumber() == attacker:GetTeamNumber() then + return + end + local duration = self:getValue("debuff_duration", 3.5) + target:AddNewModifier( + attacker, + self:GetAbility(), + DEBUFF_NAME, + {duration = duration} + ) +end +modifier_card_10 = __TS__Decorate( + modifier_card_10, + modifier_card_10, + {registerModifier(nil)}, + {kind = "class", name = "modifier_card_10"} +) +____exports.modifier_card_10 = modifier_card_10 +____exports.modifier_card_10_debuff = __TS__Class() +local modifier_card_10_debuff = ____exports.modifier_card_10_debuff +modifier_card_10_debuff.name = "modifier_card_10_debuff" +modifier_card_10_debuff.____file_path = "scripts/vscripts/cards/examples/card_10.lua" +__TS__ClassExtends(modifier_card_10_debuff, CardBaseModifier) +function modifier_card_10_debuff.prototype.getValue(self, key, fallback) + return self:getCardValue(key, fallback, CARD_ID) +end +function modifier_card_10_debuff.prototype.IsDebuff(self) + return true +end +function modifier_card_10_debuff.prototype.IsPurgable(self) + return true +end +function modifier_card_10_debuff.prototype.DeclareFunctions(self) + return {MODIFIER_PROPERTY_INCOMING_DAMAGE_PERCENTAGE, MODIFIER_PROPERTY_TOOLTIP} +end +function modifier_card_10_debuff.prototype.GetModifierIncomingDamage_Percentage(self, event) + if not IsServer() then + return 0 + end + local ownerHero = self:GetCaster() + if not ownerHero or not event.attacker or event.attacker ~= ownerHero then + return 0 + end + local target = self:GetParent() + local firedModifier = target:FindModifierByName(modifier_general_fired.name) + local firedStacks = firedModifier and firedModifier:GetStackCount() or 0 + if firedStacks <= 0 then + return 0 + end + local cardMod = ownerHero:FindModifierByName("modifier_card_10") + local copies = math.max( + 1, + math.floor(cardMod and cardMod:GetStackCount() or 0) + ) + local incomingPerStackPct = self:getValue("incoming_damage_per_fired_stack_pct", 1) + local maxBonusPct = self:getValue("max_incoming_damage_bonus_pct", 80) * copies + local totalBonusPct = firedStacks * incomingPerStackPct * copies + return math.min(totalBonusPct, maxBonusPct) +end +modifier_card_10_debuff = __TS__Decorate( + modifier_card_10_debuff, + modifier_card_10_debuff, + {registerModifier(nil)}, + {kind = "class", name = "modifier_card_10_debuff"} +) +____exports.modifier_card_10_debuff = modifier_card_10_debuff +return ____exports diff --git a/scripts/vscripts/cards/examples/card_11.lua b/scripts/vscripts/cards/examples/card_11.lua new file mode 100644 index 0000000..25fbe25 --- /dev/null +++ b/scripts/vscripts/cards/examples/card_11.lua @@ -0,0 +1,84 @@ +local ____lualib = require("lualib_bundle") +local __TS__Class = ____lualib.__TS__Class +local __TS__ClassExtends = ____lualib.__TS__ClassExtends +local __TS__Decorate = ____lualib.__TS__Decorate +local ____exports = {} +local ____CardSystem = require("cards.CardSystem") +local CardBase = ____CardSystem.CardBase +local RegisterCard = ____CardSystem.RegisterCard +local ____CardBaseModifier = require("cards.CardBaseModifier") +local CardBaseModifier = ____CardBaseModifier.CardBaseModifier +local ____dota_ts_adapter = require("lib.dota_ts_adapter") +local registerModifier = ____dota_ts_adapter.registerModifier +____exports.card_11 = __TS__Class() +local card_11 = ____exports.card_11 +card_11.name = "card_11" +card_11.____file_path = "scripts/vscripts/cards/examples/card_11.lua" +__TS__ClassExtends(card_11, CardBase) +function card_11.prototype.GetModifierName(self) + return "modifier_card_11" +end +card_11 = __TS__Decorate(card_11, card_11, {RegisterCard}, {kind = "class", name = "card_11"}) +____exports.card_11 = card_11 +____exports.modifier_card_11 = __TS__Class() +local modifier_card_11 = ____exports.modifier_card_11 +modifier_card_11.name = "modifier_card_11" +modifier_card_11.____file_path = "scripts/vscripts/cards/examples/card_11.lua" +__TS__ClassExtends(modifier_card_11, CardBaseModifier) +function modifier_card_11.prototype.getValue(self, key, fallback) + return self:getCardValue(key, fallback, 11) +end +function modifier_card_11.prototype.getPrimaryBonusPerLevel(self) + return self:getValue("primary_attribute_bonus_per_level", 2) +end +function modifier_card_11.prototype.getUniversalAllBonusPerLevel(self) + return self:getValue("universal_all_bonus_per_level", 1) +end +function modifier_card_11.prototype.getPrimaryAttribute(self) + local parent = self:GetParent() + return parent:GetPrimaryAttribute() +end +function modifier_card_11.prototype.DeclareFunctions(self) + return {MODIFIER_PROPERTY_STATS_STRENGTH_BONUS, MODIFIER_PROPERTY_STATS_AGILITY_BONUS, MODIFIER_PROPERTY_STATS_INTELLECT_BONUS, MODIFIER_PROPERTY_TOOLTIP} +end +function modifier_card_11.prototype.GetModifierBonusStats_Strength(self) + local level = self:GetParent():GetLevel() + local primaryAttribute = self:getPrimaryAttribute() + if primaryAttribute == DOTA_ATTRIBUTE_ALL then + return self:getUniversalAllBonusPerLevel() * level * self:getCardCopies() + end + if primaryAttribute == DOTA_ATTRIBUTE_STRENGTH then + return self:getPrimaryBonusPerLevel() * level * self:getCardCopies() + end + return 0 +end +function modifier_card_11.prototype.GetModifierBonusStats_Agility(self) + local level = self:GetParent():GetLevel() + local primaryAttribute = self:getPrimaryAttribute() + if primaryAttribute == DOTA_ATTRIBUTE_ALL then + return self:getUniversalAllBonusPerLevel() * level * self:getCardCopies() + end + if primaryAttribute == DOTA_ATTRIBUTE_AGILITY then + return self:getPrimaryBonusPerLevel() * level * self:getCardCopies() + end + return 0 +end +function modifier_card_11.prototype.GetModifierBonusStats_Intellect(self) + local level = self:GetParent():GetLevel() + local primaryAttribute = self:getPrimaryAttribute() + if primaryAttribute == DOTA_ATTRIBUTE_ALL then + return self:getUniversalAllBonusPerLevel() * level * self:getCardCopies() + end + if primaryAttribute == DOTA_ATTRIBUTE_INTELLECT then + return self:getPrimaryBonusPerLevel() * level * self:getCardCopies() + end + return 0 +end +modifier_card_11 = __TS__Decorate( + modifier_card_11, + modifier_card_11, + {registerModifier(nil)}, + {kind = "class", name = "modifier_card_11"} +) +____exports.modifier_card_11 = modifier_card_11 +return ____exports diff --git a/scripts/vscripts/cards/examples/card_12.lua b/scripts/vscripts/cards/examples/card_12.lua new file mode 100644 index 0000000..f45605d --- /dev/null +++ b/scripts/vscripts/cards/examples/card_12.lua @@ -0,0 +1,214 @@ +local ____lualib = require("lualib_bundle") +local __TS__Class = ____lualib.__TS__Class +local __TS__ClassExtends = ____lualib.__TS__ClassExtends +local __TS__Decorate = ____lualib.__TS__Decorate +local __TS__StringIncludes = ____lualib.__TS__StringIncludes +local ____exports = {} +local ____CardSystem = require("cards.CardSystem") +local CardBase = ____CardSystem.CardBase +local RegisterCard = ____CardSystem.RegisterCard +local ____CardBaseModifier = require("cards.CardBaseModifier") +local CardBaseModifier = ____CardBaseModifier.CardBaseModifier +local ____dota_ts_adapter = require("lib.dota_ts_adapter") +local registerModifier = ____dota_ts_adapter.registerModifier +local ____card_data = require("cards.card_data") +local CARD_DATABASE = ____card_data.CARD_DATABASE +local CARD_ID = 12 +local MARK_MODIFIER = "modifier_card_12_marked" +____exports.card_12 = __TS__Class() +local card_12 = ____exports.card_12 +card_12.name = "card_12" +card_12.____file_path = "scripts/vscripts/cards/examples/card_12.lua" +__TS__ClassExtends(card_12, CardBase) +function card_12.prototype.GetModifierName(self) + return "modifier_card_12" +end +card_12 = __TS__Decorate(card_12, card_12, {RegisterCard}, {kind = "class", name = "card_12"}) +____exports.card_12 = card_12 +____exports.modifier_card_12 = __TS__Class() +local modifier_card_12 = ____exports.modifier_card_12 +modifier_card_12.name = "modifier_card_12" +modifier_card_12.____file_path = "scripts/vscripts/cards/examples/card_12.lua" +__TS__ClassExtends(modifier_card_12, CardBaseModifier) +function modifier_card_12.prototype.IsPurgable(self) + return false +end +function modifier_card_12.prototype.getValues(self) + local ____opt_0 = CARD_DATABASE[CARD_ID] + return ____opt_0 and ____opt_0.values or ({}) +end +function modifier_card_12.prototype.getValue(self, key, fallback) + local value = self:getValues()[key] + if value == nil or value == nil then + return fallback + end + return value +end +function modifier_card_12.prototype.getMarkedTarget(self) + if self.markedTargetEntIndex == nil then + return nil + end + local target = EntIndexToHScript(self.markedTargetEntIndex) + if not target or not IsValidEntity(target) or not target:IsAlive() then + return nil + end + return target +end +function modifier_card_12.prototype.OnCustomCreated(self, params) + if not IsServer() then + return + end + self:StartIntervalThink(self:getValue("scan_interval", 8)) + self:OnIntervalThink() +end +function modifier_card_12.prototype.OnCustomRefresh(self, params) + if not IsServer() then + return + end + self:StartIntervalThink(self:getValue("scan_interval", 8)) +end +function modifier_card_12.prototype.OnIntervalThink(self) + if not IsServer() then + return + end + local parent = self:GetParent() + if not parent or not parent:IsAlive() then + return + end + local radius = self:getValue("scan_radius", 900) + local markDuration = self:getValue("mark_duration", 5) + local enemies = FindUnitsInRadius( + parent:GetTeamNumber(), + parent:GetAbsOrigin(), + nil, + radius, + DOTA_UNIT_TARGET_TEAM_ENEMY, + DOTA_UNIT_TARGET_HERO + DOTA_UNIT_TARGET_BASIC, + DOTA_UNIT_TARGET_FLAG_NONE, + FIND_CLOSEST, + false + ) + local bestTarget = nil + local bestMaxHealth = -1 + for ____, enemy in ipairs(enemies) do + do + if not enemy or not enemy:IsAlive() then + goto __continue17 + end + local maxHealth = enemy:GetMaxHealth() + if maxHealth > bestMaxHealth then + bestMaxHealth = maxHealth + bestTarget = enemy + end + end + ::__continue17:: + end + if not bestTarget then + return + end + self.markedTargetEntIndex = bestTarget:entindex() + bestTarget:AddNewModifier( + parent, + getModifierSourceAbility(nil, parent), + MARK_MODIFIER, + {duration = markDuration} + ) +end +function modifier_card_12.prototype.DeclareFunctions(self) + return {MODIFIER_EVENT_ON_DEATH, MODIFIER_PROPERTY_TOOLTIP} +end +function modifier_card_12.prototype.OnDeath(self, event) + if not IsServer() then + return + end + local parent = self:GetParent() + if not parent or not event.unit then + return + end + local markedTarget = self:getMarkedTarget() + if markedTarget and event.unit == markedTarget then + self.markedTargetEntIndex = nil + end + if event.attacker ~= parent or not markedTarget or event.unit ~= markedTarget then + return + end + local copies = self:getCardCopies() + local healPct = self:getValue("on_kill_heal_pct", 6) * copies + local manaGain = self:getValue("on_kill_mana", 35) * copies + local healAmount = parent:GetMaxHealth() * (healPct / 100) + if healAmount > 0 then + parent:Heal(healAmount, nil) + end + if manaGain > 0 then + parent:GiveMana(manaGain) + end +end +modifier_card_12 = __TS__Decorate( + modifier_card_12, + modifier_card_12, + {registerModifier(nil)}, + {kind = "class", name = "modifier_card_12"} +) +____exports.modifier_card_12 = modifier_card_12 +____exports.modifier_card_12_marked = __TS__Class() +local modifier_card_12_marked = ____exports.modifier_card_12_marked +modifier_card_12_marked.name = "modifier_card_12_marked" +modifier_card_12_marked.____file_path = "scripts/vscripts/cards/examples/card_12.lua" +__TS__ClassExtends(modifier_card_12_marked, CardBaseModifier) +function modifier_card_12_marked.prototype.isBossUnit(self, unit) + local unitName = tostring(unit:GetUnitName() or "") + return __TS__StringIncludes( + string.lower(unitName), + "boss" + ) +end +function modifier_card_12_marked.prototype.getValue(self, key, fallback) + return self:getCardValue(key, fallback, CARD_ID) +end +function modifier_card_12_marked.prototype.IsDebuff(self) + return true +end +function modifier_card_12_marked.prototype.IsPurgable(self) + return false +end +function modifier_card_12_marked.prototype.DeclareFunctions(self) + return {MODIFIER_PROPERTY_INCOMING_DAMAGE_PERCENTAGE, MODIFIER_PROPERTY_TOOLTIP} +end +function modifier_card_12_marked.prototype.GetModifierIncomingDamage_Percentage(self, event) + if not IsServer() then + return 0 + end + local target = self:GetParent() + local ownerHero = self:GetCaster() + if not target or not ownerHero then + return 0 + end + if not event.attacker or event.attacker ~= ownerHero then + return 0 + end + local ownerMod = ownerHero:FindModifierByName("modifier_card_12") + local copies = math.max( + 1, + math.floor(ownerMod and ownerMod:GetStackCount() or 0) + ) + local bonusPct = self:getValue("bonus_damage_pct", 22) * copies + if self:isBossUnit(target) then + local bossPenaltyPct = self:getValue("boss_penalty_pct", 50) + bonusPct = bonusPct * (1 - bossPenaltyPct / 100) + end + return bonusPct +end +function modifier_card_12_marked.prototype.GetEffectName(self) + return "particles/units/heroes/hero_skeletonking/wraith_king_curse_overhead_skull.vpcf" +end +function modifier_card_12_marked.prototype.GetEffectAttachType(self) + return PATTACH_OVERHEAD_FOLLOW +end +modifier_card_12_marked = __TS__Decorate( + modifier_card_12_marked, + modifier_card_12_marked, + {registerModifier(nil)}, + {kind = "class", name = "modifier_card_12_marked"} +) +____exports.modifier_card_12_marked = modifier_card_12_marked +return ____exports diff --git a/scripts/vscripts/cards/examples/card_13.lua b/scripts/vscripts/cards/examples/card_13.lua new file mode 100644 index 0000000..cf4886b --- /dev/null +++ b/scripts/vscripts/cards/examples/card_13.lua @@ -0,0 +1,135 @@ +local ____lualib = require("lualib_bundle") +local __TS__Class = ____lualib.__TS__Class +local __TS__ClassExtends = ____lualib.__TS__ClassExtends +local __TS__Decorate = ____lualib.__TS__Decorate +local ____exports = {} +local ____CardSystem = require("cards.CardSystem") +local CardBase = ____CardSystem.CardBase +local RegisterCard = ____CardSystem.RegisterCard +local ____CardBaseModifier = require("cards.CardBaseModifier") +local CardBaseModifier = ____CardBaseModifier.CardBaseModifier +local ____dota_ts_adapter = require("lib.dota_ts_adapter") +local registerModifier = ____dota_ts_adapter.registerModifier +local ____modifier_general_fired = require("abilities.modifiers.modifier_general_fired") +local modifier_general_fired = ____modifier_general_fired.modifier_general_fired +local ____luck = require("utils.luck") +local rollLuckChance = ____luck.rollLuckChance +local CARD_ID = 13 +____exports.card_13 = __TS__Class() +local card_13 = ____exports.card_13 +card_13.name = "card_13" +card_13.____file_path = "scripts/vscripts/cards/examples/card_13.lua" +__TS__ClassExtends(card_13, CardBase) +function card_13.prototype.GetModifierName(self) + return "modifier_card_13" +end +card_13 = __TS__Decorate(card_13, card_13, {RegisterCard}, {kind = "class", name = "card_13"}) +____exports.card_13 = card_13 +____exports.modifier_card_13 = __TS__Class() +local modifier_card_13 = ____exports.modifier_card_13 +modifier_card_13.name = "modifier_card_13" +modifier_card_13.____file_path = "scripts/vscripts/cards/examples/card_13.lua" +__TS__ClassExtends(modifier_card_13, CardBaseModifier) +function modifier_card_13.prototype.Precache(self, context) + PrecacheResource("particle", "particles/units/heroes/hero_techies/techies_blast_off.vpcf", context) +end +function modifier_card_13.prototype.DeclareFunctions(self) + return {MODIFIER_EVENT_ON_ATTACK_LANDED, MODIFIER_PROPERTY_TOOLTIP} +end +function modifier_card_13.prototype.getValue(self, key, fallback) + return self:getCardValue(key, fallback, CARD_ID) +end +function modifier_card_13.prototype.addFiredStacks(self, target, attacker, stacks) + if stacks <= 0 then + local ____opt_0 = target:FindModifierByName(modifier_general_fired.name) + return ____opt_0 and ____opt_0:GetStackCount() or 0 + end + local firedModifier = target:AddNewModifier( + attacker, + getModifierSourceAbility(nil, attacker), + modifier_general_fired.name, + {} + ) + if not firedModifier then + return 0 + end + do + local i = 0 + while i < stacks do + firedModifier:IncrementStackCount() + i = i + 1 + end + end + return firedModifier:GetStackCount() +end +function modifier_card_13.prototype.OnAttackLanded(self, event) + if not IsServer() then + return + end + local attacker = self:GetParent() + if not attacker or not IsValidEntity(attacker) or event.attacker ~= attacker then + return + end + local target = event.target + if not target or not IsValidEntity(target) or not target:IsAlive() then + return + end + if target:GetTeamNumber() == attacker:GetTeamNumber() then + return + end + local copies = self:getCardCopies() + local stacksOnHit = self:getValue("fired_stacks_on_hit", 3) * copies + local firedStacks = self:addFiredStacks(target, attacker, stacksOnHit) + local chancePct = self:getValue("explosion_chance_pct", 18) + local ____attacker_IsRealHero_result_2 + if attacker:IsRealHero() then + ____attacker_IsRealHero_result_2 = rollLuckChance(nil, attacker, chancePct / 100) + else + ____attacker_IsRealHero_result_2 = RollPercentage(chancePct) + end + local exploded = ____attacker_IsRealHero_result_2 + if not exploded then + return + end + local radius = self:getValue("explosion_radius", 175) + local damagePerStack = self:getValue("explosion_damage_per_stack", 10) + local damage = (firedStacks * damagePerStack * copies + attacker:GetAverageTrueAttackDamage(target)) * copies + if damage <= 0 then + return + end + local particleName = "particles/units/heroes/hero_techies/techies_blast_off.vpcf" + local particle = ParticleManager:CreateParticle(particleName, PATTACH_WORLDORIGIN, nil) + ParticleManager:SetParticleControl( + particle, + 0, + target:GetAbsOrigin() + ) + ParticleManager:SetParticleControl( + particle, + 1, + Vector(radius, 0, 0) + ) + ParticleManager:ReleaseParticleIndex(particle) + local enemies = FindUnitsInRadius( + attacker:GetTeamNumber(), + target:GetAbsOrigin(), + nil, + radius, + DOTA_UNIT_TARGET_TEAM_ENEMY, + bit.bor(DOTA_UNIT_TARGET_HERO, DOTA_UNIT_TARGET_BASIC), + DOTA_UNIT_TARGET_FLAG_NONE, + FIND_ANY_ORDER, + false + ) + for ____, enemy in ipairs(enemies) do + ApplyDamage({victim = enemy, attacker = attacker, damage = damage, damage_type = DAMAGE_TYPE_MAGICAL}) + end +end +modifier_card_13 = __TS__Decorate( + modifier_card_13, + modifier_card_13, + {registerModifier(nil)}, + {kind = "class", name = "modifier_card_13"} +) +____exports.modifier_card_13 = modifier_card_13 +return ____exports diff --git a/scripts/vscripts/cards/examples/card_14.lua b/scripts/vscripts/cards/examples/card_14.lua new file mode 100644 index 0000000..8a70589 --- /dev/null +++ b/scripts/vscripts/cards/examples/card_14.lua @@ -0,0 +1,225 @@ +local ____lualib = require("lualib_bundle") +local __TS__Delete = ____lualib.__TS__Delete +local __TS__Class = ____lualib.__TS__Class +local __TS__ClassExtends = ____lualib.__TS__ClassExtends +local __TS__Decorate = ____lualib.__TS__Decorate +local ____exports = {} +local ____CardSystem = require("cards.CardSystem") +local CardBase = ____CardSystem.CardBase +local RegisterCard = ____CardSystem.RegisterCard +local ____CardBaseModifier = require("cards.CardBaseModifier") +local CardBaseModifier = ____CardBaseModifier.CardBaseModifier +local ____dota_ts_adapter = require("lib.dota_ts_adapter") +local registerModifier = ____dota_ts_adapter.registerModifier +local ____modifier_general_fired = require("abilities.modifiers.modifier_general_fired") +local modifier_general_fired = ____modifier_general_fired.modifier_general_fired +local ____luck = require("utils.luck") +local rollLuckChance = ____luck.rollLuckChance +local CARD_ID = 14 +--- Общая глубина рикошета на герое: при нескольких копиях карты каждый modifier_card_14 — отдельный инстанс, иначе второй снова прокает от рикошетного удара. +local sharedRicochetDepthByHero = {} +local function getSharedCard14RicochetDepth(self, hero) + return sharedRicochetDepthByHero[hero:entindex()] or 0 +end +local function addSharedCard14RicochetDepth(self, hero) + local k = hero:entindex() + sharedRicochetDepthByHero[k] = (sharedRicochetDepthByHero[k] or 0) + 1 +end +local function removeSharedCard14RicochetDepth(self, hero) + local k = hero:entindex() + local next = (sharedRicochetDepthByHero[k] or 1) - 1 + if next <= 0 then + __TS__Delete(sharedRicochetDepthByHero, k) + else + sharedRicochetDepthByHero[k] = next + end +end +____exports.card_14 = __TS__Class() +local card_14 = ____exports.card_14 +card_14.name = "card_14" +card_14.____file_path = "scripts/vscripts/cards/examples/card_14.lua" +__TS__ClassExtends(card_14, CardBase) +function card_14.prototype.GetModifierName(self) + return "modifier_card_14" +end +card_14 = __TS__Decorate(card_14, card_14, {RegisterCard}, {kind = "class", name = "card_14"}) +____exports.card_14 = card_14 +____exports.modifier_card_14 = __TS__Class() +local modifier_card_14 = ____exports.modifier_card_14 +modifier_card_14.name = "modifier_card_14" +modifier_card_14.____file_path = "scripts/vscripts/cards/examples/card_14.lua" +__TS__ClassExtends(modifier_card_14, CardBaseModifier) +function modifier_card_14.prototype.____constructor(self, ...) + CardBaseModifier.prototype.____constructor(self, ...) + self.ricochetProjectile = self:GetParent():GetRangedProjectileName() or "" + self.ricochetAttackDepth = 0 + self.forcedRicochetDamagePct = 0 +end +function modifier_card_14.prototype.DeclareFunctions(self) + return {MODIFIER_EVENT_ON_ATTACK_LANDED, MODIFIER_PROPERTY_DAMAGEOUTGOING_PERCENTAGE, MODIFIER_PROPERTY_TOOLTIP} +end +function modifier_card_14.prototype.getValue(self, key, fallback) + return self:getCardValue(key, fallback, CARD_ID) +end +function modifier_card_14.prototype.addFiredStacks(self, target, attacker, stacks) + if stacks <= 0 then + return + end + local firedModifier = target:AddNewModifier( + attacker, + getModifierSourceAbility(nil, attacker), + modifier_general_fired.name, + {} + ) + if not firedModifier then + return + end + do + local i = 0 + while i < stacks do + firedModifier:IncrementStackCount() + i = i + 1 + end + end +end +function modifier_card_14.prototype.GetModifierDamageOutgoing_Percentage(self) + if self.ricochetAttackDepth <= 0 then + return 0 + end + return self.forcedRicochetDamagePct +end +function modifier_card_14.prototype.performRicochetAttack(self, attacker, target, damagePct, burnStacks) + if not attacker:IsAlive() or not target:IsAlive() then + return + end + addSharedCard14RicochetDepth(nil, attacker) + self.ricochetAttackDepth = self.ricochetAttackDepth + 1 + self.forcedRicochetDamagePct = damagePct - 100 + attacker:PerformAttack( + target, + true, + true, + true, + false, + false, + false, + true + ) + self.forcedRicochetDamagePct = 0 + self.ricochetAttackDepth = math.max(0, self.ricochetAttackDepth - 1) + removeSharedCard14RicochetDepth(nil, attacker) + self:addFiredStacks(target, attacker, burnStacks) +end +function modifier_card_14.prototype.OnAttackLanded(self, event) + if not IsServer() then + return + end + if self.ricochetAttackDepth > 0 then + return + end + local attacker = self:GetParent() + if not attacker or not IsValidEntity(attacker) or event.attacker ~= attacker then + return + end + if getSharedCard14RicochetDepth(nil, attacker) > 0 then + return + end + local target = event.target + if not target or not IsValidEntity(target) or not target:IsAlive() then + return + end + if target:GetTeamNumber() == attacker:GetTeamNumber() then + return + end + local copies = self:getCardCopies() + local chancePct = math.min( + 100, + self:getValue("proc_chance_pct", 22) * copies + ) + local ____attacker_IsRealHero_result_0 + if attacker:IsRealHero() then + ____attacker_IsRealHero_result_0 = rollLuckChance(nil, attacker, chancePct / 100) + else + ____attacker_IsRealHero_result_0 = RollPercentage(chancePct) + end + local proc = ____attacker_IsRealHero_result_0 + if not proc then + return + end + local attackDamage = attacker:GetAverageTrueAttackDamage(target) + if attackDamage <= 0 then + return + end + local burnStacks = self:getValue("fired_stacks", 3) * copies + local mainBonusPct = self:getValue("main_target_bonus_damage_pct", 35) * copies + local mainDamage = attackDamage * (mainBonusPct / 100) + if mainDamage > 0 then + ApplyDamage({victim = target, attacker = attacker, damage = mainDamage, damage_type = DAMAGE_TYPE_MAGICAL}) + end + self:addFiredStacks(target, attacker, burnStacks) + local radius = self:getValue("ricochet_radius", 300) + local splashPct = self:getValue("ricochet_damage_pct", 60) * copies + if splashPct <= 0 or radius <= 0 then + return + end + local enemies = FindUnitsInRadius( + attacker:GetTeamNumber(), + target:GetAbsOrigin(), + nil, + radius, + DOTA_UNIT_TARGET_TEAM_ENEMY, + bit.bor(DOTA_UNIT_TARGET_HERO, DOTA_UNIT_TARGET_BASIC), + DOTA_UNIT_TARGET_FLAG_NONE, + FIND_ANY_ORDER, + false + ) + local projectileSpeed = math.max( + 700, + attacker:GetProjectileSpeed() + ) + for ____, enemy in ipairs(enemies) do + do + if enemy == target then + goto __continue29 + end + if not enemy:IsAlive() then + goto __continue29 + end + local distance = (enemy:GetAbsOrigin() - target:GetAbsOrigin()):Length2D() + local travelTime = projectileSpeed > 0 and distance / projectileSpeed or 0 + ProjectileManager:CreateTrackingProjectile({ + Source = target, + Target = enemy, + Ability = nil, + EffectName = self.ricochetProjectile, + iMoveSpeed = projectileSpeed, + bDodgeable = true, + bVisibleToEnemies = true, + bReplaceExisting = false, + bProvidesVision = false + }) + Timers:CreateTimer( + travelTime, + function() + if not IsServer() then + return nil + end + if not attacker:IsAlive() or not enemy:IsAlive() then + return nil + end + self:performRicochetAttack(attacker, enemy, splashPct, burnStacks) + return nil + end + ) + end + ::__continue29:: + end +end +modifier_card_14 = __TS__Decorate( + modifier_card_14, + modifier_card_14, + {registerModifier(nil)}, + {kind = "class", name = "modifier_card_14"} +) +____exports.modifier_card_14 = modifier_card_14 +return ____exports diff --git a/scripts/vscripts/cards/examples/card_15.lua b/scripts/vscripts/cards/examples/card_15.lua new file mode 100644 index 0000000..ab6b8f6 --- /dev/null +++ b/scripts/vscripts/cards/examples/card_15.lua @@ -0,0 +1,95 @@ +local ____lualib = require("lualib_bundle") +local __TS__Class = ____lualib.__TS__Class +local __TS__ClassExtends = ____lualib.__TS__ClassExtends +local __TS__Decorate = ____lualib.__TS__Decorate +local ____exports = {} +local ____CardSystem = require("cards.CardSystem") +local CardBase = ____CardSystem.CardBase +local RegisterCard = ____CardSystem.RegisterCard +local ____CardBaseModifier = require("cards.CardBaseModifier") +local CardBaseModifier = ____CardBaseModifier.CardBaseModifier +local ____dota_ts_adapter = require("lib.dota_ts_adapter") +local registerModifier = ____dota_ts_adapter.registerModifier +local ____ability_stacking_crit = require("abilities.modifiers.ability_stacking_crit") +local modifier_stacking_crit = ____ability_stacking_crit.modifier_stacking_crit +local ____luck = require("utils.luck") +local getLuck = ____luck.getLuck +local CARD_ID = 15 +local CRIT_SOURCE = "card_15_custom_crit" +local STACKING_CRIT_LUCK_BONUS_PER_POINT = 1 +____exports.card_15 = __TS__Class() +local card_15 = ____exports.card_15 +card_15.name = "card_15" +card_15.____file_path = "scripts/vscripts/cards/examples/card_15.lua" +__TS__ClassExtends(card_15, CardBase) +function card_15.prototype.GetModifierName(self) + return "modifier_card_15" +end +card_15 = __TS__Decorate(card_15, card_15, {RegisterCard}, {kind = "class", name = "card_15"}) +____exports.card_15 = card_15 +____exports.modifier_card_15 = __TS__Class() +local modifier_card_15 = ____exports.modifier_card_15 +modifier_card_15.name = "modifier_card_15" +modifier_card_15.____file_path = "scripts/vscripts/cards/examples/card_15.lua" +__TS__ClassExtends(modifier_card_15, CardBaseModifier) +function modifier_card_15.prototype.getValue(self, key, fallback) + return self:getCardValue(key, fallback, CARD_ID) +end +function modifier_card_15.prototype.OnCustomCreated(self, params) + if not IsServer() then + return + end + self:refreshCustomCrit() + self:StartIntervalThink(1) +end +function modifier_card_15.prototype.OnCustomRefresh(self, params) + if not IsServer() then + return + end + self:refreshCustomCrit() +end +function modifier_card_15.prototype.OnIntervalThink(self) + if not IsServer() then + return + end + self:refreshCustomCrit() +end +function modifier_card_15.prototype.OnDestroy(self) + if not IsServer() then + return + end + local parent = self:GetParent() + if not parent or not IsValidEntity(parent) then + return + end + local stackingCrit = modifier_stacking_crit:GetForUnit(parent) + if stackingCrit ~= nil then + stackingCrit:RemoveCrit(CRIT_SOURCE) + end +end +function modifier_card_15.prototype.refreshCustomCrit(self) + local parent = self:GetParent() + if not parent or not IsValidEntity(parent) then + return + end + local stackingCrit = modifier_stacking_crit:GetForUnit(parent) + if not stackingCrit then + return + end + local copies = self:getCardCopies() + local baseChancePct = self:getValue("base_crit_chance_pct", 15) * copies + local baseCritMultiplierPct = self:getValue("crit_multiplier_pct", 200) * copies + local critMultiplierBonusPerLuck = self:getValue("crit_multiplier_bonus_pct_per_luck", 2.5) * copies + local luckValue = parent:IsRealHero() and getLuck(nil, parent) or 0 + local chanceForStackingCrit = baseChancePct - luckValue * STACKING_CRIT_LUCK_BONUS_PER_POINT + local critMultiplierPct = math.max(0, baseCritMultiplierPct + luckValue * critMultiplierBonusPerLuck) + stackingCrit:UpdateExistingCrit(chanceForStackingCrit, critMultiplierPct, CRIT_SOURCE) +end +modifier_card_15 = __TS__Decorate( + modifier_card_15, + modifier_card_15, + {registerModifier(nil)}, + {kind = "class", name = "modifier_card_15"} +) +____exports.modifier_card_15 = modifier_card_15 +return ____exports diff --git a/scripts/vscripts/cards/examples/card_16.lua b/scripts/vscripts/cards/examples/card_16.lua new file mode 100644 index 0000000..5b25bf0 --- /dev/null +++ b/scripts/vscripts/cards/examples/card_16.lua @@ -0,0 +1,73 @@ +local ____lualib = require("lualib_bundle") +local __TS__Class = ____lualib.__TS__Class +local __TS__ClassExtends = ____lualib.__TS__ClassExtends +local __TS__Decorate = ____lualib.__TS__Decorate +local ____exports = {} +local ____CardSystem = require("cards.CardSystem") +local CardBase = ____CardSystem.CardBase +local RegisterCard = ____CardSystem.RegisterCard +local ____CardBaseModifier = require("cards.CardBaseModifier") +local CardBaseModifier = ____CardBaseModifier.CardBaseModifier +local ____dota_ts_adapter = require("lib.dota_ts_adapter") +local registerModifier = ____dota_ts_adapter.registerModifier +local CARD_ID = 16 +____exports.card_16 = __TS__Class() +local card_16 = ____exports.card_16 +card_16.name = "card_16" +card_16.____file_path = "scripts/vscripts/cards/examples/card_16.lua" +__TS__ClassExtends(card_16, CardBase) +function card_16.prototype.GetModifierName(self) + return "modifier_card_16" +end +card_16 = __TS__Decorate(card_16, card_16, {RegisterCard}, {kind = "class", name = "card_16"}) +____exports.card_16 = card_16 +____exports.modifier_card_16 = __TS__Class() +local modifier_card_16 = ____exports.modifier_card_16 +modifier_card_16.name = "modifier_card_16" +modifier_card_16.____file_path = "scripts/vscripts/cards/examples/card_16.lua" +__TS__ClassExtends(modifier_card_16, CardBaseModifier) +function modifier_card_16.prototype.____constructor(self, ...) + CardBaseModifier.prototype.____constructor(self, ...) + self.voidStacks = 0 +end +function modifier_card_16.prototype.getValue(self, key, fallback) + return self:getCardValue(key, fallback, CARD_ID) +end +function modifier_card_16.prototype.DeclareFunctions(self) + return {MODIFIER_EVENT_ON_DEATH, MODIFIER_PROPERTY_SPELL_AMPLIFY_PERCENTAGE, MODIFIER_PROPERTY_TOOLTIP} +end +function modifier_card_16.prototype.OnDeath(self, event) + if not IsServer() then + return + end + local parent = self:GetParent() + if not parent or not IsValidEntity(parent) then + return + end + if not event.attacker or event.attacker ~= parent then + return + end + if not event.unit or not IsValidEntity(event.unit) or event.unit == parent then + return + end + if event.unit:GetTeamNumber() == parent:GetTeamNumber() then + return + end + local maxStacks = self:getValue("max_stacks", 200) + if self.voidStacks >= maxStacks then + return + end + self.voidStacks = math.min(maxStacks, self.voidStacks + 1) +end +function modifier_card_16.prototype.GetModifierSpellAmplify_Percentage(self) + local perStackPct = self:getValue("spell_amp_per_stack_pct", 0.1) + return self.voidStacks * perStackPct * self:getCardCopies() +end +modifier_card_16 = __TS__Decorate( + modifier_card_16, + modifier_card_16, + {registerModifier(nil)}, + {kind = "class", name = "modifier_card_16"} +) +____exports.modifier_card_16 = modifier_card_16 +return ____exports diff --git a/scripts/vscripts/cards/examples/card_17.lua b/scripts/vscripts/cards/examples/card_17.lua new file mode 100644 index 0000000..839daf7 --- /dev/null +++ b/scripts/vscripts/cards/examples/card_17.lua @@ -0,0 +1,83 @@ +local ____lualib = require("lualib_bundle") +local __TS__Class = ____lualib.__TS__Class +local __TS__ClassExtends = ____lualib.__TS__ClassExtends +local __TS__Decorate = ____lualib.__TS__Decorate +local ____exports = {} +local ____CardSystem = require("cards.CardSystem") +local CardBase = ____CardSystem.CardBase +local RegisterCard = ____CardSystem.RegisterCard +local ____CardBaseModifier = require("cards.CardBaseModifier") +local CardBaseModifier = ____CardBaseModifier.CardBaseModifier +local ____dota_ts_adapter = require("lib.dota_ts_adapter") +local registerModifier = ____dota_ts_adapter.registerModifier +local ____modifier_card_cursed = require("cards.modifier_card_cursed") +local addCursedStack = ____modifier_card_cursed.addCursedStack +local getCursedStackCount = ____modifier_card_cursed.getCursedStackCount +local CARD_ID = 17 +____exports.card_17 = __TS__Class() +local card_17 = ____exports.card_17 +card_17.name = "card_17" +card_17.____file_path = "scripts/vscripts/cards/examples/card_17.lua" +__TS__ClassExtends(card_17, CardBase) +function card_17.prototype.GetModifierName(self) + return "modifier_card_17" +end +card_17 = __TS__Decorate(card_17, card_17, {RegisterCard}, {kind = "class", name = "card_17"}) +____exports.card_17 = card_17 +____exports.modifier_card_17 = __TS__Class() +local modifier_card_17 = ____exports.modifier_card_17 +modifier_card_17.name = "modifier_card_17" +modifier_card_17.____file_path = "scripts/vscripts/cards/examples/card_17.lua" +__TS__ClassExtends(modifier_card_17, CardBaseModifier) +function modifier_card_17.prototype.____constructor(self, ...) + CardBaseModifier.prototype.____constructor(self, ...) + self.cursedApplied = false +end +function modifier_card_17.prototype.getValue(self, key, fallback) + return self:getCardValue(key, fallback, CARD_ID) +end +function modifier_card_17.prototype.DeclareFunctions(self) + return {MODIFIER_PROPERTY_PROCATTACK_BONUS_DAMAGE_PURE, MODIFIER_EVENT_ON_ATTACK_LANDED, MODIFIER_PROPERTY_TOOLTIP} +end +function modifier_card_17.prototype.OnCustomCreated(self, params) + local hero = self:GetParent() + if not hero or not IsValidEntity(hero) or not hero:IsRealHero() then + return + end + addCursedStack(nil, hero) + self.cursedApplied = true +end +function modifier_card_17.prototype.OnAttackLanded(self, event) + local attacker = self:GetParent() + local minHealthPct = self:getValue("min_health_pct_to_activate", 80) + if attacker:GetHealthPercent() <= minHealthPct then + return + end + if attacker ~= event.attacker then + return + end + local copies = self:getCardCopies() + local hpCostPct = self:getValue("health_cost_pct", 5) / 100 * copies + local baseFromMaxHp = attacker:GetMaxHealth() * hpCostPct + local curses = math.max( + 1, + getCursedStackCount(nil, attacker) + ) + local pctPerCurse = self:getValue("curse_damage_pct_per_curse_stack", 0) + local curseDamageMult = 1 + curses * pctPerCurse / 100 + attacker:ModifyHealth( + attacker:GetHealth() - baseFromMaxHp, + nil, + false, + 0 + ) + ApplyDamage({victim = event.target, attacker = attacker, damage = baseFromMaxHp * curseDamageMult, damage_type = DAMAGE_TYPE_PURE}) +end +modifier_card_17 = __TS__Decorate( + modifier_card_17, + modifier_card_17, + {registerModifier(nil)}, + {kind = "class", name = "modifier_card_17"} +) +____exports.modifier_card_17 = modifier_card_17 +return ____exports diff --git a/scripts/vscripts/cards/examples/card_18.lua b/scripts/vscripts/cards/examples/card_18.lua new file mode 100644 index 0000000..6334ff5 --- /dev/null +++ b/scripts/vscripts/cards/examples/card_18.lua @@ -0,0 +1,98 @@ +local ____lualib = require("lualib_bundle") +local __TS__Class = ____lualib.__TS__Class +local __TS__ClassExtends = ____lualib.__TS__ClassExtends +local __TS__Decorate = ____lualib.__TS__Decorate +local ____exports = {} +local ____CardSystem = require("cards.CardSystem") +local CardBase = ____CardSystem.CardBase +local GetPlayerCardsTaken = ____CardSystem.GetPlayerCardsTaken +local RegisterCard = ____CardSystem.RegisterCard +local ____CardBaseModifier = require("cards.CardBaseModifier") +local CardBaseModifier = ____CardBaseModifier.CardBaseModifier +local ____dota_ts_adapter = require("lib.dota_ts_adapter") +local registerModifier = ____dota_ts_adapter.registerModifier +local ____luck = require("utils.luck") +local addLuck = ____luck.addLuck +local reduceLuck = ____luck.reduceLuck +local CARD_ID = 18 +____exports.card_18 = __TS__Class() +local card_18 = ____exports.card_18 +card_18.name = "card_18" +card_18.____file_path = "scripts/vscripts/cards/examples/card_18.lua" +__TS__ClassExtends(card_18, CardBase) +function card_18.prototype.GetModifierName(self) + return "modifier_card_18" +end +card_18 = __TS__Decorate(card_18, card_18, {RegisterCard}, {kind = "class", name = "card_18"}) +____exports.card_18 = card_18 +____exports.modifier_card_18 = __TS__Class() +local modifier_card_18 = ____exports.modifier_card_18 +modifier_card_18.name = "modifier_card_18" +modifier_card_18.____file_path = "scripts/vscripts/cards/examples/card_18.lua" +__TS__ClassExtends(modifier_card_18, CardBaseModifier) +function modifier_card_18.prototype.____constructor(self, ...) + CardBaseModifier.prototype.____constructor(self, ...) + self.appliedLuckBonus = 0 +end +function modifier_card_18.prototype.getValue(self, key, fallback) + return self:getCardValue(key, fallback, CARD_ID) +end +function modifier_card_18.prototype.OnCustomCreated(self, params) + if not IsServer() then + return + end + self:updateLuckBonus() + self:StartIntervalThink(1) +end +function modifier_card_18.prototype.OnCustomRefresh(self, params) + if not IsServer() then + return + end + self:updateLuckBonus() +end +function modifier_card_18.prototype.OnIntervalThink(self) + if not IsServer() then + return + end + self:updateLuckBonus() +end +function modifier_card_18.prototype.OnDestroy(self) + if not IsServer() then + return + end + local parent = self:GetParent() + if not parent or not IsValidEntity(parent) or not parent:IsRealHero() then + return + end + if self.appliedLuckBonus > 0 then + reduceLuck(nil, parent, self.appliedLuckBonus) + self.appliedLuckBonus = 0 + end +end +function modifier_card_18.prototype.updateLuckBonus(self) + local parent = self:GetParent() + if not parent or not IsValidEntity(parent) or not parent:IsRealHero() then + return + end + local copies = self:getCardCopies() + local baseLuckBonus = self:getValue("base_luck_bonus", 10) * copies + local luckPerCardTaken = self:getValue("luck_per_card_taken", 1) * copies + local playerId = parent:GetPlayerOwnerID() + local cardsTaken = playerId >= 0 and GetPlayerCardsTaken(nil, playerId) or 0 + local desiredLuckBonus = math.max(0, baseLuckBonus + cardsTaken * luckPerCardTaken) + local delta = desiredLuckBonus - self.appliedLuckBonus + if delta > 0 then + addLuck(nil, parent, delta) + elseif delta < 0 then + reduceLuck(nil, parent, -delta) + end + self.appliedLuckBonus = desiredLuckBonus +end +modifier_card_18 = __TS__Decorate( + modifier_card_18, + modifier_card_18, + {registerModifier(nil)}, + {kind = "class", name = "modifier_card_18"} +) +____exports.modifier_card_18 = modifier_card_18 +return ____exports diff --git a/scripts/vscripts/cards/examples/card_19.lua b/scripts/vscripts/cards/examples/card_19.lua new file mode 100644 index 0000000..00ff78a --- /dev/null +++ b/scripts/vscripts/cards/examples/card_19.lua @@ -0,0 +1,120 @@ +local ____lualib = require("lualib_bundle") +local __TS__Class = ____lualib.__TS__Class +local __TS__ClassExtends = ____lualib.__TS__ClassExtends +local __TS__Decorate = ____lualib.__TS__Decorate +local ____exports = {} +local ____CardSystem = require("cards.CardSystem") +local CardBase = ____CardSystem.CardBase +local RegisterCard = ____CardSystem.RegisterCard +local ____CardBaseModifier = require("cards.CardBaseModifier") +local CardBaseModifier = ____CardBaseModifier.CardBaseModifier +local ____dota_ts_adapter = require("lib.dota_ts_adapter") +local registerModifier = ____dota_ts_adapter.registerModifier +local ____luck = require("utils.luck") +local rollLuckChance = ____luck.rollLuckChance +local CARD_ID = 19 +local GHOST_STEP_MODIFIER = "modifier_card_19_ghost_step" +____exports.card_19 = __TS__Class() +local card_19 = ____exports.card_19 +card_19.name = "card_19" +card_19.____file_path = "scripts/vscripts/cards/examples/card_19.lua" +__TS__ClassExtends(card_19, CardBase) +function card_19.prototype.GetModifierName(self) + return "modifier_card_19" +end +card_19 = __TS__Decorate(card_19, card_19, {RegisterCard}, {kind = "class", name = "card_19"}) +____exports.card_19 = card_19 +____exports.modifier_card_19 = __TS__Class() +local modifier_card_19 = ____exports.modifier_card_19 +modifier_card_19.name = "modifier_card_19" +modifier_card_19.____file_path = "scripts/vscripts/cards/examples/card_19.lua" +__TS__ClassExtends(modifier_card_19, CardBaseModifier) +function modifier_card_19.prototype.getValue(self, key, fallback) + return self:getCardValue(key, fallback, CARD_ID) +end +function modifier_card_19.prototype.DeclareFunctions(self) + return {MODIFIER_EVENT_ON_ATTACK_LANDED, MODIFIER_PROPERTY_TOOLTIP} +end +function modifier_card_19.prototype.OnAttackLanded(self, event) + if not IsServer() then + return + end + local attacker = self:GetParent() + if not attacker or not IsValidEntity(attacker) or event.attacker ~= attacker then + return + end + local target = event.target + if not target or not IsValidEntity(target) or not target:IsAlive() then + return + end + if target:GetTeamNumber() == attacker:GetTeamNumber() then + return + end + local chancePct = self:getValue("ghost_step_chance_pct", 20) + local duration = self:getValue("ghost_step_duration", 2) + local ____attacker_IsRealHero_result_0 + if attacker:IsRealHero() then + ____attacker_IsRealHero_result_0 = rollLuckChance(nil, attacker, chancePct / 100) + else + ____attacker_IsRealHero_result_0 = RollPercentage(chancePct) + end + local proc = ____attacker_IsRealHero_result_0 + if not proc or duration <= 0 then + return + end + attacker:AddNewModifier( + attacker, + getModifierSourceAbility(nil, attacker), + GHOST_STEP_MODIFIER, + {duration = duration} + ) +end +modifier_card_19 = __TS__Decorate( + modifier_card_19, + modifier_card_19, + {registerModifier(nil)}, + {kind = "class", name = "modifier_card_19"} +) +____exports.modifier_card_19 = modifier_card_19 +____exports.modifier_card_19_ghost_step = __TS__Class() +local modifier_card_19_ghost_step = ____exports.modifier_card_19_ghost_step +modifier_card_19_ghost_step.name = "modifier_card_19_ghost_step" +modifier_card_19_ghost_step.____file_path = "scripts/vscripts/cards/examples/card_19.lua" +__TS__ClassExtends(modifier_card_19_ghost_step, CardBaseModifier) +function modifier_card_19_ghost_step.prototype.getCardCopiesFromOwner(self) + local owner = self:GetCaster() + if not owner or not IsValidEntity(owner) then + return 1 + end + local cardMod = owner:FindModifierByName("modifier_card_19") + return math.max( + 1, + math.floor(cardMod and cardMod:GetStackCount() or 0) + ) +end +function modifier_card_19_ghost_step.prototype.getValue(self, key, fallback) + return self:getCardValue(key, fallback, CARD_ID) * self:getCardCopiesFromOwner() +end +function modifier_card_19_ghost_step.prototype.IsPurgable(self) + return false +end +function modifier_card_19_ghost_step.prototype.DeclareFunctions(self) + return {MODIFIER_PROPERTY_MOVESPEED_BONUS_PERCENTAGE, MODIFIER_PROPERTY_EVASION_CONSTANT, MODIFIER_PROPERTY_TOOLTIP} +end +function modifier_card_19_ghost_step.prototype.GetModifierMoveSpeedBonus_Percentage(self) + return self:getValue("ghost_step_move_speed_pct", 20) +end +function modifier_card_19_ghost_step.prototype.GetModifierEvasion_Constant(self) + return self:getValue("ghost_step_evasion_pct", 15) +end +function modifier_card_19_ghost_step.prototype.CheckState(self) + return {[MODIFIER_STATE_NO_UNIT_COLLISION] = true} +end +modifier_card_19_ghost_step = __TS__Decorate( + modifier_card_19_ghost_step, + modifier_card_19_ghost_step, + {registerModifier(nil)}, + {kind = "class", name = "modifier_card_19_ghost_step"} +) +____exports.modifier_card_19_ghost_step = modifier_card_19_ghost_step +return ____exports diff --git a/scripts/vscripts/cards/examples/card_2.lua b/scripts/vscripts/cards/examples/card_2.lua new file mode 100644 index 0000000..e837be4 --- /dev/null +++ b/scripts/vscripts/cards/examples/card_2.lua @@ -0,0 +1,89 @@ +local ____lualib = require("lualib_bundle") +local __TS__Class = ____lualib.__TS__Class +local __TS__ClassExtends = ____lualib.__TS__ClassExtends +local __TS__Decorate = ____lualib.__TS__Decorate +local ____exports = {} +local ____CardSystem = require("cards.CardSystem") +local CardBase = ____CardSystem.CardBase +local RegisterCard = ____CardSystem.RegisterCard +local ____CardBaseModifier = require("cards.CardBaseModifier") +local CardBaseModifier = ____CardBaseModifier.CardBaseModifier +local ____dota_ts_adapter = require("lib.dota_ts_adapter") +local registerModifier = ____dota_ts_adapter.registerModifier +local ____card_bat_stacking = require("cards.card_bat_stacking") +local applyHeroTargetBat = ____card_bat_stacking.applyHeroTargetBat +local computeAttackSpeedBonusForTargetBat = ____card_bat_stacking.computeAttackSpeedBonusForTargetBat +local MODIFIER_CARD_43 = ____card_bat_stacking.MODIFIER_CARD_43 +local CARD_ID = 2 +local DEFAULT_MOVE_SPEED_LIMIT = 550 +____exports.card_2 = __TS__Class() +local card_2 = ____exports.card_2 +card_2.name = "card_2" +card_2.____file_path = "scripts/vscripts/cards/examples/card_2.lua" +__TS__ClassExtends(card_2, CardBase) +function card_2.prototype.GetModifierName(self) + return "modifier_card_2" +end +card_2 = __TS__Decorate(card_2, card_2, {RegisterCard}, {kind = "class", name = "card_2"}) +____exports.card_2 = card_2 +____exports.modifier_card_2 = __TS__Class() +local modifier_card_2 = ____exports.modifier_card_2 +modifier_card_2.name = "modifier_card_2" +modifier_card_2.____file_path = "scripts/vscripts/cards/examples/card_2.lua" +__TS__ClassExtends(modifier_card_2, CardBaseModifier) +function modifier_card_2.prototype.CheckState(self) + if self:getCardValue("pass_through_units", 0, CARD_ID) <= 0 then + return {} + end + return {[MODIFIER_STATE_NO_UNIT_COLLISION] = true} +end +function modifier_card_2.prototype.DeclareFunctions(self) + return {MODIFIER_PROPERTY_MOVESPEED_BONUS_CONSTANT, MODIFIER_PROPERTY_MOVESPEED_LIMIT, MODIFIER_PROPERTY_ATTACKSPEED_BONUS_CONSTANT, MODIFIER_PROPERTY_TOOLTIP} +end +function modifier_card_2.prototype.GetModifierMoveSpeedBonus_Constant(self) + return self:getScaledCardValue("speed_bonus", 45, CARD_ID) +end +function modifier_card_2.prototype.GetModifierMoveSpeed_Limit(self) + local limit = self:getCardValue("speed_limit", 0, CARD_ID) + if limit <= DEFAULT_MOVE_SPEED_LIMIT then + return DEFAULT_MOVE_SPEED_LIMIT + end + return limit * self:getCardCopies() +end +function modifier_card_2.prototype.GetModifierAttackSpeedBonus_Constant(self) + local attackTimeReductionPct = self:getScaledCardValue("attacktime", 0, CARD_ID) + if attackTimeReductionPct <= 0 then + return 0 + end + local parent = self:GetParent() + if not parent or not IsValidEntity(parent) then + return 0 + end + if parent:HasModifier(MODIFIER_CARD_43) then + return 0 + end + if IsServer() then + applyHeroTargetBat(nil, parent, 0) + end + return computeAttackSpeedBonusForTargetBat(nil, parent, 0) +end +function modifier_card_2.prototype.OnCustomCreated(self) + if not IsServer() then + return + end + local parent = self:GetParent() + if not parent or not IsValidEntity(parent) or parent:HasModifier(MODIFIER_CARD_43) then + return + end + if self:getCardValue("attacktime", 0, CARD_ID) > 0 then + applyHeroTargetBat(nil, parent, 0) + end +end +modifier_card_2 = __TS__Decorate( + modifier_card_2, + modifier_card_2, + {registerModifier(nil)}, + {kind = "class", name = "modifier_card_2"} +) +____exports.modifier_card_2 = modifier_card_2 +return ____exports diff --git a/scripts/vscripts/cards/examples/card_20.lua b/scripts/vscripts/cards/examples/card_20.lua new file mode 100644 index 0000000..0627909 --- /dev/null +++ b/scripts/vscripts/cards/examples/card_20.lua @@ -0,0 +1,69 @@ +local ____lualib = require("lualib_bundle") +local __TS__Class = ____lualib.__TS__Class +local __TS__ClassExtends = ____lualib.__TS__ClassExtends +local __TS__Decorate = ____lualib.__TS__Decorate +local ____exports = {} +local ____CardSystem = require("cards.CardSystem") +local CardBase = ____CardSystem.CardBase +local RegisterCard = ____CardSystem.RegisterCard +local ____CardBaseModifier = require("cards.CardBaseModifier") +local CardBaseModifier = ____CardBaseModifier.CardBaseModifier +local ____dota_ts_adapter = require("lib.dota_ts_adapter") +local registerModifier = ____dota_ts_adapter.registerModifier +local CARD_ID = 20 +____exports.card_20 = __TS__Class() +local card_20 = ____exports.card_20 +card_20.name = "card_20" +card_20.____file_path = "scripts/vscripts/cards/examples/card_20.lua" +__TS__ClassExtends(card_20, CardBase) +function card_20.prototype.GetModifierName(self) + return "modifier_card_20" +end +card_20 = __TS__Decorate(card_20, card_20, {RegisterCard}, {kind = "class", name = "card_20"}) +____exports.card_20 = card_20 +____exports.modifier_card_20 = __TS__Class() +local modifier_card_20 = ____exports.modifier_card_20 +modifier_card_20.name = "modifier_card_20" +modifier_card_20.____file_path = "scripts/vscripts/cards/examples/card_20.lua" +__TS__ClassExtends(modifier_card_20, CardBaseModifier) +function modifier_card_20.prototype.getValue(self, key, fallback) + return self:getCardValue(key, fallback, CARD_ID) +end +function modifier_card_20.prototype.IsPurgable(self) + return false +end +function modifier_card_20.prototype.DeclareFunctions(self) + return {MODIFIER_PROPERTY_MOVESPEED_BONUS_PERCENTAGE, MODIFIER_PROPERTY_MOVESPEED_LIMIT, MODIFIER_PROPERTY_IGNORE_MOVESPEED_LIMIT, MODIFIER_PROPERTY_TOOLTIP} +end +function modifier_card_20.prototype.GetModifierIgnoreMovespeedLimit(self) + return 1 +end +function modifier_card_20.prototype.GetModifierMoveSpeed_Limit(self) + return self:getValue("speed_limit", 100000) +end +function modifier_card_20.prototype.GetModifierMoveSpeedBonus_Percentage(self) + local hero = self:GetParent() + if not hero or not IsValidEntity(hero) or not hero:IsRealHero() then + return 0 + end + local playerId = hero:GetPlayerOwnerID() + if playerId == nil or playerId == nil or playerId < 0 then + return 0 + end + local totalGold = PlayerResource:GetGold(playerId) + local goldPerStep = math.max( + 1, + self:getValue("gold_per_step", 100) + ) + local bonusPctPerStep = self:getValue("move_speed_pct_per_step", 1) + local steps = math.floor(totalGold / goldPerStep) + return steps * bonusPctPerStep * self:getCardCopies() +end +modifier_card_20 = __TS__Decorate( + modifier_card_20, + modifier_card_20, + {registerModifier(nil)}, + {kind = "class", name = "modifier_card_20"} +) +____exports.modifier_card_20 = modifier_card_20 +return ____exports diff --git a/scripts/vscripts/cards/examples/card_21.lua b/scripts/vscripts/cards/examples/card_21.lua new file mode 100644 index 0000000..a19eee6 --- /dev/null +++ b/scripts/vscripts/cards/examples/card_21.lua @@ -0,0 +1,73 @@ +local ____lualib = require("lualib_bundle") +local __TS__Class = ____lualib.__TS__Class +local __TS__ClassExtends = ____lualib.__TS__ClassExtends +local __TS__Decorate = ____lualib.__TS__Decorate +local __TS__StringStartsWith = ____lualib.__TS__StringStartsWith +local ____exports = {} +local ____CardSystem = require("cards.CardSystem") +local CardBase = ____CardSystem.CardBase +local RegisterCard = ____CardSystem.RegisterCard +local ____CardBaseModifier = require("cards.CardBaseModifier") +local CardBaseModifier = ____CardBaseModifier.CardBaseModifier +local ____dota_ts_adapter = require("lib.dota_ts_adapter") +local registerModifier = ____dota_ts_adapter.registerModifier +local CARD_ID = 21 +____exports.card_21 = __TS__Class() +local card_21 = ____exports.card_21 +card_21.name = "card_21" +card_21.____file_path = "scripts/vscripts/cards/examples/card_21.lua" +__TS__ClassExtends(card_21, CardBase) +function card_21.prototype.GetModifierName(self) + return "modifier_card_21" +end +card_21 = __TS__Decorate(card_21, card_21, {RegisterCard}, {kind = "class", name = "card_21"}) +____exports.card_21 = card_21 +____exports.modifier_card_21 = __TS__Class() +local modifier_card_21 = ____exports.modifier_card_21 +modifier_card_21.name = "modifier_card_21" +modifier_card_21.____file_path = "scripts/vscripts/cards/examples/card_21.lua" +__TS__ClassExtends(modifier_card_21, CardBaseModifier) +function modifier_card_21.prototype.getValue(self, key, fallback) + return self:getCardValue(key, fallback, CARD_ID) +end +function modifier_card_21.prototype.DeclareFunctions(self) + return {MODIFIER_PROPERTY_PROCATTACK_BONUS_DAMAGE_PURE, MODIFIER_PROPERTY_TOOLTIP} +end +function modifier_card_21.prototype.GetModifierProcAttack_BonusDamage_Pure(self, event) + if not IsServer() then + return 0 + end + local attacker = self:GetParent() + if not attacker or not IsValidEntity(attacker) or event.attacker ~= attacker then + return 0 + end + local target = event.target + if not target or not IsValidEntity(target) or not target:IsAlive() then + return 0 + end + if target:GetTeamNumber() == attacker:GetTeamNumber() then + return 0 + end + local targetName = target:GetUnitName() + if not targetName or not __TS__StringStartsWith(targetName, "npc_wave") then + return 0 + end + local basePureDamage = self:getValue("wave_pure_damage_base", 125) + local ownerAttackDamage = attacker:GetAverageTrueAttackDamage(target) + local totalPureDamage = math.max(0, basePureDamage + ownerAttackDamage) + local particle = ParticleManager:CreateParticle("particles/units/heroes/hero_omniknight/omniknight_shard_hammer_of_purity_target.vpcf", PATTACH_ABSORIGIN_FOLLOW, target) + ParticleManager:ReleaseParticleIndex(particle) + if totalPureDamage <= 0 then + return 0 + end + local copies = self:getCardCopies() + return (self:getValue("wave_pure_damage_base", 125) + event.attacker:GetAverageTrueAttackDamage(event.target) * self:getValue("wave_pure_damage_base_hand", 0.25)) * copies +end +modifier_card_21 = __TS__Decorate( + modifier_card_21, + modifier_card_21, + {registerModifier(nil)}, + {kind = "class", name = "modifier_card_21"} +) +____exports.modifier_card_21 = modifier_card_21 +return ____exports diff --git a/scripts/vscripts/cards/examples/card_22.lua b/scripts/vscripts/cards/examples/card_22.lua new file mode 100644 index 0000000..d4f937f --- /dev/null +++ b/scripts/vscripts/cards/examples/card_22.lua @@ -0,0 +1,242 @@ +local ____lualib = require("lualib_bundle") +local __TS__Class = ____lualib.__TS__Class +local __TS__ClassExtends = ____lualib.__TS__ClassExtends +local __TS__Decorate = ____lualib.__TS__Decorate +local ____exports = {} +local ____custom_game_events = require("custom_game_events") +local ensurePlayerCardSystem = ____custom_game_events.ensurePlayerCardSystem +local sendZiMorningGoldCard = ____custom_game_events.sendZiMorningGoldCard +local ____CardSystem = require("cards.CardSystem") +local CardBase = ____CardSystem.CardBase +local RegisterCard = ____CardSystem.RegisterCard +local ____CardBaseModifier = require("cards.CardBaseModifier") +local CardBaseModifier = ____CardBaseModifier.CardBaseModifier +local ____dota_ts_adapter = require("lib.dota_ts_adapter") +local registerModifier = ____dota_ts_adapter.registerModifier +local ____card_value_resolver = require("cards.card_value_resolver") +local getCardValueByLevel = ____card_value_resolver.getCardValueByLevel +local ____WaveManager = require("WaveManager") +local WaveManager = ____WaveManager.WaveManager +local CARD_ID = 22 +local lastMorningSeqCard22ByPlayer = {} +--- Сколько раз уже выдали бонус «при взятии» (по числу копий, без повтора при апгрейде). +local card22PickupGrantsByPlayer = {} +--- Золото на рассвете после ночи `nightIndex` (1 → base, 2 → base−decay, …). +function ____exports.computeCard22MorningGold(self, hero, nightIndex, explicitLevel) + local night = math.max( + 1, + math.floor(nightIndex) + ) + local base = getCardValueByLevel( + nil, + CARD_ID, + hero, + "morning_gold_base", + 700, + explicitLevel + ) + local decay = getCardValueByLevel( + nil, + CARD_ID, + hero, + "morning_gold_decay_per_night", + 100, + explicitLevel + ) + return math.max( + 0, + math.floor(base - decay * (night - 1)) + ) +end +--- Следующая утренняя выплата от текущего номера ночи в WaveManager. +local function computeCard22NextMorningGold(self, hero, explicitLevel) + local currentNight = WaveManager:getInstance():GetCurrentNight() + local nextMorningNightIndex = math.max(1, currentNight + 1) + return ____exports.computeCard22MorningGold(nil, hero, nextMorningNightIndex, explicitLevel) +end +local function grantCard22Gold(self, playerId, goldAmount, nightIndex, morningSequence) + if goldAmount <= 0 then + return + end + PlayerResource:ModifyGold(playerId, goldAmount, true, DOTA_ModifyGold_Unspecified) + sendZiMorningGoldCard(nil, playerId, {morningSequence = morningSequence, nightIndex = nightIndex, goldAmount = goldAmount}) +end +____exports.card_22 = __TS__Class() +local card_22 = ____exports.card_22 +card_22.name = "card_22" +card_22.____file_path = "scripts/vscripts/cards/examples/card_22.lua" +__TS__ClassExtends(card_22, CardBase) +function card_22.prototype.GetModifierName(self) + return "modifier_card_22" +end +card_22 = __TS__Decorate(card_22, card_22, {RegisterCard}, {kind = "class", name = "card_22"}) +____exports.card_22 = card_22 +____exports.modifier_card_22 = __TS__Class() +local modifier_card_22 = ____exports.modifier_card_22 +modifier_card_22.name = "modifier_card_22" +modifier_card_22.____file_path = "scripts/vscripts/cards/examples/card_22.lua" +__TS__ClassExtends(modifier_card_22, CardBaseModifier) +function modifier_card_22.prototype.OnCustomCreated(self) + if not IsServer() then + return + end + local hero = self:GetParent() + if not hero or not IsValidEntity(hero) or not hero:IsRealHero() then + return + end + local pickupPct = self:getCardValue("pickup_gold_pct", 0, CARD_ID) + if not (pickupPct > 0) then + return + end + local playerId = hero:GetPlayerOwnerID() + if playerId == nil or playerId < 0 then + return + end + local cardSystem = ensurePlayerCardSystem(nil, playerId) + local activeCopies = cardSystem and cardSystem:GetActiveCardCopies(CARD_ID) or 1 + local alreadyGranted = card22PickupGrantsByPlayer[playerId] or 0 + if alreadyGranted >= activeCopies then + return + end + local nextMorningGold = computeCard22NextMorningGold(nil, hero) + local instantGold = math.floor(nextMorningGold * pickupPct / 100) + if instantGold <= 0 then + return + end + grantCard22Gold( + nil, + playerId, + instantGold, + WaveManager:getInstance():GetCurrentNight(), + 0 + ) + card22PickupGrantsByPlayer[playerId] = alreadyGranted + 1 +end +function modifier_card_22.prototype.DeclareFunctions(self) + return {MODIFIER_PROPERTY_TOOLTIP} +end +function modifier_card_22.prototype.OnTooltip(self) + local hero = self:GetParent() + if not hero or not IsValidEntity(hero) then + return 0 + end + return computeCard22NextMorningGold(nil, hero) +end +modifier_card_22 = __TS__Decorate( + modifier_card_22, + modifier_card_22, + {registerModifier(nil)}, + {kind = "class", name = "modifier_card_22"} +) +____exports.modifier_card_22 = modifier_card_22 +--- Рассвет: полная утренняя выплата. +function ____exports.notifyCard22MorningStarted(self, currentNight, morningSequence) + if not IsServer() then + return + end + local nightIndex = math.max( + 1, + math.floor(currentNight) + ) + do + local i = 0 + while i < DOTA_MAX_PLAYERS do + do + local pid = i + local player = PlayerResource:GetPlayer(pid) + if not player then + goto __continue19 + end + local cardSystem = ensurePlayerCardSystem(nil, pid) + if not cardSystem then + goto __continue19 + end + local copies = cardSystem:GetActiveCardCopies(CARD_ID) + if copies <= 0 then + goto __continue19 + end + local hero = player:GetAssignedHero() + if not hero or not IsValidEntity(hero) or not hero:IsRealHero() then + goto __continue19 + end + local prevSeq = lastMorningSeqCard22ByPlayer[pid] or 0 + if morningSequence <= prevSeq then + goto __continue19 + end + local morningGold = ____exports.computeCard22MorningGold(nil, hero, nightIndex) + local goldReward = math.max( + 0, + math.floor(morningGold * copies) + ) + if goldReward > 0 then + grantCard22Gold( + nil, + pid, + goldReward, + nightIndex, + morningSequence + ) + end + lastMorningSeqCard22ByPlayer[pid] = morningSequence + end + ::__continue19:: + i = i + 1 + end + end +end +--- Начало ночи (ур. 3+): 50% от золота следующего рассвета. +function ____exports.notifyCard22NightStarted(self) + if not IsServer() then + return + end + local currentNight = WaveManager:getInstance():GetCurrentNight() + local nextMorningNightIndex = math.max(1, currentNight + 1) + do + local i = 0 + while i < DOTA_MAX_PLAYERS do + do + local pid = i + local player = PlayerResource:GetPlayer(pid) + if not player then + goto __continue28 + end + local cardSystem = ensurePlayerCardSystem(nil, pid) + if not cardSystem then + goto __continue28 + end + local copies = cardSystem:GetActiveCardCopies(CARD_ID) + if copies <= 0 then + goto __continue28 + end + local hero = player:GetAssignedHero() + if not hero or not IsValidEntity(hero) or not hero:IsRealHero() then + goto __continue28 + end + local preNightPct = getCardValueByLevel( + nil, + CARD_ID, + hero, + "pre_night_gold_pct", + 0 + ) + if not (preNightPct > 0) then + goto __continue28 + end + local nextMorningGold = ____exports.computeCard22MorningGold(nil, hero, nextMorningNightIndex) + local preNightGold = math.floor(nextMorningGold * preNightPct / 100 * copies) + if preNightGold > 0 then + grantCard22Gold( + nil, + pid, + preNightGold, + nextMorningNightIndex, + 0 + ) + end + end + ::__continue28:: + i = i + 1 + end + end +end +return ____exports diff --git a/scripts/vscripts/cards/examples/card_23.lua b/scripts/vscripts/cards/examples/card_23.lua new file mode 100644 index 0000000..8362359 --- /dev/null +++ b/scripts/vscripts/cards/examples/card_23.lua @@ -0,0 +1,138 @@ +local ____lualib = require("lualib_bundle") +local __TS__Class = ____lualib.__TS__Class +local __TS__ClassExtends = ____lualib.__TS__ClassExtends +local __TS__Decorate = ____lualib.__TS__Decorate +local ____exports = {} +local ____CardSystem = require("cards.CardSystem") +local CardBase = ____CardSystem.CardBase +local RegisterCard = ____CardSystem.RegisterCard +local ____CardBaseModifier = require("cards.CardBaseModifier") +local CardBaseModifier = ____CardBaseModifier.CardBaseModifier +local ____dota_ts_adapter = require("lib.dota_ts_adapter") +local registerModifier = ____dota_ts_adapter.registerModifier +local ____modifier_card_greed = require("cards.modifier_card_greed") +local updateGreedForHero = ____modifier_card_greed.updateGreedForHero +local ____WaveManager = require("WaveManager") +local WaveManager = ____WaveManager.WaveManager +local CARD_ID = 23 +local function isCard23GoldTickBlocked(self) + if WaveManager:getInstance():isEndingCutsceneReadyVoteOpen() then + return true + end + local cutscene = GameRules.CutsceneManager + if cutscene ~= nil and cutscene:isCutsceneActive() then + return true + end + return false +end +____exports.card_23 = __TS__Class() +local card_23 = ____exports.card_23 +card_23.name = "card_23" +card_23.____file_path = "scripts/vscripts/cards/examples/card_23.lua" +__TS__ClassExtends(card_23, CardBase) +function card_23.prototype.GetModifierName(self) + return "modifier_card_23" +end +card_23 = __TS__Decorate(card_23, card_23, {RegisterCard}, {kind = "class", name = "card_23"}) +____exports.card_23 = card_23 +____exports.modifier_card_23 = __TS__Class() +local modifier_card_23 = ____exports.modifier_card_23 +modifier_card_23.name = "modifier_card_23" +modifier_card_23.____file_path = "scripts/vscripts/cards/examples/card_23.lua" +__TS__ClassExtends(modifier_card_23, CardBaseModifier) +function modifier_card_23.prototype.____constructor(self, ...) + CardBaseModifier.prototype.____constructor(self, ...) + self.lastGoldSnapshot = 0 +end +function modifier_card_23.prototype.getValue(self, key, fallback) + return self:getCardValue(key, fallback, CARD_ID) +end +function modifier_card_23.prototype.OnCustomCreated(self, params) + if not IsServer() then + return + end + self.lastGoldSnapshot = self:getCurrentPlayerGold() + self:startGoldTick() +end +function modifier_card_23.prototype.OnCustomRefresh(self, params) + if not IsServer() then + return + end + self.lastGoldSnapshot = self:getCurrentPlayerGold() + self:startGoldTick() +end +function modifier_card_23.prototype.startGoldTick(self) + local tickSeconds = math.max( + 1, + self:getValue("gold_tick_interval_sec", 60) + ) + self:StartIntervalThink(tickSeconds) +end +function modifier_card_23.prototype.getCurrentPlayerGold(self) + local hero = self:GetParent() + if not hero or not IsValidEntity(hero) or not hero:IsRealHero() then + return 0 + end + local playerId = hero:GetPlayerOwnerID() + if playerId == nil or playerId == nil or playerId < 0 then + return 0 + end + return math.max( + 0, + PlayerResource:GetGold(playerId) + ) +end +function modifier_card_23.prototype.OnIntervalThink(self) + if not IsServer() then + return + end + if isCard23GoldTickBlocked(nil) then + self.lastGoldSnapshot = self:getCurrentPlayerGold() + return + end + local hero = self:GetParent() + if not hero or not IsValidEntity(hero) or not hero:IsRealHero() then + return + end + local playerId = hero:GetPlayerOwnerID() + if playerId == nil or playerId == nil or playerId < 0 then + return + end + local currentGold = PlayerResource:GetGold(playerId) + if currentGold <= 0 then + self.lastGoldSnapshot = 0 + return + end + local percentPerMinute = self:getScaledCardValue("gold_income_pct_per_minute", 15) + local bonusGold = math.floor(currentGold * (percentPerMinute / 100)) + if bonusGold <= 0 then + self.lastGoldSnapshot = currentGold + return + end + local earnedGoldForTick = math.max(0, currentGold - self.lastGoldSnapshot) + local minEarnedPct = math.max( + 0, + self:getScaledCardValue("min_earned_gold_pct_of_dividend_required", 25) + ) + local minEarnedGoldRequired = math.ceil(bonusGold * (minEarnedPct / 100)) + if earnedGoldForTick < minEarnedGoldRequired then + self.lastGoldSnapshot = currentGold + return + end + PlayerResource:ModifyGold(playerId, bonusGold, true, DOTA_ModifyGold_Unspecified) + self.lastGoldSnapshot = math.max( + 0, + PlayerResource:GetGold(playerId) + ) + local ____updateGreedForHero_2 = updateGreedForHero + local ____opt_0 = PlayerResource:GetPlayer(playerId) + ____updateGreedForHero_2(nil, hero, ____opt_0 and ____opt_0.cardSystem) +end +modifier_card_23 = __TS__Decorate( + modifier_card_23, + modifier_card_23, + {registerModifier(nil)}, + {kind = "class", name = "modifier_card_23"} +) +____exports.modifier_card_23 = modifier_card_23 +return ____exports diff --git a/scripts/vscripts/cards/examples/card_24.lua b/scripts/vscripts/cards/examples/card_24.lua new file mode 100644 index 0000000..231e476 --- /dev/null +++ b/scripts/vscripts/cards/examples/card_24.lua @@ -0,0 +1,52 @@ +local ____lualib = require("lualib_bundle") +local __TS__Class = ____lualib.__TS__Class +local __TS__ClassExtends = ____lualib.__TS__ClassExtends +local __TS__Decorate = ____lualib.__TS__Decorate +local ____exports = {} +local ____CardSystem = require("cards.CardSystem") +local CardBase = ____CardSystem.CardBase +local RegisterCard = ____CardSystem.RegisterCard +local ____CardBaseModifier = require("cards.CardBaseModifier") +local CardBaseModifier = ____CardBaseModifier.CardBaseModifier +local ____dota_ts_adapter = require("lib.dota_ts_adapter") +local registerModifier = ____dota_ts_adapter.registerModifier +local CARD_ID = 24 +____exports.card_24 = __TS__Class() +local card_24 = ____exports.card_24 +card_24.name = "card_24" +card_24.____file_path = "scripts/vscripts/cards/examples/card_24.lua" +__TS__ClassExtends(card_24, CardBase) +function card_24.prototype.GetModifierName(self) + return "modifier_card_24" +end +card_24 = __TS__Decorate(card_24, card_24, {RegisterCard}, {kind = "class", name = "card_24"}) +____exports.card_24 = card_24 +____exports.modifier_card_24 = __TS__Class() +local modifier_card_24 = ____exports.modifier_card_24 +modifier_card_24.name = "modifier_card_24" +modifier_card_24.____file_path = "scripts/vscripts/cards/examples/card_24.lua" +__TS__ClassExtends(modifier_card_24, CardBaseModifier) +function modifier_card_24.prototype.getValue(self, key, fallback) + return self:getCardValue(key, fallback, CARD_ID) +end +function modifier_card_24.prototype.DeclareFunctions(self) + return {MODIFIER_PROPERTY_DAMAGEOUTGOING_PERCENTAGE, MODIFIER_PROPERTY_SPELL_AMPLIFY_PERCENTAGE, MODIFIER_PROPERTY_TOOLTIP} +end +function modifier_card_24.prototype.GetModifierDamageOutgoing_Percentage(self) + local currentGold = self:GetParent():GetGold() + local attackDamagePerStep = self:getScaledCardValue("attack_damage_per_step", 0.5) + return currentGold * attackDamagePerStep * 0.01 +end +function modifier_card_24.prototype.GetModifierSpellAmplify_Percentage(self) + local currentGold = self:GetParent():GetGold() + local spellAmpPerStepPct = self:getScaledCardValue("spell_amp_per_step_pct", 0.5) + return currentGold * spellAmpPerStepPct * 0.01 +end +modifier_card_24 = __TS__Decorate( + modifier_card_24, + modifier_card_24, + {registerModifier(nil)}, + {kind = "class", name = "modifier_card_24"} +) +____exports.modifier_card_24 = modifier_card_24 +return ____exports diff --git a/scripts/vscripts/cards/examples/card_25.lua b/scripts/vscripts/cards/examples/card_25.lua new file mode 100644 index 0000000..9bc09ad --- /dev/null +++ b/scripts/vscripts/cards/examples/card_25.lua @@ -0,0 +1,89 @@ +local ____lualib = require("lualib_bundle") +local __TS__Class = ____lualib.__TS__Class +local __TS__ClassExtends = ____lualib.__TS__ClassExtends +local __TS__Decorate = ____lualib.__TS__Decorate +local ____exports = {} +local ____CardSystem = require("cards.CardSystem") +local CardBase = ____CardSystem.CardBase +local RegisterCard = ____CardSystem.RegisterCard +local ____CardBaseModifier = require("cards.CardBaseModifier") +local CardBaseModifier = ____CardBaseModifier.CardBaseModifier +local ____dota_ts_adapter = require("lib.dota_ts_adapter") +local registerModifier = ____dota_ts_adapter.registerModifier +local CARD_ID = 25 +____exports.card_25 = __TS__Class() +local card_25 = ____exports.card_25 +card_25.name = "card_25" +card_25.____file_path = "scripts/vscripts/cards/examples/card_25.lua" +__TS__ClassExtends(card_25, CardBase) +function card_25.prototype.GetModifierName(self) + return "modifier_card_25" +end +card_25 = __TS__Decorate(card_25, card_25, {RegisterCard}, {kind = "class", name = "card_25"}) +____exports.card_25 = card_25 +____exports.modifier_card_25 = __TS__Class() +local modifier_card_25 = ____exports.modifier_card_25 +modifier_card_25.name = "modifier_card_25" +modifier_card_25.____file_path = "scripts/vscripts/cards/examples/card_25.lua" +__TS__ClassExtends(modifier_card_25, CardBaseModifier) +function modifier_card_25.prototype.____constructor(self, ...) + CardBaseModifier.prototype.____constructor(self, ...) + self.manaBonusLock = false +end +function modifier_card_25.prototype.getValue(self, key, fallback) + return self:getCardValue(key, fallback, CARD_ID) +end +function modifier_card_25.prototype.DeclareFunctions(self) + return {MODIFIER_PROPERTY_SPELL_AMPLIFY_PERCENTAGE, MODIFIER_PROPERTY_MANA_REGEN_CONSTANT, MODIFIER_PROPERTY_MANA_BONUS, MODIFIER_PROPERTY_TOOLTIP} +end +function modifier_card_25.prototype.getManaSteps(self) + local hero = self:GetParent() + if not hero or not IsValidEntity(hero) then + return 0 + end + local manaPerStep = math.max( + 1, + self:getValue("mana_per_step", 100) + ) + return math.floor(hero:GetMana() / manaPerStep) +end +function modifier_card_25.prototype.GetModifierSpellAmplify_Percentage(self) + local steps = self:getManaSteps() + if steps <= 0 then + return 0 + end + local spellAmpPerStepPct = self:getScaledCardValue("spell_amp_per_step_pct", 0.1) + return steps * spellAmpPerStepPct +end +function modifier_card_25.prototype.GetModifierConstantManaRegen(self) + local steps = self:getManaSteps() + if steps <= 0 then + return 0 + end + local manaRegenPerStep = self:getScaledCardValue("mana_regen_per_step", 0.1) + return steps * manaRegenPerStep +end +function modifier_card_25.prototype.GetModifierManaBonus(self) + if not IsServer() then + return 0 + end + local pct = self:getScaledCardValue("max_mana_bonus_pct", 0) + if pct <= 0 then + return 0 + end + if self.manaBonusLock then + return 0 + end + self.manaBonusLock = true + local baseMaxMana = self:GetParent():GetMaxMana() + self.manaBonusLock = false + return math.floor(baseMaxMana * (pct / 100)) +end +modifier_card_25 = __TS__Decorate( + modifier_card_25, + modifier_card_25, + {registerModifier(nil)}, + {kind = "class", name = "modifier_card_25"} +) +____exports.modifier_card_25 = modifier_card_25 +return ____exports diff --git a/scripts/vscripts/cards/examples/card_26.lua b/scripts/vscripts/cards/examples/card_26.lua new file mode 100644 index 0000000..15d0689 --- /dev/null +++ b/scripts/vscripts/cards/examples/card_26.lua @@ -0,0 +1,154 @@ +local ____lualib = require("lualib_bundle") +local __TS__Class = ____lualib.__TS__Class +local __TS__ClassExtends = ____lualib.__TS__ClassExtends +local __TS__Decorate = ____lualib.__TS__Decorate +local ____exports = {} +local ____CardSystem = require("cards.CardSystem") +local CardBase = ____CardSystem.CardBase +local RegisterCard = ____CardSystem.RegisterCard +local ____CardBaseModifier = require("cards.CardBaseModifier") +local CardBaseModifier = ____CardBaseModifier.CardBaseModifier +local ____dota_ts_adapter = require("lib.dota_ts_adapter") +local registerModifier = ____dota_ts_adapter.registerModifier +local ____card_value_resolver = require("cards.card_value_resolver") +local getCardValueByLevel = ____card_value_resolver.getCardValueByLevel +local CARD_ID = 26 +local INVIS_MODIFIER_NAME = "modifier_card_26_invisibility" +local function getCard26Value(self, hero, key, fallback) + return getCardValueByLevel( + nil, + CARD_ID, + hero, + key, + fallback + ) +end +____exports.card_26 = __TS__Class() +local card_26 = ____exports.card_26 +card_26.name = "card_26" +card_26.____file_path = "scripts/vscripts/cards/examples/card_26.lua" +__TS__ClassExtends(card_26, CardBase) +function card_26.prototype.GetModifierName(self) + return "modifier_card_26" +end +card_26 = __TS__Decorate(card_26, card_26, {RegisterCard}, {kind = "class", name = "card_26"}) +____exports.card_26 = card_26 +--- Основной модификатор карты: проверка порога по событию урона, КД, снятие спасения когда отхилились. +____exports.modifier_card_26 = __TS__Class() +local modifier_card_26 = ____exports.modifier_card_26 +modifier_card_26.name = "modifier_card_26" +modifier_card_26.____file_path = "scripts/vscripts/cards/examples/card_26.lua" +__TS__ClassExtends(modifier_card_26, CardBaseModifier) +function modifier_card_26.prototype.____constructor(self, ...) + CardBaseModifier.prototype.____constructor(self, ...) + self.cooldownEndTime = 0 +end +function modifier_card_26.prototype.OnCustomCreated(self, params) + if not IsServer() then + return + end +end +function modifier_card_26.prototype.OnCustomRefresh(self, params) + if not IsServer() then + return + end +end +function modifier_card_26.prototype.DeclareFunctions(self) + return {MODIFIER_EVENT_ON_TAKEDAMAGE, MODIFIER_PROPERTY_TOOLTIP} +end +function modifier_card_26.prototype.OnTakeDamage(self, event) + if not IsServer() then + return + end + local parent = self:GetParent() + if not parent or not IsValidEntity(parent) or not parent:IsAlive() then + return + end + if event.unit ~= parent then + return + end + if not parent:IsRealHero() then + return + end + local now = GameRules:GetGameTime() + if now < self.cooldownEndTime then + return + end + if parent:HasModifier(INVIS_MODIFIER_NAME) then + return + end + local triggerHealth = getCard26Value(nil, parent, "trigger_health_threshold", 100) + if parent:GetHealth() >= triggerHealth then + return + end + parent:AddNewModifier( + parent, + getModifierSourceAbility(nil, parent), + INVIS_MODIFIER_NAME, + {duration = getCard26Value(nil, parent, "invisibility_duration", 10) * self:getCardCopies()} + ) +end +modifier_card_26 = __TS__Decorate( + modifier_card_26, + modifier_card_26, + {registerModifier(nil)}, + {kind = "class", name = "modifier_card_26"} +) +____exports.modifier_card_26 = modifier_card_26 +--- Режим спасения: инвиз + реген к макс. HP + min 1 HP (всё в одном месте). +____exports.modifier_card_26_invisibility = __TS__Class() +local modifier_card_26_invisibility = ____exports.modifier_card_26_invisibility +modifier_card_26_invisibility.name = "modifier_card_26_invisibility" +modifier_card_26_invisibility.____file_path = "scripts/vscripts/cards/examples/card_26.lua" +__TS__ClassExtends(modifier_card_26_invisibility, CardBaseModifier) +function modifier_card_26_invisibility.prototype.getCardCopiesFromOwner(self) + local owner = self:GetCaster() + if not owner or not IsValidEntity(owner) then + return 1 + end + local cardMod = owner:FindModifierByName("modifier_card_26") + return math.max( + 1, + math.floor(cardMod and cardMod:GetStackCount() or 0) + ) +end +function modifier_card_26_invisibility.prototype.GetModifierInvisibilityLevel(self) + return 2 +end +function modifier_card_26_invisibility.prototype.GetEffectName(self) + return "particles/units/heroes/hero_phantom_assassin/phantom_assassin_blur_start.vpcf" +end +function modifier_card_26_invisibility.prototype.GetEffectAttachType(self) + return PATTACH_OVERHEAD_FOLLOW +end +function modifier_card_26_invisibility.prototype.GetStatusEffectName(self) + return "particles/status_fx/status_effect_phantom_assassin_blur.vpcf" +end +function modifier_card_26_invisibility.prototype.StatusEffectPriority(self) + return MODIFIER_PRIORITY_SUPER_ULTRA +end +function modifier_card_26_invisibility.prototype.DeclareFunctions(self) + return {MODIFIER_PROPERTY_MIN_HEALTH, MODIFIER_PROPERTY_HEALTH_REGEN_CONSTANT, MODIFIER_PROPERTY_INVISIBILITY_LEVEL, MODIFIER_PROPERTY_TOOLTIP} +end +function modifier_card_26_invisibility.prototype.GetMinHealth(self) + return 1 +end +function modifier_card_26_invisibility.prototype.GetModifierConstantHealthRegen(self) + local hero = self:GetParent() + if not hero or not IsValidEntity(hero) then + return 0 + end + local healPctPerSecond = getCard26Value(nil, hero, "heal_pct_per_second", 10) * self:getCardCopiesFromOwner() + return hero:GetMaxHealth() * (healPctPerSecond / 100) +end +function modifier_card_26_invisibility.prototype.CheckState(self) + return {[MODIFIER_STATE_INVISIBLE] = true, [MODIFIER_STATE_NO_UNIT_COLLISION] = true} +end +modifier_card_26_invisibility = __TS__Decorate( + modifier_card_26_invisibility, + modifier_card_26_invisibility, + {registerModifier(nil)}, + {kind = "class", name = "modifier_card_26_invisibility"} +) +____exports.modifier_card_26_invisibility = modifier_card_26_invisibility +return ____exports diff --git a/scripts/vscripts/cards/examples/card_27.lua b/scripts/vscripts/cards/examples/card_27.lua new file mode 100644 index 0000000..19c6f56 --- /dev/null +++ b/scripts/vscripts/cards/examples/card_27.lua @@ -0,0 +1,53 @@ +local ____lualib = require("lualib_bundle") +local __TS__Class = ____lualib.__TS__Class +local __TS__ClassExtends = ____lualib.__TS__ClassExtends +local __TS__Decorate = ____lualib.__TS__Decorate +local ____exports = {} +local ____CardSystem = require("cards.CardSystem") +local CardBase = ____CardSystem.CardBase +local RegisterCard = ____CardSystem.RegisterCard +local ____CardBaseModifier = require("cards.CardBaseModifier") +local CardBaseModifier = ____CardBaseModifier.CardBaseModifier +local ____dota_ts_adapter = require("lib.dota_ts_adapter") +local registerModifier = ____dota_ts_adapter.registerModifier +local CARD_ID = 27 +____exports.card_27 = __TS__Class() +local card_27 = ____exports.card_27 +card_27.name = "card_27" +card_27.____file_path = "scripts/vscripts/cards/examples/card_27.lua" +__TS__ClassExtends(card_27, CardBase) +function card_27.prototype.GetModifierName(self) + return "modifier_card_27" +end +card_27 = __TS__Decorate(card_27, card_27, {RegisterCard}, {kind = "class", name = "card_27"}) +____exports.card_27 = card_27 +____exports.modifier_card_27 = __TS__Class() +local modifier_card_27 = ____exports.modifier_card_27 +modifier_card_27.name = "modifier_card_27" +modifier_card_27.____file_path = "scripts/vscripts/cards/examples/card_27.lua" +__TS__ClassExtends(modifier_card_27, CardBaseModifier) +function modifier_card_27.prototype.getValue(self, key, fallback) + return self:getCardValue(key, fallback, CARD_ID) +end +function modifier_card_27.prototype.DeclareFunctions(self) + return {MODIFIER_PROPERTY_PREATTACK_BONUS_DAMAGE, MODIFIER_PROPERTY_TOOLTIP} +end +function modifier_card_27.prototype.GetModifierPreAttack_BonusDamage(self) + local hero = self:GetParent() + if not hero or not IsValidEntity(hero) then + return 0 + end + local manaToDamagePct = self:getScaledCardValue("mana_to_damage_pct", 15) + if manaToDamagePct <= 0 then + return 0 + end + return hero:GetMana() * (manaToDamagePct / 100) +end +modifier_card_27 = __TS__Decorate( + modifier_card_27, + modifier_card_27, + {registerModifier(nil)}, + {kind = "class", name = "modifier_card_27"} +) +____exports.modifier_card_27 = modifier_card_27 +return ____exports diff --git a/scripts/vscripts/cards/examples/card_28.lua b/scripts/vscripts/cards/examples/card_28.lua new file mode 100644 index 0000000..149a68e --- /dev/null +++ b/scripts/vscripts/cards/examples/card_28.lua @@ -0,0 +1,53 @@ +local ____lualib = require("lualib_bundle") +local __TS__Class = ____lualib.__TS__Class +local __TS__ClassExtends = ____lualib.__TS__ClassExtends +local __TS__Decorate = ____lualib.__TS__Decorate +local ____exports = {} +local ____CardSystem = require("cards.CardSystem") +local CardBase = ____CardSystem.CardBase +local RegisterCard = ____CardSystem.RegisterCard +local ____CardBaseModifier = require("cards.CardBaseModifier") +local CardBaseModifier = ____CardBaseModifier.CardBaseModifier +local ____dota_ts_adapter = require("lib.dota_ts_adapter") +local registerModifier = ____dota_ts_adapter.registerModifier +local CARD_ID = 28 +____exports.card_28 = __TS__Class() +local card_28 = ____exports.card_28 +card_28.name = "card_28" +card_28.____file_path = "scripts/vscripts/cards/examples/card_28.lua" +__TS__ClassExtends(card_28, CardBase) +function card_28.prototype.GetModifierName(self) + return "modifier_card_28" +end +card_28 = __TS__Decorate(card_28, card_28, {RegisterCard}, {kind = "class", name = "card_28"}) +____exports.card_28 = card_28 +____exports.modifier_card_28 = __TS__Class() +local modifier_card_28 = ____exports.modifier_card_28 +modifier_card_28.name = "modifier_card_28" +modifier_card_28.____file_path = "scripts/vscripts/cards/examples/card_28.lua" +__TS__ClassExtends(modifier_card_28, CardBaseModifier) +function modifier_card_28.prototype.getValue(self, key, fallback) + return self:getCardValue(key, fallback, CARD_ID) +end +function modifier_card_28.prototype.DeclareFunctions(self) + return {MODIFIER_PROPERTY_PREATTACK_BONUS_DAMAGE, MODIFIER_PROPERTY_TOOLTIP} +end +function modifier_card_28.prototype.GetModifierPreAttack_BonusDamage(self) + local hero = self:GetParent() + if not hero or not IsValidEntity(hero) then + return 0 + end + local healthToDamagePct = self:getScaledCardValue("health_to_damage_pct", 15) + if healthToDamagePct <= 0 then + return 0 + end + return hero:GetHealth() * (healthToDamagePct / 100) +end +modifier_card_28 = __TS__Decorate( + modifier_card_28, + modifier_card_28, + {registerModifier(nil)}, + {kind = "class", name = "modifier_card_28"} +) +____exports.modifier_card_28 = modifier_card_28 +return ____exports diff --git a/scripts/vscripts/cards/examples/card_29.lua b/scripts/vscripts/cards/examples/card_29.lua new file mode 100644 index 0000000..968e6e1 --- /dev/null +++ b/scripts/vscripts/cards/examples/card_29.lua @@ -0,0 +1,131 @@ +local ____lualib = require("lualib_bundle") +local __TS__Class = ____lualib.__TS__Class +local __TS__ClassExtends = ____lualib.__TS__ClassExtends +local __TS__Decorate = ____lualib.__TS__Decorate +local ____exports = {} +local ____CardSystem = require("cards.CardSystem") +local CardBase = ____CardSystem.CardBase +local RegisterCard = ____CardSystem.RegisterCard +local ____CardBaseModifier = require("cards.CardBaseModifier") +local CardBaseModifier = ____CardBaseModifier.CardBaseModifier +local ____dota_ts_adapter = require("lib.dota_ts_adapter") +local registerModifier = ____dota_ts_adapter.registerModifier +local ____vampirism = require("utils.vampirism") +local addPhysicalVampirism = ____vampirism.addPhysicalVampirism +local reducePhysicalVampirism = ____vampirism.reducePhysicalVampirism +local ____incoming_damage_reduction_combine = require("utils.incoming_damage_reduction_combine") +local removeIncomingDamageReductionSource = ____incoming_damage_reduction_combine.removeIncomingDamageReductionSource +local setIncomingDamageReductionSource = ____incoming_damage_reduction_combine.setIncomingDamageReductionSource +local CARD_ID = 29 +local CARD_29_INCOMING_SOURCE = "modifier_card_29" +____exports.card_29 = __TS__Class() +local card_29 = ____exports.card_29 +card_29.name = "card_29" +card_29.____file_path = "scripts/vscripts/cards/examples/card_29.lua" +__TS__ClassExtends(card_29, CardBase) +function card_29.prototype.GetModifierName(self) + return "modifier_card_29" +end +card_29 = __TS__Decorate(card_29, card_29, {RegisterCard}, {kind = "class", name = "card_29"}) +____exports.card_29 = card_29 +____exports.modifier_card_29 = __TS__Class() +local modifier_card_29 = ____exports.modifier_card_29 +modifier_card_29.name = "modifier_card_29" +modifier_card_29.____file_path = "scripts/vscripts/cards/examples/card_29.lua" +__TS__ClassExtends(modifier_card_29, CardBaseModifier) +function modifier_card_29.prototype.____constructor(self, ...) + CardBaseModifier.prototype.____constructor(self, ...) + self.nightBonusApplied = false + self.appliedVampirism = 0 +end +function modifier_card_29.prototype.getValue(self, key, fallback) + return self:getCardValue(key, fallback, CARD_ID) +end +function modifier_card_29.prototype.OnCustomCreated(self, params) + if not IsServer() then + return + end + setIncomingDamageReductionSource( + nil, + self:GetParent(), + CARD_29_INCOMING_SOURCE, + function() + if GameRules:IsDaytime() then + return 0 + end + return math.max( + 0, + self:getScaledCardValue("night_incoming_damage_reduce_pct", 5) + ) + end + ) + self:updateNightState() + self:StartIntervalThink(0.5) +end +function modifier_card_29.prototype.OnCustomRefresh(self, _params) + if not IsServer() then + return + end + local hero = self:GetParent() + if hero and IsValidEntity(hero) and self.nightBonusApplied then + self:removeNightBonus(hero) + end + self:updateNightState() +end +function modifier_card_29.prototype.OnDestroy(self) + if not IsServer() then + return + end + local hero = self:GetParent() + if not hero or not IsValidEntity(hero) then + return + end + removeIncomingDamageReductionSource(nil, hero, CARD_29_INCOMING_SOURCE) + self:removeNightBonus(hero) +end +function modifier_card_29.prototype.OnIntervalThink(self) + if not IsServer() then + return + end + self:updateNightState() +end +function modifier_card_29.prototype.updateNightState(self) + local hero = self:GetParent() + if not hero or not IsValidEntity(hero) or not hero:IsRealHero() then + return + end + if GameRules:IsDaytime() then + self:removeNightBonus(hero) + return + end + self:applyNightBonus(hero) +end +function modifier_card_29.prototype.applyNightBonus(self, hero) + if self.nightBonusApplied then + return + end + local vampirismBonus = self:getScaledCardValue("night_vampirism_pct", 10) + if vampirismBonus > 0 then + addPhysicalVampirism(nil, hero, vampirismBonus) + self.appliedVampirism = vampirismBonus + end + self.nightBonusApplied = true +end +function modifier_card_29.prototype.removeNightBonus(self, hero) + if not self.nightBonusApplied then + return + end + if self.appliedVampirism > 0 then + reducePhysicalVampirism(nil, hero, self.appliedVampirism) + self.appliedVampirism = 0 + end + self.nightBonusApplied = false +end +modifier_card_29 = __TS__Decorate( + modifier_card_29, + modifier_card_29, + {registerModifier(nil)}, + {kind = "class", name = "modifier_card_29"} +) +____exports.modifier_card_29 = modifier_card_29 +return ____exports diff --git a/scripts/vscripts/cards/examples/card_3.lua b/scripts/vscripts/cards/examples/card_3.lua new file mode 100644 index 0000000..e2fe078 --- /dev/null +++ b/scripts/vscripts/cards/examples/card_3.lua @@ -0,0 +1,85 @@ +local ____lualib = require("lualib_bundle") +local __TS__Class = ____lualib.__TS__Class +local __TS__ClassExtends = ____lualib.__TS__ClassExtends +local __TS__Decorate = ____lualib.__TS__Decorate +local ____exports = {} +local ____CardSystem = require("cards.CardSystem") +local CardBase = ____CardSystem.CardBase +local RegisterCard = ____CardSystem.RegisterCard +local ShowCardSelectionToPlayer = ____CardSystem.ShowCardSelectionToPlayer +local ____CardBaseModifier = require("cards.CardBaseModifier") +local CardBaseModifier = ____CardBaseModifier.CardBaseModifier +local ____dota_ts_adapter = require("lib.dota_ts_adapter") +local registerModifier = ____dota_ts_adapter.registerModifier +local ____card_value_resolver = require("cards.card_value_resolver") +local getCardValueByLevel = ____card_value_resolver.getCardValueByLevel +____exports.card_3 = __TS__Class() +local card_3 = ____exports.card_3 +card_3.name = "card_3" +card_3.____file_path = "scripts/vscripts/cards/examples/card_3.lua" +__TS__ClassExtends(card_3, CardBase) +function card_3.prototype.GetModifierName(self) + return "modifier_card_3" +end +card_3 = __TS__Decorate(card_3, card_3, {RegisterCard}, {kind = "class", name = "card_3"}) +____exports.card_3 = card_3 +____exports.modifier_card_3 = __TS__Class() +local modifier_card_3 = ____exports.modifier_card_3 +modifier_card_3.name = "modifier_card_3" +modifier_card_3.____file_path = "scripts/vscripts/cards/examples/card_3.lua" +__TS__ClassExtends(modifier_card_3, CardBaseModifier) +function modifier_card_3.prototype.OnCustomCreated(self, _params) + if not IsServer() then + return + end + local hero = self:GetParent() + if not hero or not IsValidEntity(hero) or not hero:IsRealHero() then + return + end + local playerId = hero:GetPlayerID() + local card53Copies = self:getCard53Copies(hero) + if playerId ~= -1 and card53Copies > 0 then + local extraChoicesPerCopy = math.max( + 1, + math.floor(getCardValueByLevel( + nil, + 53, + hero, + "extra_card_choices", + 3 + )) + ) + local totalExtraChoices = extraChoicesPerCopy * card53Copies + ShowCardSelectionToPlayer(nil, playerId, totalExtraChoices, "card_3_with_card_53") + end +end +function modifier_card_3.prototype.getCard53Copies(self, hero) + local copies = 0 + do + local i = 0 + while i < hero:GetModifierCount() do + if hero:GetModifierNameByIndex(i) == "modifier_card_53" then + copies = copies + 1 + end + i = i + 1 + end + end + return copies +end +function modifier_card_3.prototype.DeclareFunctions(self) + return {MODIFIER_PROPERTY_SPELL_AMPLIFY_PERCENTAGE, MODIFIER_PROPERTY_DAMAGEOUTGOING_PERCENTAGE, MODIFIER_PROPERTY_TOOLTIP} +end +function modifier_card_3.prototype.GetModifierSpellAmplify_Percentage(self, _event) + return self:getScaledCardValue("damage_pct", 5, 3) +end +function modifier_card_3.prototype.GetModifierDamageOutgoing_Percentage(self, _event) + return self:getScaledCardValue("damage_pct", 5, 3) +end +modifier_card_3 = __TS__Decorate( + modifier_card_3, + modifier_card_3, + {registerModifier(nil)}, + {kind = "class", name = "modifier_card_3"} +) +____exports.modifier_card_3 = modifier_card_3 +return ____exports diff --git a/scripts/vscripts/cards/examples/card_30.lua b/scripts/vscripts/cards/examples/card_30.lua new file mode 100644 index 0000000..4f05ada --- /dev/null +++ b/scripts/vscripts/cards/examples/card_30.lua @@ -0,0 +1,135 @@ +local ____lualib = require("lualib_bundle") +local __TS__Class = ____lualib.__TS__Class +local __TS__ClassExtends = ____lualib.__TS__ClassExtends +local __TS__Decorate = ____lualib.__TS__Decorate +local ____exports = {} +local ____CardSystem = require("cards.CardSystem") +local CardBase = ____CardSystem.CardBase +local RegisterCard = ____CardSystem.RegisterCard +local ____CardBaseModifier = require("cards.CardBaseModifier") +local CardBaseModifier = ____CardBaseModifier.CardBaseModifier +local ____dota_ts_adapter = require("lib.dota_ts_adapter") +local registerModifier = ____dota_ts_adapter.registerModifier +local ____modifier_card_cursed = require("cards.modifier_card_cursed") +local addCursedStack = ____modifier_card_cursed.addCursedStack +local getCursedStackCount = ____modifier_card_cursed.getCursedStackCount +local removeCursedStack = ____modifier_card_cursed.removeCursedStack +local CARD_ID = 30 +____exports.card_30 = __TS__Class() +local card_30 = ____exports.card_30 +card_30.name = "card_30" +card_30.____file_path = "scripts/vscripts/cards/examples/card_30.lua" +__TS__ClassExtends(card_30, CardBase) +function card_30.prototype.GetModifierName(self) + return "modifier_card_30" +end +card_30 = __TS__Decorate(card_30, card_30, {RegisterCard}, {kind = "class", name = "card_30"}) +____exports.card_30 = card_30 +____exports.modifier_card_30 = __TS__Class() +local modifier_card_30 = ____exports.modifier_card_30 +modifier_card_30.name = "modifier_card_30" +modifier_card_30.____file_path = "scripts/vscripts/cards/examples/card_30.lua" +__TS__ClassExtends(modifier_card_30, CardBaseModifier) +function modifier_card_30.prototype.____constructor(self, ...) + CardBaseModifier.prototype.____constructor(self, ...) + self.cursedApplied = false + self.levelOnPickup = 1 +end +function modifier_card_30.prototype.getValue(self, key, fallback) + return self:getCardValue(key, fallback, CARD_ID) +end +function modifier_card_30.prototype.OnCustomCreated(self, params) + if not IsServer() then + return + end + self:capturePickupLevel() + self:applyCursed() +end +function modifier_card_30.prototype.OnDestroy(self) + if not IsServer() then + return + end + local hero = self:GetParent() + if not hero or not IsValidEntity(hero) then + return + end + if not self.cursedApplied then + return + end + removeCursedStack(nil, hero) + self.cursedApplied = false +end +function modifier_card_30.prototype.DeclareFunctions(self) + return {MODIFIER_PROPERTY_STATS_STRENGTH_BONUS, MODIFIER_PROPERTY_STATS_AGILITY_BONUS, MODIFIER_PROPERTY_STATS_INTELLECT_BONUS, MODIFIER_PROPERTY_TOOLTIP} +end +function modifier_card_30.prototype.GetModifierBonusStats_Strength(self) + local hero = self:GetParent() + if not hero or not IsValidEntity(hero) then + return 0 + end + local level = self:getLevelsAfterPickup(hero) + local curses = getCursedStackCount(nil, hero) + if level <= 0 or curses <= 0 then + return 0 + end + local strengthPerLevel = self:getValue("strength_per_level", 2) + return level * strengthPerLevel * curses * self:getCardCopies() +end +function modifier_card_30.prototype.GetModifierBonusStats_Agility(self) + local hero = self:GetParent() + if not hero or not IsValidEntity(hero) then + return 0 + end + local level = self:getLevelsAfterPickup(hero) + local curses = getCursedStackCount(nil, hero) + if level <= 0 or curses <= 0 then + return 0 + end + local agilityPenaltyPerLevel = self:getValue("agility_penalty_per_level", 1) + return -level * agilityPenaltyPerLevel * curses * self:getCardCopies() +end +function modifier_card_30.prototype.GetModifierBonusStats_Intellect(self) + local hero = self:GetParent() + if not hero or not IsValidEntity(hero) then + return 0 + end + local level = self:getLevelsAfterPickup(hero) + local curses = getCursedStackCount(nil, hero) + if level <= 0 or curses <= 0 then + return 0 + end + local intellectPenaltyPerLevel = self:getValue("intellect_penalty_per_level", 1) + return -level * intellectPenaltyPerLevel * curses * self:getCardCopies() +end +function modifier_card_30.prototype.applyCursed(self) + if self.cursedApplied then + return + end + local hero = self:GetParent() + if not hero or not IsValidEntity(hero) or not hero:IsRealHero() then + return + end + addCursedStack(nil, hero) + self.cursedApplied = true +end +function modifier_card_30.prototype.getLevelsAfterPickup(self, hero) + return math.max( + 0, + hero:GetLevel() - self.levelOnPickup + ) +end +function modifier_card_30.prototype.capturePickupLevel(self) + local hero = self:GetParent() + if not hero or not IsValidEntity(hero) or not hero:IsRealHero() then + return + end + self.levelOnPickup = hero:GetLevel() +end +modifier_card_30 = __TS__Decorate( + modifier_card_30, + modifier_card_30, + {registerModifier(nil)}, + {kind = "class", name = "modifier_card_30"} +) +____exports.modifier_card_30 = modifier_card_30 +return ____exports diff --git a/scripts/vscripts/cards/examples/card_31.lua b/scripts/vscripts/cards/examples/card_31.lua new file mode 100644 index 0000000..eb8a913 --- /dev/null +++ b/scripts/vscripts/cards/examples/card_31.lua @@ -0,0 +1,135 @@ +local ____lualib = require("lualib_bundle") +local __TS__Class = ____lualib.__TS__Class +local __TS__ClassExtends = ____lualib.__TS__ClassExtends +local __TS__Decorate = ____lualib.__TS__Decorate +local ____exports = {} +local ____CardSystem = require("cards.CardSystem") +local CardBase = ____CardSystem.CardBase +local RegisterCard = ____CardSystem.RegisterCard +local ____CardBaseModifier = require("cards.CardBaseModifier") +local CardBaseModifier = ____CardBaseModifier.CardBaseModifier +local ____dota_ts_adapter = require("lib.dota_ts_adapter") +local registerModifier = ____dota_ts_adapter.registerModifier +local ____modifier_card_cursed = require("cards.modifier_card_cursed") +local addCursedStack = ____modifier_card_cursed.addCursedStack +local getCursedStackCount = ____modifier_card_cursed.getCursedStackCount +local removeCursedStack = ____modifier_card_cursed.removeCursedStack +local CARD_ID = 31 +____exports.card_31 = __TS__Class() +local card_31 = ____exports.card_31 +card_31.name = "card_31" +card_31.____file_path = "scripts/vscripts/cards/examples/card_31.lua" +__TS__ClassExtends(card_31, CardBase) +function card_31.prototype.GetModifierName(self) + return "modifier_card_31" +end +card_31 = __TS__Decorate(card_31, card_31, {RegisterCard}, {kind = "class", name = "card_31"}) +____exports.card_31 = card_31 +____exports.modifier_card_31 = __TS__Class() +local modifier_card_31 = ____exports.modifier_card_31 +modifier_card_31.name = "modifier_card_31" +modifier_card_31.____file_path = "scripts/vscripts/cards/examples/card_31.lua" +__TS__ClassExtends(modifier_card_31, CardBaseModifier) +function modifier_card_31.prototype.____constructor(self, ...) + CardBaseModifier.prototype.____constructor(self, ...) + self.cursedApplied = false + self.levelOnPickup = 1 +end +function modifier_card_31.prototype.getValue(self, key, fallback) + return self:getCardValue(key, fallback, CARD_ID) +end +function modifier_card_31.prototype.OnCustomCreated(self, params) + if not IsServer() then + return + end + self:capturePickupLevel() + self:applyCursed() +end +function modifier_card_31.prototype.OnDestroy(self) + if not IsServer() then + return + end + local hero = self:GetParent() + if not hero or not IsValidEntity(hero) then + return + end + if not self.cursedApplied then + return + end + removeCursedStack(nil, hero) + self.cursedApplied = false +end +function modifier_card_31.prototype.DeclareFunctions(self) + return {MODIFIER_PROPERTY_STATS_STRENGTH_BONUS, MODIFIER_PROPERTY_STATS_AGILITY_BONUS, MODIFIER_PROPERTY_STATS_INTELLECT_BONUS, MODIFIER_PROPERTY_TOOLTIP} +end +function modifier_card_31.prototype.GetModifierBonusStats_Strength(self) + local hero = self:GetParent() + if not hero or not IsValidEntity(hero) then + return 0 + end + local level = self:getLevelsAfterPickup(hero) + local curses = getCursedStackCount(nil, hero) + if level <= 0 or curses <= 0 then + return 0 + end + local strengthPenaltyPerLevel = self:getValue("strength_penalty_per_level", 1) + return -level * strengthPenaltyPerLevel * curses * self:getCardCopies() +end +function modifier_card_31.prototype.GetModifierBonusStats_Agility(self) + local hero = self:GetParent() + if not hero or not IsValidEntity(hero) then + return 0 + end + local level = self:getLevelsAfterPickup(hero) + local curses = getCursedStackCount(nil, hero) + if level <= 0 or curses <= 0 then + return 0 + end + local agilityPerLevel = self:getValue("agility_per_level", 2) + return level * agilityPerLevel * curses * self:getCardCopies() +end +function modifier_card_31.prototype.GetModifierBonusStats_Intellect(self) + local hero = self:GetParent() + if not hero or not IsValidEntity(hero) then + return 0 + end + local level = self:getLevelsAfterPickup(hero) + local curses = getCursedStackCount(nil, hero) + if level <= 0 or curses <= 0 then + return 0 + end + local intellectPenaltyPerLevel = self:getValue("intellect_penalty_per_level", 1) + return -level * intellectPenaltyPerLevel * curses * self:getCardCopies() +end +function modifier_card_31.prototype.applyCursed(self) + if self.cursedApplied then + return + end + local hero = self:GetParent() + if not hero or not IsValidEntity(hero) or not hero:IsRealHero() then + return + end + addCursedStack(nil, hero) + self.cursedApplied = true +end +function modifier_card_31.prototype.getLevelsAfterPickup(self, hero) + return math.max( + 0, + hero:GetLevel() - self.levelOnPickup + ) +end +function modifier_card_31.prototype.capturePickupLevel(self) + local hero = self:GetParent() + if not hero or not IsValidEntity(hero) or not hero:IsRealHero() then + return + end + self.levelOnPickup = hero:GetLevel() +end +modifier_card_31 = __TS__Decorate( + modifier_card_31, + modifier_card_31, + {registerModifier(nil)}, + {kind = "class", name = "modifier_card_31"} +) +____exports.modifier_card_31 = modifier_card_31 +return ____exports diff --git a/scripts/vscripts/cards/examples/card_32.lua b/scripts/vscripts/cards/examples/card_32.lua new file mode 100644 index 0000000..80e92b6 --- /dev/null +++ b/scripts/vscripts/cards/examples/card_32.lua @@ -0,0 +1,60 @@ +local ____lualib = require("lualib_bundle") +local __TS__Class = ____lualib.__TS__Class +local __TS__ClassExtends = ____lualib.__TS__ClassExtends +local __TS__Decorate = ____lualib.__TS__Decorate +local ____exports = {} +local ____CardSystem = require("cards.CardSystem") +local CardBase = ____CardSystem.CardBase +local RegisterCard = ____CardSystem.RegisterCard +local ____CardBaseModifier = require("cards.CardBaseModifier") +local CardBaseModifier = ____CardBaseModifier.CardBaseModifier +local ____dota_ts_adapter = require("lib.dota_ts_adapter") +local registerModifier = ____dota_ts_adapter.registerModifier +local CARD_ID = 32 +____exports.card_32 = __TS__Class() +local card_32 = ____exports.card_32 +card_32.name = "card_32" +card_32.____file_path = "scripts/vscripts/cards/examples/card_32.lua" +__TS__ClassExtends(card_32, CardBase) +function card_32.prototype.GetModifierName(self) + return "modifier_card_32" +end +card_32 = __TS__Decorate(card_32, card_32, {RegisterCard}, {kind = "class", name = "card_32"}) +____exports.card_32 = card_32 +____exports.modifier_card_32 = __TS__Class() +local modifier_card_32 = ____exports.modifier_card_32 +modifier_card_32.name = "modifier_card_32" +modifier_card_32.____file_path = "scripts/vscripts/cards/examples/card_32.lua" +__TS__ClassExtends(modifier_card_32, CardBaseModifier) +function modifier_card_32.prototype.____constructor(self, ...) + CardBaseModifier.prototype.____constructor(self, ...) + self.deathPenaltyStacks = 0 +end +function modifier_card_32.prototype.getValue(self, key, fallback) + return self:getCardValue(key, fallback, CARD_ID) +end +function modifier_card_32.prototype.DeclareFunctions(self) + return {MODIFIER_PROPERTY_COOLDOWN_PERCENTAGE, MODIFIER_EVENT_ON_DEATH, MODIFIER_PROPERTY_TOOLTIP} +end +function modifier_card_32.prototype.GetModifierPercentageCooldown(self) + local baseCooldownReduction = self:getScaledCardValue("base_cooldown_reduction_pct", 25) + local cooldownLossPerDeath = self:getValue("cooldown_loss_per_death_pct", 5) + return math.max(0, baseCooldownReduction - cooldownLossPerDeath * self.deathPenaltyStacks) +end +function modifier_card_32.prototype.OnDeath(self, event) + if not IsServer() then + return + end + if event.unit ~= self:GetParent() then + return + end + self.deathPenaltyStacks = self.deathPenaltyStacks + 1 +end +modifier_card_32 = __TS__Decorate( + modifier_card_32, + modifier_card_32, + {registerModifier(nil)}, + {kind = "class", name = "modifier_card_32"} +) +____exports.modifier_card_32 = modifier_card_32 +return ____exports diff --git a/scripts/vscripts/cards/examples/card_33.lua b/scripts/vscripts/cards/examples/card_33.lua new file mode 100644 index 0000000..bc02700 --- /dev/null +++ b/scripts/vscripts/cards/examples/card_33.lua @@ -0,0 +1,93 @@ +local ____lualib = require("lualib_bundle") +local __TS__Class = ____lualib.__TS__Class +local __TS__ClassExtends = ____lualib.__TS__ClassExtends +local __TS__Decorate = ____lualib.__TS__Decorate +local ____exports = {} +local ____CardSystem = require("cards.CardSystem") +local CardBase = ____CardSystem.CardBase +local RegisterCard = ____CardSystem.RegisterCard +local ShowCardSelectionExactOptionsToPlayer = ____CardSystem.ShowCardSelectionExactOptionsToPlayer +local ____CardBaseModifier = require("cards.CardBaseModifier") +local CardBaseModifier = ____CardBaseModifier.CardBaseModifier +local ____dota_ts_adapter = require("lib.dota_ts_adapter") +local registerModifier = ____dota_ts_adapter.registerModifier +local ____card_catalog = require("card_catalog") +local SMALL_MANA_SURGE_CARD_ID = ____card_catalog.SMALL_MANA_SURGE_CARD_ID +local CARD_ID = 33 +local SMALL_MANA_SURGE_SOURCE = "card_33_small_mana_surge" +____exports.card_33 = __TS__Class() +local card_33 = ____exports.card_33 +card_33.name = "card_33" +card_33.____file_path = "scripts/vscripts/cards/examples/card_33.lua" +__TS__ClassExtends(card_33, CardBase) +function card_33.prototype.OnCreated(self) + if not IsServer() then + return + end + local hero = self:GetHero() + if not hero or not IsValidEntity(hero) or not hero:IsRealHero() then + return + end + local playerId = hero:GetPlayerOwnerID() + if playerId == nil or playerId == nil or playerId < 0 then + return + end + local player = PlayerResource:GetPlayer(playerId) + local ____opt_0 = player and player.cardSystem + local level = ____opt_0 and ____opt_0:GetCardLevel(CARD_ID) or 1 + if level >= 3 then + ShowCardSelectionExactOptionsToPlayer(nil, playerId, {SMALL_MANA_SURGE_CARD_ID}, SMALL_MANA_SURGE_SOURCE) + end +end +function card_33.prototype.GetModifierName(self) + return "modifier_card_33" +end +card_33 = __TS__Decorate(card_33, card_33, {RegisterCard}, {kind = "class", name = "card_33"}) +____exports.card_33 = card_33 +____exports.modifier_card_33 = __TS__Class() +local modifier_card_33 = ____exports.modifier_card_33 +modifier_card_33.name = "modifier_card_33" +modifier_card_33.____file_path = "scripts/vscripts/cards/examples/card_33.lua" +__TS__ClassExtends(modifier_card_33, CardBaseModifier) +function modifier_card_33.prototype.____constructor(self, ...) + CardBaseModifier.prototype.____constructor(self, ...) + self.manaBonusSnapshot = 0 +end +function modifier_card_33.prototype.getValue(self, key, fallback) + return self:getCardValue(key, fallback, CARD_ID) +end +function modifier_card_33.prototype.addManaSnapshotBonus(self) + local hero = self:GetParent() + if not hero or not IsValidEntity(hero) then + return + end + local manaIncreasePct = self:getValue("mana_increase_pct", 25) + local manaBonus = math.floor(hero:GetMaxMana() * (manaIncreasePct / 100)) + self.manaBonusSnapshot = self.manaBonusSnapshot + math.max(0, manaBonus) +end +function modifier_card_33.prototype.OnCustomCreated(self, _params) + if not IsServer() then + return + end + self:addManaSnapshotBonus() +end +function modifier_card_33.prototype.OnCustomRefresh(self, _params) + if not IsServer() then + return + end + self:addManaSnapshotBonus() +end +function modifier_card_33.prototype.DeclareFunctions(self) + return {MODIFIER_PROPERTY_MANA_BONUS, MODIFIER_PROPERTY_TOOLTIP} +end +function modifier_card_33.prototype.GetModifierManaBonus(self) + return self.manaBonusSnapshot +end +modifier_card_33 = __TS__Decorate( + modifier_card_33, + modifier_card_33, + {registerModifier(nil)}, + {kind = "class", name = "modifier_card_33"} +) +____exports.modifier_card_33 = modifier_card_33 +return ____exports diff --git a/scripts/vscripts/cards/examples/card_34.lua b/scripts/vscripts/cards/examples/card_34.lua new file mode 100644 index 0000000..7a9572c --- /dev/null +++ b/scripts/vscripts/cards/examples/card_34.lua @@ -0,0 +1,97 @@ +local ____lualib = require("lualib_bundle") +local __TS__Class = ____lualib.__TS__Class +local __TS__ClassExtends = ____lualib.__TS__ClassExtends +local __TS__Decorate = ____lualib.__TS__Decorate +local ____exports = {} +local ____CardSystem = require("cards.CardSystem") +local CardBase = ____CardSystem.CardBase +local RegisterCard = ____CardSystem.RegisterCard +local ____CardBaseModifier = require("cards.CardBaseModifier") +local CardBaseModifier = ____CardBaseModifier.CardBaseModifier +local ____dota_ts_adapter = require("lib.dota_ts_adapter") +local registerModifier = ____dota_ts_adapter.registerModifier +local ____vampirism = require("utils.vampirism") +local precacheVampirismParticle = ____vampirism.precacheVampirismParticle +local ____heal_tracker = require("utils.heal_tracker") +local HealWithBattlePass = ____heal_tracker.HealWithBattlePass +local CARD_ID = 34 +____exports.card_34 = __TS__Class() +local card_34 = ____exports.card_34 +card_34.name = "card_34" +card_34.____file_path = "scripts/vscripts/cards/examples/card_34.lua" +__TS__ClassExtends(card_34, CardBase) +function card_34.prototype.GetModifierName(self) + return "modifier_card_34" +end +card_34 = __TS__Decorate(card_34, card_34, {RegisterCard}, {kind = "class", name = "card_34"}) +____exports.card_34 = card_34 +____exports.modifier_card_34 = __TS__Class() +local modifier_card_34 = ____exports.modifier_card_34 +modifier_card_34.name = "modifier_card_34" +modifier_card_34.____file_path = "scripts/vscripts/cards/examples/card_34.lua" +__TS__ClassExtends(modifier_card_34, CardBaseModifier) +function modifier_card_34.prototype.Precache(self, context) + precacheVampirismParticle(nil, context) +end +function modifier_card_34.prototype.getValue(self, key, fallback) + return self:getCardValue(key, fallback, CARD_ID) +end +function modifier_card_34.prototype.DeclareFunctions(self) + return {MODIFIER_EVENT_ON_DEATH, MODIFIER_PROPERTY_TOOLTIP} +end +function modifier_card_34.prototype.OnDeath(self, event) + if not IsServer() then + return + end + local hero = self:GetParent() + local deadUnit = event.unit + if not hero or not IsValidEntity(hero) or not deadUnit or not IsValidEntity(deadUnit) then + return + end + if event.attacker ~= hero then + return + end + if deadUnit:GetTeamNumber() == hero:GetTeamNumber() then + return + end + local copies = self:getCardCopies() + local healFromEnemyPct = self:getValue("heal_from_enemy_max_hp_pct", 15) * copies + local healFromAttackDamagePct = self:getValue("heal_from_attack_damage_pct", 100) * copies + local healFromEnemy = deadUnit:GetMaxHealth() * (healFromEnemyPct / 100) + local attackDamage = hero:GetAverageTrueAttackDamage(deadUnit) + local healFromAttack = attackDamage * (healFromAttackDamagePct / 100) + local totalHeal = healFromEnemy + healFromAttack + if totalHeal <= 0 then + return + end + HealWithBattlePass( + nil, + hero, + healFromEnemy, + nil, + hero + ) + SendOverheadEventMessage( + nil, + OVERHEAD_ALERT_HEAL, + hero, + totalHeal, + hero:GetPlayerOwner() + ) + local pfx = ParticleManager:CreateParticle("particles/units/heroes/hero_bloodseeker/bloodseeker_bloodbath.vpcf", PATTACH_ABSORIGIN_FOLLOW, hero) + ParticleManager:SetParticleControl( + pfx, + 0, + hero:GetAbsOrigin() + ) + ParticleManager:ReleaseParticleIndex(pfx) + EmitSoundOn("hero_bloodseeker.bloodRite.silence", hero) +end +modifier_card_34 = __TS__Decorate( + modifier_card_34, + modifier_card_34, + {registerModifier(nil)}, + {kind = "class", name = "modifier_card_34"} +) +____exports.modifier_card_34 = modifier_card_34 +return ____exports diff --git a/scripts/vscripts/cards/examples/card_35.lua b/scripts/vscripts/cards/examples/card_35.lua new file mode 100644 index 0000000..edf42cc --- /dev/null +++ b/scripts/vscripts/cards/examples/card_35.lua @@ -0,0 +1,151 @@ +local ____lualib = require("lualib_bundle") +local __TS__Class = ____lualib.__TS__Class +local __TS__ClassExtends = ____lualib.__TS__ClassExtends +local __TS__Decorate = ____lualib.__TS__Decorate +local __TS__StringIncludes = ____lualib.__TS__StringIncludes +local ____exports = {} +local ____CardSystem = require("cards.CardSystem") +local CardBase = ____CardSystem.CardBase +local RegisterCard = ____CardSystem.RegisterCard +local ____CardBaseModifier = require("cards.CardBaseModifier") +local CardBaseModifier = ____CardBaseModifier.CardBaseModifier +local ____dota_ts_adapter = require("lib.dota_ts_adapter") +local registerModifier = ____dota_ts_adapter.registerModifier +local CARD_ID = 35 +local POISON_MODIFIER = "modifier_card_35_poison" +____exports.card_35 = __TS__Class() +local card_35 = ____exports.card_35 +card_35.name = "card_35" +card_35.____file_path = "scripts/vscripts/cards/examples/card_35.lua" +__TS__ClassExtends(card_35, CardBase) +function card_35.prototype.GetModifierName(self) + return "modifier_card_35" +end +card_35 = __TS__Decorate(card_35, card_35, {RegisterCard}, {kind = "class", name = "card_35"}) +____exports.card_35 = card_35 +____exports.modifier_card_35 = __TS__Class() +local modifier_card_35 = ____exports.modifier_card_35 +modifier_card_35.name = "modifier_card_35" +modifier_card_35.____file_path = "scripts/vscripts/cards/examples/card_35.lua" +__TS__ClassExtends(modifier_card_35, CardBaseModifier) +function modifier_card_35.prototype.getValue(self, key, fallback) + return self:getCardValue(key, fallback, CARD_ID) +end +function modifier_card_35.prototype.DeclareFunctions(self) + return {MODIFIER_EVENT_ON_ATTACK_LANDED, MODIFIER_PROPERTY_TOOLTIP} +end +function modifier_card_35.prototype.OnAttackLanded(self, event) + if not IsServer() then + return + end + local attacker = self:GetParent() + if not attacker or not IsValidEntity(attacker) or event.attacker ~= attacker then + return + end + local target = event.target + if not target or not IsValidEntity(target) or not target:IsAlive() then + return + end + if target:GetTeamNumber() == attacker:GetTeamNumber() then + return + end + local duration = self:getValue("poison_duration", 3) + target:AddNewModifier( + attacker, + getModifierSourceAbility(nil, attacker), + POISON_MODIFIER, + {duration = duration} + ) +end +modifier_card_35 = __TS__Decorate( + modifier_card_35, + modifier_card_35, + {registerModifier(nil)}, + {kind = "class", name = "modifier_card_35"} +) +____exports.modifier_card_35 = modifier_card_35 +____exports.modifier_card_35_poison = __TS__Class() +local modifier_card_35_poison = ____exports.modifier_card_35_poison +modifier_card_35_poison.name = "modifier_card_35_poison" +modifier_card_35_poison.____file_path = "scripts/vscripts/cards/examples/card_35.lua" +__TS__ClassExtends(modifier_card_35_poison, CardBaseModifier) +function modifier_card_35_poison.prototype.getCardCopiesFromOwner(self) + local owner = self:GetCaster() + if not owner or not IsValidEntity(owner) then + return 1 + end + local cardMod = owner:FindModifierByName("modifier_card_35") + return math.max( + 1, + math.floor(cardMod and cardMod:GetStackCount() or 0) + ) +end +function modifier_card_35_poison.prototype.getValue(self, key, fallback) + return self:getCardValue(key, fallback, CARD_ID) * self:getCardCopiesFromOwner() +end +function modifier_card_35_poison.prototype.IsDebuff(self) + return true +end +function modifier_card_35_poison.prototype.IsPurgable(self) + return true +end +function modifier_card_35_poison.prototype.GetEffectName(self) + return "particles/units/heroes/hero_viper/viper_poison_debuff.vpcf" +end +function modifier_card_35_poison.prototype.GetEffectAttachType(self) + return PATTACH_ABSORIGIN_FOLLOW +end +function modifier_card_35_poison.prototype.OnCustomCreated(self, params) + if not IsServer() then + return + end + local tickInterval = self:getValue("poison_tick_interval", 1) + self:StartIntervalThink(math.max(0.1, tickInterval)) +end +function modifier_card_35_poison.prototype.OnCustomRefresh(self, params) + if not IsServer() then + return + end + local tickInterval = self:getValue("poison_tick_interval", 1) + self:StartIntervalThink(math.max(0.1, tickInterval)) +end +function modifier_card_35_poison.prototype.isBoss(self, unit) + local unitName = string.lower(tostring(unit:GetUnitName() or "")) + return __TS__StringIncludes(unitName, "npc_boss") or __TS__StringIncludes(unitName, "wave_boss") or __TS__StringIncludes(unitName, "boss") +end +function modifier_card_35_poison.prototype.OnIntervalThink(self) + if not IsServer() then + return + end + local target = self:GetParent() + local attacker = self:GetCaster() + if not target or not IsValidEntity(target) or not target:IsAlive() then + return + end + if not attacker or not IsValidEntity(attacker) then + return + end + local hpPct = self:isBoss(target) and self:getValue("boss_damage_current_hp_pct", 1) or self:getValue("damage_current_hp_pct", 5) + local damage = target:GetHealth() * (hpPct / 100) + if damage <= 0 then + return + end + local dealtDamage = ApplyDamage({victim = target, attacker = attacker, damage = damage, damage_type = DAMAGE_TYPE_MAGICAL}) + if dealtDamage > 0 then + SendOverheadEventMessage( + nil, + OVERHEAD_ALERT_BONUS_SPELL_DAMAGE, + target, + math.floor(dealtDamage), + attacker:GetPlayerOwner() + ) + end +end +modifier_card_35_poison = __TS__Decorate( + modifier_card_35_poison, + modifier_card_35_poison, + {registerModifier(nil)}, + {kind = "class", name = "modifier_card_35_poison"} +) +____exports.modifier_card_35_poison = modifier_card_35_poison +return ____exports diff --git a/scripts/vscripts/cards/examples/card_36.lua b/scripts/vscripts/cards/examples/card_36.lua new file mode 100644 index 0000000..3b6957a --- /dev/null +++ b/scripts/vscripts/cards/examples/card_36.lua @@ -0,0 +1,121 @@ +local ____lualib = require("lualib_bundle") +local __TS__Class = ____lualib.__TS__Class +local __TS__ClassExtends = ____lualib.__TS__ClassExtends +local __TS__Decorate = ____lualib.__TS__Decorate +local ____exports = {} +local ____CardSystem = require("cards.CardSystem") +local CardBase = ____CardSystem.CardBase +local RegisterCard = ____CardSystem.RegisterCard +local ____CardBaseModifier = require("cards.CardBaseModifier") +local CardBaseModifier = ____CardBaseModifier.CardBaseModifier +local ____dota_ts_adapter = require("lib.dota_ts_adapter") +local registerModifier = ____dota_ts_adapter.registerModifier +local ____crystal_currency = require("crystal_currency") +local GetCrystalCurrency = ____crystal_currency.GetCrystalCurrency +local CARD_ID = 36 +____exports.card_36 = __TS__Class() +local card_36 = ____exports.card_36 +card_36.name = "card_36" +card_36.____file_path = "scripts/vscripts/cards/examples/card_36.lua" +__TS__ClassExtends(card_36, CardBase) +function card_36.prototype.GetModifierName(self) + return "modifier_card_36" +end +card_36 = __TS__Decorate(card_36, card_36, {RegisterCard}, {kind = "class", name = "card_36"}) +____exports.card_36 = card_36 +____exports.modifier_card_36 = __TS__Class() +local modifier_card_36 = ____exports.modifier_card_36 +modifier_card_36.name = "modifier_card_36" +modifier_card_36.____file_path = "scripts/vscripts/cards/examples/card_36.lua" +__TS__ClassExtends(modifier_card_36, CardBaseModifier) +function modifier_card_36.prototype.____constructor(self, ...) + CardBaseModifier.prototype.____constructor(self, ...) + self.spentCrystalsTracked = 0 + self.spendListenerId = nil + self.outgoingBonusStacks = 0 +end +function modifier_card_36.prototype.DeclareFunctions(self) + return {MODIFIER_PROPERTY_DAMAGEOUTGOING_PERCENTAGE, MODIFIER_PROPERTY_TOOLTIP} +end +function modifier_card_36.prototype.getValue(self, key, fallback) + return self:getCardValue(key, fallback, CARD_ID) +end +function modifier_card_36.prototype.getOwnerPlayerId(self) + local hero = self:GetParent() + if not hero or not IsValidEntity(hero) or not hero:IsRealHero() then + return nil + end + local playerId = hero:GetPlayerOwnerID() + if playerId == nil or playerId == nil or playerId < 0 then + return nil + end + return playerId +end +function modifier_card_36.prototype.OnCustomCreated(self, params) + if not IsServer() then + return + end + local playerId = self:getOwnerPlayerId() + if playerId == nil then + print("[CARD_36] OnCustomCreated: playerId undefined") + return + end + local currency = GetCrystalCurrency(nil) + self.spentCrystalsTracked = currency:getTotalSpentCrystals(playerId) + print((((("[CARD_36] OnCustomCreated: player=" .. tostring(playerId)) .. ", trackedSpent=") .. tostring(self.spentCrystalsTracked)) .. ", currentStacks=") .. tostring(self:GetStackCount())) + self:recalculateStacks() + self.spendListenerId = currency:addSpendListener(function(____, eventPlayerId, spentAmount, _newTotal, totalSpent) + if eventPlayerId ~= playerId then + return + end + if spentAmount <= 0 then + return + end + self.spentCrystalsTracked = totalSpent + print((((((("[CARD_36] SpendEvent: player=" .. tostring(playerId)) .. ", delta=") .. tostring(spentAmount)) .. ", trackedSpent=") .. tostring(self.spentCrystalsTracked)) .. ", stacksBefore=") .. tostring(self:GetStackCount())) + self:recalculateStacks() + end) + print((("[CARD_36] ListenerRegistered: player=" .. tostring(playerId)) .. ", listenerId=") .. tostring(self.spendListenerId)) +end +function modifier_card_36.prototype.OnDestroy(self) + if not IsServer() then + return + end + if self.spendListenerId ~= nil then + print("[CARD_36] OnDestroy: remove listenerId=" .. tostring(self.spendListenerId)) + GetCrystalCurrency(nil):removeSpendListener(self.spendListenerId) + self.spendListenerId = nil + end +end +function modifier_card_36.prototype.recalculateStacks(self) + local crystalsPerStep = math.max( + 1, + self:getValue("crystals_per_step", 2) + ) + local damagePctPerStep = self:getValue("damage_pct_per_step", 1) + local maxBonusPct = math.max( + 0, + self:getValue("max_bonus_pct", 40) + ) + if damagePctPerStep <= 0 then + self.outgoingBonusStacks = 0 + print("[CARD_36] Recalc: damagePctPerStep<=0 -> stacks=0") + return + end + local rawStacks = math.floor(self.spentCrystalsTracked / crystalsPerStep) + local maxStacks = math.floor(maxBonusPct / damagePctPerStep) + self.outgoingBonusStacks = math.min(rawStacks, maxStacks) + print((((((((("[CARD_36] Recalc: trackedSpent=" .. tostring(self.spentCrystalsTracked)) .. ", step=") .. tostring(crystalsPerStep)) .. ", raw=") .. tostring(rawStacks)) .. ", maxStacks=") .. tostring(maxStacks)) .. ", finalStacks=") .. tostring(self.outgoingBonusStacks)) +end +function modifier_card_36.prototype.GetModifierDamageOutgoing_Percentage(self, _event) + local damagePctPerStep = self:getValue("damage_pct_per_step", 1) + return self.outgoingBonusStacks * damagePctPerStep * self:getCardCopies() +end +modifier_card_36 = __TS__Decorate( + modifier_card_36, + modifier_card_36, + {registerModifier(nil)}, + {kind = "class", name = "modifier_card_36"} +) +____exports.modifier_card_36 = modifier_card_36 +return ____exports diff --git a/scripts/vscripts/cards/examples/card_37.lua b/scripts/vscripts/cards/examples/card_37.lua new file mode 100644 index 0000000..d2533ba --- /dev/null +++ b/scripts/vscripts/cards/examples/card_37.lua @@ -0,0 +1,64 @@ +local ____lualib = require("lualib_bundle") +local __TS__Class = ____lualib.__TS__Class +local __TS__ClassExtends = ____lualib.__TS__ClassExtends +local __TS__Decorate = ____lualib.__TS__Decorate +local ____exports = {} +local ____CardSystem = require("cards.CardSystem") +local CardBase = ____CardSystem.CardBase +local RegisterCard = ____CardSystem.RegisterCard +local ____card_value_resolver = require("cards.card_value_resolver") +local getCardValueByLevel = ____card_value_resolver.getCardValueByLevel +local ____crystal_currency = require("crystal_currency") +local GetCrystalCurrency = ____crystal_currency.GetCrystalCurrency +local ____WaveManager = require("WaveManager") +local WaveManager = ____WaveManager.WaveManager +local CARD_ID = 37 +____exports.card_37 = __TS__Class() +local card_37 = ____exports.card_37 +card_37.name = "card_37" +card_37.____file_path = "scripts/vscripts/cards/examples/card_37.lua" +__TS__ClassExtends(card_37, CardBase) +function card_37.prototype.OnCreated(self) + if not IsServer() then + return + end + local hero = self:GetHero() + if not hero or not IsValidEntity(hero) or not hero:IsRealHero() then + return + end + local playerId = hero:GetPlayerOwnerID() + if playerId == nil or playerId == nil or playerId < 0 then + return + end + local baseCrystals = self:getValue("base_crystals", 15) + local crystalsPerNight = self:getValue("crystals_per_night", 5) + local currentNight = math.max( + 0, + WaveManager:getInstance():GetCurrentNight() + ) + local crystalsReward = math.max( + 0, + math.floor(baseCrystals + crystalsPerNight * currentNight) + ) + if crystalsReward > 0 then + GetCrystalCurrency(nil):addCrystals(playerId, crystalsReward) + end +end +function card_37.prototype.GetModifierName(self) + return nil +end +function card_37.prototype.IsHidden(self) + return true +end +function card_37.prototype.getValue(self, key, fallback) + return getCardValueByLevel( + nil, + CARD_ID, + self:GetHero(), + key, + fallback + ) +end +card_37 = __TS__Decorate(card_37, card_37, {RegisterCard}, {kind = "class", name = "card_37"}) +____exports.card_37 = card_37 +return ____exports diff --git a/scripts/vscripts/cards/examples/card_38.lua b/scripts/vscripts/cards/examples/card_38.lua new file mode 100644 index 0000000..9b065d8 --- /dev/null +++ b/scripts/vscripts/cards/examples/card_38.lua @@ -0,0 +1,97 @@ +local ____lualib = require("lualib_bundle") +local __TS__Class = ____lualib.__TS__Class +local __TS__ClassExtends = ____lualib.__TS__ClassExtends +local __TS__Decorate = ____lualib.__TS__Decorate +local ____exports = {} +local ____custom_game_events = require("custom_game_events") +local ensurePlayerCardSystem = ____custom_game_events.ensurePlayerCardSystem +local sendZiMorningCrystalsCard = ____custom_game_events.sendZiMorningCrystalsCard +local ____CardSystem = require("cards.CardSystem") +local CardBase = ____CardSystem.CardBase +local RegisterCard = ____CardSystem.RegisterCard +local ____CardBaseModifier = require("cards.CardBaseModifier") +local CardBaseModifier = ____CardBaseModifier.CardBaseModifier +local ____dota_ts_adapter = require("lib.dota_ts_adapter") +local registerModifier = ____dota_ts_adapter.registerModifier +local ____crystal_currency = require("crystal_currency") +local GetCrystalCurrency = ____crystal_currency.GetCrystalCurrency +local ____card_value_resolver = require("cards.card_value_resolver") +local getCardValueByLevel = ____card_value_resolver.getCardValueByLevel +local CARD_ID = 38 +____exports.card_38 = __TS__Class() +local card_38 = ____exports.card_38 +card_38.name = "card_38" +card_38.____file_path = "scripts/vscripts/cards/examples/card_38.lua" +__TS__ClassExtends(card_38, CardBase) +function card_38.prototype.GetModifierName(self) + return "modifier_card_38" +end +card_38 = __TS__Decorate(card_38, card_38, {RegisterCard}, {kind = "class", name = "card_38"}) +____exports.card_38 = card_38 +____exports.modifier_card_38 = __TS__Class() +local modifier_card_38 = ____exports.modifier_card_38 +modifier_card_38.name = "modifier_card_38" +modifier_card_38.____file_path = "scripts/vscripts/cards/examples/card_38.lua" +__TS__ClassExtends(modifier_card_38, CardBaseModifier) +modifier_card_38 = __TS__Decorate( + modifier_card_38, + modifier_card_38, + {registerModifier(nil)}, + {kind = "class", name = "modifier_card_38"} +) +____exports.modifier_card_38 = modifier_card_38 +local lastMorningSeqCard38ByPlayer = {} +function ____exports.notifyCard38MorningStarted(self, currentNight, morningSequence) + if not IsServer() then + return + end + local nightIndex = math.max(1, currentNight) + do + local i = 0 + while i < DOTA_MAX_PLAYERS do + do + local pid = i + local player = PlayerResource:GetPlayer(pid) + if not player then + goto __continue5 + end + local cardSystem = ensurePlayerCardSystem(nil, pid) + if not cardSystem then + goto __continue5 + end + local copies = cardSystem:GetActiveCardCopies(CARD_ID) + if copies <= 0 then + goto __continue5 + end + local hero = player:GetAssignedHero() + if not hero or not IsValidEntity(hero) or not hero:IsRealHero() then + goto __continue5 + end + local mult = math.max( + 1, + getCardValueByLevel( + nil, + CARD_ID, + hero, + "multiplier", + 2 + ) + ) + local prevSeq = lastMorningSeqCard38ByPlayer[pid] or 0 + if morningSequence <= prevSeq then + goto __continue5 + end + local crystals = GetCrystalCurrency(nil):getCrystals(pid) + local bonus = math.floor(crystals * (mult - 1) * copies) + if bonus > 0 then + GetCrystalCurrency(nil):addCrystals(pid, bonus) + sendZiMorningCrystalsCard(nil, pid, {morningSequence = morningSequence, nightIndex = nightIndex, crystalBonus = bonus}) + end + lastMorningSeqCard38ByPlayer[pid] = morningSequence + end + ::__continue5:: + i = i + 1 + end + end +end +return ____exports diff --git a/scripts/vscripts/cards/examples/card_39.lua b/scripts/vscripts/cards/examples/card_39.lua new file mode 100644 index 0000000..8ae9e84 --- /dev/null +++ b/scripts/vscripts/cards/examples/card_39.lua @@ -0,0 +1,62 @@ +local ____lualib = require("lualib_bundle") +local __TS__Class = ____lualib.__TS__Class +local __TS__ClassExtends = ____lualib.__TS__ClassExtends +local __TS__Decorate = ____lualib.__TS__Decorate +local ____exports = {} +local ____CardSystem = require("cards.CardSystem") +local CardBase = ____CardSystem.CardBase +local RegisterCard = ____CardSystem.RegisterCard +local ____card_value_resolver = require("cards.card_value_resolver") +local getCardValueByLevel = ____card_value_resolver.getCardValueByLevel +local ____crystal_currency = require("crystal_currency") +local GetCrystalCurrency = ____crystal_currency.GetCrystalCurrency +local CARD_ID = 39 +____exports.card_39 = __TS__Class() +local card_39 = ____exports.card_39 +card_39.name = "card_39" +card_39.____file_path = "scripts/vscripts/cards/examples/card_39.lua" +__TS__ClassExtends(card_39, CardBase) +function card_39.prototype.OnCreated(self) + if not IsServer() then + return + end + local hero = self:GetHero() + if not hero or not IsValidEntity(hero) or not hero:IsRealHero() then + return + end + local playerId = hero:GetPlayerOwnerID() + if playerId == nil or playerId == nil or playerId < 0 then + return + end + local goldPerCrystal = math.max( + 1, + math.floor(self:getValue("gold_per_crystal", 150)) + ) + local currency = GetCrystalCurrency(nil) + local crystals = currency:getCrystals(playerId) + if crystals <= 0 then + return + end + local gold = crystals * goldPerCrystal + currency:setCrystals(playerId, 0) + hero:ModifyGold(gold, true, 0) + print((((((("[CARD_39] player=" .. tostring(playerId)) .. ", crystals=") .. tostring(crystals)) .. ", gold=") .. tostring(gold)) .. ", rate=") .. tostring(goldPerCrystal)) +end +function card_39.prototype.GetModifierName(self) + return nil +end +function card_39.prototype.IsHidden(self) + return true +end +function card_39.prototype.getValue(self, key, fallback) + return getCardValueByLevel( + nil, + CARD_ID, + self:GetHero(), + key, + fallback + ) +end +card_39 = __TS__Decorate(card_39, card_39, {RegisterCard}, {kind = "class", name = "card_39"}) +____exports.card_39 = card_39 +return ____exports diff --git a/scripts/vscripts/cards/examples/card_4.lua b/scripts/vscripts/cards/examples/card_4.lua new file mode 100644 index 0000000..308456f --- /dev/null +++ b/scripts/vscripts/cards/examples/card_4.lua @@ -0,0 +1,51 @@ +local ____lualib = require("lualib_bundle") +local __TS__Class = ____lualib.__TS__Class +local __TS__ClassExtends = ____lualib.__TS__ClassExtends +local __TS__Decorate = ____lualib.__TS__Decorate +local ____exports = {} +local ____CardSystem = require("cards.CardSystem") +local AddCardToPlayerPool = ____CardSystem.AddCardToPlayerPool +local CardBase = ____CardSystem.CardBase +local RegisterCard = ____CardSystem.RegisterCard +local ____CardBaseModifier = require("cards.CardBaseModifier") +local CardBaseModifier = ____CardBaseModifier.CardBaseModifier +local ____dota_ts_adapter = require("lib.dota_ts_adapter") +local registerModifier = ____dota_ts_adapter.registerModifier +____exports.card_4 = __TS__Class() +local card_4 = ____exports.card_4 +card_4.name = "card_4" +card_4.____file_path = "scripts/vscripts/cards/examples/card_4.lua" +__TS__ClassExtends(card_4, CardBase) +function card_4.prototype.GetModifierName(self) + return "modifier_card_4" +end +card_4 = __TS__Decorate(card_4, card_4, {RegisterCard}, {kind = "class", name = "card_4"}) +____exports.card_4 = card_4 +____exports.modifier_card_4 = __TS__Class() +local modifier_card_4 = ____exports.modifier_card_4 +modifier_card_4.name = "modifier_card_4" +modifier_card_4.____file_path = "scripts/vscripts/cards/examples/card_4.lua" +__TS__ClassExtends(modifier_card_4, CardBaseModifier) +function modifier_card_4.prototype.OnCreated(self, params) + if IsClient() then + return + end + local player = self:GetParent() + AddCardToPlayerPool( + nil, + player:GetPlayerID(), + 3, + 1, + self:getCardValue("card_bonus", 3, 4), + "card_4_pool_bonus" + ) + self:Destroy() +end +modifier_card_4 = __TS__Decorate( + modifier_card_4, + modifier_card_4, + {registerModifier(nil)}, + {kind = "class", name = "modifier_card_4"} +) +____exports.modifier_card_4 = modifier_card_4 +return ____exports diff --git a/scripts/vscripts/cards/examples/card_40.lua b/scripts/vscripts/cards/examples/card_40.lua new file mode 100644 index 0000000..cf8e7e4 --- /dev/null +++ b/scripts/vscripts/cards/examples/card_40.lua @@ -0,0 +1,63 @@ +local ____lualib = require("lualib_bundle") +local __TS__Class = ____lualib.__TS__Class +local __TS__ClassExtends = ____lualib.__TS__ClassExtends +local __TS__Decorate = ____lualib.__TS__Decorate +local ____exports = {} +local ____CardSystem = require("cards.CardSystem") +local CardBase = ____CardSystem.CardBase +local RegisterCard = ____CardSystem.RegisterCard +local ____card_value_resolver = require("cards.card_value_resolver") +local getCardValueByLevel = ____card_value_resolver.getCardValueByLevel +local ____crystal_currency = require("crystal_currency") +local GetCrystalCurrency = ____crystal_currency.GetCrystalCurrency +local CARD_ID = 40 +____exports.card_40 = __TS__Class() +local card_40 = ____exports.card_40 +card_40.name = "card_40" +card_40.____file_path = "scripts/vscripts/cards/examples/card_40.lua" +__TS__ClassExtends(card_40, CardBase) +function card_40.prototype.OnCreated(self) + if not IsServer() then + return + end + local hero = self:GetHero() + if not hero or not IsValidEntity(hero) or not hero:IsRealHero() then + return + end + local playerId = hero:GetPlayerOwnerID() + if playerId == nil or playerId == nil or playerId < 0 then + return + end + local goldPerCrystal = math.max( + 1, + math.floor(self:getValue("gold_per_crystal", 150)) + ) + local currentGold = PlayerResource:GetGold(playerId) + local crystalsToAdd = math.floor(currentGold / goldPerCrystal) + if crystalsToAdd <= 0 then + return + end + local goldToRemove = crystalsToAdd * goldPerCrystal + local currency = GetCrystalCurrency(nil) + hero:ModifyGold(-goldToRemove, true, 0) + currency:addCrystals(playerId, crystalsToAdd) + print((((((((("[CARD_40] player=" .. tostring(playerId)) .. ", gold_in=") .. tostring(currentGold)) .. ", gold_out=") .. tostring(goldToRemove)) .. ", crystals=") .. tostring(crystalsToAdd)) .. ", rate=") .. tostring(goldPerCrystal)) +end +function card_40.prototype.GetModifierName(self) + return nil +end +function card_40.prototype.IsHidden(self) + return true +end +function card_40.prototype.getValue(self, key, fallback) + return getCardValueByLevel( + nil, + CARD_ID, + self:GetHero(), + key, + fallback + ) +end +card_40 = __TS__Decorate(card_40, card_40, {RegisterCard}, {kind = "class", name = "card_40"}) +____exports.card_40 = card_40 +return ____exports diff --git a/scripts/vscripts/cards/examples/card_41.lua b/scripts/vscripts/cards/examples/card_41.lua new file mode 100644 index 0000000..ebedcf9 --- /dev/null +++ b/scripts/vscripts/cards/examples/card_41.lua @@ -0,0 +1,98 @@ +local ____lualib = require("lualib_bundle") +local __TS__Class = ____lualib.__TS__Class +local __TS__ClassExtends = ____lualib.__TS__ClassExtends +local __TS__Decorate = ____lualib.__TS__Decorate +local ____exports = {} +local ____CardSystem = require("cards.CardSystem") +local AddCardToPlayerPool = ____CardSystem.AddCardToPlayerPool +local CardBase = ____CardSystem.CardBase +local RegisterCard = ____CardSystem.RegisterCard +local ShowCardSelectionExactOptionsToPlayer = ____CardSystem.ShowCardSelectionExactOptionsToPlayer +local ____CardBaseModifier = require("cards.CardBaseModifier") +local CardBaseModifier = ____CardBaseModifier.CardBaseModifier +local ____dota_ts_adapter = require("lib.dota_ts_adapter") +local registerModifier = ____dota_ts_adapter.registerModifier +local ____card_value_resolver = require("cards.card_value_resolver") +local getCardValueByLevel = ____card_value_resolver.getCardValueByLevel +local CARD_ID = 41 +local BLADE_CARD_IDS = {27, 28} +local POOL_SOURCE = "card_41_injected_pool_cards" +local BLADE_CHOICE_SOURCE = "card_41_blade_choice" +local function injectBladePairsToPool(self, playerId, ____pairs) + local normalizedPairs = math.max( + 1, + math.floor(____pairs) + ) + do + local pairIndex = 0 + while pairIndex < normalizedPairs do + for ____, bladeId in ipairs(BLADE_CARD_IDS) do + AddCardToPlayerPool( + nil, + playerId, + bladeId, + 1, + 1, + POOL_SOURCE + ) + end + pairIndex = pairIndex + 1 + end + end +end +____exports.card_41 = __TS__Class() +local card_41 = ____exports.card_41 +card_41.name = "card_41" +card_41.____file_path = "scripts/vscripts/cards/examples/card_41.lua" +__TS__ClassExtends(card_41, CardBase) +function card_41.prototype.OnCreated(self) + if not IsServer() then + return + end + local hero = self:GetHero() + if not hero or not IsValidEntity(hero) or not hero:IsRealHero() then + return + end + local playerId = hero:GetPlayerOwnerID() + if playerId == nil or playerId == nil or playerId < 0 then + return + end + local player = PlayerResource:GetPlayer(playerId) + local ____opt_0 = player and player.cardSystem + local level = ____opt_0 and ____opt_0:GetCardLevel(CARD_ID) or 1 + local ____pairs = math.max( + 1, + math.floor(getCardValueByLevel( + nil, + CARD_ID, + hero, + "pool_pairs", + 1 + )) + ) + injectBladePairsToPool(nil, playerId, ____pairs) + if level >= 2 then + ShowCardSelectionExactOptionsToPlayer(nil, playerId, BLADE_CARD_IDS, BLADE_CHOICE_SOURCE) + end +end +function card_41.prototype.GetModifierName(self) + return "modifier_card_41" +end +function card_41.prototype.IsHidden(self) + return true +end +card_41 = __TS__Decorate(card_41, card_41, {RegisterCard}, {kind = "class", name = "card_41"}) +____exports.card_41 = card_41 +____exports.modifier_card_41 = __TS__Class() +local modifier_card_41 = ____exports.modifier_card_41 +modifier_card_41.name = "modifier_card_41" +modifier_card_41.____file_path = "scripts/vscripts/cards/examples/card_41.lua" +__TS__ClassExtends(modifier_card_41, CardBaseModifier) +modifier_card_41 = __TS__Decorate( + modifier_card_41, + modifier_card_41, + {registerModifier(nil)}, + {kind = "class", name = "modifier_card_41"} +) +____exports.modifier_card_41 = modifier_card_41 +return ____exports diff --git a/scripts/vscripts/cards/examples/card_42.lua b/scripts/vscripts/cards/examples/card_42.lua new file mode 100644 index 0000000..06be9c7 --- /dev/null +++ b/scripts/vscripts/cards/examples/card_42.lua @@ -0,0 +1,48 @@ +local ____lualib = require("lualib_bundle") +local __TS__Class = ____lualib.__TS__Class +local __TS__ClassExtends = ____lualib.__TS__ClassExtends +local __TS__Decorate = ____lualib.__TS__Decorate +local ____exports = {} +local ____CardSystem = require("cards.CardSystem") +local CardBase = ____CardSystem.CardBase +local RegisterCard = ____CardSystem.RegisterCard +local ____CardBaseModifier = require("cards.CardBaseModifier") +local CardBaseModifier = ____CardBaseModifier.CardBaseModifier +local ____dota_ts_adapter = require("lib.dota_ts_adapter") +local registerModifier = ____dota_ts_adapter.registerModifier +local CARD_ID = 42 +____exports.card_42 = __TS__Class() +local card_42 = ____exports.card_42 +card_42.name = "card_42" +card_42.____file_path = "scripts/vscripts/cards/examples/card_42.lua" +__TS__ClassExtends(card_42, CardBase) +function card_42.prototype.GetModifierName(self) + return "modifier_card_42" +end +card_42 = __TS__Decorate(card_42, card_42, {RegisterCard}, {kind = "class", name = "card_42"}) +____exports.card_42 = card_42 +____exports.modifier_card_42 = __TS__Class() +local modifier_card_42 = ____exports.modifier_card_42 +modifier_card_42.name = "modifier_card_42" +modifier_card_42.____file_path = "scripts/vscripts/cards/examples/card_42.lua" +__TS__ClassExtends(modifier_card_42, CardBaseModifier) +function modifier_card_42.prototype.getValue(self, key, fallback) + return self:getCardValue(key, fallback, CARD_ID) +end +function modifier_card_42.prototype.DeclareFunctions(self) + return {MODIFIER_PROPERTY_EXP_RATE_BOOST, MODIFIER_PROPERTY_TOOLTIP} +end +function modifier_card_42.prototype.GetModifierPercentageExpRateBoost(self) + return self:getScaledCardValue("exp_bonus_pct", 20) +end +function modifier_card_42.prototype.GetTexture(self) + return "default_items/xp" +end +modifier_card_42 = __TS__Decorate( + modifier_card_42, + modifier_card_42, + {registerModifier(nil)}, + {kind = "class", name = "modifier_card_42"} +) +____exports.modifier_card_42 = modifier_card_42 +return ____exports diff --git a/scripts/vscripts/cards/examples/card_43.lua b/scripts/vscripts/cards/examples/card_43.lua new file mode 100644 index 0000000..23f373b --- /dev/null +++ b/scripts/vscripts/cards/examples/card_43.lua @@ -0,0 +1,150 @@ +local ____lualib = require("lualib_bundle") +local __TS__Class = ____lualib.__TS__Class +local __TS__ClassExtends = ____lualib.__TS__ClassExtends +local __TS__Decorate = ____lualib.__TS__Decorate +local __TS__Number = ____lualib.__TS__Number +local __TS__NumberIsFinite = ____lualib.__TS__NumberIsFinite +local ____exports = {} +local ____CardSystem = require("cards.CardSystem") +local CardBase = ____CardSystem.CardBase +local RegisterCard = ____CardSystem.RegisterCard +local ____CardBaseModifier = require("cards.CardBaseModifier") +local CardBaseModifier = ____CardBaseModifier.CardBaseModifier +local ____dota_ts_adapter = require("lib.dota_ts_adapter") +local registerModifier = ____dota_ts_adapter.registerModifier +local ____card_bat_stacking = require("cards.card_bat_stacking") +local applyHeroTargetBat = ____card_bat_stacking.applyHeroTargetBat +local computeAttackSpeedBonusForTargetBat = ____card_bat_stacking.computeAttackSpeedBonusForTargetBat +local MODIFIER_CARD_43 = ____card_bat_stacking.MODIFIER_CARD_43 +local CARD_ID = 43 +____exports.card_43 = __TS__Class() +local card_43 = ____exports.card_43 +card_43.name = "card_43" +card_43.____file_path = "scripts/vscripts/cards/examples/card_43.lua" +__TS__ClassExtends(card_43, CardBase) +function card_43.prototype.GetModifierName(self) + return MODIFIER_CARD_43 +end +card_43 = __TS__Decorate(card_43, card_43, {RegisterCard}, {kind = "class", name = "card_43"}) +____exports.card_43 = card_43 +____exports.modifier_card_43 = __TS__Class() +local modifier_card_43 = ____exports.modifier_card_43 +modifier_card_43.name = "modifier_card_43" +modifier_card_43.____file_path = "scripts/vscripts/cards/examples/card_43.lua" +__TS__ClassExtends(modifier_card_43, CardBaseModifier) +function modifier_card_43.prototype.____constructor(self, ...) + CardBaseModifier.prototype.____constructor(self, ...) + self.totalBatReductionFlat = 0 + self.totalProjectileBonus = 0 + self.totalAnimReductionPct = 0 +end +function modifier_card_43.prototype.OnCustomCreated(self, _params) + if not IsServer() then + return + end + Timers:CreateTimer( + 0, + function() + if self:IsNull() then + return nil + end + self:refreshBonuses() + return nil + end + ) +end +function modifier_card_43.prototype.OnCustomRefresh(self, params) + local ____opt_result_2 + if params ~= nil then + ____opt_result_2 = params.card_level + end + local levelRaw = __TS__Number(____opt_result_2) + if __TS__NumberIsFinite(levelRaw) and levelRaw > 0 then + self.cardLevelSnapshot = math.floor(levelRaw) + end + if not IsServer() then + return + end + self:refreshBonuses() +end +function modifier_card_43.prototype.DeclareFunctions(self) + return {MODIFIER_PROPERTY_ATTACKSPEED_BONUS_CONSTANT, MODIFIER_PROPERTY_PROJECTILE_SPEED_BONUS, MODIFIER_PROPERTY_ATTACK_ANIM_TIME_PERCENTAGE, MODIFIER_PROPERTY_TOOLTIP} +end +function modifier_card_43.prototype.GetModifierAttackSpeedBonus_Constant(self) + local parent = self:GetParent() + if not parent or not IsValidEntity(parent) then + return 0 + end + return computeAttackSpeedBonusForTargetBat(nil, parent, self.totalBatReductionFlat) +end +function modifier_card_43.prototype.GetModifierProjectileSpeedBonus(self) + if self.totalProjectileBonus <= 0 then + return 0 + end + local parent = self:GetParent() + if not parent or not IsValidEntity(parent) or not parent:IsRangedAttacker() then + return 0 + end + return self.totalProjectileBonus +end +function modifier_card_43.prototype.GetModifierPercentageAttackAnimTime(self) + return self.totalAnimReductionPct +end +function modifier_card_43.prototype.OnTooltip(self) + return self:getCardCopies() +end +function modifier_card_43.prototype.HandleCustomTransmitterData(self, data) + local ____opt_result_5 + if data ~= nil then + ____opt_result_5 = data.card_level_snapshot + end + local levelRaw = __TS__Number(____opt_result_5) + if __TS__NumberIsFinite(levelRaw) and levelRaw > 0 then + self.cardLevelSnapshot = math.floor(levelRaw) + end + local ____opt_result_8 + if data ~= nil then + ____opt_result_8 = data.bat_flat + end + self.totalBatReductionFlat = __TS__Number(____opt_result_8) or 0 + local ____opt_result_11 + if data ~= nil then + ____opt_result_11 = data.projectile_bonus + end + self.totalProjectileBonus = __TS__Number(____opt_result_11) or 0 + local ____opt_result_14 + if data ~= nil then + ____opt_result_14 = data.anim_pct + end + self.totalAnimReductionPct = __TS__Number(____opt_result_14) or 0 +end +function modifier_card_43.prototype.AddCustomTransmitterData(self) + return {card_level_snapshot = self.cardLevelSnapshot or 1, bat_flat = self.totalBatReductionFlat, projectile_bonus = self.totalProjectileBonus, anim_pct = self.totalAnimReductionPct} +end +function modifier_card_43.prototype.refreshBonuses(self) + if not IsServer() then + return + end + local parent = self:GetParent() + if not parent or not IsValidEntity(parent) then + return + end + local copies = self:getCardCopies() + local batPerCopy = self:getCardValue("bat_reduction", 0.1, CARD_ID) + self.totalBatReductionFlat = copies * batPerCopy + self.totalProjectileBonus = copies * self:getCardValue("projectile_speed_bonus", 0, CARD_ID) + self.totalAnimReductionPct = copies * self:getCardValue("attack_anim_reduction_pct", 0, CARD_ID) + applyHeroTargetBat(nil, parent, self.totalBatReductionFlat) + self:SendBuffRefreshToClients() + if parent:IsRealHero() then + parent:CalculateStatBonus(true) + end +end +modifier_card_43 = __TS__Decorate( + modifier_card_43, + modifier_card_43, + {registerModifier(nil)}, + {kind = "class", name = "modifier_card_43"} +) +____exports.modifier_card_43 = modifier_card_43 +return ____exports diff --git a/scripts/vscripts/cards/examples/card_44.lua b/scripts/vscripts/cards/examples/card_44.lua new file mode 100644 index 0000000..9522eed --- /dev/null +++ b/scripts/vscripts/cards/examples/card_44.lua @@ -0,0 +1,76 @@ +local ____lualib = require("lualib_bundle") +local __TS__Class = ____lualib.__TS__Class +local __TS__ClassExtends = ____lualib.__TS__ClassExtends +local __TS__Decorate = ____lualib.__TS__Decorate +local ____exports = {} +local ____CardSystem = require("cards.CardSystem") +local CardBase = ____CardSystem.CardBase +local RegisterCard = ____CardSystem.RegisterCard +local ____CardBaseModifier = require("cards.CardBaseModifier") +local CardBaseModifier = ____CardBaseModifier.CardBaseModifier +local ____dota_ts_adapter = require("lib.dota_ts_adapter") +local registerModifier = ____dota_ts_adapter.registerModifier +local ____luck = require("utils.luck") +local getLuck = ____luck.getLuck +local CARD_ID = 44 +____exports.card_44 = __TS__Class() +local card_44 = ____exports.card_44 +card_44.name = "card_44" +card_44.____file_path = "scripts/vscripts/cards/examples/card_44.lua" +__TS__ClassExtends(card_44, CardBase) +function card_44.prototype.GetModifierName(self) + return "modifier_card_44" +end +card_44 = __TS__Decorate(card_44, card_44, {RegisterCard}, {kind = "class", name = "card_44"}) +____exports.card_44 = card_44 +____exports.modifier_card_44 = __TS__Class() +local modifier_card_44 = ____exports.modifier_card_44 +modifier_card_44.name = "modifier_card_44" +modifier_card_44.____file_path = "scripts/vscripts/cards/examples/card_44.lua" +__TS__ClassExtends(modifier_card_44, CardBaseModifier) +function modifier_card_44.prototype.GetAttributes(self) + return MODIFIER_ATTRIBUTE_MULTIPLE +end +function modifier_card_44.prototype.getValue(self, key, fallback) + return self:getCardValue(key, fallback, CARD_ID) +end +function modifier_card_44.prototype.DeclareFunctions(self) + return {MODIFIER_PROPERTY_MOVESPEED_BONUS_PERCENTAGE, MODIFIER_PROPERTY_EVASION_CONSTANT, MODIFIER_PROPERTY_TOOLTIP} +end +function modifier_card_44.prototype.GetModifierMoveSpeedBonus_Percentage(self) + if self:GetParent():PassivesDisabled() then + return 0 + end + local parent = self:GetParent() + if not parent or not parent:IsHero() then + return 0 + end + local copies = self:getCardCopies() + local base = self:getValue("bonus_move_speed_pct", 5) * copies + local perLuck = self:getValue("luck_bonus_pct_per_point", 0.5) * copies + local luck = parent:IsRealHero() and getLuck(nil, parent) or 0 + return base + luck * perLuck +end +function modifier_card_44.prototype.GetModifierEvasion_Constant(self) + if self:GetParent():PassivesDisabled() then + return 0 + end + local parent = self:GetParent() + if not parent or not parent:IsHero() then + return 0 + end + local copies = self:getCardCopies() + local base = self:getValue("bonus_evasion_pct", 5) * copies + local perLuck = self:getValue("luck_bonus_pct_per_point", 0.5) * copies + local cap = self:getValue("max_evasion_pct", 75) * copies + local luck = parent:IsRealHero() and getLuck(nil, parent) or 0 + return math.min(cap, base + luck * perLuck) +end +modifier_card_44 = __TS__Decorate( + modifier_card_44, + modifier_card_44, + {registerModifier(nil)}, + {kind = "class", name = "modifier_card_44"} +) +____exports.modifier_card_44 = modifier_card_44 +return ____exports diff --git a/scripts/vscripts/cards/examples/card_45.lua b/scripts/vscripts/cards/examples/card_45.lua new file mode 100644 index 0000000..2be74cc --- /dev/null +++ b/scripts/vscripts/cards/examples/card_45.lua @@ -0,0 +1,124 @@ +local ____lualib = require("lualib_bundle") +local __TS__Class = ____lualib.__TS__Class +local __TS__ClassExtends = ____lualib.__TS__ClassExtends +local __TS__Decorate = ____lualib.__TS__Decorate +local ____exports = {} +local ____custom_game_events = require("custom_game_events") +local ensurePlayerCardSystem = ____custom_game_events.ensurePlayerCardSystem +local ____CardSystem = require("cards.CardSystem") +local CardBase = ____CardSystem.CardBase +local RegisterCard = ____CardSystem.RegisterCard +local ____CardBaseModifier = require("cards.CardBaseModifier") +local CardBaseModifier = ____CardBaseModifier.CardBaseModifier +local ____dota_ts_adapter = require("lib.dota_ts_adapter") +local registerModifier = ____dota_ts_adapter.registerModifier +local CARD_ID = 45 +____exports.card_45 = __TS__Class() +local card_45 = ____exports.card_45 +card_45.name = "card_45" +card_45.____file_path = "scripts/vscripts/cards/examples/card_45.lua" +__TS__ClassExtends(card_45, CardBase) +function card_45.prototype.GetModifierName(self) + return "modifier_card_45" +end +card_45 = __TS__Decorate(card_45, card_45, {RegisterCard}, {kind = "class", name = "card_45"}) +____exports.card_45 = card_45 +____exports.modifier_card_45 = __TS__Class() +local modifier_card_45 = ____exports.modifier_card_45 +modifier_card_45.name = "modifier_card_45" +modifier_card_45.____file_path = "scripts/vscripts/cards/examples/card_45.lua" +__TS__ClassExtends(modifier_card_45, CardBaseModifier) +function modifier_card_45.prototype.____constructor(self, ...) + CardBaseModifier.prototype.____constructor(self, ...) + self.speedBonusPct = 0 +end +function modifier_card_45.prototype.getValue(self, key, fallback) + return self:getCardValue(key, fallback, CARD_ID) +end +function modifier_card_45.prototype.getMaxBonusPct(self) + return self:getScaledCardValue("max_bonus_pct", 60) +end +function modifier_card_45.prototype.resetSpeedBonusToMax(self) + self.speedBonusPct = self:getMaxBonusPct() + local parent = self:GetParent() + if parent and parent:IsHero() then + parent:CalculateStatBonus(true) + end +end +function modifier_card_45.prototype.OnCustomCreated(self, _params) + if not IsServer() then + return + end + self:resetSpeedBonusToMax() + self:StartIntervalThink(60) +end +function modifier_card_45.prototype.OnCustomRefresh(self, _params) + if not IsServer() then + return + end + self.speedBonusPct = self.speedBonusPct + self:getValue("max_bonus_pct", 60) + local parent = self:GetParent() + if parent and parent:IsHero() then + parent:CalculateStatBonus(true) + end +end +function modifier_card_45.prototype.OnIntervalThink(self) + if not IsServer() then + return + end + local decay = math.floor(self:getValue("decay_pct_per_minute", 10)) + self.speedBonusPct = math.max(0, self.speedBonusPct - decay) + local parent = self:GetParent() + if parent and parent:IsHero() then + parent:CalculateStatBonus(true) + end +end +function modifier_card_45.prototype.DeclareFunctions(self) + return {MODIFIER_PROPERTY_MOVESPEED_BONUS_PERCENTAGE, MODIFIER_PROPERTY_TOOLTIP} +end +function modifier_card_45.prototype.GetModifierMoveSpeedBonus_Percentage(self) + return self.speedBonusPct +end +function modifier_card_45.prototype.GetTexture(self) + return "item_energy_drink" +end +modifier_card_45 = __TS__Decorate( + modifier_card_45, + modifier_card_45, + {registerModifier(nil)}, + {kind = "class", name = "modifier_card_45"} +) +____exports.modifier_card_45 = modifier_card_45 +--- Рассвет: восстановить бонус до max_bonus_pct (вызывается из DayNightCycleManager). +function ____exports.notifyCard45MorningStarted(self, _morningSequence) + if not IsServer() then + return + end + do + local i = 0 + while i < DOTA_MAX_PLAYERS do + do + local pid = i + local player = PlayerResource:GetPlayer(pid) + if not player then + goto __continue20 + end + local cardSystem = ensurePlayerCardSystem(nil, pid) + if not cardSystem or cardSystem:GetActiveCardCopies(CARD_ID) <= 0 then + goto __continue20 + end + local hero = player:GetAssignedHero() + if not hero or not IsValidEntity(hero) or not hero:IsRealHero() then + goto __continue20 + end + local mod = hero:FindModifierByName("modifier_card_45") + if mod then + mod:resetSpeedBonusToMax() + end + end + ::__continue20:: + i = i + 1 + end + end +end +return ____exports diff --git a/scripts/vscripts/cards/examples/card_46.lua b/scripts/vscripts/cards/examples/card_46.lua new file mode 100644 index 0000000..2c4406d --- /dev/null +++ b/scripts/vscripts/cards/examples/card_46.lua @@ -0,0 +1,77 @@ +local ____lualib = require("lualib_bundle") +local __TS__Class = ____lualib.__TS__Class +local __TS__ClassExtends = ____lualib.__TS__ClassExtends +local __TS__Decorate = ____lualib.__TS__Decorate +local ____exports = {} +local ____CardSystem = require("cards.CardSystem") +local CardBase = ____CardSystem.CardBase +local RegisterCard = ____CardSystem.RegisterCard +local ____CardBaseModifier = require("cards.CardBaseModifier") +local CardBaseModifier = ____CardBaseModifier.CardBaseModifier +local ____dota_ts_adapter = require("lib.dota_ts_adapter") +local registerModifier = ____dota_ts_adapter.registerModifier +local CARD_ID = 46 +____exports.card_46 = __TS__Class() +local card_46 = ____exports.card_46 +card_46.name = "card_46" +card_46.____file_path = "scripts/vscripts/cards/examples/card_46.lua" +__TS__ClassExtends(card_46, CardBase) +function card_46.prototype.GetModifierName(self) + return "modifier_card_46" +end +card_46 = __TS__Decorate(card_46, card_46, {RegisterCard}, {kind = "class", name = "card_46"}) +____exports.card_46 = card_46 +____exports.modifier_card_46 = __TS__Class() +local modifier_card_46 = ____exports.modifier_card_46 +modifier_card_46.name = "modifier_card_46" +modifier_card_46.____file_path = "scripts/vscripts/cards/examples/card_46.lua" +__TS__ClassExtends(modifier_card_46, CardBaseModifier) +function modifier_card_46.prototype.getValue(self, key, fallback) + return self:getCardValue(key, fallback, CARD_ID) +end +function modifier_card_46.prototype.DeclareFunctions(self) + return {MODIFIER_EVENT_ON_TAKEDAMAGE, MODIFIER_PROPERTY_TOOLTIP} +end +function modifier_card_46.prototype.OnTakeDamage(self, event) + if not IsServer() then + return + end + local parent = self:GetParent() + if not parent or not IsValidEntity(parent) or not parent:IsAlive() then + return + end + if event.unit ~= parent then + return + end + if not parent:IsRealHero() then + return + end + local damage = event.damage + if damage <= 0 then + return + end + local pct = self:getScaledCardValue("damage_to_mana_pct", 30) + local manaGain = damage * (pct / 100) + if manaGain <= 0 then + return + end + parent:GiveMana(manaGain) + SendOverheadEventMessage( + nil, + OVERHEAD_ALERT_MANA_ADD, + parent, + manaGain, + parent:GetPlayerOwner() + ) +end +function modifier_card_46.prototype.GetTexture(self) + return "item_magic_stone" +end +modifier_card_46 = __TS__Decorate( + modifier_card_46, + modifier_card_46, + {registerModifier(nil)}, + {kind = "class", name = "modifier_card_46"} +) +____exports.modifier_card_46 = modifier_card_46 +return ____exports diff --git a/scripts/vscripts/cards/examples/card_47.lua b/scripts/vscripts/cards/examples/card_47.lua new file mode 100644 index 0000000..75c475f --- /dev/null +++ b/scripts/vscripts/cards/examples/card_47.lua @@ -0,0 +1,82 @@ +local ____lualib = require("lualib_bundle") +local __TS__Class = ____lualib.__TS__Class +local __TS__ClassExtends = ____lualib.__TS__ClassExtends +local __TS__Decorate = ____lualib.__TS__Decorate +local ____exports = {} +local ____CardSystem = require("cards.CardSystem") +local CardBase = ____CardSystem.CardBase +local RegisterCard = ____CardSystem.RegisterCard +local ____CardBaseModifier = require("cards.CardBaseModifier") +local CardBaseModifier = ____CardBaseModifier.CardBaseModifier +local ____dota_ts_adapter = require("lib.dota_ts_adapter") +local registerModifier = ____dota_ts_adapter.registerModifier +local CARD_ID = 47 +local START_BONUS_LEVELS = 1 +local MAX_HERO_LEVEL = 30 +____exports.card_47 = __TS__Class() +local card_47 = ____exports.card_47 +card_47.name = "card_47" +card_47.____file_path = "scripts/vscripts/cards/examples/card_47.lua" +__TS__ClassExtends(card_47, CardBase) +function card_47.prototype.OnCreated(self) + if not IsServer() then + return + end + local hero = self:GetHero() + if not hero or not IsValidEntity(hero) or not hero:IsRealHero() then + return + end + do + local i = 0 + while i < START_BONUS_LEVELS do + if hero:GetLevel() >= MAX_HERO_LEVEL then + break + end + hero:HeroLevelUp(true) + i = i + 1 + end + end +end +function card_47.prototype.GetModifierName(self) + return "modifier_card_47" +end +card_47 = __TS__Decorate(card_47, card_47, {RegisterCard}, {kind = "class", name = "card_47"}) +____exports.card_47 = card_47 +____exports.modifier_card_47 = __TS__Class() +local modifier_card_47 = ____exports.modifier_card_47 +modifier_card_47.name = "modifier_card_47" +modifier_card_47.____file_path = "scripts/vscripts/cards/examples/card_47.lua" +__TS__ClassExtends(modifier_card_47, CardBaseModifier) +function modifier_card_47.prototype.IsPermanent(self) + return false +end +function modifier_card_47.prototype.IsHidden(self) + return false +end +function modifier_card_47.prototype.OnCustomCreated(self) + local durationSec = math.max( + 0.1, + self:getCardValue("exp_boost_duration_sec", 180, CARD_ID) + ) + self:SetDuration(durationSec, true) +end +function modifier_card_47.prototype.DeclareFunctions(self) + return {MODIFIER_PROPERTY_EXP_RATE_BOOST, MODIFIER_PROPERTY_TOOLTIP} +end +function modifier_card_47.prototype.GetModifierPercentageExpRateBoost(self) + return self:getScaledCardValue("exp_bonus_pct", 100, CARD_ID) +end +function modifier_card_47.prototype.OnTooltip(self) + return self:GetModifierPercentageExpRateBoost() +end +function modifier_card_47.prototype.GetTexture(self) + return "default_items/xp" +end +modifier_card_47 = __TS__Decorate( + modifier_card_47, + modifier_card_47, + {registerModifier(nil)}, + {kind = "class", name = "modifier_card_47"} +) +____exports.modifier_card_47 = modifier_card_47 +return ____exports diff --git a/scripts/vscripts/cards/examples/card_48.lua b/scripts/vscripts/cards/examples/card_48.lua new file mode 100644 index 0000000..93d9afc --- /dev/null +++ b/scripts/vscripts/cards/examples/card_48.lua @@ -0,0 +1,172 @@ +local ____lualib = require("lualib_bundle") +local __TS__Class = ____lualib.__TS__Class +local __TS__ClassExtends = ____lualib.__TS__ClassExtends +local __TS__Decorate = ____lualib.__TS__Decorate +local ____exports = {} +local ____CardSystem = require("cards.CardSystem") +local CardBase = ____CardSystem.CardBase +local RegisterCard = ____CardSystem.RegisterCard +local ____CardBaseModifier = require("cards.CardBaseModifier") +local CardBaseModifier = ____CardBaseModifier.CardBaseModifier +local ____dota_ts_adapter = require("lib.dota_ts_adapter") +local registerModifier = ____dota_ts_adapter.registerModifier +local ____WaveManager = require("WaveManager") +local WaveManager = ____WaveManager.WaveManager +local ____incoming_damage_reduction_combine = require("utils.incoming_damage_reduction_combine") +local removeIncomingDamageReductionSource = ____incoming_damage_reduction_combine.removeIncomingDamageReductionSource +local setIncomingDamageReductionSource = ____incoming_damage_reduction_combine.setIncomingDamageReductionSource +local CARD_ID = 48 +local BUFF_NAME = "modifier_card_48_insurance_buff" +local CARD_48_INSURANCE_BUFF_INCOMING_SOURCE = BUFF_NAME +____exports.card_48 = __TS__Class() +local card_48 = ____exports.card_48 +card_48.name = "card_48" +card_48.____file_path = "scripts/vscripts/cards/examples/card_48.lua" +__TS__ClassExtends(card_48, CardBase) +function card_48.prototype.GetModifierName(self) + return "modifier_card_48" +end +card_48 = __TS__Decorate(card_48, card_48, {RegisterCard}, {kind = "class", name = "card_48"}) +____exports.card_48 = card_48 +____exports.modifier_card_48 = __TS__Class() +local modifier_card_48 = ____exports.modifier_card_48 +modifier_card_48.name = "modifier_card_48" +modifier_card_48.____file_path = "scripts/vscripts/cards/examples/card_48.lua" +__TS__ClassExtends(modifier_card_48, CardBaseModifier) +function modifier_card_48.prototype.____constructor(self, ...) + CardBaseModifier.prototype.____constructor(self, ...) + self.insuranceReady = true + self.trackedNight = -1 +end +function modifier_card_48.prototype.getValue(self, key, fallback) + return self:getCardValue(key, fallback, CARD_ID) +end +function modifier_card_48.prototype.OnCustomCreated(self, params) + if not IsServer() then + return + end + self.trackedNight = WaveManager:getInstance():GetCurrentNight() + self.insuranceReady = true + self:StartIntervalThink(1) +end +function modifier_card_48.prototype.OnIntervalThink(self) + if not IsServer() then + return + end + local night = WaveManager:getInstance():GetCurrentNight() + if night ~= self.trackedNight then + self.trackedNight = night + self.insuranceReady = true + end +end +function modifier_card_48.prototype.DeclareFunctions(self) + return {MODIFIER_EVENT_ON_TAKEDAMAGE, MODIFIER_PROPERTY_TOOLTIP} +end +function modifier_card_48.prototype.OnTakeDamage(self, event) + if not IsServer() then + return + end + local parent = self:GetParent() + if not parent or not IsValidEntity(parent) or not parent:IsAlive() then + return + end + if event.unit ~= parent then + return + end + if not parent:IsRealHero() then + return + end + if not self.insuranceReady then + return + end + local maxHp = parent:GetMaxHealth() + if maxHp <= 0 then + return + end + local hpPct = parent:GetHealth() / maxHp * 100 + local triggerPct = self:getValue("trigger_hp_pct", 25) + if hpPct >= triggerPct then + return + end + self.insuranceReady = false + local copies = self:getCardCopies() + local healPct = self:getValue("shield_heal_max_hp_pct", 10) * copies + local heal = maxHp * (healPct / 100) + if heal > 0 then + parent:Heal(heal, nil) + end + local duration = self:getValue("insurance_duration", 3) + parent:AddNewModifier( + parent, + getModifierSourceAbility(nil, parent), + BUFF_NAME, + {duration = duration} + ) +end +modifier_card_48 = __TS__Decorate( + modifier_card_48, + modifier_card_48, + {registerModifier(nil)}, + {kind = "class", name = "modifier_card_48"} +) +____exports.modifier_card_48 = modifier_card_48 +____exports.modifier_card_48_insurance_buff = __TS__Class() +local modifier_card_48_insurance_buff = ____exports.modifier_card_48_insurance_buff +modifier_card_48_insurance_buff.name = "modifier_card_48_insurance_buff" +modifier_card_48_insurance_buff.____file_path = "scripts/vscripts/cards/examples/card_48.lua" +__TS__ClassExtends(modifier_card_48_insurance_buff, CardBaseModifier) +function modifier_card_48_insurance_buff.prototype.getCardCopiesFromOwner(self) + local owner = self:GetCaster() + if not owner or not IsValidEntity(owner) then + return 1 + end + local cardMod = owner:FindModifierByName("modifier_card_48") + return math.max( + 1, + math.floor(cardMod and cardMod:GetStackCount() or 0) + ) +end +function modifier_card_48_insurance_buff.prototype.getValue(self, key, fallback) + return self:getCardValue(key, fallback, CARD_ID) * self:getCardCopiesFromOwner() +end +function modifier_card_48_insurance_buff.prototype.IsPurgable(self) + return true +end +function modifier_card_48_insurance_buff.prototype.OnCustomCreated(self, params) + if not IsServer() then + return + end + setIncomingDamageReductionSource( + nil, + self:GetParent(), + CARD_48_INSURANCE_BUFF_INCOMING_SOURCE, + function() + local reduce = self:getValue("incoming_damage_reduction_pct", 25) + return math.max(0, reduce) + end + ) +end +function modifier_card_48_insurance_buff.prototype.OnDestroy(self) + if not IsServer() then + return + end + removeIncomingDamageReductionSource( + nil, + self:GetParent(), + CARD_48_INSURANCE_BUFF_INCOMING_SOURCE + ) +end +function modifier_card_48_insurance_buff.prototype.GetEffectName(self) + return "particles/items_fx/aegis_timer.vpcf" +end +function modifier_card_48_insurance_buff.prototype.GetEffectAttachType(self) + return PATTACH_ABSORIGIN_FOLLOW +end +modifier_card_48_insurance_buff = __TS__Decorate( + modifier_card_48_insurance_buff, + modifier_card_48_insurance_buff, + {registerModifier(nil)}, + {kind = "class", name = "modifier_card_48_insurance_buff"} +) +____exports.modifier_card_48_insurance_buff = modifier_card_48_insurance_buff +return ____exports diff --git a/scripts/vscripts/cards/examples/card_49.lua b/scripts/vscripts/cards/examples/card_49.lua new file mode 100644 index 0000000..7b0a9a2 --- /dev/null +++ b/scripts/vscripts/cards/examples/card_49.lua @@ -0,0 +1,40 @@ +local ____lualib = require("lualib_bundle") +local __TS__Class = ____lualib.__TS__Class +local __TS__ClassExtends = ____lualib.__TS__ClassExtends +local __TS__Decorate = ____lualib.__TS__Decorate +local ____exports = {} +require("cards.examples.card_49_modifier") +local ____custom_game_events = require("custom_game_events") +local ensurePlayerCardSystem = ____custom_game_events.ensurePlayerCardSystem +local ____CardSystem = require("cards.CardSystem") +local CardBase = ____CardSystem.CardBase +local RegisterCard = ____CardSystem.RegisterCard +____exports.card_49 = __TS__Class() +local card_49 = ____exports.card_49 +card_49.name = "card_49" +card_49.____file_path = "scripts/vscripts/cards/examples/card_49.lua" +__TS__ClassExtends(card_49, CardBase) +function card_49.prototype.GetModifierName(self) + return "modifier_card_49" +end +card_49 = __TS__Decorate(card_49, card_49, {RegisterCard}, {kind = "class", name = "card_49"}) +____exports.card_49 = card_49 +--- Рассвет: снова доступен бесплатный платный реролл для игроков с картой 49. +function ____exports.notifyCard49MorningStarted(self, _morningSequence) + if not IsServer() then + return + end + do + local i = 0 + while i < DOTA_MAX_PLAYERS do + local pid = i + local cs = ensurePlayerCardSystem(nil, pid) + if cs then + cs:RefreshCard49DailyFreeReroll() + cs:PushRerollCostToClient() + end + i = i + 1 + end + end +end +return ____exports diff --git a/scripts/vscripts/cards/examples/card_49_modifier.lua b/scripts/vscripts/cards/examples/card_49_modifier.lua new file mode 100644 index 0000000..c9bdc14 --- /dev/null +++ b/scripts/vscripts/cards/examples/card_49_modifier.lua @@ -0,0 +1,59 @@ +local ____lualib = require("lualib_bundle") +local __TS__Class = ____lualib.__TS__Class +local __TS__ClassExtends = ____lualib.__TS__ClassExtends +local __TS__Decorate = ____lualib.__TS__Decorate +local ____exports = {} +local ____CardBaseModifier = require("cards.CardBaseModifier") +local CardBaseModifier = ____CardBaseModifier.CardBaseModifier +local ____dota_ts_adapter = require("lib.dota_ts_adapter") +local registerModifier = ____dota_ts_adapter.registerModifier +____exports.modifier_card_49 = __TS__Class() +local modifier_card_49 = ____exports.modifier_card_49 +modifier_card_49.name = "modifier_card_49" +modifier_card_49.____file_path = "scripts/vscripts/cards/examples/card_49_modifier.lua" +__TS__ClassExtends(modifier_card_49, CardBaseModifier) +function modifier_card_49.prototype.OnDestroy(self) + if not IsServer() then + return + end + local hero = self:GetParent() + if not hero or not IsValidEntity(hero) then + return + end + local player = hero:GetPlayerOwner() + if player ~= nil and player.cardSystem ~= nil then + player.cardSystem:PushRerollCostToClient() + end +end +function modifier_card_49.prototype.OnCustomCreated(self, params) + if not IsServer() then + return + end + local hero = self:GetParent() + if not hero or not IsValidEntity(hero) or not hero:IsRealHero() then + return + end + Timers:CreateTimer( + 0, + function() + local player = hero:GetPlayerOwner() + if not player or not player.cardSystem then + return nil + end + player.cardSystem:RefreshCard49DailyFreeReroll() + player.cardSystem:ShowCardSelection(3, "modifier_card_49_bonus_selection") + return nil + end + ) +end +function modifier_card_49.prototype.IsHidden(self) + return true +end +modifier_card_49 = __TS__Decorate( + modifier_card_49, + modifier_card_49, + {registerModifier(nil)}, + {kind = "class", name = "modifier_card_49"} +) +____exports.modifier_card_49 = modifier_card_49 +return ____exports diff --git a/scripts/vscripts/cards/examples/card_5.lua b/scripts/vscripts/cards/examples/card_5.lua new file mode 100644 index 0000000..385c902 --- /dev/null +++ b/scripts/vscripts/cards/examples/card_5.lua @@ -0,0 +1,118 @@ +local ____lualib = require("lualib_bundle") +local __TS__Class = ____lualib.__TS__Class +local __TS__ClassExtends = ____lualib.__TS__ClassExtends +local __TS__Decorate = ____lualib.__TS__Decorate +local ____exports = {} +local ____CardSystem = require("cards.CardSystem") +local CardBase = ____CardSystem.CardBase +local GetPlayerActiveCardCountExcludingInherent = ____CardSystem.GetPlayerActiveCardCountExcludingInherent +local RegisterCard = ____CardSystem.RegisterCard +local ____CardBaseModifier = require("cards.CardBaseModifier") +local CardBaseModifier = ____CardBaseModifier.CardBaseModifier +local ____dota_ts_adapter = require("lib.dota_ts_adapter") +local registerModifier = ____dota_ts_adapter.registerModifier +local ____modifier_stats_multiplier = require("modifiers.modifier_stats_multiplier") +local removeStatsMultiplierSource = ____modifier_stats_multiplier.removeStatsMultiplierSource +local setStatsMultiplierSource = ____modifier_stats_multiplier.setStatsMultiplierSource +local CARD_5_SOURCE_SEQ = 1 +____exports.card_5 = __TS__Class() +local card_5 = ____exports.card_5 +card_5.name = "card_5" +card_5.____file_path = "scripts/vscripts/cards/examples/card_5.lua" +__TS__ClassExtends(card_5, CardBase) +function card_5.prototype.GetModifierName(self) + return "modifier_card_5" +end +card_5 = __TS__Decorate(card_5, card_5, {RegisterCard}, {kind = "class", name = "card_5"}) +____exports.card_5 = card_5 +____exports.modifier_card_5 = __TS__Class() +local modifier_card_5 = ____exports.modifier_card_5 +modifier_card_5.name = "modifier_card_5" +modifier_card_5.____file_path = "scripts/vscripts/cards/examples/card_5.lua" +__TS__ClassExtends(modifier_card_5, CardBaseModifier) +function modifier_card_5.prototype.____constructor(self, ...) + CardBaseModifier.prototype.____constructor(self, ...) + self.sourceId = "" + self.pctPerCard = 0 + self.cumulativeCardsBeforeCopies = 0 + self.accountedCopies = 0 +end +function modifier_card_5.prototype.GetAttributes(self) + return MODIFIER_ATTRIBUTE_MULTIPLE +end +function modifier_card_5.prototype.OnCustomCreated(self, params) + if not IsServer() then + return + end + local hero = self:GetParent() + if not hero or not IsValidEntity(hero) or not hero:IsRealHero() then + return + end + local ____CARD_5_SOURCE_SEQ_0 = CARD_5_SOURCE_SEQ + CARD_5_SOURCE_SEQ = ____CARD_5_SOURCE_SEQ_0 + 1 + self.sourceId = "card_5_" .. tostring(____CARD_5_SOURCE_SEQ_0) + local pid = hero:GetPlayerID() + self.pctPerCard = self:getCardValue("card_bonus_pct", 0, 5) + local activeNow = GetPlayerActiveCardCountExcludingInherent(nil, pid) + self.cumulativeCardsBeforeCopies = math.max(0, activeNow - 1) + self.accountedCopies = self:getCardCopies() + print((((((((((("[card_5] OnCustomCreated player=" .. tostring(pid)) .. " active_now=") .. tostring(activeNow)) .. " copies=") .. tostring(self.accountedCopies)) .. " cumulative_before=") .. tostring(self.cumulativeCardsBeforeCopies)) .. " card_bonus_pct=") .. tostring(self.pctPerCard)) .. " source=") .. self.sourceId) + setStatsMultiplierSource( + nil, + hero, + self.sourceId, + function() + if not hero or not IsValidEntity(hero) or not hero:IsRealHero() then + return 0 + end + return self.pctPerCard * self.cumulativeCardsBeforeCopies + end + ) +end +function modifier_card_5.prototype.OnCustomRefresh(self, _params) + if not IsServer() then + return + end + local hero = self:GetParent() + if not hero or not IsValidEntity(hero) or not hero:IsRealHero() then + return + end + local currentCopies = self:getCardCopies() + if currentCopies <= self.accountedCopies then + return + end + local ownerId = hero:GetPlayerID() + if ownerId < 0 then + return + end + local activeNow = GetPlayerActiveCardCountExcludingInherent(nil, ownerId) + local newCopies = currentCopies - self.accountedCopies + do + local i = 0 + while i < newCopies do + self.cumulativeCardsBeforeCopies = self.cumulativeCardsBeforeCopies + math.max(0, activeNow - 1 - i) + i = i + 1 + end + end + self.accountedCopies = currentCopies +end +function modifier_card_5.prototype.OnDestroy(self) + if not IsServer() then + return + end + local hero = self:GetParent() + if not hero or not IsValidEntity(hero) or not hero:IsRealHero() then + return + end + if self.sourceId ~= "" then + removeStatsMultiplierSource(nil, hero, self.sourceId) + end +end +modifier_card_5 = __TS__Decorate( + modifier_card_5, + modifier_card_5, + {registerModifier(nil)}, + {kind = "class", name = "modifier_card_5"} +) +____exports.modifier_card_5 = modifier_card_5 +return ____exports diff --git a/scripts/vscripts/cards/examples/card_50.lua b/scripts/vscripts/cards/examples/card_50.lua new file mode 100644 index 0000000..b029b4e --- /dev/null +++ b/scripts/vscripts/cards/examples/card_50.lua @@ -0,0 +1,58 @@ +local ____lualib = require("lualib_bundle") +local __TS__Class = ____lualib.__TS__Class +local __TS__ClassExtends = ____lualib.__TS__ClassExtends +local __TS__Decorate = ____lualib.__TS__Decorate +local ____exports = {} +local ____CardSystem = require("cards.CardSystem") +local CardBase = ____CardSystem.CardBase +local RegisterCard = ____CardSystem.RegisterCard +local ____CardBaseModifier = require("cards.CardBaseModifier") +local CardBaseModifier = ____CardBaseModifier.CardBaseModifier +local ____dota_ts_adapter = require("lib.dota_ts_adapter") +local registerModifier = ____dota_ts_adapter.registerModifier +____exports.card_50 = __TS__Class() +local card_50 = ____exports.card_50 +card_50.name = "card_50" +card_50.____file_path = "scripts/vscripts/cards/examples/card_50.lua" +__TS__ClassExtends(card_50, CardBase) +function card_50.prototype.GetModifierName(self) + return "modifier_card_50" +end +card_50 = __TS__Decorate(card_50, card_50, {RegisterCard}, {kind = "class", name = "card_50"}) +____exports.card_50 = card_50 +____exports.modifier_card_50 = __TS__Class() +local modifier_card_50 = ____exports.modifier_card_50 +modifier_card_50.name = "modifier_card_50" +modifier_card_50.____file_path = "scripts/vscripts/cards/examples/card_50.lua" +__TS__ClassExtends(modifier_card_50, CardBaseModifier) +function modifier_card_50.prototype.OnCustomCreated(self, params) + if not IsServer() then + return + end + local hero = self:GetParent() + if not hero or not IsValidEntity(hero) or not hero:IsRealHero() then + return + end + Timers:CreateTimer( + 0, + function() + local player = hero:GetPlayerOwner() + if not player or not player.cardSystem then + return nil + end + player.cardSystem:ShowLegendaryNonInherentCatalogSelection(3, "modifier_card_50_legendary_pick", {50}) + return nil + end + ) +end +function modifier_card_50.prototype.IsHidden(self) + return true +end +modifier_card_50 = __TS__Decorate( + modifier_card_50, + modifier_card_50, + {registerModifier(nil)}, + {kind = "class", name = "modifier_card_50"} +) +____exports.modifier_card_50 = modifier_card_50 +return ____exports diff --git a/scripts/vscripts/cards/examples/card_51.lua b/scripts/vscripts/cards/examples/card_51.lua new file mode 100644 index 0000000..586f4a2 --- /dev/null +++ b/scripts/vscripts/cards/examples/card_51.lua @@ -0,0 +1,151 @@ +local ____lualib = require("lualib_bundle") +local __TS__Class = ____lualib.__TS__Class +local __TS__ClassExtends = ____lualib.__TS__ClassExtends +local __TS__Decorate = ____lualib.__TS__Decorate +local ____exports = {} +local ____custom_game_events = require("custom_game_events") +local ensurePlayerCardSystem = ____custom_game_events.ensurePlayerCardSystem +local ____CardSystem = require("cards.CardSystem") +local CardBase = ____CardSystem.CardBase +local RegisterCard = ____CardSystem.RegisterCard +local ____CardBaseModifier = require("cards.CardBaseModifier") +local CardBaseModifier = ____CardBaseModifier.CardBaseModifier +local ____dota_ts_adapter = require("lib.dota_ts_adapter") +local registerModifier = ____dota_ts_adapter.registerModifier +local ____card_value_resolver = require("cards.card_value_resolver") +local getCardValueByLevel = ____card_value_resolver.getCardValueByLevel +local CARD_ID = 51 +function ____exports.getCard51MorningGoldCap(self, hero, explicitLevel) + local baseCap = math.max( + 0, + math.floor(getCardValueByLevel( + nil, + CARD_ID, + hero, + "morning_gold_cap", + 400, + explicitLevel + )) + ) + if not hero or not IsValidEntity(hero) or not hero:IsRealHero() then + return baseCap + end + local playerId = hero:GetPlayerOwnerID() + if playerId == nil or playerId == nil or playerId < 0 then + return baseCap + end + local cardSystem = ensurePlayerCardSystem(nil, playerId) + local copies = math.max( + 1, + cardSystem and cardSystem:GetActiveCardCopies(CARD_ID) or 1 + ) + return baseCap * copies +end +function ____exports.getCard51MorningGoldEarned(self, playerId) + local cardSystem = ensurePlayerCardSystem(nil, playerId) + if not cardSystem then + return 0 + end + return cardSystem:GetCard51MorningGoldEarned() +end +function ____exports.getCard51MorningGoldRemaining(self, hero, playerId) + local cap = ____exports.getCard51MorningGoldCap(nil, hero) + return math.max( + 0, + cap - ____exports.getCard51MorningGoldEarned(nil, playerId) + ) +end +--- Рассвет: сброс дневного лимита золота от Midas Chestplate. +function ____exports.notifyCard51MorningStarted(self, _morningSequence) + if not IsServer() then + return + end + do + local i = 0 + while i < DOTA_MAX_PLAYERS do + do + local pid = i + local cardSystem = ensurePlayerCardSystem(nil, pid) + if not cardSystem then + goto __continue10 + end + cardSystem:ResetCard51MorningGold() + end + ::__continue10:: + i = i + 1 + end + end +end +____exports.card_51 = __TS__Class() +local card_51 = ____exports.card_51 +card_51.name = "card_51" +card_51.____file_path = "scripts/vscripts/cards/examples/card_51.lua" +__TS__ClassExtends(card_51, CardBase) +function card_51.prototype.GetModifierName(self) + return "modifier_card_51" +end +card_51 = __TS__Decorate(card_51, card_51, {RegisterCard}, {kind = "class", name = "card_51"}) +____exports.card_51 = card_51 +____exports.modifier_card_51 = __TS__Class() +local modifier_card_51 = ____exports.modifier_card_51 +modifier_card_51.name = "modifier_card_51" +modifier_card_51.____file_path = "scripts/vscripts/cards/examples/card_51.lua" +__TS__ClassExtends(modifier_card_51, CardBaseModifier) +function modifier_card_51.prototype.____constructor(self, ...) + CardBaseModifier.prototype.____constructor(self, ...) + self.totalDamageTaken = 0 +end +function modifier_card_51.prototype.getValue(self, key, fallback) + return self:getCardValue(key, fallback, CARD_ID) +end +function modifier_card_51.prototype.DeclareFunctions(self) + return {MODIFIER_PROPERTY_INCOMING_DAMAGE_PERCENTAGE, MODIFIER_EVENT_ON_TAKEDAMAGE, MODIFIER_PROPERTY_TOOLTIP} +end +function modifier_card_51.prototype.GetModifierIncomingDamage_Percentage(self) + local basePct = self:getScaledCardValue("incoming_damage_base_pct", 25) + local per100 = self:getValue("incoming_damage_per_100_taken", 0.1) * self:getCardCopies() + local steps = math.floor(self.totalDamageTaken / 100) + return basePct + steps * per100 +end +function modifier_card_51.prototype.OnTakeDamage(self, event) + if not IsServer() then + return + end + local parent = self:GetParent() + if not parent or not IsValidEntity(parent) or not parent:IsAlive() or not parent:IsRealHero() then + return + end + if event.unit ~= parent then + return + end + local damageTaken = event.damage + if damageTaken <= 0 then + return + end + self.totalDamageTaken = self.totalDamageTaken + damageTaken + local pid = parent:GetPlayerOwnerID() + if pid == nil or pid == nil or pid < 0 then + return + end + local cardSystem = ensurePlayerCardSystem(nil, pid) + if not cardSystem then + return + end + local goldPct = self:getScaledCardValue("gold_from_damage_taken_pct", 11) + local goldGain = math.floor(damageTaken * goldPct / 100) + cardSystem:TryGrantCard51GoldFromDamage(parent, goldGain) +end +function modifier_card_51.prototype.OnTooltip(self) + return math.floor(self:GetModifierIncomingDamage_Percentage() + 0.0001) +end +function modifier_card_51.prototype.GetTexture(self) + return "item_hand_of_midas" +end +modifier_card_51 = __TS__Decorate( + modifier_card_51, + modifier_card_51, + {registerModifier(nil)}, + {kind = "class", name = "modifier_card_51"} +) +____exports.modifier_card_51 = modifier_card_51 +return ____exports diff --git a/scripts/vscripts/cards/examples/card_52.lua b/scripts/vscripts/cards/examples/card_52.lua new file mode 100644 index 0000000..3f01502 --- /dev/null +++ b/scripts/vscripts/cards/examples/card_52.lua @@ -0,0 +1,70 @@ +local ____lualib = require("lualib_bundle") +local __TS__Class = ____lualib.__TS__Class +local __TS__ClassExtends = ____lualib.__TS__ClassExtends +local __TS__Decorate = ____lualib.__TS__Decorate +local ____exports = {} +local ____CardSystem = require("cards.CardSystem") +local CardBase = ____CardSystem.CardBase +local RegisterCard = ____CardSystem.RegisterCard +local ____CardBaseModifier = require("cards.CardBaseModifier") +local CardBaseModifier = ____CardBaseModifier.CardBaseModifier +local ____dota_ts_adapter = require("lib.dota_ts_adapter") +local registerModifier = ____dota_ts_adapter.registerModifier +local ____modifier_card_cursed = require("cards.modifier_card_cursed") +local addCursedStack = ____modifier_card_cursed.addCursedStack +local removeCursedStack = ____modifier_card_cursed.removeCursedStack +____exports.card_52 = __TS__Class() +local card_52 = ____exports.card_52 +card_52.name = "card_52" +card_52.____file_path = "scripts/vscripts/cards/examples/card_52.lua" +__TS__ClassExtends(card_52, CardBase) +function card_52.prototype.GetModifierName(self) + return "modifier_card_52" +end +card_52 = __TS__Decorate(card_52, card_52, {RegisterCard}, {kind = "class", name = "card_52"}) +____exports.card_52 = card_52 +____exports.modifier_card_52 = __TS__Class() +local modifier_card_52 = ____exports.modifier_card_52 +modifier_card_52.name = "modifier_card_52" +modifier_card_52.____file_path = "scripts/vscripts/cards/examples/card_52.lua" +__TS__ClassExtends(modifier_card_52, CardBaseModifier) +function modifier_card_52.prototype.____constructor(self, ...) + CardBaseModifier.prototype.____constructor(self, ...) + self.cursedApplied = false +end +function modifier_card_52.prototype.OnCustomCreated(self, params) + if not IsServer() then + return + end + local hero = self:GetParent() + if not hero or not IsValidEntity(hero) or not hero:IsRealHero() then + return + end + addCursedStack(nil, hero) + self.cursedApplied = true +end +function modifier_card_52.prototype.OnDestroy(self) + if not IsServer() then + return + end + if not self.cursedApplied then + return + end + local hero = self:GetParent() + if not hero or not IsValidEntity(hero) then + return + end + removeCursedStack(nil, hero) + self.cursedApplied = false +end +function modifier_card_52.prototype.GetAttributes(self) + return MODIFIER_ATTRIBUTE_MULTIPLE +end +modifier_card_52 = __TS__Decorate( + modifier_card_52, + modifier_card_52, + {registerModifier(nil)}, + {kind = "class", name = "modifier_card_52"} +) +____exports.modifier_card_52 = modifier_card_52 +return ____exports diff --git a/scripts/vscripts/cards/examples/card_53.lua b/scripts/vscripts/cards/examples/card_53.lua new file mode 100644 index 0000000..d9fdca5 --- /dev/null +++ b/scripts/vscripts/cards/examples/card_53.lua @@ -0,0 +1,35 @@ +local ____lualib = require("lualib_bundle") +local __TS__Class = ____lualib.__TS__Class +local __TS__ClassExtends = ____lualib.__TS__ClassExtends +local __TS__Decorate = ____lualib.__TS__Decorate +local ____exports = {} +local ____CardSystem = require("cards.CardSystem") +local CardBase = ____CardSystem.CardBase +local RegisterCard = ____CardSystem.RegisterCard +local ____CardBaseModifier = require("cards.CardBaseModifier") +local CardBaseModifier = ____CardBaseModifier.CardBaseModifier +local ____dota_ts_adapter = require("lib.dota_ts_adapter") +local registerModifier = ____dota_ts_adapter.registerModifier +____exports.card_53 = __TS__Class() +local card_53 = ____exports.card_53 +card_53.name = "card_53" +card_53.____file_path = "scripts/vscripts/cards/examples/card_53.lua" +__TS__ClassExtends(card_53, CardBase) +function card_53.prototype.GetModifierName(self) + return "modifier_card_53" +end +card_53 = __TS__Decorate(card_53, card_53, {RegisterCard}, {kind = "class", name = "card_53"}) +____exports.card_53 = card_53 +____exports.modifier_card_53 = __TS__Class() +local modifier_card_53 = ____exports.modifier_card_53 +modifier_card_53.name = "modifier_card_53" +modifier_card_53.____file_path = "scripts/vscripts/cards/examples/card_53.lua" +__TS__ClassExtends(modifier_card_53, CardBaseModifier) +modifier_card_53 = __TS__Decorate( + modifier_card_53, + modifier_card_53, + {registerModifier(nil)}, + {kind = "class", name = "modifier_card_53"} +) +____exports.modifier_card_53 = modifier_card_53 +return ____exports diff --git a/scripts/vscripts/cards/examples/card_53_link_hook.lua b/scripts/vscripts/cards/examples/card_53_link_hook.lua new file mode 100644 index 0000000..054eb44 --- /dev/null +++ b/scripts/vscripts/cards/examples/card_53_link_hook.lua @@ -0,0 +1,75 @@ +--[[ Generated with https://github.com/TypeScriptToLua/TypeScriptToLua ]] +local ____exports = {} +local ____card_data = require("cards.card_data") +local CARD_DATABASE = ____card_data.CARD_DATABASE +local CARD_ID = 53 +local MOD_NAME = "modifier_card_53" +function ____exports.card53OnTargetHealed(self, target, amount, healer) + if not IsServer() then + return + end + if amount <= 0 then + return + end + if not target or not IsValidEntity(target) or not target:IsAlive() then + return + end + local ____this_1 + ____this_1 = target + local ____opt_0 = ____this_1.IsRealHero + if ____opt_0 ~= nil then + ____opt_0 = ____opt_0(____this_1) + end + if not ____opt_0 or not target:HasModifier(MOD_NAME) then + return + end + local ____opt_2 = CARD_DATABASE[CARD_ID] + local row = ____opt_2 and ____opt_2.values + local radius = (row and row.link_radius) ~= nil and math.max(0, row.link_radius) or 800 + local sharePct = (row and row.heal_transfer_pct) ~= nil and row.heal_transfer_pct or 20 + if radius <= 0 or sharePct <= 0 then + return + end + local share = amount * (sharePct / 100) + if share <= 0 then + return + end + local allies = FindUnitsInRadius( + target:GetTeamNumber(), + target:GetAbsOrigin(), + nil, + radius, + DOTA_UNIT_TARGET_TEAM_FRIENDLY, + DOTA_UNIT_TARGET_HERO, + DOTA_UNIT_TARGET_FLAG_INVULNERABLE, + FIND_CLOSEST, + false + ) + local recipients = {} + for ____, u in ipairs(allies) do + do + if u == target then + goto __continue9 + end + if not u or not IsValidEntity(u) or not u:IsAlive() or not u:IsRealHero() then + goto __continue9 + end + if healer and u == healer then + goto __continue9 + end + recipients[#recipients + 1] = u + end + ::__continue9:: + end + if #recipients == 0 then + return + end + local each = share / #recipients + if each <= 0 then + return + end + for ____, a in ipairs(recipients) do + a:Heal(each, nil) + end +end +return ____exports diff --git a/scripts/vscripts/cards/examples/card_54.lua b/scripts/vscripts/cards/examples/card_54.lua new file mode 100644 index 0000000..25ba95f --- /dev/null +++ b/scripts/vscripts/cards/examples/card_54.lua @@ -0,0 +1,135 @@ +local ____lualib = require("lualib_bundle") +local __TS__Class = ____lualib.__TS__Class +local __TS__ClassExtends = ____lualib.__TS__ClassExtends +local __TS__Decorate = ____lualib.__TS__Decorate +local ____exports = {} +local ____CardSystem = require("cards.CardSystem") +local CardBase = ____CardSystem.CardBase +local RegisterCard = ____CardSystem.RegisterCard +local ____CardBaseModifier = require("cards.CardBaseModifier") +local CardBaseModifier = ____CardBaseModifier.CardBaseModifier +local ____dota_ts_adapter = require("lib.dota_ts_adapter") +local registerModifier = ____dota_ts_adapter.registerModifier +local ____modifier_card_cursed = require("cards.modifier_card_cursed") +local addCursedStack = ____modifier_card_cursed.addCursedStack +local getCursedStackCount = ____modifier_card_cursed.getCursedStackCount +local removeCursedStack = ____modifier_card_cursed.removeCursedStack +local CARD_ID = 54 +____exports.card_54 = __TS__Class() +local card_54 = ____exports.card_54 +card_54.name = "card_54" +card_54.____file_path = "scripts/vscripts/cards/examples/card_54.lua" +__TS__ClassExtends(card_54, CardBase) +function card_54.prototype.GetModifierName(self) + return "modifier_card_54" +end +card_54 = __TS__Decorate(card_54, card_54, {RegisterCard}, {kind = "class", name = "card_54"}) +____exports.card_54 = card_54 +____exports.modifier_card_54 = __TS__Class() +local modifier_card_54 = ____exports.modifier_card_54 +modifier_card_54.name = "modifier_card_54" +modifier_card_54.____file_path = "scripts/vscripts/cards/examples/card_54.lua" +__TS__ClassExtends(modifier_card_54, CardBaseModifier) +function modifier_card_54.prototype.____constructor(self, ...) + CardBaseModifier.prototype.____constructor(self, ...) + self.cursedApplied = false + self.levelOnPickup = 1 +end +function modifier_card_54.prototype.getValue(self, key, fallback) + return self:getCardValue(key, fallback, CARD_ID) +end +function modifier_card_54.prototype.OnCustomCreated(self, params) + if not IsServer() then + return + end + self:capturePickupLevel() + self:applyCursed() +end +function modifier_card_54.prototype.OnDestroy(self) + if not IsServer() then + return + end + local hero = self:GetParent() + if not hero or not IsValidEntity(hero) then + return + end + if not self.cursedApplied then + return + end + removeCursedStack(nil, hero) + self.cursedApplied = false +end +function modifier_card_54.prototype.DeclareFunctions(self) + return {MODIFIER_PROPERTY_STATS_STRENGTH_BONUS, MODIFIER_PROPERTY_STATS_AGILITY_BONUS, MODIFIER_PROPERTY_STATS_INTELLECT_BONUS, MODIFIER_PROPERTY_TOOLTIP} +end +function modifier_card_54.prototype.GetModifierBonusStats_Strength(self) + local hero = self:GetParent() + if not hero or not IsValidEntity(hero) then + return 0 + end + local level = self:getLevelsAfterPickup(hero) + local curses = getCursedStackCount(nil, hero) + if level <= 0 or curses <= 0 then + return 0 + end + local strengthPenaltyPerLevel = self:getValue("strength_penalty_per_level", 1) + return -level * strengthPenaltyPerLevel * curses * self:getCardCopies() +end +function modifier_card_54.prototype.GetModifierBonusStats_Agility(self) + local hero = self:GetParent() + if not hero or not IsValidEntity(hero) then + return 0 + end + local level = self:getLevelsAfterPickup(hero) + local curses = getCursedStackCount(nil, hero) + if level <= 0 or curses <= 0 then + return 0 + end + local agilityPenaltyPerLevel = self:getValue("agility_penalty_per_level", 1) + return -level * agilityPenaltyPerLevel * curses * self:getCardCopies() +end +function modifier_card_54.prototype.GetModifierBonusStats_Intellect(self) + local hero = self:GetParent() + if not hero or not IsValidEntity(hero) then + return 0 + end + local level = self:getLevelsAfterPickup(hero) + local curses = getCursedStackCount(nil, hero) + if level <= 0 or curses <= 0 then + return 0 + end + local intellectPerLevel = self:getValue("intellect_per_level", 2) + return level * intellectPerLevel * curses * self:getCardCopies() +end +function modifier_card_54.prototype.applyCursed(self) + if self.cursedApplied then + return + end + local hero = self:GetParent() + if not hero or not IsValidEntity(hero) or not hero:IsRealHero() then + return + end + addCursedStack(nil, hero) + self.cursedApplied = true +end +function modifier_card_54.prototype.getLevelsAfterPickup(self, hero) + return math.max( + 0, + hero:GetLevel() - self.levelOnPickup + ) +end +function modifier_card_54.prototype.capturePickupLevel(self) + local hero = self:GetParent() + if not hero or not IsValidEntity(hero) or not hero:IsRealHero() then + return + end + self.levelOnPickup = hero:GetLevel() +end +modifier_card_54 = __TS__Decorate( + modifier_card_54, + modifier_card_54, + {registerModifier(nil)}, + {kind = "class", name = "modifier_card_54"} +) +____exports.modifier_card_54 = modifier_card_54 +return ____exports diff --git a/scripts/vscripts/cards/examples/card_55.lua b/scripts/vscripts/cards/examples/card_55.lua new file mode 100644 index 0000000..a5dbefd --- /dev/null +++ b/scripts/vscripts/cards/examples/card_55.lua @@ -0,0 +1,161 @@ +local ____lualib = require("lualib_bundle") +local __TS__Class = ____lualib.__TS__Class +local __TS__ClassExtends = ____lualib.__TS__ClassExtends +local __TS__Decorate = ____lualib.__TS__Decorate +local ____exports = {} +local ____CardSystem = require("cards.CardSystem") +local CardBase = ____CardSystem.CardBase +local RegisterCard = ____CardSystem.RegisterCard +local ____CardBaseModifier = require("cards.CardBaseModifier") +local CardBaseModifier = ____CardBaseModifier.CardBaseModifier +local ____dota_ts_adapter = require("lib.dota_ts_adapter") +local registerModifier = ____dota_ts_adapter.registerModifier +local CARD_ID = 55 +____exports.card_55 = __TS__Class() +local card_55 = ____exports.card_55 +card_55.name = "card_55" +card_55.____file_path = "scripts/vscripts/cards/examples/card_55.lua" +__TS__ClassExtends(card_55, CardBase) +function card_55.prototype.GetModifierName(self) + return "modifier_card_55" +end +card_55 = __TS__Decorate(card_55, card_55, {RegisterCard}, {kind = "class", name = "card_55"}) +____exports.card_55 = card_55 +____exports.modifier_card_55 = __TS__Class() +local modifier_card_55 = ____exports.modifier_card_55 +modifier_card_55.name = "modifier_card_55" +modifier_card_55.____file_path = "scripts/vscripts/cards/examples/card_55.lua" +__TS__ClassExtends(modifier_card_55, CardBaseModifier) +function modifier_card_55.prototype.OnCustomCreated(self, params) + if not IsServer() then + return + end + local applied = self:applyToHomer() + if not applied then + self:StartIntervalThink(1) + end +end +function modifier_card_55.prototype.OnIntervalThink(self) + if not IsServer() then + return + end + local applied = self:applyToHomer() + if applied then + self:StartIntervalThink(-1) + end +end +function modifier_card_55.prototype.OnDestroy(self) + if not IsServer() then + return + end + if self.homerHealthMod and not self.homerHealthMod:IsNull() then + self.homerHealthMod:Destroy() + end + self.homerHealthMod = nil +end +function modifier_card_55.prototype.GetAttributes(self) + return MODIFIER_ATTRIBUTE_MULTIPLE +end +function modifier_card_55.prototype.getHealthBonusPct(self) + return self:getScaledCardValue("max_health_bonus_pct", 25, CARD_ID) +end +function modifier_card_55.prototype.OnCustomRefresh(self, _params) + if not IsServer() then + return + end + if self.homerHealthMod and not self.homerHealthMod:IsNull() then + local homerMod = self.homerHealthMod + homerMod:setBonusPct(self:getScaledCardValue("max_health_bonus_pct", 25, CARD_ID)) + end +end +function modifier_card_55.prototype.findHomer(self) + local byTargetName = Entities:FindByName(nil, "npc_homer") + if byTargetName and IsValidEntity(byTargetName) and not byTargetName:IsNull() and byTargetName:GetUnitName() == "npc_homer" then + return byTargetName + end + local units = FindUnitsInRadius( + DOTA_TEAM_GOODGUYS, + Vector(0, 0, 0), + nil, + 30000, + DOTA_UNIT_TARGET_TEAM_BOTH, + DOTA_UNIT_TARGET_BASIC + DOTA_UNIT_TARGET_HERO, + DOTA_UNIT_TARGET_FLAG_INVULNERABLE, + FIND_ANY_ORDER, + false + ) + for ____, unit in ipairs(units) do + if unit and IsValidEntity(unit) and not unit:IsNull() and unit:GetUnitName() == "npc_homer" then + return unit + end + end + return nil +end +function modifier_card_55.prototype.applyToHomer(self) + local owner = self:GetParent() + if not owner or not IsValidEntity(owner) then + return false + end + if self.homerHealthMod and not self.homerHealthMod:IsNull() then + return true + end + local homer = self:findHomer() + if not homer then + return false + end + self.homerHealthMod = homer:AddNewModifier( + owner, + getModifierSourceAbility(nil, owner), + ____exports.modifier_card_55_homer.name, + {bonus_pct = self:getHealthBonusPct()} + ) + return self.homerHealthMod ~= nil and not self.homerHealthMod:IsNull() +end +modifier_card_55 = __TS__Decorate( + modifier_card_55, + modifier_card_55, + {registerModifier(nil)}, + {kind = "class", name = "modifier_card_55"} +) +____exports.modifier_card_55 = modifier_card_55 +____exports.modifier_card_55_homer = __TS__Class() +local modifier_card_55_homer = ____exports.modifier_card_55_homer +modifier_card_55_homer.name = "modifier_card_55_homer" +modifier_card_55_homer.____file_path = "scripts/vscripts/cards/examples/card_55.lua" +__TS__ClassExtends(modifier_card_55_homer, CardBaseModifier) +function modifier_card_55_homer.prototype.____constructor(self, ...) + CardBaseModifier.prototype.____constructor(self, ...) + self.bonusPct = 25 +end +function modifier_card_55_homer.prototype.setBonusPct(self, pct) + self.bonusPct = pct + local homer = self:GetParent() + if homer and IsValidEntity(homer) and homer:IsRealHero() then + homer:CalculateStatBonus(true) + end +end +function modifier_card_55_homer.prototype.OnCustomCreated(self, params) + if params and params.bonus_pct ~= nil then + self.bonusPct = params.bonus_pct + end +end +function modifier_card_55_homer.prototype.IsHidden(self) + return true +end +function modifier_card_55_homer.prototype.GetAttributes(self) + return MODIFIER_ATTRIBUTE_MULTIPLE +end +function modifier_card_55_homer.prototype.DeclareFunctions(self) + return {MODIFIER_PROPERTY_EXTRA_HEALTH_PERCENTAGE} +end +function modifier_card_55_homer.prototype.GetModifierExtraHealthPercentage(self) + return self.bonusPct +end +modifier_card_55_homer = __TS__Decorate( + modifier_card_55_homer, + modifier_card_55_homer, + {registerModifier(nil)}, + {kind = "class", name = "modifier_card_55_homer"} +) +____exports.modifier_card_55_homer = modifier_card_55_homer +return ____exports diff --git a/scripts/vscripts/cards/examples/card_56.lua b/scripts/vscripts/cards/examples/card_56.lua new file mode 100644 index 0000000..b01ac77 --- /dev/null +++ b/scripts/vscripts/cards/examples/card_56.lua @@ -0,0 +1,115 @@ +local ____lualib = require("lualib_bundle") +local __TS__Class = ____lualib.__TS__Class +local __TS__ClassExtends = ____lualib.__TS__ClassExtends +local __TS__Decorate = ____lualib.__TS__Decorate +local ____exports = {} +local ____CardSystem = require("cards.CardSystem") +local CardBase = ____CardSystem.CardBase +local RegisterCard = ____CardSystem.RegisterCard +local ____CardBaseModifier = require("cards.CardBaseModifier") +local CardBaseModifier = ____CardBaseModifier.CardBaseModifier +local ____dota_ts_adapter = require("lib.dota_ts_adapter") +local registerModifier = ____dota_ts_adapter.registerModifier +local ____modifier_card_56_homer = require("cards.modifier_card_56_homer") +local CARD_56_HOMER_MODIFIER_NAME = ____modifier_card_56_homer.CARD_56_HOMER_MODIFIER_NAME +local CARD_ID = 56 +____exports.card_56 = __TS__Class() +local card_56 = ____exports.card_56 +card_56.name = "card_56" +card_56.____file_path = "scripts/vscripts/cards/examples/card_56.lua" +__TS__ClassExtends(card_56, CardBase) +function card_56.prototype.GetModifierName(self) + return "modifier_card_56" +end +card_56 = __TS__Decorate(card_56, card_56, {RegisterCard}, {kind = "class", name = "card_56"}) +____exports.card_56 = card_56 +____exports.modifier_card_56 = __TS__Class() +local modifier_card_56 = ____exports.modifier_card_56 +modifier_card_56.name = "modifier_card_56" +modifier_card_56.____file_path = "scripts/vscripts/cards/examples/card_56.lua" +__TS__ClassExtends(modifier_card_56, CardBaseModifier) +function modifier_card_56.prototype.OnCustomCreated(self, params) + if not IsServer() then + return + end + local applied = self:applyToHomer() + if not applied then + self:StartIntervalThink(1) + end +end +function modifier_card_56.prototype.OnIntervalThink(self) + if not IsServer() then + return + end + local applied = self:applyToHomer() + if applied then + self:StartIntervalThink(-1) + end +end +function modifier_card_56.prototype.OnDestroy(self) + if not IsServer() then + return + end + if self.homerReflectMod and not self.homerReflectMod:IsNull() then + self.homerReflectMod:Destroy() + end + self.homerReflectMod = nil +end +function modifier_card_56.prototype.GetAttributes(self) + return MODIFIER_ATTRIBUTE_MULTIPLE +end +function modifier_card_56.prototype.findHomer(self) + local byTargetName = Entities:FindByName(nil, "npc_homer") + if byTargetName and IsValidEntity(byTargetName) and not byTargetName:IsNull() and byTargetName:GetUnitName() == "npc_homer" then + return byTargetName + end + local units = FindUnitsInRadius( + DOTA_TEAM_GOODGUYS, + Vector(0, 0, 0), + nil, + 30000, + DOTA_UNIT_TARGET_TEAM_BOTH, + DOTA_UNIT_TARGET_BASIC + DOTA_UNIT_TARGET_HERO, + DOTA_UNIT_TARGET_FLAG_INVULNERABLE, + FIND_ANY_ORDER, + false + ) + for ____, unit in ipairs(units) do + if unit and IsValidEntity(unit) and not unit:IsNull() and unit:GetUnitName() == "npc_homer" then + return unit + end + end + return nil +end +function modifier_card_56.prototype.applyToHomer(self) + local owner = self:GetParent() + if not owner or not IsValidEntity(owner) then + return false + end + if self.homerReflectMod and not self.homerReflectMod:IsNull() then + return true + end + local homer = self:findHomer() + if not homer then + return false + end + local reflectPct = math.max( + 0, + self:getScaledCardValue("creep_reflect_pct", 15, CARD_ID) + ) + self.homerReflectMod = homer:AddNewModifier( + owner, + getModifierSourceAbility(nil, owner), + CARD_56_HOMER_MODIFIER_NAME, + {reflect_pct = reflectPct} + ) + return self.homerReflectMod ~= nil and not self.homerReflectMod:IsNull() +end +modifier_card_56 = __TS__Decorate( + modifier_card_56, + modifier_card_56, + {registerModifier(nil)}, + {kind = "class", name = "modifier_card_56"} +) +____exports.modifier_card_56 = modifier_card_56 +return ____exports diff --git a/scripts/vscripts/cards/examples/card_57.lua b/scripts/vscripts/cards/examples/card_57.lua new file mode 100644 index 0000000..c5348bd --- /dev/null +++ b/scripts/vscripts/cards/examples/card_57.lua @@ -0,0 +1,58 @@ +local ____lualib = require("lualib_bundle") +local __TS__Class = ____lualib.__TS__Class +local __TS__ClassExtends = ____lualib.__TS__ClassExtends +local __TS__Decorate = ____lualib.__TS__Decorate +local ____exports = {} +local ____CardSystem = require("cards.CardSystem") +local CardBase = ____CardSystem.CardBase +local RegisterCard = ____CardSystem.RegisterCard +local ____CardBaseModifier = require("cards.CardBaseModifier") +local CardBaseModifier = ____CardBaseModifier.CardBaseModifier +local ____dota_ts_adapter = require("lib.dota_ts_adapter") +local registerModifier = ____dota_ts_adapter.registerModifier +____exports.card_57 = __TS__Class() +local card_57 = ____exports.card_57 +card_57.name = "card_57" +card_57.____file_path = "scripts/vscripts/cards/examples/card_57.lua" +__TS__ClassExtends(card_57, CardBase) +function card_57.prototype.GetModifierName(self) + return "modifier_card_57" +end +card_57 = __TS__Decorate(card_57, card_57, {RegisterCard}, {kind = "class", name = "card_57"}) +____exports.card_57 = card_57 +____exports.modifier_card_57 = __TS__Class() +local modifier_card_57 = ____exports.modifier_card_57 +modifier_card_57.name = "modifier_card_57" +modifier_card_57.____file_path = "scripts/vscripts/cards/examples/card_57.lua" +__TS__ClassExtends(modifier_card_57, CardBaseModifier) +function modifier_card_57.prototype.OnCustomCreated(self, params) + if not IsServer() then + return + end + local hero = self:GetParent() + if not hero or not IsValidEntity(hero) or not hero:IsRealHero() then + return + end + Timers:CreateTimer( + 0, + function() + local player = hero:GetPlayerOwner() + if not player or not player.cardSystem then + return nil + end + player.cardSystem:DuplicateCurrentPool() + return nil + end + ) +end +function modifier_card_57.prototype.IsHidden(self) + return true +end +modifier_card_57 = __TS__Decorate( + modifier_card_57, + modifier_card_57, + {registerModifier(nil)}, + {kind = "class", name = "modifier_card_57"} +) +____exports.modifier_card_57 = modifier_card_57 +return ____exports diff --git a/scripts/vscripts/cards/examples/card_58.lua b/scripts/vscripts/cards/examples/card_58.lua new file mode 100644 index 0000000..6f30ba9 --- /dev/null +++ b/scripts/vscripts/cards/examples/card_58.lua @@ -0,0 +1,73 @@ +local ____lualib = require("lualib_bundle") +local __TS__Class = ____lualib.__TS__Class +local __TS__ClassExtends = ____lualib.__TS__ClassExtends +local __TS__Decorate = ____lualib.__TS__Decorate +local ____exports = {} +local ____CardSystem = require("cards.CardSystem") +local CardBase = ____CardSystem.CardBase +local RegisterCard = ____CardSystem.RegisterCard +local ____CardBaseModifier = require("cards.CardBaseModifier") +local CardBaseModifier = ____CardBaseModifier.CardBaseModifier +local ____dota_ts_adapter = require("lib.dota_ts_adapter") +local registerModifier = ____dota_ts_adapter.registerModifier +local CARD_ID = 58 +____exports.card_58 = __TS__Class() +local card_58 = ____exports.card_58 +card_58.name = "card_58" +card_58.____file_path = "scripts/vscripts/cards/examples/card_58.lua" +__TS__ClassExtends(card_58, CardBase) +function card_58.prototype.GetModifierName(self) + return "modifier_card_58" +end +card_58 = __TS__Decorate(card_58, card_58, {RegisterCard}, {kind = "class", name = "card_58"}) +____exports.card_58 = card_58 +____exports.modifier_card_58 = __TS__Class() +local modifier_card_58 = ____exports.modifier_card_58 +modifier_card_58.name = "modifier_card_58" +modifier_card_58.____file_path = "scripts/vscripts/cards/examples/card_58.lua" +__TS__ClassExtends(modifier_card_58, CardBaseModifier) +function modifier_card_58.prototype.OnCustomCreated(self, params) + if not IsServer() then + return + end + local hero = self:GetParent() + if not hero or not IsValidEntity(hero) or not hero:IsRealHero() then + return + end + Timers:CreateTimer( + 0, + function() + local player = hero:GetPlayerOwner() + if not player or not player.cardSystem then + return nil + end + local extraSelections = math.max( + 0, + math.floor(self:getCardValue("extra_selections_on_start", 2, CARD_ID)) + ) + local cardsPerSelection = math.max( + 1, + math.floor(self:getCardValue("cards_per_selection", 3, CARD_ID)) + ) + do + local i = 0 + while i < extraSelections do + player.cardSystem:ShowCardSelection(cardsPerSelection, "modifier_card_58_bonus_selection") + i = i + 1 + end + end + return nil + end + ) +end +function modifier_card_58.prototype.IsHidden(self) + return true +end +modifier_card_58 = __TS__Decorate( + modifier_card_58, + modifier_card_58, + {registerModifier(nil)}, + {kind = "class", name = "modifier_card_58"} +) +____exports.modifier_card_58 = modifier_card_58 +return ____exports diff --git a/scripts/vscripts/cards/examples/card_59.lua b/scripts/vscripts/cards/examples/card_59.lua new file mode 100644 index 0000000..4d17703 --- /dev/null +++ b/scripts/vscripts/cards/examples/card_59.lua @@ -0,0 +1,48 @@ +local ____lualib = require("lualib_bundle") +local __TS__Class = ____lualib.__TS__Class +local __TS__ClassExtends = ____lualib.__TS__ClassExtends +local __TS__Decorate = ____lualib.__TS__Decorate +local ____exports = {} +local ____CardSystem = require("cards.CardSystem") +local CardBase = ____CardSystem.CardBase +local RegisterCard = ____CardSystem.RegisterCard +local ____card_value_resolver = require("cards.card_value_resolver") +local getCardValueByLevel = ____card_value_resolver.getCardValueByLevel +local CARD_ID = 59 +____exports.card_59 = __TS__Class() +local card_59 = ____exports.card_59 +card_59.name = "card_59" +card_59.____file_path = "scripts/vscripts/cards/examples/card_59.lua" +__TS__ClassExtends(card_59, CardBase) +function card_59.prototype.OnCreated(self) + if not IsServer() then + return + end + local hero = self:GetHero() + if not hero or not IsValidEntity(hero) or not hero:IsRealHero() then + return + end + local xpReward = math.max( + 0, + math.floor(getCardValueByLevel( + nil, + CARD_ID, + hero, + "xp_reward", + 1500 + )) + ) + if xpReward <= 0 then + return + end + hero:AddExperience(xpReward, DOTA_ModifyXP_Unspecified, false, true) +end +function card_59.prototype.GetModifierName(self) + return nil +end +function card_59.prototype.IsHidden(self) + return true +end +card_59 = __TS__Decorate(card_59, card_59, {RegisterCard}, {kind = "class", name = "card_59"}) +____exports.card_59 = card_59 +return ____exports diff --git a/scripts/vscripts/cards/examples/card_6.lua b/scripts/vscripts/cards/examples/card_6.lua new file mode 100644 index 0000000..6e3a503 --- /dev/null +++ b/scripts/vscripts/cards/examples/card_6.lua @@ -0,0 +1,70 @@ +local ____lualib = require("lualib_bundle") +local __TS__Class = ____lualib.__TS__Class +local __TS__ClassExtends = ____lualib.__TS__ClassExtends +local __TS__Decorate = ____lualib.__TS__Decorate +local ____exports = {} +local ____CardSystem = require("cards.CardSystem") +local CardBase = ____CardSystem.CardBase +local RegisterCard = ____CardSystem.RegisterCard +local ____CardBaseModifier = require("cards.CardBaseModifier") +local CardBaseModifier = ____CardBaseModifier.CardBaseModifier +local ____dota_ts_adapter = require("lib.dota_ts_adapter") +local registerModifier = ____dota_ts_adapter.registerModifier +local ____card_value_resolver = require("cards.card_value_resolver") +local getCardValueByLevel = ____card_value_resolver.getCardValueByLevel +local CARD_ID = 6 +____exports.card_6 = __TS__Class() +local card_6 = ____exports.card_6 +card_6.name = "card_6" +card_6.____file_path = "scripts/vscripts/cards/examples/card_6.lua" +__TS__ClassExtends(card_6, CardBase) +function card_6.prototype.GetModifierName(self) + return "modifier_card_6" +end +card_6 = __TS__Decorate(card_6, card_6, {RegisterCard}, {kind = "class", name = "card_6"}) +____exports.card_6 = card_6 +____exports.modifier_card_6 = __TS__Class() +local modifier_card_6 = ____exports.modifier_card_6 +modifier_card_6.name = "modifier_card_6" +modifier_card_6.____file_path = "scripts/vscripts/cards/examples/card_6.lua" +__TS__ClassExtends(modifier_card_6, CardBaseModifier) +function modifier_card_6.prototype.OnCustomCreated(self, params) + if not IsServer() then + return + end + local hero = self:GetParent() + if not hero or not IsValidEntity(hero) then + return + end + Timers:CreateTimer( + 0, + function() + local player = hero:GetPlayerOwner() + if not player or not player.cardSystem then + return nil + end + player.cardSystem:TryGrantCard6Level2FreeRerolls() + local showCount = math.max( + 1, + math.floor(getCardValueByLevel( + nil, + CARD_ID, + hero, + "card_show_count", + 3 + )) + ) + player.cardSystem:ShowCardSelection(showCount, "modifier_card_6_bonus_selection") + self:Destroy() + return nil + end + ) +end +modifier_card_6 = __TS__Decorate( + modifier_card_6, + modifier_card_6, + {registerModifier(nil)}, + {kind = "class", name = "modifier_card_6"} +) +____exports.modifier_card_6 = modifier_card_6 +return ____exports diff --git a/scripts/vscripts/cards/examples/card_60.lua b/scripts/vscripts/cards/examples/card_60.lua new file mode 100644 index 0000000..876e7f4 --- /dev/null +++ b/scripts/vscripts/cards/examples/card_60.lua @@ -0,0 +1,52 @@ +local ____lualib = require("lualib_bundle") +local __TS__Class = ____lualib.__TS__Class +local __TS__ClassExtends = ____lualib.__TS__ClassExtends +local __TS__Decorate = ____lualib.__TS__Decorate +local ____exports = {} +local ____CardSystem = require("cards.CardSystem") +local CardBase = ____CardSystem.CardBase +local RegisterCard = ____CardSystem.RegisterCard +local ____card_value_resolver = require("cards.card_value_resolver") +local getCardValueByLevel = ____card_value_resolver.getCardValueByLevel +local CARD_ID = 60 +____exports.card_60 = __TS__Class() +local card_60 = ____exports.card_60 +card_60.name = "card_60" +card_60.____file_path = "scripts/vscripts/cards/examples/card_60.lua" +__TS__ClassExtends(card_60, CardBase) +function card_60.prototype.OnCreated(self) + if not IsServer() then + return + end + local hero = self:GetHero() + if not hero or not IsValidEntity(hero) or not hero:IsRealHero() then + return + end + local playerId = hero:GetPlayerOwnerID() + if playerId == nil or playerId == nil or playerId < 0 then + return + end + local goldReward = math.max( + 0, + math.floor(getCardValueByLevel( + nil, + CARD_ID, + hero, + "gold_reward", + 2000 + )) + ) + if goldReward <= 0 then + return + end + PlayerResource:ModifyGold(playerId, goldReward, true, DOTA_ModifyGold_Unspecified) +end +function card_60.prototype.GetModifierName(self) + return nil +end +function card_60.prototype.IsHidden(self) + return true +end +card_60 = __TS__Decorate(card_60, card_60, {RegisterCard}, {kind = "class", name = "card_60"}) +____exports.card_60 = card_60 +return ____exports diff --git a/scripts/vscripts/cards/examples/card_61.lua b/scripts/vscripts/cards/examples/card_61.lua new file mode 100644 index 0000000..2702bfc --- /dev/null +++ b/scripts/vscripts/cards/examples/card_61.lua @@ -0,0 +1,54 @@ +local ____lualib = require("lualib_bundle") +local __TS__Class = ____lualib.__TS__Class +local __TS__ClassExtends = ____lualib.__TS__ClassExtends +local __TS__Decorate = ____lualib.__TS__Decorate +local ____exports = {} +local ____CardSystem = require("cards.CardSystem") +local CardBase = ____CardSystem.CardBase +local RegisterCard = ____CardSystem.RegisterCard +local ____card_value_resolver = require("cards.card_value_resolver") +local getCardValueByLevel = ____card_value_resolver.getCardValueByLevel +local ____crystal_currency = require("crystal_currency") +local GetCrystalCurrency = ____crystal_currency.GetCrystalCurrency +local CARD_ID = 61 +____exports.card_61 = __TS__Class() +local card_61 = ____exports.card_61 +card_61.name = "card_61" +card_61.____file_path = "scripts/vscripts/cards/examples/card_61.lua" +__TS__ClassExtends(card_61, CardBase) +function card_61.prototype.OnCreated(self) + if not IsServer() then + return + end + local hero = self:GetHero() + if not hero or not IsValidEntity(hero) or not hero:IsRealHero() then + return + end + local playerId = hero:GetPlayerOwnerID() + if playerId == nil or playerId == nil or playerId < 0 then + return + end + local crystalsReward = math.max( + 0, + math.floor(getCardValueByLevel( + nil, + CARD_ID, + hero, + "crystals_reward", + 25 + )) + ) + if crystalsReward <= 0 then + return + end + GetCrystalCurrency(nil):addCrystals(playerId, crystalsReward) +end +function card_61.prototype.GetModifierName(self) + return nil +end +function card_61.prototype.IsHidden(self) + return true +end +card_61 = __TS__Decorate(card_61, card_61, {RegisterCard}, {kind = "class", name = "card_61"}) +____exports.card_61 = card_61 +return ____exports diff --git a/scripts/vscripts/cards/examples/card_62.lua b/scripts/vscripts/cards/examples/card_62.lua new file mode 100644 index 0000000..97e850b --- /dev/null +++ b/scripts/vscripts/cards/examples/card_62.lua @@ -0,0 +1,41 @@ +local ____lualib = require("lualib_bundle") +local __TS__Class = ____lualib.__TS__Class +local __TS__ClassExtends = ____lualib.__TS__ClassExtends +local __TS__Decorate = ____lualib.__TS__Decorate +local ____exports = {} +local ____card_catalog = require("card_catalog") +local WISH_GIFT_CARD_IDS = ____card_catalog.WISH_GIFT_CARD_IDS +local ____CardSystem = require("cards.CardSystem") +local CardBase = ____CardSystem.CardBase +local RegisterCard = ____CardSystem.RegisterCard +local ShowCardSelectionExactOptionsToPlayer = ____CardSystem.ShowCardSelectionExactOptionsToPlayer +local CARD_ID = 62 +local WISH_OPTIONS = WISH_GIFT_CARD_IDS +____exports.card_62 = __TS__Class() +local card_62 = ____exports.card_62 +card_62.name = "card_62" +card_62.____file_path = "scripts/vscripts/cards/examples/card_62.lua" +__TS__ClassExtends(card_62, CardBase) +function card_62.prototype.OnCreated(self) + if not IsServer() then + return + end + local hero = self:GetHero() + if not hero or not IsValidEntity(hero) or not hero:IsRealHero() then + return + end + local playerId = hero:GetPlayerOwnerID() + if playerId == nil or playerId == nil or playerId < 0 then + return + end + ShowCardSelectionExactOptionsToPlayer(nil, playerId, WISH_OPTIONS, "card_62_wish_choice") +end +function card_62.prototype.GetModifierName(self) + return nil +end +function card_62.prototype.IsHidden(self) + return true +end +card_62 = __TS__Decorate(card_62, card_62, {RegisterCard}, {kind = "class", name = "card_62"}) +____exports.card_62 = card_62 +return ____exports diff --git a/scripts/vscripts/cards/examples/card_63.lua b/scripts/vscripts/cards/examples/card_63.lua new file mode 100644 index 0000000..72ec073 --- /dev/null +++ b/scripts/vscripts/cards/examples/card_63.lua @@ -0,0 +1,145 @@ +local ____lualib = require("lualib_bundle") +local __TS__Class = ____lualib.__TS__Class +local __TS__ClassExtends = ____lualib.__TS__ClassExtends +local __TS__Decorate = ____lualib.__TS__Decorate +local ____exports = {} +local ____custom_game_events = require("custom_game_events") +local ensurePlayerCardSystem = ____custom_game_events.ensurePlayerCardSystem +local ____CardSystem = require("cards.CardSystem") +local CardBase = ____CardSystem.CardBase +local RegisterCard = ____CardSystem.RegisterCard +local ____CardBaseModifier = require("cards.CardBaseModifier") +local CardBaseModifier = ____CardBaseModifier.CardBaseModifier +local ____dota_ts_adapter = require("lib.dota_ts_adapter") +local registerModifier = ____dota_ts_adapter.registerModifier +local ____card_value_resolver = require("cards.card_value_resolver") +local getCardValueByLevel = ____card_value_resolver.getCardValueByLevel +local CARD_ID = 63 +local DEFAULT_BONUS_SELECTION_COUNT = 3 +local BONUS_SELECTION_SOURCE = "card_63_survivor_contract_bonus" +local hadNightDeathByPlayer = {} +local lastMorningSeqCard63ByPlayer = {} +local isDeathListenerInitialized = false +local function initializeNightDeathListener(self) + if not IsServer() or isDeathListenerInitialized then + return + end + isDeathListenerInitialized = true + ListenToGameEvent( + "entity_killed", + function(event) + local killed = EntIndexToHScript(event.entindex_killed) + if not killed or not IsValidEntity(killed) or not killed:IsRealHero() then + return + end + if GameRules:IsDaytime() then + return + end + local playerId = killed:GetPlayerOwnerID() + if playerId == nil or playerId < 0 then + return + end + hadNightDeathByPlayer[playerId] = true + end, + nil + ) +end +____exports.card_63 = __TS__Class() +local card_63 = ____exports.card_63 +card_63.name = "card_63" +card_63.____file_path = "scripts/vscripts/cards/examples/card_63.lua" +__TS__ClassExtends(card_63, CardBase) +function card_63.prototype.OnCreated(self) + initializeNightDeathListener(nil) +end +function card_63.prototype.GetModifierName(self) + return "modifier_card_63" +end +card_63 = __TS__Decorate(card_63, card_63, {RegisterCard}, {kind = "class", name = "card_63"}) +____exports.card_63 = card_63 +____exports.modifier_card_63 = __TS__Class() +local modifier_card_63 = ____exports.modifier_card_63 +modifier_card_63.name = "modifier_card_63" +modifier_card_63.____file_path = "scripts/vscripts/cards/examples/card_63.lua" +__TS__ClassExtends(modifier_card_63, CardBaseModifier) +modifier_card_63 = __TS__Decorate( + modifier_card_63, + modifier_card_63, + {registerModifier(nil)}, + {kind = "class", name = "modifier_card_63"} +) +____exports.modifier_card_63 = modifier_card_63 +--- Начало ночи: сбрасываем флаг смертей для нового цикла ночи. +function ____exports.notifyCard63NightStarted(self) + if not IsServer() then + return + end + do + local i = 0 + while i < DOTA_MAX_PLAYERS do + hadNightDeathByPlayer[i] = false + i = i + 1 + end + end +end +--- Рассвет: если за ночь не было смертей, даём дополнительный выбор карты. +function ____exports.notifyCard63MorningStarted(self, _morningSequence) + if not IsServer() then + return + end + do + local i = 0 + while i < DOTA_MAX_PLAYERS do + do + local pid = i + local player = PlayerResource:GetPlayer(pid) + if not player then + goto __continue15 + end + local cardSystem = ensurePlayerCardSystem(nil, pid) + if not cardSystem then + goto __continue15 + end + local copies = cardSystem:GetActiveCardCopies(CARD_ID) + if copies <= 0 then + goto __continue15 + end + local hero = player:GetAssignedHero() + if not hero or not IsValidEntity(hero) or not hero:IsRealHero() then + goto __continue15 + end + local prevSeq = lastMorningSeqCard63ByPlayer[pid] or 0 + if _morningSequence <= prevSeq then + goto __continue15 + end + if hadNightDeathByPlayer[pid] == true then + goto __continue15 + end + local baseBonusCount = math.max( + 1, + getCardValueByLevel( + nil, + CARD_ID, + hero, + "bonus_card_choices", + DEFAULT_BONUS_SELECTION_COUNT + ) + ) + do + local copyIndex = 0 + while copyIndex < copies do + cardSystem:ShowCardSelection( + baseBonusCount, + (BONUS_SELECTION_SOURCE .. "_") .. tostring(copyIndex + 1) + ) + copyIndex = copyIndex + 1 + end + end + lastMorningSeqCard63ByPlayer[pid] = _morningSequence + end + ::__continue15:: + i = i + 1 + end + end +end +return ____exports diff --git a/scripts/vscripts/cards/examples/card_64.lua b/scripts/vscripts/cards/examples/card_64.lua new file mode 100644 index 0000000..50b3466 --- /dev/null +++ b/scripts/vscripts/cards/examples/card_64.lua @@ -0,0 +1,84 @@ +local ____lualib = require("lualib_bundle") +local __TS__Class = ____lualib.__TS__Class +local __TS__ClassExtends = ____lualib.__TS__ClassExtends +local __TS__Decorate = ____lualib.__TS__Decorate +local ____exports = {} +local ____CardSystem = require("cards.CardSystem") +local CardBase = ____CardSystem.CardBase +local RegisterCard = ____CardSystem.RegisterCard +local ____CardBaseModifier = require("cards.CardBaseModifier") +local CardBaseModifier = ____CardBaseModifier.CardBaseModifier +local ____dota_ts_adapter = require("lib.dota_ts_adapter") +local registerModifier = ____dota_ts_adapter.registerModifier +local ____ability_stacking_crit = require("abilities.modifiers.ability_stacking_crit") +local modifier_stacking_crit = ____ability_stacking_crit.modifier_stacking_crit +local CARD_ID = 64 +____exports.card_64 = __TS__Class() +local card_64 = ____exports.card_64 +card_64.name = "card_64" +card_64.____file_path = "scripts/vscripts/cards/examples/card_64.lua" +__TS__ClassExtends(card_64, CardBase) +function card_64.prototype.GetModifierName(self) + return "modifier_card_64" +end +card_64 = __TS__Decorate(card_64, card_64, {RegisterCard}, {kind = "class", name = "card_64"}) +____exports.card_64 = card_64 +____exports.modifier_card_64 = __TS__Class() +local modifier_card_64 = ____exports.modifier_card_64 +modifier_card_64.name = "modifier_card_64" +modifier_card_64.____file_path = "scripts/vscripts/cards/examples/card_64.lua" +__TS__ClassExtends(modifier_card_64, CardBaseModifier) +function modifier_card_64.prototype.getValue(self, key, fallback) + return self:getCardValue(key, fallback, CARD_ID) +end +function modifier_card_64.prototype.OnCustomCreated(self, params) + if not IsServer() then + return + end +end +function modifier_card_64.prototype.OnCustomRefresh(self, params) + if not IsServer() then + return + end +end +function modifier_card_64.prototype.DeclareFunctions(self) + return {MODIFIER_EVENT_ON_ATTACK_START} +end +function modifier_card_64.prototype.OnAttackStart(self, params) + if not IsServer() then + return + end + local attacker = self:GetParent() + if not attacker or not IsValidEntity(attacker) or not attacker:IsRealHero() then + return + end + if params.attacker ~= attacker then + return + end + local target = params.target + if not target or not IsValidEntity(target) or target:IsNull() or not target:IsAlive() then + return + end + if target:GetTeamNumber() == attacker:GetTeamNumber() then + return + end + local hpThresholdPct = self:getValue("hp_threshold_pct", 25) + local targetHpPct = target:GetHealthPercent() + local stackingCrit = modifier_stacking_crit:GetForUnit(attacker) + if not stackingCrit then + return + end + if targetHpPct <= hpThresholdPct then + stackingCrit:ForceNextCritOnTarget(target) + return + end + stackingCrit:ForceNextCritOnTarget(nil) +end +modifier_card_64 = __TS__Decorate( + modifier_card_64, + modifier_card_64, + {registerModifier(nil)}, + {kind = "class", name = "modifier_card_64"} +) +____exports.modifier_card_64 = modifier_card_64 +return ____exports diff --git a/scripts/vscripts/cards/examples/card_65.lua b/scripts/vscripts/cards/examples/card_65.lua new file mode 100644 index 0000000..1e33462 --- /dev/null +++ b/scripts/vscripts/cards/examples/card_65.lua @@ -0,0 +1,114 @@ +local ____lualib = require("lualib_bundle") +local __TS__Class = ____lualib.__TS__Class +local __TS__ClassExtends = ____lualib.__TS__ClassExtends +local __TS__Decorate = ____lualib.__TS__Decorate +local ____exports = {} +local ____CardSystem = require("cards.CardSystem") +local CardBase = ____CardSystem.CardBase +local RegisterCard = ____CardSystem.RegisterCard +local ____CardBaseModifier = require("cards.CardBaseModifier") +local CardBaseModifier = ____CardBaseModifier.CardBaseModifier +local ____dota_ts_adapter = require("lib.dota_ts_adapter") +local registerModifier = ____dota_ts_adapter.registerModifier +local ____ability_stacking_crit = require("abilities.modifiers.ability_stacking_crit") +local modifier_stacking_crit = ____ability_stacking_crit.modifier_stacking_crit +local CARD_ID = 65 +local CRIT_SOURCE = "card_65_first_bite_crit" +____exports.card_65 = __TS__Class() +local card_65 = ____exports.card_65 +card_65.name = "card_65" +card_65.____file_path = "scripts/vscripts/cards/examples/card_65.lua" +__TS__ClassExtends(card_65, CardBase) +function card_65.prototype.GetModifierName(self) + return "modifier_card_65" +end +card_65 = __TS__Decorate(card_65, card_65, {RegisterCard}, {kind = "class", name = "card_65"}) +____exports.card_65 = card_65 +____exports.modifier_card_65 = __TS__Class() +local modifier_card_65 = ____exports.modifier_card_65 +modifier_card_65.name = "modifier_card_65" +modifier_card_65.____file_path = "scripts/vscripts/cards/examples/card_65.lua" +__TS__ClassExtends(modifier_card_65, CardBaseModifier) +function modifier_card_65.prototype.getValue(self, key, fallback) + return self:getCardValue(key, fallback, CARD_ID) +end +function modifier_card_65.prototype.OnCustomCreated(self, params) + if not IsServer() then + return + end + self:ensureCustomCritRegistered() +end +function modifier_card_65.prototype.OnCustomRefresh(self, params) + if not IsServer() then + return + end + self:ensureCustomCritRegistered() +end +function modifier_card_65.prototype.OnDestroy(self) + if not IsServer() then + return + end + local attacker = self:GetParent() + if not attacker or not IsValidEntity(attacker) or not attacker:IsRealHero() then + return + end + local stackingCrit = modifier_stacking_crit:GetForUnit(attacker) + if stackingCrit ~= nil then + stackingCrit:RemoveCrit(CRIT_SOURCE) + end + if stackingCrit ~= nil then + stackingCrit:ForceNextCritOnTarget(nil) + end +end +function modifier_card_65.prototype.DeclareFunctions(self) + return {MODIFIER_EVENT_ON_ATTACK_START} +end +function modifier_card_65.prototype.OnAttackStart(self, params) + if not IsServer() then + return + end + local attacker = self:GetParent() + if not attacker or not IsValidEntity(attacker) or not attacker:IsRealHero() then + return + end + if params.attacker ~= attacker then + return + end + local target = params.target + if not target or not IsValidEntity(target) or target:IsNull() or not target:IsAlive() then + return + end + if target:GetTeamNumber() == attacker:GetTeamNumber() then + return + end + local stackingCrit = modifier_stacking_crit:GetForUnit(attacker) + if not stackingCrit then + return + end + local fullHpThresholdPct = self:getValue("target_hp_full_pct", 100) + if target:GetHealthPercent() >= fullHpThresholdPct then + stackingCrit:ForceNextCritOnTarget(target) + return + end + stackingCrit:ForceNextCritOnTarget(nil) +end +function modifier_card_65.prototype.ensureCustomCritRegistered(self) + local attacker = self:GetParent() + if not attacker or not IsValidEntity(attacker) or not attacker:IsRealHero() then + return + end + local stackingCrit = modifier_stacking_crit:GetForUnit(attacker) + if not stackingCrit then + return + end + local critMultiplierPct = self:getValue("crit_multiplier_pct", 200) * self:getCardCopies() + stackingCrit:UpdateExistingCrit(0, critMultiplierPct, CRIT_SOURCE) +end +modifier_card_65 = __TS__Decorate( + modifier_card_65, + modifier_card_65, + {registerModifier(nil)}, + {kind = "class", name = "modifier_card_65"} +) +____exports.modifier_card_65 = modifier_card_65 +return ____exports diff --git a/scripts/vscripts/cards/examples/card_66.lua b/scripts/vscripts/cards/examples/card_66.lua new file mode 100644 index 0000000..b3995d5 --- /dev/null +++ b/scripts/vscripts/cards/examples/card_66.lua @@ -0,0 +1,124 @@ +local ____lualib = require("lualib_bundle") +local __TS__Class = ____lualib.__TS__Class +local __TS__ClassExtends = ____lualib.__TS__ClassExtends +local __TS__Decorate = ____lualib.__TS__Decorate +local ____exports = {} +local ____CardSystem = require("cards.CardSystem") +local CardBase = ____CardSystem.CardBase +local RegisterCard = ____CardSystem.RegisterCard +local ____CardBaseModifier = require("cards.CardBaseModifier") +local CardBaseModifier = ____CardBaseModifier.CardBaseModifier +local ____dota_ts_adapter = require("lib.dota_ts_adapter") +local registerModifier = ____dota_ts_adapter.registerModifier +local CARD_ID = 66 +____exports.card_66 = __TS__Class() +local card_66 = ____exports.card_66 +card_66.name = "card_66" +card_66.____file_path = "scripts/vscripts/cards/examples/card_66.lua" +__TS__ClassExtends(card_66, CardBase) +function card_66.prototype.GetModifierName(self) + return "modifier_card_66" +end +card_66 = __TS__Decorate(card_66, card_66, {RegisterCard}, {kind = "class", name = "card_66"}) +____exports.card_66 = card_66 +____exports.modifier_card_66 = __TS__Class() +local modifier_card_66 = ____exports.modifier_card_66 +modifier_card_66.name = "modifier_card_66" +modifier_card_66.____file_path = "scripts/vscripts/cards/examples/card_66.lua" +__TS__ClassExtends(modifier_card_66, CardBaseModifier) +function modifier_card_66.prototype.____constructor(self, ...) + CardBaseModifier.prototype.____constructor(self, ...) + self.pendingStrikeCharges = 0 +end +function modifier_card_66.prototype.getValue(self, key, fallback) + return self:getCardValue(key, fallback, CARD_ID) +end +function modifier_card_66.prototype.OnCustomCreated(self, _params) + if not IsServer() then + return + end + self.pendingStrikeCharges = 0 +end +function modifier_card_66.prototype.OnCustomRefresh(self, _params) + if not IsServer() then + return + end + if self.pendingStrikeCharges < 0 then + self.pendingStrikeCharges = 0 + end +end +function modifier_card_66.prototype.DeclareFunctions(self) + return {MODIFIER_EVENT_ON_ABILITY_EXECUTED, MODIFIER_EVENT_ON_ATTACK_LANDED, MODIFIER_PROPERTY_TOOLTIP} +end +function modifier_card_66.prototype.OnAbilityExecuted(self, event) + if not IsServer() then + return + end + local hero = self:GetParent() + if not hero or not IsValidEntity(hero) or not hero:IsRealHero() then + return + end + if event.unit ~= hero then + return + end + local ability = event.ability + if not ability or ability:IsNull() then + return + end + if ability:IsItem() then + return + end + self.pendingStrikeCharges = self.pendingStrikeCharges + 1 +end +function modifier_card_66.prototype.OnAttackLanded(self, event) + if not IsServer() then + return + end + if self.pendingStrikeCharges <= 0 then + return + end + local attacker = self:GetParent() + if not attacker or not IsValidEntity(attacker) or not attacker:IsRealHero() then + return + end + if event.attacker ~= attacker then + return + end + local target = event.target + if not target or not IsValidEntity(target) or target:IsNull() or not target:IsAlive() then + return + end + if target:GetTeamNumber() == attacker:GetTeamNumber() then + return + end + local copies = self:getCardCopies() + local hpPct = self:getValue("bonus_from_health_pct", 50) / 100 * copies + local manaPct = self:getValue("bonus_from_mana_pct", 50) / 100 * copies + local bonusDamage = attacker:GetHealth() * hpPct + attacker:GetMana() * manaPct + local pfx = ParticleManager:CreateParticle("particles/econ/items/dazzle/dazzle_ti9/dazzle_shadow_wave_ti9_crimson_impact_damage.vpcf", PATTACH_ABSORIGIN_FOLLOW, target) + ParticleManager:ReleaseParticleIndex(pfx) + SendOverheadEventMessage( + nil, + OVERHEAD_ALERT_DEADLY_BLOW, + target, + bonusDamage, + attacker:GetPlayerOwner() + ) + EmitSoundOnLocationWithCaster( + target:GetAbsOrigin(), + "Hero_ChaosKnight.ChaosStrike", + attacker + ) + if bonusDamage > 0 then + ApplyDamage({victim = target, attacker = attacker, damage = bonusDamage, damage_type = DAMAGE_TYPE_MAGICAL}) + end + self.pendingStrikeCharges = math.max(0, self.pendingStrikeCharges - 1) +end +modifier_card_66 = __TS__Decorate( + modifier_card_66, + modifier_card_66, + {registerModifier(nil)}, + {kind = "class", name = "modifier_card_66"} +) +____exports.modifier_card_66 = modifier_card_66 +return ____exports diff --git a/scripts/vscripts/cards/examples/card_67.lua b/scripts/vscripts/cards/examples/card_67.lua new file mode 100644 index 0000000..622ed1a --- /dev/null +++ b/scripts/vscripts/cards/examples/card_67.lua @@ -0,0 +1,138 @@ +local ____lualib = require("lualib_bundle") +local __TS__Class = ____lualib.__TS__Class +local __TS__ClassExtends = ____lualib.__TS__ClassExtends +local __TS__Decorate = ____lualib.__TS__Decorate +local ____exports = {} +local ____CardSystem = require("cards.CardSystem") +local CardBase = ____CardSystem.CardBase +local RegisterCard = ____CardSystem.RegisterCard +local ____CardBaseModifier = require("cards.CardBaseModifier") +local CardBaseModifier = ____CardBaseModifier.CardBaseModifier +local ____dota_ts_adapter = require("lib.dota_ts_adapter") +local registerModifier = ____dota_ts_adapter.registerModifier +local ____luck = require("utils.luck") +local rollLuckChance = ____luck.rollLuckChance +local ____card_value_resolver = require("cards.card_value_resolver") +local getCardValueByLevel = ____card_value_resolver.getCardValueByLevel +local CARD_ID = 67 +local PROC_HIT_PARTICLE = "particles/econ/items/centaur/centaur_ti9/centaur_double_edge_ti9_hit_tgt.vpcf" +local PROC_HIT_SOUND = "Hero_Centaur.DoubleEdge" +____exports.card_67 = __TS__Class() +local card_67 = ____exports.card_67 +card_67.name = "card_67" +card_67.____file_path = "scripts/vscripts/cards/examples/card_67.lua" +__TS__ClassExtends(card_67, CardBase) +function card_67.prototype.GetModifierName(self) + return "modifier_card_67" +end +card_67 = __TS__Decorate(card_67, card_67, {RegisterCard}, {kind = "class", name = "card_67"}) +____exports.card_67 = card_67 +____exports.modifier_card_67 = __TS__Class() +local modifier_card_67 = ____exports.modifier_card_67 +modifier_card_67.name = "modifier_card_67" +modifier_card_67.____file_path = "scripts/vscripts/cards/examples/card_67.lua" +__TS__ClassExtends(modifier_card_67, CardBaseModifier) +function modifier_card_67.prototype.____constructor(self, ...) + CardBaseModifier.prototype.____constructor(self, ...) + self.nextProcTime = 0 +end +function modifier_card_67.prototype.playProcFx(self, attacker, target) + local targetOrigin = target:GetAbsOrigin() + local pfx = ParticleManager:CreateParticle(PROC_HIT_PARTICLE, PATTACH_ABSORIGIN, attacker) + ParticleManager:SetParticleControl(pfx, 0, targetOrigin) + ParticleManager:SetParticleControl(pfx, 1, targetOrigin) + ParticleManager:SetParticleControl(pfx, 2, targetOrigin) + ParticleManager:SetParticleControl(pfx, 3, targetOrigin) + ParticleManager:SetParticleControl(pfx, 4, targetOrigin) + ParticleManager:SetParticleControl(pfx, 5, targetOrigin) + ParticleManager:SetParticleControl(pfx, 9, targetOrigin) + ParticleManager:ReleaseParticleIndex(pfx) +end +function modifier_card_67.prototype.getValue(self, key, fallback) + local hero = self:GetParent() + return getCardValueByLevel( + nil, + CARD_ID, + hero, + key, + fallback + ) +end +function modifier_card_67.prototype.OnCustomCreated(self, params) + local hero = self:GetParent() + if not hero or not IsValidEntity(hero) then + return + end + hero:CalculateStatBonus(true) +end +function modifier_card_67.prototype.DeclareFunctions(self) + return {MODIFIER_PROPERTY_EXTRA_HEALTH_BONUS, MODIFIER_PROPERTY_DAMAGEOUTGOING_PERCENTAGE, MODIFIER_EVENT_ON_ATTACK_LANDED, MODIFIER_PROPERTY_TOOLTIP} +end +function modifier_card_67.prototype.GetModifierExtraHealthBonus(self) + local hero = self:GetParent() + if not hero or not IsValidEntity(hero) then + return 0 + end + local hpPerStrength = self:getScaledCardValue("hp_per_strength", 2.5) + return hero:GetStrength() * hpPerStrength +end +function modifier_card_67.prototype.GetModifierDamageOutgoing_Percentage(self) + local hero = self:GetParent() + if not hero or not IsValidEntity(hero) then + return 0 + end + local outgoingPctPerStrength = self:getScaledCardValue("outgoing_pct_per_strength", 1) + return hero:GetStrength() * outgoingPctPerStrength +end +function modifier_card_67.prototype.OnAttackLanded(self, event) + if not IsServer() then + return + end + local attacker = self:GetParent() + if not attacker or not IsValidEntity(attacker) or not attacker:IsRealHero() then + return + end + if event.attacker ~= attacker then + return + end + local target = event.target + if not target or not IsValidEntity(target) or target:IsNull() or not target:IsAlive() then + return + end + if target:GetTeamNumber() == attacker:GetTeamNumber() then + return + end + local now = GameRules:GetGameTime() + if now < self.nextProcTime then + return + end + local procChancePct = self:getValue("proc_chance_pct", 50) + local didProc = rollLuckChance(nil, attacker, procChancePct / 100) + if not didProc then + return + end + local bonusDamagePctFromMaxHp = self:getScaledCardValue("bonus_damage_from_max_hp_pct", 70) + local bonusDamage = attacker:GetMaxHealth() * (bonusDamagePctFromMaxHp / 100) + if bonusDamage <= 0 then + return + end + ApplyDamage({victim = target, attacker = attacker, damage = bonusDamage, damage_type = DAMAGE_TYPE_PHYSICAL}) + EmitSoundOn(PROC_HIT_SOUND, target) + self:playProcFx(attacker, target) + SendOverheadEventMessage( + nil, + OVERHEAD_ALERT_CRITICAL, + target, + bonusDamage, + attacker:GetPlayerOwner() + ) + self.nextProcTime = now + self:getValue("proc_cooldown_sec", 3) +end +modifier_card_67 = __TS__Decorate( + modifier_card_67, + modifier_card_67, + {registerModifier(nil)}, + {kind = "class", name = "modifier_card_67"} +) +____exports.modifier_card_67 = modifier_card_67 +return ____exports diff --git a/scripts/vscripts/cards/examples/card_68.lua b/scripts/vscripts/cards/examples/card_68.lua new file mode 100644 index 0000000..3c1c5da --- /dev/null +++ b/scripts/vscripts/cards/examples/card_68.lua @@ -0,0 +1,179 @@ +local ____lualib = require("lualib_bundle") +local __TS__Number = ____lualib.__TS__Number +local __TS__NumberIsFinite = ____lualib.__TS__NumberIsFinite +local __TS__Class = ____lualib.__TS__Class +local __TS__ClassExtends = ____lualib.__TS__ClassExtends +local __TS__Decorate = ____lualib.__TS__Decorate +local ____exports = {} +local ____CardSystem = require("cards.CardSystem") +local AddCardToPlayerPool = ____CardSystem.AddCardToPlayerPool +local CardBase = ____CardSystem.CardBase +local RegisterCard = ____CardSystem.RegisterCard +local ____card_value_resolver = require("cards.card_value_resolver") +local getCardValueByLevel = ____card_value_resolver.getCardValueByLevel +local ____card_slag = require("cards.card_slag") +local isSlagCardId = ____card_slag.isSlagCardId +local ____modifier_card_cursed = require("cards.modifier_card_cursed") +local addCursedStack = ____modifier_card_cursed.addCursedStack +local isCursedPoolCardId = ____modifier_card_cursed.isCursedPoolCardId +____exports.CARD_68_ID = 68 +local CARD_ID = ____exports.CARD_68_ID +____exports.CARD_68_OPTION_SLOTS = 3 +local function shuffleInPlace(self, ids) + do + local i = #ids - 1 + while i > 0 do + local j = RandomInt(0, i) + local tmp = ids[i + 1] + ids[i + 1] = ids[j + 1] + ids[j + 1] = tmp + i = i - 1 + end + end +end +local function buildOfferIdsFromPool(self, poolCardIds, optionSlots, excludeIds, includeCardId) + local slots = math.max( + 1, + math.floor(optionSlots) + ) + --- Сама «Три долга» в свой бонусный выбор не попадает (иначе бесконечный цикл). + local alwaysExclude = {[____exports.CARD_68_ID] = true} + local previousScreenExclude = {} + for ____, raw in ipairs(excludeIds) do + local id = math.floor(__TS__Number(raw)) + if __TS__NumberIsFinite(id) and id > 0 and id ~= 404 and not isSlagCardId(nil, id) and id ~= ____exports.CARD_68_ID then + previousScreenExclude[id] = true + end + end + local function collectUnique(____, respectPreviousScreen) + local unique = {} + local seen = {} + for ____, raw in ipairs(poolCardIds) do + do + local id = math.floor(__TS__Number(raw)) + if not __TS__NumberIsFinite(id) or id <= 0 or id == 404 or isSlagCardId(nil, id) then + goto __continue9 + end + if not includeCardId(nil, id) then + goto __continue9 + end + if alwaysExclude[id] then + goto __continue9 + end + if respectPreviousScreen and previousScreenExclude[id] then + goto __continue9 + end + if seen[id] then + goto __continue9 + end + seen[id] = true + unique[#unique + 1] = id + end + ::__continue9:: + end + return unique + end + local unique = collectUnique(nil, true) + if #unique < slots then + unique = collectUnique(nil, false) + end + if #unique == 0 then + return {} + end + shuffleInPlace(nil, unique) + local out = {} + do + local i = 0 + while i < slots do + out[#out + 1] = i < #unique and unique[i + 1] or 404 + i = i + 1 + end + end + shuffleInPlace(nil, out) + return out +end +--- До optionSlots id для экрана выбора: проклятые карты из пула колоды. +-- excludeIds — варианты только что закрытого окна выбора (чтобы не повторять тот же экран). +function ____exports.buildCursedOfferIdsFromPool(self, poolCardIds, optionSlots, excludeIds) + if optionSlots == nil then + optionSlots = ____exports.CARD_68_OPTION_SLOTS + end + if excludeIds == nil then + excludeIds = {} + end + return buildOfferIdsFromPool( + nil, + poolCardIds, + optionSlots, + excludeIds, + isCursedPoolCardId + ) +end +--- С картой 69: любые карты из пула колоды (не только проклятые). +function ____exports.buildAnyOfferIdsFromPool(self, poolCardIds, optionSlots, excludeIds) + if optionSlots == nil then + optionSlots = ____exports.CARD_68_OPTION_SLOTS + end + if excludeIds == nil then + excludeIds = {} + end + return buildOfferIdsFromPool( + nil, + poolCardIds, + optionSlots, + excludeIds, + function() return true end + ) +end +____exports.card_68 = __TS__Class() +local card_68 = ____exports.card_68 +card_68.name = "card_68" +card_68.____file_path = "scripts/vscripts/cards/examples/card_68.lua" +__TS__ClassExtends(card_68, CardBase) +function card_68.prototype.OnCreated(self) + if not IsServer() then + return + end + local hero = self:GetHero() + if not hero or not IsValidEntity(hero) or not hero:IsRealHero() then + return + end + local playerId = hero:GetPlayerOwnerID() + if playerId == nil or playerId == nil or playerId < 0 then + return + end + local curseStacks = math.max( + 1, + math.floor(getCardValueByLevel( + nil, + CARD_ID, + hero, + "curse_stacks", + 1 + )) + ) + do + local i = 0 + while i < curseStacks do + addCursedStack(nil, hero) + i = i + 1 + end + end + AddCardToPlayerPool( + nil, + playerId, + CARD_ID, + 1, + 1, + "card_68_shuffle_back" + ) +end +function card_68.prototype.GetModifierName(self) + return nil +end +function card_68.prototype.IsHidden(self) + return true +end +card_68 = __TS__Decorate(card_68, card_68, {RegisterCard}, {kind = "class", name = "card_68"}) +____exports.card_68 = card_68 +return ____exports diff --git a/scripts/vscripts/cards/examples/card_69.lua b/scripts/vscripts/cards/examples/card_69.lua new file mode 100644 index 0000000..b7f75b2 --- /dev/null +++ b/scripts/vscripts/cards/examples/card_69.lua @@ -0,0 +1,48 @@ +local ____lualib = require("lualib_bundle") +local __TS__Class = ____lualib.__TS__Class +local __TS__ClassExtends = ____lualib.__TS__ClassExtends +local __TS__Decorate = ____lualib.__TS__Decorate +local ____exports = {} +local ____CardSystem = require("cards.CardSystem") +local CardBase = ____CardSystem.CardBase +local RegisterCard = ____CardSystem.RegisterCard +local ____CardBaseModifier = require("cards.CardBaseModifier") +local CardBaseModifier = ____CardBaseModifier.CardBaseModifier +local ____dota_ts_adapter = require("lib.dota_ts_adapter") +local registerModifier = ____dota_ts_adapter.registerModifier +local ____modifier_card_cursed = require("cards.modifier_card_cursed") +local getCursedStackCount = ____modifier_card_cursed.getCursedStackCount +local CARD_ID = 69 +____exports.card_69 = __TS__Class() +local card_69 = ____exports.card_69 +card_69.name = "card_69" +card_69.____file_path = "scripts/vscripts/cards/examples/card_69.lua" +__TS__ClassExtends(card_69, CardBase) +function card_69.prototype.GetModifierName(self) + return "modifier_card_69" +end +card_69 = __TS__Decorate(card_69, card_69, {RegisterCard}, {kind = "class", name = "card_69"}) +____exports.card_69 = card_69 +____exports.modifier_card_69 = __TS__Class() +local modifier_card_69 = ____exports.modifier_card_69 +modifier_card_69.name = "modifier_card_69" +modifier_card_69.____file_path = "scripts/vscripts/cards/examples/card_69.lua" +__TS__ClassExtends(modifier_card_69, CardBaseModifier) +function modifier_card_69.prototype.DeclareFunctions(self) + return {MODIFIER_PROPERTY_DAMAGEOUTGOING_PERCENTAGE, MODIFIER_PROPERTY_TOOLTIP} +end +function modifier_card_69.prototype.GetModifierDamageOutgoing_Percentage(self) + local hero = self:GetParent() + if not hero or not IsValidEntity(hero) or not hero:IsRealHero() then + return 0 + end + return getCursedStackCount(nil, hero) * self:getScaledCardValue("outgoing_damage_per_curse_stack_pct", 5, CARD_ID) +end +modifier_card_69 = __TS__Decorate( + modifier_card_69, + modifier_card_69, + {registerModifier(nil)}, + {kind = "class", name = "modifier_card_69"} +) +____exports.modifier_card_69 = modifier_card_69 +return ____exports diff --git a/scripts/vscripts/cards/examples/card_7.lua b/scripts/vscripts/cards/examples/card_7.lua new file mode 100644 index 0000000..f2f5c05 --- /dev/null +++ b/scripts/vscripts/cards/examples/card_7.lua @@ -0,0 +1,82 @@ +local ____lualib = require("lualib_bundle") +local __TS__Class = ____lualib.__TS__Class +local __TS__ClassExtends = ____lualib.__TS__ClassExtends +local __TS__Decorate = ____lualib.__TS__Decorate +local ____exports = {} +local ____CardSystem = require("cards.CardSystem") +local CardBase = ____CardSystem.CardBase +local RegisterCard = ____CardSystem.RegisterCard +local ____CardBaseModifier = require("cards.CardBaseModifier") +local CardBaseModifier = ____CardBaseModifier.CardBaseModifier +local ____dota_ts_adapter = require("lib.dota_ts_adapter") +local registerModifier = ____dota_ts_adapter.registerModifier +local ____modifier_card_cursed = require("cards.modifier_card_cursed") +local addCursedStack = ____modifier_card_cursed.addCursedStack +local ____DayNightCycleManager = require("DayNightCycleManager") +local DayNightCycleManager = ____DayNightCycleManager.DayNightCycleManager +local ____modifier_stats_multiplier = require("modifiers.modifier_stats_multiplier") +local setStatsMultiplierSource = ____modifier_stats_multiplier.setStatsMultiplierSource +local CARD_7_SOURCE_SEQ = 1 +____exports.card_7 = __TS__Class() +local card_7 = ____exports.card_7 +card_7.name = "card_7" +card_7.____file_path = "scripts/vscripts/cards/examples/card_7.lua" +__TS__ClassExtends(card_7, CardBase) +function card_7.prototype.GetModifierName(self) + return "modifier_card_7" +end +card_7 = __TS__Decorate(card_7, card_7, {RegisterCard}, {kind = "class", name = "card_7"}) +____exports.card_7 = card_7 +____exports.modifier_card_7 = __TS__Class() +local modifier_card_7 = ____exports.modifier_card_7 +modifier_card_7.name = "modifier_card_7" +modifier_card_7.____file_path = "scripts/vscripts/cards/examples/card_7.lua" +__TS__ClassExtends(modifier_card_7, CardBaseModifier) +function modifier_card_7.prototype.____constructor(self, ...) + CardBaseModifier.prototype.____constructor(self, ...) + self.cursedApplied = false + self.sourceId = "" +end +function modifier_card_7.prototype.OnCustomCreated(self, params) + if not IsServer() then + return + end + local hero = self:GetParent() + if not hero or not IsValidEntity(hero) or not hero:IsRealHero() then + return + end + addCursedStack(nil, hero) + self.cursedApplied = true + local ____CARD_7_SOURCE_SEQ_0 = CARD_7_SOURCE_SEQ + CARD_7_SOURCE_SEQ = ____CARD_7_SOURCE_SEQ_0 + 1 + self.sourceId = "card_7_" .. tostring(____CARD_7_SOURCE_SEQ_0) + local dn = DayNightCycleManager:getInstance() + local isDay = dn:IsDaytime() + local pct7 = self:getCardValue("daynight_bonus_pct", 0, 7) + local signDbg = isDay and -1 or 1 + print((((((((((("[card_7] OnCustomCreated player=" .. tostring(hero:GetPlayerID())) .. " daynight_bonus_pct=") .. tostring(pct7)) .. " IsDaytime=") .. tostring(isDay)) .. " sign=") .. tostring(signDbg)) .. " вклад_%=") .. tostring(pct7 * signDbg)) .. " source=") .. self.sourceId) + setStatsMultiplierSource( + nil, + hero, + self.sourceId, + function() + if not hero or not IsValidEntity(hero) or not hero:IsRealHero() then + return 0 + end + local card7Pct = self:getCardValue("daynight_bonus_pct", 0, 7) + local sign = DayNightCycleManager:getInstance():IsDaytime() and -1 or 1 + return card7Pct * sign * self:getCardCopies() + end + ) +end +function modifier_card_7.prototype.GetAttributes(self) + return MODIFIER_ATTRIBUTE_MULTIPLE +end +modifier_card_7 = __TS__Decorate( + modifier_card_7, + modifier_card_7, + {registerModifier(nil)}, + {kind = "class", name = "modifier_card_7"} +) +____exports.modifier_card_7 = modifier_card_7 +return ____exports diff --git a/scripts/vscripts/cards/examples/card_70.lua b/scripts/vscripts/cards/examples/card_70.lua new file mode 100644 index 0000000..3c75561 --- /dev/null +++ b/scripts/vscripts/cards/examples/card_70.lua @@ -0,0 +1,89 @@ +local ____lualib = require("lualib_bundle") +local __TS__Class = ____lualib.__TS__Class +local __TS__ClassExtends = ____lualib.__TS__ClassExtends +local __TS__Decorate = ____lualib.__TS__Decorate +local ____exports = {} +local ____CardSystem = require("cards.CardSystem") +local CardBase = ____CardSystem.CardBase +local RegisterCard = ____CardSystem.RegisterCard +local ____CardBaseModifier = require("cards.CardBaseModifier") +local CardBaseModifier = ____CardBaseModifier.CardBaseModifier +local ____dota_ts_adapter = require("lib.dota_ts_adapter") +local registerModifier = ____dota_ts_adapter.registerModifier +local ____hero_rage = require("abilities.system.hero_rage") +local heroRageGetCurrent = ____hero_rage.heroRageGetCurrent +local heroRageGetModifier = ____hero_rage.heroRageGetModifier +local ____incoming_damage_reduction_combine = require("utils.incoming_damage_reduction_combine") +local removeIncomingDamageReductionSource = ____incoming_damage_reduction_combine.removeIncomingDamageReductionSource +local setIncomingDamageReductionSource = ____incoming_damage_reduction_combine.setIncomingDamageReductionSource +local CARD_ID = 70 +local CARD_70_INCOMING_SOURCE = "modifier_card_70" +____exports.card_70 = __TS__Class() +local card_70 = ____exports.card_70 +card_70.name = "card_70" +card_70.____file_path = "scripts/vscripts/cards/examples/card_70.lua" +__TS__ClassExtends(card_70, CardBase) +function card_70.prototype.GetModifierName(self) + return "modifier_card_70" +end +card_70 = __TS__Decorate(card_70, card_70, {RegisterCard}, {kind = "class", name = "card_70"}) +____exports.card_70 = card_70 +____exports.modifier_card_70 = __TS__Class() +local modifier_card_70 = ____exports.modifier_card_70 +modifier_card_70.name = "modifier_card_70" +modifier_card_70.____file_path = "scripts/vscripts/cards/examples/card_70.lua" +__TS__ClassExtends(modifier_card_70, CardBaseModifier) +function modifier_card_70.prototype.OnCustomRefresh(self, _params) + if not IsServer() then + return + end + removeIncomingDamageReductionSource( + nil, + self:GetParent(), + CARD_70_INCOMING_SOURCE + ) + self:OnCustomCreated(_params) +end +function modifier_card_70.prototype.OnCustomCreated(self, _params) + if not IsServer() then + return + end + setIncomingDamageReductionSource( + nil, + self:GetParent(), + CARD_70_INCOMING_SOURCE, + function() + local hero = self:GetParent() + if not hero or not IsValidEntity(hero) or not hero:IsRealHero() then + return 0 + end + if not heroRageGetModifier(nil, hero) then + return 0 + end + local rage = heroRageGetCurrent(nil, hero) + if rage <= 0 then + return 0 + end + local perUnit = self:getScaledCardValue("incoming_reduction_per_rage_pct", 0.1, CARD_ID) + return math.max(0, rage * perUnit) + end + ) +end +function modifier_card_70.prototype.OnDestroy(self) + if not IsServer() then + return + end + removeIncomingDamageReductionSource( + nil, + self:GetParent(), + CARD_70_INCOMING_SOURCE + ) +end +modifier_card_70 = __TS__Decorate( + modifier_card_70, + modifier_card_70, + {registerModifier(nil)}, + {kind = "class", name = "modifier_card_70"} +) +____exports.modifier_card_70 = modifier_card_70 +return ____exports diff --git a/scripts/vscripts/cards/examples/card_71.lua b/scripts/vscripts/cards/examples/card_71.lua new file mode 100644 index 0000000..8449e6f --- /dev/null +++ b/scripts/vscripts/cards/examples/card_71.lua @@ -0,0 +1,74 @@ +local ____lualib = require("lualib_bundle") +local __TS__Class = ____lualib.__TS__Class +local __TS__ClassExtends = ____lualib.__TS__ClassExtends +local __TS__Decorate = ____lualib.__TS__Decorate +local ____exports = {} +local ____CardSystem = require("cards.CardSystem") +local CardBase = ____CardSystem.CardBase +local RegisterCard = ____CardSystem.RegisterCard +local ____CardBaseModifier = require("cards.CardBaseModifier") +local CardBaseModifier = ____CardBaseModifier.CardBaseModifier +local ____dota_ts_adapter = require("lib.dota_ts_adapter") +local registerModifier = ____dota_ts_adapter.registerModifier +local ____modifier_card_cursed = require("cards.modifier_card_cursed") +local getCursedStackCount = ____modifier_card_cursed.getCursedStackCount +local CARD_ID = 71 +____exports.card_71 = __TS__Class() +local card_71 = ____exports.card_71 +card_71.name = "card_71" +card_71.____file_path = "scripts/vscripts/cards/examples/card_71.lua" +__TS__ClassExtends(card_71, CardBase) +function card_71.prototype.GetModifierName(self) + return "modifier_card_71" +end +card_71 = __TS__Decorate(card_71, card_71, {RegisterCard}, {kind = "class", name = "card_71"}) +____exports.card_71 = card_71 +____exports.modifier_card_71 = __TS__Class() +local modifier_card_71 = ____exports.modifier_card_71 +modifier_card_71.name = "modifier_card_71" +modifier_card_71.____file_path = "scripts/vscripts/cards/examples/card_71.lua" +__TS__ClassExtends(modifier_card_71, CardBaseModifier) +function modifier_card_71.prototype.OnCustomCreated(self, params) + if not IsServer() then + return + end + self:StartIntervalThink(1) +end +function modifier_card_71.prototype.OnIntervalThink(self) + if not IsServer() then + return + end + local hero = self:GetParent() + if not hero or not IsValidEntity(hero) or not hero:IsAlive() or not hero:IsRealHero() then + return + end + local dotPct = self:getScaledCardValue("self_damage_max_hp_pct_per_sec", 2.5, CARD_ID) + local hpLoss = hero:GetMaxHealth() * (math.max(0, dotPct) / 100) + if hpLoss <= 0 then + return + end + local newHealth = math.max( + 1, + hero:GetHealth() - hpLoss + ) + hero:ModifyHealth(newHealth, nil, false, 0) +end +function modifier_card_71.prototype.DeclareFunctions(self) + return {MODIFIER_PROPERTY_EXTRA_HEALTH_PERCENTAGE, MODIFIER_PROPERTY_TOOLTIP} +end +function modifier_card_71.prototype.GetModifierExtraHealthPercentage(self) + local hero = self:GetParent() + if not hero or not IsValidEntity(hero) or not hero:IsRealHero() then + return 0 + end + local perCurse = self:getScaledCardValue("max_health_pct_per_curse_stack", 1, CARD_ID) + return getCursedStackCount(nil, hero) * perCurse +end +modifier_card_71 = __TS__Decorate( + modifier_card_71, + modifier_card_71, + {registerModifier(nil)}, + {kind = "class", name = "modifier_card_71"} +) +____exports.modifier_card_71 = modifier_card_71 +return ____exports diff --git a/scripts/vscripts/cards/examples/card_72.lua b/scripts/vscripts/cards/examples/card_72.lua new file mode 100644 index 0000000..f00f276 --- /dev/null +++ b/scripts/vscripts/cards/examples/card_72.lua @@ -0,0 +1,54 @@ +local ____lualib = require("lualib_bundle") +local __TS__Class = ____lualib.__TS__Class +local __TS__ClassExtends = ____lualib.__TS__ClassExtends +local __TS__Decorate = ____lualib.__TS__Decorate +local ____exports = {} +local ____CardSystem = require("cards.CardSystem") +local CardBase = ____CardSystem.CardBase +local RegisterCard = ____CardSystem.RegisterCard +local ____CardBaseModifier = require("cards.CardBaseModifier") +local CardBaseModifier = ____CardBaseModifier.CardBaseModifier +local ____dota_ts_adapter = require("lib.dota_ts_adapter") +local registerModifier = ____dota_ts_adapter.registerModifier +local ____hero_rage = require("abilities.system.hero_rage") +local heroRageGetModifier = ____hero_rage.heroRageGetModifier +local CARD_ID = 72 +____exports.card_72 = __TS__Class() +local card_72 = ____exports.card_72 +card_72.name = "card_72" +card_72.____file_path = "scripts/vscripts/cards/examples/card_72.lua" +__TS__ClassExtends(card_72, CardBase) +function card_72.prototype.GetModifierName(self) + return "modifier_card_72" +end +card_72 = __TS__Decorate(card_72, card_72, {RegisterCard}, {kind = "class", name = "card_72"}) +____exports.card_72 = card_72 +____exports.modifier_card_72 = __TS__Class() +local modifier_card_72 = ____exports.modifier_card_72 +modifier_card_72.name = "modifier_card_72" +modifier_card_72.____file_path = "scripts/vscripts/cards/examples/card_72.lua" +__TS__ClassExtends(modifier_card_72, CardBaseModifier) +function modifier_card_72.prototype.DeclareFunctions(self) + return {MODIFIER_PROPERTY_COOLDOWN_PERCENTAGE, MODIFIER_PROPERTY_TOOLTIP} +end +function modifier_card_72.prototype.GetModifierPercentageCooldown(self) + if not IsServer() then + return 0 + end + local hero = self:GetParent() + if not hero or not IsValidEntity(hero) or not hero:IsRealHero() then + return 0 + end + if not heroRageGetModifier(nil, hero) then + return 0 + end + return self:getScaledCardValue("cooldown_reduction_pct", 15, CARD_ID) +end +modifier_card_72 = __TS__Decorate( + modifier_card_72, + modifier_card_72, + {registerModifier(nil)}, + {kind = "class", name = "modifier_card_72"} +) +____exports.modifier_card_72 = modifier_card_72 +return ____exports diff --git a/scripts/vscripts/cards/examples/card_73.lua b/scripts/vscripts/cards/examples/card_73.lua new file mode 100644 index 0000000..d126d03 --- /dev/null +++ b/scripts/vscripts/cards/examples/card_73.lua @@ -0,0 +1,179 @@ +local ____lualib = require("lualib_bundle") +local __TS__Class = ____lualib.__TS__Class +local __TS__ClassExtends = ____lualib.__TS__ClassExtends +local __TS__Decorate = ____lualib.__TS__Decorate +local ____exports = {} +local ____CardSystem = require("cards.CardSystem") +local CardBase = ____CardSystem.CardBase +local RegisterCard = ____CardSystem.RegisterCard +local ____CardBaseModifier = require("cards.CardBaseModifier") +local CardBaseModifier = ____CardBaseModifier.CardBaseModifier +local ____dota_ts_adapter = require("lib.dota_ts_adapter") +local registerModifier = ____dota_ts_adapter.registerModifier +local CARD_ID = 73 +local THINK_INTERVAL = 0.3 +____exports.card_73 = __TS__Class() +local card_73 = ____exports.card_73 +card_73.name = "card_73" +card_73.____file_path = "scripts/vscripts/cards/examples/card_73.lua" +__TS__ClassExtends(card_73, CardBase) +function card_73.prototype.GetModifierName(self) + return "modifier_card_73" +end +card_73 = __TS__Decorate(card_73, card_73, {RegisterCard}, {kind = "class", name = "card_73"}) +____exports.card_73 = card_73 +____exports.modifier_card_73 = __TS__Class() +local modifier_card_73 = ____exports.modifier_card_73 +modifier_card_73.name = "modifier_card_73" +modifier_card_73.____file_path = "scripts/vscripts/cards/examples/card_73.lua" +__TS__ClassExtends(modifier_card_73, CardBaseModifier) +function modifier_card_73.prototype.____constructor(self, ...) + CardBaseModifier.prototype.____constructor(self, ...) + self.skipRumHooks = false + self.pendingDeferredDamage = 0 + self.drainEndTime = 0 + self.pendingTooltipStacks = 0 +end +function modifier_card_73.prototype.getValue(self, key, fallback) + return self:getCardValue(key, fallback, CARD_ID) +end +function modifier_card_73.prototype.DeclareFunctions(self) + return {MODIFIER_PROPERTY_INCOMING_DAMAGE_PERCENTAGE, MODIFIER_EVENT_ON_TAKEDAMAGE, MODIFIER_PROPERTY_TOOLTIP} +end +function modifier_card_73.prototype.syncPendingTooltipStacks(self) + self.pendingTooltipStacks = math.min( + 2147483647, + math.max( + 0, + math.floor(self.pendingDeferredDamage + 0.0001) + ) + ) +end +function modifier_card_73.prototype.ensureDrainThinkRunning(self, duration) + if duration <= 0 then + return + end + self:StartIntervalThink(THINK_INTERVAL) +end +function modifier_card_73.prototype.GetModifierIncomingDamage_Percentage(self, _event) + if self.skipRumHooks then + return 0 + end + local deferPct = math.min( + 95, + math.max( + 1, + math.floor(self:getScaledCardValue("deferred_damage_pct", 30)) + ) + ) + return -deferPct +end +function modifier_card_73.prototype.OnTakeDamage(self, event) + if not IsServer() or self.skipRumHooks then + return + end + local parent = self:GetParent() + if not parent or not IsValidEntity(parent) or not parent:IsAlive() or not parent:IsRealHero() then + return + end + if event.unit ~= parent then + return + end + local deferPct = math.min( + 95, + math.max( + 1, + math.floor(self:getScaledCardValue("deferred_damage_pct", 30)) + ) + ) + local duration = math.max( + 0.1, + self:getValue("deferred_dot_duration_sec", 3) + ) + local immediateDamage = event.damage + if immediateDamage <= 0 then + return + end + local deferred = math.floor(immediateDamage * deferPct / (100 - deferPct) * 100 + 0.5) / 100 + if deferred <= 0 then + return + end + local attacker = event.attacker + if attacker and IsValidEntity(attacker) and attacker:IsAlive() and attacker:GetTeamNumber() ~= parent:GetTeamNumber() then + self.rumDamageAttacker = attacker + elseif not self.rumDamageAttacker or not IsValidEntity(self.rumDamageAttacker) then + self.rumDamageAttacker = parent + end + self.pendingDeferredDamage = self.pendingDeferredDamage + deferred + self.drainEndTime = GameRules:GetGameTime() + duration + self:syncPendingTooltipStacks() + self:ensureDrainThinkRunning(duration) +end +function modifier_card_73.prototype.OnIntervalThink(self) + if not IsServer() then + return + end + local hero = self:GetParent() + if not hero or not IsValidEntity(hero) or not hero:IsAlive() or not hero:IsRealHero() then + self.pendingDeferredDamage = 0 + self:syncPendingTooltipStacks() + self:StartIntervalThink(-1) + return + end + if self.pendingDeferredDamage <= 0.001 then + self.pendingDeferredDamage = 0 + self:syncPendingTooltipStacks() + self:StartIntervalThink(-1) + return + end + local now = GameRules:GetGameTime() + local tLeft = self.drainEndTime - now + local damageAttacker = self.rumDamageAttacker and IsValidEntity(self.rumDamageAttacker) and self.rumDamageAttacker or hero + local slice = 0 + if tLeft <= 0 then + slice = self.pendingDeferredDamage + elseif tLeft <= THINK_INTERVAL * 0.5 then + slice = self.pendingDeferredDamage + else + slice = self.pendingDeferredDamage * (THINK_INTERVAL / tLeft) + end + slice = math.max( + 0, + math.floor(slice * 100 + 0.5) / 100 + ) + if slice <= 0 then + slice = math.min(self.pendingDeferredDamage, 0.01) + end + slice = math.min(slice, self.pendingDeferredDamage) + self.pendingDeferredDamage = self.pendingDeferredDamage - slice + if self.pendingDeferredDamage < 0.001 then + self.pendingDeferredDamage = 0 + end + self:syncPendingTooltipStacks() + self.skipRumHooks = true + ApplyDamage({ + victim = hero, + attacker = damageAttacker, + damage = slice, + damage_type = DAMAGE_TYPE_PURE, + damage_flags = DOTA_DAMAGE_FLAG_NO_SPELL_AMPLIFICATION + }) + self.skipRumHooks = false + if self.pendingDeferredDamage <= 0.001 then + self:StartIntervalThink(-1) + end +end +function modifier_card_73.prototype.OnTooltip(self) + return self.pendingTooltipStacks +end +function modifier_card_73.prototype.GetTexture(self) + return "item_bottle" +end +modifier_card_73 = __TS__Decorate( + modifier_card_73, + modifier_card_73, + {registerModifier(nil)}, + {kind = "class", name = "modifier_card_73"} +) +____exports.modifier_card_73 = modifier_card_73 +return ____exports diff --git a/scripts/vscripts/cards/examples/card_74.lua b/scripts/vscripts/cards/examples/card_74.lua new file mode 100644 index 0000000..e14024c --- /dev/null +++ b/scripts/vscripts/cards/examples/card_74.lua @@ -0,0 +1,122 @@ +local ____lualib = require("lualib_bundle") +local __TS__Class = ____lualib.__TS__Class +local __TS__ClassExtends = ____lualib.__TS__ClassExtends +local __TS__Decorate = ____lualib.__TS__Decorate +local ____exports = {} +local ____CardSystem = require("cards.CardSystem") +local CardBase = ____CardSystem.CardBase +local RegisterCard = ____CardSystem.RegisterCard +local ____CardBaseModifier = require("cards.CardBaseModifier") +local CardBaseModifier = ____CardBaseModifier.CardBaseModifier +local ____dota_ts_adapter = require("lib.dota_ts_adapter") +local registerModifier = ____dota_ts_adapter.registerModifier +local ____crit_mult = require("utils.crit_mult") +local addCritMult = ____crit_mult.addCritMult +local CARD_ID = 74 +local ARMOR_RECALC_INTERVAL = 0.25 +--- Раз в секунду подтягиваем крит-мульт к актуальному уровню карты (без полного пересоздания модификатора). +local CRIT_MULT_RESYNC_INTERVAL = 1 +local LOG = "[card_74]" +____exports.card_74 = __TS__Class() +local card_74 = ____exports.card_74 +card_74.name = "card_74" +card_74.____file_path = "scripts/vscripts/cards/examples/card_74.lua" +__TS__ClassExtends(card_74, CardBase) +function card_74.prototype.GetModifierName(self) + return "modifier_card_74" +end +card_74 = __TS__Decorate(card_74, card_74, {RegisterCard}, {kind = "class", name = "card_74"}) +____exports.card_74 = card_74 +____exports.modifier_card_74 = __TS__Class() +local modifier_card_74 = ____exports.modifier_card_74 +modifier_card_74.name = "modifier_card_74" +modifier_card_74.____file_path = "scripts/vscripts/cards/examples/card_74.lua" +__TS__ClassExtends(modifier_card_74, CardBaseModifier) +function modifier_card_74.prototype.getValue(self, key, fallback) + return self:getCardValue(key, fallback, CARD_ID) +end +function modifier_card_74.prototype.OnCustomCreated(self, _params) + local hero = self:GetParent() + addCritMult( + nil, + hero, + self:getScaledCardValue("crit_mult_bonus_pct", 30) + ) +end +function modifier_card_74.prototype.OnCustomRefresh(self, _params) + local hero = self:GetParent() + if not hero or not IsValidEntity(hero) then + return + end + addCritMult( + nil, + hero, + self:getValue("crit_mult_bonus_pct", 30) + ) +end +function modifier_card_74.prototype.IsAura(self) + return not self:GetParent():PassivesDisabled() +end +function modifier_card_74.prototype.GetAuraRadius(self) + return self:getValue("aura_radius", 1000) +end +function modifier_card_74.prototype.GetModifierAura(self) + return ____exports.modifier_card_74_armor_reduce.name +end +function modifier_card_74.prototype.GetAuraSearchTeam(self) + return DOTA_UNIT_TARGET_TEAM_ENEMY +end +function modifier_card_74.prototype.GetAuraDuration(self) + return 0 +end +function modifier_card_74.prototype.GetAuraSearchFlags(self) + return DOTA_UNIT_TARGET_FLAG_NONE +end +function modifier_card_74.prototype.GetAuraSearchType(self) + return DOTA_UNIT_TARGET_HERO + DOTA_UNIT_TARGET_BASIC +end +modifier_card_74 = __TS__Decorate( + modifier_card_74, + modifier_card_74, + {registerModifier(nil)}, + {kind = "class", name = "modifier_card_74"} +) +____exports.modifier_card_74 = modifier_card_74 +____exports.modifier_card_74_armor_reduce = __TS__Class() +local modifier_card_74_armor_reduce = ____exports.modifier_card_74_armor_reduce +modifier_card_74_armor_reduce.name = "modifier_card_74_armor_reduce" +modifier_card_74_armor_reduce.____file_path = "scripts/vscripts/cards/examples/card_74.lua" +__TS__ClassExtends(modifier_card_74_armor_reduce, CardBaseModifier) +function modifier_card_74_armor_reduce.prototype.getCardCopiesFromOwner(self) + local owner = self:GetCaster() + if not owner or not IsValidEntity(owner) then + return 1 + end + local cardMod = owner:FindModifierByName("modifier_card_74") + return math.max( + 1, + math.floor(cardMod and cardMod:GetStackCount() or 0) + ) +end +function modifier_card_74_armor_reduce.prototype.getValue(self, key, fallback) + return self:getCardValue(key, fallback, CARD_ID) * self:getCardCopiesFromOwner() +end +function modifier_card_74_armor_reduce.prototype.IsHidden(self) + return false +end +function modifier_card_74_armor_reduce.prototype.DeclareFunctions(self) + return {MODIFIER_PROPERTY_PHYSICAL_ARMOR_BONUS} +end +function modifier_card_74_armor_reduce.prototype.GetModifierPhysicalArmorBonus(self) + local parent = self:GetParent() + local baseArmor = parent:GetPhysicalArmorBaseValue() + return -baseArmor * (self:getValue("enemy_armor_reduction_pct", 20) / 100) +end +modifier_card_74_armor_reduce = __TS__Decorate( + modifier_card_74_armor_reduce, + modifier_card_74_armor_reduce, + {registerModifier(nil)}, + {kind = "class", name = "modifier_card_74_armor_reduce"} +) +____exports.modifier_card_74_armor_reduce = modifier_card_74_armor_reduce +return ____exports diff --git a/scripts/vscripts/cards/examples/card_75.lua b/scripts/vscripts/cards/examples/card_75.lua new file mode 100644 index 0000000..8982c94 --- /dev/null +++ b/scripts/vscripts/cards/examples/card_75.lua @@ -0,0 +1,193 @@ +local ____lualib = require("lualib_bundle") +local __TS__Class = ____lualib.__TS__Class +local __TS__ClassExtends = ____lualib.__TS__ClassExtends +local __TS__Decorate = ____lualib.__TS__Decorate +local ____exports = {} +local ____CardSystem = require("cards.CardSystem") +local CardBase = ____CardSystem.CardBase +local RegisterCard = ____CardSystem.RegisterCard +local ____CardBaseModifier = require("cards.CardBaseModifier") +local CardBaseModifier = ____CardBaseModifier.CardBaseModifier +local ____dota_ts_adapter = require("lib.dota_ts_adapter") +local registerModifier = ____dota_ts_adapter.registerModifier +local CARD_ID = 75 +local RING_PARTICLE = "particles/units/heroes/hero_arc_warden/arc_warden_magnetic_tempest_ring.vpcf" +local BLAST_PARTICLE = "particles/units/heroes/hero_invoker/invoker_emp_explode.vpcf" +local function countEnemiesInBlast(self, team, center, radius) + local units = FindUnitsInRadius( + team, + center, + nil, + radius, + DOTA_UNIT_TARGET_TEAM_ENEMY, + bit.bor(DOTA_UNIT_TARGET_HERO, DOTA_UNIT_TARGET_BASIC), + DOTA_UNIT_TARGET_FLAG_NONE, + FIND_ANY_ORDER, + false + ) + return #units +end +--- Центр взрыва — позиция врага, где в радиусе blast попадает больше всего целей. +local function findBestBlastPosition(self, hero, searchRadius, blastRadius) + local enemies = FindUnitsInRadius( + hero:GetTeamNumber(), + hero:GetAbsOrigin(), + nil, + searchRadius, + DOTA_UNIT_TARGET_TEAM_ENEMY, + bit.bor(DOTA_UNIT_TARGET_HERO, DOTA_UNIT_TARGET_BASIC), + DOTA_UNIT_TARGET_FLAG_NONE, + FIND_ANY_ORDER, + false + ) + if #enemies == 0 then + return nil + end + local bestPosition = enemies[1]:GetAbsOrigin() + local bestHits = -1 + local team = hero:GetTeamNumber() + for ____, enemy in ipairs(enemies) do + local candidate = enemy:GetAbsOrigin() + local hits = countEnemiesInBlast(nil, team, candidate, blastRadius) + if hits > bestHits then + bestHits = hits + bestPosition = candidate + end + end + return bestPosition +end +____exports.card_75 = __TS__Class() +local card_75 = ____exports.card_75 +card_75.name = "card_75" +card_75.____file_path = "scripts/vscripts/cards/examples/card_75.lua" +__TS__ClassExtends(card_75, CardBase) +function card_75.prototype.GetModifierName(self) + return "modifier_card_75" +end +card_75 = __TS__Decorate(card_75, card_75, {RegisterCard}, {kind = "class", name = "card_75"}) +____exports.card_75 = card_75 +____exports.modifier_card_75 = __TS__Class() +local modifier_card_75 = ____exports.modifier_card_75 +modifier_card_75.name = "modifier_card_75" +modifier_card_75.____file_path = "scripts/vscripts/cards/examples/card_75.lua" +__TS__ClassExtends(modifier_card_75, CardBaseModifier) +function modifier_card_75.prototype.getValue(self, key, fallback) + return self:getCardValue(key, fallback, CARD_ID) +end +function modifier_card_75.prototype.OnCustomCreated(self, _params) + if not IsServer() then + return + end + local interval = math.max( + 0.5, + self:getValue("blast_interval_sec", 4) + ) + self:StartIntervalThink(interval) +end +function modifier_card_75.prototype.OnCustomRefresh(self, _params) + if not IsServer() then + return + end + local interval = math.max( + 0.5, + self:getValue("blast_interval_sec", 4) + ) + self:StartIntervalThink(interval) +end +function modifier_card_75.prototype.OnIntervalThink(self) + if not IsServer() then + return + end + local hero = self:GetParent() + if not hero or not IsValidEntity(hero) or not hero:IsAlive() or not hero:IsRealHero() then + return + end + local manaCostPct = self:getScaledCardValue("mana_cost_pct", 15) + local maxMana = hero:GetMaxMana() + if maxMana <= 0 then + return + end + local manaCost = maxMana * (manaCostPct / 100) + if hero:GetMana() < manaCost then + return + end + local searchRadius = self:getValue("search_radius", 1000) + local blastRadius = self:getValue("blast_radius", 175) + local blastCenter = findBestBlastPosition(nil, hero, searchRadius, blastRadius) + if not blastCenter then + return + end + hero:SetMana(math.max( + 0, + hero:GetMana() - manaCost + )) + local baseDamage = self:getScaledCardValue("base_damage", 90) + local damagePct = self:getScaledCardValue("damage_from_max_mana_pct", 5) + local damage = baseDamage + maxMana * (damagePct / 100) + if damage <= 0 then + return + end + local blastDelay = math.max( + 0.1, + self:getValue("blast_delay_sec", 1) + ) + local heroEntIndex = hero:entindex() + local ringParticle = ParticleManager:CreateParticle(RING_PARTICLE, PATTACH_WORLDORIGIN, nil) + ParticleManager:SetParticleControl(ringParticle, 0, blastCenter) + ParticleManager:SetParticleControl( + ringParticle, + 1, + Vector(blastRadius, blastRadius, blastRadius) + ) + EmitSoundOnLocationWithCaster(blastCenter, "Hero_Invoker.EMP.Charge", hero) + Timers:CreateTimer( + blastDelay, + function() + local caster = EntIndexToHScript(heroEntIndex) + if not caster or not IsValidEntity(caster) or not caster:IsAlive() then + ParticleManager:DestroyParticle(ringParticle, false) + ParticleManager:ReleaseParticleIndex(ringParticle) + return + end + ParticleManager:DestroyParticle(ringParticle, false) + ParticleManager:ReleaseParticleIndex(ringParticle) + local explosion = ParticleManager:CreateParticle(BLAST_PARTICLE, PATTACH_WORLDORIGIN, nil) + ParticleManager:SetParticleControl(explosion, 0, blastCenter) + ParticleManager:SetParticleControl( + explosion, + 1, + Vector(blastRadius, blastRadius, blastRadius) + ) + ParticleManager:ReleaseParticleIndex(explosion) + EmitSoundOnLocationWithCaster(blastCenter, "Hero_Invoker.EMP.Discharge", caster) + local victims = FindUnitsInRadius( + caster:GetTeamNumber(), + blastCenter, + nil, + blastRadius, + DOTA_UNIT_TARGET_TEAM_ENEMY, + bit.bor(DOTA_UNIT_TARGET_HERO, DOTA_UNIT_TARGET_BASIC), + DOTA_UNIT_TARGET_FLAG_NONE, + FIND_ANY_ORDER, + false + ) + for ____, victim in ipairs(victims) do + do + if not victim or not IsValidEntity(victim) or not victim:IsAlive() then + goto __continue23 + end + ApplyDamage({victim = victim, attacker = caster, damage = damage, damage_type = DAMAGE_TYPE_PURE}) + end + ::__continue23:: + end + end + ) +end +modifier_card_75 = __TS__Decorate( + modifier_card_75, + modifier_card_75, + {registerModifier(nil)}, + {kind = "class", name = "modifier_card_75"} +) +____exports.modifier_card_75 = modifier_card_75 +return ____exports diff --git a/scripts/vscripts/cards/examples/card_76.lua b/scripts/vscripts/cards/examples/card_76.lua new file mode 100644 index 0000000..ea3b28c --- /dev/null +++ b/scripts/vscripts/cards/examples/card_76.lua @@ -0,0 +1,166 @@ +local ____lualib = require("lualib_bundle") +local __TS__Class = ____lualib.__TS__Class +local __TS__ClassExtends = ____lualib.__TS__ClassExtends +local __TS__Decorate = ____lualib.__TS__Decorate +local ____exports = {} +local ____CardSystem = require("cards.CardSystem") +local CardBase = ____CardSystem.CardBase +local RegisterCard = ____CardSystem.RegisterCard +local ____CardBaseModifier = require("cards.CardBaseModifier") +local CardBaseModifier = ____CardBaseModifier.CardBaseModifier +local ____dota_ts_adapter = require("lib.dota_ts_adapter") +local registerModifier = ____dota_ts_adapter.registerModifier +local ____heal_tracker = require("utils.heal_tracker") +local HealWithBattlePass = ____heal_tracker.HealWithBattlePass +local CARD_ID = 76 +local STRIKE_PARTICLE = "particles/econ/items/pugna/pugna_ward_ti5/pugna_ward_attack_heavy_ti_5.vpcf" +____exports.card_76 = __TS__Class() +local card_76 = ____exports.card_76 +card_76.name = "card_76" +card_76.____file_path = "scripts/vscripts/cards/examples/card_76.lua" +__TS__ClassExtends(card_76, CardBase) +function card_76.prototype.GetModifierName(self) + return "modifier_card_76" +end +card_76 = __TS__Decorate(card_76, card_76, {RegisterCard}, {kind = "class", name = "card_76"}) +____exports.card_76 = card_76 +____exports.modifier_card_76 = __TS__Class() +local modifier_card_76 = ____exports.modifier_card_76 +modifier_card_76.name = "modifier_card_76" +modifier_card_76.____file_path = "scripts/vscripts/cards/examples/card_76.lua" +__TS__ClassExtends(modifier_card_76, CardBaseModifier) +function modifier_card_76.prototype.getValue(self, key, fallback) + return self:getCardValue(key, fallback, CARD_ID) +end +function modifier_card_76.prototype.Precache(self, context) + PrecacheResource("particle", STRIKE_PARTICLE, context) +end +function modifier_card_76.prototype.DeclareFunctions(self) + return {MODIFIER_EVENT_ON_ABILITY_FULLY_CAST, MODIFIER_PROPERTY_TOOLTIP} +end +function modifier_card_76.prototype.OnAbilityFullyCast(self, event) + if not IsServer() then + return + end + local hero = self:GetParent() + if not hero or not IsValidEntity(hero) or not hero:IsAlive() or not hero:IsRealHero() then + return + end + if event.unit ~= hero then + return + end + local ability = event.ability + if not ability or ability:IsNull() then + return + end + if ability:IsItem() or ability:IsToggle() then + return + end + local radius = self:getValue("radius", 250) + local maxTargets = math.max( + 1, + math.floor(self:getValue("target_count", 1)) + ) + local manaDamagePct = self:getScaledCardValue("mana_damage_pct", 20) + local currentMana = hero:GetMana() + if currentMana <= 0 or manaDamagePct <= 0 then + return + end + local damage = currentMana * (manaDamagePct / 100) + if damage <= 0 then + return + end + local enemies = FindUnitsInRadius( + hero:GetTeamNumber(), + hero:GetAbsOrigin(), + nil, + radius, + DOTA_UNIT_TARGET_TEAM_ENEMY, + bit.bor(DOTA_UNIT_TARGET_HERO, DOTA_UNIT_TARGET_BASIC), + bit.bor(DOTA_UNIT_TARGET_FLAG_FOW_VISIBLE, DOTA_UNIT_TARGET_FLAG_NO_INVIS), + FIND_CLOSEST, + false + ) + local hitCount = 0 + local totalDamageDealt = 0 + for ____, enemy in ipairs(enemies) do + do + if not enemy or not IsValidEntity(enemy) or enemy:IsNull() or not enemy:IsAlive() then + goto __continue14 + end + if hitCount >= maxTargets then + break + end + hitCount = hitCount + 1 + local healthBefore = enemy:GetHealth() + self:playStrikeEffect(hero, enemy) + ApplyDamage({victim = enemy, attacker = hero, damage = damage, damage_type = DAMAGE_TYPE_MAGICAL}) + local damageDealt = math.max( + 0, + healthBefore - math.max( + 0, + enemy:GetHealth() + ) + ) + if damageDealt > 0 then + totalDamageDealt = totalDamageDealt + damageDealt + SendOverheadEventMessage( + nil, + OVERHEAD_ALERT_BONUS_SPELL_DAMAGE, + enemy, + damageDealt, + hero:GetPlayerOwner() + ) + end + end + ::__continue14:: + end + if totalDamageDealt > 0 and hero:IsAlive() then + HealWithBattlePass( + nil, + hero, + totalDamageDealt, + nil, + hero, + false + ) + SendOverheadEventMessage( + nil, + OVERHEAD_ALERT_HEAL, + hero, + totalDamageDealt, + hero:GetPlayerOwner() + ) + end +end +function modifier_card_76.prototype.playStrikeEffect(self, caster, target) + local particle = ParticleManager:CreateParticle(STRIKE_PARTICLE, PATTACH_CUSTOMORIGIN, caster) + ParticleManager:SetParticleControlEnt( + particle, + 0, + caster, + PATTACH_POINT_FOLLOW, + "attach_attack1", + caster:GetAbsOrigin(), + true + ) + ParticleManager:SetParticleControlEnt( + particle, + 1, + target, + PATTACH_POINT_FOLLOW, + "attach_hitloc", + target:GetAbsOrigin(), + true + ) + ParticleManager:ReleaseParticleIndex(particle) + EmitSoundOn("Hero_Lion.FingerOfDeath", caster) +end +modifier_card_76 = __TS__Decorate( + modifier_card_76, + modifier_card_76, + {registerModifier(nil)}, + {kind = "class", name = "modifier_card_76"} +) +____exports.modifier_card_76 = modifier_card_76 +return ____exports diff --git a/scripts/vscripts/cards/examples/card_77.lua b/scripts/vscripts/cards/examples/card_77.lua new file mode 100644 index 0000000..79365a5 --- /dev/null +++ b/scripts/vscripts/cards/examples/card_77.lua @@ -0,0 +1,174 @@ +local ____lualib = require("lualib_bundle") +local __TS__Class = ____lualib.__TS__Class +local __TS__ClassExtends = ____lualib.__TS__ClassExtends +local __TS__Decorate = ____lualib.__TS__Decorate +local ____exports = {} +local ____CardSystem = require("cards.CardSystem") +local CardBase = ____CardSystem.CardBase +local RegisterCard = ____CardSystem.RegisterCard +local ____CardBaseModifier = require("cards.CardBaseModifier") +local CardBaseModifier = ____CardBaseModifier.CardBaseModifier +local ____dota_ts_adapter = require("lib.dota_ts_adapter") +local registerModifier = ____dota_ts_adapter.registerModifier +local ____incoming_damage_reduction_combine = require("utils.incoming_damage_reduction_combine") +local removeIncomingDamageReductionSource = ____incoming_damage_reduction_combine.removeIncomingDamageReductionSource +local setIncomingDamageReductionEventSource = ____incoming_damage_reduction_combine.setIncomingDamageReductionEventSource +local CARD_ID = 77 +local CARD_77_INCOMING_SOURCE = "modifier_card_77" +local MANA_SHIELD_PARTICLE = "particles/econ/items/medusa/medusa_daughters/medusa_daughters_mana_shield_shell_impact_d.vpcf" +____exports.card_77 = __TS__Class() +local card_77 = ____exports.card_77 +card_77.name = "card_77" +card_77.____file_path = "scripts/vscripts/cards/examples/card_77.lua" +__TS__ClassExtends(card_77, CardBase) +function card_77.prototype.GetModifierName(self) + return "modifier_card_77" +end +card_77 = __TS__Decorate(card_77, card_77, {RegisterCard}, {kind = "class", name = "card_77"}) +____exports.card_77 = card_77 +____exports.modifier_card_77 = __TS__Class() +local modifier_card_77 = ____exports.modifier_card_77 +modifier_card_77.name = "modifier_card_77" +modifier_card_77.____file_path = "scripts/vscripts/cards/examples/card_77.lua" +__TS__ClassExtends(modifier_card_77, CardBaseModifier) +function modifier_card_77.prototype.getValue(self, key, fallback) + return self:getCardValue(key, fallback, CARD_ID) +end +function modifier_card_77.prototype.OnCustomRefresh(self, _params) + if not IsServer() then + return + end + removeIncomingDamageReductionSource( + nil, + self:GetParent(), + CARD_77_INCOMING_SOURCE + ) + self:OnCustomCreated(_params) +end +function modifier_card_77.prototype.OnCustomCreated(self, params) + if not IsServer() then + return + end + setIncomingDamageReductionEventSource( + nil, + self:GetParent(), + CARD_77_INCOMING_SOURCE, + function(____, event) + local hero = self:GetParent() + if not hero or not IsValidEntity(hero) or not hero:IsAlive() or not hero:IsRealHero() then + return 0 + end + local reducePct = math.min( + 95, + math.max( + 1, + math.floor(self:getScaledCardValue("damage_reduction_pct", 60)) + ) + ) + local manaPerMitigated = math.max( + 0.01, + self:getScaledCardValue("mana_per_mitigated_damage", 1) + ) + local baseDamage = self:readBaseDamageFromAttackEvent(event) + if baseDamage <= 0 then + return 0 + end + local targetBlocked = baseDamage * (reducePct / 100) + local maxBlockedFromMana = hero:GetMana() / manaPerMitigated + local actualBlocked = math.min(targetBlocked, maxBlockedFromMana) + if actualBlocked <= 0 then + return 0 + end + return math.max(0, actualBlocked / baseDamage * 100) + end + ) +end +function modifier_card_77.prototype.OnDestroy(self) + if not IsServer() then + return + end + removeIncomingDamageReductionSource( + nil, + self:GetParent(), + CARD_77_INCOMING_SOURCE + ) +end +function modifier_card_77.prototype.DeclareFunctions(self) + return {MODIFIER_EVENT_ON_TAKEDAMAGE, MODIFIER_PROPERTY_TOOLTIP} +end +function modifier_card_77.prototype.GetEffectName(self) + return "particles/units/heroes/hero_medusa/medusa_mana_shield.vpcf" +end +function modifier_card_77.prototype.GetEffectAttachType(self) + return PATTACH_ABSORIGIN_FOLLOW +end +function modifier_card_77.prototype.readBaseDamageFromAttackEvent(self, event) + local raw = event + local fromOriginal = raw.original_damage + if fromOriginal ~= nil and fromOriginal ~= nil and fromOriginal > 0 then + return fromOriginal + end + local d = raw.damage + if d ~= nil and d ~= nil and d > 0 then + return d + end + return 0 +end +function modifier_card_77.prototype.OnTakeDamage(self, event) + if not IsServer() then + return + end + local parent = self:GetParent() + if not parent or not IsValidEntity(parent) or not parent:IsAlive() or not parent:IsRealHero() then + return + end + if event.unit ~= parent then + return + end + local reducePct = math.min( + 95, + math.max( + 1, + math.floor(self:getScaledCardValue("damage_reduction_pct", 60)) + ) + ) + local manaPerMitigated = math.max( + 0.01, + self:getScaledCardValue("mana_per_mitigated_damage", 1) + ) + local rawEv = event + local original = rawEv.original_damage ~= nil and rawEv.original_damage ~= nil and rawEv.original_damage or event.damage + local finalDamage = event.damage + if original <= 0 then + return + end + local totalMitigated = math.max(0, original - finalDamage) + if totalMitigated <= 0 then + return + end + local expectedFromCard = original * (reducePct / 100) + local attributedBlock = math.min(expectedFromCard, totalMitigated) + if attributedBlock <= 0 then + return + end + local manaCost = attributedBlock * manaPerMitigated + if manaCost <= 0 then + return + end + local manaBefore = parent:GetMana() + local manaPaid = math.min(manaCost, manaBefore) + if manaPaid <= 0 then + return + end + parent:SetMana(math.max(0, manaBefore - manaPaid)) + local shieldFx = ParticleManager:CreateParticle(MANA_SHIELD_PARTICLE, PATTACH_ABSORIGIN_FOLLOW, parent) + ParticleManager:ReleaseParticleIndex(shieldFx) +end +modifier_card_77 = __TS__Decorate( + modifier_card_77, + modifier_card_77, + {registerModifier(nil)}, + {kind = "class", name = "modifier_card_77"} +) +____exports.modifier_card_77 = modifier_card_77 +return ____exports diff --git a/scripts/vscripts/cards/examples/card_78.lua b/scripts/vscripts/cards/examples/card_78.lua new file mode 100644 index 0000000..4246c1b --- /dev/null +++ b/scripts/vscripts/cards/examples/card_78.lua @@ -0,0 +1,140 @@ +local ____lualib = require("lualib_bundle") +local __TS__Class = ____lualib.__TS__Class +local __TS__ClassExtends = ____lualib.__TS__ClassExtends +local __TS__Decorate = ____lualib.__TS__Decorate +local ____exports = {} +local ____CardSystem = require("cards.CardSystem") +local CardBase = ____CardSystem.CardBase +local RegisterCard = ____CardSystem.RegisterCard +local ____CardBaseModifier = require("cards.CardBaseModifier") +local CardBaseModifier = ____CardBaseModifier.CardBaseModifier +local ____dota_ts_adapter = require("lib.dota_ts_adapter") +local registerModifier = ____dota_ts_adapter.registerModifier +local CARD_ID = 78 +local STACK_MODIFIER = "modifier_card_78_mana_stack" +____exports.card_78 = __TS__Class() +local card_78 = ____exports.card_78 +card_78.name = "card_78" +card_78.____file_path = "scripts/vscripts/cards/examples/card_78.lua" +__TS__ClassExtends(card_78, CardBase) +function card_78.prototype.GetModifierName(self) + return "modifier_card_78" +end +card_78 = __TS__Decorate(card_78, card_78, {RegisterCard}, {kind = "class", name = "card_78"}) +____exports.card_78 = card_78 +____exports.modifier_card_78 = __TS__Class() +local modifier_card_78 = ____exports.modifier_card_78 +modifier_card_78.name = "modifier_card_78" +modifier_card_78.____file_path = "scripts/vscripts/cards/examples/card_78.lua" +__TS__ClassExtends(modifier_card_78, CardBaseModifier) +function modifier_card_78.prototype.getValue(self, key, fallback) + return self:getCardValue(key, fallback, CARD_ID) +end +function modifier_card_78.prototype.DeclareFunctions(self) + return {MODIFIER_EVENT_ON_ABILITY_FULLY_CAST, MODIFIER_PROPERTY_TOOLTIP} +end +function modifier_card_78.prototype.OnAbilityFullyCast(self, event) + if not IsServer() then + return + end + local hero = self:GetParent() + if not hero or not IsValidEntity(hero) or not hero:IsAlive() or not hero:IsRealHero() then + return + end + if event.unit ~= hero then + return + end + local ability = event.ability + if not ability or ability:IsNull() then + return + end + if ability:IsItem() or ability:IsToggle() then + return + end + local duration = math.max( + 0.1, + self:getValue("stack_duration_sec", 10) + ) + hero:AddNewModifier( + hero, + getModifierSourceAbility(nil, hero), + STACK_MODIFIER, + {duration = duration} + ) +end +modifier_card_78 = __TS__Decorate( + modifier_card_78, + modifier_card_78, + {registerModifier(nil)}, + {kind = "class", name = "modifier_card_78"} +) +____exports.modifier_card_78 = modifier_card_78 +____exports.modifier_card_78_mana_stack = __TS__Class() +local modifier_card_78_mana_stack = ____exports.modifier_card_78_mana_stack +modifier_card_78_mana_stack.name = "modifier_card_78_mana_stack" +modifier_card_78_mana_stack.____file_path = "scripts/vscripts/cards/examples/card_78.lua" +__TS__ClassExtends(modifier_card_78_mana_stack, CardBaseModifier) +function modifier_card_78_mana_stack.prototype.____constructor(self, ...) + CardBaseModifier.prototype.____constructor(self, ...) + self.manaBonusFlat = 0 +end +function modifier_card_78_mana_stack.prototype.getCardCopiesFromOwner(self) + local owner = self:GetCaster() + if not owner or not IsValidEntity(owner) then + return 1 + end + local cardMod = owner:FindModifierByName("modifier_card_78") + return math.max( + 1, + math.floor(cardMod and cardMod:GetStackCount() or 0) + ) +end +function modifier_card_78_mana_stack.prototype.getValue(self, key, fallback) + return self:getCardValue(key, fallback, CARD_ID) * self:getCardCopiesFromOwner() +end +function modifier_card_78_mana_stack.prototype.IsHidden(self) + return false +end +function modifier_card_78_mana_stack.prototype.IsDebuff(self) + return false +end +function modifier_card_78_mana_stack.prototype.IsPurgable(self) + return true +end +function modifier_card_78_mana_stack.prototype.GetAttributes(self) + return MODIFIER_ATTRIBUTE_MULTIPLE +end +function modifier_card_78_mana_stack.prototype.OnCustomCreated(self, _params) + if not IsServer() then + return + end + local hero = self:GetParent() + if not hero or not IsValidEntity(hero) then + return + end + local pct = self:getValue("max_mana_pct_per_stack", 0.5) + self.manaBonusFlat = math.max( + 0, + math.floor(hero:GetMaxMana() * (pct / 100)) + ) +end +function modifier_card_78_mana_stack.prototype.DeclareFunctions(self) + return {MODIFIER_PROPERTY_MANA_BONUS, MODIFIER_PROPERTY_TOOLTIP} +end +function modifier_card_78_mana_stack.prototype.GetModifierManaBonus(self) + return self.manaBonusFlat +end +function modifier_card_78_mana_stack.prototype.OnTooltip(self) + return self:getValue("max_mana_pct_per_stack", 0.5) +end +function modifier_card_78_mana_stack.prototype.GetTexture(self) + return "cards/card_" .. tostring(CARD_ID) +end +modifier_card_78_mana_stack = __TS__Decorate( + modifier_card_78_mana_stack, + modifier_card_78_mana_stack, + {registerModifier(nil)}, + {kind = "class", name = "modifier_card_78_mana_stack"} +) +____exports.modifier_card_78_mana_stack = modifier_card_78_mana_stack +return ____exports diff --git a/scripts/vscripts/cards/examples/card_79.lua b/scripts/vscripts/cards/examples/card_79.lua new file mode 100644 index 0000000..c6fa7ba --- /dev/null +++ b/scripts/vscripts/cards/examples/card_79.lua @@ -0,0 +1,59 @@ +local ____lualib = require("lualib_bundle") +local __TS__Class = ____lualib.__TS__Class +local __TS__ClassExtends = ____lualib.__TS__ClassExtends +local __TS__Decorate = ____lualib.__TS__Decorate +local ____exports = {} +local ____CardSystem = require("cards.CardSystem") +local CardBase = ____CardSystem.CardBase +local RegisterCard = ____CardSystem.RegisterCard +local ____CardBaseModifier = require("cards.CardBaseModifier") +local CardBaseModifier = ____CardBaseModifier.CardBaseModifier +local ____dota_ts_adapter = require("lib.dota_ts_adapter") +local registerModifier = ____dota_ts_adapter.registerModifier +local CARD_ID = 79 +____exports.card_79 = __TS__Class() +local card_79 = ____exports.card_79 +card_79.name = "card_79" +card_79.____file_path = "scripts/vscripts/cards/examples/card_79.lua" +__TS__ClassExtends(card_79, CardBase) +function card_79.prototype.GetModifierName(self) + return "modifier_card_79" +end +card_79 = __TS__Decorate(card_79, card_79, {RegisterCard}, {kind = "class", name = "card_79"}) +____exports.card_79 = card_79 +____exports.modifier_card_79 = __TS__Class() +local modifier_card_79 = ____exports.modifier_card_79 +modifier_card_79.name = "modifier_card_79" +modifier_card_79.____file_path = "scripts/vscripts/cards/examples/card_79.lua" +__TS__ClassExtends(modifier_card_79, CardBaseModifier) +function modifier_card_79.prototype.OnCustomCreated(self, _params) + if not IsServer() then + return + end + local hero = self:GetParent() + if not hero or not IsValidEntity(hero) or not hero:IsRealHero() then + return + end + Timers:CreateTimer( + 0, + function() + local player = hero:GetPlayerOwner() + if not player or not player.cardSystem then + return nil + end + player.cardSystem:ScheduleRepeatNextSelectedCardOnce() + return nil + end + ) +end +function modifier_card_79.prototype.IsHidden(self) + return true +end +modifier_card_79 = __TS__Decorate( + modifier_card_79, + modifier_card_79, + {registerModifier(nil)}, + {kind = "class", name = "modifier_card_79"} +) +____exports.modifier_card_79 = modifier_card_79 +return ____exports diff --git a/scripts/vscripts/cards/examples/card_8.lua b/scripts/vscripts/cards/examples/card_8.lua new file mode 100644 index 0000000..946ff36 --- /dev/null +++ b/scripts/vscripts/cards/examples/card_8.lua @@ -0,0 +1,146 @@ +local ____lualib = require("lualib_bundle") +local __TS__Class = ____lualib.__TS__Class +local __TS__ClassExtends = ____lualib.__TS__ClassExtends +local __TS__Decorate = ____lualib.__TS__Decorate +local __TS__Number = ____lualib.__TS__Number +local __TS__NumberIsFinite = ____lualib.__TS__NumberIsFinite +local __TS__ObjectAssign = ____lualib.__TS__ObjectAssign +local ____exports = {} +local ____CardSystem = require("cards.CardSystem") +local CardBase = ____CardSystem.CardBase +local RegisterCard = ____CardSystem.RegisterCard +local ____CardBaseModifier = require("cards.CardBaseModifier") +local CardBaseModifier = ____CardBaseModifier.CardBaseModifier +local ____dota_ts_adapter = require("lib.dota_ts_adapter") +local registerModifier = ____dota_ts_adapter.registerModifier +local ____modifier_card_cursed = require("cards.modifier_card_cursed") +local addCursedStack = ____modifier_card_cursed.addCursedStack +local removeCursedStack = ____modifier_card_cursed.removeCursedStack +____exports.card_8 = __TS__Class() +local card_8 = ____exports.card_8 +card_8.name = "card_8" +card_8.____file_path = "scripts/vscripts/cards/examples/card_8.lua" +__TS__ClassExtends(card_8, CardBase) +function card_8.prototype.GetModifierName(self) + return "modifier_card_8" +end +card_8 = __TS__Decorate(card_8, card_8, {RegisterCard}, {kind = "class", name = "card_8"}) +____exports.card_8 = card_8 +____exports.modifier_card_8 = __TS__Class() +local modifier_card_8 = ____exports.modifier_card_8 +modifier_card_8.name = "modifier_card_8" +modifier_card_8.____file_path = "scripts/vscripts/cards/examples/card_8.lua" +__TS__ClassExtends(modifier_card_8, CardBaseModifier) +function modifier_card_8.prototype.____constructor(self, ...) + CardBaseModifier.prototype.____constructor(self, ...) + self.appliedCursedStacks = 0 + self.rampStacks = 0 +end +function modifier_card_8.prototype.OnCustomCreated(self, params) + if not IsServer() then + return + end + local hero = self:GetParent() + if not hero or not IsValidEntity(hero) or not hero:IsRealHero() then + return + end + self:syncCursedStacks(hero) +end +function modifier_card_8.prototype.OnCustomRefresh(self, _params) + if not IsServer() then + return + end + local hero = self:GetParent() + if not hero or not IsValidEntity(hero) or not hero:IsRealHero() then + return + end + self:syncCursedStacks(hero) + hero:CalculateStatBonus(true) + self:SendBuffRefreshToClients() +end +function modifier_card_8.prototype.GetAttributes(self) + return MODIFIER_ATTRIBUTE_MULTIPLE +end +function modifier_card_8.prototype.DeclareFunctions(self) + return {MODIFIER_PROPERTY_EXTRA_HEALTH_BONUS, MODIFIER_EVENT_ON_DEATH, MODIFIER_PROPERTY_PREATTACK_BONUS_DAMAGE, MODIFIER_PROPERTY_TOOLTIP} +end +function modifier_card_8.prototype.OnDeath(self, event) + if not IsServer() then + return + end + local hero = self:GetParent() + if not hero or not IsValidEntity(hero) or not hero:IsRealHero() then + return + end + if event.attacker ~= hero then + return + end + if event.unit == hero then + return + end + local hpThreshold = self:getCardValue("stack_health_threshold", 900, 8) + if hero:GetHealth() < hpThreshold then + return + end + self.rampStacks = self.rampStacks + 1 + hero:CalculateStatBonus(true) + self:SendBuffRefreshToClients() +end +function modifier_card_8.prototype.GetModifierPreAttack_BonusDamage(self) + local perStack = self:getCardValue("damage_bonus_health_decress", 0, 8) + return perStack * self.rampStacks * self:getCardCopies() +end +function modifier_card_8.prototype.GetModifierExtraHealthBonus(self) + local perStack = self:getCardValue("damage_bonus_health_decress", 0, 8) + return -perStack * self.rampStacks * self:getCardCopies() +end +function modifier_card_8.prototype.OnDestroy(self) + if not IsServer() then + return + end + local hero = self:GetParent() + if not hero or not IsValidEntity(hero) or not hero:IsRealHero() then + return + end + while self.appliedCursedStacks > 0 do + removeCursedStack(nil, hero) + self.appliedCursedStacks = self.appliedCursedStacks - 1 + end +end +function modifier_card_8.prototype.syncCursedStacks(self, hero) + local desiredStacks = self:getCardCopies() + while self.appliedCursedStacks < desiredStacks do + addCursedStack(nil, hero) + self.appliedCursedStacks = self.appliedCursedStacks + 1 + end + while self.appliedCursedStacks > desiredStacks do + removeCursedStack(nil, hero) + self.appliedCursedStacks = self.appliedCursedStacks - 1 + end +end +function modifier_card_8.prototype.HandleCustomTransmitterData(self, data) + CardBaseModifier.prototype.HandleCustomTransmitterData(self, data) + local ____opt_result_2 + if data ~= nil then + ____opt_result_2 = data.card8_ramp_stacks + end + local rampStacksRaw = __TS__Number(____opt_result_2) + if __TS__NumberIsFinite(rampStacksRaw) and rampStacksRaw >= 0 then + self.rampStacks = math.floor(rampStacksRaw) + end +end +function modifier_card_8.prototype.AddCustomTransmitterData(self) + return __TS__ObjectAssign( + {}, + CardBaseModifier.prototype.AddCustomTransmitterData(self), + {card8_ramp_stacks = self.rampStacks} + ) +end +modifier_card_8 = __TS__Decorate( + modifier_card_8, + modifier_card_8, + {registerModifier(nil)}, + {kind = "class", name = "modifier_card_8"} +) +____exports.modifier_card_8 = modifier_card_8 +return ____exports diff --git a/scripts/vscripts/cards/examples/card_80.lua b/scripts/vscripts/cards/examples/card_80.lua new file mode 100644 index 0000000..cdbc29a --- /dev/null +++ b/scripts/vscripts/cards/examples/card_80.lua @@ -0,0 +1,206 @@ +local ____lualib = require("lualib_bundle") +local __TS__Number = ____lualib.__TS__Number +local __TS__Class = ____lualib.__TS__Class +local __TS__ClassExtends = ____lualib.__TS__ClassExtends +local __TS__Decorate = ____lualib.__TS__Decorate +local ____exports = {} +local ____CardSystem = require("cards.CardSystem") +local AddCardToPlayerPool = ____CardSystem.AddCardToPlayerPool +local CardBase = ____CardSystem.CardBase +local RegisterCard = ____CardSystem.RegisterCard +local ____CardBaseModifier = require("cards.CardBaseModifier") +local CardBaseModifier = ____CardBaseModifier.CardBaseModifier +local ____dota_ts_adapter = require("lib.dota_ts_adapter") +local registerModifier = ____dota_ts_adapter.registerModifier +local ____modifier_card_cursed = require("cards.modifier_card_cursed") +local CURSED_PACT_COMPLETE_MODIFIER = ____modifier_card_cursed.CURSED_PACT_COMPLETE_MODIFIER +local getCursedStackCount = ____modifier_card_cursed.getCursedStackCount +local CARD_ID = 80 +--- Осколки Фростморна, которые замешиваются в колоду (синхронизируй с описанием карты 80). +____exports.FROSTMOURNE_SHARD_CARD_IDS = {81, 82, 83} +--- Вся линия Фростморна — нельзя дублировать «Зеркалом судьбы» (57) и «Эхом выбора» (79). +____exports.FROSTMOURNE_MIRROR_PROTECTED_CARD_IDS = { + CARD_ID, + unpack(____exports.FROSTMOURNE_SHARD_CARD_IDS) +} +function ____exports.isFrostmourneMirrorProtectedCardId(self, cardId) + local id = math.floor(__TS__Number(cardId)) + for ____, protectedId in ipairs(____exports.FROSTMOURNE_MIRROR_PROTECTED_CARD_IDS) do + if protectedId == id then + return true + end + end + return false +end +function ____exports.isFrostmourneShardCardId(self, cardId) + for ____, id in ipairs(____exports.FROSTMOURNE_SHARD_CARD_IDS) do + if id == cardId then + return true + end + end + return false +end +--- Вызывается из CardSystem при получении карты игроком. +function ____exports.notifyCard80PlayerTookCard(self, hero, cardId) + if not IsServer() or not hero or not IsValidEntity(hero) or not hero:IsRealHero() then + return + end + local tracker = hero:FindModifierByName("modifier_card_80") + if tracker ~= nil then + tracker:OnShardCardTaken(cardId) + end +end +____exports.card_80 = __TS__Class() +local card_80 = ____exports.card_80 +card_80.name = "card_80" +card_80.____file_path = "scripts/vscripts/cards/examples/card_80.lua" +__TS__ClassExtends(card_80, CardBase) +function card_80.prototype.GetModifierName(self) + return "modifier_card_80" +end +card_80 = __TS__Decorate(card_80, card_80, {RegisterCard}, {kind = "class", name = "card_80"}) +____exports.card_80 = card_80 +____exports.modifier_card_80 = __TS__Class() +local modifier_card_80 = ____exports.modifier_card_80 +modifier_card_80.name = "modifier_card_80" +modifier_card_80.____file_path = "scripts/vscripts/cards/examples/card_80.lua" +__TS__ClassExtends(modifier_card_80, CardBaseModifier) +function modifier_card_80.prototype.____constructor(self, ...) + CardBaseModifier.prototype.____constructor(self, ...) + self.takenShardIds = {} + self.shardsCollected = 0 +end +function modifier_card_80.prototype.OnCustomCreated(self, _params) + if not IsServer() then + return + end + local hero = self:GetParent() + if not hero or not IsValidEntity(hero) or not hero:IsRealHero() then + return + end + local playerId = hero:GetPlayerOwnerID() + if playerId == nil or playerId == nil or playerId < 0 then + return + end + for ____, shardId in ipairs(____exports.FROSTMOURNE_SHARD_CARD_IDS) do + AddCardToPlayerPool( + nil, + playerId, + shardId, + 1, + 1, + "card_80_frostmourne_shards" + ) + end + self.shardsCollected = 0 +end +function modifier_card_80.prototype.OnShardCardTaken(self, cardId) + if not IsServer() then + return + end + if not ____exports.isFrostmourneShardCardId(nil, cardId) then + return + end + if self.takenShardIds[cardId] then + return + end + self.takenShardIds[cardId] = true + local takenCount = 0 + for ____, id in ipairs(____exports.FROSTMOURNE_SHARD_CARD_IDS) do + if self.takenShardIds[id] then + takenCount = takenCount + 1 + end + end + self.shardsCollected = takenCount + if takenCount < #____exports.FROSTMOURNE_SHARD_CARD_IDS then + return + end + local hero = self:GetParent() + if not hero or not IsValidEntity(hero) or hero:HasModifier(CURSED_PACT_COMPLETE_MODIFIER) then + return + end + hero:AddNewModifier( + hero, + getModifierSourceAbility(nil, hero), + CURSED_PACT_COMPLETE_MODIFIER, + {} + ) + hero:CalculateStatBonus(true) + EmitSoundOn("Hero_Antimage.ManaBreak", hero) +end +function modifier_card_80.prototype.DeclareFunctions(self) + return {MODIFIER_PROPERTY_TOOLTIP} +end +function modifier_card_80.prototype.OnTooltip(self) + return self.shardsCollected +end +function modifier_card_80.prototype.IsHidden(self) + return false +end +function modifier_card_80.prototype.GetTexture(self) + return "cards/card_" .. tostring(CARD_ID) +end +modifier_card_80 = __TS__Decorate( + modifier_card_80, + modifier_card_80, + {registerModifier(nil)}, + {kind = "class", name = "modifier_card_80"} +) +____exports.modifier_card_80 = modifier_card_80 +____exports.modifier_card_80_pact_complete = __TS__Class() +local modifier_card_80_pact_complete = ____exports.modifier_card_80_pact_complete +modifier_card_80_pact_complete.name = "modifier_card_80_pact_complete" +modifier_card_80_pact_complete.____file_path = "scripts/vscripts/cards/examples/card_80.lua" +__TS__ClassExtends(modifier_card_80_pact_complete, CardBaseModifier) +function modifier_card_80_pact_complete.prototype.getCardCopiesFromFrostmourne(self) + local hero = self:GetParent() + if not hero or not IsValidEntity(hero) then + return 1 + end + local cardMod = hero:FindModifierByName("modifier_card_80") + return math.max( + 1, + math.floor(cardMod and cardMod:GetStackCount() or 0) + ) +end +function modifier_card_80_pact_complete.prototype.getValue(self, key, fallback) + return self:getCardValue(key, fallback, CARD_ID) * self:getCardCopiesFromFrostmourne() +end +function modifier_card_80_pact_complete.prototype.IsHidden(self) + return false +end +function modifier_card_80_pact_complete.prototype.IsDebuff(self) + return false +end +function modifier_card_80_pact_complete.prototype.IsPurgable(self) + return false +end +function modifier_card_80_pact_complete.prototype.DeclareFunctions(self) + return {MODIFIER_PROPERTY_DAMAGEOUTGOING_PERCENTAGE, MODIFIER_PROPERTY_TOOLTIP} +end +function modifier_card_80_pact_complete.prototype.GetModifierDamageOutgoing_Percentage(self) + local hero = self:GetParent() + if not hero or not IsValidEntity(hero) or not hero:IsRealHero() then + return 0 + end + local pctPerStack = self:getValue("outgoing_damage_per_curse_stack_pct", 5) + return getCursedStackCount(nil, hero) * pctPerStack +end +function modifier_card_80_pact_complete.prototype.OnTooltip(self) + local hero = self:GetParent() + if not hero or not IsValidEntity(hero) then + return 0 + end + return getCursedStackCount(nil, hero) +end +function modifier_card_80_pact_complete.prototype.GetTexture(self) + return "cards/card_" .. tostring(CARD_ID) +end +modifier_card_80_pact_complete = __TS__Decorate( + modifier_card_80_pact_complete, + modifier_card_80_pact_complete, + {registerModifier(nil)}, + {kind = "class", name = "modifier_card_80_pact_complete"} +) +____exports.modifier_card_80_pact_complete = modifier_card_80_pact_complete +return ____exports diff --git a/scripts/vscripts/cards/examples/card_81.lua b/scripts/vscripts/cards/examples/card_81.lua new file mode 100644 index 0000000..ef33809 --- /dev/null +++ b/scripts/vscripts/cards/examples/card_81.lua @@ -0,0 +1,69 @@ +local ____lualib = require("lualib_bundle") +local __TS__Class = ____lualib.__TS__Class +local __TS__ClassExtends = ____lualib.__TS__ClassExtends +local __TS__Decorate = ____lualib.__TS__Decorate +local ____exports = {} +local ____CardSystem = require("cards.CardSystem") +local CardBase = ____CardSystem.CardBase +local RegisterCard = ____CardSystem.RegisterCard +local ____CardBaseModifier = require("cards.CardBaseModifier") +local CardBaseModifier = ____CardBaseModifier.CardBaseModifier +local ____dota_ts_adapter = require("lib.dota_ts_adapter") +local registerModifier = ____dota_ts_adapter.registerModifier +local ____modifier_card_cursed = require("cards.modifier_card_cursed") +local addCursedStack = ____modifier_card_cursed.addCursedStack +local CARD_ID = 81 +____exports.card_81 = __TS__Class() +local card_81 = ____exports.card_81 +card_81.name = "card_81" +card_81.____file_path = "scripts/vscripts/cards/examples/card_81.lua" +__TS__ClassExtends(card_81, CardBase) +function card_81.prototype.GetModifierName(self) + return "modifier_card_81" +end +card_81 = __TS__Decorate(card_81, card_81, {RegisterCard}, {kind = "class", name = "card_81"}) +____exports.card_81 = card_81 +____exports.modifier_card_81 = __TS__Class() +local modifier_card_81 = ____exports.modifier_card_81 +modifier_card_81.name = "modifier_card_81" +modifier_card_81.____file_path = "scripts/vscripts/cards/examples/card_81.lua" +__TS__ClassExtends(modifier_card_81, CardBaseModifier) +function modifier_card_81.prototype.getValue(self, key, fallback) + return self:getCardValue(key, fallback, CARD_ID) +end +function modifier_card_81.prototype.OnCustomCreated(self, _params) + if not IsServer() then + return + end + local hero = self:GetParent() + if not hero or not IsValidEntity(hero) or not hero:IsRealHero() then + return + end + addCursedStack(nil, hero) +end +function modifier_card_81.prototype.DeclareFunctions(self) + return {MODIFIER_PROPERTY_DAMAGEOUTGOING_PERCENTAGE, MODIFIER_PROPERTY_SPELL_AMPLIFY_PERCENTAGE, MODIFIER_PROPERTY_TOOLTIP} +end +function modifier_card_81.prototype.GetModifierDamageOutgoing_Percentage(self) + return self:getScaledCardValue("outgoing_damage_pct", 15) +end +function modifier_card_81.prototype.GetModifierSpellAmplify_Percentage(self) + return self:getScaledCardValue("spell_amp_pct", 15) +end +function modifier_card_81.prototype.OnTooltip(self) + return self:getScaledCardValue("outgoing_damage_pct", 15) +end +function modifier_card_81.prototype.IsHidden(self) + return false +end +function modifier_card_81.prototype.GetTexture(self) + return "cards/card_" .. tostring(CARD_ID) +end +modifier_card_81 = __TS__Decorate( + modifier_card_81, + modifier_card_81, + {registerModifier(nil)}, + {kind = "class", name = "modifier_card_81"} +) +____exports.modifier_card_81 = modifier_card_81 +return ____exports diff --git a/scripts/vscripts/cards/examples/card_82.lua b/scripts/vscripts/cards/examples/card_82.lua new file mode 100644 index 0000000..579e606 --- /dev/null +++ b/scripts/vscripts/cards/examples/card_82.lua @@ -0,0 +1,90 @@ +local ____lualib = require("lualib_bundle") +local __TS__Class = ____lualib.__TS__Class +local __TS__ClassExtends = ____lualib.__TS__ClassExtends +local __TS__Decorate = ____lualib.__TS__Decorate +local ____exports = {} +local ____CardSystem = require("cards.CardSystem") +local CardBase = ____CardSystem.CardBase +local RegisterCard = ____CardSystem.RegisterCard +local ____CardBaseModifier = require("cards.CardBaseModifier") +local CardBaseModifier = ____CardBaseModifier.CardBaseModifier +local ____dota_ts_adapter = require("lib.dota_ts_adapter") +local registerModifier = ____dota_ts_adapter.registerModifier +local ____modifier_card_cursed = require("cards.modifier_card_cursed") +local addCursedStack = ____modifier_card_cursed.addCursedStack +local CARD_ID = 82 +____exports.card_82 = __TS__Class() +local card_82 = ____exports.card_82 +card_82.name = "card_82" +card_82.____file_path = "scripts/vscripts/cards/examples/card_82.lua" +__TS__ClassExtends(card_82, CardBase) +function card_82.prototype.GetModifierName(self) + return "modifier_card_82" +end +card_82 = __TS__Decorate(card_82, card_82, {RegisterCard}, {kind = "class", name = "card_82"}) +____exports.card_82 = card_82 +____exports.modifier_card_82 = __TS__Class() +local modifier_card_82 = ____exports.modifier_card_82 +modifier_card_82.name = "modifier_card_82" +modifier_card_82.____file_path = "scripts/vscripts/cards/examples/card_82.lua" +__TS__ClassExtends(modifier_card_82, CardBaseModifier) +function modifier_card_82.prototype.____constructor(self, ...) + CardBaseModifier.prototype.____constructor(self, ...) + self.manaBonusSnapshot = 0 +end +function modifier_card_82.prototype.getValue(self, key, fallback) + return self:getCardValue(key, fallback, CARD_ID) +end +function modifier_card_82.prototype.OnCustomCreated(self, _params) + if not IsServer() then + return + end + local hero = self:GetParent() + if not hero or not IsValidEntity(hero) or not hero:IsRealHero() then + return + end + addCursedStack(nil, hero) + local manaPct = self:getValue("max_mana_pct", 20) + local manaBonus = math.floor(hero:GetMaxMana() * (math.max(0, manaPct) / 100)) + self.manaBonusSnapshot = self.manaBonusSnapshot + math.max(0, manaBonus) + hero:CalculateStatBonus(true) +end +function modifier_card_82.prototype.OnCustomRefresh(self, _params) + if not IsServer() then + return + end + local hero = self:GetParent() + if not hero or not IsValidEntity(hero) or not hero:IsRealHero() then + return + end + local manaPct = self:getValue("max_mana_pct", 20) + local manaBonus = math.floor(hero:GetMaxMana() * (math.max(0, manaPct) / 100)) + self.manaBonusSnapshot = self.manaBonusSnapshot + math.max(0, manaBonus) + hero:CalculateStatBonus(true) +end +function modifier_card_82.prototype.DeclareFunctions(self) + return {MODIFIER_PROPERTY_EXTRA_HEALTH_PERCENTAGE, MODIFIER_PROPERTY_MANA_BONUS, MODIFIER_PROPERTY_TOOLTIP} +end +function modifier_card_82.prototype.GetModifierExtraHealthPercentage(self) + return self:getScaledCardValue("max_health_pct", 20) +end +function modifier_card_82.prototype.GetModifierManaBonus(self) + return self.manaBonusSnapshot +end +function modifier_card_82.prototype.OnTooltip(self) + return self:getScaledCardValue("max_health_pct", 20) +end +function modifier_card_82.prototype.IsHidden(self) + return false +end +function modifier_card_82.prototype.GetTexture(self) + return "cards/card_" .. tostring(CARD_ID) +end +modifier_card_82 = __TS__Decorate( + modifier_card_82, + modifier_card_82, + {registerModifier(nil)}, + {kind = "class", name = "modifier_card_82"} +) +____exports.modifier_card_82 = modifier_card_82 +return ____exports diff --git a/scripts/vscripts/cards/examples/card_83.lua b/scripts/vscripts/cards/examples/card_83.lua new file mode 100644 index 0000000..d98d7cd --- /dev/null +++ b/scripts/vscripts/cards/examples/card_83.lua @@ -0,0 +1,130 @@ +local ____lualib = require("lualib_bundle") +local __TS__Class = ____lualib.__TS__Class +local __TS__ClassExtends = ____lualib.__TS__ClassExtends +local __TS__Decorate = ____lualib.__TS__Decorate +local ____exports = {} +local ____CardSystem = require("cards.CardSystem") +local CardBase = ____CardSystem.CardBase +local RegisterCard = ____CardSystem.RegisterCard +local ____CardBaseModifier = require("cards.CardBaseModifier") +local CardBaseModifier = ____CardBaseModifier.CardBaseModifier +local ____dota_ts_adapter = require("lib.dota_ts_adapter") +local registerModifier = ____dota_ts_adapter.registerModifier +local ____modifier_card_cursed = require("cards.modifier_card_cursed") +local addCursedStack = ____modifier_card_cursed.addCursedStack +local ____vampirism = require("utils.vampirism") +local addMagicalVampirism = ____vampirism.addMagicalVampirism +local addPhysicalVampirism = ____vampirism.addPhysicalVampirism +local precacheVampirismParticle = ____vampirism.precacheVampirismParticle +local reduceMagicalVampirism = ____vampirism.reduceMagicalVampirism +local reducePhysicalVampirism = ____vampirism.reducePhysicalVampirism +local CARD_ID = 83 +____exports.card_83 = __TS__Class() +local card_83 = ____exports.card_83 +card_83.name = "card_83" +card_83.____file_path = "scripts/vscripts/cards/examples/card_83.lua" +__TS__ClassExtends(card_83, CardBase) +function card_83.prototype.GetModifierName(self) + return "modifier_card_83" +end +card_83 = __TS__Decorate(card_83, card_83, {RegisterCard}, {kind = "class", name = "card_83"}) +____exports.card_83 = card_83 +____exports.modifier_card_83 = __TS__Class() +local modifier_card_83 = ____exports.modifier_card_83 +modifier_card_83.name = "modifier_card_83" +modifier_card_83.____file_path = "scripts/vscripts/cards/examples/card_83.lua" +__TS__ClassExtends(modifier_card_83, CardBaseModifier) +function modifier_card_83.prototype.____constructor(self, ...) + CardBaseModifier.prototype.____constructor(self, ...) + self.vampirismApplied = false + self.appliedPhysicalVamp = 0 + self.appliedMagicalVamp = 0 +end +function modifier_card_83.prototype.getValue(self, key, fallback) + return self:getCardValue(key, fallback, CARD_ID) +end +function modifier_card_83.prototype.Precache(self, context) + precacheVampirismParticle(nil, context) +end +function modifier_card_83.prototype.OnCustomCreated(self, _params) + if not IsServer() then + return + end + local hero = self:GetParent() + if not hero or not IsValidEntity(hero) or not hero:IsRealHero() then + return + end + addCursedStack(nil, hero) + self:applyVampirism(hero) +end +function modifier_card_83.prototype.OnDestroy(self) + if not IsServer() then + return + end + local hero = self:GetParent() + if not hero or not IsValidEntity(hero) then + return + end + self:removeVampirism(hero) +end +function modifier_card_83.prototype.OnCustomRefresh(self, _params) + if not IsServer() then + return + end + local hero = self:GetParent() + if not hero or not IsValidEntity(hero) then + return + end + self:removeVampirism(hero) + self:applyVampirism(hero) +end +function modifier_card_83.prototype.applyVampirism(self, hero) + local physicalPct = self:getScaledCardValue("physical_vampirism_pct", 10) + local magicalPct = self:getScaledCardValue("magical_vampirism_pct", 10) + if physicalPct > 0 then + addPhysicalVampirism(nil, hero, physicalPct) + self.appliedPhysicalVamp = physicalPct + end + if magicalPct > 0 then + addMagicalVampirism(nil, hero, magicalPct) + self.appliedMagicalVamp = magicalPct + end + self.vampirismApplied = physicalPct > 0 or magicalPct > 0 +end +function modifier_card_83.prototype.removeVampirism(self, hero) + if not self.vampirismApplied then + return + end + if self.appliedPhysicalVamp > 0 then + reducePhysicalVampirism(nil, hero, self.appliedPhysicalVamp) + self.appliedPhysicalVamp = 0 + end + if self.appliedMagicalVamp > 0 then + reduceMagicalVampirism(nil, hero, self.appliedMagicalVamp) + self.appliedMagicalVamp = 0 + end + self.vampirismApplied = false +end +function modifier_card_83.prototype.DeclareFunctions(self) + return {MODIFIER_PROPERTY_MODEL_SCALE, MODIFIER_PROPERTY_TOOLTIP} +end +function modifier_card_83.prototype.GetModifierModelScale(self) + return self:getScaledCardValue("model_scale_pct", 20) +end +function modifier_card_83.prototype.OnTooltip(self) + return self:getScaledCardValue("model_scale_pct", 20) +end +function modifier_card_83.prototype.IsHidden(self) + return false +end +function modifier_card_83.prototype.GetTexture(self) + return "cards/card_" .. tostring(CARD_ID) +end +modifier_card_83 = __TS__Decorate( + modifier_card_83, + modifier_card_83, + {registerModifier(nil)}, + {kind = "class", name = "modifier_card_83"} +) +____exports.modifier_card_83 = modifier_card_83 +return ____exports diff --git a/scripts/vscripts/cards/examples/card_84.lua b/scripts/vscripts/cards/examples/card_84.lua new file mode 100644 index 0000000..529b466 --- /dev/null +++ b/scripts/vscripts/cards/examples/card_84.lua @@ -0,0 +1,106 @@ +local ____lualib = require("lualib_bundle") +local __TS__Class = ____lualib.__TS__Class +local __TS__ClassExtends = ____lualib.__TS__ClassExtends +local __TS__Decorate = ____lualib.__TS__Decorate +local ____exports = {} +local ____CardSystem = require("cards.CardSystem") +local CardBase = ____CardSystem.CardBase +local RegisterCard = ____CardSystem.RegisterCard +local ____CardBaseModifier = require("cards.CardBaseModifier") +local CardBaseModifier = ____CardBaseModifier.CardBaseModifier +local ____dota_ts_adapter = require("lib.dota_ts_adapter") +local registerModifier = ____dota_ts_adapter.registerModifier +local CARD_ID = 84 +____exports.card_84 = __TS__Class() +local card_84 = ____exports.card_84 +card_84.name = "card_84" +card_84.____file_path = "scripts/vscripts/cards/examples/card_84.lua" +__TS__ClassExtends(card_84, CardBase) +function card_84.prototype.GetModifierName(self) + return "modifier_card_84" +end +card_84 = __TS__Decorate(card_84, card_84, {RegisterCard}, {kind = "class", name = "card_84"}) +____exports.card_84 = card_84 +____exports.modifier_card_84 = __TS__Class() +local modifier_card_84 = ____exports.modifier_card_84 +modifier_card_84.name = "modifier_card_84" +modifier_card_84.____file_path = "scripts/vscripts/cards/examples/card_84.lua" +__TS__ClassExtends(modifier_card_84, CardBaseModifier) +function modifier_card_84.prototype.____constructor(self, ...) + CardBaseModifier.prototype.____constructor(self, ...) + self.frozenSpellAmp = 0 +end +function modifier_card_84.prototype.getValue(self, key, fallback) + return self:getCardValue(key, fallback, CARD_ID) +end +function modifier_card_84.prototype.OnCustomCreated(self, _params) + if not IsServer() then + return + end + local hero = self:GetParent() + if not hero or not IsValidEntity(hero) or not hero:IsRealHero() then + return + end + self:applyFrozenSpellAmpSnapshot(hero) + if self.frozenSpellAmp <= 0 then + Timers:CreateTimer( + 0, + function() + if self:IsNull() or not IsValidEntity(hero) then + return nil + end + if self.frozenSpellAmp > 0 then + return nil + end + self:applyFrozenSpellAmpSnapshot(hero) + return nil + end + ) + end +end +function modifier_card_84.prototype.OnCustomRefresh(self, _params) + if not IsServer() then + return + end + local hero = self:GetParent() + if not hero or not IsValidEntity(hero) or not hero:IsRealHero() then + return + end + self:applyFrozenSpellAmpSnapshot(hero) +end +function modifier_card_84.prototype.applyFrozenSpellAmpSnapshot(self, hero) + hero:CalculateStatBonus(true) + local spellAmpFromManaPct = math.max( + 0, + self:getValue("spell_amp_from_mana_pct", 15) + ) + local spellAmpManaScale = math.max( + 0, + self:getValue("spell_amp_mana_scale", 0.1) + ) + local spellAmpBonus = math.floor(hero:GetMaxMana() * (spellAmpFromManaPct / 100) * spellAmpManaScale) + self.frozenSpellAmp = self.frozenSpellAmp + math.max(0, spellAmpBonus) +end +function modifier_card_84.prototype.DeclareFunctions(self) + return {MODIFIER_PROPERTY_SPELL_AMPLIFY_PERCENTAGE, MODIFIER_PROPERTY_TOOLTIP} +end +function modifier_card_84.prototype.GetModifierSpellAmplify_Percentage(self) + return self.frozenSpellAmp +end +function modifier_card_84.prototype.OnTooltip(self) + return self:GetModifierSpellAmplify_Percentage() +end +function modifier_card_84.prototype.GetTexture(self) + return "cards/card_" .. tostring(CARD_ID) +end +function modifier_card_84.prototype.IsHidden(self) + return false +end +modifier_card_84 = __TS__Decorate( + modifier_card_84, + modifier_card_84, + {registerModifier(nil)}, + {kind = "class", name = "modifier_card_84"} +) +____exports.modifier_card_84 = modifier_card_84 +return ____exports diff --git a/scripts/vscripts/cards/examples/card_85.lua b/scripts/vscripts/cards/examples/card_85.lua new file mode 100644 index 0000000..6ba2203 --- /dev/null +++ b/scripts/vscripts/cards/examples/card_85.lua @@ -0,0 +1,80 @@ +local ____lualib = require("lualib_bundle") +local __TS__Class = ____lualib.__TS__Class +local __TS__ClassExtends = ____lualib.__TS__ClassExtends +local __TS__Decorate = ____lualib.__TS__Decorate +local ____exports = {} +local ____CardSystem = require("cards.CardSystem") +local AddSlagToPlayerPool = ____CardSystem.AddSlagToPlayerPool +local CardBase = ____CardSystem.CardBase +local RegisterCard = ____CardSystem.RegisterCard +local ShowCardSelectionToPlayer = ____CardSystem.ShowCardSelectionToPlayer +local ____card_slag = require("cards.card_slag") +local FISHING_CARD_ID = ____card_slag.FISHING_CARD_ID +local ____card_value_resolver = require("cards.card_value_resolver") +local getCardValueByLevel = ____card_value_resolver.getCardValueByLevel +local CARD_ID = FISHING_CARD_ID +local BONUS_SELECTION_SOURCE = "card_85_fishing_bonus" +local SLAG_POOL_SOURCE = "card_85_fishing_slag" +____exports.card_85 = __TS__Class() +local card_85 = ____exports.card_85 +card_85.name = "card_85" +card_85.____file_path = "scripts/vscripts/cards/examples/card_85.lua" +__TS__ClassExtends(card_85, CardBase) +function card_85.prototype.OnCreated(self) + if not IsServer() then + return + end + local hero = self:GetHero() + if not hero or not IsValidEntity(hero) or not hero:IsRealHero() then + return + end + local playerId = hero:GetPlayerOwnerID() + if playerId == nil or playerId == nil or playerId < 0 then + return + end + Timers:CreateTimer( + 0, + function() + local slagCopies = math.max( + 1, + math.floor(getCardValueByLevel( + nil, + CARD_ID, + hero, + "slag_pool_copies", + 1 + )) + ) + AddSlagToPlayerPool( + nil, + playerId, + 1, + slagCopies, + SLAG_POOL_SOURCE + ) + local ____opt_0 = PlayerResource:GetPlayer(playerId) + local cardSystem = ____opt_0 and ____opt_0.cardSystem + if cardSystem ~= nil then + cardSystem:SetPoolCardFromSource(CARD_ID, 1, BONUS_SELECTION_SOURCE) + end + local bonusChoices = math.max( + 1, + math.floor(getCardValueByLevel( + nil, + CARD_ID, + hero, + "bonus_card_choices", + 1 + )) + ) + ShowCardSelectionToPlayer(nil, playerId, bonusChoices, BONUS_SELECTION_SOURCE) + return nil + end + ) +end +function card_85.prototype.GetModifierName(self) + return nil +end +card_85 = __TS__Decorate(card_85, card_85, {RegisterCard}, {kind = "class", name = "card_85"}) +____exports.card_85 = card_85 +return ____exports diff --git a/scripts/vscripts/cards/examples/card_86.lua b/scripts/vscripts/cards/examples/card_86.lua new file mode 100644 index 0000000..90d4a09 --- /dev/null +++ b/scripts/vscripts/cards/examples/card_86.lua @@ -0,0 +1,22 @@ +local ____lualib = require("lualib_bundle") +local __TS__Class = ____lualib.__TS__Class +local __TS__ClassExtends = ____lualib.__TS__ClassExtends +local __TS__Decorate = ____lualib.__TS__Decorate +local ____exports = {} +local ____CardSystem = require("cards.CardSystem") +local CardBase = ____CardSystem.CardBase +local RegisterCard = ____CardSystem.RegisterCard +____exports.card_86 = __TS__Class() +local card_86 = ____exports.card_86 +card_86.name = "card_86" +card_86.____file_path = "scripts/vscripts/cards/examples/card_86.lua" +__TS__ClassExtends(card_86, CardBase) +function card_86.prototype.GetModifierName(self) + return nil +end +function card_86.prototype.IsHidden(self) + return true +end +card_86 = __TS__Decorate(card_86, card_86, {RegisterCard}, {kind = "class", name = "card_86"}) +____exports.card_86 = card_86 +return ____exports diff --git a/scripts/vscripts/cards/examples/card_87.lua b/scripts/vscripts/cards/examples/card_87.lua new file mode 100644 index 0000000..d16b344 --- /dev/null +++ b/scripts/vscripts/cards/examples/card_87.lua @@ -0,0 +1,109 @@ +local ____lualib = require("lualib_bundle") +local __TS__Class = ____lualib.__TS__Class +local __TS__ClassExtends = ____lualib.__TS__ClassExtends +local __TS__Decorate = ____lualib.__TS__Decorate +local ____exports = {} +local ____CardSystem = require("cards.CardSystem") +local CardBase = ____CardSystem.CardBase +local RegisterCard = ____CardSystem.RegisterCard +local ____CardBaseModifier = require("cards.CardBaseModifier") +local CardBaseModifier = ____CardBaseModifier.CardBaseModifier +local ____dota_ts_adapter = require("lib.dota_ts_adapter") +local registerModifier = ____dota_ts_adapter.registerModifier +local ____card_slag = require("cards.card_slag") +local SLAG_CARD_ID = ____card_slag.SLAG_CARD_ID +local CARD_ID = 87 +____exports.card_87 = __TS__Class() +local card_87 = ____exports.card_87 +card_87.name = "card_87" +card_87.____file_path = "scripts/vscripts/cards/examples/card_87.lua" +__TS__ClassExtends(card_87, CardBase) +function card_87.prototype.GetModifierName(self) + return "modifier_card_87" +end +card_87 = __TS__Decorate(card_87, card_87, {RegisterCard}, {kind = "class", name = "card_87"}) +____exports.card_87 = card_87 +____exports.modifier_card_87 = __TS__Class() +local modifier_card_87 = ____exports.modifier_card_87 +modifier_card_87.name = "modifier_card_87" +modifier_card_87.____file_path = "scripts/vscripts/cards/examples/card_87.lua" +__TS__ClassExtends(modifier_card_87, CardBaseModifier) +function modifier_card_87.prototype.____constructor(self, ...) + CardBaseModifier.prototype.____constructor(self, ...) + self.cachedSlagCount = -1 + self.cachedPct = 0 +end +function modifier_card_87.prototype.OnCustomCreated(self, _params) + if not IsServer() then + return + end + self:refreshFromPool(true) + self:StartIntervalThink(0.25) +end +function modifier_card_87.prototype.OnCustomRefresh(self, _params) + if not IsServer() then + return + end + self:refreshFromPool(true) +end +function modifier_card_87.prototype.OnIntervalThink(self) + if not IsServer() then + return + end + self:refreshFromPool(false) +end +function modifier_card_87.prototype.DeclareFunctions(self) + return {MODIFIER_PROPERTY_STATS_STRENGTH_BONUS, MODIFIER_PROPERTY_STATS_AGILITY_BONUS, MODIFIER_PROPERTY_STATS_INTELLECT_BONUS, MODIFIER_PROPERTY_TOOLTIP} +end +function modifier_card_87.prototype.OnTooltip(self) + return self.cachedPct * self:getCardCopies() +end +function modifier_card_87.prototype.GetModifierBonusStats_Strength(self) + local hero = self:GetParent() + if not hero or not IsValidEntity(hero) then + return 0 + end + return (hero:GetStrength() or 0) * (self.cachedPct * self:getCardCopies() / 100) +end +function modifier_card_87.prototype.GetModifierBonusStats_Agility(self) + local hero = self:GetParent() + if not hero or not IsValidEntity(hero) then + return 0 + end + return (hero:GetAgility() or 0) * (self.cachedPct * self:getCardCopies() / 100) +end +function modifier_card_87.prototype.GetModifierBonusStats_Intellect(self) + local hero = self:GetParent() + if not hero or not IsValidEntity(hero) then + return 0 + end + return (hero:GetIntellect(true) or 0) * (self.cachedPct * self:getCardCopies() / 100) +end +function modifier_card_87.prototype.refreshFromPool(self, force) + local hero = self:GetParent() + if not hero or not IsValidEntity(hero) or not hero:IsRealHero() then + return + end + local ____opt_0 = hero:GetPlayerOwner() + local cardSystem = ____opt_0 and ____opt_0.cardSystem + if not cardSystem then + return + end + local slagCount = cardSystem:GetPoolCardWeightSum(SLAG_CARD_ID) + if not force and slagCount == self.cachedSlagCount then + return + end + self.cachedSlagCount = slagCount + local pctPerSlag = self:getCardValue("attr_pct_per_slag", 2.5, CARD_ID) + self.cachedPct = slagCount * pctPerSlag + self:SendBuffRefreshToClients() + hero:CalculateStatBonus(true) +end +modifier_card_87 = __TS__Decorate( + modifier_card_87, + modifier_card_87, + {registerModifier(nil)}, + {kind = "class", name = "modifier_card_87"} +) +____exports.modifier_card_87 = modifier_card_87 +return ____exports diff --git a/scripts/vscripts/cards/examples/card_88.lua b/scripts/vscripts/cards/examples/card_88.lua new file mode 100644 index 0000000..98b8f23 --- /dev/null +++ b/scripts/vscripts/cards/examples/card_88.lua @@ -0,0 +1,38 @@ +local ____lualib = require("lualib_bundle") +local __TS__Class = ____lualib.__TS__Class +local __TS__ClassExtends = ____lualib.__TS__ClassExtends +local __TS__Decorate = ____lualib.__TS__Decorate +local ____exports = {} +local ____CardSystem = require("cards.CardSystem") +local CardBase = ____CardSystem.CardBase +local RegisterCard = ____CardSystem.RegisterCard +local ____CardBaseModifier = require("cards.CardBaseModifier") +local CardBaseModifier = ____CardBaseModifier.CardBaseModifier +local ____dota_ts_adapter = require("lib.dota_ts_adapter") +local registerModifier = ____dota_ts_adapter.registerModifier +____exports.card_88 = __TS__Class() +local card_88 = ____exports.card_88 +card_88.name = "card_88" +card_88.____file_path = "scripts/vscripts/cards/examples/card_88.lua" +__TS__ClassExtends(card_88, CardBase) +function card_88.prototype.GetModifierName(self) + return "modifier_card_88" +end +card_88 = __TS__Decorate(card_88, card_88, {RegisterCard}, {kind = "class", name = "card_88"}) +____exports.card_88 = card_88 +____exports.modifier_card_88 = __TS__Class() +local modifier_card_88 = ____exports.modifier_card_88 +modifier_card_88.name = "modifier_card_88" +modifier_card_88.____file_path = "scripts/vscripts/cards/examples/card_88.lua" +__TS__ClassExtends(modifier_card_88, CardBaseModifier) +function modifier_card_88.prototype.IsHidden(self) + return true +end +modifier_card_88 = __TS__Decorate( + modifier_card_88, + modifier_card_88, + {registerModifier(nil)}, + {kind = "class", name = "modifier_card_88"} +) +____exports.modifier_card_88 = modifier_card_88 +return ____exports diff --git a/scripts/vscripts/cards/examples/card_9.lua b/scripts/vscripts/cards/examples/card_9.lua new file mode 100644 index 0000000..f359067 --- /dev/null +++ b/scripts/vscripts/cards/examples/card_9.lua @@ -0,0 +1,288 @@ +local ____lualib = require("lualib_bundle") +local __TS__Class = ____lualib.__TS__Class +local __TS__ClassExtends = ____lualib.__TS__ClassExtends +local __TS__Decorate = ____lualib.__TS__Decorate +local __TS__ArraySplice = ____lualib.__TS__ArraySplice +local __TS__Number = ____lualib.__TS__Number +local ____exports = {} +local ____CardSystem = require("cards.CardSystem") +local CardBase = ____CardSystem.CardBase +local RegisterCard = ____CardSystem.RegisterCard +local ____CardBaseModifier = require("cards.CardBaseModifier") +local CardBaseModifier = ____CardBaseModifier.CardBaseModifier +local ____dota_ts_adapter = require("lib.dota_ts_adapter") +local registerModifier = ____dota_ts_adapter.registerModifier +local ____modifier_card_cursed = require("cards.modifier_card_cursed") +local addCursedStack = ____modifier_card_cursed.addCursedStack +local removeCursedStack = ____modifier_card_cursed.removeCursedStack +local ____modifier_card_greed = require("cards.modifier_card_greed") +local updateGreedForHero = ____modifier_card_greed.updateGreedForHero +local MAIN_INVENTORY_SLOT_COUNT = 6 +____exports.card_9 = __TS__Class() +local card_9 = ____exports.card_9 +card_9.name = "card_9" +card_9.____file_path = "scripts/vscripts/cards/examples/card_9.lua" +__TS__ClassExtends(card_9, CardBase) +function card_9.prototype.GetModifierName(self) + return "modifier_card_9" +end +card_9 = __TS__Decorate(card_9, card_9, {RegisterCard}, {kind = "class", name = "card_9"}) +____exports.card_9 = card_9 +____exports.modifier_card_9 = __TS__Class() +local modifier_card_9 = ____exports.modifier_card_9 +modifier_card_9.name = "modifier_card_9" +modifier_card_9.____file_path = "scripts/vscripts/cards/examples/card_9.lua" +__TS__ClassExtends(modifier_card_9, CardBaseModifier) +function modifier_card_9.prototype.____constructor(self, ...) + CardBaseModifier.prototype.____constructor(self, ...) + self.appliedCursedStacks = 0 +end +function modifier_card_9.prototype.IsHidden(self) + return false +end +function modifier_card_9.prototype.IsDebuff(self) + return true +end +function modifier_card_9.prototype.OnCustomCreated(self, params) + if not IsServer() then + return + end + local hero = self:GetParent() + if not hero or not IsValidEntity(hero) or not hero:IsRealHero() then + return + end + self:syncCursedStacks(hero) + local ____temp_0 + if hero:GetPlayerOwnerID() >= 0 then + ____temp_0 = PlayerResource:GetPlayer(hero:GetPlayerOwnerID()) + else + ____temp_0 = nil + end + local player = ____temp_0 + updateGreedForHero(nil, hero, player and player.cardSystem) +end +function modifier_card_9.prototype.OnCustomRefresh(self, _params) + if not IsServer() then + return + end + local hero = self:GetParent() + if not hero or not IsValidEntity(hero) or not hero:IsRealHero() then + return + end + self:syncCursedStacks(hero) + local ____temp_3 + if hero:GetPlayerOwnerID() >= 0 then + ____temp_3 = PlayerResource:GetPlayer(hero:GetPlayerOwnerID()) + else + ____temp_3 = nil + end + local player = ____temp_3 + updateGreedForHero(nil, hero, player and player.cardSystem) +end +function modifier_card_9.prototype.OnDestroy(self) + if not IsServer() then + return + end + local hero = self:GetParent() + if hero and IsValidEntity(hero) and hero:IsRealHero() then + local ____temp_6 + if hero:GetPlayerOwnerID() >= 0 then + ____temp_6 = PlayerResource:GetPlayer(hero:GetPlayerOwnerID()) + else + ____temp_6 = nil + end + local player = ____temp_6 + updateGreedForHero(nil, hero, player and player.cardSystem) + end + if not hero or not IsValidEntity(hero) then + return + end + while self.appliedCursedStacks > 0 do + removeCursedStack(nil, hero) + self.appliedCursedStacks = self.appliedCursedStacks - 1 + end +end +function modifier_card_9.prototype.DeclareFunctions(self) + return {MODIFIER_EVENT_ON_DEATH} +end +function modifier_card_9.prototype.OnDeath(self, event) + if not IsServer() then + return + end + local hero = self:GetParent() + if not hero or not IsValidEntity(hero) or not hero:IsRealHero() then + return + end + if event.attacker == hero and event.unit ~= hero then + local bonusGold = self:getKillGoldBonus(event) + if bonusGold > 0 then + local playerId = hero:GetPlayerOwnerID() + if playerId ~= nil and playerId ~= nil and playerId >= 0 then + PlayerResource:ModifyGold(playerId, bonusGold, true, DOTA_ModifyGold_Unspecified) + self:sendGoldOverheadInThreeBursts(hero, bonusGold) + end + end + return + end + if event.unit ~= hero then + return + end + self:stripAllGold(hero) + self:removeRandomMainInventoryItems( + hero, + self:getCardCopies() + ) + local ____temp_9 + if hero:GetPlayerOwnerID() >= 0 then + ____temp_9 = PlayerResource:GetPlayer(hero:GetPlayerOwnerID()) + else + ____temp_9 = nil + end + local player = ____temp_9 + updateGreedForHero(nil, hero, player and player.cardSystem) +end +function modifier_card_9.prototype.stripAllGold(self, hero) + local playerId = hero:GetPlayerOwnerID() + if playerId == nil or playerId == nil or playerId < 0 then + return + end + local currentGold = PlayerResource:GetGold(playerId) + if currentGold <= 0 then + return + end + PlayerResource:ModifyGold(playerId, -currentGold, false, DOTA_ModifyGold_Death) +end +function modifier_card_9.prototype.removeRandomMainInventoryItems(self, hero, count) + local items = {} + do + local slot = 0 + while slot < MAIN_INVENTORY_SLOT_COUNT do + local item = hero:GetItemInSlot(slot) + if item and IsValidEntity(item) and not item:IsNull() then + items[#items + 1] = item + end + slot = slot + 1 + end + end + if #items == 0 then + return + end + local toRemove = math.min( + #items, + math.max( + 1, + math.floor(count) + ) + ) + do + local i = 0 + while i < toRemove do + if #items <= 0 then + break + end + local idx = RandomInt(0, #items - 1) + local item = unpack(__TS__ArraySplice(items, idx, 1)) + if item and IsValidEntity(item) and not item:IsNull() then + hero:RemoveItem(item) + end + i = i + 1 + end + end +end +function modifier_card_9.prototype.getKillGoldBonus(self, event) + local victim = event.unit + local baseGold = 0 + if victim and IsValidEntity(victim) and victim ~= self:GetParent() then + local ____math_max_15 = math.max + local ____math_floor_14 = math.floor + local ____opt_12 = victim.GetGoldBounty + baseGold = ____math_max_15( + 0, + ____math_floor_14(____opt_12 and ____opt_12(victim) or 0) + ) + end + if baseGold <= 0 then + local ____math_floor_18 = math.floor + local ____event_gold_bounty_16 = event.gold_bounty + if ____event_gold_bounty_16 == nil then + ____event_gold_bounty_16 = event.gold + end + local ____event_gold_bounty_16_17 = ____event_gold_bounty_16 + if ____event_gold_bounty_16_17 == nil then + ____event_gold_bounty_16_17 = 0 + end + local eventGold = ____math_floor_18(__TS__Number(____event_gold_bounty_16_17)) + baseGold = math.max(0, eventGold) + end + if baseGold <= 0 then + return 0 + end + local bonusPct = self:getScaledCardValue("kill_gold_bonus_pct", 100, 9) + return math.max( + 0, + math.floor(baseGold * bonusPct / 100) + ) +end +function modifier_card_9.prototype.syncCursedStacks(self, hero) + local desiredStacks = self:getCardCopies() + while self.appliedCursedStacks < desiredStacks do + addCursedStack(nil, hero) + self.appliedCursedStacks = self.appliedCursedStacks + 1 + end + while self.appliedCursedStacks > desiredStacks do + removeCursedStack(nil, hero) + self.appliedCursedStacks = self.appliedCursedStacks - 1 + end +end +function modifier_card_9.prototype.sendGoldOverheadInThreeBursts(self, hero, totalGold) + local total = math.max( + 0, + math.floor(totalGold) + ) + if total <= 0 then + return + end + local chunkA = math.floor(total / 3) + local chunkB = math.floor(total / 3) + local chunkC = total - chunkA - chunkB + local chunks = {chunkA, chunkB, chunkC} + local delays = {0, 0.1, 0.2} + local owner = hero:GetPlayerOwner() + do + local i = 0 + while i < #chunks do + do + local chunkGold = chunks[i + 1] + if chunkGold <= 0 then + goto __continue43 + end + Timers:CreateTimer( + delays[i + 1], + function() + if not hero or not IsValidEntity(hero) or hero:IsNull() then + return nil + end + SendOverheadEventMessage( + nil, + OVERHEAD_ALERT_GOLD, + hero, + chunkGold, + owner + ) + return nil + end + ) + end + ::__continue43:: + i = i + 1 + end + end +end +modifier_card_9 = __TS__Decorate( + modifier_card_9, + modifier_card_9, + {registerModifier(nil)}, + {kind = "class", name = "modifier_card_9"} +) +____exports.modifier_card_9 = modifier_card_9 +return ____exports diff --git a/scripts/vscripts/cards/examples/card_new.lua b/scripts/vscripts/cards/examples/card_new.lua new file mode 100644 index 0000000..963b302 --- /dev/null +++ b/scripts/vscripts/cards/examples/card_new.lua @@ -0,0 +1,56 @@ +local ____lualib = require("lualib_bundle") +local __TS__Class = ____lualib.__TS__Class +local __TS__ClassExtends = ____lualib.__TS__ClassExtends +local __TS__Decorate = ____lualib.__TS__Decorate +local ____exports = {} +local ____CardSystem = require("cards.CardSystem") +local AddCardToPlayerPool = ____CardSystem.AddCardToPlayerPool +local CardBase = ____CardSystem.CardBase +local RegisterCard = ____CardSystem.RegisterCard +local ____CardBaseModifier = require("cards.CardBaseModifier") +local CardBaseModifier = ____CardBaseModifier.CardBaseModifier +local ____dota_ts_adapter = require("lib.dota_ts_adapter") +local registerModifier = ____dota_ts_adapter.registerModifier +local ____card_data = require("cards.card_data") +local CARD_DATABASE = ____card_data.CARD_DATABASE +____exports.card_4 = __TS__Class() +local card_4 = ____exports.card_4 +card_4.name = "card_4" +card_4.____file_path = "scripts/vscripts/cards/examples/card_new.lua" +__TS__ClassExtends(card_4, CardBase) +function card_4.prototype.GetModifierName(self) + return "modifier_card_4" +end +card_4 = __TS__Decorate(card_4, card_4, {RegisterCard}, {kind = "class", name = "card_4"}) +____exports.card_4 = card_4 +____exports.modifier_card_4 = __TS__Class() +local modifier_card_4 = ____exports.modifier_card_4 +modifier_card_4.name = "modifier_card_4" +modifier_card_4.____file_path = "scripts/vscripts/cards/examples/card_new.lua" +__TS__ClassExtends(modifier_card_4, CardBaseModifier) +function modifier_card_4.prototype.OnCreated(self, params) + self:Destroy() +end +function modifier_card_4.prototype.OnDestroy(self) + if IsClient() then + return + end + local cardData = CARD_DATABASE[4] + local player = self:GetParent():GetPlayerOwner() + local ____ = cardData.values.damage_pct + AddCardToPlayerPool( + nil, + player:GetPlayerID(), + 3, + 1, + 10 + ) +end +modifier_card_4 = __TS__Decorate( + modifier_card_4, + modifier_card_4, + {registerModifier(nil)}, + {kind = "class", name = "modifier_card_4"} +) +____exports.modifier_card_4 = modifier_card_4 +return ____exports diff --git a/scripts/vscripts/cards/examples/guaranteed_cards_examples.lua b/scripts/vscripts/cards/examples/guaranteed_cards_examples.lua new file mode 100644 index 0000000..1b09262 --- /dev/null +++ b/scripts/vscripts/cards/examples/guaranteed_cards_examples.lua @@ -0,0 +1,93 @@ +--[[ Generated with https://github.com/TypeScriptToLua/TypeScriptToLua ]] +local ____exports = {} +local ____CardSystem = require("cards.CardSystem") +local AddPlayerGuaranteedQualityRerolls = ____CardSystem.AddPlayerGuaranteedQualityRerolls +local CardQuality = ____CardSystem.CardQuality +--- Пример: добавить 3 рерола с 1 легендарной картой в каждом (пропускать если нет карт) +function ____exports.AddLegendaryRerolls(self, playerId) + AddPlayerGuaranteedQualityRerolls( + nil, + playerId, + CardQuality.LEGENDARY, + 3, + 1, + "skip" + ) +end +--- Пример: добавить 5 реролов с 2 эпическими картами в каждом (заменять на следующее качество) +function ____exports.AddEpicRerolls(self, playerId) + AddPlayerGuaranteedQualityRerolls( + nil, + playerId, + CardQuality.EPIC, + 5, + 2, + "replace" + ) +end +--- Пример: добавить 2 рерола с 1 редкой картой в каждом (откладывать если нет карт) +function ____exports.AddRareRerolls(self, playerId) + AddPlayerGuaranteedQualityRerolls( + nil, + playerId, + CardQuality.RARE, + 2, + 1, + "delay" + ) +end +--- Пример: демонстрация разных вариантов поведения при отсутствии карт +function ____exports.DemonstrateFallbackBehaviors(self, playerId) + AddPlayerGuaranteedQualityRerolls( + nil, + playerId, + CardQuality.LEGENDARY, + 2, + 1, + "skip" + ) + AddPlayerGuaranteedQualityRerolls( + nil, + playerId, + CardQuality.LEGENDARY, + 2, + 1, + "replace" + ) + AddPlayerGuaranteedQualityRerolls( + nil, + playerId, + CardQuality.EPIC, + 3, + 2, + "delay" + ) +end +--- Пример: тестирование случая когда у игрока нет карт нужного качества +function ____exports.TestMissingCardsScenario(self, playerId) + AddPlayerGuaranteedQualityRerolls( + nil, + playerId, + CardQuality.LEGENDARY, + 1, + 3, + "skip" + ) + AddPlayerGuaranteedQualityRerolls( + nil, + playerId, + CardQuality.LEGENDARY, + 1, + 3, + "replace" + ) + AddPlayerGuaranteedQualityRerolls( + nil, + playerId, + CardQuality.LEGENDARY, + 1, + 3, + "delay" + ) +end +return ____exports diff --git a/scripts/vscripts/cards/examples/quality_filter_examples.lua b/scripts/vscripts/cards/examples/quality_filter_examples.lua new file mode 100644 index 0000000..543798e --- /dev/null +++ b/scripts/vscripts/cards/examples/quality_filter_examples.lua @@ -0,0 +1,105 @@ +local ____lualib = require("lualib_bundle") +local __TS__ArrayMap = ____lualib.__TS__ArrayMap +local ____exports = {} +local ____CardSystem = require("cards.CardSystem") +local ShowCardSelectionToPlayer = ____CardSystem.ShowCardSelectionToPlayer +local SetPlayerRerollQualityFilter = ____CardSystem.SetPlayerRerollQualityFilter +local GetPlayerRerollQualityFilter = ____CardSystem.GetPlayerRerollQualityFilter +local CardQuality = ____CardSystem.CardQuality +--- Пример: показать только редкие и эпические карты +function ____exports.ShowRareAndEpicCards(self, playerId) + local qualityFilter = {CardQuality.RARE, CardQuality.EPIC} + ShowCardSelectionToPlayer( + nil, + playerId, + 3, + "rare_epic_selection", + qualityFilter + ) +end +--- Пример: показать только легендарные карты +function ____exports.ShowLegendaryCards(self, playerId) + local qualityFilter = {CardQuality.LEGENDARY} + ShowCardSelectionToPlayer( + nil, + playerId, + 2, + "legendary_selection", + qualityFilter + ) +end +--- Пример: установить фильтр качества для рерола +function ____exports.SetRerollToEpicOnly(self, playerId) + local qualityFilter = {CardQuality.EPIC} + SetPlayerRerollQualityFilter(nil, playerId, qualityFilter) +end +--- Пример: установить фильтр качества для рерола на редкие и легендарные +function ____exports.SetRerollToRareAndLegendary(self, playerId) + local qualityFilter = {CardQuality.RARE, CardQuality.LEGENDARY} + SetPlayerRerollQualityFilter(nil, playerId, qualityFilter) +end +--- Пример: сбросить фильтр качества для рерола +function ____exports.ClearRerollQualityFilter(self, playerId) + SetPlayerRerollQualityFilter(nil, playerId, nil) +end +--- Пример: проверить текущий фильтр качества рерола +function ____exports.CheckRerollQualityFilter(self, playerId) + local currentFilter = GetPlayerRerollQualityFilter(nil, playerId) + if currentFilter then + local qualityNames = table.concat( + __TS__ArrayMap( + currentFilter, + function(____, q) return CardQuality[q] end + ), + ", " + ) + else + end +end +--- Пример: показать карты после ночи с фильтром качества +function ____exports.ShowNightCardsWithQuality(self, playerId, minQuality) + if minQuality == nil then + minQuality = CardQuality.RARE + end + local qualityFilter = {} + do + local quality = minQuality + while quality <= CardQuality.LEGENDARY do + qualityFilter[#qualityFilter + 1] = quality + quality = quality + 1 + end + end + ShowCardSelectionToPlayer( + nil, + playerId, + 3, + "night_completion_filtered", + qualityFilter + ) +end +--- Пример: прогрессивный фильтр качества в зависимости от уровня +function ____exports.ShowProgressiveQualityCards(self, playerId, playerLevel) + local qualityFilter + if playerLevel >= 20 then + qualityFilter = {CardQuality.EPIC, CardQuality.LEGENDARY} + elseif playerLevel >= 10 then + qualityFilter = {CardQuality.RARE, CardQuality.EPIC, CardQuality.LEGENDARY} + else + qualityFilter = {CardQuality.COMMON, CardQuality.RARE, CardQuality.EPIC, CardQuality.LEGENDARY} + end + local qualityNames = table.concat( + __TS__ArrayMap( + qualityFilter, + function(____, q) return CardQuality[q] end + ), + ", " + ) + ShowCardSelectionToPlayer( + nil, + playerId, + 3, + "progressive_quality", + qualityFilter + ) +end +return ____exports diff --git a/scripts/vscripts/cards/modifier_card_56_homer.lua b/scripts/vscripts/cards/modifier_card_56_homer.lua new file mode 100644 index 0000000..0e941f2 --- /dev/null +++ b/scripts/vscripts/cards/modifier_card_56_homer.lua @@ -0,0 +1,93 @@ +local ____lualib = require("lualib_bundle") +local __TS__Class = ____lualib.__TS__Class +local __TS__ClassExtends = ____lualib.__TS__ClassExtends +local __TS__Decorate = ____lualib.__TS__Decorate +local ____exports = {} +local ____dota_ts_adapter = require("lib.dota_ts_adapter") +local BaseModifier = ____dota_ts_adapter.BaseModifier +local registerModifier = ____dota_ts_adapter.registerModifier +____exports.CARD_56_HOMER_MODIFIER_NAME = "modifier_card_56_homer" +____exports.modifier_card_56_homer = __TS__Class() +local modifier_card_56_homer = ____exports.modifier_card_56_homer +modifier_card_56_homer.name = "modifier_card_56_homer" +modifier_card_56_homer.____file_path = "scripts/vscripts/cards/modifier_card_56_homer.lua" +__TS__ClassExtends(modifier_card_56_homer, BaseModifier) +function modifier_card_56_homer.prototype.____constructor(self, ...) + BaseModifier.prototype.____constructor(self, ...) + self.reflectPct = 15 +end +function modifier_card_56_homer.prototype.OnCreated(self, params) + if params and params.reflect_pct ~= nil then + self.reflectPct = params.reflect_pct + end +end +function modifier_card_56_homer.prototype.IsHidden(self) + return true +end +function modifier_card_56_homer.prototype.IsPurgable(self) + return false +end +function modifier_card_56_homer.prototype.RemoveOnDeath(self) + return false +end +function modifier_card_56_homer.prototype.GetAttributes(self) + return MODIFIER_ATTRIBUTE_MULTIPLE +end +function modifier_card_56_homer.prototype.DeclareFunctions(self) + return {MODIFIER_EVENT_ON_TAKEDAMAGE} +end +function modifier_card_56_homer.prototype.OnTakeDamage(self, event) + if not IsServer() then + return + end + local victim = event.unit + if not victim or not IsValidEntity(victim) or victim:IsNull() then + return + end + if victim ~= self:GetParent() then + return + end + if victim:GetUnitName() ~= "npc_homer" then + return + end + if not victim:IsAlive() then + return + end + local attacker = event.attacker + if not attacker or not IsValidEntity(attacker) or attacker:IsNull() then + return + end + if not attacker:IsAlive() then + return + end + if attacker:GetTeamNumber() == victim:GetTeamNumber() then + return + end + if not attacker:IsCreep() then + return + end + local damage = event.damage + if damage <= 0 or self.reflectPct <= 0 then + return + end + local reflectedDamage = damage * (self.reflectPct / 100) + if reflectedDamage <= 0 then + return + end + ApplyDamage({ + victim = attacker, + attacker = victim, + damage = reflectedDamage, + damage_type = event.damage_type, + ability = nil, + damage_flags = DOTA_DAMAGE_FLAG_REFLECTION + DOTA_DAMAGE_FLAG_NO_SPELL_AMPLIFICATION + }) +end +modifier_card_56_homer = __TS__Decorate( + modifier_card_56_homer, + modifier_card_56_homer, + {registerModifier(nil)}, + {kind = "class", name = "modifier_card_56_homer"} +) +____exports.modifier_card_56_homer = modifier_card_56_homer +return ____exports diff --git a/scripts/vscripts/cards/modifier_card_cursed.lua b/scripts/vscripts/cards/modifier_card_cursed.lua new file mode 100644 index 0000000..c6e581e --- /dev/null +++ b/scripts/vscripts/cards/modifier_card_cursed.lua @@ -0,0 +1,126 @@ +local ____lualib = require("lualib_bundle") +local __TS__Class = ____lualib.__TS__Class +local __TS__ClassExtends = ____lualib.__TS__ClassExtends +local __TS__Decorate = ____lualib.__TS__Decorate +local ____exports = {} +local ____card_catalog = require("card_catalog") +local FROSTMOURNE_SHARD_CARD_IDS = ____card_catalog.FROSTMOURNE_SHARD_CARD_IDS +local ____dota_ts_adapter = require("lib.dota_ts_adapter") +local BaseModifier = ____dota_ts_adapter.BaseModifier +local registerModifier = ____dota_ts_adapter.registerModifier +____exports.CURSED_MODIFIER_NAME = "modifier_card_cursed" +--- Карта 80: пакт выполнен — проклятие не усиливает входящий урон. +____exports.CURSED_PACT_COMPLETE_MODIFIER = "modifier_card_80_pact_complete" +--- ID карт для «Трёх долгов» (карта 68): проклятые карты и осколки Фростморна (81–83), если они в пуле колоды. +-- Синхронизируй при добавлении новых проклятых карт. +____exports.CURSED_POOL_CARD_IDS = { + 7, + 8, + 9, + 17, + 30, + 31, + 52, + 54, + 68, + 69, + unpack(FROSTMOURNE_SHARD_CARD_IDS) +} +function ____exports.isCursedPoolCardId(self, cardId) + for ____, id in ipairs(____exports.CURSED_POOL_CARD_IDS) do + if id == cardId then + return true + end + end + return false +end +--- Положительный % к входящему урону за один стак проклятия (суммируется со стаками). +local INCOMING_DAMAGE_PCT_PER_CURSE_STACK = 25 +____exports.modifier_card_cursed = __TS__Class() +local modifier_card_cursed = ____exports.modifier_card_cursed +modifier_card_cursed.name = "modifier_card_cursed" +modifier_card_cursed.____file_path = "scripts/vscripts/cards/modifier_card_cursed.lua" +__TS__ClassExtends(modifier_card_cursed, BaseModifier) +function modifier_card_cursed.prototype.DeclareFunctions(self) + return {MODIFIER_PROPERTY_TOOLTIP, MODIFIER_PROPERTY_INCOMING_DAMAGE_PERCENTAGE} +end +function modifier_card_cursed.prototype.GetModifierIncomingDamage_Percentage(self) + if not IsServer() then + return 0 + end + local parent = self:GetParent() + if parent and IsValidEntity(parent) and parent:HasModifier(____exports.CURSED_PACT_COMPLETE_MODIFIER) then + return 0 + end + return self:GetStackCount() * INCOMING_DAMAGE_PCT_PER_CURSE_STACK +end +function modifier_card_cursed.prototype.GetTooltip(self) + return "dota_tooltip_modifier_modifier_card_cursed_Description" +end +function modifier_card_cursed.prototype.OnTooltip(self) + return self:GetStackCount() +end +function modifier_card_cursed.prototype.IsHidden(self) + return false +end +function modifier_card_cursed.prototype.IsDebuff(self) + return false +end +function modifier_card_cursed.prototype.IsPurgable(self) + return false +end +function modifier_card_cursed.prototype.RemoveOnDeath(self) + return false +end +function modifier_card_cursed.prototype.GetTexture(self) + return "doom_bringer_doom" +end +modifier_card_cursed = __TS__Decorate( + modifier_card_cursed, + modifier_card_cursed, + {registerModifier(nil)}, + {kind = "class", name = "modifier_card_cursed"} +) +____exports.modifier_card_cursed = modifier_card_cursed +function ____exports.addCursedStack(self, hero) + if not IsServer() or not hero or not IsValidEntity(hero) then + return + end + local modifier = hero:FindModifierByName(____exports.CURSED_MODIFIER_NAME) + if not modifier then + modifier = hero:AddNewModifier( + hero, + getModifierSourceAbility(nil, hero), + ____exports.CURSED_MODIFIER_NAME, + {} + ) + end + if not modifier then + return + end + modifier:SetStackCount(modifier:GetStackCount() + 1) + hero:CalculateStatBonus(true) +end +function ____exports.removeCursedStack(self, hero) + if not IsServer() or not hero or not IsValidEntity(hero) then + return + end + local modifier = hero:FindModifierByName(____exports.CURSED_MODIFIER_NAME) + if not modifier then + return + end + local nextCount = modifier:GetStackCount() - 1 + if nextCount <= 0 then + modifier:Destroy() + return + end + modifier:SetStackCount(nextCount) +end +function ____exports.getCursedStackCount(self, hero) + if not hero or not IsValidEntity(hero) then + return 0 + end + local modifier = hero:FindModifierByName(____exports.CURSED_MODIFIER_NAME) + return modifier and modifier:GetStackCount() or 0 +end +return ____exports diff --git a/scripts/vscripts/cards/modifier_card_greed.lua b/scripts/vscripts/cards/modifier_card_greed.lua new file mode 100644 index 0000000..a9114ec --- /dev/null +++ b/scripts/vscripts/cards/modifier_card_greed.lua @@ -0,0 +1,339 @@ +local ____lualib = require("lualib_bundle") +local __TS__Number = ____lualib.__TS__Number +local __TS__Class = ____lualib.__TS__Class +local __TS__ClassExtends = ____lualib.__TS__ClassExtends +local __TS__NumberIsFinite = ____lualib.__TS__NumberIsFinite +local __TS__Decorate = ____lualib.__TS__Decorate +local ____exports = {} +local ____dota_ts_adapter = require("lib.dota_ts_adapter") +local BaseModifier = ____dota_ts_adapter.BaseModifier +local registerModifier = ____dota_ts_adapter.registerModifier +____exports.GREED_MODIFIER_NAME = "modifier_card_greed" +--- Нетворса за +1 к каждому атрибуту (умножается на стаки алчности — число карт с алчностью на иконке). +____exports.GREED_NET_WORTH_PER_STACK = 250 +--- Карты с общей алчностью (включая card_9 — проклятие алчного мидаса). +____exports.GREED_POOL_CARD_IDS = { + 9, + 20, + 23, + 24, + 51 +} +local HIDDEN_GREED_CARD_IDS = {} +local hiddenGreedCardsTaken = {} +function ____exports.isGreedPoolCardId(self, cardId) + local normalized = math.floor(__TS__Number(cardId)) + for ____, poolId in ipairs(____exports.GREED_POOL_CARD_IDS) do + if poolId == normalized then + return true + end + end + return false +end +local function isHiddenGreedCardId(self, cardId) + local normalized = math.floor(__TS__Number(cardId)) + for ____, hiddenId in ipairs(HIDDEN_GREED_CARD_IDS) do + if hiddenId == normalized then + return true + end + end + return false +end +local function resolveCardSystem(self, playerId, cardSystemRef) + local ____cardSystemRef_2 = cardSystemRef + if ____cardSystemRef_2 == nil then + local ____opt_0 = PlayerResource:GetPlayer(playerId) + ____cardSystemRef_2 = ____opt_0 and ____opt_0.cardSystem + end + return ____cardSystemRef_2 +end +--- Сколько карт с алчностью активно — число на иконке баффа (1, 2, 3…). +local function countActiveGreedCards(self, playerId, hero, cardSystemRef) + local total = math.max( + 0, + math.floor(hiddenGreedCardsTaken[playerId] or 0) + ) + local cardSystem = resolveCardSystem(nil, playerId, cardSystemRef) + if cardSystem then + for ____, cardId in ipairs(____exports.GREED_POOL_CARD_IDS) do + do + if isHiddenGreedCardId(nil, cardId) then + goto __continue13 + end + total = total + cardSystem:GetActiveCardCopies(cardId) + end + ::__continue13:: + end + return total + end + for ____, cardId in ipairs(____exports.GREED_POOL_CARD_IDS) do + do + if isHiddenGreedCardId(nil, cardId) then + goto __continue16 + end + local modName = "modifier_card_" .. tostring(cardId) + do + local i = 0 + while i < hero:GetModifierCount() do + if hero:GetModifierNameByIndex(i) == modName then + total = total + 1 + end + i = i + 1 + end + end + end + ::__continue16:: + end + return total +end +--- Золото + предметы (для бонуса к атрибуту). +function ____exports.getHeroNetWorthForGreed(self, hero, playerId) + local playerGold = math.max( + 0, + PlayerResource:GetGold(playerId) + ) + local heroGold = math.max( + 0, + hero:GetGold() + ) + local worth = math.max(playerGold, heroGold) + do + local slot = 0 + while slot < 16 do + local item = hero:GetItemInSlot(slot) + if item and not item:IsNull() then + worth = worth + math.max( + 0, + item:GetCost() + ) + end + slot = slot + 1 + end + end + return worth +end +--- Бонус ко всем атрибутам от нетворса (+1 за каждые GREED_NET_WORTH_PER_STACK). +function ____exports.computeGreedStatBonus(self, hero, playerId, netWorthPerStack) + if netWorthPerStack == nil then + netWorthPerStack = ____exports.GREED_NET_WORTH_PER_STACK + end + local perStack = math.max( + 1, + math.floor(netWorthPerStack) + ) + return math.max( + 0, + math.floor(____exports.getHeroNetWorthForGreed(nil, hero, playerId) / perStack) + ) +end +--- +-- @deprecated Используй computeGreedStatBonus +function ____exports.computeGreedStacks(self, hero, playerId, netWorthPerStack) + if netWorthPerStack == nil then + netWorthPerStack = ____exports.GREED_NET_WORTH_PER_STACK + end + return ____exports.computeGreedStatBonus(nil, hero, playerId, netWorthPerStack) +end +local function removeGreedModifier(self, hero) + local existing = hero:FindModifierByName(____exports.GREED_MODIFIER_NAME) + if existing and not existing:IsNull() then + existing:Destroy() + end +end +function ____exports.updateGreedForHero(self, hero, cardSystemRef) + if not IsServer() or not hero or not IsValidEntity(hero) or not hero:IsRealHero() then + return + end + local playerId = hero:GetPlayerOwnerID() + if playerId == nil or playerId == nil or playerId < 0 then + return + end + local cardStacks = countActiveGreedCards(nil, playerId, hero, cardSystemRef) + if cardStacks <= 0 then + removeGreedModifier(nil, hero) + return + end + local modifier = hero:FindModifierByName(____exports.GREED_MODIFIER_NAME) + if not modifier then + modifier = hero:AddNewModifier( + hero, + getModifierSourceAbility(nil, hero), + ____exports.GREED_MODIFIER_NAME, + {} + ) + end + if not modifier then + print("[modifier_card_greed] AddNewModifier failed — проверь LinkLuaModifier / cards_init") + return + end + modifier:refresh(cardStacks, hero, playerId) +end +function ____exports.registerHiddenGreedCard(self, hero, cardId) + if not IsServer() or not hero or not IsValidEntity(hero) or not hero:IsRealHero() then + return + end + local normalized = math.floor(__TS__Number(cardId)) + if not isHiddenGreedCardId(nil, normalized) then + return + end + local playerId = hero:GetPlayerOwnerID() + if playerId == nil or playerId == nil or playerId < 0 then + return + end + hiddenGreedCardsTaken[playerId] = math.max( + 0, + math.floor(hiddenGreedCardsTaken[playerId] or 0) + ) + 1 +end +____exports.modifier_card_greed = __TS__Class() +local modifier_card_greed = ____exports.modifier_card_greed +modifier_card_greed.name = "modifier_card_greed" +modifier_card_greed.____file_path = "scripts/vscripts/cards/modifier_card_greed.lua" +__TS__ClassExtends(modifier_card_greed, BaseModifier) +function modifier_card_greed.prototype.____constructor(self, ...) + BaseModifier.prototype.____constructor(self, ...) + self.cardStacks = 0 + self.statBonus = 0 +end +function modifier_card_greed.prototype.OnCreated(self) + if not IsServer() then + return + end + self.cardStacks = 0 + self.statBonus = 0 + self:SetHasCustomTransmitterData(true) + self:StartIntervalThink(0.25) + local hero = self:GetParent() + if hero and IsValidEntity(hero) and hero:IsRealHero() then + local playerId = hero:GetPlayerOwnerID() + if playerId ~= nil and playerId ~= nil and playerId >= 0 then + local ____opt_3 = PlayerResource:GetPlayer(playerId) + local cardSystem = ____opt_3 and ____opt_3.cardSystem + local cardStacks = countActiveGreedCards(nil, playerId, hero, cardSystem) + if cardStacks > 0 then + self:refresh(cardStacks, hero, playerId) + end + end + end +end +function modifier_card_greed.prototype.IsHidden(self) + return false +end +function modifier_card_greed.prototype.IsDebuff(self) + return false +end +function modifier_card_greed.prototype.IsPurgable(self) + return false +end +function modifier_card_greed.prototype.RemoveOnDeath(self) + return false +end +function modifier_card_greed.prototype.IsPermanent(self) + return true +end +function modifier_card_greed.prototype.GetTexture(self) + return "alchemist_goblins_greed" +end +function modifier_card_greed.prototype.GetTooltip(self) + return "dota_tooltip_modifier_modifier_card_greed_Description" +end +function modifier_card_greed.prototype.OnTooltip(self) + return self.statBonus * self:GetStackCount() +end +function modifier_card_greed.prototype.HandleCustomTransmitterData(self, data) + local ____opt_result_7 + if data ~= nil then + ____opt_result_7 = data.card_stacks + end + local cardStacksRaw = __TS__Number(____opt_result_7) + if __TS__NumberIsFinite(cardStacksRaw) then + self.cardStacks = math.max( + 0, + math.floor(cardStacksRaw) + ) + end + local ____opt_result_10 + if data ~= nil then + ____opt_result_10 = data.stat_bonus + end + local statBonusRaw = __TS__Number(____opt_result_10) + if __TS__NumberIsFinite(statBonusRaw) then + self.statBonus = math.max( + 0, + math.floor(statBonusRaw) + ) + end +end +function modifier_card_greed.prototype.AddCustomTransmitterData(self) + return {card_stacks = self.cardStacks, stat_bonus = self.statBonus} +end +function modifier_card_greed.prototype.OnIntervalThink(self) + local hero = self:GetParent() + if not hero or not IsValidEntity(hero) or not hero:IsRealHero() then + return + end + local playerId = hero:GetPlayerOwnerID() + if playerId == nil or playerId == nil or playerId < 0 then + return + end + local ____opt_11 = PlayerResource:GetPlayer(playerId) + local cardSystem = ____opt_11 and ____opt_11.cardSystem + local cardStacks = countActiveGreedCards(nil, playerId, hero, cardSystem) + if cardStacks <= 0 then + self:Destroy() + return + end + self:refresh(cardStacks, hero, playerId) +end +function modifier_card_greed.prototype.refresh(self, cardStacks, hero, playerId) + local nextCardStacks = math.max( + 0, + math.floor(cardStacks) + ) + local nextStatBonus = ____exports.computeGreedStatBonus(nil, hero, playerId) + local cardChanged = self.cardStacks ~= nextCardStacks + local statChanged = self.statBonus ~= nextStatBonus + if not cardChanged and not statChanged then + return + end + self.cardStacks = nextCardStacks + self.statBonus = nextStatBonus + self:SetStackCount(nextCardStacks) + self:SendBuffRefreshToClients() + self:ForceRefresh() + hero:CalculateStatBonus(true) +end +function modifier_card_greed.prototype.getLiveStatBonus(self) + if not IsServer() then + return self.statBonus + end + local hero = self:GetParent() + if not hero or not IsValidEntity(hero) or not hero:IsRealHero() then + return 0 + end + local playerId = hero:GetPlayerOwnerID() + if playerId == nil or playerId == nil or playerId < 0 then + return 0 + end + return ____exports.computeGreedStatBonus(nil, hero, playerId) +end +function modifier_card_greed.prototype.DeclareFunctions(self) + return {MODIFIER_PROPERTY_STATS_STRENGTH_BONUS, MODIFIER_PROPERTY_STATS_AGILITY_BONUS, MODIFIER_PROPERTY_STATS_INTELLECT_BONUS, MODIFIER_PROPERTY_TOOLTIP} +end +function modifier_card_greed.prototype.GetModifierBonusStats_Strength(self) + return self:getLiveStatBonus() * self:GetStackCount() +end +function modifier_card_greed.prototype.GetModifierBonusStats_Agility(self) + return self:getLiveStatBonus() * self:GetStackCount() +end +function modifier_card_greed.prototype.GetModifierBonusStats_Intellect(self) + return self:getLiveStatBonus() * self:GetStackCount() +end +modifier_card_greed = __TS__Decorate( + modifier_card_greed, + modifier_card_greed, + {registerModifier(nil)}, + {kind = "class", name = "modifier_card_greed"} +) +____exports.modifier_card_greed = modifier_card_greed +return ____exports diff --git a/scripts/vscripts/chat_wheel_grant.lua b/scripts/vscripts/chat_wheel_grant.lua new file mode 100644 index 0000000..05e8531 --- /dev/null +++ b/scripts/vscripts/chat_wheel_grant.lua @@ -0,0 +1,111 @@ +local ____lualib = require("lualib_bundle") +local __TS__ObjectEntries = ____lualib.__TS__ObjectEntries +local ____exports = {} +local ____player_info = require("player_info") +local PlayerInfo = ____player_info.PlayerInfo +local ____server_config = require("server_config") +local SERVER_CONFIG = ____server_config.SERVER_CONFIG +local ____api_helper = require("api_helper") +local setApiHeaders = ____api_helper.setApiHeaders +--- Ключ — sound_id (без префикса chat_wheel_sound_) +____exports.CHAT_WHEEL_SOUND_GRANT_META = { + agent_gabena = {name = "Агент Габена", sound = "agent_gabena", icon = "s2r://panorama/images/chat_wheel/chat_wheel_icon_png.vtex"}, + cat_shnapy = {name = "Шни шна...", sound = "cat_shnapy", icon = "s2r://panorama/images/chat_wheel/chat_wheel_icon_png.vtex", video = "file://{images}/custom_game/cat_shnapy.webm"}, + get_out = {name = "GET OUT", sound = "get_out", icon = "s2r://panorama/images/chat_wheel/chat_wheel_icon_png.vtex"}, + ura_pobeda = {name = "Ура победа", sound = "ura_pobeda", icon = "s2r://panorama/images/chat_wheel/chat_wheel_icon_png.vtex"}, + byd_dobr_idi = {name = "Будь добр, иди..", sound = "byd_dobr_idi", icon = "s2r://panorama/images/chat_wheel/chat_wheel_icon_png.vtex"}, + jump = {name = "Прыгай дура!", sound = "jump", icon = "s2r://panorama/images/chat_wheel/chat_wheel_icon_png.vtex", video = "file://{images}/custom_game/jump.webm"}, + oi_tak_nravitsa = {name = "Ой, так нравится", sound = "oi_tak_nravitsa", icon = "s2r://panorama/images/chat_wheel/chat_wheel_icon_png.vtex", video = "file://{images}/custom_game/oi_tak_nravitsa.webm"}, + ne_ponimaiu_karina_strimersha_slozhno_slozhno = {name = "Ничего не понимаю (Карина, «сложно-сложно»)", sound = "ne_ponimaiu_karina_strimersha_slozhno_slozhno", icon = "s2r://panorama/images/chat_wheel/chat_wheel_icon_png.vtex", video = "file://{images}/custom_game/ne_ponimaiu_karina_strimersha_slozhno_slozhno.webm"}, + kitty_flex = {name = "Китти флекс", sound = "kitty_flex", icon = "s2r://panorama/images/chat_wheel/chat_wheel_icon_png.vtex", video = "file://{images}/custom_game/kitty_flex.webm"}, + eblo_razraba = {name = "Ебло разработчика", sound = "eblo_razraba", icon = "s2r://panorama/images/chat_wheel/chat_wheel_icon_png.vtex", video = "file://{images}/custom_game/eblo_razraba.webm"}, + kuda = {name = "Куда", sound = "kuda", icon = "s2r://panorama/images/chat_wheel/chat_wheel_icon_png.vtex"}, + bruh = {name = "Bruh", sound = "bruh", icon = "s2r://panorama/images/chat_wheel/chat_wheel_icon_png.vtex"}, + shizofreniya = {name = "Шизофрения", sound = "shizofreniya", icon = "s2r://panorama/images/chat_wheel/chat_wheel_icon_png.vtex"}, + vot_eto_povorot = {name = "Вот это поворот", sound = "vot_eto_povorot", icon = "s2r://panorama/images/chat_wheel/chat_wheel_icon_png.vtex"}, + fbi_open_up = {name = "FBI open up", sound = "fbi_open_up", icon = "s2r://panorama/images/chat_wheel/chat_wheel_icon_png.vtex"}, + nepravilno_poprobuy_esche_raz = {name = "Неправильно, попробуй ещё раз", sound = "nepravilno_poprobuy_esche_raz", icon = "s2r://panorama/images/chat_wheel/chat_wheel_icon_png.vtex"}, + dobro_pozhalovat_na_server_shizofreniya = {name = "Добро пожаловать на сервер шизофрении", sound = "dobro_pozhalovat_na_server_shizofreniya", icon = "s2r://panorama/images/chat_wheel/chat_wheel_icon_png.vtex"}, + nya = {name = "Ня", sound = "nya", icon = "s2r://panorama/images/chat_wheel/chat_wheel_icon_png.vtex"}, + na_nas_napali = {name = "На нас напали", sound = "na_nas_napali", icon = "s2r://panorama/images/chat_wheel/chat_wheel_icon_png.vtex"}, + murlok = {name = "Мурлок", sound = "murlok", icon = "s2r://panorama/images/chat_wheel/chat_wheel_icon_png.vtex"}, + kak_zhit_to_a = {name = "Как жить-то а", sound = "kak_zhit_to_a", icon = "s2r://panorama/images/chat_wheel/chat_wheel_icon_png.vtex"}, + zaika = {name = "Зайка", sound = "zaika", icon = "s2r://panorama/images/chat_wheel/chat_wheel_icon_png.vtex", video = "file://{images}/custom_game/zaika.webm"} +} +--- Webm для чата, если в sounds_wheel нет поля video (бэкенд/API отрезал или старые записи). +-- Ключ — имя звукового события (поле sound в колесе / invasion_sounds). +function ____exports.getChatWheelVideoPathForSound(self, sound) + if not sound or sound == "" then + return nil + end + for ____, ____value in ipairs(__TS__ObjectEntries(____exports.CHAT_WHEEL_SOUND_GRANT_META)) do + local id = ____value[1] + local meta = ____value[2] + local key = meta.sound and meta.sound ~= "" and meta.sound or id + if key == sound or id == sound then + local v = meta.video or meta.video_path + if v and #v > 0 then + return v + end + end + end + return nil +end +local function saveSoundsWheelToServer(self, playerId, soundsWheel) + local steamId = PlayerResource:GetSteamAccountID(playerId) + if not steamId then + return + end + local request = CreateHTTPRequest( + "PUT", + ((SERVER_CONFIG.API_URL .. "/player/") .. tostring(steamId)) .. "/sounds_wheel" + ) + setApiHeaders(nil, request) + request:SetHTTPRequestRawPostBody( + "application/json", + json.encode({sounds_wheel = soundsWheel}) + ) + request:Send(function(result) + if result.StatusCode >= 200 and result.StatusCode < 300 then + print("[CHAT_WHEEL_GRANT] sounds_wheel сохранён для игрока " .. tostring(playerId)) + else + print("[CHAT_WHEEL_GRANT] Ошибка сохранения sounds_wheel: StatusCode=" .. tostring(result.StatusCode)) + end + end) +end +--- Выдать звук чат-колеса (Battle Pass / акция); валюту не списывает. +function ____exports.grantChatWheelSoundFromBattlePass(self, playerId, soundId) + local meta = ____exports.CHAT_WHEEL_SOUND_GRANT_META[soundId] + if not meta then + print(("[CHAT_WHEEL_GRANT] Нет метаданных для sound_id=" .. soundId) .. ", используем fallback") + end + local playerInfoData = PlayerInfo:GetPlayerInfo(playerId) + if not playerInfoData then + playerInfoData = {} + end + local soundsWheel = playerInfoData.sounds_wheel or ({}) + if soundsWheel[soundId] then + print((("[CHAT_WHEEL_GRANT] Звук " .. soundId) .. " уже есть у игрока ") .. tostring(playerId)) + return + end + if not playerInfoData.sounds_wheel then + playerInfoData.sounds_wheel = {} + end + local soundToSave = meta and meta.sound and meta.sound ~= "" and meta.sound or soundId + local webm = meta and meta.video or meta and meta.video_path + playerInfoData.sounds_wheel[soundId] = { + name = meta and meta.name or soundId, + type = "sound", + sellPrice = 0, + sound = soundToSave, + icon = meta and meta.icon, + video = webm, + video_path = webm + } + PlayerInfo:UpdatePlayerInfo(playerId, playerInfoData) + saveSoundsWheelToServer(nil, playerId, playerInfoData.sounds_wheel) + local storeModule = require("store_manager") + storeModule.StoreManager:getInstance():registerChatWheelSoundFromBattlePass(playerId, soundId) + print((("[CHAT_WHEEL_GRANT] Выдан звук " .. soundId) .. " игроку ") .. tostring(playerId)) +end +return ____exports diff --git a/scripts/vscripts/common/incoming_damage_reduction_math.lua b/scripts/vscripts/common/incoming_damage_reduction_math.lua new file mode 100644 index 0000000..fc92975 --- /dev/null +++ b/scripts/vscripts/common/incoming_damage_reduction_math.lua @@ -0,0 +1,23 @@ +local ____lualib = require("lualib_bundle") +local __TS__NumberIsFinite = ____lualib.__TS__NumberIsFinite +local ____exports = {} +--- Верхний предел после убывания (как в `incoming_damage_reduction_combine`). +____exports.MAX_EFFECTIVE_INCOMING_REDUCTION_FROM_LINEAR_SUM_PCT = 92 +____exports.INCOMING_DAMAGE_REDUCTION_STAT_KEY = "incoming_damage_reduction_pct" +--- «Бумажная» сумма % снижения входящего → эффективный % в бою (убывающая отдача). +-- +-- @param linearSumPositivePct сумма вкладов из KV/арсенала (+20+20+…) +function ____exports.resolveIncomingDamageReductionPctFromLinearSum(self, linearSumPositivePct) + if not __TS__NumberIsFinite(linearSumPositivePct) or linearSumPositivePct <= 0 then + return 0 + end + local s = linearSumPositivePct + local hyperbolic = 100 * s / (100 + s) + return math.min(____exports.MAX_EFFECTIVE_INCOMING_REDUCTION_FROM_LINEAR_SUM_PCT, hyperbolic) +end +--- Значение для MODIFIER_PROPERTY_INCOMING_DAMAGE_PERCENTAGE (отрицательное = меньше урона). +function ____exports.incomingDamageReductionModifierValue(self, linearSumPositivePct) + local effective = ____exports.resolveIncomingDamageReductionPctFromLinearSum(nil, linearSumPositivePct) + return effective > 0 and -effective or 0 +end +return ____exports diff --git a/scripts/vscripts/contracts/contract_backend_adapter.lua b/scripts/vscripts/contracts/contract_backend_adapter.lua new file mode 100644 index 0000000..b45e0b5 --- /dev/null +++ b/scripts/vscripts/contracts/contract_backend_adapter.lua @@ -0,0 +1,192 @@ +local ____lualib = require("lualib_bundle") +local __TS__ArrayIsArray = ____lualib.__TS__ArrayIsArray +local ____exports = {} +local ____server_config = require("server_config") +local SERVER_CONFIG = ____server_config.SERVER_CONFIG +local ____api_helper = require("api_helper") +local encodeApiBody = ____api_helper.encodeApiBody +local setApiHeaders = ____api_helper.setApiHeaders +local setApiHeadersLong = ____api_helper.setApiHeadersLong +local function API() + return SERVER_CONFIG.API_URL +end +--- MVP: верхняя граница множителя с бэка (п.19 плана). +____exports.CONTRACT_MULTIPLIER_MAX_MVP = 10 +function ____exports.requestIdDrop(self, sessionOrMatchKey, steamId, rollIndex) + return (((("drop:" .. sessionOrMatchKey) .. ":") .. steamId) .. ":") .. tostring(rollIndex) +end +function ____exports.requestIdNominate(self, sessionId, steamId, contractInstanceId) + return (((("nominate:" .. sessionId) .. ":") .. steamId) .. ":") .. contractInstanceId +end +function ____exports.requestIdVote(self, sessionId, voterSteamId, contractInstanceId) + return (((("vote:" .. sessionId) .. ":") .. voterSteamId) .. ":") .. contractInstanceId +end +function ____exports.requestIdFinalize(self, sessionId) + return "finalize:" .. sessionId +end +function ____exports.requestIdLinkMatch(self, sessionId, matchId) + return (("link-match:" .. sessionId) .. ":") .. matchId +end +local function decodeJsonBody(self, result) + if result.Body == nil or result.Body == nil or result.Body == "" then + return nil + end + do + local function ____catch() + return true, nil + end + local ____try, ____hasReturned, ____returnValue = pcall(function() + return true, {json.decode(result.Body)} + end) + if not ____try then + ____hasReturned, ____returnValue = ____catch() + end + if ____hasReturned then + return ____returnValue + end + end +end +--- П.19: валидный множитель для применения в игре, иначе undefined. +function ____exports.clampContractMultiplierFromBackend(self, raw) + if type(raw) ~= "number" then + return nil + end + local n = raw + if n ~= n then + return nil + end + if n == math.huge or n == -math.huge then + return nil + end + if n < 1 or n > ____exports.CONTRACT_MULTIPLIER_MAX_MVP then + return nil + end + return n +end +local function extractContractsArray(self, data) + if not data then + return {} + end + if __TS__ArrayIsArray(data) then + return data + end + if data.contracts and __TS__ArrayIsArray(data.contracts) then + return data.contracts + end + return {} +end +function ____exports.contractAdapterGetPlayerContracts(self, steamId, callback) + local url = (API(nil) .. "/contracts/player/") .. steamId + local req = CreateHTTPRequest("GET", url) + setApiHeaders(nil, req) + req:Send(function(result) + if result.StatusCode >= 200 and result.StatusCode < 300 then + local data = decodeJsonBody(nil, result) + callback( + nil, + extractContractsArray(nil, data), + false + ) + return + end + print((("[contract_backend_adapter] GET player contracts HTTP " .. tostring(result.StatusCode)) .. " steam=") .. steamId) + callback(nil, nil, true) + end) +end +function ____exports.contractAdapterSaveDroppedContract(self, steamId, requestId, draft, callback) + local url = API(nil) .. "/contracts/drop" + local req = CreateHTTPRequest("POST", url) + setApiHeadersLong(nil, req) + req:SetHTTPRequestRawPostBody( + "application/json", + encodeApiBody(nil, {request_id = requestId, steam_id = steamId, contract = draft}) + ) + req:Send(function(result) + if result.StatusCode < 200 or result.StatusCode >= 300 then + print((((("[contract_backend_adapter] drop HTTP " .. tostring(result.StatusCode)) .. " steam=") .. steamId) .. " body=") .. tostring(result.Body)) + callback(nil, nil, false) + return + end + local data = decodeJsonBody(nil, result) + if data and data.ok == false then + print("[contract_backend_adapter] drop ok=false steam=" .. steamId) + callback(nil, nil, false) + return + end + local c = data and data.contract + if c and c.contract_instance_id then + callback(nil, c, true) + return + end + print("[contract_backend_adapter] drop missing contract in body steam=" .. steamId) + callback(nil, nil, false) + end) +end +function ____exports.contractAdapterNominate(self, sessionId, requestId, steamId, contractInstanceId, callback) + local url = ((API(nil) .. "/contracts/session/") .. sessionId) .. "/nominate" + local req = CreateHTTPRequest("POST", url) + setApiHeaders(nil, req) + req:SetHTTPRequestRawPostBody( + "application/json", + encodeApiBody(nil, {request_id = requestId, steam_id = steamId, contract_instance_id = contractInstanceId}) + ) + req:Send(function(result) + callback(nil, result.StatusCode >= 200 and result.StatusCode < 300) + end) +end +function ____exports.contractAdapterVote(self, sessionId, requestId, voterSteamId, contractInstanceId, callback) + local url = ((API(nil) .. "/contracts/session/") .. sessionId) .. "/vote" + local req = CreateHTTPRequest("POST", url) + setApiHeaders(nil, req) + req:SetHTTPRequestRawPostBody( + "application/json", + encodeApiBody(nil, {request_id = requestId, voter_steam_id = voterSteamId, contract_instance_id = contractInstanceId}) + ) + req:Send(function(result) + callback(nil, result.StatusCode >= 200 and result.StatusCode < 300) + end) +end +function ____exports.contractAdapterFinalizeContractVoting(self, sessionId, requestId, matchId, localWinnerContractInstanceId, candidatesSnapshot, votesSnapshot, callback) + local url = ((API(nil) .. "/contracts/session/") .. sessionId) .. "/finalize" + local req = CreateHTTPRequest("POST", url) + setApiHeadersLong(nil, req) + local body = { + request_id = requestId, + session_id = sessionId, + match_id = matchId, + local_winner_contract_instance_id = localWinnerContractInstanceId, + candidates_snapshot = candidatesSnapshot, + votes_snapshot = votesSnapshot + } + req:SetHTTPRequestRawPostBody( + "application/json", + encodeApiBody(nil, body) + ) + req:Send(function(result) + if result.StatusCode < 200 or result.StatusCode >= 300 then + print((("[contract_backend_adapter] finalize HTTP " .. tostring(result.StatusCode)) .. " session=") .. sessionId) + callback(nil, nil, false) + return + end + local data = decodeJsonBody(nil, result) + if not data or type(data.ok) ~= "boolean" then + print("[contract_backend_adapter] finalize bad body session=" .. sessionId) + callback(nil, nil, false) + return + end + callback(nil, data, true) + end) +end +function ____exports.contractAdapterLinkSessionToMatch(self, sessionId, requestId, matchId, callback) + local url = ((API(nil) .. "/contracts/session/") .. sessionId) .. "/link-match" + local req = CreateHTTPRequest("POST", url) + setApiHeaders(nil, req) + req:SetHTTPRequestRawPostBody( + "application/json", + encodeApiBody(nil, {request_id = requestId, match_id = matchId}) + ) + req:Send(function(result) + callback(nil, result.StatusCode >= 200 and result.StatusCode < 300) + end) +end +return ____exports diff --git a/scripts/vscripts/contracts/contract_drop_config.lua b/scripts/vscripts/contracts/contract_drop_config.lua new file mode 100644 index 0000000..ce13e7c --- /dev/null +++ b/scripts/vscripts/contracts/contract_drop_config.lua @@ -0,0 +1,29 @@ +--[[ Generated with https://github.com/TypeScriptToLua/TypeScriptToLua ]] +local ____exports = {} +--- Веса тира после победы на Impossible. Tier 0 = без дропа. +____exports.CONTRACT_DROP_TIERS = { + {0, 15}, + {1, 55}, + {2, 20}, + {3, 7}, + {4, 3} +} +function ____exports.rollContractDropTier(self) + local sum = 0 + for ____, ____value in ipairs(____exports.CONTRACT_DROP_TIERS) do + local w = ____value[2] + sum = sum + w + end + local r = RandomInt(1, sum) + local acc = 0 + for ____, ____value in ipairs(____exports.CONTRACT_DROP_TIERS) do + local tier = ____value[1] + local w = ____value[2] + acc = acc + w + if r <= acc then + return tier + end + end + return 0 +end +return ____exports diff --git a/scripts/vscripts/contracts/contract_generator.lua b/scripts/vscripts/contracts/contract_generator.lua new file mode 100644 index 0000000..964e530 --- /dev/null +++ b/scripts/vscripts/contracts/contract_generator.lua @@ -0,0 +1,34 @@ +local ____lualib = require("lualib_bundle") +local __TS__NumberToFixed = ____lualib.__TS__NumberToFixed +local ____exports = {} +local function isoLikeTimestamp(self) + local ms = math.floor(GameRules:GetGameTime() * 1000) + return (("game_" .. tostring(ms)) .. "_") .. tostring(RandomInt(10000, 99999)) +end +local function buildModifiersForTier(self, tier) + local mult = 1 + tier * 0.08 + return {{modifier_id = "contract_strain", title = "Напряжение", description = "Дополнительный множатель сложности от контракта.", value = mult}} +end +--- Черновик до SaveDroppedContract; финальный id — с бэкенда. +function ____exports.generateContractDraft(self, ownerSteamId, tier, draftInstanceId) + local seed = (draftInstanceId .. "_") .. tostring(RandomInt(1, 2147483647)) + local difficulty_multiplier = 1 + tier * 0.1 + local name = tier <= 1 and "Пакт выжившего" or (tier == 2 and "Условие крови" or (tier == 3 and "Клятва бездны" or "Смертельный завет")) + local description = ((("Контракт " .. tostring(tier)) .. " тира. Дополнительно ×") .. __TS__NumberToFixed(difficulty_multiplier, 2)) .. " к масштабу врагов (поверх сложности)." + return { + contract_instance_id = draftInstanceId, + owner_steam_id = ownerSteamId, + tier = tier, + name = name, + description = description, + seed = seed, + difficulty_multiplier = difficulty_multiplier, + modifiers = buildModifiersForTier(nil, tier), + durability = 3, + is_broken = false, + source = "impossible_win_drop", + created_at = isoLikeTimestamp(nil), + application_conditions = "Активен только при подтверждённом контракте матча и сложности Impossible." + } +end +return ____exports diff --git a/scripts/vscripts/contracts/contract_match_manager.lua b/scripts/vscripts/contracts/contract_match_manager.lua new file mode 100644 index 0000000..19c4c99 --- /dev/null +++ b/scripts/vscripts/contracts/contract_match_manager.lua @@ -0,0 +1,541 @@ +local ____lualib = require("lualib_bundle") +local __TS__Class = ____lualib.__TS__Class +local Map = ____lualib.Map +local __TS__New = ____lualib.__TS__New +local __TS__ArrayMap = ____lualib.__TS__ArrayMap +local __TS__ObjectKeys = ____lualib.__TS__ObjectKeys +local __TS__ArrayFind = ____lualib.__TS__ArrayFind +local __TS__ArraySome = ____lualib.__TS__ArraySome +local __TS__ArrayFilter = ____lualib.__TS__ArrayFilter +local ____exports = {} +local ____contract_steam_compare = require("contracts.contract_steam_compare") +local compareSteamIdDecimalString = ____contract_steam_compare.compareSteamIdDecimalString +local ____contract_backend_adapter = require("contracts.contract_backend_adapter") +local clampContractMultiplierFromBackend = ____contract_backend_adapter.clampContractMultiplierFromBackend +local contractAdapterFinalizeContractVoting = ____contract_backend_adapter.contractAdapterFinalizeContractVoting +local contractAdapterGetPlayerContracts = ____contract_backend_adapter.contractAdapterGetPlayerContracts +local contractAdapterLinkSessionToMatch = ____contract_backend_adapter.contractAdapterLinkSessionToMatch +local contractAdapterNominate = ____contract_backend_adapter.contractAdapterNominate +local contractAdapterSaveDroppedContract = ____contract_backend_adapter.contractAdapterSaveDroppedContract +local contractAdapterVote = ____contract_backend_adapter.contractAdapterVote +local requestIdDrop = ____contract_backend_adapter.requestIdDrop +local requestIdFinalize = ____contract_backend_adapter.requestIdFinalize +local requestIdLinkMatch = ____contract_backend_adapter.requestIdLinkMatch +local requestIdNominate = ____contract_backend_adapter.requestIdNominate +local requestIdVote = ____contract_backend_adapter.requestIdVote +local ____contract_drop_config = require("contracts.contract_drop_config") +local rollContractDropTier = ____contract_drop_config.rollContractDropTier +local ____contract_generator = require("contracts.contract_generator") +local generateContractDraft = ____contract_generator.generateContractDraft +local NET_TABLE = "contract_match" +local NET_KEY = "state" +local FINALIZE_TIMEOUT_SEC = 10 +____exports.ContractMatchManager = __TS__Class() +local ContractMatchManager = ____exports.ContractMatchManager +ContractMatchManager.name = "ContractMatchManager" +ContractMatchManager.____file_path = "scripts/vscripts/contracts/contract_match_manager.lua" +function ContractMatchManager.prototype.____constructor(self) + self.listenersRegistered = false + self.inventoryBySteam = __TS__New(Map) + self.candidates = {} + self.votesByVoterSteam = {} + self.nominateOrderSeq = 0 + self.publicState = { + contract_session_id = "", + phase = "voting", + candidates = {}, + vote_counts = {}, + active_contract = nil, + contract_multiplier = 1 + } + self.confirmedActiveContract = nil + self.confirmedMultiplier = 1 + self.finalizeResolved = false + self.finalizeAttemptSerial = 0 + self.activeFinalizeAttempt = 0 + self.linkMatchDone = false + self.dropRollIndex = 0 +end +function ContractMatchManager.getInstance(self) + if not ____exports.ContractMatchManager.instance then + ____exports.ContractMatchManager.instance = __TS__New(____exports.ContractMatchManager) + end + return ____exports.ContractMatchManager.instance +end +function ContractMatchManager.prototype.init(self) + if self.listenersRegistered or type(CustomGameEventManager) == "nil" then + return + end + CustomGameEventManager:RegisterListener( + "invasion_contracts_request", + function(_src, data) + local pid = data.PlayerID + self:onContractsRequest(pid) + end + ) + CustomGameEventManager:RegisterListener( + "invasion_contract_nominate", + function(_src, data) + local pid = data.PlayerID + local ____tostring_1 = tostring + local ____data_contract_instance_id_0 = data.contract_instance_id + if ____data_contract_instance_id_0 == nil then + ____data_contract_instance_id_0 = "" + end + local cid = ____tostring_1(____data_contract_instance_id_0) + self:onNominate(pid, cid) + end + ) + CustomGameEventManager:RegisterListener( + "invasion_contract_vote", + function(_src, data) + local pid = data.PlayerID + local ____tostring_3 = tostring + local ____data_contract_instance_id_2 = data.contract_instance_id + if ____data_contract_instance_id_2 == nil then + ____data_contract_instance_id_2 = "" + end + local cid = ____tostring_3(____data_contract_instance_id_2) + self:onVote(pid, cid) + end + ) + self.listenersRegistered = true + self:syncNetTable() +end +function ContractMatchManager.prototype.getConfirmedContractMultiplier(self) + return self.confirmedMultiplier +end +function ContractMatchManager.prototype.getConfirmedActiveContract(self) + return self.confirmedActiveContract +end +function ContractMatchManager.prototype.tryLinkSessionToBackendMatch(self, backendMatchId) + local sid = self:getOrCreateSessionId() + local mid = backendMatchId + if not mid or self.linkMatchDone then + return + end + local rid = requestIdLinkMatch(nil, sid, mid) + contractAdapterLinkSessionToMatch( + nil, + sid, + rid, + mid, + function(____, ok) + if ok then + self.linkMatchDone = true + print((("[ContractMatchManager] link-match ok session=" .. sid) .. " match=") .. mid) + end + end + ) +end +function ContractMatchManager.prototype.beginFinalizeForDifficulty(self, leader, backendMatchId, onWorldApplyReady) + if leader ~= "impossible" then + self.confirmedActiveContract = nil + self.confirmedMultiplier = 1 + self.publicState.phase = "locked_without_contract" + self.publicState.active_contract = nil + self.publicState.contract_multiplier = 1 + self.publicState.public_error_code = nil + self:syncNetTable() + onWorldApplyReady(nil) + return + end + self.finalizeAttemptSerial = self.finalizeAttemptSerial + 1 + local attempt = self.finalizeAttemptSerial + self.activeFinalizeAttempt = attempt + self.finalizeResolved = false + self.publicState.phase = "finalizing" + self.publicState.public_error_code = nil + self:syncNetTable() + Timers:CreateTimer( + FINALIZE_TIMEOUT_SEC, + function() + if self.finalizeResolved then + return nil + end + if self.activeFinalizeAttempt ~= attempt then + return nil + end + if self.publicState.phase ~= "finalizing" then + return nil + end + print("[ContractMatchManager] finalize timeout attempt=" .. tostring(attempt)) + self:applyFinalizeFailure(attempt, "contracts_finalize_failed", onWorldApplyReady) + return nil + end + ) + local sid = self:getOrCreateSessionId() + local matchId = backendMatchId or nil + local localWinner = self:computeLocalWinnerContractId() + local candSnap = __TS__ArrayMap( + self.candidates, + function(____, c) return {contract_instance_id = c.contract_instance_id, owner_steam_id = c.owner_steam_id, nominated_at = c.nominated_at, nominated_order = c.nominated_order} end + ) + local voteSnap = {} + for ____, voter in ipairs(__TS__ObjectKeys(self.votesByVoterSteam)) do + local cid = self.votesByVoterSteam[voter] + if cid ~= nil and cid ~= "" then + voteSnap[#voteSnap + 1] = {voter_steam_id = voter, contract_instance_id = cid} + end + end + local rid = requestIdFinalize(nil, sid) + contractAdapterFinalizeContractVoting( + nil, + sid, + rid, + matchId, + localWinner, + candSnap, + voteSnap, + function(____, resp, httpOk) + if self.finalizeResolved then + print("[ContractMatchManager] finalize HTTP late ignored attempt=" .. tostring(attempt)) + return + end + if self.activeFinalizeAttempt ~= attempt then + print("[ContractMatchManager] finalize HTTP stale attempt=" .. tostring(attempt)) + return + end + if self.publicState.phase ~= "finalizing" then + print("[ContractMatchManager] finalize HTTP wrong phase") + return + end + if not httpOk or not resp or not resp.ok then + print("[ContractMatchManager] finalize HTTP/body fail attempt=" .. tostring(attempt)) + self:applyFinalizeFailure(attempt, "contracts_finalize_failed", onWorldApplyReady) + return + end + local mult = clampContractMultiplierFromBackend(nil, resp.contract_multiplier) + if mult == nil then + print("[ContractMatchManager] finalize invalid multiplier raw=" .. tostring(resp.contract_multiplier)) + self:applyFinalizeFailure(attempt, "contracts_finalize_failed", onWorldApplyReady) + return + end + self.finalizeResolved = true + self.confirmedMultiplier = mult + self.confirmedActiveContract = resp.active_contract or nil + self.publicState.active_contract = self.confirmedActiveContract + self.publicState.contract_multiplier = mult + self.publicState.phase = self.confirmedActiveContract and "locked" or "locked_without_contract" + self.publicState.public_error_code = nil + self:syncNetTable() + onWorldApplyReady(nil) + end + ) +end +function ContractMatchManager.prototype.processImpossibleVictoryDrops(self, allowPersistedDrop) + if not allowPersistedDrop then + print("[ContractMatchManager] drop skipped: match end rewards / stats blocked") + return + end + do + local i = 0 + while i < DOTA_MAX_PLAYERS do + do + local pid = i + if not PlayerResource:IsValidPlayerID(pid) or not PlayerResource:IsValidPlayer(pid) or PlayerResource:IsFakeClient(pid) then + goto __continue34 + end + local steam = tostring(PlayerResource:GetSteamAccountID(pid)) + local tier = rollContractDropTier(nil) + if tier <= 0 then + goto __continue34 + end + self.dropRollIndex = self.dropRollIndex + 1 + local draftId = (((("draft_" .. self:getOrCreateSessionId()) .. "_") .. steam) .. "_") .. tostring(self.dropRollIndex) + local draft = generateContractDraft(nil, steam, tier, draftId) + local rid = requestIdDrop( + nil, + self:getOrCreateSessionId(), + steam, + self.dropRollIndex + ) + contractAdapterSaveDroppedContract( + nil, + steam, + rid, + draft, + function(____, canonical, ok) + if not ok or not canonical then + print("[ContractMatchManager] drop save failed steam=" .. steam) + return + end + if type(CustomGameEventManager) ~= "nil" then + local pl = PlayerResource:GetPlayer(pid) + if pl then + CustomGameEventManager:Send_ServerToPlayer(pl, "invasion_contract_drop_result", {ok = true, contract = canonical}) + end + end + end + ) + end + ::__continue34:: + i = i + 1 + end + end +end +function ContractMatchManager.prototype.getOrCreateSessionId(self) + if not self.sessionId then + local t = math.floor(GameRules:GetGameTime() * 1000) + self.sessionId = (("cs_" .. tostring(t)) .. "_") .. tostring(RandomInt(100000, 999999)) + self.publicState.contract_session_id = self.sessionId + end + return self.sessionId +end +function ContractMatchManager.prototype.onContractsRequest(self, playerId) + if not PlayerResource:IsValidPlayerID(playerId) or not PlayerResource:IsValidPlayer(playerId) then + return + end + local steam = tostring(PlayerResource:GetSteamAccountID(playerId)) + contractAdapterGetPlayerContracts( + nil, + steam, + function(____, list, err) + if list == nil then + self:sendInventory(playerId, nil, "contracts_load_failed", true) + return + end + self.inventoryBySteam:set(steam, list) + self:sendInventory(playerId, list, nil, true) + end + ) +end +function ContractMatchManager.prototype.sendInventory(self, playerId, contracts, errorCode, done) + if type(CustomGameEventManager) == "nil" then + return + end + local payload = {contracts = contracts, loading_done = done} + if errorCode then + payload.error_code = errorCode + end + local pl = PlayerResource:GetPlayer(playerId) + if pl then + CustomGameEventManager:Send_ServerToPlayer(pl, "invasion_contracts_inventory", payload) + end +end +function ContractMatchManager.prototype.onNominate(self, playerId, contractInstanceId) + if self.publicState.phase ~= "voting" then + print("[ContractMatchManager] nominate rejected phase=" .. self.publicState.phase) + return + end + if not contractInstanceId or #contractInstanceId == 0 then + return + end + if not PlayerResource:IsValidPlayerID(playerId) or not PlayerResource:IsValidPlayer(playerId) then + return + end + local steam = tostring(PlayerResource:GetSteamAccountID(playerId)) + local inv = self.inventoryBySteam:get(steam) + local ____opt_4 = inv + local found = ____opt_4 and __TS__ArrayFind( + inv, + function(____, c) return c.contract_instance_id == contractInstanceId end + ) + if not found or found.is_broken or found.owner_steam_id ~= steam then + self:syncNetTableErrorFlash("contracts_invalid_contract") + return + end + local sid = self:getOrCreateSessionId() + local rid = requestIdNominate(nil, sid, steam, contractInstanceId) + contractAdapterNominate( + nil, + sid, + rid, + steam, + contractInstanceId, + function(____, ok) + if not ok then + print((("[ContractMatchManager] nominate HTTP fail steam=" .. steam) .. " id=") .. contractInstanceId) + self:syncNetTableErrorFlash("contracts_invalid_contract") + return + end + self:removeCandidateByOwner(steam) + self.nominateOrderSeq = self.nominateOrderSeq + 1 + local pub = { + contract_instance_id = contractInstanceId, + owner_steam_id = steam, + nominated_at = GameRules:GetGameTime(), + nominated_order = self.nominateOrderSeq, + name = found.name, + tier = found.tier + } + local ____self_candidates_6 = self.candidates + ____self_candidates_6[#____self_candidates_6 + 1] = pub + self.votesByVoterSteam[steam] = contractInstanceId + self:rebuildVoteCounts() + self:syncNetTable() + end + ) +end +function ContractMatchManager.prototype.onVote(self, playerId, contractInstanceId) + if self.publicState.phase ~= "voting" then + print("[ContractMatchManager] vote rejected phase=" .. self.publicState.phase) + return + end + if not contractInstanceId then + return + end + if not PlayerResource:IsValidPlayerID(playerId) or not PlayerResource:IsValidPlayer(playerId) then + return + end + local voter = tostring(PlayerResource:GetSteamAccountID(playerId)) + local exists = __TS__ArraySome( + self.candidates, + function(____, c) return c.contract_instance_id == contractInstanceId end + ) + if not exists then + self:syncNetTableErrorFlash("contracts_vote_rejected") + return + end + local sid = self:getOrCreateSessionId() + local rid = requestIdVote(nil, sid, voter, contractInstanceId) + contractAdapterVote( + nil, + sid, + rid, + voter, + contractInstanceId, + function(____, ok) + if not ok then + print("[ContractMatchManager] vote HTTP fail voter=" .. voter) + self:syncNetTableErrorFlash("contracts_vote_rejected") + return + end + self.votesByVoterSteam[voter] = contractInstanceId + self:rebuildVoteCounts() + self:syncNetTable() + end + ) +end +function ContractMatchManager.prototype.removeCandidateByOwner(self, steam) + self.candidates = __TS__ArrayFilter( + self.candidates, + function(____, c) return c.owner_steam_id ~= steam end + ) +end +function ContractMatchManager.prototype.rebuildVoteCounts(self) + local counts = {} + for ____, voter in ipairs(__TS__ObjectKeys(self.votesByVoterSteam)) do + local cid = self.votesByVoterSteam[voter] + if cid ~= nil and cid ~= "" then + counts[cid] = (counts[cid] or 0) + 1 + end + end + self.publicState.vote_counts = counts + self.publicState.candidates = self.candidates +end +function ContractMatchManager.prototype.computeLocalWinnerContractId(self) + if #self.candidates == 0 then + return nil + end + local counts = {} + for ____, voter in ipairs(__TS__ObjectKeys(self.votesByVoterSteam)) do + local cid = self.votesByVoterSteam[voter] + if cid ~= nil and cid ~= "" then + counts[cid] = (counts[cid] or 0) + 1 + end + end + local totalVotes = 0 + for ____, k in ipairs(__TS__ObjectKeys(counts)) do + totalVotes = totalVotes + (counts[k] or 0) + end + if totalVotes == 0 then + return nil + end + local bestIds = {} + local bestVotes = -1 + for ____, cid in ipairs(__TS__ObjectKeys(counts)) do + local n = counts[cid] or 0 + if n > bestVotes then + bestVotes = n + bestIds = {cid} + elseif n == bestVotes then + bestIds[#bestIds + 1] = cid + end + end + if #bestIds == 1 then + return bestIds[1] + end + return self:pickTieBreakContractId(bestIds) +end +function ContractMatchManager.prototype.pickTieBreakContractId(self, ids) + local byId = {} + for ____, c in ipairs(self.candidates) do + byId[c.contract_instance_id] = c + end + local best = ids[1] + do + local i = 1 + while i < #ids do + local cur = ids[i + 1] + if self:compareCandidatesForTie(byId[cur], byId[best], cur, best) < 0 then + best = cur + end + i = i + 1 + end + end + return best +end +function ContractMatchManager.prototype.compareCandidatesForTie(self, ca, cb, idA, idB) + if not ca or not cb then + return 0 + end + if ca.nominated_order ~= cb.nominated_order then + return ca.nominated_order < cb.nominated_order and -1 or 1 + end + local steamCmp = compareSteamIdDecimalString(nil, ca.owner_steam_id, cb.owner_steam_id) + if steamCmp ~= 0 then + return steamCmp + end + if idA < idB then + return -1 + end + if idA > idB then + return 1 + end + return 0 +end +function ContractMatchManager.prototype.applyFinalizeFailure(self, attempt, code, onWorldApplyReady) + if self.finalizeResolved then + return + end + if self.activeFinalizeAttempt ~= attempt then + return + end + if self.activeFinalizeAttempt ~= attempt then + return + end + self.finalizeResolved = true + self.confirmedActiveContract = nil + self.confirmedMultiplier = 1 + self.publicState.active_contract = nil + self.publicState.contract_multiplier = 1 + self.publicState.phase = "finalize_error" + self.publicState.public_error_code = code + self:syncNetTable() + onWorldApplyReady(nil) +end +function ContractMatchManager.prototype.syncNetTableErrorFlash(self, code) + self.publicState.public_error_code = code + self:syncNetTable() + Timers:CreateTimer( + 0.1, + function() + self.publicState.public_error_code = nil + self:syncNetTable() + return nil + end + ) +end +function ContractMatchManager.prototype.syncNetTable(self) + if type(CustomNetTables) == "nil" then + return + end + self.publicState.candidates = self.candidates + self:rebuildVoteCounts() + if not self.publicState.contract_session_id and self.sessionId then + self.publicState.contract_session_id = self.sessionId + end + CustomNetTables:SetTableValue(NET_TABLE, NET_KEY, self.publicState) +end +return ____exports diff --git a/scripts/vscripts/contracts/contract_steam_compare.lua b/scripts/vscripts/contracts/contract_steam_compare.lua new file mode 100644 index 0000000..4116f58 --- /dev/null +++ b/scripts/vscripts/contracts/contract_steam_compare.lua @@ -0,0 +1,22 @@ +--[[ Generated with https://github.com/TypeScriptToLua/TypeScriptToLua ]] +local ____exports = {} +--- Сравнение SteamID64 как decimal string (п.15 плана): без tonumber/Number на id. +-- Возвращает -1 если a < b, 0 если равны, 1 если a > b. +function ____exports.compareSteamIdDecimalString(self, a, b) + local la = #a + local lb = #b + if la < lb then + return -1 + end + if la > lb then + return 1 + end + if a < b then + return -1 + end + if a > b then + return 1 + end + return 0 +end +return ____exports diff --git a/scripts/vscripts/contracts/contract_types.lua b/scripts/vscripts/contracts/contract_types.lua new file mode 100644 index 0000000..8e2ee58 --- /dev/null +++ b/scripts/vscripts/contracts/contract_types.lua @@ -0,0 +1,3 @@ +--[[ Generated with https://github.com/TypeScriptToLua/TypeScriptToLua ]] +local ____exports = {} +return ____exports diff --git a/scripts/vscripts/contracts_manager.lua b/scripts/vscripts/contracts_manager.lua new file mode 100644 index 0000000..c154c1e --- /dev/null +++ b/scripts/vscripts/contracts_manager.lua @@ -0,0 +1,402 @@ +local ____lualib = require("lualib_bundle") +local __TS__Class = ____lualib.__TS__Class +local Map = ____lualib.Map +local __TS__New = ____lualib.__TS__New +local __TS__ArrayFind = ____lualib.__TS__ArrayFind +local __TS__ArrayFindIndex = ____lualib.__TS__ArrayFindIndex +local __TS__ArrayIsArray = ____lualib.__TS__ArrayIsArray +local __TS__ArrayMap = ____lualib.__TS__ArrayMap +local __TS__NumberToFixed = ____lualib.__TS__NumberToFixed +local __TS__Number = ____lualib.__TS__Number +local __TS__ObjectValues = ____lualib.__TS__ObjectValues +local ____exports = {} +local ____api_helper = require("api_helper") +local encodeApiBody = ____api_helper.encodeApiBody +local setApiHeaders = ____api_helper.setApiHeaders +local ____server_config = require("server_config") +local SERVER_CONFIG = ____server_config.SERVER_CONFIG +local MAX_CONTRACT_SLOTS = 4 +local MAX_CONTRACTS_PER_PLAYER = 24 +local CONTRACT_DEBUFF_POOL = { + "Герои слабее на 20%", + "Герои слабее на 35%", + "Герои теряют 30% скорости атаки", + "Герои получают +20% входящего урона", + "Крипы получают +30% скорости передвижения", + "Снижение лечения героев на 40%", + "Кулдауны способностей героев +20%" +} +local CONTRACT_TITLE_POOL = { + "Смертельный приговор", + "Пакт отчаяния", + "Кровавый кодекс", + "Ночь без надежды", + "Предел боли" +} +local function randomIntInclusive(self, min, max) + return RandomInt(min, max) +end +local function debuffsArrayToMap(self, lines) + local result = {} + do + local i = 0 + while i < #lines do + result[tostring(i)] = lines[i + 1] + i = i + 1 + end + end + return result +end +____exports.ContractsManager = __TS__Class() +local ContractsManager = ____exports.ContractsManager +ContractsManager.name = "ContractsManager" +ContractsManager.____file_path = "scripts/vscripts/contracts_manager.lua" +function ContractsManager.prototype.____constructor(self) + self.contractsByPlayer = __TS__New(Map) + self.activeSlots = {} + if not IsServer() then + return + end + CustomGameEventManager:RegisterListener( + "contracts_request_inventory", + function(_source, payload) + local playerId = payload.PlayerID + if playerId == nil or playerId < 0 then + return + end + self:loadContractsFromServer(playerId, true) + end + ) + ListenToGameEvent( + "player_connect_full", + function(event) + local playerId = event.PlayerID + if playerId == nil or playerId < 0 then + return + end + Timers:CreateTimer( + 1, + function() + self:loadContractsFromServer(playerId, true) + return nil + end + ) + end, + nil + ) +end +function ContractsManager.getInstance(self) + if not ____exports.ContractsManager.instance then + ____exports.ContractsManager.instance = __TS__New(____exports.ContractsManager) + end + return ____exports.ContractsManager.instance +end +function ContractsManager.prototype.getActiveSlots(self) + return self.activeSlots +end +function ContractsManager.prototype.clearActiveSlots(self) + self.activeSlots = {} +end +function ContractsManager.prototype.getContractForPlayer(self, playerId, contractId) + local list = self.contractsByPlayer:get(playerId) or ({}) + return __TS__ArrayFind( + list, + function(____, it) return it.id == contractId end + ) +end +function ContractsManager.prototype.proposePlayerContract(self, playerId, contractId) + local contract = self:getContractForPlayer(playerId, contractId) + if not contract then + return false + end + local sameContractIdx = __TS__ArrayFindIndex( + self.activeSlots, + function(____, it) return it.contractId == contractId end + ) + if sameContractIdx >= 0 then + self.activeSlots[sameContractIdx + 1] = { + contractId = contract.id, + ownerPlayerId = playerId, + title = contract.title, + statMultiplier = contract.statMultiplier, + rewardBonusPct = contract.rewardBonusPct, + debuffs = {unpack(contract.debuffs)} + } + return true + end + local existingByPlayerIdx = __TS__ArrayFindIndex( + self.activeSlots, + function(____, it) return it.ownerPlayerId == playerId end + ) + if existingByPlayerIdx >= 0 then + self.activeSlots[existingByPlayerIdx + 1] = { + contractId = contract.id, + ownerPlayerId = playerId, + title = contract.title, + statMultiplier = contract.statMultiplier, + rewardBonusPct = contract.rewardBonusPct, + debuffs = {unpack(contract.debuffs)} + } + return true + end + if #self.activeSlots >= MAX_CONTRACT_SLOTS then + return false + end + local ____self_activeSlots_0 = self.activeSlots + ____self_activeSlots_0[#____self_activeSlots_0 + 1] = { + contractId = contract.id, + ownerPlayerId = playerId, + title = contract.title, + statMultiplier = contract.statMultiplier, + rewardBonusPct = contract.rewardBonusPct, + debuffs = {unpack(contract.debuffs)} + } + return true +end +function ContractsManager.prototype.getContractById(self, contractId) + return __TS__ArrayFind( + self.activeSlots, + function(____, it) return it.contractId == contractId end + ) +end +function ContractsManager.prototype.shouldGrantAfterWin(self, modeType, difficultyKey) + return modeType == "contract" or difficultyKey == "impossible" +end +function ContractsManager.prototype.grantProgressiveContractsToWinners(self, nextTierBase) + do + local playerId = 0 + while playerId < DOTA_MAX_TEAM_PLAYERS do + do + local pid = playerId + if not PlayerResource:IsValidPlayerID(pid) or not PlayerResource:IsValidPlayer(pid) or PlayerResource:IsFakeClient(pid) then + goto __continue29 + end + self:grantOneContract(pid, nextTierBase) + end + ::__continue29:: + playerId = playerId + 1 + end + end +end +function ContractsManager.prototype.getHighestTierForPlayer(self, playerId) + local list = self.contractsByPlayer:get(playerId) or ({}) + local maxTier = 0 + for ____, contract in ipairs(list) do + if contract.tier > maxTier then + maxTier = contract.tier + end + end + return maxTier +end +function ContractsManager.prototype.grantOneContract(self, playerId, minTier) + local nextTier = math.max(1, minTier) + local generated = self:generateRandomContract(nextTier) + local list = self.contractsByPlayer:get(playerId) or ({}) + if #list >= MAX_CONTRACTS_PER_PLAYER then + table.remove(list, 1) + end + list[#list + 1] = generated + self.contractsByPlayer:set(playerId, list) + self:persistContractsToServer(playerId) + self:sendInventoryToPlayer(playerId) + local steamId = PlayerResource:GetSteamAccountID(playerId) + if not steamId then + return + end + local req = CreateHTTPRequest( + "POST", + ((SERVER_CONFIG.API_URL .. "/player/") .. tostring(steamId)) .. "/contracts/grant" + ) + setApiHeaders(nil, req) + req:SetHTTPRequestRawPostBody( + "application/json", + encodeApiBody( + nil, + { + contract = self:toApiContract(generated), + min_tier = minTier + } + ) + ) + req:Send(function(_res) return nil end) +end +function ContractsManager.prototype.loadContractsFromServer(self, playerId, syncToClient) + local steamId = PlayerResource:GetSteamAccountID(playerId) + if not steamId then + if syncToClient then + self:sendInventoryToPlayer(playerId) + end + return + end + local request = CreateHTTPRequest( + "GET", + ((SERVER_CONFIG.API_URL .. "/player/") .. tostring(steamId)) .. "/contracts" + ) + setApiHeaders(nil, request) + request:Send(function(response) + if response.StatusCode < 200 or response.StatusCode >= 300 then + if syncToClient then + self:sendInventoryToPlayer(playerId) + end + return + end + local raw = {json.decode(response.Body or "null")} + local arr = __TS__ArrayIsArray(raw) and raw or (__TS__ArrayIsArray(raw and raw.contracts) and (raw and raw.contracts) or ({})) + local parsed = {} + for ____, item in ipairs(arr) do + local contract = self:fromApiContract(item) + if contract then + parsed[#parsed + 1] = contract + end + end + self.contractsByPlayer:set(playerId, parsed) + if syncToClient then + self:sendInventoryToPlayer(playerId) + end + end) +end +function ContractsManager.prototype.persistContractsToServer(self, playerId) + local steamId = PlayerResource:GetSteamAccountID(playerId) + if not steamId then + return + end + local list = self.contractsByPlayer:get(playerId) or ({}) + local request = CreateHTTPRequest( + "PUT", + ((SERVER_CONFIG.API_URL .. "/player/") .. tostring(steamId)) .. "/contracts" + ) + setApiHeaders(nil, request) + request:SetHTTPRequestRawPostBody( + "application/json", + encodeApiBody( + nil, + {contracts = __TS__ArrayMap( + list, + function(____, it) return self:toApiContract(it) end + )} + ) + ) + request:Send(function(_res) return nil end) +end +function ContractsManager.prototype.sendInventoryToPlayer(self, playerId) + local player = PlayerResource:GetPlayer(playerId) + if not player then + return + end + local contracts = self.contractsByPlayer:get(playerId) or ({}) + local payload = {} + for ____, contract in ipairs(contracts) do + payload[contract.id] = { + id = contract.id, + tier = contract.tier, + title = contract.title, + stat_multiplier = contract.statMultiplier, + reward_bonus_pct = contract.rewardBonusPct, + debuffs = debuffsArrayToMap(nil, contract.debuffs) + } + end + CustomGameEventManager:Send_ServerToPlayer(player, "contracts_inventory_update", {contracts = payload}) +end +function ContractsManager.prototype.generateRandomContract(self, tier) + local id = "contract_" .. DoUniqueString("cid") + local titleBase = CONTRACT_TITLE_POOL[randomIntInclusive(nil, 0, #CONTRACT_TITLE_POOL - 1) + 1] + local statMultiplier = __TS__Number(__TS__NumberToFixed( + 4 + tier * 0.5 + RandomFloat(0, 1.2), + 2 + )) + local rewardBonusPct = randomIntInclusive(nil, 10 + tier * 2, 30 + tier * 4) + local debuffCount = randomIntInclusive(nil, 1, 3) + local picked = {} + do + local i = 0 + while i < debuffCount do + picked[#picked + 1] = CONTRACT_DEBUFF_POOL[randomIntInclusive(nil, 0, #CONTRACT_DEBUFF_POOL - 1) + 1] + i = i + 1 + end + end + return { + id = id, + tier = tier, + title = (titleBase .. " T") .. tostring(tier), + statMultiplier = statMultiplier, + rewardBonusPct = rewardBonusPct, + debuffs = picked + } +end +function ContractsManager.prototype.fromApiContract(self, item) + local ____tostring_6 = tostring + local ____item_id_5 = item.id + if ____item_id_5 == nil then + ____item_id_5 = "" + end + local id = ____tostring_6(____item_id_5) + if id == "" then + return nil + end + local ____tonumber_9 = tonumber + local ____tostring_8 = tostring + local ____item_tier_7 = item.tier + if ____item_tier_7 == nil then + ____item_tier_7 = "1" + end + local tier = ____tonumber_9(____tostring_8(____item_tier_7)) or 1 + local ____tostring_11 = tostring + local ____item_title_10 = item.title + if ____item_title_10 == nil then + ____item_title_10 = "Смертельный приговор" + end + local title = ____tostring_11(____item_title_10) + local ____tonumber_15 = tonumber + local ____tostring_14 = tostring + local ____item_stat_multiplier_12 = item.stat_multiplier + if ____item_stat_multiplier_12 == nil then + ____item_stat_multiplier_12 = item.statMultiplier + end + local ____item_stat_multiplier_12_13 = ____item_stat_multiplier_12 + if ____item_stat_multiplier_12_13 == nil then + ____item_stat_multiplier_12_13 = "4" + end + local statMultiplier = ____tonumber_15(____tostring_14(____item_stat_multiplier_12_13)) or 4 + local ____tonumber_19 = tonumber + local ____tostring_18 = tostring + local ____item_reward_bonus_pct_16 = item.reward_bonus_pct + if ____item_reward_bonus_pct_16 == nil then + ____item_reward_bonus_pct_16 = item.rewardBonusPct + end + local ____item_reward_bonus_pct_16_17 = ____item_reward_bonus_pct_16 + if ____item_reward_bonus_pct_16_17 == nil then + ____item_reward_bonus_pct_16_17 = "15" + end + local rewardBonusPct = ____tonumber_19(____tostring_18(____item_reward_bonus_pct_16_17)) or 15 + local rawDebuffs = item.debuffs + local debuffs = {} + if __TS__ArrayIsArray(rawDebuffs) then + for ____, value in ipairs(rawDebuffs) do + debuffs[#debuffs + 1] = tostring(value) + end + elseif rawDebuffs and type(rawDebuffs) == "table" then + for ____, value in ipairs(__TS__ObjectValues(rawDebuffs)) do + debuffs[#debuffs + 1] = tostring(value) + end + end + return { + id = id, + tier = tier, + title = title, + statMultiplier = statMultiplier, + rewardBonusPct = rewardBonusPct, + debuffs = debuffs + } +end +function ContractsManager.prototype.toApiContract(self, contract) + return { + id = contract.id, + tier = contract.tier, + title = contract.title, + stat_multiplier = contract.statMultiplier, + reward_bonus_pct = contract.rewardBonusPct, + debuffs = contract.debuffs + } +end +____exports.Contracts = ____exports.ContractsManager:getInstance() +return ____exports diff --git a/scripts/vscripts/cooking/cookingsystem.lua b/scripts/vscripts/cooking/cookingsystem.lua new file mode 100644 index 0000000..5a01d38 --- /dev/null +++ b/scripts/vscripts/cooking/cookingsystem.lua @@ -0,0 +1,659 @@ +local ____lualib = require("lualib_bundle") +local __TS__Class = ____lualib.__TS__Class +local Map = ____lualib.Map +local __TS__New = ____lualib.__TS__New +local Set = ____lualib.Set +local __TS__Iterator = ____lualib.__TS__Iterator +local __TS__ArrayFilter = ____lualib.__TS__ArrayFilter +local __TS__ArrayMap = ____lualib.__TS__ArrayMap +local ____exports = {} +local ____recipes = require("cooking.recipes") +local isRecipeUnlockedForResult = ____recipes.isRecipeUnlockedForResult +local RECIPES = ____recipes.RECIPES +require("cooking.modifier_campfire_cooking") +local ____QuestSystem = require("quests.QuestSystem") +local QuestSystem = ____QuestSystem.QuestSystem +____exports.CookingSystem = __TS__Class() +local CookingSystem = ____exports.CookingSystem +CookingSystem.name = "CookingSystem" +CookingSystem.____file_path = "scripts/vscripts/cooking/CookingSystem.lua" +function CookingSystem.prototype.____constructor(self) + self.campfires = __TS__New(Map) + self.detectionRadius = 300 + self.isChecking = false + self.cookingDuration = 5 + self.openDistance = 400 + self.firstTimePlayers = __TS__New(Set) + self:initialize() +end +function CookingSystem.getInstance(self) + if not ____exports.CookingSystem.instance then + ____exports.CookingSystem.instance = __TS__New(____exports.CookingSystem) + end + return ____exports.CookingSystem.instance +end +function CookingSystem.initialize(self) + ____exports.CookingSystem:getInstance() +end +function CookingSystem.prototype.initialize(self) + CustomGameEventManager:RegisterListener( + "cooking_start", + function(_, event) + self:onCookingStart(event) + end + ) + CustomGameEventManager:RegisterListener( + "cooking_claim_dish", + function(_, event) + self:onClaimDish(event) + end + ) + CustomGameEventManager:RegisterListener( + "cooking_request_update", + function(_, event) + self:onRequestUpdate(event) + end + ) + CustomGameEventManager:RegisterListener( + "cooking_panel_close", + function(_, event) + self:onPanelClose(event) + end + ) + ListenToGameEvent( + "game_rules_state_change", + function() + self:onGameStateChange() + end, + nil + ) + ListenToGameEvent( + "npc_spawned", + function(event) + self:onNpcSpawned(event) + end, + nil + ) +end +function CookingSystem.prototype.onGameStateChange(self) + local state = GameRules:State_Get() + if state == DOTA_GAMERULES_STATE_GAME_IN_PROGRESS and not self.isChecking then + Timers:CreateTimer( + 0.5, + function() + self:startChecking() + return nil + end + ) + end +end +function CookingSystem.prototype.onNpcSpawned(self, event) + if not IsServer() then + return + end + local unit = EntIndexToHScript(event.entindex) + if not unit or not IsValidEntity(unit) then + return + end + local unitName = unit:GetUnitName() + if unitName == "npc_campfire" then + local index = unit:GetEntityIndex() + Timers:CreateTimer( + 0.1, + function() + if IsValidEntity(unit) then + self:registerCampfire(unit) + end + return nil + end + ) + end +end +function CookingSystem.prototype.registerCampfire(self, campfire) + local index = campfire:GetEntityIndex() + if not campfire:HasModifier("modifier_campfire_cooking") then + campfire:AddNewModifier( + campfire, + getModifierSourceAbility(nil, campfire), + "modifier_campfire_cooking", + {} + ) + end + if not self.campfires:has(index) then + self.campfires:set( + index, + { + campfire = campfire, + nearbyHeroes = __TS__New(Set) + } + ) + end +end +function CookingSystem.prototype.startChecking(self) + if self.isChecking then + return + end + self.isChecking = true + self:scanForExistingCampfires() + local checkInterval = 0.5 + local checkLoop + checkLoop = function() + if not self.isChecking then + return + end + if self.campfires.size > 0 then + self:checkNearbyCampfires() + else + self:scanForExistingCampfires() + end + Timers:CreateTimer(checkInterval, checkLoop) + end + checkLoop(nil) +end +function CookingSystem.prototype.scanForExistingCampfires(self) + if not IsServer() then + return + end + local foundCount = 0 + local classnames = {"npc_dota_base_addon", "npc_dota_creature", "npc_dota_base"} + for ____, classname in ipairs(classnames) do + local allUnits = Entities:FindAllByClassname(classname) + for ____, unit in ipairs(allUnits) do + do + if not unit or not IsValidEntity(unit) then + goto __continue34 + end + local unitName = unit:GetUnitName() + if unitName == "npc_campfire" then + local index = unit:GetEntityIndex() + if not self.campfires:has(index) then + self:registerCampfire(unit) + foundCount = foundCount + 1 + end + end + end + ::__continue34:: + end + end + local currentEntity = Entities:First() + while currentEntity ~= nil do + if IsValidEntity(currentEntity) then + local unit = currentEntity + if unit.GetUnitName and unit:GetUnitName() == "npc_campfire" then + local index = unit:GetEntityIndex() + if not self.campfires:has(index) then + self:registerCampfire(unit) + foundCount = foundCount + 1 + end + end + end + currentEntity = Entities:Next(currentEntity) + end +end +function CookingSystem.prototype.checkNearbyCampfires(self) + if not IsServer() then + return + end + if self.campfires.size == 0 then + return + end + local allHeroes = {} + do + local playerId = 0 + while playerId < PlayerResource:GetPlayerCount() do + if PlayerResource:IsValidPlayerID(playerId) then + local hero = PlayerResource:GetSelectedHeroEntity(playerId) + if hero and IsValidEntity(hero) and hero:IsRealHero() then + allHeroes[#allHeroes + 1] = hero + end + end + playerId = playerId + 1 + end + end + for ____, ____value in __TS__Iterator(self.campfires:entries()) do + local index = ____value[1] + local data = ____value[2] + do + local campfire = data.campfire + if not campfire or not IsValidEntity(campfire) then + self.campfires:delete(index) + goto __continue50 + end + local campfirePos = campfire:GetAbsOrigin() + if not campfirePos then + goto __continue50 + end + local nearbyHeroes = __TS__New(Set) + for ____, hero in ipairs(allHeroes) do + do + if not hero or not IsValidEntity(hero) or not hero:IsRealHero() then + goto __continue53 + end + local heroPos = hero:GetAbsOrigin() + if not heroPos then + goto __continue53 + end + local distance = ((campfirePos.x - heroPos.x) ^ 2 + (campfirePos.y - heroPos.y) ^ 2) ^ 0.5 + local playerID = hero:GetPlayerOwnerID() + if distance <= self.detectionRadius then + nearbyHeroes:add(playerID) + if data.lockedByPlayer == playerID and data.nearbyHeroes:has(playerID) then + end + end + end + ::__continue53:: + end + for ____, playerID in __TS__Iterator(data.nearbyHeroes) do + if not nearbyHeroes:has(playerID) then + if data.lockedByPlayer == playerID then + data.lockedByPlayer = nil + end + local player = PlayerResource:GetPlayer(playerID) + if player then + CustomGameEventManager:Send_ServerToPlayer(player, "cooking_panel_remove", {campfireIndex = index}) + end + end + end + data.nearbyHeroes = nearbyHeroes + end + ::__continue50:: + end +end +function CookingSystem.prototype.updateCookingPanel(self, campfireIndex, playerID) + if not IsServer() then + return + end + local data = self.campfires:get(campfireIndex) + if not data then + return + end + local campfire = data.campfire + if not campfire or not IsValidEntity(campfire) then + return + end + local player = PlayerResource:GetPlayer(playerID) + if not player then + return + end + local hero = player:GetAssignedHero() + if not hero or not IsValidEntity(hero) then + return + end + local modifier = campfire:FindModifierByName("modifier_campfire_cooking") + if not modifier then + campfire:AddNewModifier( + campfire, + getModifierSourceAbility(nil, campfire), + "modifier_campfire_cooking", + {} + ) + modifier = campfire:FindModifierByName("modifier_campfire_cooking") + if not modifier then + return + end + end + local cookingOrders = modifier:GetCookingOrders() + local readyDishes = modifier:GetReadyDishesForPlayer(playerID) + local currentTime = GameRules:GetGameTime() + local cookingOrdersData = __TS__ArrayMap( + __TS__ArrayFilter( + cookingOrders, + function(____, order) return order.playerID == playerID end + ), + function(____, order) return { + recipeIndex = order.recipeIndex, + playerID = order.playerID, + startTime = order.startTime, + duration = order.duration, + result = order.result, + remainingTime = math.max(0, order.duration - (currentTime - order.startTime)) + } end + ) + local readyDishesData = __TS__ArrayMap( + readyDishes, + function(____, dish) return {recipeIndex = dish.recipeIndex, playerID = dish.playerID, result = dish.result, charges = dish.charges} end + ) + local playerRecipes = {} + do + local i = 0 + while i < #RECIPES do + do + local recipe = RECIPES[i + 1] + if recipe.locked and not isRecipeUnlockedForResult(nil, recipe.result) then + goto __continue76 + end + playerRecipes[#playerRecipes + 1] = { + index = i, + name = recipe.result, + ingredients = recipe.ingredients, + result = recipe.result, + canCook = self:canCook(hero, recipe.ingredients), + duration = recipe.duration ~= nil and recipe.duration or self.cookingDuration, + category = recipe.category or "dish" + } + end + ::__continue76:: + i = i + 1 + end + end + local panelData = {campfireIndex = campfireIndex, recipes = playerRecipes, cookingOrders = cookingOrdersData, readyDishes = readyDishesData} + CustomGameEventManager:Send_ServerToPlayer(player, "cooking_panel_update", panelData) +end +function CookingSystem.prototype.onCookingStart(self, event) + if not IsServer() then + return + end + local playerID = event.PlayerID ~= nil and event.PlayerID or -1 + if playerID == -1 then + return + end + local player = PlayerResource:GetPlayer(playerID) + if not player then + return + end + local hero = player:GetAssignedHero() + if not hero or not IsValidEntity(hero) then + return + end + local recipeIndex = event.recipeIndex + if recipeIndex < 0 or recipeIndex >= #RECIPES then + return + end + local recipe = RECIPES[recipeIndex + 1] + if recipe.locked and not isRecipeUnlockedForResult(nil, recipe.result) then + CustomGameEventManager:Send_ServerToPlayer(player, "cooking_result", {success = false, error = "Рецепт закрыт"}) + return + end + if not self:canCook(hero, recipe.ingredients) then + CustomGameEventManager:Send_ServerToPlayer(player, "cooking_result", {success = false, error = "Недостаточно ингредиентов"}) + return + end + local data = self.campfires:get(event.campfireIndex) + if not data then + return + end + local campfire = data.campfire + if not campfire or not IsValidEntity(campfire) then + return + end + local modifier = campfire:FindModifierByName("modifier_campfire_cooking") + if not modifier then + return + end + self:removeIngredients(hero, recipe.ingredients) + local currentTime = GameRules:GetGameTime() + local recipeDuration = recipe.duration ~= nil and recipe.duration or self.cookingDuration + modifier:AddCookingOrder({ + recipeIndex = recipeIndex, + playerID = playerID, + startTime = currentTime, + duration = recipeDuration, + result = recipe.result + }) + CustomGameEventManager:Send_ServerToPlayer(player, "cooking_result", {success = true, message = "Готовка начата"}) + self:updateCookingPanel(event.campfireIndex, playerID) +end +function CookingSystem.prototype.onRequestUpdate(self, event) + if not IsServer() then + return + end + local playerID = event.PlayerID + if playerID == nil then + return + end + local player = PlayerResource:GetPlayer(playerID) + if not player then + return + end + local campfireIndex = event.campfireIndex + self:updateCookingPanel(campfireIndex, playerID) +end +function CookingSystem.prototype.onPanelClose(self, event) + if not IsServer() then + return + end + local playerID = event.PlayerID + if playerID == nil then + return + end + local data = self.campfires:get(event.campfireIndex) + if not data then + return + end + if data.lockedByPlayer ~= playerID then + return + end + data.lockedByPlayer = nil + local player = PlayerResource:GetPlayer(playerID) + if player then + CustomGameEventManager:Send_ServerToPlayer(player, "cooking_panel_remove", {campfireIndex = event.campfireIndex}) + end +end +function CookingSystem.prototype.onClaimDish(self, event) + if not IsServer() then + return + end + local ____temp_0 + if event.playerID ~= nil then + ____temp_0 = event.playerID + else + ____temp_0 = event.PlayerID ~= nil and event.PlayerID or -1 + end + local playerID = ____temp_0 + if playerID == -1 then + return + end + local player = PlayerResource:GetPlayer(playerID) + if not player then + return + end + local hero = player:GetAssignedHero() + if not hero or not IsValidEntity(hero) then + return + end + local data = self.campfires:get(event.campfireIndex) + if not data then + return + end + local campfire = data.campfire + if not campfire or not IsValidEntity(campfire) then + return + end + local modifier = campfire:FindModifierByName("modifier_campfire_cooking") + if not modifier then + return + end + local allReadyDishes = modifier:GetReadyDishes() + local dish = nil + local globalIndex = -1 + if event.result ~= nil and event.recipeIndex ~= nil then + do + local i = 0 + while i < #allReadyDishes do + local d = allReadyDishes[i + 1] + if d.result == event.result and d.recipeIndex == event.recipeIndex and d.playerID == playerID then + dish = d + globalIndex = i + break + end + i = i + 1 + end + end + elseif event.dishIndex ~= nil then + local readyDishes = modifier:GetReadyDishesForPlayer(playerID) + if event.dishIndex >= 0 and event.dishIndex < #readyDishes then + dish = readyDishes[event.dishIndex + 1] + do + local i = 0 + while i < #allReadyDishes do + if allReadyDishes[i + 1] == dish then + globalIndex = i + break + end + i = i + 1 + end + end + end + end + if not dish or globalIndex == -1 then + return + end + local resultItem = hero:AddItemByName(dish.result) + if not resultItem then + local item = CreateItem(dish.result, nil, nil) + if item then + if dish.charges > 1 then + item:SetCurrentCharges(dish.charges) + end + CreateItemOnPositionSync( + hero:GetAbsOrigin(), + item + ) + end + else + if dish.charges > 1 then + resultItem:SetCurrentCharges(dish.charges) + end + end + modifier:RemoveReadyDish(globalIndex) + if dish.result == "item_grilled_meat" then + local questSystem = QuestSystem:getInstance() + local ____temp_1 + if dish.charges > 0 then + ____temp_1 = dish.charges + else + ____temp_1 = 1 + end + local portions = ____temp_1 + questSystem:addQuestProgress("firestar_quest_gourmet", "cook_meat", portions) + end + self:updateCookingPanel(event.campfireIndex, playerID) +end +function CookingSystem.prototype.isCampfireOccupied(self, campfireIndex, playerID) + if not IsServer() then + return false + end + local data = self.campfires:get(campfireIndex) + if not data then + return false + end + return data.lockedByPlayer ~= nil and data.lockedByPlayer ~= playerID +end +function CookingSystem.prototype.onCampfireClick(self, campfireIndex, playerID) + if not IsServer() then + return + end + local data = self.campfires:get(campfireIndex) + if not data then + return + end + local campfire = data.campfire + if not campfire or not IsValidEntity(campfire) then + return + end + local player = PlayerResource:GetPlayer(playerID) + if not player then + return + end + if not self.firstTimePlayers:has(playerID) then + self.firstTimePlayers:add(playerID) + CustomGameEventManager:Send_ServerToPlayer(player, "cooking_intro_video", {videoName = "cm", campfireIndex = campfireIndex}) + end + if data.lockedByPlayer ~= nil and data.lockedByPlayer ~= playerID then + return + end + if not player then + return + end + local hero = player:GetAssignedHero() + if not hero or not IsValidEntity(hero) then + return + end + local campfirePos = campfire:GetAbsOrigin() + local heroPos = hero:GetAbsOrigin() + if not campfirePos or not heroPos then + return + end + local distance = ((campfirePos.x - heroPos.x) ^ 2 + (campfirePos.y - heroPos.y) ^ 2) ^ 0.5 + if distance <= self.openDistance then + if data.lockedByPlayer == nil then + data.lockedByPlayer = playerID + end + if not data.nearbyHeroes:has(playerID) then + data.nearbyHeroes:add(playerID) + end + self:updateCookingPanel(campfireIndex, playerID) + end +end +function CookingSystem.prototype.canCook(self, hero, ingredients) + local ingredientCounts = __TS__New(Map) + for ____, ingredient in ipairs(ingredients) do + if ingredient and ingredient ~= "" then + ingredientCounts:set( + ingredient, + (ingredientCounts:get(ingredient) or 0) + 1 + ) + end + end + for ____, ____value in __TS__Iterator(ingredientCounts:entries()) do + local itemName = ____value[1] + local requiredCount = ____value[2] + local foundCount = 0 + do + local i = 0 + while i < 15 do + local item = hero:GetItemInSlot(i) + if item and IsValidEntity(item) and item:GetAbilityName() == itemName then + local charges = item:GetCurrentCharges() + local initialCharges = item:GetInitialCharges() + foundCount = foundCount + (charges > 0 and charges or (initialCharges > 0 and initialCharges or 1)) + end + i = i + 1 + end + end + if foundCount < requiredCount then + return false + end + end + return true +end +function CookingSystem.prototype.removeIngredients(self, hero, ingredients) + local ingredientCounts = __TS__New(Map) + for ____, ingredient in ipairs(ingredients) do + if ingredient and ingredient ~= "" then + ingredientCounts:set( + ingredient, + (ingredientCounts:get(ingredient) or 0) + 1 + ) + end + end + for ____, ____value in __TS__Iterator(ingredientCounts:entries()) do + local itemName = ____value[1] + local countToRemove = ____value[2] + local removedCount = 0 + do + local i = 0 + while i < 15 and removedCount < countToRemove do + local item = hero:GetItemInSlot(i) + if item and IsValidEntity(item) and item:GetAbilityName() == itemName then + local charges = item:GetCurrentCharges() + local initialCharges = item:GetInitialCharges() + local itemCharges = charges > 0 and charges or (initialCharges > 0 and initialCharges or 1) + if itemCharges > 1 and removedCount + itemCharges <= countToRemove then + hero:RemoveItem(item) + removedCount = removedCount + itemCharges + elseif itemCharges > 1 then + local toRemove = countToRemove - removedCount + item:SetCurrentCharges(itemCharges - toRemove) + removedCount = removedCount + toRemove + else + hero:RemoveItem(item) + removedCount = removedCount + 1 + end + end + i = i + 1 + end + end + end +end +return ____exports diff --git a/scripts/vscripts/cooking/modifier_campfire_cooking.lua b/scripts/vscripts/cooking/modifier_campfire_cooking.lua new file mode 100644 index 0000000..6ae95fc --- /dev/null +++ b/scripts/vscripts/cooking/modifier_campfire_cooking.lua @@ -0,0 +1,118 @@ +local ____lualib = require("lualib_bundle") +local __TS__Class = ____lualib.__TS__Class +local __TS__ClassExtends = ____lualib.__TS__ClassExtends +local __TS__ArraySplice = ____lualib.__TS__ArraySplice +local __TS__ArrayFilter = ____lualib.__TS__ArrayFilter +local __TS__Decorate = ____lualib.__TS__Decorate +local ____exports = {} +local ____dota_ts_adapter = require("lib.dota_ts_adapter") +local BaseModifier = ____dota_ts_adapter.BaseModifier +local registerModifier = ____dota_ts_adapter.registerModifier +____exports.modifier_campfire_cooking = __TS__Class() +local modifier_campfire_cooking = ____exports.modifier_campfire_cooking +modifier_campfire_cooking.name = "modifier_campfire_cooking" +modifier_campfire_cooking.____file_path = "scripts/vscripts/cooking/modifier_campfire_cooking.lua" +__TS__ClassExtends(modifier_campfire_cooking, BaseModifier) +function modifier_campfire_cooking.prototype.____constructor(self, ...) + BaseModifier.prototype.____constructor(self, ...) + self.cookingOrders = {} + self.readyDishes = {} +end +function modifier_campfire_cooking.prototype.IsHidden(self) + return true +end +function modifier_campfire_cooking.prototype.IsPurgable(self) + return false +end +function modifier_campfire_cooking.prototype.IsPermanent(self) + return true +end +function modifier_campfire_cooking.prototype.OnCreated(self, params) + if IsServer() then + self.cookingOrders = {} + self.readyDishes = {} + local parent = self:GetParent() + if parent and IsValidEntity(parent) then + parent:SetThink( + function() + self:CheckCookingTimers() + return 0.1 + end, + "CampfireCookingThink", + "0", + nil + ) + end + end +end +function modifier_campfire_cooking.prototype.CheckCookingTimers(self) + if not IsServer() then + return + end + local currentTime = GameRules:GetGameTime() + local ordersToComplete = {} + do + local i = #self.cookingOrders - 1 + while i >= 0 do + local order = self.cookingOrders[i + 1] + local elapsed = currentTime - order.startTime + if elapsed >= order.duration then + self:AddReadyDish({recipeIndex = order.recipeIndex, playerID = order.playerID, result = order.result, charges = 1}) + ordersToComplete[#ordersToComplete + 1] = i + end + i = i - 1 + end + end + for ____, index in ipairs(ordersToComplete) do + self:RemoveCookingOrder(index) + end +end +function modifier_campfire_cooking.prototype.AddCookingOrder(self, order) + if not IsServer() then + return + end + local ____self_cookingOrders_0 = self.cookingOrders + ____self_cookingOrders_0[#____self_cookingOrders_0 + 1] = order +end +function modifier_campfire_cooking.prototype.GetCookingOrders(self) + return self.cookingOrders +end +function modifier_campfire_cooking.prototype.RemoveCookingOrder(self, orderIndex) + if not IsServer() then + return + end + __TS__ArraySplice(self.cookingOrders, orderIndex, 1) +end +function modifier_campfire_cooking.prototype.AddReadyDish(self, dish) + if not IsServer() then + return + end + local ____self_readyDishes_1 = self.readyDishes + ____self_readyDishes_1[#____self_readyDishes_1 + 1] = dish +end +function modifier_campfire_cooking.prototype.GetReadyDishes(self) + return self.readyDishes +end +function modifier_campfire_cooking.prototype.RemoveReadyDish(self, dishIndex) + if not IsServer() then + return + end + __TS__ArraySplice(self.readyDishes, dishIndex, 1) +end +function modifier_campfire_cooking.prototype.GetReadyDishesForPlayer(self, playerID) + return __TS__ArrayFilter( + self.readyDishes, + function(____, dish) return dish.playerID == playerID end + ) +end +function modifier_campfire_cooking.prototype.DeclareFunctions(self) + return {} +end +modifier_campfire_cooking = __TS__Decorate( + modifier_campfire_cooking, + modifier_campfire_cooking, + {registerModifier(nil)}, + {kind = "class", name = "modifier_campfire_cooking"} +) +____exports.modifier_campfire_cooking = modifier_campfire_cooking +return ____exports diff --git a/scripts/vscripts/cooking/recipes.lua b/scripts/vscripts/cooking/recipes.lua new file mode 100644 index 0000000..af7101e --- /dev/null +++ b/scripts/vscripts/cooking/recipes.lua @@ -0,0 +1,45 @@ +local ____lualib = require("lualib_bundle") +local Set = ____lualib.Set +local __TS__New = ____lualib.__TS__New +local ____exports = {} +local unlockedRecipesByResult = __TS__New(Set) +function ____exports.isRecipeUnlockedForResult(self, recipeResult) + return unlockedRecipesByResult:has(recipeResult) +end +function ____exports.openRecipe(self, recipeResult) + unlockedRecipesByResult:add(recipeResult) +end +function ____exports.closeRecipe(self, recipeResult) + unlockedRecipesByResult:delete(recipeResult) +end +____exports.RECIPES = { + { + ingredients = {"item_testo", "item_testo", "item_testo", ""}, + result = "item_testo_pizza", + duration = 15, + category = "ingredient", + locked = true + }, + { + ingredients = {"item_egg", "item_egg", "item_milk", "item_milk"}, + result = "item_mayonnaise", + duration = 25, + category = "ingredient", + locked = true + }, + { + ingredients = {"item_testo_pizza", "item_grilled_meat", "item_cheese", "item_mayonnaise"}, + result = "item_pizza", + duration = 20, + category = "dish", + locked = true + }, + {ingredients = {"item_coffe_bean", "item_milk", "item_banana", ""}, result = "item_cocktail", duration = 45, category = "dish"}, + {ingredients = {"item_coffe_bean", "item_coffe_bean", "", ""}, result = "item_coffee", duration = 45, category = "dish"}, + {ingredients = {"item_testo", "", "", ""}, result = "item_bread", duration = 45, category = "ingredient"}, + {ingredients = {"item_milk", "item_milk", "item_milk", ""}, result = "item_cheese", duration = 15, category = "ingredient"}, + {ingredients = {"item_bread", "item_grilled_meat", "item_cheese", "item_egg"}, result = "item_sandwich", duration = 45, category = "dish"}, + {ingredients = {"item_coffe_bean", "item_coffe_bean", "item_coffe_bean", "item_coffe_bean"}, result = "item_energy_drink", duration = 90, category = "dish"}, + {ingredients = {"item_meat", "", "", ""}, result = "item_grilled_meat", duration = 15, category = "ingredient"} +} +return ____exports diff --git a/scripts/vscripts/crystal_currency.lua b/scripts/vscripts/crystal_currency.lua new file mode 100644 index 0000000..7c83e08 --- /dev/null +++ b/scripts/vscripts/crystal_currency.lua @@ -0,0 +1,226 @@ +local ____lualib = require("lualib_bundle") +local __TS__Class = ____lualib.__TS__Class +local Map = ____lualib.Map +local __TS__New = ____lualib.__TS__New +local __TS__Iterator = ____lualib.__TS__Iterator +local ____exports = {} +____exports.CrystalCurrency = __TS__Class() +local CrystalCurrency = ____exports.CrystalCurrency +CrystalCurrency.name = "CrystalCurrency" +CrystalCurrency.____file_path = "scripts/vscripts/crystal_currency.lua" +function CrystalCurrency.prototype.____constructor(self) + self.playerCrystals = __TS__New(Map) + self.playerTotalSpentCrystals = __TS__New(Map) + self.animationTimers = __TS__New(Map) + self.spendListeners = __TS__New(Map) + self.nextSpendListenerId = 1 + self:initializePlayers() + self:setupEventListeners() +end +function CrystalCurrency.getInstance(self) + if not ____exports.CrystalCurrency.instance then + ____exports.CrystalCurrency.instance = __TS__New(____exports.CrystalCurrency) + end + return ____exports.CrystalCurrency.instance +end +function CrystalCurrency.prototype.initializePlayers(self) + do + local i = 0 + while i < DOTA_MAX_TEAM_PLAYERS do + if PlayerResource:IsValidPlayer(i) then + self.playerCrystals:set(i, 0) + self.playerTotalSpentCrystals:set(i, 0) + self:updateCrystalDisplay(i) + end + i = i + 1 + end + end +end +function CrystalCurrency.prototype.setupEventListeners(self) +end +function CrystalCurrency.prototype.cleanupPlayerTimers(self, playerId) + local timerId = self.animationTimers:get(playerId) + if timerId then + GameRules:GetGameModeEntity():SetContextThink( + "crystal_animate_" .. tostring(playerId), + nil, + 0 + ) + self.animationTimers:delete(playerId) + end +end +function CrystalCurrency.prototype.getCrystals(self, playerId) + return self.playerCrystals:get(playerId) or 0 +end +function CrystalCurrency.prototype.addCrystals(self, playerId, amount) + if amount <= 0 then + return false + end + local currentCrystals = self:getCrystals(playerId) + local newAmount = currentCrystals + amount + self.playerCrystals:set(playerId, newAmount) + local existingTimer = self.animationTimers:get(playerId) + if existingTimer then + GameRules:GetGameModeEntity():SetContextThink( + "crystal_animate_" .. tostring(playerId), + nil, + 0 + ) + end + local timerId = GameRules:GetGameModeEntity():SetContextThink( + "crystal_animate_" .. tostring(playerId), + function() + local player = PlayerResource:GetPlayer(playerId) + if player then + CustomGameEventManager:Send_ServerToPlayer(player, "crystal_animate", {amount = newAmount, added = amount}) + end + self.animationTimers:delete(playerId) + return -1 + end, + 0.1 + ) + self.animationTimers:set(playerId, timerId) + GameRules:GetGameModeEntity():SetContextThink( + "crystal_net_table_" .. tostring(playerId), + function() + self:updateCrystalDisplay(playerId) + return -1 + end, + 0.2 + ) + self:sendCrystalNotification(playerId, amount, "crystal_gained") + return true +end +function CrystalCurrency.prototype.removeCrystals(self, playerId, amount) + if amount <= 0 then + return false + end + local currentCrystals = self:getCrystals(playerId) + if currentCrystals < amount then + print((((("[CRYSTAL] removeCrystals FAIL: player=" .. tostring(playerId)) .. ", need=") .. tostring(amount)) .. ", have=") .. tostring(currentCrystals)) + return false + end + local newAmount = currentCrystals - amount + local totalSpent = (self.playerTotalSpentCrystals:get(playerId) or 0) + amount + self.playerCrystals:set(playerId, newAmount) + self.playerTotalSpentCrystals:set(playerId, totalSpent) + print((((((((("[CRYSTAL] removeCrystals OK: player=" .. tostring(playerId)) .. ", spent=") .. tostring(amount)) .. ", before=") .. tostring(currentCrystals)) .. ", after=") .. tostring(newAmount)) .. ", totalSpent=") .. tostring(totalSpent)) + local existingTimer = self.animationTimers:get(playerId) + if existingTimer then + GameRules:GetGameModeEntity():SetContextThink( + "crystal_animate_" .. tostring(playerId), + nil, + 0 + ) + end + local timerId = GameRules:GetGameModeEntity():SetContextThink( + "crystal_animate_" .. tostring(playerId), + function() + local player = PlayerResource:GetPlayer(playerId) + if player then + CustomGameEventManager:Send_ServerToPlayer(player, "crystal_animate", {amount = newAmount, added = -amount}) + end + self.animationTimers:delete(playerId) + return -1 + end, + 0.1 + ) + self.animationTimers:set(playerId, timerId) + self:updateCrystalDisplay(playerId) + self:notifyCrystalsSpent(playerId, amount, newAmount, totalSpent) + return true +end +function CrystalCurrency.prototype.setCrystals(self, playerId, amount) + if amount < 0 then + return false + end + self.playerCrystals:set(playerId, amount) + self:updateCrystalDisplay(playerId) + return true +end +function CrystalCurrency.prototype.canAfford(self, playerId, cost) + return self:getCrystals(playerId) >= cost +end +function CrystalCurrency.prototype.getTotalSpentCrystals(self, playerId) + return self.playerTotalSpentCrystals:get(playerId) or 0 +end +function CrystalCurrency.prototype.addSpendListener(self, callback) + local ____self_0, ____nextSpendListenerId_1 = self, "nextSpendListenerId" + local ____self_nextSpendListenerId_2 = ____self_0[____nextSpendListenerId_1] + ____self_0[____nextSpendListenerId_1] = ____self_nextSpendListenerId_2 + 1 + local id = ____self_nextSpendListenerId_2 + self.spendListeners:set(id, callback) + print((("[CRYSTAL] addSpendListener: id=" .. tostring(id)) .. ", listeners=") .. tostring(self.spendListeners.size)) + return id +end +function CrystalCurrency.prototype.removeSpendListener(self, listenerId) + self.spendListeners:delete(listenerId) + print((("[CRYSTAL] removeSpendListener: id=" .. tostring(listenerId)) .. ", listeners=") .. tostring(self.spendListeners.size)) +end +function CrystalCurrency.prototype.notifyCrystalsSpent(self, playerId, spentAmount, newTotal, totalSpent) + print((((((((("[CRYSTAL] notifyCrystalsSpent: player=" .. tostring(playerId)) .. ", spent=") .. tostring(spentAmount)) .. ", newTotal=") .. tostring(newTotal)) .. ", totalSpent=") .. tostring(totalSpent)) .. ", listeners=") .. tostring(self.spendListeners.size)) + for ____, callback in __TS__Iterator(self.spendListeners:values()) do + callback( + nil, + playerId, + spentAmount, + newTotal, + totalSpent + ) + end +end +function CrystalCurrency.prototype.updateCrystalDisplay(self, playerId) + local crystals = self:getCrystals(playerId) + local key = "crystals_" .. tostring(playerId) + local data = {amount = crystals} + CustomNetTables:SetTableValue("crystal_currency", key, data) +end +function CrystalCurrency.prototype.sendCrystalNotification(self, playerId, amount, ____type) + local player = PlayerResource:GetPlayer(playerId) + if player then + CustomGameEventManager:Send_ServerToPlayer( + player, + "crystal_currency_changed", + { + amount = amount, + type = ____type, + total = self:getCrystals(playerId) + } + ) + end +end +function CrystalCurrency.prototype.giveCrystalsForKill(self, _playerId, _unitName) +end +function CrystalCurrency.prototype.convertGoldToCrystals(self, playerId, goldAmount) + local hero = PlayerResource:GetSelectedHeroEntity(playerId) + if not hero then + return false + end + local currentGold = PlayerResource:GetGold(playerId) + if currentGold < goldAmount then + return false + end + local crystalAmount = math.floor(goldAmount / 100) + if crystalAmount <= 0 then + return false + end + hero:ModifyGold(-goldAmount, true, 0) + self:addCrystals(playerId, crystalAmount) + return true +end +function CrystalCurrency.prototype.convertCrystalsToGold(self, playerId, crystalAmount) + if not self:canAfford(playerId, crystalAmount) then + return false + end + local goldAmount = crystalAmount * 100 + self:removeCrystals(playerId, crystalAmount) + local hero = PlayerResource:GetSelectedHeroEntity(playerId) + if hero then + hero:ModifyGold(goldAmount, true, 0) + end + return true +end +function ____exports.GetCrystalCurrency(self) + return ____exports.CrystalCurrency:getInstance() +end +return ____exports diff --git a/scripts/vscripts/custom_game_events.lua b/scripts/vscripts/custom_game_events.lua new file mode 100644 index 0000000..7467e7e --- /dev/null +++ b/scripts/vscripts/custom_game_events.lua @@ -0,0 +1,58 @@ +local ____lualib = require("lualib_bundle") +local __TS__New = ____lualib.__TS__New +local ____exports = {} +local ____CardSystem = require("cards.CardSystem") +local CardSystem = ____CardSystem.CardSystem +--- Гарантировать CardSystem у контроллера (например до рассвета, если спавн ещё не выставил колоду). +function ____exports.ensurePlayerCardSystem(self, playerId) + local player = PlayerResource:GetPlayer(playerId) + if not player then + return nil + end + if not player.cardSystem then + player.cardSystem = __TS__New(CardSystem, playerId) + end + return player.cardSystem +end +--- Сервер → игрок: золото по карте 22 на рассвете. +function ____exports.sendZiMorningGoldCard(self, playerId, payload) + if not IsServer() then + return + end + local player = PlayerResource:GetPlayer(playerId) + if not player then + return + end + CustomGameEventManager:Send_ServerToPlayer(player, "zi_morning_gold_card", { + playerId = playerId, + cardId = 22, + morningSequence = payload.morningSequence, + nightIndex = payload.nightIndex, + goldAmount = payload.goldAmount + }) +end +--- Сервер → игрок: бонус кристаллов по карте 38 на рассвете. +function ____exports.sendZiMorningCrystalsCard(self, playerId, payload) + if not IsServer() then + return + end + local player = PlayerResource:GetPlayer(playerId) + if not player then + return + end + CustomGameEventManager:Send_ServerToPlayer(player, "zi_morning_crystals_card", { + playerId = playerId, + cardId = 38, + morningSequence = payload.morningSequence, + nightIndex = payload.nightIndex, + crystalBonus = payload.crystalBonus + }) +end +--- Сервер → все клиенты: фаза дня/ночи + счётчики (для UI/FX поверх day_night_timer_update). +function ____exports.broadcastZiCyclePhase(self, payload) + if not IsServer() then + return + end + CustomGameEventManager:Send_ServerToAllClients("zi_cycle_phase", payload) +end +return ____exports diff --git a/scripts/vscripts/cutscenes/cutscenefunctions.lua b/scripts/vscripts/cutscenes/cutscenefunctions.lua new file mode 100644 index 0000000..f66df3b --- /dev/null +++ b/scripts/vscripts/cutscenes/cutscenefunctions.lua @@ -0,0 +1,424 @@ +--[[ Generated with https://github.com/TypeScriptToLua/TypeScriptToLua ]] +local ____exports = {} +local setCouriersBlockedForCutscene, spawnCutsceneBossIfNeeded, CUTSCENE_BOSS_UNIT, BOSS_CUTSCENE_STUN_FULL_DURATION, BOSS_CUTSCENE_STUN_AFTER_SKIP, cutsceneBossUnstunSerial, cutsceneBossTeleportGeneration, CUTSCENE_COURIER_BLOCK_MODIFIER +local ____SpawnManager = require("SpawnManager") +local SpawnManager = ____SpawnManager.SpawnManager +function setCouriersBlockedForCutscene(self, blocked) + local couriers = Entities:FindAllByClassname("npc_dota_courier") + if #couriers <= 0 then + return + end + for ____, courier in ipairs(couriers) do + do + local courierNpc = courier + if not courierNpc or not IsValidEntity(courierNpc) then + goto __continue29 + end + if blocked then + if not courierNpc:HasModifier(CUTSCENE_COURIER_BLOCK_MODIFIER) then + courierNpc:AddNewModifier( + courierNpc, + getModifierSourceAbility(nil, courierNpc), + CUTSCENE_COURIER_BLOCK_MODIFIER, + {} + ) + end + goto __continue29 + end + if courierNpc:HasModifier(CUTSCENE_COURIER_BLOCK_MODIFIER) then + courierNpc:RemoveModifierByName(CUTSCENE_COURIER_BLOCK_MODIFIER) + end + end + ::__continue29:: + end +end +function spawnCutsceneBossIfNeeded(self) + local existing = Entities:FindByName(nil, CUTSCENE_BOSS_UNIT) + if existing ~= nil and IsValidEntity(existing) then + return existing + end + local bossSpawn = Entities:FindByName(nil, "point_boss_spawn_point") + if bossSpawn == nil then + return nil + end + local spawnPos = bossSpawn:GetAbsOrigin() + local boss = CreateUnitByName( + CUTSCENE_BOSS_UNIT, + spawnPos, + true, + nil, + nil, + DOTA_TEAM_BADGUYS + ) + if boss ~= nil and IsValidEntity(boss) then + FindClearSpaceForUnit(boss, spawnPos, true) + boss:SetEntityName(CUTSCENE_BOSS_UNIT) + boss:AddNewModifier( + boss, + getModifierSourceAbility(nil, boss), + "modifier_stunned", + {} + ) + local spawnGameTime = GameRules:GetGameTime() + local unstunDone = false + cutsceneBossUnstunSerial = cutsceneBossUnstunSerial + 1 + local fullTimerName = "cutscene_boss_unstun_" .. tostring(cutsceneBossUnstunSerial) + local function applyUnstun() + if unstunDone or boss == nil or not IsValidEntity(boss) then + return + end + unstunDone = true + Timers:RemoveTimer(fullTimerName) + boss:RemoveModifierByName("modifier_stunned") + end + Timers:CreateTimer( + fullTimerName, + { + endTime = BOSS_CUTSCENE_STUN_FULL_DURATION, + callback = function() + applyUnstun(nil) + return nil + end + } + ) + local pollCutsceneSkipUnstun + pollCutsceneSkipUnstun = function() + if unstunDone or boss == nil or not IsValidEntity(boss) then + return + end + local mgr = GameRules.CutsceneManager + if mgr ~= nil and mgr:isCutsceneActive() then + Timers:CreateTimer(0.1, pollCutsceneSkipUnstun) + return + end + local elapsed = GameRules:GetGameTime() - spawnGameTime + if elapsed < BOSS_CUTSCENE_STUN_FULL_DURATION - 0.02 then + Timers:RemoveTimer(fullTimerName) + Timers:CreateTimer( + BOSS_CUTSCENE_STUN_AFTER_SKIP, + function() + applyUnstun(nil) + end + ) + return + end + if not unstunDone then + applyUnstun(nil) + end + end + Timers:CreateTimer(0.1, pollCutsceneSkipUnstun) + return boss + end + return nil +end +function ____exports.teleport_heroes(self, options) + local cinematic = (options and options.cinematic) ~= false + local myGen = cutsceneBossTeleportGeneration + local forwardPoint = Entities:FindByName(nil, "point_forward") + local bossSpawn = Entities:FindByName(nil, "point_boss_spawn_point") + local teleportPoints = {} + do + local i = 0 + while i < 4 do + teleportPoints[i + 1] = Entities:FindByName( + nil, + "point_boss_player_" .. tostring(i) + ) + if not teleportPoints[i + 1] then + return + end + i = i + 1 + end + end + local boss + if bossSpawn ~= nil then + boss = spawnCutsceneBossIfNeeded(nil) + end + local function faceTowardsForward() + if forwardPoint == nil then + return + end + if myGen ~= cutsceneBossTeleportGeneration then + return + end + local lookAt = forwardPoint:GetAbsOrigin() + if boss ~= nil and IsValidEntity(boss) then + boss:FaceTowards(lookAt) + end + do + local p = 0 + while p < 10 do + do + local pl = PlayerResource:GetPlayer(p) + if not pl then + goto __continue56 + end + local hero = pl.GetAssignedHero and pl:GetAssignedHero() + if hero ~= nil and IsValidEntity(hero) then + hero:FaceTowards(lookAt) + end + end + ::__continue56:: + p = p + 1 + end + end + end + if not cinematic then + faceTowardsForward(nil) + do + local i = 0 + while i < 10 do + do + local player = PlayerResource:GetPlayer(i) + if not player then + goto __continue60 + end + local hero = player.GetAssignedHero and player:GetAssignedHero() + if hero == nil then + goto __continue60 + end + local ____temp_2 + if i < 4 then + ____temp_2 = teleportPoints[i + 1] + else + ____temp_2 = nil + end + local point = ____temp_2 + if point ~= nil then + hero:SetAbsOrigin(point:GetAbsOrigin()) + FindClearSpaceForUnit( + hero, + point:GetAbsOrigin(), + true + ) + end + if forwardPoint ~= nil then + hero:FaceTowards(forwardPoint:GetAbsOrigin()) + end + end + ::__continue60:: + i = i + 1 + end + end + return + end + Timers:CreateTimer( + 0.1, + function() + faceTowardsForward(nil) + return nil + end + ) + do + local i = 0 + while i < 10 do + local slot = i + local player = PlayerResource:GetPlayer(slot) + if player then + local hero = player.GetAssignedHero and player:GetAssignedHero() + if hero ~= nil then + local particle = ParticleManager:CreateParticle("particles/items2_fx/teleport_start.vpcf", PATTACH_ABSORIGIN, hero) + ParticleManager:ReleaseParticleIndex(particle) + Timers:CreateTimer( + 3, + function() + if myGen ~= cutsceneBossTeleportGeneration then + ParticleManager:DestroyParticle(particle, true) + return nil + end + ParticleManager:DestroyParticle(particle, false) + local ____temp_3 + if slot < 4 then + ____temp_3 = teleportPoints[slot + 1] + else + ____temp_3 = nil + end + local point = ____temp_3 + if point ~= nil then + hero:SetAbsOrigin(point:GetAbsOrigin()) + FindClearSpaceForUnit( + hero, + point:GetAbsOrigin(), + true + ) + end + if forwardPoint ~= nil then + hero:FaceTowards(forwardPoint:GetAbsOrigin()) + end + return nil + end + ) + end + end + i = i + 1 + end + end +end +--- Прекеш партиклов телепорта в camera_heroes_endlap / teleport_heroes +function ____exports.precacheCutsceneParticles(self, context) + PrecacheResource("particle", "particles/items2_fx/teleport_start.vpcf", context) + PrecacheResource("particle", "particles/items2_fx/teleport_end.vpcf", context) + PrecacheResource("model", "models/items/nevermore/diabolical_fiend_shoulder/diabolical_fiend_shoulder.vmdl", context) + PrecacheResource("model", "models/items/shadow_fiend/arms_deso/arms_deso.vmdl", context) + PrecacheResource("model", "models/items/nevermore/sf_souls_tyrant_head/sf_souls_tyrant_head.vmdl", context) +end +____exports.CutsceneFunctions = { + camera_start_ending = function(self) + SpawnManager:getInstance():RemoveAllSpawnZones() + setCouriersBlockedForCutscene(nil, true) + do + local i = 0 + while i < 4 do + local point = Entities:FindByName( + nil, + "point_player_" .. tostring(i) + ) + local player = PlayerResource:GetPlayer(i) + if player and point then + local hero = player.GetAssignedHero and player:GetAssignedHero() + if hero ~= nil then + if not hero:IsAlive() then + hero:RespawnHero(false, false) + end + hero:SetTimeUntilRespawn(-1) + hero:SetRespawnsDisabled(true) + hero:SetBuybackCooldownTime(9999999) + local dennySpawn = Entities:FindByName(nil, "point_denny_spawn"):GetAbsOrigin() + local randomOffset = Vector( + RandomFloat(-30, 30), + RandomFloat(-30, 30), + 0 + ) + Timers:CreateTimer( + 0.1, + function() + hero:FaceTowards(dennySpawn:__add(randomOffset)) + end + ) + do + local slot = 0 + while slot < 16 do + local item = hero:GetItemInSlot(slot) + if item and item.GetName and item:GetName() == "item_tpscroll" then + hero:RemoveItem(item) + end + slot = slot + 1 + end + end + hero:SetAbsOrigin(point:GetAbsOrigin()) + FindClearSpaceForUnit( + hero, + point:GetAbsOrigin(), + true + ) + hero:Stop() + end + end + i = i + 1 + end + end + local dennySpawnPoint = Entities:FindByName(nil, "point_denny_spawn") + if dennySpawnPoint then + local denny = Entities:FindByName(nil, "npc_quest_giver_denny") + if denny ~= nil then + local pointDenny = Entities:FindByName(nil, "point_denny_spawn") + local pointDennyMove = Entities:FindByName(nil, "point_denny") + if pointDenny and pointDennyMove then + denny:SetAbsOrigin(pointDenny:GetAbsOrigin()) + FindClearSpaceForUnit( + denny, + pointDenny:GetAbsOrigin(), + true + ) + denny:MoveToPosition(toVectorWS( + nil, + pointDennyMove:GetAbsOrigin() + )) + end + end + end + end, + camera_heroes_expedition = function(self) + do + local i = 0 + while i < 10 do + local player = PlayerResource:GetPlayer(i) + if player then + local hero = player.GetAssignedHero and player:GetAssignedHero() + local movePos = Entities:FindByName( + nil, + ("point_player_" .. tostring(i)) .. "_movepos" + ) + if hero and movePos then + hero:MoveToPosition(toVectorWS( + nil, + movePos:GetAbsOrigin() + )) + end + end + i = i + 1 + end + end + end, + camera_heroes_endlap = function(self) + local denny = Entities:FindByName(nil, "npc_quest_giver_denny") + if denny == nil then + return + end + local sumX = 0 + local sumY = 0 + local sumZ = 0 + local count = 0 + do + local i = 0 + while i < 4 do + do + local player = PlayerResource:GetPlayer(i) + if not player then + goto __continue20 + end + local hero = player.GetAssignedHero and player:GetAssignedHero() + if hero == nil then + goto __continue20 + end + local o = hero:GetAbsOrigin() + sumX = sumX + o.x + sumY = sumY + o.y + sumZ = sumZ + o.z + count = count + 1 + hero:FaceTowards(denny:GetAbsOrigin()) + end + ::__continue20:: + i = i + 1 + end + end + if count > 0 then + local dennyPos = denny:GetAbsOrigin() + denny:FaceTowards(Vector(sumX / count, sumY / count, dennyPos.z)) + end + local generationAtSchedule = cutsceneBossTeleportGeneration + Timers:CreateTimer( + 4, + function() + if generationAtSchedule ~= cutsceneBossTeleportGeneration then + return nil + end + ____exports.teleport_heroes(nil, {cinematic = true}) + return nil + end + ) + end +} +CUTSCENE_BOSS_UNIT = "npc_boss_nevermore" +BOSS_CUTSCENE_STUN_FULL_DURATION = 12.8 +BOSS_CUTSCENE_STUN_AFTER_SKIP = 0.1 +cutsceneBossUnstunSerial = 0 +--- Инкремент отменяет отложенный телепорт из camera_heroes_endlap и колбэки киношного teleport_heroes. +cutsceneBossTeleportGeneration = 0 +--- Вызов при досрочном завершении camera_start_ending (скип): сразу арена + герои. +function ____exports.finishCameraStartEndingTeleportNow(self) + cutsceneBossTeleportGeneration = cutsceneBossTeleportGeneration + 1 + ____exports.teleport_heroes(nil, {cinematic = false}) +end +CUTSCENE_COURIER_BLOCK_MODIFIER = "modifier_stunned" +return ____exports diff --git a/scripts/vscripts/cutscenes/cutscenemanager.lua b/scripts/vscripts/cutscenes/cutscenemanager.lua new file mode 100644 index 0000000..6b155f8 --- /dev/null +++ b/scripts/vscripts/cutscenes/cutscenemanager.lua @@ -0,0 +1,450 @@ +local ____lualib = require("lualib_bundle") +local __TS__Class = ____lualib.__TS__Class +local Set = ____lualib.Set +local __TS__New = ____lualib.__TS__New +local __TS__ArrayMap = ____lualib.__TS__ArrayMap +local __TS__StringTrim = ____lualib.__TS__StringTrim +local __TS__Decorate = ____lualib.__TS__Decorate +local ____exports = {} +local ____DayNightCycleManager = require("DayNightCycleManager") +local DayNightCycleManager = ____DayNightCycleManager.DayNightCycleManager +local ____modifier_cutscene_lock = require("modifiers.modifier_cutscene_lock") +local modifier_cutscene_lock = ____modifier_cutscene_lock.modifier_cutscene_lock +local ____modifier_cutscene_npc_lock = require("modifiers.modifier_cutscene_npc_lock") +local modifier_cutscene_npc_lock = ____modifier_cutscene_npc_lock.modifier_cutscene_npc_lock +local ____tstl_2Dutils = require("lib.tstl-utils") +local reloadable = ____tstl_2Dutils.reloadable +local ____player_connection_state = require("utils.player_connection_state") +local DOTA_CONNECTION_STATE = ____player_connection_state.DOTA_CONNECTION_STATE +local ____CutsceneFunctions = require("cutscenes.CutsceneFunctions") +local finishCameraStartEndingTeleportNow = ____CutsceneFunctions.finishCameraStartEndingTeleportNow +local ____CutsceneTable = require("cutscenes.CutsceneTable") +local CutsceneTable = ____CutsceneTable.CutsceneTable +local CUTSCENE_AUTO_SKIP_POLL_TIMER = "CutsceneDisconnectAutoSkip" +____exports.CutsceneManager = __TS__Class() +local CutsceneManager = ____exports.CutsceneManager +CutsceneManager.name = "CutsceneManager" +CutsceneManager.____file_path = "scripts/vscripts/cutscenes/CutsceneManager.lua" +function CutsceneManager.prototype.____constructor(self) + self.playedSceneIds = __TS__New(Set) + self.isGameEnding = false + self:registerEvents() +end +function CutsceneManager.prototype.isPlayerOfflineForCutsceneSkip(self, playerId) + if not PlayerResource:IsValidPlayerID(playerId) then + return true + end + return PlayerResource:GetConnectionState(playerId) ~= DOTA_CONNECTION_STATE.CONNECTED +end +function CutsceneManager.prototype.startAutoSkipDisconnectPoll(self) + Timers:RemoveTimer(CUTSCENE_AUTO_SKIP_POLL_TIMER) + Timers:CreateTimer( + CUTSCENE_AUTO_SKIP_POLL_TIMER, + { + callback = function() + if not self.active then + return nil + end + self:applyAutoSkipVotesForDisconnectedParticipants() + return 0.25 + end, + endTime = 0 + } + ) +end +function CutsceneManager.prototype.applyAutoSkipVotesForDisconnectedParticipants(self) + if not self.active then + return + end + for ____, playerId in ipairs(self.active.participantPlayerIds) do + do + if self.active.votedPlayers:has(playerId) then + goto __continue10 + end + if not self:isPlayerOfflineForCutsceneSkip(playerId) then + goto __continue10 + end + self.active.votedPlayers:add(playerId) + CustomGameEventManager:Send_ServerToAllClients("cutscene_vote_to_skip_success", {playerId = playerId}) + end + ::__continue10:: + end + self:tryFinishCutsceneIfAllParticipantsVoted() +end +function CutsceneManager.prototype.tryFinishCutsceneIfAllParticipantsVoted(self) + if not self.active then + return + end + if #self.active.participantPlayerIds == 0 then + return + end + for ____, playerId in ipairs(self.active.participantPlayerIds) do + if not self.active.votedPlayers:has(playerId) then + return + end + end + self:finishCutscene() +end +function CutsceneManager.prototype.registerEvents(self) + CustomGameEventManager:RegisterListener( + "end_cutscene", + function() return self:finishCutscene() end + ) + CustomGameEventManager:RegisterListener( + "cutscene_camera_change", + function(_, data) return self:onCameraChange(data) end + ) + CustomGameEventManager:RegisterListener( + "cutscene_dialog_action", + function(_, data) return self:onDialogAction(data) end + ) + CustomGameEventManager:RegisterListener( + "cutscene_dialog_function", + function(_, data) return self:onDialogFunction(data) end + ) + CustomGameEventManager:RegisterListener( + "cutscene_vote_to_skip", + function(_, data) return self:onVoteToSkip(data) end + ) +end +function CutsceneManager.prototype.notifyPlayerConnected(self, playerId) + if not self.active then + return + end + local player = PlayerResource:GetPlayer(playerId) + if not player then + return + end + local ____CustomGameEventManager_Send_ServerToPlayer_2 = CustomGameEventManager.Send_ServerToPlayer + local ____opt_0 = self.active.npc + ____CustomGameEventManager_Send_ServerToPlayer_2( + CustomGameEventManager, + player, + "start_cutscene", + { + npc = ____opt_0 and ____opt_0:entindex(), + scene = self.active.scene + } + ) +end +function CutsceneManager.prototype.startScene(self, sceneId, options) + if self.active then + print(((("[CutsceneManager] startScene(\"" .. sceneId) .. "\") отклонено: уже идёт \"") .. self.active.sceneId) .. "\"") + return false + end + local scene = self:getSceneConfig(sceneId) + if not scene then + print(("[CutsceneManager] startScene(\"" .. sceneId) .. "\") отклонено: нет сцены в CutsceneTable") + return false + end + if not (options and options.force) and self.playedSceneIds:has(sceneId) then + print(("[CutsceneManager] startScene(\"" .. sceneId) .. "\") отклонено: уже в playedSceneIds (без force)") + return false + end + local ____scene_npc_5 + if scene.npc then + ____scene_npc_5 = Entities:FindByName(nil, scene.npc) or nil + else + ____scene_npc_5 = nil + end + local npc = ____scene_npc_5 + local participantPlayerIds = __TS__ArrayMap( + self:getPlayers(), + function(____, p) return p.playerId end + ) + self.active = { + sceneId = sceneId, + scene = scene, + npc = npc, + votedPlayers = __TS__New(Set), + participantPlayerIds = participantPlayerIds, + onEnd = options and options.onEnd + } + self.playedSceneIds:add(sceneId) + DayNightCycleManager:getInstance():onCutsceneStarted() + self:applyAutoSkipVotesForDisconnectedParticipants() + self:startAutoSkipDisconnectPoll() + self:applySceneState() + CustomGameEventManager:Send_ServerToAllClients( + "start_cutscene", + { + npc = npc and npc:entindex(), + scene = scene + } + ) + local ____print_14 = print + local ____sceneId_13 = sceneId + local ____temp_12 = options and options.force + if ____temp_12 == nil then + ____temp_12 = false + end + ____print_14((((("[CutsceneManager] startScene(\"" .. ____sceneId_13) .. "\") OK, force=") .. tostring(____temp_12)) .. ", npc=") .. (scene.npc or "none")) + return true +end +function CutsceneManager.prototype.getSceneConfig(self, sceneId) + local loaded = require("cutscenes.CutsceneTable") + local ____opt_15 = loaded.CutsceneTable + return ____opt_15 and ____opt_15[sceneId] or CutsceneTable[sceneId] +end +function CutsceneManager.prototype.startEndingCutscene(self, victory) + if self.isGameEnding then + return + end + self.isGameEnding = true + self:startScene( + victory and "ending_victory" or "ending_defeat", + { + force = true, + onEnd = function() + local ____GameRules_SetGameWinner_18 = GameRules.SetGameWinner + local ____victory_17 + if victory then + ____victory_17 = DOTA_TEAM_GOODGUYS + else + ____victory_17 = DOTA_TEAM_BADGUYS + end + ____GameRules_SetGameWinner_18(GameRules, ____victory_17) + end + } + ) +end +function CutsceneManager.prototype.triggerMidWaveWarning(self, night, waveIndex) + if night == 1 and waveIndex == 2 then + self:startScene("mid_wave_warning") + end +end +function CutsceneManager.prototype.isCutsceneActive(self) + return self.active ~= nil +end +function CutsceneManager.prototype.resolveInitialCameraTarget(self, scene, hero, npc) + local d1 = scene.dialogs[1] + local cam = d1 and d1.camera + if cam and cam ~= "default" and type(cam) == "table" and cam.name ~= nil and string.lower(__TS__StringTrim(cam.name)) == "hero" then + return hero + end + return npc +end +function CutsceneManager.prototype.applySceneState(self) + if not self.active then + return + end + local ____self_active_21 = self.active + local scene = ____self_active_21.scene + local npc = ____self_active_21.npc + local players = self:getPlayers() + local npcPos = npc and npc:GetAbsOrigin() + local forward = npc and npc:GetForwardVector() + local npcAngles = npc and npc:GetAngles() + for ____, ____value in ipairs(players) do + local playerId = ____value.playerId + local hero = ____value.hero + do + if not hero then + goto __continue45 + end + local camTarget = self:resolveInitialCameraTarget(scene, hero, npc) + if camTarget ~= nil then + PlayerResource:SetCameraTarget(playerId, camTarget) + end + hero:AddNewModifier( + hero, + getModifierSourceAbility(nil, hero), + modifier_cutscene_lock.name, + {} + ) + hero:SetControllableByPlayer(playerId, false) + hero:Stop() + if not scene.disableTeleport and npcPos and forward and npcAngles then + local distance = 260 + playerId * 15 + hero:SetAngles(0, npcAngles.y + 180, 0) + FindClearSpaceForUnit(hero, npcPos + forward * distance, true) + end + end + ::__continue45:: + end + if scene.lockNpc and npc then + npc:AddNewModifier( + npc, + getModifierSourceAbility(nil, npc), + modifier_cutscene_npc_lock.name, + {} + ) + end +end +function CutsceneManager.prototype.clearSceneState(self) + if not self.active then + return + end + local players = self:getPlayers() + for ____, ____value in ipairs(players) do + local playerId = ____value.playerId + local hero = ____value.hero + do + if not hero then + goto __continue53 + end + PlayerResource:SetCameraTarget(playerId, nil) + CenterCameraOnUnit(playerId, hero) + hero:SetControllableByPlayer(playerId, true) + hero:RemoveModifierByName(modifier_cutscene_lock.name) + end + ::__continue53:: + end + if self.active.scene.lockNpc and self.active.npc then + self.active.npc:RemoveModifierByName(modifier_cutscene_npc_lock.name) + end +end +function CutsceneManager.prototype.finishCutscene(self) + if not self.active then + return + end + local finished = self.active + Timers:RemoveTimer(CUTSCENE_AUTO_SKIP_POLL_TIMER) + self:clearSceneState() + self.active = nil + DayNightCycleManager:getInstance():onCutsceneEnded() + do + pcall(function() + CustomGameEventManager:Send_ServerToAllClients("cutscene_closed", {}) + end) + end + if finished.sceneId == "camera_start_ending" then + finishCameraStartEndingTeleportNow(nil) + end + local ____this_29 + ____this_29 = finished.scene + local ____opt_28 = ____this_29.onSuccess + if ____opt_28 ~= nil then + ____opt_28(____this_29) + end + local ____opt_30 = finished.onEnd + if ____opt_30 ~= nil then + ____opt_30(finished) + end +end +function CutsceneManager.prototype.onDialogAction(self, data) + local action = data.dialog.action + if not action then + return + end + local unit = self:resolveDialogNpc(data.dialog.npc) + if not unit then + return + end + unit:StartGestureWithPlaybackRate(action.name, action.rate) +end +function CutsceneManager.prototype.onDialogFunction(self, data) + if not self.active then + return + end + local ____opt_34 = self.active.scene.dialogs[data.dialogId] + local ____opt_32 = ____opt_34 and ____opt_34.onStart + if ____opt_32 ~= nil then + ____opt_32(____opt_34) + end +end +function CutsceneManager.prototype.onCameraChange(self, data) + if not self.active then + return + end + local camera = data.dialog.camera + if not camera then + return + end + local players = self:getPlayers() + if camera == "default" then + for ____, ____value in ipairs(players) do + local playerId = ____value.playerId + PlayerResource:SetCameraTarget(playerId, self.active.npc) + end + return + end + if string.lower(__TS__StringTrim(camera.name)) == "hero" then + for ____, ____value in ipairs(players) do + local playerId = ____value.playerId + local hero = ____value.hero + do + if not hero then + goto __continue73 + end + PlayerResource:SetCameraTarget(playerId, hero) + end + ::__continue73:: + end + return + end + local target = Entities:FindByName(nil, camera.name) + if not target and self.active.npc ~= nil and IsValidEntity(self.active.npc) then + target = self.active.npc + end + if not target then + return + end + for ____, ____value in ipairs(players) do + local playerId = ____value.playerId + PlayerResource:SetCameraTarget(playerId, target) + end + if camera.vision and data.visionDuration and data.visionDuration > 0 then + local team = DOTA_TEAM_GOODGUYS + local ____opt_36 = players[1] + local hero = ____opt_36 and ____opt_36.hero + if hero then + team = hero:GetTeamNumber() + end + local ____opt_38 = Entities:FindByName(nil, camera.vision.name) + local visionPoint = ____opt_38 and ____opt_38:GetAbsOrigin() or target:GetAbsOrigin() + AddFOWViewer( + team, + visionPoint, + camera.vision.radius, + data.visionDuration, + false + ) + end +end +function CutsceneManager.prototype.onVoteToSkip(self, data) + if not self.active then + return + end + if self.active.votedPlayers:has(data.playerId) then + return + end + self.active.votedPlayers:add(data.playerId) + CustomGameEventManager:Send_ServerToAllClients("cutscene_vote_to_skip_success", {playerId = data.playerId}) + self:applyAutoSkipVotesForDisconnectedParticipants() + self:tryFinishCutsceneIfAllParticipantsVoted() +end +function CutsceneManager.prototype.resolveDialogNpc(self, name) + if name then + return Entities:FindByName(nil, name) or nil + end + local ____opt_40 = self.active + return ____opt_40 and ____opt_40.npc +end +function CutsceneManager.prototype.getPlayers(self) + local players = {} + do + local playerId = 0 + while playerId < DOTA_MAX_PLAYERS do + do + if not PlayerResource:IsValidPlayerID(playerId) then + goto __continue88 + end + local player = PlayerResource:GetPlayer(playerId) + if not player then + goto __continue88 + end + players[#players + 1] = { + playerId = playerId, + hero = PlayerResource:GetSelectedHeroEntity(playerId) + } + end + ::__continue88:: + playerId = playerId + 1 + end + end + return players +end +CutsceneManager = __TS__Decorate(CutsceneManager, CutsceneManager, {reloadable}, {kind = "class", name = "CutsceneManager"}) +____exports.CutsceneManager = CutsceneManager +return ____exports diff --git a/scripts/vscripts/cutscenes/cutscenetable.lua b/scripts/vscripts/cutscenes/cutscenetable.lua new file mode 100644 index 0000000..c04d8cd --- /dev/null +++ b/scripts/vscripts/cutscenes/cutscenetable.lua @@ -0,0 +1,72 @@ +--[[ Generated with https://github.com/TypeScriptToLua/TypeScriptToLua ]] +local ____exports = {} +local ____CutsceneFunctions = require("cutscenes.CutsceneFunctions") +local CutsceneFunctions = ____CutsceneFunctions.CutsceneFunctions +--- Камера крутится вокруг `name` (SetCameraTarget). highView давал вид «как в матче» и уводил в кусты; +-- fromNpcEyes у Денни низко садился за деревья — убрано. Ближе дистанции, якорь чаще на `hero`. +____exports.CutsceneTable = {camera_start_ending = { + name = "camera_start_ending", + npc = "npc_quest_giver_denny", + disableTeleport = true, + lockNpc = false, + dialogs = { + [1] = {duration = 3.5, onStart = CutsceneFunctions.camera_start_ending, text = "#cutscene_camera_start_ending_1", camera = { + name = "hero", + fromNpcEyes = false, + highView = false, + distance = 635, + pitch = 24, + lerpDuration = 0.55, + angle = "back", + animation = {distance = -6} + }}, + [2] = {duration = 4, onStart = CutsceneFunctions.camera_heroes_expedition, text = "#cutscene_camera_start_ending_2", camera = { + name = "hero", + highView = false, + lookAtName = "npc_quest_giver_denny", + distance = 905, + pitch = 29, + lerpDuration = 0.85, + animation = {distance = 9} + }}, + [3] = {duration = 3.3, onStart = CutsceneFunctions.camera_heroes_endlap, text = "#cutscene_camera_start_ending_3", camera = { + name = "hero", + fromNpcEyes = true, + highView = false, + lookAtName = "npc_quest_giver_denny", + distance = 1155, + pitch = 32, + lerpDuration = 0 + }}, + [4] = {duration = 5.5, text = "#cutscene_camera_start_ending_4", camera = { + name = "hero", + highView = false, + lookAtName = "hero", + distance = 565, + pitch = 34, + lerpDuration = 2, + animation = {distance = 12} + }}, + [5] = {duration = 2, text = "#cutscene_camera_start_ending_5", camera = { + name = "npc_quest_giver_denny", + fromNpcEyes = false, + highView = true, + angle = "face", + distance = 295, + pitch = 26, + lerpDuration = 0.55 + }}, + [6] = {duration = 2, npc = "npc_quest_giver_denny", text = "#cutscene_camera_start_ending_6", camera = { + name = "npc_boss_nevermore", + fromNpcEyes = false, + highView = true, + angle = "face", + heightDistanceBonus = 300, + distance = 695, + pitch = 32, + lerpDuration = 0.55, + animation = {angle = 12} + }} + } +}} +return ____exports diff --git a/scripts/vscripts/daynightcyclemanager.lua b/scripts/vscripts/daynightcyclemanager.lua new file mode 100644 index 0000000..93e7448 --- /dev/null +++ b/scripts/vscripts/daynightcyclemanager.lua @@ -0,0 +1,512 @@ +local ____lualib = require("lualib_bundle") +local __TS__Class = ____lualib.__TS__Class +local Map = ____lualib.Map +local __TS__New = ____lualib.__TS__New +local __TS__Iterator = ____lualib.__TS__Iterator +local __TS__ArrayIncludes = ____lualib.__TS__ArrayIncludes +local ____exports = {} +local ____WaveManager = require("WaveManager") +local WaveManager = ____WaveManager.WaveManager +local ____blackshop = require("blackshop") +local BlackShop = ____blackshop.BlackShop +local ____custom_game_events = require("custom_game_events") +local broadcastZiCyclePhase = ____custom_game_events.broadcastZiCyclePhase +local ____CardSystem = require("cards.CardSystem") +local shouldPlayerSkipDawnCardSelection = ____CardSystem.shouldPlayerSkipDawnCardSelection +local ShowCardSelectionToPlayer = ____CardSystem.ShowCardSelectionToPlayer +local ____card_22 = require("cards.examples.card_22") +local notifyCard22MorningStarted = ____card_22.notifyCard22MorningStarted +local notifyCard22NightStarted = ____card_22.notifyCard22NightStarted +local ____card_38 = require("cards.examples.card_38") +local notifyCard38MorningStarted = ____card_38.notifyCard38MorningStarted +local ____card_45 = require("cards.examples.card_45") +local notifyCard45MorningStarted = ____card_45.notifyCard45MorningStarted +local ____card_49 = require("cards.examples.card_49") +local notifyCard49MorningStarted = ____card_49.notifyCard49MorningStarted +local ____card_51 = require("cards.examples.card_51") +local notifyCard51MorningStarted = ____card_51.notifyCard51MorningStarted +local ____card_63 = require("cards.examples.card_63") +local notifyCard63MorningStarted = ____card_63.notifyCard63MorningStarted +local notifyCard63NightStarted = ____card_63.notifyCard63NightStarted +local ____card_52_effects = require("cards.card_52_effects") +local CARD_52_MIN_NIGHT_DURATION_SEC = ____card_52_effects.CARD_52_MIN_NIGHT_DURATION_SEC +local getNightDurationReductionSecFromCard52 = ____card_52_effects.getNightDurationReductionSecFromCard52 +local ____modifier_stats_multiplier = require("modifiers.modifier_stats_multiplier") +local invalidateStatsMultiplierSumCache = ____modifier_stats_multiplier.invalidateStatsMultiplierSumCache +--- Номер финальной ночи (совпадает с WAVE_SETTINGS в WaveManager). +local FINAL_NIGHT_NUMBER = 7 +--- Сколько секунд длится эта ночь на таймере UI и до утра. +local FINAL_NIGHT_DURATION_SEC = 9999 +--- Огонь на зомби в дневной фазе (Crownfall). +local DAYTIME_ZOMBIE_BURN_FIRE_PFX = "particles/econ/items/wraith_king/wraith_king_ti6_bracer/wraith_king_ti6_hellfireblast_debuff_fire.vpcf" +local ENABLE_DAY_NIGHT_DEBUG_LOG = false +____exports.DayNightCycleManager = __TS__Class() +local DayNightCycleManager = ____exports.DayNightCycleManager +DayNightCycleManager.name = "DayNightCycleManager" +DayNightCycleManager.____file_path = "scripts/vscripts/DayNightCycleManager.lua" +function DayNightCycleManager.prototype.____constructor(self) + self.dayNightCycleDuration = 300 + self.isDayTime = false + self.dayNightTimer = "DayNightTimer" + self.dayDuration = 300 + self.nightDuration = 300 + self.nextDayDuration = 300 + self.nextNightDuration = 300 + self.timeLeft = 0 + self.morningSequence = 0 + self.cutsceneDayNightFreezeDepth = 0 + self.freezeTimeLeftDecrement = false + self.zombieUnitNames = { + "npc_wave_zombie", + "npc_wave_dead_skeleton", + "npc_wave_half_zombie", + "npc_wave_bearst_zombie", + "npc_wave_toxin_zombie", + "npc_wave_skeleton_zombie", + "npc_wave_skeleton_zombie_half", + "npc_wave_dead_skeleton_archer" + } + self.zombieDayBurnFireByEnt = __TS__New(Map) +end +function DayNightCycleManager.prototype.clearAllDayBurnFireParticles(self) + for ____, ____value in __TS__Iterator(self.zombieDayBurnFireByEnt) do + local pfx = ____value[2] + ParticleManager:DestroyParticle(pfx, false) + ParticleManager:ReleaseParticleIndex(pfx) + end + self.zombieDayBurnFireByEnt:clear() +end +function DayNightCycleManager.prototype.pruneDayBurnFireParticles(self) + local toRemove = {} + for ____, ____value in __TS__Iterator(self.zombieDayBurnFireByEnt) do + local ei = ____value[1] + local pfx = ____value[2] + local u = EntIndexToHScript(ei) + if not u or not IsValidEntity(u) or not u:IsAlive() then + ParticleManager:DestroyParticle(pfx, false) + ParticleManager:ReleaseParticleIndex(pfx) + toRemove[#toRemove + 1] = ei + end + end + for ____, ei in ipairs(toRemove) do + self.zombieDayBurnFireByEnt:delete(ei) + end +end +function DayNightCycleManager.prototype.ensureDayBurnFireOnZombie(self, zombieNPC) + local ei = zombieNPC:entindex() + if self.zombieDayBurnFireByEnt:has(ei) then + return + end + local pfx = ParticleManager:CreateParticle(DAYTIME_ZOMBIE_BURN_FIRE_PFX, PATTACH_ABSORIGIN_FOLLOW, zombieNPC) + self.zombieDayBurnFireByEnt:set(ei, pfx) +end +function DayNightCycleManager.prototype.applyDaytimeZombieBurnDamage(self) + if GetMapName() ~= "invasion" then + return + end + self:pruneDayBurnFireParticles() + for ____, zombieNPC in ipairs(WaveManager:getInstance():getAliveSpawnedWaveUnits()) do + do + if not __TS__ArrayIncludes( + self.zombieUnitNames, + zombieNPC:GetUnitName() + ) then + goto __continue16 + end + ApplyDamage({ + victim = zombieNPC, + attacker = zombieNPC, + damage = 50 + zombieNPC:GetMaxHealth() * 0.02, + damage_type = DAMAGE_TYPE_PURE + }) + self:ensureDayBurnFireOnZombie(zombieNPC) + end + ::__continue16:: + end +end +function DayNightCycleManager.getInstance(self) + if not ____exports.DayNightCycleManager.instance then + ____exports.DayNightCycleManager.instance = __TS__New(____exports.DayNightCycleManager) + end + return ____exports.DayNightCycleManager.instance +end +function DayNightCycleManager.prototype.Initialize(self) + self.isDayTime = false + self.dayDuration = 300 + self.nightDuration = 300 + self.nextDayDuration = 300 + self.nextNightDuration = 300 + self:StartDayNightCycle() +end +function DayNightCycleManager.prototype.StartDayNightCycle(self) + self.timeLeft = self.isDayTime and self.dayDuration or self.nightDuration + Timers:CreateTimer( + "TimeLeftTimer", + { + callback = function() + if not self.freezeTimeLeftDecrement then + self.timeLeft = self.timeLeft - 1 + end + do + pcall(function() + CustomGameEventManager:Send_ServerToAllClients("day_night_timer_update", {isDay = self.isDayTime, timeLeft = self.timeLeft}) + end) + end + return 1 + end, + endTime = 0 + } + ) + Timers:CreateTimer( + self.dayNightTimer, + { + callback = function() return self:SwitchDayNight() end, + endTime = 0 + } + ) +end +function DayNightCycleManager.prototype.runMorningSequenceAndPostNightCardSelection(self) + self.morningSequence = self.morningSequence + 1 + local nightForCards = WaveManager:getInstance():GetCurrentNight() + notifyCard22MorningStarted(nil, nightForCards, self.morningSequence) + notifyCard38MorningStarted(nil, nightForCards, self.morningSequence) + notifyCard45MorningStarted(nil, self.morningSequence) + notifyCard49MorningStarted(nil, self.morningSequence) + notifyCard63MorningStarted(nil, self.morningSequence) + notifyCard51MorningStarted(nil, self.morningSequence) + broadcastZiCyclePhase(nil, {isDay = true, morningSequence = self.morningSequence, nightIndex = nightForCards}) + self:ShowCardSelectionAfterNight() +end +function DayNightCycleManager.prototype.SwitchDayNight(self) + self.isDayTime = not self.isDayTime + BlackShop:getInstance():RefreshShop() + self:refreshHeroStatsAfterDayNightSwitch() + if self.isDayTime then + GameRules:SetTimeOfDay(0.75) + self.dayDuration = self.nextDayDuration + self.timeLeft = self.dayDuration + self:runMorningSequenceAndPostNightCardSelection() + self:clearAllDayBurnFireParticles() + Timers:CreateTimer( + "ZombieBurnTimer", + { + callback = function() + if self.isDayTime then + self:applyDaytimeZombieBurnDamage() + return 1 + end + return nil + end, + endTime = 0 + } + ) + return self.dayDuration + else + broadcastZiCyclePhase( + nil, + { + isDay = false, + morningSequence = self.morningSequence, + nightIndex = WaveManager:getInstance():GetCurrentNight() + } + ) + GameRules:SetTimeOfDay(0.25) + local card52NightReduce = getNightDurationReductionSecFromCard52(nil) + self.nightDuration = math.max(CARD_52_MIN_NIGHT_DURATION_SEC, self.nextNightDuration - card52NightReduce) + self.timeLeft = self.nightDuration + notifyCard63NightStarted(nil) + notifyCard22NightStarted(nil) + Timers:RemoveTimer("ZombieBurnTimer") + self:clearAllDayBurnFireParticles() + local waveManager = WaveManager:getInstance() + Timers:CreateTimer( + 0.1, + function() + waveManager:SetCurrentNight(waveManager:GetCurrentNight() + 1) + local night = waveManager:GetCurrentNight() + if night == FINAL_NIGHT_NUMBER then + self:SetNightDuration(FINAL_NIGHT_DURATION_SEC) + self.nightDuration = FINAL_NIGHT_DURATION_SEC + self:SetTimeLeft(FINAL_NIGHT_DURATION_SEC) + end + waveManager:SpawnNextWave() + CustomGameEventManager:Send_ServerToAllClients( + "wave_started", + {night = waveManager:GetCurrentNight()} + ) + return nil + end + ) + return self.nightDuration + end +end +function DayNightCycleManager.prototype.refreshHeroStatsAfterDayNightSwitch(self) + local n = 0 + do + local i = 0 + while i < DOTA_MAX_PLAYERS do + local hero = PlayerResource:GetSelectedHeroEntity(i) + if hero and IsValidEntity(hero) and hero:IsRealHero() then + invalidateStatsMultiplierSumCache(nil, hero) + hero:CalculateStatBonus(true) + n = n + 1 + end + i = i + 1 + end + end + if ENABLE_DAY_NIGHT_DEBUG_LOG then + print((("[DayNight] refreshHeroStatsAfterDayNightSwitch isDay=" .. tostring(self.isDayTime)) .. " героев=") .. tostring(n)) + end +end +function DayNightCycleManager.prototype.SetDayNightCycleDuration(self, seconds) + if seconds < 60 then + seconds = 60 + end + self.dayNightCycleDuration = seconds + Timers:RemoveTimer(self.dayNightTimer) + Timers:RemoveTimer("TimeLeftTimer") + self:StartDayNightCycle() +end +function DayNightCycleManager.prototype.GetDayNightCycleDuration(self) + return self.dayNightCycleDuration +end +function DayNightCycleManager.prototype.IsDaytime(self) + return self.isDayTime +end +function DayNightCycleManager.prototype.CanVoteSkipToNight(self) + if GameRules:State_Get() ~= DOTA_GAMERULES_STATE_GAME_IN_PROGRESS then + return false + end + if not self.isDayTime then + return false + end + if self.cutsceneDayNightFreezeDepth > 0 then + return false + end + if self.freezeTimeLeftDecrement then + return false + end + return true +end +function DayNightCycleManager.prototype.SetDayDuration(self, seconds) + if seconds < 60 then + seconds = 60 + end + self.nextDayDuration = seconds +end +function DayNightCycleManager.prototype.adjustNextDayDurationBy(self, deltaSec) + if deltaSec == 0 then + return + end + self:SetDayDuration(self.nextDayDuration + deltaSec) +end +function DayNightCycleManager.prototype.SetNightDuration(self, seconds) + if seconds < 60 then + seconds = 60 + end + self.nextNightDuration = seconds +end +function DayNightCycleManager.prototype.GetDayDuration(self) + return self.dayDuration +end +function DayNightCycleManager.prototype.GetNightDuration(self) + return self.nightDuration +end +function DayNightCycleManager.prototype.GetTimeLeft(self) + return self.timeLeft +end +function DayNightCycleManager.prototype.onCutsceneStarted(self) + if GameRules:State_Get() < DOTA_GAMERULES_STATE_GAME_IN_PROGRESS then + return + end + self.cutsceneDayNightFreezeDepth = self.cutsceneDayNightFreezeDepth + 1 + if self.cutsceneDayNightFreezeDepth ~= 1 then + return + end + Timers:RemoveTimer(self.dayNightTimer) + self.freezeTimeLeftDecrement = true + GameRules:SetTimeOfDay(0.25) +end +function DayNightCycleManager.prototype.onCutsceneEnded(self) + self.cutsceneDayNightFreezeDepth = self.cutsceneDayNightFreezeDepth - 1 + if self.cutsceneDayNightFreezeDepth < 0 then + self.cutsceneDayNightFreezeDepth = 0 + end + if self.cutsceneDayNightFreezeDepth > 0 then + return + end + self.freezeTimeLeftDecrement = false + if GameRules:State_Get() < DOTA_GAMERULES_STATE_GAME_IN_PROGRESS then + return + end + GameRules:SetTimeOfDay(self.isDayTime and 0.75 or 0.25) + Timers:RemoveTimer(self.dayNightTimer) + Timers:CreateTimer( + self.dayNightTimer, + { + callback = function() return self:SwitchDayNight() end, + endTime = self.timeLeft + } + ) + do + pcall(function() + CustomGameEventManager:Send_ServerToAllClients("day_night_timer_update", {isDay = self.isDayTime, timeLeft = self.timeLeft}) + end) + end +end +function DayNightCycleManager.prototype.SyncStateToPlayer(self, playerId) + do + local ____try, ____hasReturned, ____returnValue = pcall(function() + local player = PlayerResource:GetPlayer(playerId) + if not player then + return true + end + CustomGameEventManager:Send_ServerToPlayer(player, "day_night_timer_update", {isDay = self.isDayTime, timeLeft = self.timeLeft}) + end) + if ____try and ____hasReturned then + return ____returnValue + end + end +end +function DayNightCycleManager.prototype.AdjustTimeLeft(self, delta) + local minTime = 1 + local maxTime = 356400 + local newTimeLeft = self.timeLeft + delta + if newTimeLeft < minTime then + newTimeLeft = minTime + elseif newTimeLeft > maxTime then + newTimeLeft = maxTime + end + self.timeLeft = newTimeLeft + Timers:RemoveTimer(self.dayNightTimer) + Timers:CreateTimer( + self.dayNightTimer, + { + callback = function() return self:SwitchDayNight() end, + endTime = self.timeLeft + } + ) +end +function DayNightCycleManager.prototype.SetTimeLeft(self, seconds) + local minTime = 1 + local maxTime = 356400 + local newTimeLeft = math.floor(seconds) + if newTimeLeft < minTime then + newTimeLeft = minTime + elseif newTimeLeft > maxTime then + newTimeLeft = maxTime + end + self.timeLeft = newTimeLeft + Timers:RemoveTimer(self.dayNightTimer) + Timers:CreateTimer( + self.dayNightTimer, + { + callback = function() return self:SwitchDayNight() end, + endTime = self.timeLeft + } + ) +end +function DayNightCycleManager.prototype.TryInstantSwitchDayToNightFromVote(self) + if GameRules:State_Get() ~= DOTA_GAMERULES_STATE_GAME_IN_PROGRESS then + return false + end + if not self.isDayTime then + return false + end + if self.cutsceneDayNightFreezeDepth > 0 or self.freezeTimeLeftDecrement then + return false + end + Timers:RemoveTimer(self.dayNightTimer) + self:SwitchDayNight() + Timers:CreateTimer( + self.dayNightTimer, + { + callback = function() return self:SwitchDayNight() end, + endTime = self.timeLeft + } + ) + do + pcall(function() + CustomGameEventManager:Send_ServerToAllClients("day_night_timer_update", {isDay = self.isDayTime, timeLeft = self.timeLeft}) + end) + end + return true +end +function DayNightCycleManager.prototype.ForceNightIn(self, seconds) + if not self.isDayTime then + return + end + local delay = math.max( + 1, + math.floor(seconds) + ) + self.timeLeft = delay + Timers:RemoveTimer(self.dayNightTimer) + Timers:CreateTimer( + self.dayNightTimer, + { + callback = function() return self:SwitchDayNight() end, + endTime = delay + } + ) +end +function DayNightCycleManager.prototype.ForceDay(self) + if self.isDayTime then + return + end + Timers:RemoveTimer(self.dayNightTimer) + Timers:RemoveTimer("ZombieBurnTimer") + self:clearAllDayBurnFireParticles() + self.isDayTime = true + BlackShop:getInstance():RefreshShop() + self:refreshHeroStatsAfterDayNightSwitch() + GameRules:SetTimeOfDay(0.75) + self.dayDuration = self.nextDayDuration + self.timeLeft = self.dayDuration + self:runMorningSequenceAndPostNightCardSelection() + Timers:CreateTimer( + "ZombieBurnTimer", + { + callback = function() + if self.isDayTime then + self:applyDaytimeZombieBurnDamage() + return 1 + end + return nil + end, + endTime = 0 + } + ) + Timers:CreateTimer( + self.dayNightTimer, + { + callback = function() return self:SwitchDayNight() end, + endTime = self.dayDuration + } + ) +end +function DayNightCycleManager.prototype.ShowCardSelectionAfterNight(self) + do + local playerId = 0 + while playerId < PlayerResource:GetPlayerCount() do + do + local pid = playerId + local player = PlayerResource:GetPlayer(pid) + if not player then + goto __continue92 + end + local hero = player:GetAssignedHero() + if not hero or not hero:IsAlive() then + goto __continue92 + end + if shouldPlayerSkipDawnCardSelection(nil, pid) then + goto __continue92 + end + ShowCardSelectionToPlayer(nil, pid, 3, "night_completion") + end + ::__continue92:: + playerId = playerId + 1 + end + end +end +return ____exports diff --git a/scripts/vscripts/death_sentence/contracts_backend.lua b/scripts/vscripts/death_sentence/contracts_backend.lua new file mode 100644 index 0000000..95799a2 --- /dev/null +++ b/scripts/vscripts/death_sentence/contracts_backend.lua @@ -0,0 +1,212 @@ +local ____lualib = require("lualib_bundle") +local __TS__ArrayIsArray = ____lualib.__TS__ArrayIsArray +local __TS__NumberIsFinite = ____lualib.__TS__NumberIsFinite +local ____exports = {} +local ____api_helper = require("api_helper") +local setApiHeaders = ____api_helper.setApiHeaders +local encodeApiBody = ____api_helper.encodeApiBody +local ____server_config = require("server_config") +local SERVER_CONFIG = ____server_config.SERVER_CONFIG +local ____contracts_registry = require("death_sentence.contracts_registry") +local normalizeDeathSentenceTitleIndex = ____contracts_registry.normalizeDeathSentenceTitleIndex +local normalizeDeathSentenceContractDurability = ____contracts_registry.normalizeDeathSentenceContractDurability +local normalizeDeathSentenceContractDurabilityMax = ____contracts_registry.normalizeDeathSentenceContractDurabilityMax +local getDeathSentenceComplicationPickCount = ____contracts_registry.getDeathSentenceComplicationPickCount +local LOG_PREFIX = "[DSContractsAPI]" +____exports.DEATH_SENTENCE_CONTRACT_ROSTER_CAP = 90 +--- Старые записи (armor/movespeed/full_brutality) приводим к `none` — геймплей только через усложнения-абилки. +local function normalizeDeathSentenceTraitId(self, _v) + return "none" +end +local function isValidRarity(self, v) + return v == "common" or v == "rare" or v == "epic" or v == "legendary" or v == "mythic" +end +--- Разбор ответа GET / тела сохранённого JSON → массив экземпляров (0…CAP) или null при неверной структуре. +function ____exports.parseDeathSentenceContractRosterPayload(self, raw) + if not raw or type(raw) ~= "table" then + return nil + end + local root = raw + local wrap = root.death_sentence_contracts + if not wrap or type(wrap) ~= "table" then + return nil + end + local rosterRaw = wrap.roster + if not __TS__ArrayIsArray(rosterRaw) then + return nil + end + local out = {} + for ____, row in ipairs(rosterRaw) do + do + if not row or type(row) ~= "table" then + goto __continue8 + end + local o = row + local instanceId = o.instanceId + local serialNum = type(o.serial) == "number" and o.serial or tonumber(o.serial) + if type(serialNum) ~= "number" or not __TS__NumberIsFinite(serialNum) then + goto __continue8 + end + local rarity = o.rarity + local rmNum = type(o.rewardMultiplier) == "number" and o.rewardMultiplier or tonumber(o.rewardMultiplier) + if type(rmNum) ~= "number" or not __TS__NumberIsFinite(rmNum) then + goto __continue8 + end + local compRaw = o.complicationIds + if type(instanceId) ~= "string" or #instanceId == 0 then + goto __continue8 + end + if not isValidRarity(nil, rarity) then + goto __continue8 + end + local complicationIds = {} + if __TS__ArrayIsArray(compRaw) then + for ____, c in ipairs(compRaw) do + if type(c) == "string" and #c > 0 then + complicationIds[#complicationIds + 1] = c + end + end + end + local maxComp = getDeathSentenceComplicationPickCount(nil, rarity) + while #complicationIds > maxComp do + table.remove(complicationIds) + end + local titleIndex = normalizeDeathSentenceTitleIndex(nil, o.titleIndex, instanceId) + local durability = normalizeDeathSentenceContractDurability(nil, o.durability, instanceId) + local ____o_durabilityMax_0 = o.durabilityMax + if ____o_durabilityMax_0 == nil then + ____o_durabilityMax_0 = o.durability_max + end + local rawMax = ____o_durabilityMax_0 + local durabilityMax = normalizeDeathSentenceContractDurabilityMax(nil, rawMax, durability, instanceId) + local inst = { + instanceId = instanceId, + serial = serialNum, + titleIndex = titleIndex, + rarity = rarity, + rewardMultiplier = rmNum, + traitId = normalizeDeathSentenceTraitId(nil, o.traitId), + complicationIds = complicationIds, + durability = durability, + durabilityMax = durabilityMax + } + local fav = o.favorite + if fav == true or fav == 1 then + inst.favorite = true + elseif fav == false or fav == 0 then + inst.favorite = false + end + local pin = o.pinned + if pin == true or pin == 1 then + inst.pinned = true + elseif pin == false or pin == 0 then + inst.pinned = false + end + out[#out + 1] = inst + end + ::__continue8:: + end + while #out > ____exports.DEATH_SENTENCE_CONTRACT_ROSTER_CAP do + table.remove(out) + end + return out +end +local function decodeJsonBody(self, body) + do + local function ____catch() + return true, nil + end + local ____try, ____hasReturned, ____returnValue = pcall(function() + return true, {json.decode(body)} + end) + if not ____try then + ____hasReturned, ____returnValue = ____catch() + end + if ____hasReturned then + return ____returnValue + end + end +end +--- Извлечь объект из ответа API (массив из одного элемента или объект). +local function unwrapApiJsonObject(self, decoded) + if __TS__ArrayIsArray(decoded) and #decoded > 0 then + return decoded[1] + end + return decoded +end +--- Загрузить ростер с бэка для steam_id. В колбэке roster=null при ошибке / пусто / невалидно. +function ____exports.loadDeathSentenceContractsFromBackend(self, steamId, done) + local url = ((SERVER_CONFIG.API_URL .. "/player/") .. steamId) .. "/death_sentence_contracts" + local request = CreateHTTPRequest("GET", url) + setApiHeaders(nil, request) + request:Send(function(result) + if result.StatusCode < 200 or result.StatusCode >= 300 then + print((((LOG_PREFIX .. " GET fail steam=") .. steamId) .. " code=") .. tostring(result.StatusCode)) + done(nil, nil) + return + end + local bodyStr = result.Body ~= nil and tostring(result.Body) or "" + if #bodyStr == 0 then + done(nil, nil) + return + end + local decoded = decodeJsonBody(nil, bodyStr) + local obj = unwrapApiJsonObject(nil, decoded) + local roster = ____exports.parseDeathSentenceContractRosterPayload(nil, obj) + if roster == nil then + print((LOG_PREFIX .. " GET parse invalid steam=") .. steamId) + done(nil, nil) + return + end + print((((LOG_PREFIX .. " GET ok steam=") .. steamId) .. " count=") .. tostring(#roster)) + done(nil, roster) + end) +end +--- Сохранить ростер на бэк для одного steam_id. +function ____exports.saveDeathSentenceContractsToBackend(self, steamId, roster, done) + local url = ((SERVER_CONFIG.API_URL .. "/player/") .. steamId) .. "/death_sentence_contracts" + local request = CreateHTTPRequest("PUT", url) + setApiHeaders(nil, request) + local payload = {death_sentence_contracts = {roster = roster}} + request:SetHTTPRequestRawPostBody( + "application/json", + encodeApiBody(nil, payload) + ) + request:Send(function(result) + local ok = result.StatusCode >= 200 and result.StatusCode < 300 + if ok then + print((LOG_PREFIX .. " PUT ok steam=") .. steamId) + else + print((((LOG_PREFIX .. " PUT fail steam=") .. steamId) .. " code=") .. tostring(result.StatusCode)) + end + if done then + done(nil, ok) + end + end) +end +--- Сохранить один и тот же ростер всем подключённым игрокам (одинаковая сетка лобби). +function ____exports.saveDeathSentenceContractsRosterToAllConnectedPlayers(self, roster) + do + local i = 0 + while i < DOTA_MAX_PLAYERS do + do + local pid = i + if not PlayerResource:IsValidPlayerID(pid) then + goto __continue41 + end + local steamId = PlayerResource:GetSteamAccountID(pid) + if not steamId or steamId == 0 then + goto __continue41 + end + ____exports.saveDeathSentenceContractsToBackend( + nil, + tostring(steamId), + roster + ) + end + ::__continue41:: + i = i + 1 + end + end +end +return ____exports diff --git a/scripts/vscripts/death_sentence/contracts_registry.lua b/scripts/vscripts/death_sentence/contracts_registry.lua new file mode 100644 index 0000000..c82af17 --- /dev/null +++ b/scripts/vscripts/death_sentence/contracts_registry.lua @@ -0,0 +1,332 @@ +local ____lualib = require("lualib_bundle") +local Set = ____lualib.Set +local __TS__New = ____lualib.__TS__New +local __TS__NumberIsFinite = ____lualib.__TS__NumberIsFinite +local ____exports = {} +local grantContractCreepAbility +function grantContractCreepAbility(self, unit, abilityName) + if not IsValidEntity(unit) or not unit:IsAlive() then + return + end + if unit:FindAbilityByName(abilityName) then + return + end + do + pcall(function() + local ab = unit:AddAbility(abilityName) + if ab ~= nil then + local maxLevel = ab:GetMaxLevel() + ab:SetLevel(maxLevel > 0 and maxLevel or 1) + end + end) + end +end +--- Сколько вариантов названия «приговор» в локализации (#ds_sentence_title_001 …). +____exports.DEATH_SENTENCE_TITLE_INDEX_MAX = 100 +--- Восстановление прочности контракта до максимума за донат-осколки (магазин). +____exports.DEATH_SENTENCE_CONTRACT_REPAIR_DONATE_COST = 25 +--- Только усложнения, дающие крипу способность из wave_creep_abilities (без чистых статов / глобальных модификаторов). +____exports.DEATH_SENTENCE_COMPLICATION_POOL = { + "ds_complication_explode", + "ds_complication_zombie_virus", + "ds_complication_ghost_evasive", + "ds_complication_phasing_march", + "ds_complication_zombie_armor_decress", + "ds_complication_toxin", + "ds_complication_full_brutality", + "ds_complication_desperate_vampirism", + "ds_complication_head_leap", + "ds_complication_bone_armor" +} +--- Сколько случайных «дебаффов» волн (усложнений) по редкости приговора. +function ____exports.getDeathSentenceComplicationPickCount(self, rarity) + if rarity == "common" then + return 0 + end + if rarity == "rare" then + return 1 + end + if rarity == "epic" then + return 2 + end + if rarity == "legendary" then + return 3 + end + return 4 +end +--- Шанс (1…100), что при появлении врага на него повесится одна строка усложнения из приговора. +-- Броски по строкам независимы — одному юниту может достаться сразу несколько абилок. +____exports.DEATH_SENTENCE_CONTRACT_CREEP_ABILITY_SPAWN_CHANCE_PCT = 25 +--- Юниты ивентов (виспы, рыбалка, бомбы) — без пассивок усложнения приговора при спавне. +local DEATH_SENTENCE_CONTRACT_BUFF_BLOCKED_UNITS = __TS__New(Set, {"npc_wisps", "npc_fish_1", "npc_fish_2", "npc_bomb"}) +--- Усложнение приговора → пассивка крипа из wave_creep_abilities (только то, что вешается при спавне). +local DEATH_SENTENCE_COMPLICATION_CREEP_ABILITY = { + ds_complication_explode = "zombie_death_explosion", + ds_complication_zombie_virus = "zombie_virus", + ds_complication_ghost_evasive = "ghost_evasive", + ds_complication_phasing_march = "wave_phasing_march", + ds_complication_zombie_armor_decress = "zombie_armor_decress", + ds_complication_toxin = "toxin", + ds_complication_full_brutality = "wave_full_brutality", + ds_complication_desperate_vampirism = "wave_desperate_vampirism", + ds_complication_head_leap = "contract_head_leap", + ds_complication_bone_armor = "bone_armor" +} +local function roundMult2(self, x) + return math.floor(x * 100 + 0.5) / 100 +end +local function rollRarity(self) + local r = RandomInt(1, 100) + if r <= 40 then + return "common" + end + if r <= 70 then + return "rare" + end + if r <= 90 then + return "epic" + end + if r <= 98 then + return "legendary" + end + return "mythic" +end +local function rollRewardMultiplierForRarity(self, r) + if r == "common" then + return roundMult2( + nil, + RandomFloat(3, 4) + ) + end + if r == "rare" then + return roundMult2( + nil, + RandomFloat(4, 5) + ) + end + if r == "epic" then + return roundMult2( + nil, + RandomFloat(5, 6) + ) + end + if r == "legendary" then + return roundMult2( + nil, + RandomFloat(6, 7) + ) + end + return roundMult2( + nil, + RandomFloat(7, 9) + ) +end +--- Детерминированный индекс названия для старых записей без titleIndex. +function ____exports.inferDeathSentenceTitleIndexFromInstanceId(self, instanceId) + local h = 0 + do + local i = 0 + while i < #instanceId do + local ch = string.byte(instanceId, i + 1) or 0 + h = (h * 31 + ch) % ____exports.DEATH_SENTENCE_TITLE_INDEX_MAX + i = i + 1 + end + end + return h + 1 +end +--- Детерминированная прочность 1…5 для старых записей без поля (без RNG при каждом GET). +function ____exports.inferDeathSentenceDurabilityFromInstanceId(self, instanceId) + local h = 0 + do + local i = 0 + while i < #instanceId do + local ch = string.byte(instanceId, i + 1) or 0 + h = (h * 31 + ch) % 1000000 + i = i + 1 + end + end + return h % 5 + 1 +end +function ____exports.rollDeathSentenceContractDurability(self) + return RandomInt(1, 5) +end +function ____exports.normalizeDeathSentenceContractDurability(self, raw, instanceId) + local n = type(raw) == "number" and raw or tonumber(raw) + if type(n) == "number" and __TS__NumberIsFinite(n) then + local f = math.floor(n) + if f >= 1 and f <= 5 then + return f + end + end + return ____exports.inferDeathSentenceDurabilityFromInstanceId(nil, instanceId) +end +--- Макс. прочность из JSON; если поля нет или битое — не ниже текущей `currentDurability`. +function ____exports.normalizeDeathSentenceContractDurabilityMax(self, rawMax, currentDurability, instanceId) + local n = type(rawMax) == "number" and rawMax or tonumber(rawMax) + local m + if type(n) == "number" and __TS__NumberIsFinite(n) then + local f = math.floor(n) + if f >= 1 and f <= 5 then + m = f + else + m = currentDurability + end + else + m = currentDurability + end + return math.max(m, currentDurability) +end +function ____exports.normalizeDeathSentenceTitleIndex(self, raw, instanceId) + local n = type(raw) == "number" and raw or tonumber(raw) + if type(n) ~= "number" or not __TS__NumberIsFinite(n) then + return ____exports.inferDeathSentenceTitleIndexFromInstanceId(nil, instanceId) + end + local f = math.floor(n) + if f < 1 or f > ____exports.DEATH_SENTENCE_TITLE_INDEX_MAX then + return ____exports.inferDeathSentenceTitleIndexFromInstanceId(nil, instanceId) + end + return f +end +--- Пыль (dust_currency) за разбор приговора. +function ____exports.getDeathSentenceDismantleShardReward(self, rarity) + local base = { + common = 15, + rare = 35, + epic = 70, + legendary = 120, + mythic = 200 + } + return base[rarity] or 15 +end +--- Сколько секунд вычитается из дня за один слот усложнения «короткий день». +____exports.DEATH_SENTENCE_SHORT_DAY_SEC_PER_STACK = 60 +local SCARCE_LOOT_DROP_FACTOR_PER_STACK = 0.5 +--- Множитель шанса выпадения предметов с юнитов (см. ItemDropSystem): каждый слот `ds_complication_scarce_loot` даёт ×0.5. +function ____exports.getDeathSentenceItemDropChanceMultiplier(self, instance) + if not instance then + return 1 + end + local stacks = 0 + for ____, cid in ipairs(instance.complicationIds) do + if cid == "ds_complication_scarce_loot" then + stacks = stacks + 1 + end + end + if stacks <= 0 then + return 1 + end + return math.pow(SCARCE_LOOT_DROP_FACTOR_PER_STACK, stacks) +end +local function rollComplicationIdsForRarity(self, rarity) + local need = ____exports.getDeathSentenceComplicationPickCount(nil, rarity) + if need <= 0 then + return {} + end + local out = {} + while #out < need do + local pool = {unpack(____exports.DEATH_SENTENCE_COMPLICATION_POOL)} + do + local i = #pool - 1 + while i > 0 do + local j = RandomInt(0, i) + local tmp = pool[i + 1] + pool[i + 1] = pool[j + 1] + pool[j + 1] = tmp + i = i - 1 + end + end + do + local k = 0 + while k < #pool and #out < need do + out[#out + 1] = pool[k + 1] + k = k + 1 + end + end + end + return out +end +--- Один экземпляр в общем ростере (слот в инвентаре). +function ____exports.generateDeathSentenceContractInstance(self, slotIndex) + local rarity = rollRarity(nil) + local d = ____exports.rollDeathSentenceContractDurability(nil) + return { + instanceId = (("ds_ci_" .. tostring(slotIndex)) .. "_") .. tostring(RandomInt(10000, 99999999)), + serial = slotIndex + 1, + titleIndex = RandomInt(1, ____exports.DEATH_SENTENCE_TITLE_INDEX_MAX), + rarity = rarity, + rewardMultiplier = rollRewardMultiplierForRarity(nil, rarity), + traitId = "none", + complicationIds = rollComplicationIdsForRarity(nil, rarity), + durability = d, + durabilityMax = d + } +end +--- Создание экземпляра контракта с принудительной редкостью (для наград конца матча). +function ____exports.generateDeathSentenceContractInstanceWithRarity(self, slotIndex, rarity) + local d = ____exports.rollDeathSentenceContractDurability(nil) + return { + instanceId = (("ds_ci_" .. tostring(slotIndex)) .. "_") .. tostring(RandomInt(10000, 99999999)), + serial = slotIndex + 1, + titleIndex = RandomInt(1, ____exports.DEATH_SENTENCE_TITLE_INDEX_MAX), + rarity = rarity, + rewardMultiplier = rollRewardMultiplierForRarity(nil, rarity), + traitId = "none", + complicationIds = rollComplicationIdsForRarity(nil, rarity), + durability = d, + durabilityMax = d + } +end +--- После настройки длины дня в лобби: каждый слот `ds_complication_short_day` у выигравшего приговора +-- уменьшает длительность следующего дня на `DEATH_SENTENCE_SHORT_DAY_SEC_PER_STACK` секунд (не ниже порога менеджера). +function ____exports.applyDeathSentenceContractDayDurationAdjustments(self, instance) + if not IsServer() or not instance then + return + end + local stacks = 0 + for ____, cid in ipairs(instance.complicationIds) do + if cid == "ds_complication_short_day" then + stacks = stacks + 1 + end + end + if stacks <= 0 then + return + end + do + pcall(function() + local ____require_result_0 = require("DayNightCycleManager") + local DayNightCycleManager = ____require_result_0.DayNightCycleManager + DayNightCycleManager:getInstance():adjustNextDayDurationBy(-____exports.DEATH_SENTENCE_SHORT_DAY_SEC_PER_STACK * stacks) + end) + end +end +--- После базового скейла сложности в GameMode (враги). +function ____exports.applyDeathSentenceContractOnEnemySpawn(self, unit, instance) + if not IsServer() or not IsValidEntity(unit) then + return + end + if unit:GetTeam() == DOTA_TEAM_GOODGUYS then + return + end + if not instance then + return + end + local unitName = unit:GetUnitName() + if unitName and DEATH_SENTENCE_CONTRACT_BUFF_BLOCKED_UNITS:has(unitName) then + return + end + local chance = ____exports.DEATH_SENTENCE_CONTRACT_CREEP_ABILITY_SPAWN_CHANCE_PCT + for ____, cid in ipairs(instance.complicationIds) do + do + local abilityName = DEATH_SENTENCE_COMPLICATION_CREEP_ABILITY[cid] + if not abilityName then + goto __continue60 + end + if RandomInt(1, 100) > chance then + goto __continue60 + end + grantContractCreepAbility(nil, unit, abilityName) + end + ::__continue60:: + end +end +return ____exports diff --git a/scripts/vscripts/death_sentence_contracts_registry.lua b/scripts/vscripts/death_sentence_contracts_registry.lua new file mode 100644 index 0000000..6552de2 --- /dev/null +++ b/scripts/vscripts/death_sentence_contracts_registry.lua @@ -0,0 +1,31 @@ +local ____lualib = require("lualib_bundle") +local __TS__ArrayMap = ____lualib.__TS__ArrayMap +local __TS__ArrayFind = ____lualib.__TS__ArrayFind +local ____exports = {} +local CONTRACTS = {{id = "ds_contract_iron", nameToken = "death_sentence_contract_iron", rarity = "common", rewardMultiplier = 1}, {id = "ds_contract_blood", nameToken = "death_sentence_contract_blood", rarity = "rare", rewardMultiplier = 1.1}} +function ____exports.getDeathSentenceContractIds(self) + return __TS__ArrayMap( + CONTRACTS, + function(____, c) return c.id end + ) +end +function ____exports.getDeathSentenceContractDef(self, id) + return __TS__ArrayFind( + CONTRACTS, + function(____, c) return c.id == id end + ) +end +--- После базового скейла сложности в GameMode (враги). +function ____exports.applyDeathSentenceContractOnEnemySpawn(self, unit, contractId) + if not IsServer() or not contractId or not IsValidEntity(unit) then + return + end + if unit:GetTeam() == DOTA_TEAM_GOODGUYS then + return + end + if contractId == "ds_contract_blood" then + local arm = unit:GetPhysicalArmorBaseValue() + unit:SetPhysicalArmorBaseValue(arm + 1) + end +end +return ____exports diff --git a/scripts/vscripts/difficulty_manager.lua b/scripts/vscripts/difficulty_manager.lua new file mode 100644 index 0000000..fd50f53 --- /dev/null +++ b/scripts/vscripts/difficulty_manager.lua @@ -0,0 +1,1574 @@ +local ____lualib = require("lualib_bundle") +local __TS__Class = ____lualib.__TS__Class +local __TS__New = ____lualib.__TS__New +local __TS__ArrayFindIndex = ____lualib.__TS__ArrayFindIndex +local __TS__ObjectAssign = ____lualib.__TS__ObjectAssign +local Set = ____lualib.Set +local __TS__ArraySort = ____lualib.__TS__ArraySort +local __TS__ArrayFind = ____lualib.__TS__ArrayFind +local __TS__Iterator = ____lualib.__TS__Iterator +local __TS__ArrayFilter = ____lualib.__TS__ArrayFilter +local __TS__ArrayIsArray = ____lualib.__TS__ArrayIsArray +local __TS__Number = ____lualib.__TS__Number +local __TS__ObjectKeys = ____lualib.__TS__ObjectKeys +local __TS__ArraySplice = ____lualib.__TS__ArraySplice +local __TS__ArrayMap = ____lualib.__TS__ArrayMap +local __TS__ObjectEntries = ____lualib.__TS__ObjectEntries +local __TS__ArraySetLength = ____lualib.__TS__ArraySetLength +local __TS__Delete = ____lualib.__TS__Delete +local __TS__ArrayIncludes = ____lualib.__TS__ArrayIncludes +local __TS__ArraySome = ____lualib.__TS__ArraySome +local ____exports = {} +local ____contracts_registry = require("death_sentence.contracts_registry") +local getDeathSentenceDismantleShardReward = ____contracts_registry.getDeathSentenceDismantleShardReward +local applyDeathSentenceContractDayDurationAdjustments = ____contracts_registry.applyDeathSentenceContractDayDurationAdjustments +local generateDeathSentenceContractInstanceWithRarity = ____contracts_registry.generateDeathSentenceContractInstanceWithRarity +local normalizeDeathSentenceContractDurability = ____contracts_registry.normalizeDeathSentenceContractDurability +local normalizeDeathSentenceContractDurabilityMax = ____contracts_registry.normalizeDeathSentenceContractDurabilityMax +local DEATH_SENTENCE_CONTRACT_REPAIR_DONATE_COST = ____contracts_registry.DEATH_SENTENCE_CONTRACT_REPAIR_DONATE_COST +local ____contracts_backend = require("death_sentence.contracts_backend") +local loadDeathSentenceContractsFromBackend = ____contracts_backend.loadDeathSentenceContractsFromBackend +local saveDeathSentenceContractsToBackend = ____contracts_backend.saveDeathSentenceContractsToBackend +local ____real_lobby_player = require("utils.real_lobby_player") +local isRealLobbyPlayer = ____real_lobby_player.isRealLobbyPlayer +local countRealLobbyPlayers = ____real_lobby_player.countRealLobbyPlayers +local ____player_connection_state = require("utils.player_connection_state") +local DOTA_CONNECTION_STATE = ____player_connection_state.DOTA_CONNECTION_STATE +local ____store_manager = require("store_manager") +local StoreManager = ____store_manager.StoreManager +local DEATH_SENTENCE_EMPTY_CONTRACT_VOTE = "__empty_death_sentence_contract__" +--- Дебаг: фиктивный «чужой» контракт во 2-й колонке превью при **одном** живом игроке. +-- Не трогает бэкенд. Только для локалки — в репозитории держи `false`. +local DEATH_SENTENCE_DEBUG_INJECT_SOLO_PEER_CONTRACT = false +local DifficultyManager = __TS__Class() +DifficultyManager.name = "DifficultyManager" +DifficultyManager.____file_path = "scripts/vscripts/difficulty_manager.lua" +function DifficultyManager.prototype.____constructor(self) + self.diffs = { + easy = 0, + normal = 0, + hard = 0, + impossible = 0, + death_sentence = 0 + } + self.players = {} + self.contractVotes = {} + self.contractColumnOfferByPlayer = {} + self.contractInventoryByPlayer = {} + self.leader = "normal" + self.selectionEnd = false + self.listenersRegistered = false + self.winningContractId = nil + self.winningContractSnapshot = nil + self.sharedContractRoster = {} + self.sharedContractRosterBuilt = false + self.deathSentenceRosterHydrated = false + self.deathSentenceHydrateStarted = false + self.deathSentenceHydrateWaiting = false + self.deathSentenceHydratedByPlayer = {} + self.deathSentenceHydrateWaitingByPlayer = {} + self.deathSentenceRosterMutationEpoch = 0 + self.debugSoloPeerContractInstance = nil +end +function DifficultyManager.prototype.areAllRealLobbyPlayersFinishedLoading(self) + local need = countRealLobbyPlayers(nil) + if need <= 0 then + return false + end + local ready = 0 + do + local i = 0 + while i < DOTA_MAX_PLAYERS do + do + local pid = i + if not isRealLobbyPlayer(nil, pid) then + goto __continue5 + end + if PlayerResource:GetConnectionState(pid) == DOTA_CONNECTION_STATE.CONNECTED then + ready = ready + 1 + end + end + ::__continue5:: + i = i + 1 + end + end + return ready == need +end +function DifficultyManager.prototype.tickDeathSentenceRosterHydrationWait(self) + if self.deathSentenceHydrateStarted then + return nil + end + local state = GameRules:State_Get() + if state ~= DOTA_GAMERULES_STATE_CUSTOM_GAME_SETUP then + self:beginDeathSentenceRosterHydration() + return nil + end + if self:areAllRealLobbyPlayersFinishedLoading() then + self:beginDeathSentenceRosterHydration() + return nil + end + return 0.25 +end +function DifficultyManager.getInstance(self) + if not DifficultyManager.instance then + DifficultyManager.instance = __TS__New(DifficultyManager) + end + return DifficultyManager.instance +end +function DifficultyManager.prototype.Init(self) + if not self.listenersRegistered then + if type(CustomGameEventManager) == "nil" then + return + end + CustomGameEventManager:RegisterListener( + "invasion_select_difficulty", + function(_, data) + self:Select({PlayerID = data.PlayerID, diff = data.diff}) + end + ) + CustomGameEventManager:RegisterListener( + "invasion_vote_death_sentence_contract", + function(_, data) + self:SelectContractVote({PlayerID = data.PlayerID, contractId = data.contractId}) + end + ) + CustomGameEventManager:RegisterListener( + "invasion_set_death_sentence_column_offer", + function(_, data) + self:SetDeathSentenceColumnOffer({PlayerID = data.PlayerID, contractId = data.contractId}) + end + ) + CustomGameEventManager:RegisterListener( + "invasion_pick_own_death_sentence_contract", + function(_, data) + self:PickOwnDeathSentenceFromInventory({PlayerID = data.PlayerID, contractId = data.contractId}) + end + ) + CustomGameEventManager:RegisterListener( + "invasion_dismantle_death_sentence_contract", + function(_, data) + self:DismantleDeathSentenceContract({PlayerID = data.PlayerID, instanceId = data.instanceId}) + end + ) + CustomGameEventManager:RegisterListener( + "invasion_death_sentence_contract_dismantle_batch", + function(_, data) + self:DismantleDeathSentenceContractsBatch({PlayerID = data.PlayerID, instanceIds = data.instanceIds}) + end + ) + CustomGameEventManager:RegisterListener( + "invasion_death_sentence_contract_toggle_favorite", + function(_, data) + self:ToggleDeathSentenceContractFavorite({PlayerID = data.PlayerID, instanceId = data.instanceId}) + end + ) + CustomGameEventManager:RegisterListener( + "invasion_death_sentence_contract_toggle_pin", + function(_, data) + self:ToggleDeathSentenceContractPin({PlayerID = data.PlayerID, instanceId = data.instanceId}) + end + ) + CustomGameEventManager:RegisterListener( + "invasion_request_death_sentence_contracts_sync", + function(_, data) + self:RequestDeathSentenceContractsSync(data.PlayerID) + end + ) + CustomGameEventManager:RegisterListener( + "invasion_repair_death_sentence_contract_durability", + function(_, data) + self:RepairDeathSentenceContractDurability({PlayerID = data.PlayerID, instanceId = data.instanceId}) + end + ) + ListenToGameEvent( + "player_connect_full", + function(event) + local playerId = event.PlayerID + if playerId ~= nil then + self:RequestDeathSentenceContractsSync(playerId) + self:sendUpdateToAllClients() + end + end, + nil + ) + self.listenersRegistered = true + end + self:recalculateLeader() + Timers:CreateTimer( + 0.25, + function() return self:tickDeathSentenceRosterHydrationWait() end + ) + Timers:CreateTimer( + 0.75, + function() + self:sendUpdateToAllClients() + return nil + end + ) +end +function DifficultyManager.prototype.Select(self, data) + if self.selectionEnd then + return + end + local playerId = data.PlayerID + local newDiff = data.diff + local previousVote = self.players[playerId] + if newDiff ~= "death_sentence" then + self.contractVotes[playerId] = nil + end + if previousVote == newDiff then + if previousVote ~= nil and previousVote ~= nil then + local ____self_diffs_0, ____previousVote_1 = self.diffs, previousVote + ____self_diffs_0[____previousVote_1] = ____self_diffs_0[____previousVote_1] - 1 + end + self.players[playerId] = nil + else + if previousVote and previousVote ~= nil then + local ____self_diffs_2, ____previousVote_3 = self.diffs, previousVote + ____self_diffs_2[____previousVote_3] = ____self_diffs_2[____previousVote_3] - 1 + end + self.players[playerId] = newDiff + local ____self_diffs_4, ____newDiff_5 = self.diffs, newDiff + ____self_diffs_4[____newDiff_5] = ____self_diffs_4[____newDiff_5] + 1 + end + self:recalculateLeader() + self:sendUpdateToAllClients() +end +function DifficultyManager.prototype.SelectContractVote(self, data) + if self.selectionEnd then + return + end + local playerId = data.PlayerID + self:ensurePlayerContractInventory(playerId) + self:sanitizePlayerContractInventory(playerId) + local newContract = data.contractId + if newContract == nil or newContract == "" then + newContract = nil + end + local prev = self.contractVotes[playerId] + if newContract ~= nil and newContract ~= DEATH_SENTENCE_EMPTY_CONTRACT_VOTE and not self:contractInstanceIdInLobbySharedRoster(newContract) then + return + end + if prev ~= nil and prev ~= nil and prev == newContract then + self.contractVotes[playerId] = nil + else + self.contractVotes[playerId] = newContract + end + if self.contractVotes[playerId] ~= nil then + self:forceDifficultyVote(playerId, "death_sentence") + end + self:sendUpdateToAllClients() +end +function DifficultyManager.prototype.PickOwnDeathSentenceFromInventory(self, data) + if self.selectionEnd then + return + end + local playerId = data.PlayerID + self:ensurePlayerContractInventory(playerId) + self:sanitizePlayerContractInventory(playerId) + local id = data.contractId + if id == nil or id == "" then + id = nil + end + if id == nil or id == DEATH_SENTENCE_EMPTY_CONTRACT_VOTE then + self.contractColumnOfferByPlayer[playerId] = nil + self.contractVotes[playerId] = nil + self:sendUpdateToAllClients() + return + end + if not self:contractInstanceInPlayerInventory(playerId, id) then + return + end + self.contractColumnOfferByPlayer[playerId] = id + self.contractVotes[playerId] = id + self:forceDifficultyVote(playerId, "death_sentence") + self:sendUpdateToAllClients() +end +function DifficultyManager.prototype.SetDeathSentenceColumnOffer(self, data) + if self.selectionEnd then + return + end + local playerId = data.PlayerID + if not PlayerResource:IsValidPlayerID(playerId) or PlayerResource:IsFakeClient(playerId) then + return + end + local nid = data.contractId + if nid == nil or nid == "" then + nid = nil + end + if nid ~= nil and not self:contractInstanceInPlayerInventory(playerId, nid) then + return + end + self.contractColumnOfferByPlayer[playerId] = nid + self:sendUpdateToAllClients() +end +function DifficultyManager.prototype.contractInstanceInPlayerInventory(self, playerId, instanceId) + local inv = self.contractInventoryByPlayer[playerId] + if not inv then + return false + end + for ____, row in ipairs(inv) do + if row.instanceId == instanceId then + return true + end + end + return false +end +function DifficultyManager.prototype.sanitizeColumnOfferAfterInventoryChange(self, playerId) + local sid = self.contractColumnOfferByPlayer[playerId] + if sid == nil or sid == nil then + return + end + if not self:contractInstanceInPlayerInventory(playerId, sid) then + self.contractColumnOfferByPlayer[playerId] = nil + end +end +function DifficultyManager.prototype.sendDeathSentenceDismantleResult(self, playerId, ok, shards, meta) + local player = PlayerResource:GetPlayer(playerId) + if not player then + return + end + CustomGameEventManager:Send_ServerToPlayer(player, "death_sentence_contract_dismantle_result", {ok = ok, shards = shards, batch_count = meta and meta.batchCount or 0, skipped_pinned = meta and meta.skippedPinned or 0}) +end +function DifficultyManager.prototype.sendDeathSentenceRepairResult(self, playerId, ok, reason, success) + local player = PlayerResource:GetPlayer(playerId) + if not player then + return + end + local payload = {ok = ok, reason = reason or ""} + if ok and success then + payload.instance_id = success.instanceId + payload.durability = success.durability + payload.durability_max = success.durabilityMax + end + CustomGameEventManager:Send_ServerToPlayer(player, "death_sentence_contract_repair_result", payload) +end +function DifficultyManager.prototype.RepairDeathSentenceContractDurability(self, data) + local playerId = data.PlayerID + local instanceId = data.instanceId + local function fail(____, reason) + return self:sendDeathSentenceRepairResult(playerId, false, reason) + end + if not PlayerResource:IsValidPlayerID(playerId) or PlayerResource:IsFakeClient(playerId) then + fail(nil, "invalid") + return + end + if self.selectionEnd or type(instanceId) ~= "string" or #instanceId == 0 then + fail(nil, "state") + return + end + if GameRules:State_Get() ~= DOTA_GAMERULES_STATE_CUSTOM_GAME_SETUP then + fail(nil, "state") + return + end + if not self.deathSentenceHydratedByPlayer[playerId] then + fail(nil, "state") + return + end + local inv = self.contractInventoryByPlayer[playerId] or ({}) + local idx = __TS__ArrayFindIndex( + inv, + function(____, x) return x.instanceId == instanceId end + ) + if idx < 0 then + fail(nil, "not_found") + return + end + local inst = inv[idx + 1] + local cur = normalizeDeathSentenceContractDurability(nil, inst.durability, inst.instanceId) + local max = normalizeDeathSentenceContractDurabilityMax(nil, inst.durabilityMax, cur, inst.instanceId) + inst.durability = cur + inst.durabilityMax = max + if cur >= max then + fail(nil, "full") + return + end + local store = StoreManager:getInstance() + local cost = DEATH_SENTENCE_CONTRACT_REPAIR_DONATE_COST + if store:getDonateCurrency(playerId) < cost then + fail(nil, "funds") + return + end + local prevDur = cur + inst.durability = max + if not store:tryConsumeDonateCurrency(playerId, cost) then + inst.durability = prevDur + fail(nil, "funds") + return + end + self.deathSentenceRosterMutationEpoch = self.deathSentenceRosterMutationEpoch + 1 + self:sortContractRosterForDisplay(inv) + self:rebuildSharedContractRosterFromPlayerInventories() + self:syncContractInventoryToClient(playerId) + self:saveContractRosterForPlayer( + playerId, + function(____, okSave) + if not okSave then + inst.durability = prevDur + store:addDonateCurrency(playerId, cost) + self:syncContractInventoryToClient(playerId) + self:sendUpdateToAllClients() + fail(nil, "save") + return + end + self:sendDeathSentenceRepairResult( + playerId, + true, + "", + { + instanceId = inst.instanceId, + durability = max, + durabilityMax = normalizeDeathSentenceContractDurabilityMax(nil, inst.durabilityMax, max, inst.instanceId) + } + ) + self:sendUpdateToAllClients() + end + ) +end +function DifficultyManager.prototype.shouldInjectDebugSoloPeerContract(self) + return DEATH_SENTENCE_DEBUG_INJECT_SOLO_PEER_CONTRACT and countRealLobbyPlayers(nil) == 1 +end +function DifficultyManager.prototype.getOrCreateDebugSoloPeerContract(self) + if self.debugSoloPeerContractInstance then + return self.debugSoloPeerContractInstance + end + local base = generateDeathSentenceContractInstanceWithRarity(nil, 1, "legendary") + self.debugSoloPeerContractInstance = __TS__ObjectAssign({}, base, { + instanceId = "ds_ci__debug_solo_peer__", + serial = 2, + titleIndex = 42, + pinned = false, + favorite = false + }) + return self.debugSoloPeerContractInstance +end +function DifficultyManager.prototype.rebuildSharedContractRosterFromPlayerInventories(self) + local seen = __TS__New(Set) + local next = {} + do + local i = 0 + while i < DOTA_MAX_PLAYERS do + do + local pid = i + local inv = self.contractInventoryByPlayer[pid] + if not inv then + goto __continue85 + end + for ____, inst in ipairs(inv) do + do + if not inst or seen:has(inst.instanceId) then + goto __continue87 + end + seen:add(inst.instanceId) + next[#next + 1] = inst + end + ::__continue87:: + end + end + ::__continue85:: + i = i + 1 + end + end + if self:shouldInjectDebugSoloPeerContract() then + local dbg = self:getOrCreateDebugSoloPeerContract() + if not seen:has(dbg.instanceId) then + seen:add(dbg.instanceId) + next[#next + 1] = dbg + end + end + self.sharedContractRoster = next + self.sharedContractRosterBuilt = true + self:sortSharedContractRosterForDisplay() + self:invalidateContractVotesNotInRoster() +end +function DifficultyManager.prototype.sortContractRosterForDisplay(self, roster) + __TS__ArraySort( + roster, + function(____, a, b) + local pinA = a.pinned and 1 or 0 + local pinB = b.pinned and 1 or 0 + if pinA ~= pinB then + return pinB - pinA + end + local favA = a.favorite and 1 or 0 + local favB = b.favorite and 1 or 0 + if favA ~= favB then + return favB - favA + end + return a.serial - b.serial + end + ) +end +function DifficultyManager.prototype.sortSharedContractRosterForDisplay(self) + self:sortContractRosterForDisplay(self.sharedContractRoster) +end +function DifficultyManager.prototype.syncContractInventoryToClient(self, playerId) + if not PlayerResource:IsValidPlayerID(playerId) then + return + end + CustomNetTables:SetTableValue( + "death_sentence_contracts", + tostring(playerId), + {roster = self.contractInventoryByPlayer[playerId] or ({})} + ) +end +function DifficultyManager.prototype.ToggleDeathSentenceContractFavorite(self, data) + local playerId = data.PlayerID + local instanceId = data.instanceId + if not PlayerResource:IsValidPlayerID(playerId) or PlayerResource:IsFakeClient(playerId) then + return + end + if self.selectionEnd or type(instanceId) ~= "string" or #instanceId == 0 then + return + end + if GameRules:State_Get() ~= DOTA_GAMERULES_STATE_CUSTOM_GAME_SETUP then + return + end + if not self.deathSentenceHydratedByPlayer[playerId] then + return + end + local inv = self.contractInventoryByPlayer[playerId] or ({}) + local inst = __TS__ArrayFind( + inv, + function(____, x) return x.instanceId == instanceId end + ) + if not inst then + return + end + inst.favorite = not inst.favorite + self.deathSentenceRosterMutationEpoch = self.deathSentenceRosterMutationEpoch + 1 + self:sortContractRosterForDisplay(inv) + self:rebuildSharedContractRosterFromPlayerInventories() + self:syncContractInventoryToClient(playerId) + self:saveContractRosterForPlayer( + playerId, + function() + end + ) + self:sendUpdateToAllClients() +end +function DifficultyManager.prototype.ToggleDeathSentenceContractPin(self, data) + local playerId = data.PlayerID + local instanceId = data.instanceId + if not PlayerResource:IsValidPlayerID(playerId) or PlayerResource:IsFakeClient(playerId) then + return + end + if self.selectionEnd or type(instanceId) ~= "string" or #instanceId == 0 then + return + end + if GameRules:State_Get() ~= DOTA_GAMERULES_STATE_CUSTOM_GAME_SETUP then + return + end + if not self.deathSentenceHydratedByPlayer[playerId] then + return + end + local inv = self.contractInventoryByPlayer[playerId] or ({}) + local inst = __TS__ArrayFind( + inv, + function(____, x) return x.instanceId == instanceId end + ) + if not inst then + return + end + inst.pinned = not inst.pinned + self.deathSentenceRosterMutationEpoch = self.deathSentenceRosterMutationEpoch + 1 + self:sortContractRosterForDisplay(inv) + self:rebuildSharedContractRosterFromPlayerInventories() + self:syncContractInventoryToClient(playerId) + self:saveContractRosterForPlayer( + playerId, + function() + end + ) + self:sendUpdateToAllClients() +end +function DifficultyManager.prototype.DismantleDeathSentenceContractsBatch(self, data) + local playerId = data.PlayerID + local rawIds = self:normalizeContractBatchIds(data.instanceIds) + if not PlayerResource:IsValidPlayerID(playerId) then + return + end + if PlayerResource:IsFakeClient(playerId) then + self:sendDeathSentenceDismantleResult(playerId, false, 0) + return + end + if self.selectionEnd or #rawIds == 0 then + self:sendDeathSentenceDismantleResult(playerId, false, 0) + return + end + if GameRules:State_Get() ~= DOTA_GAMERULES_STATE_CUSTOM_GAME_SETUP then + self:sendDeathSentenceDismantleResult(playerId, false, 0) + return + end + if not self.deathSentenceHydratedByPlayer[playerId] then + self:sendDeathSentenceDismantleResult(playerId, false, 0) + return + end + local inv = self.contractInventoryByPlayer[playerId] or ({}) + local want = __TS__New(Set) + for ____, id in ipairs(rawIds) do + if type(id) == "string" and #id > 0 then + want:add(id) + end + end + if want.size == 0 then + self:sendDeathSentenceDismantleResult(playerId, false, 0) + return + end + local skippedPinned = 0 + local totalShards = 0 + local removed = 0 + local toRemove = {} + for ____, id in __TS__Iterator(want) do + do + local inst = __TS__ArrayFind( + inv, + function(____, x) return x.instanceId == id end + ) + if not inst then + goto __continue125 + end + if inst.pinned == true then + skippedPinned = skippedPinned + 1 + goto __continue125 + end + toRemove[#toRemove + 1] = id + totalShards = totalShards + getDeathSentenceDismantleShardReward(nil, inst.rarity) + end + ::__continue125:: + end + if #toRemove == 0 then + self:sendDeathSentenceDismantleResult(playerId, false, 0, {batchCount = 0, skippedPinned = skippedPinned}) + return + end + local removeSet = __TS__New(Set, toRemove) + self.contractInventoryByPlayer[playerId] = __TS__ArrayFilter( + inv, + function(____, x) return not removeSet:has(x.instanceId) end + ) + removed = #toRemove + self.deathSentenceRosterMutationEpoch = self.deathSentenceRosterMutationEpoch + 1 + self:sortContractRosterForDisplay(self.contractInventoryByPlayer[playerId]) + self:rebuildSharedContractRosterFromPlayerInventories() + self:sanitizeColumnOfferAfterInventoryChange(playerId) + self:syncContractInventoryToClient(playerId) + self:invalidateContractVotesNotInRoster() + self:saveContractRosterForPlayer( + playerId, + function() + end + ) + StoreManager:getInstance():grantDustCurrencyMatchEndReward(playerId, totalShards) + self:sendDeathSentenceDismantleResult(playerId, true, totalShards, {batchCount = removed, skippedPinned = skippedPinned}) + self:sendUpdateToAllClients() +end +function DifficultyManager.prototype.normalizeContractBatchIds(self, raw) + if not raw then + return {} + end + if __TS__ArrayIsArray(raw) then + local out = {} + for ____, id in ipairs(raw) do + if type(id) == "string" and #id > 0 then + out[#out + 1] = id + end + end + return out + end + if type(raw) == "table" then + local obj = raw + local out = {} + local keys = __TS__ArraySort( + __TS__ObjectKeys(obj), + function(____, a, b) return __TS__Number(a) - __TS__Number(b) end + ) + for ____, key in ipairs(keys) do + local val = obj[key] + if type(val) == "string" and #val > 0 then + out[#out + 1] = val + end + end + return out + end + return {} +end +function DifficultyManager.prototype.DismantleDeathSentenceContract(self, data) + local playerId = data.PlayerID + local instanceId = data.instanceId + if not PlayerResource:IsValidPlayerID(playerId) then + return + end + if PlayerResource:IsFakeClient(playerId) then + self:sendDeathSentenceDismantleResult(playerId, false, 0) + return + end + if self.selectionEnd or type(instanceId) ~= "string" or #instanceId == 0 then + self:sendDeathSentenceDismantleResult(playerId, false, 0) + return + end + if GameRules:State_Get() ~= DOTA_GAMERULES_STATE_CUSTOM_GAME_SETUP then + self:sendDeathSentenceDismantleResult(playerId, false, 0) + return + end + if not self.deathSentenceHydratedByPlayer[playerId] then + self:sendDeathSentenceDismantleResult(playerId, false, 0) + return + end + local inv = self.contractInventoryByPlayer[playerId] or ({}) + local idx = __TS__ArrayFindIndex( + inv, + function(____, x) return x.instanceId == instanceId end + ) + if idx < 0 then + self:sendDeathSentenceDismantleResult(playerId, false, 0) + return + end + local inst = inv[idx + 1] + if inst.pinned == true then + self:sendDeathSentenceDismantleResult(playerId, false, 0) + return + end + local shards = getDeathSentenceDismantleShardReward(nil, inst.rarity) + __TS__ArraySplice(inv, idx, 1) + self.deathSentenceRosterMutationEpoch = self.deathSentenceRosterMutationEpoch + 1 + self:sortContractRosterForDisplay(inv) + self:rebuildSharedContractRosterFromPlayerInventories() + self:sanitizeColumnOfferAfterInventoryChange(playerId) + self:syncContractInventoryToClient(playerId) + self:invalidateContractVotesNotInRoster() + self:saveContractRosterForPlayer( + playerId, + function() + end + ) + StoreManager:getInstance():grantDustCurrencyMatchEndReward(playerId, shards) + self:sendDeathSentenceDismantleResult(playerId, true, shards) + self:sendUpdateToAllClients() +end +function DifficultyManager.prototype.getSteamIdForContracts(self, playerId) + if not PlayerResource:IsValidPlayerID(playerId) or PlayerResource:IsFakeClient(playerId) then + return nil + end + local steamId = PlayerResource:GetSteamAccountID(playerId) + if not steamId or steamId == 0 then + return nil + end + return tostring(steamId) +end +function DifficultyManager.prototype.invalidateContractVotesNotInRoster(self) + local valid = __TS__New( + Set, + __TS__ArrayMap( + self.sharedContractRoster, + function(____, x) return x.instanceId end + ) + ) + do + local i = 0 + while i < DOTA_MAX_PLAYERS do + local pid = i + local v = self.contractVotes[pid] + if v and not valid:has(v) then + self.contractVotes[pid] = nil + end + i = i + 1 + end + end +end +function DifficultyManager.prototype.applyPlayerContractRosterFromBackend(self, playerId, roster) + local next = roster ~= nil and ({unpack(roster)}) or ({}) + while #next > DifficultyManager.CONTRACT_INVENTORY_CAP do + table.remove(next) + end + self:sortContractRosterForDisplay(next) + self.contractInventoryByPlayer[playerId] = next + self.deathSentenceHydratedByPlayer[playerId] = true + self:syncContractInventoryToClient(playerId) + self:rebuildSharedContractRosterFromPlayerInventories() + self:sanitizeColumnOfferAfterInventoryChange(playerId) + print(((("[DifficultyManager] Death Sentence: ростер игрока pid=" .. tostring(playerId)) .. " с бэка, шт.=") .. tostring(#next)) .. ".") +end +function DifficultyManager.prototype.loadDeathSentenceRosterForPlayer(self, playerId, done) + if not PlayerResource:IsValidPlayerID(playerId) or PlayerResource:IsFakeClient(playerId) then + if done ~= nil then + done(nil) + end + return + end + if self.deathSentenceHydratedByPlayer[playerId] then + if done ~= nil then + done(nil) + end + return + end + if self.deathSentenceHydrateWaitingByPlayer[playerId] then + return + end + local steam = self:getSteamIdForContracts(playerId) + if not steam then + print(("[DifficultyManager] Death Sentence: SteamID для pid=" .. tostring(playerId)) .. " пока недоступен, повторим позже.") + if done ~= nil then + done(nil) + end + return + end + self.deathSentenceHydrateWaitingByPlayer[playerId] = true + local mutationEpochAtFetch = self.deathSentenceRosterMutationEpoch + loadDeathSentenceContractsFromBackend( + nil, + steam, + function(____, roster) + self.deathSentenceHydrateWaitingByPlayer[playerId] = false + if self.deathSentenceRosterMutationEpoch ~= mutationEpochAtFetch and self.deathSentenceHydratedByPlayer[playerId] then + print(("[DifficultyManager] Death Sentence: ответ GET игрока pid=" .. tostring(playerId)) .. " проигнорирован (локально уже меняли ростер после старта загрузки).") + if done ~= nil then + done(nil) + end + return + end + self:applyPlayerContractRosterFromBackend(playerId, roster) + if done ~= nil then + done(nil) + end + end + ) +end +function DifficultyManager.prototype.beginDeathSentenceRosterHydration(self, preferredPlayerId) + if self.deathSentenceHydrateStarted then + return + end + self.deathSentenceHydrateStarted = true + local players = {} + if preferredPlayerId ~= nil and PlayerResource:IsValidPlayerID(preferredPlayerId) and not PlayerResource:IsFakeClient(preferredPlayerId) then + players[#players + 1] = preferredPlayerId + else + do + local i = 0 + while i < DOTA_MAX_PLAYERS do + do + local pid = i + if not isRealLobbyPlayer(nil, pid) then + goto __continue174 + end + if PlayerResource:GetConnectionState(pid) ~= DOTA_CONNECTION_STATE.CONNECTED then + goto __continue174 + end + players[#players + 1] = pid + end + ::__continue174:: + i = i + 1 + end + end + end + if #players <= 0 then + self.deathSentenceHydrateStarted = false + self.deathSentenceHydrateWaiting = false + print("[DifficultyManager] Death Sentence: нет игроков для загрузки ростеров, повторим позже.") + return + end + self.deathSentenceHydrateWaiting = true + local pending = #players + local function onDone() + pending = pending - 1 + if pending > 0 then + return + end + self.deathSentenceHydrateWaiting = false + self:finishDeathSentenceRosterHydration() + end + for ____, playerId in ipairs(players) do + self:loadDeathSentenceRosterForPlayer(playerId, onDone) + end +end +function DifficultyManager.prototype.RequestDeathSentenceContractsSync(self, playerId) + if not PlayerResource:IsValidPlayerID(playerId) or PlayerResource:IsFakeClient(playerId) then + return + end + if not self.deathSentenceHydratedByPlayer[playerId] then + self:loadDeathSentenceRosterForPlayer( + playerId, + function() + self:finishDeathSentenceRosterHydration() + end + ) + return + end + self:sanitizePlayerContractInventory(playerId) + self:syncContractInventoryToClient(playerId) + self:sendUpdateToAllClients() +end +function DifficultyManager.prototype.finishDeathSentenceRosterHydration(self) + self.deathSentenceRosterHydrated = true + self:sendUpdateToAllClients() +end +function DifficultyManager.prototype.ensureSharedContractRoster(self) + if self.sharedContractRosterBuilt then + return + end + if self.deathSentenceHydrateWaiting then + return + end + self:rebuildSharedContractRosterFromPlayerInventories() +end +function DifficultyManager.prototype.ensurePlayerContractInventory(self, playerId) + if not self.deathSentenceHydratedByPlayer[playerId] then + return + end + local inv = self.contractInventoryByPlayer[playerId] + if not inv then + self.contractInventoryByPlayer[playerId] = {} + end +end +function DifficultyManager.prototype.sanitizePlayerContractInventory(self, playerId) + if not self.deathSentenceHydratedByPlayer[playerId] then + return + end + local listRef = self.contractInventoryByPlayer[playerId] + if not listRef then + self.contractInventoryByPlayer[playerId] = {} + return + end + local first = listRef[1] + if type(first) == "string" then + self.contractInventoryByPlayer[playerId] = {} + return + end + local next = {} + for ____, row in ipairs(listRef) do + do + local inst = row + if not inst or type(inst.instanceId) ~= "string" then + goto __continue197 + end + inst.durability = normalizeDeathSentenceContractDurability(nil, inst.durability, inst.instanceId) + inst.durabilityMax = normalizeDeathSentenceContractDurabilityMax(nil, inst.durabilityMax, inst.durability, inst.instanceId) + next[#next + 1] = inst + if #next >= DifficultyManager.CONTRACT_INVENTORY_CAP then + break + end + end + ::__continue197:: + end + self:sortContractRosterForDisplay(next) + self.contractInventoryByPlayer[playerId] = next +end +function DifficultyManager.prototype.contractInstanceIdInLobbySharedRoster(self, contractInstanceId) + self:ensureSharedContractRoster() + for ____, inst in ipairs(self.sharedContractRoster) do + if inst.instanceId == contractInstanceId then + return true + end + end + return false +end +function DifficultyManager.prototype.forceDifficultyVote(self, playerId, newDiff) + local previousVote = self.players[playerId] + if previousVote == newDiff then + return + end + if previousVote ~= nil and previousVote ~= nil then + local ____self_diffs_20, ____previousVote_21 = self.diffs, previousVote + ____self_diffs_20[____previousVote_21] = ____self_diffs_20[____previousVote_21] - 1 + end + self.players[playerId] = newDiff + local ____self_diffs_22, ____newDiff_23 = self.diffs, newDiff + ____self_diffs_22[____newDiff_23] = ____self_diffs_22[____newDiff_23] + 1 + self:recalculateLeader() +end +function DifficultyManager.prototype.recalculateLeader(self) + local maxVotes = 0 + local newLeader = "normal" + for ____, ____value in ipairs(__TS__ObjectEntries(self.diffs)) do + local diff = ____value[1] + local votes = ____value[2] + if votes > maxVotes then + maxVotes = votes + newLeader = diff + end + end + if maxVotes == 0 then + newLeader = "normal" + end + self.leader = newLeader +end +function DifficultyManager.prototype.resolveWinningContract(self) + local ids = __TS__ArrayMap( + self.sharedContractRoster, + function(____, x) return x.instanceId end + ) + if #ids == 0 then + return nil + end + local counts = {} + for ____, id in ipairs(ids) do + counts[id] = 0 + end + for ____, ____value in ipairs(__TS__ObjectEntries(self.contractVotes)) do + local pidStr = ____value[1] + local contractId = ____value[2] + do + local pid = tonumber(pidStr) + if self.players[pid] ~= "death_sentence" then + goto __continue218 + end + if not contractId or contractId == "" or contractId == DEATH_SENTENCE_EMPTY_CONTRACT_VOTE then + goto __continue218 + end + if counts[contractId] == nil then + goto __continue218 + end + counts[contractId] = (counts[contractId] or 0) + 1 + end + ::__continue218:: + end + local max = 0 + local leaders = {} + for ____, id in ipairs(ids) do + local c = counts[id] or 0 + if c > max then + max = c + __TS__ArraySetLength(leaders, 0) + leaders[#leaders + 1] = id + elseif c == max and c > 0 then + leaders[#leaders + 1] = id + end + end + if max == 0 then + return nil + end + return leaders[RandomInt(0, #leaders - 1) + 1] +end +function DifficultyManager.prototype.sendUpdateToAllClients(self) + if type(CustomGameEventManager) == "nil" then + return + end + if self.deathSentenceRosterHydrated then + do + local i = 0 + while i < DOTA_MAX_PLAYERS do + do + local playerId = i + if not PlayerResource:IsValidPlayerID(playerId) then + goto __continue231 + end + if not self.deathSentenceHydratedByPlayer[playerId] then + goto __continue231 + end + self:ensurePlayerContractInventory(playerId) + self:sanitizePlayerContractInventory(playerId) + self:syncContractInventoryToClient(playerId) + end + ::__continue231:: + i = i + 1 + end + end + end + local updateData = {votes = self.diffs, currentLeader = self.leader, playerVotes = self.players} + if self.deathSentenceRosterHydrated then + updateData.contractVotes = self.contractVotes + if self:shouldInjectDebugSoloPeerContract() then + updateData.debugSoloPeerContract = self:getOrCreateDebugSoloPeerContract() + end + local details = {} + do + local i = 0 + while i < DOTA_MAX_PLAYERS do + do + local pid = i + local contractId = self.contractVotes[pid] + if not contractId or contractId == DEATH_SENTENCE_EMPTY_CONTRACT_VOTE then + details[pid] = nil + goto __continue236 + end + local ____opt_24 = self.contractInventoryByPlayer[pid] + local own = ____opt_24 and __TS__ArrayFind( + self.contractInventoryByPlayer[pid], + function(____, x) return x.instanceId == contractId end + ) or nil + local ____own_27 = own + if ____own_27 == nil then + local ____table_sharedContractRosterBuilt_26 + if self.sharedContractRosterBuilt then + ____table_sharedContractRosterBuilt_26 = __TS__ArrayFind( + self.sharedContractRoster, + function(____, x) return x.instanceId == contractId end + ) or nil + else + ____table_sharedContractRosterBuilt_26 = nil + end + ____own_27 = ____table_sharedContractRosterBuilt_26 + end + details[pid] = ____own_27 + end + ::__continue236:: + i = i + 1 + end + end + updateData.contractVoteDetails = details + local columnOffer = {} + do + local j = 0 + while j < DOTA_MAX_PLAYERS do + do + local pOffer = j + if not PlayerResource:IsValidPlayerID(pOffer) then + goto __continue240 + end + local c = self.contractColumnOfferByPlayer[pOffer] + columnOffer[pOffer] = c ~= nil and c ~= nil and c or nil + end + ::__continue240:: + j = j + 1 + end + end + updateData.contractColumnOffer = columnOffer + end + CustomGameEventManager:Send_ServerToAllClients("update_difficulty_selections", updateData) +end +function DifficultyManager.prototype.ensureHeroSelectionResolvedForMatchStart(self) + if self.selectionEnd then + return + end + self:OnHeroSelectionState() +end +function DifficultyManager.prototype.getDeathSentenceContractPayloadForLossPenalty(self, playerId) + if self.leader ~= "death_sentence" then + return nil + end + local active = self:getActiveDeathSentenceContractPayload() + if active then + return active + end + local vote = self.contractVotes[playerId] + if not vote or vote == DEATH_SENTENCE_EMPTY_CONTRACT_VOTE then + return nil + end + local ____opt_28 = self.contractInventoryByPlayer[playerId] + local inst = ____opt_28 and __TS__ArrayFind( + self.contractInventoryByPlayer[playerId], + function(____, x) return x.instanceId == vote end + ) or __TS__ArrayFind( + self.sharedContractRoster, + function(____, x) return x.instanceId == vote end + ) or nil + if not inst then + return nil + end + local durability = normalizeDeathSentenceContractDurability(nil, inst.durability, inst.instanceId) + return { + instanceId = inst.instanceId, + serial = inst.serial, + titleIndex = inst.titleIndex, + rarity = inst.rarity, + rewardMultiplier = inst.rewardMultiplier, + traitId = inst.traitId, + complicationIds = {unpack(inst.complicationIds)}, + durability = durability, + durabilityMax = normalizeDeathSentenceContractDurabilityMax(nil, inst.durabilityMax, durability, inst.instanceId) + } +end +function DifficultyManager.prototype.forceReloadDeathSentenceContractsFromBackend(self, playerId) + if not PlayerResource:IsValidPlayerID(playerId) or PlayerResource:IsFakeClient(playerId) then + return + end + self.deathSentenceHydratedByPlayer[playerId] = false + __TS__Delete(self.deathSentenceHydrateWaitingByPlayer, playerId) + self:loadDeathSentenceRosterForPlayer( + playerId, + function() + self:finishDeathSentenceRosterHydration() + end + ) +end +function DifficultyManager.prototype.OnHeroSelectionState(self) + self.selectionEnd = true + if not self.sharedContractRosterBuilt and not self.deathSentenceHydrateWaiting then + self:rebuildSharedContractRosterFromPlayerInventories() + end + if not self.deathSentenceRosterHydrated then + self.deathSentenceRosterHydrated = true + end + local ____temp_30 + if self.leader == "death_sentence" then + ____temp_30 = self:resolveWinningContract() + else + ____temp_30 = nil + end + self.winningContractId = ____temp_30 + local ____temp_31 + if self.winningContractId ~= nil then + ____temp_31 = __TS__ArrayFind( + self.sharedContractRoster, + function(____, x) return x.instanceId == self.winningContractId end + ) or nil + else + ____temp_31 = nil + end + self.winningContractSnapshot = ____temp_31 + if type(CustomGameEventManager) == "nil" then + return + end + CustomGameEventManager:Send_ServerToAllClients( + "difficulty_selected", + { + difficulty = self.leader, + death_sentence_contract = self.winningContractId, + death_sentence_contract_data = self.winningContractSnapshot and ({ + instanceId = self.winningContractSnapshot.instanceId, + serial = self.winningContractSnapshot.serial, + titleIndex = self.winningContractSnapshot.titleIndex, + rarity = self.winningContractSnapshot.rarity, + rewardMultiplier = self.winningContractSnapshot.rewardMultiplier, + traitId = self.winningContractSnapshot.traitId, + complicationIds = {unpack(self.winningContractSnapshot.complicationIds)}, + durability = normalizeDeathSentenceContractDurability(nil, self.winningContractSnapshot.durability, self.winningContractSnapshot.instanceId), + durabilityMax = normalizeDeathSentenceContractDurabilityMax( + nil, + self.winningContractSnapshot.durabilityMax, + normalizeDeathSentenceContractDurability(nil, self.winningContractSnapshot.durability, self.winningContractSnapshot.instanceId), + self.winningContractSnapshot.instanceId + ) + }) or nil + } + ) + if __TS__ArrayIncludes({ + "easy", + "normal", + "hard", + "impossible", + "death_sentence" + }, self.leader) then + local units = FindUnitsInRadius( + DOTA_TEAM_GOODGUYS, + Vector(0, 0, 0), + nil, + -1, + DOTA_UNIT_TARGET_TEAM_ENEMY, + DOTA_UNIT_TARGET_ALL, + DOTA_UNIT_TARGET_FLAG_MAGIC_IMMUNE_ENEMIES, + FIND_ANY_ORDER, + false + ) + _G.Difficulter = self:getNpcStatScale() + for ____, unit in ipairs(units) do + self:NPC(unit) + end + local dayLength = 0 + local NightLength = 0 + repeat + local ____switch262 = self.leader + local ____cond262 = ____switch262 == "easy" + if ____cond262 then + dayLength = -60 + NightLength = -60 + break + end + ____cond262 = ____cond262 or ____switch262 == "normal" + if ____cond262 then + dayLength = -120 + NightLength = -120 + break + end + ____cond262 = ____cond262 or ____switch262 == "hard" + if ____cond262 then + dayLength = -60 + NightLength = -60 + break + end + ____cond262 = ____cond262 or (____switch262 == "impossible" or ____switch262 == "death_sentence") + if ____cond262 then + dayLength = -120 + NightLength = -120 + break + end + until true + do + pcall(function() + local ____require_result_32 = require("DayNightCycleManager") + local DayNightCycleManager = ____require_result_32.DayNightCycleManager + local dayManager = DayNightCycleManager:getInstance() + local oldDayDuration = dayManager:GetDayDuration() + local oldNightDuration = dayManager:GetNightDuration() + dayManager:SetDayDuration(oldDayDuration + dayLength) + dayManager:SetNightDuration(oldNightDuration + NightLength) + if self.leader == "death_sentence" then + applyDeathSentenceContractDayDurationAdjustments(nil, self.winningContractSnapshot) + end + end) + end + end + self:sendUpdateToAllClients() +end +function DifficultyManager.prototype.getNpcStatScale(self) + repeat + local ____switch266 = self.leader + local ____cond266 = ____switch266 == "easy" + if ____cond266 then + return 0.5 + end + ____cond266 = ____cond266 or ____switch266 == "normal" + if ____cond266 then + return 1 + end + ____cond266 = ____cond266 or ____switch266 == "hard" + if ____cond266 then + return 2 + end + ____cond266 = ____cond266 or ____switch266 == "impossible" + if ____cond266 then + return 4 + end + ____cond266 = ____cond266 or ____switch266 == "death_sentence" + if ____cond266 then + local ____opt_33 = self.winningContractSnapshot + return ____opt_33 and ____opt_33.rewardMultiplier or 6 + end + do + return 1 + end + until true +end +function DifficultyManager.prototype.getWinningContractSnapshot(self) + return self.winningContractSnapshot +end +function DifficultyManager.prototype.getActiveDeathSentenceContract(self) + if not self.selectionEnd then + return nil + end + return self.winningContractId +end +function DifficultyManager.prototype.getActiveDeathSentenceContractPayload(self) + if not self.selectionEnd or not self.winningContractSnapshot then + return nil + end + local w = self.winningContractSnapshot + local durability = normalizeDeathSentenceContractDurability(nil, w.durability, w.instanceId) + return { + instanceId = w.instanceId, + serial = w.serial, + titleIndex = w.titleIndex, + rarity = w.rarity, + rewardMultiplier = w.rewardMultiplier, + traitId = w.traitId, + complicationIds = {unpack(w.complicationIds)}, + durability = durability, + durabilityMax = normalizeDeathSentenceContractDurabilityMax(nil, w.durabilityMax, durability, w.instanceId) + } +end +function DifficultyManager.prototype.getDeathSentenceContractRewardMultiplier(self) + local ____opt_35 = self.winningContractSnapshot + return ____opt_35 and ____opt_35.rewardMultiplier or 3.5 +end +function DifficultyManager.prototype.getRarityIndex(self, rarity) + local order = { + "common", + "rare", + "epic", + "legendary", + "mythic" + } + do + local i = 0 + while i < #order do + if order[i + 1] == rarity then + return i + end + i = i + 1 + end + end + return 0 +end +function DifficultyManager.prototype.rarityByIndex(self, index) + local order = { + "common", + "rare", + "epic", + "legendary", + "mythic" + } + local clamped = math.max( + 0, + math.min( + #order - 1, + math.floor(index) + ) + ) + return order[clamped + 1] +end +function DifficultyManager.prototype.roundContractMultiplier(self, value) + return math.floor(value * 100 + 0.5) / 100 +end +function DifficultyManager.prototype.rollMatchEndContractRarity(self) + if self.leader ~= "death_sentence" then + local r = RandomInt(1, 100) + if r <= 70 then + return "rare" + end + if r <= 95 then + return "epic" + end + return "legendary" + end + local ____opt_37 = self.winningContractSnapshot + local base = ____opt_37 and ____opt_37.rarity or "epic" + local baseIdx = self:getRarityIndex(base) + local roll = RandomInt(1, 100) + if roll <= 75 then + return base + end + return self:rarityByIndex(baseIdx + 1) +end +function DifficultyManager.prototype.grantMatchEndContractIfEligible(self, playerId) + if self.leader ~= "impossible" and self.leader ~= "death_sentence" then + return nil + end + if not PlayerResource:IsValidPlayerID(playerId) or PlayerResource:IsFakeClient(playerId) then + return nil + end + if not self.deathSentenceHydratedByPlayer[playerId] then + print(("[DifficultyManager] grantMatchEndContractIfEligible: pid=" .. tostring(playerId)) .. " пропуск — ростер не подтянут с бэка (hyd=false).") + return nil + end + self:ensurePlayerContractInventory(playerId) + self:sanitizePlayerContractInventory(playerId) + local inv = self.contractInventoryByPlayer[playerId] or ({}) + if #inv >= DifficultyManager.CONTRACT_INVENTORY_CAP then + print(((((("[DifficultyManager] grantMatchEndContractIfEligible: pid=" .. tostring(playerId)) .. " пропуск — лимит ростера ") .. tostring(DifficultyManager.CONTRACT_INVENTORY_CAP)) .. " (сейчас ") .. tostring(#inv)) .. ").") + return nil + end + local rarity = self:rollMatchEndContractRarity() + local serial = #inv + 1 + local created = generateDeathSentenceContractInstanceWithRarity(nil, serial - 1, rarity) + if self.leader == "death_sentence" then + local passed = self.winningContractSnapshot + if passed then + local delta = RandomFloat(0.25, 1) + created.rewardMultiplier = self:roundContractMultiplier(passed.rewardMultiplier + delta) + end + end + inv[#inv + 1] = created + self:sortContractRosterForDisplay(inv) + self.deathSentenceRosterMutationEpoch = self.deathSentenceRosterMutationEpoch + 1 + self:rebuildSharedContractRosterFromPlayerInventories() + self:syncContractInventoryToClient(playerId) + self:sendUpdateToAllClients() + return created +end +function DifficultyManager.prototype.applyDeathSentenceContractDurabilityOnLoss(self, done) + if self.leader ~= "death_sentence" then + done(nil) + return + end + local cid = self.winningContractId + if not cid or cid == DEATH_SENTENCE_EMPTY_CONTRACT_VOTE then + done(nil) + return + end + local ownerPid + do + local i = 0 + while i < DOTA_MAX_PLAYERS do + do + local pid = i + if not PlayerResource:IsValidPlayerID(pid) or PlayerResource:IsFakeClient(pid) then + goto __continue293 + end + local inv = self.contractInventoryByPlayer[pid] + if not inv then + goto __continue293 + end + if __TS__ArraySome( + inv, + function(____, x) return x.instanceId == cid end + ) then + ownerPid = pid + break + end + end + ::__continue293:: + i = i + 1 + end + end + if ownerPid == nil then + print(("[DifficultyManager] DS durability loss: экземпляр " .. cid) .. " не найден ни в одном ростере") + done(nil) + return + end + local inv = self.contractInventoryByPlayer[ownerPid] + local idx = __TS__ArrayFindIndex( + inv, + function(____, x) return x.instanceId == cid end + ) + if idx < 0 then + done(nil) + return + end + local inst = inv[idx + 1] + local cur = normalizeDeathSentenceContractDurability(nil, inst.durability, inst.instanceId) + inst.durability = cur + local next = cur - 1 + if next <= 0 then + __TS__ArraySplice(inv, idx, 1) + do + local j = 0 + while j < DOTA_MAX_PLAYERS do + local pid = j + if self.contractVotes[pid] == cid then + self.contractVotes[pid] = nil + end + if self.contractColumnOfferByPlayer[pid] == cid then + self.contractColumnOfferByPlayer[pid] = nil + end + j = j + 1 + end + end + print(((("[DifficultyManager] DS durability: приговор " .. cid) .. " сломан (было ") .. tostring(cur)) .. "→0), удалён из инвентаря") + else + inst.durability = next + print((((("[DifficultyManager] DS durability: приговор " .. cid) .. " ") .. tostring(cur)) .. "→") .. tostring(next)) + end + self:sortContractRosterForDisplay(inv) + self.deathSentenceRosterMutationEpoch = self.deathSentenceRosterMutationEpoch + 1 + self:rebuildSharedContractRosterFromPlayerInventories() + self:saveContractRosterForPlayer( + ownerPid, + function() + self:sendUpdateToAllClients() + done(nil) + end + ) +end +function DifficultyManager.prototype.saveContractRosterForPlayer(self, playerId, done) + if not PlayerResource:IsValidPlayerID(playerId) or PlayerResource:IsFakeClient(playerId) then + done(nil, false) + return + end + local steamId = PlayerResource:GetSteamAccountID(playerId) + if not steamId or steamId == 0 then + done(nil, false) + return + end + saveDeathSentenceContractsToBackend( + nil, + tostring(steamId), + self.contractInventoryByPlayer[playerId] or ({}), + done + ) +end +function DifficultyManager.prototype.NPC(self, npc) + if npc:GetTeam() == DOTA_TEAM_GOODGUYS then + return + end + local s = self:getNpcStatScale() + local result = s + npc:SetBaseMaxHealth(npc:GetMaxHealth() * result) + npc:SetMaxHealth(npc:GetMaxHealth() * result) + npc:SetHealth(npc:GetMaxHealth()) + npc:SetBaseHealthRegen(npc:GetBaseHealthRegen() * result) + npc:SetBaseDamageMin(npc:GetBaseDamageMin() * result) + npc:SetBaseDamageMax(npc:GetBaseDamageMax() * result) + _G.Difficulter = s +end +DifficultyManager.CONTRACT_INVENTORY_CAP = 30 +____exports.Difficulty = DifficultyManager:getInstance() +return ____exports diff --git a/scripts/vscripts/equipment/api.lua b/scripts/vscripts/equipment/api.lua new file mode 100644 index 0000000..6becf26 --- /dev/null +++ b/scripts/vscripts/equipment/api.lua @@ -0,0 +1,93 @@ +local ____lualib = require("lualib_bundle") +local __TS__ArrayIsArray = ____lualib.__TS__ArrayIsArray +local ____exports = {} +local ____api_helper = require("api_helper") +local encodeApiBody = ____api_helper.encodeApiBody +local setApiHeaders = ____api_helper.setApiHeaders +local ____server_config = require("server_config") +local SERVER_CONFIG = ____server_config.SERVER_CONFIG +local function decodeBody(self, body, fallback) + do + local ____try, ____hasReturned, ____returnValue = pcall(function() + local parsed = {json.decode(body)} + if __TS__ArrayIsArray(parsed) and #parsed > 0 then + return true, parsed[1] + end + if parsed and type(parsed) == "table" then + return true, parsed + end + end) + if ____try and ____hasReturned then + return ____returnValue + end + end + return fallback +end +function ____exports.getSteamIdForApi(self, playerId) + local steamId = PlayerResource:GetSteamAccountID(playerId) + if not steamId or steamId <= 0 then + return nil + end + return steamId +end +function ____exports.loadEquipmentStateFromApi(self, playerId, onDone) + local steamId = ____exports.getSteamIdForApi(nil, playerId) + if not steamId then + onDone(nil, nil) + return + end + local req = CreateHTTPRequest( + "GET", + ((SERVER_CONFIG.API_URL .. "/player/") .. tostring(steamId)) .. "/equipment" + ) + setApiHeaders(nil, req) + req:Send(function(res) + if res.StatusCode >= 200 and res.StatusCode < 300 then + local parsed = decodeBody( + nil, + tostring(res.Body or ""), + {} + ) + if parsed.equipment then + onDone(nil, parsed.equipment) + else + onDone(nil, parsed) + end + return + end + onDone(nil, nil) + end) +end +function ____exports.saveEquipmentStateToApi(self, playerId, state) + local steamId = ____exports.getSteamIdForApi(nil, playerId) + if not steamId then + return + end + local req = CreateHTTPRequest( + "PUT", + ((SERVER_CONFIG.API_URL .. "/player/") .. tostring(steamId)) .. "/equipment" + ) + setApiHeaders(nil, req) + req:SetHTTPRequestRawPostBody( + "application/json", + encodeApiBody(nil, {equipment = state}) + ) + req:Send(function() return nil end) +end +function ____exports.postEquipmentDropToApi(self, playerId, item) + local steamId = ____exports.getSteamIdForApi(nil, playerId) + if not steamId then + return + end + local req = CreateHTTPRequest( + "POST", + ((SERVER_CONFIG.API_URL .. "/player/") .. tostring(steamId)) .. "/equipment/drop" + ) + setApiHeaders(nil, req) + req:SetHTTPRequestRawPostBody( + "application/json", + encodeApiBody(nil, {item = item}) + ) + req:Send(function() return nil end) +end +return ____exports diff --git a/scripts/vscripts/equipment/balance.lua b/scripts/vscripts/equipment/balance.lua new file mode 100644 index 0000000..a0cdcf4 --- /dev/null +++ b/scripts/vscripts/equipment/balance.lua @@ -0,0 +1,76 @@ +--[[ Generated with https://github.com/TypeScriptToLua/TypeScriptToLua ]] +local ____exports = {} +function ____exports.getRarityBaseStatCount(self, rarity) + repeat + local ____switch3 = rarity + local ____cond3 = ____switch3 == "uncommon" + if ____cond3 then + return 1 + end + ____cond3 = ____cond3 or ____switch3 == "rare" + if ____cond3 then + return 2 + end + ____cond3 = ____cond3 or ____switch3 == "epic" + if ____cond3 then + return 3 + end + ____cond3 = ____cond3 or ____switch3 == "legendary" + if ____cond3 then + return 4 + end + do + return 1 + end + until true +end +function ____exports.getUpgradeCostByStage(self, rarity, upgradeStage) + local baseByRarity = {uncommon = 50, rare = 120, epic = 260, legendary = 500} + local multipliers = { + 1, + 1.6, + 2.4, + 3.5, + 5 + } + local stage = math.max( + 0, + math.min( + 4, + math.floor(upgradeStage) + ) + ) + return math.floor(baseByRarity[rarity] * multipliers[stage + 1]) +end +function ____exports.getDifficultyBaseRarity(self, diff) + repeat + local ____switch6 = diff + local ____cond6 = ____switch6 == "easy" + if ____cond6 then + return "uncommon" + end + ____cond6 = ____cond6 or ____switch6 == "hard" + if ____cond6 then + return "epic" + end + ____cond6 = ____cond6 or ____switch6 == "impossible" + if ____cond6 then + return "epic" + end + ____cond6 = ____cond6 or ____switch6 == "normal" + do + return "rare" + end + until true +end +function ____exports.maybeUpgradeRarityAtDrop(self, baseRarity, diff) + if diff ~= "impossible" then + return baseRarity + end + local roll = RandomInt(1, 100) + if roll <= 35 then + return "legendary" + end + return baseRarity +end +return ____exports diff --git a/scripts/vscripts/equipment/constants.lua b/scripts/vscripts/equipment/constants.lua new file mode 100644 index 0000000..cbf80de --- /dev/null +++ b/scripts/vscripts/equipment/constants.lua @@ -0,0 +1,32 @@ +--[[ Generated with https://github.com/TypeScriptToLua/TypeScriptToLua ]] +local ____exports = {} +____exports.EQUIPMENT_STATE_VERSION = 1 +____exports.EQUIPMENT_MAX_UPGRADE_STAGE = 5 +____exports.EQUIPMENT_LOADOUT_COUNT = 3 +____exports.EQUIPMENT_SLOTS = { + "helmet", + "armor", + "weapon", + "boots", + "ring", + "necklace" +} +____exports.EQUIPMENT_STAT_POOL = { + "luck_pct", + "strength_pct", + "intellect_pct", + "damage_pct", + "damage_reduction_pct", + "spell_amp_pct" +} +function ____exports.createEmptyLoadout(self) + return { + helmet = nil, + armor = nil, + weapon = nil, + boots = nil, + ring = nil, + necklace = nil + } +end +return ____exports diff --git a/scripts/vscripts/equipment/manager.lua b/scripts/vscripts/equipment/manager.lua new file mode 100644 index 0000000..93b6072 --- /dev/null +++ b/scripts/vscripts/equipment/manager.lua @@ -0,0 +1,437 @@ +local ____lualib = require("lualib_bundle") +local __TS__Class = ____lualib.__TS__Class +local Map = ____lualib.Map +local __TS__New = ____lualib.__TS__New +local __TS__Number = ____lualib.__TS__Number +local __TS__ArrayFind = ____lualib.__TS__ArrayFind +local __TS__ObjectKeys = ____lualib.__TS__ObjectKeys +local __TS__ArrayIsArray = ____lualib.__TS__ArrayIsArray +local __TS__ObjectAssign = ____lualib.__TS__ObjectAssign +local Set = ____lualib.Set +local __TS__ObjectValues = ____lualib.__TS__ObjectValues +local __TS__ArrayFilter = ____lualib.__TS__ArrayFilter +local ____exports = {} +local ____difficulty_manager = require("difficulty_manager") +local Difficulty = ____difficulty_manager.Difficulty +local ____player_info = require("player_info") +local PlayerInfo = ____player_info.PlayerInfo +local ____store_manager = require("store_manager") +local StoreManager = ____store_manager.StoreManager +local ____modifier_equipment_stats = require("abilities.modifiers.modifier_equipment_stats") +local modifier_equipment_stats = ____modifier_equipment_stats.modifier_equipment_stats +local ____constants = require("equipment.constants") +local EQUIPMENT_MAX_UPGRADE_STAGE = ____constants.EQUIPMENT_MAX_UPGRADE_STAGE +local EQUIPMENT_STATE_VERSION = ____constants.EQUIPMENT_STATE_VERSION +local createEmptyLoadout = ____constants.createEmptyLoadout +local ____balance = require("equipment.balance") +local getUpgradeCostByStage = ____balance.getUpgradeCostByStage +local ____api = require("equipment.api") +local postEquipmentDropToApi = ____api.postEquipmentDropToApi +local loadEquipmentStateFromApi = ____api.loadEquipmentStateFromApi +local saveEquipmentStateToApi = ____api.saveEquipmentStateToApi +local ____roll = require("equipment.roll") +local rollAdditionalStatForStageFive = ____roll.rollAdditionalStatForStageFive +local rollPostMatchItem = ____roll.rollPostMatchItem +local rollRandomUpgradeDeltaPct = ____roll.rollRandomUpgradeDeltaPct +local EQUIPMENT_MODIFIER_NAME = modifier_equipment_stats.name +____exports.EquipmentManager = __TS__Class() +local EquipmentManager = ____exports.EquipmentManager +EquipmentManager.name = "EquipmentManager" +EquipmentManager.____file_path = "scripts/vscripts/equipment/manager.lua" +function EquipmentManager.prototype.____constructor(self) + self.playerState = __TS__New(Map) + self.playerOpLock = __TS__New(Map) + if not IsServer() then + return + end + self:registerEvents() + self:registerLoadHooks() +end +function EquipmentManager.getInstance(self) + if not ____exports.EquipmentManager.instance then + ____exports.EquipmentManager.instance = __TS__New(____exports.EquipmentManager) + end + return ____exports.EquipmentManager.instance +end +function EquipmentManager.prototype.registerLoadHooks(self) + ListenToGameEvent( + "player_connect_full", + function(event) + local playerId = event.PlayerID + if playerId == nil or playerId < 0 then + return + end + self:ensureLoaded(playerId) + end, + nil + ) +end +function EquipmentManager.prototype.registerEvents(self) + CustomGameEventManager:RegisterListener( + "equipment_request_state", + function(_source, event) + self:ensureLoaded(event.PlayerID, true) + end + ) + CustomGameEventManager:RegisterListener( + "equipment_set_active_loadout", + function(_source, event) + self:withPlayerLock( + event.PlayerID, + function() + local state = self:getOrCreateState(event.PlayerID) + local nextIndex = math.floor(__TS__Number(event.loadout_index or 1)) + if nextIndex < 1 or nextIndex > 3 then + self:sendActionResult(event.PlayerID, {success = false, message = "Некорректный слот пресета"}) + return + end + state.activeLoadoutIndex = nextIndex + self:afterStateChanged(event.PlayerID, state) + end + ) + end + ) + CustomGameEventManager:RegisterListener( + "equipment_equip_item", + function(_source, event) + self:withPlayerLock( + event.PlayerID, + function() + local state = self:getOrCreateState(event.PlayerID) + local instanceId = tostring(event.instance_id or "") + local item = __TS__ArrayFind( + state.inventory, + function(____, x) return x.instanceId == instanceId end + ) + if not item then + self:sendActionResult(event.PlayerID, {success = false, message = "Предмет не найден"}) + return + end + local idxRaw = __TS__Number(event.loadout_index or state.activeLoadoutIndex) + local idx = math.floor(idxRaw) + local loadoutIndex = idx >= 1 and idx <= 3 and idx or state.activeLoadoutIndex + self:removeItemFromAllLoadouts(state, instanceId) + state.loadouts[loadoutIndex][item.slotType] = instanceId + self:afterStateChanged(event.PlayerID, state) + end + ) + end + ) + CustomGameEventManager:RegisterListener( + "equipment_unequip_item", + function(_source, event) + self:withPlayerLock( + event.PlayerID, + function() + local state = self:getOrCreateState(event.PlayerID) + local instanceId = tostring(event.instance_id or "") + local idxRaw = __TS__Number(event.loadout_index or state.activeLoadoutIndex) + local idx = math.floor(idxRaw) + local loadoutIndex = idx >= 1 and idx <= 3 and idx or state.activeLoadoutIndex + for ____, slot in ipairs(__TS__ObjectKeys(state.loadouts[loadoutIndex])) do + if state.loadouts[loadoutIndex][slot] == instanceId then + state.loadouts[loadoutIndex][slot] = nil + end + end + self:afterStateChanged(event.PlayerID, state) + end + ) + end + ) + CustomGameEventManager:RegisterListener( + "equipment_upgrade_item", + function(_source, event) + self:withPlayerLock( + event.PlayerID, + function() + local state = self:getOrCreateState(event.PlayerID) + local instanceId = tostring(event.instance_id or "") + local item = __TS__ArrayFind( + state.inventory, + function(____, x) return x.instanceId == instanceId end + ) + if not item then + self:sendActionResult(event.PlayerID, {success = false, message = "Предмет не найден"}) + return + end + if item.upgradeStage >= EQUIPMENT_MAX_UPGRADE_STAGE then + self:sendActionResult(event.PlayerID, {success = false, message = "Предмет уже максимального уровня"}) + return + end + local store = StoreManager:getInstance() + local cost = getUpgradeCostByStage(nil, item.rarity, item.upgradeStage) + if not store:removeFreeCurrency(event.PlayerID, cost) then + self:sendActionResult(event.PlayerID, {success = false, message = "Недостаточно free currency"}) + return + end + store:saveCurrencyToServer(event.PlayerID) + item.upgradeStage = item.upgradeStage + 1 + if item.upgradeStage <= 4 then + for ____, stat in ipairs(item.stats) do + stat.valuePct = stat.valuePct + rollRandomUpgradeDeltaPct(nil) + end + elseif item.upgradeStage == 5 then + local extra = rollAdditionalStatForStageFive(nil, item.stats, item.rarity) + if extra then + local ____item_stats_0 = item.stats + ____item_stats_0[#____item_stats_0 + 1] = extra + end + end + self:afterStateChanged(event.PlayerID, state) + end + ) + end + ) +end +function EquipmentManager.prototype.ensureLoaded(self, playerId, forcePublish) + if forcePublish == nil then + forcePublish = false + end + local hadState = self.playerState:has(playerId) + local ____local = self:getOrCreateState(playerId) + if not hadState or forcePublish then + self:publishState(playerId, ____local) + end + loadEquipmentStateFromApi( + nil, + playerId, + function(____, remote) + if not remote then + return + end + local normalized = self:normalizeState(remote) + self.playerState:set(playerId, normalized) + self:publishState(playerId, normalized) + end + ) +end +function EquipmentManager.prototype.rollDropForPlayer(self, playerId, difficultyKey) + if not PlayerResource:IsValidPlayerID(playerId) or PlayerResource:IsFakeClient(playerId) then + return nil + end + local state = self:getOrCreateState(playerId) + local diff = difficultyKey or Difficulty.leader or "normal" + local item = rollPostMatchItem(nil, playerId, diff) + local ____state_inventory_1 = state.inventory + ____state_inventory_1[#____state_inventory_1 + 1] = item + self:afterStateChanged(playerId, state, false) + postEquipmentDropToApi(nil, playerId, item) + local player = PlayerResource:GetPlayer(playerId) + if player then + CustomGameEventManager:Send_ServerToPlayer(player, "equipment_drop_received", {item = item}) + end + return item +end +function EquipmentManager.prototype.applyEquipmentToHero(self, hero) + if not hero or not hero:IsRealHero() or hero:IsIllusion() then + return + end + local playerId = hero:GetPlayerOwnerID() + if playerId == nil or playerId < 0 then + return + end + local state = self:getOrCreateState(playerId) + local aggregate = self:aggregateStatsForActiveLoadout(state) + hero:AddNewModifier( + hero, + nil, + EQUIPMENT_MODIFIER_NAME, + { + luckPct = tostring(aggregate.luckPct), + strengthPct = tostring(aggregate.strengthPct), + intellectPct = tostring(aggregate.intellectPct), + damagePct = tostring(aggregate.damagePct), + damageReductionPct = tostring(aggregate.damageReductionPct), + spellAmpPct = tostring(aggregate.spellAmpPct) + } + ) +end +function EquipmentManager.prototype.reapplyForPlayer(self, playerId) + if not PlayerResource:IsValidPlayerID(playerId) then + return + end + local hero = PlayerResource:GetSelectedHeroEntity(playerId) + if not hero then + return + end + self:applyEquipmentToHero(hero) +end +function EquipmentManager.prototype.getOrCreateState(self, playerId) + local existing = self.playerState:get(playerId) + if existing then + return existing + end + local info = PlayerInfo:GetPlayerInfo(playerId) + local fromInfo = info and info.equipment_state + local state = self:normalizeState(fromInfo) + self.playerState:set(playerId, state) + return state +end +function EquipmentManager.prototype.normalizeState(self, state) + local fallback = { + version = EQUIPMENT_STATE_VERSION, + inventory = {}, + activeLoadoutIndex = 1, + loadouts = { + [1] = createEmptyLoadout(nil), + [2] = createEmptyLoadout(nil), + [3] = createEmptyLoadout(nil) + } + } + if not state or type(state) ~= "table" then + return fallback + end + local ____state_activeLoadoutIndex_4 = state.activeLoadoutIndex + if ____state_activeLoadoutIndex_4 == nil then + ____state_activeLoadoutIndex_4 = 1 + end + local activeRaw = __TS__Number(____state_activeLoadoutIndex_4) + local active = activeRaw >= 1 and activeRaw <= 3 and math.floor(activeRaw) or 1 + local ____EQUIPMENT_STATE_VERSION_16 = EQUIPMENT_STATE_VERSION + local ____temp_17 = __TS__ArrayIsArray(state.inventory) and state.inventory or ({}) + local ____createEmptyLoadout_result_7 = createEmptyLoadout(nil) + local ____opt_5 = state.loadouts + if ____opt_5 ~= nil then + ____opt_5 = ____opt_5[1] + end + local ____TS__ObjectAssign_result_14 = __TS__ObjectAssign({}, ____createEmptyLoadout_result_7, ____opt_5) + local ____createEmptyLoadout_result_10 = createEmptyLoadout(nil) + local ____opt_8 = state.loadouts + if ____opt_8 ~= nil then + ____opt_8 = ____opt_8[2] + end + local ____TS__ObjectAssign_result_15 = __TS__ObjectAssign({}, ____createEmptyLoadout_result_10, ____opt_8) + local ____createEmptyLoadout_result_13 = createEmptyLoadout(nil) + local ____opt_11 = state.loadouts + if ____opt_11 ~= nil then + ____opt_11 = ____opt_11[3] + end + return { + version = ____EQUIPMENT_STATE_VERSION_16, + inventory = ____temp_17, + activeLoadoutIndex = active, + loadouts = { + [1] = ____TS__ObjectAssign_result_14, + [2] = ____TS__ObjectAssign_result_15, + [3] = __TS__ObjectAssign({}, ____createEmptyLoadout_result_13, ____opt_11) + } + } +end +function EquipmentManager.prototype.withPlayerLock(self, playerId, fn) + if self.playerOpLock:get(playerId) then + self:sendActionResult(playerId, {success = false, message = "Операция уже выполняется, подожди"}) + return + end + self.playerOpLock:set(playerId, true) + do + pcall(function() + fn(nil) + end) + do + self.playerOpLock:set(playerId, false) + end + end +end +function EquipmentManager.prototype.afterStateChanged(self, playerId, state, includeActionOk) + if includeActionOk == nil then + includeActionOk = true + end + self.playerState:set(playerId, state) + self:publishState(playerId, state) + saveEquipmentStateToApi(nil, playerId, state) + if includeActionOk then + self:sendActionResult(playerId, {success = true}) + end + self:reapplyForPlayer(playerId) +end +function EquipmentManager.prototype.publishState(self, playerId, state) + CustomNetTables:SetTableValue( + "equipment", + tostring(playerId), + state + ) + local playerInfo = PlayerInfo:GetPlayerInfo(playerId) or ({}) + playerInfo.equipment_state = state + PlayerInfo:UpdatePlayerInfo(playerId, playerInfo) + local player = PlayerResource:GetPlayer(playerId) + if player then + CustomGameEventManager:Send_ServerToPlayer(player, "equipment_state", {state = state}) + end +end +function EquipmentManager.prototype.sendActionResult(self, playerId, payload) + local player = PlayerResource:GetPlayer(playerId) + if not player then + return + end + CustomGameEventManager:Send_ServerToPlayer(player, "equipment_action_result", payload) +end +function EquipmentManager.prototype.removeItemFromAllLoadouts(self, state, instanceId) + local loadouts = {state.loadouts[1], state.loadouts[2], state.loadouts[3]} + for ____, loadout in ipairs(loadouts) do + for ____, slot in ipairs(__TS__ObjectKeys(loadout)) do + if loadout[slot] == instanceId then + loadout[slot] = nil + end + end + end +end +function EquipmentManager.prototype.aggregateStatsForActiveLoadout(self, state) + local loadout = state.loadouts[state.activeLoadoutIndex] + local equippedIds = __TS__New(Set) + for ____, value in ipairs(__TS__ObjectValues(loadout)) do + if value then + equippedIds:add(value) + end + end + local equipped = __TS__ArrayFilter( + state.inventory, + function(____, x) return equippedIds:has(x.instanceId) end + ) + local result = { + luckPct = 0, + strengthPct = 0, + intellectPct = 0, + damagePct = 0, + damageReductionPct = 0, + spellAmpPct = 0 + } + for ____, item in ipairs(equipped) do + for ____, stat in ipairs(item.stats) do + repeat + local ____switch74 = stat.statKey + local ____cond74 = ____switch74 == "luck_pct" + if ____cond74 then + result.luckPct = result.luckPct + stat.valuePct + break + end + ____cond74 = ____cond74 or ____switch74 == "strength_pct" + if ____cond74 then + result.strengthPct = result.strengthPct + stat.valuePct + break + end + ____cond74 = ____cond74 or ____switch74 == "intellect_pct" + if ____cond74 then + result.intellectPct = result.intellectPct + stat.valuePct + break + end + ____cond74 = ____cond74 or ____switch74 == "damage_pct" + if ____cond74 then + result.damagePct = result.damagePct + stat.valuePct + break + end + ____cond74 = ____cond74 or ____switch74 == "damage_reduction_pct" + if ____cond74 then + result.damageReductionPct = result.damageReductionPct + stat.valuePct + break + end + ____cond74 = ____cond74 or ____switch74 == "spell_amp_pct" + if ____cond74 then + result.spellAmpPct = result.spellAmpPct + stat.valuePct + break + end + until true + end + end + return result +end +return ____exports diff --git a/scripts/vscripts/equipment/roll.lua b/scripts/vscripts/equipment/roll.lua new file mode 100644 index 0000000..c20ac11 --- /dev/null +++ b/scripts/vscripts/equipment/roll.lua @@ -0,0 +1,107 @@ +local ____lualib = require("lualib_bundle") +local __TS__ArraySplice = ____lualib.__TS__ArraySplice +local __TS__ArrayMap = ____lualib.__TS__ArrayMap +local Set = ____lualib.Set +local __TS__New = ____lualib.__TS__New +local __TS__ArrayFilter = ____lualib.__TS__ArrayFilter +local ____exports = {} +local ____constants = require("equipment.constants") +local EQUIPMENT_SLOTS = ____constants.EQUIPMENT_SLOTS +local EQUIPMENT_STAT_POOL = ____constants.EQUIPMENT_STAT_POOL +local ____balance = require("equipment.balance") +local getDifficultyBaseRarity = ____balance.getDifficultyBaseRarity +local getRarityBaseStatCount = ____balance.getRarityBaseStatCount +local maybeUpgradeRarityAtDrop = ____balance.maybeUpgradeRarityAtDrop +local function rollSlot(self) + return EQUIPMENT_SLOTS[RandomInt(0, #EQUIPMENT_SLOTS - 1) + 1] +end +local function rollStatValuePct(self, rarity) + repeat + local ____switch4 = rarity + local ____cond4 = ____switch4 == "uncommon" + if ____cond4 then + return RandomInt(1, 5) + end + ____cond4 = ____cond4 or ____switch4 == "rare" + if ____cond4 then + return RandomInt(3, 8) + end + ____cond4 = ____cond4 or ____switch4 == "epic" + if ____cond4 then + return RandomInt(6, 12) + end + ____cond4 = ____cond4 or ____switch4 == "legendary" + if ____cond4 then + return RandomInt(10, 16) + end + do + return RandomInt(1, 5) + end + until true +end +local function pickUniqueStatKeys(self, count) + local pool = {unpack(EQUIPMENT_STAT_POOL)} + local result = {} + local capped = math.max( + 1, + math.min(count, #pool) + ) + while #result < capped and #pool > 0 do + local idx = RandomInt(0, #pool - 1) + local picked = unpack(__TS__ArraySplice(pool, idx, 1)) + result[#result + 1] = picked + end + return result +end +function ____exports.rollInitialStatsByRarity(self, rarity) + local statCount = getRarityBaseStatCount(nil, rarity) + return __TS__ArrayMap( + pickUniqueStatKeys(nil, statCount), + function(____, statKey) return { + statKey = statKey, + valuePct = rollStatValuePct(nil, rarity) + } end + ) +end +function ____exports.rollRandomUpgradeDeltaPct(self) + return RandomInt(1, 3) +end +function ____exports.rollAdditionalStatForStageFive(self, existingStats, rarity) + local used = __TS__New( + Set, + __TS__ArrayMap( + existingStats, + function(____, x) return x.statKey end + ) + ) + local available = __TS__ArrayFilter( + EQUIPMENT_STAT_POOL, + function(____, k) return not used:has(k) end + ) + if #available <= 0 then + return nil + end + local picked = available[RandomInt(0, #available - 1) + 1] + return { + statKey = picked, + valuePct = rollStatValuePct(nil, rarity) + } +end +function ____exports.rollPostMatchItem(self, playerId, difficultyKey) + local steamId = PlayerResource:GetSteamAccountID(playerId) + local baseRarity = getDifficultyBaseRarity(nil, difficultyKey) + local rarity = maybeUpgradeRarityAtDrop(nil, baseRarity, difficultyKey) + local slotType = rollSlot(nil) + local now = math.floor(GameRules:GetGameTime() * 1000) + local instanceId = (((tostring(steamId) .. "_") .. tostring(now)) .. "_") .. tostring(RandomInt(1000, 9999)) + return { + instanceId = instanceId, + templateId = (("equip_" .. slotType) .. "_") .. rarity, + slotType = slotType, + rarity = rarity, + upgradeStage = 0, + stats = ____exports.rollInitialStatsByRarity(nil, rarity), + createdAt = now + } +end +return ____exports diff --git a/scripts/vscripts/equipment/types.lua b/scripts/vscripts/equipment/types.lua new file mode 100644 index 0000000..8e2ee58 --- /dev/null +++ b/scripts/vscripts/equipment/types.lua @@ -0,0 +1,3 @@ +--[[ Generated with https://github.com/TypeScriptToLua/TypeScriptToLua ]] +local ____exports = {} +return ____exports diff --git a/scripts/vscripts/events/random_events.lua b/scripts/vscripts/events/random_events.lua new file mode 100644 index 0000000..b211522 --- /dev/null +++ b/scripts/vscripts/events/random_events.lua @@ -0,0 +1,261 @@ +local ____lualib = require("lualib_bundle") +local __TS__Class = ____lualib.__TS__Class +local __TS__New = ____lualib.__TS__New +local __TS__ArrayFind = ____lualib.__TS__ArrayFind +local __TS__ArrayForEach = ____lualib.__TS__ArrayForEach +local ____exports = {} +local ____DayNightCycleManager = require("DayNightCycleManager") +local DayNightCycleManager = ____DayNightCycleManager.DayNightCycleManager +____exports.RandomEventsManager = __TS__Class() +local RandomEventsManager = ____exports.RandomEventsManager +RandomEventsManager.name = "RandomEventsManager" +RandomEventsManager.____file_path = "scripts/vscripts/events/random_events.lua" +function RandomEventsManager.prototype.____constructor(self) + self.events = {} + self.gameTimer = 0 + self.nextEventTime = 0 + self:registerEvents() + self:startEventLoop() +end +function RandomEventsManager.getInstance(self) + if not ____exports.RandomEventsManager.instance then + ____exports.RandomEventsManager.instance = __TS__New(____exports.RandomEventsManager) + end + return ____exports.RandomEventsManager.instance +end +function RandomEventsManager.prototype.registerEvents(self) + self.events = { + { + name = "gold_rush", + chance = 0.1, + minInterval = 600, + maxInterval = 3600, + duration = 45, + execute = function() return self:startGoldRush() end + }, + { + name = "midas_hands", + chance = 0.1, + minInterval = 1200, + maxInterval = 2400, + duration = 25, + execute = function() return self:StartMidasHands() end + } + } +end +function RandomEventsManager.prototype.startEventLoop(self) + Timers:CreateTimer(function() + self.gameTimer = self.gameTimer + 1 + if self.gameTimer % 60 == 0 then + local dayNightManager = DayNightCycleManager:getInstance() + local isDaytime = dayNightManager:IsDaytime() + if isDaytime then + self:triggerRandomEvent() + end + end + return 1 + end) +end +function RandomEventsManager.prototype.checkAndTriggerEvents(self) + if self.gameTimer >= self.nextEventTime then + local dayNightManager = DayNightCycleManager:getInstance() + if dayNightManager:IsDaytime() then + self:triggerRandomEvent() + end + local randomEvent = self:getRandomEvent() + if randomEvent then + self.nextEventTime = self.gameTimer + RandomInt(randomEvent.minInterval, randomEvent.maxInterval) + end + end +end +function RandomEventsManager.prototype.getRandomEvent(self) + local roll = RandomInt(1, 100) + local chanceSum = 0 + for ____, event in ipairs(self.events) do + chanceSum = chanceSum + event.chance + if roll <= chanceSum then + return event + end + end + return nil +end +function RandomEventsManager.prototype.triggerRandomEvent(self) + local event = self:getRandomEvent() + if event then + event:execute() + end +end +function RandomEventsManager.prototype.sendEventNotification(self, eventName, duration, description) + CustomGameEventManager:Send_ServerToAllClients("random_event_started", {event_name = eventName, duration = duration, description = description}) +end +function RandomEventsManager.prototype.startGoldRush(self) + local event = __TS__ArrayFind( + self.events, + function(____, e) return e.name == "gold_rush" end + ) + local duration = 45 + self:sendEventNotification("#event_gold_rush", duration, "#event_gold_rush_description") + local numGoldPiles = 10 * PlayerResource:GetPlayerCountForTeam(DOTA_TEAM_GOODGUYS) + local currentPile = 0 + local spawnNextGold + spawnNextGold = function() + if currentPile >= numGoldPiles then + return + end + local heroes = FindUnitsInRadius( + DOTA_TEAM_GOODGUYS, + Vector(0, 0, 0), + nil, + FIND_UNITS_EVERYWHERE, + DOTA_UNIT_TARGET_TEAM_FRIENDLY, + DOTA_UNIT_TARGET_HERO, + DOTA_UNIT_TARGET_FLAG_NONE, + FIND_ANY_ORDER, + false + ) + if #heroes > 0 then + local randomHero = heroes[RandomInt(0, #heroes - 1) + 1] + local heroPos = randomHero:GetAbsOrigin() + local spawnDistance = RandomFloat(300, 800) + local randomAngle = RandomFloat(0, 2 * math.pi) + local randomX = heroPos.x + spawnDistance * math.cos(randomAngle) + local randomY = heroPos.y + spawnDistance * math.sin(randomAngle) + local location = Vector(randomX, randomY, 0) + local groundHeight = GetGroundHeight(location, nil) + local spawnPos = Vector(randomX, randomY) + local item = CreateItem("item_bag_of_gold", nil, nil) + if item ~= nil then + local container = CreateItemOnPositionSync(spawnPos, item) + local dropRadius = RandomFloat(50, 100) + item:LaunchLootInitialHeight( + false, + 1000, + 150, + 1, + spawnPos + RandomVector(dropRadius) + ) + if container ~= nil then + Timers:CreateTimer( + 30, + function() + if IsValidEntity(container) then + container:RemoveSelf() + end + end + ) + currentPile = currentPile + 1 + Timers:CreateTimer( + duration / numGoldPiles, + function() return spawnNextGold(nil) end + ) + end + end + end + end + spawnNextGold(nil) + GameRules:SendCustomMessage( + ("Золотая лихорадка! (" .. tostring(duration)) .. " сек) Собирайте золото, пока оно не исчезло!", + 0, + 0 + ) +end +function RandomEventsManager.prototype.StartMidasHands(self) + local event = __TS__ArrayFind( + self.events, + function(____, e) return e.name == "midas_hands" end + ) + local duration = event and event.duration or 180 + self:sendEventNotification("#event_midas_hands", duration, "#event_midas_hands_description") + local eventHandler = ListenToGameEvent( + "entity_killed", + function(event) + local killedUnit = EntIndexToHScript(event.entindex_killed) + if killedUnit and killedUnit:GetTeamNumber() ~= DOTA_TEAM_GOODGUYS then + local position = killedUnit:GetAbsOrigin() + if RandomFloat(1, 100) <= 15 then + local item = CreateItem("item_bag_of_gold", nil, nil) + if item then + CreateItemOnPositionSync(position, item) + local dropRadius = RandomFloat(50, 100) + item:LaunchLootInitialHeight( + false, + 0, + 150, + 0.5, + position + RandomVector(dropRadius) + ) + end + end + end + end, + nil + ) + Timers:CreateTimer( + duration, + function() + StopListeningToGameEvent(eventHandler) + GameRules:SendCustomMessage("Благословение Мидаса закончилось!", 0, 0) + end + ) + GameRules:SendCustomMessage( + ("Благословение Мидаса! (" .. tostring(duration)) .. " сек) После убийства любого врага, вы получите мешок с золотом!", + 0, + 0 + ) +end +function RandomEventsManager.prototype.createHealingSprings(self) + local event = __TS__ArrayFind( + self.events, + function(____, e) return e.name == "healing_springs" end + ) + local duration = event and event.duration or 120 + local springLocations = Entities:FindAllByName("spring_point_*") + local springs = {} + __TS__ArrayForEach( + springLocations, + function(____, location) + local spring = CreateUnitByName( + "npc_healing_spring", + location:GetAbsOrigin(), + true, + nil, + nil, + DOTA_TEAM_NEUTRALS + ) + springs[#springs + 1] = spring + end + ) + Timers:CreateTimer( + duration, + function() + __TS__ArrayForEach( + springs, + function(____, spring) + if IsValidEntity(spring) then + spring:RemoveSelf() + end + end + ) + GameRules:SendCustomMessage("Целебные источники иссякли!", 0, 0) + end + ) + GameRules:SendCustomMessage( + ("Появились целебные источники! (" .. tostring(duration)) .. " сек) Восстановите здоровье и ману!", + 0, + 0 + ) +end +function RandomEventsManager.prototype.triggerEvent(self, eventName) + local dayNightManager = DayNightCycleManager:getInstance() + if not dayNightManager:IsDaytime() then + return + end + local event = __TS__ArrayFind( + self.events, + function(____, e) return e.name == eventName end + ) + if event then + event:execute() + end +end +return ____exports diff --git a/scripts/vscripts/filters/order_filter.lua b/scripts/vscripts/filters/order_filter.lua new file mode 100644 index 0000000..84addd4 --- /dev/null +++ b/scripts/vscripts/filters/order_filter.lua @@ -0,0 +1,220 @@ +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 ZFilter +local ____blackshop = require("blackshop") +local BlackShop = ____blackshop.BlackShop +local ____crystal_currency = require("crystal_currency") +local CrystalCurrency = ____crystal_currency.CrystalCurrency +local ____CookingSystem = require("cooking.CookingSystem") +local CookingSystem = ____CookingSystem.CookingSystem +local QUEST_GIVERS = { + "npc_quest_giver_kunkka", + "npc_quest_giver_denny", + "npc_quest_giver_oldmen", + "npc_quest_giver_firestar", + "npc_quest_giver_lina", + "npc_quest_giver_maiden", + "npc_quest_giver_friend", + "npc_quest_giver_largo", + "npc_quest_giver_doctor" +} +local function resolveOrderFilterUnitTargetIndex(self, data) + local ext = data + if data.entindex_target > 0 then + return data.entindex_target + end + if ext.entindex_target_const ~= nil and ext.entindex_target_const > 0 then + return ext.entindex_target_const + end + if ext.target_index ~= nil and ext.target_index > 0 then + return ext.target_index + end + return data.entindex_target +end +function ____exports.InitOrderFilter(self) + ZFilter:init() +end +ZFilter = __TS__Class() +ZFilter.name = "ZFilter" +ZFilter.____file_path = "scripts/vscripts/filters/order_filter.lua" +function ZFilter.prototype.____constructor(self) +end +function ZFilter.init(self) + if not ZFilter.instance then + ZFilter.instance = __TS__New(ZFilter) + GameRules:GetGameModeEntity():SetExecuteOrderFilter( + function(____, event) return ZFilter.instance:OrderFilter(event) end, + ZFilter.instance + ) + end +end +function ZFilter.prototype.OrderFilter(self, data) + local targetIndex = resolveOrderFilterUnitTargetIndex(nil, data) + local ____temp_0 + if targetIndex > 0 then + ____temp_0 = EntIndexToHScript(targetIndex) + else + ____temp_0 = nil + end + local target = ____temp_0 + if target and not target:IsNull() and target:IsBaseNPC() and target:GetUnitName() == "npc_market_blackshop" then + local unit = EntIndexToHScript(data.units["0"]) + if not unit or unit:IsNull() then + return true + end + local playerID = unit:GetPlayerOwnerID() + local player = PlayerResource:GetPlayer(playerID) + if not player then + return true + end + local shopPos = target:GetAbsOrigin() + local heroPos = unit:GetAbsOrigin() + if shopPos == nil or shopPos == nil or heroPos == nil or heroPos == nil then + return true + end + local openDistance = 400 + local distance = ((shopPos.x - heroPos.x) ^ 2 + (shopPos.y - heroPos.y) ^ 2) ^ 0.5 + unit:MoveToPosition(toVectorWS(nil, shopPos)) + if distance <= openDistance then + CustomGameEventManager:Send_ServerToPlayer(player, "black_shop_toggle", {}) + return false + end + local function checkDistance() + if not unit or not IsValidEntity(unit) or not target or not IsValidEntity(target) then + return nil + end + local currentHeroPos = unit:GetAbsOrigin() + local currentShopPos = target:GetAbsOrigin() + if currentHeroPos ~= nil and currentHeroPos ~= nil and currentShopPos ~= nil and currentShopPos ~= nil then + local currentDistance = ((currentShopPos.x - currentHeroPos.x) ^ 2 + (currentShopPos.y - currentHeroPos.y) ^ 2) ^ 0.5 + if currentDistance <= openDistance then + local currentPlayer = PlayerResource:GetPlayer(playerID) + if currentPlayer then + CustomGameEventManager:Send_ServerToPlayer(currentPlayer, "black_shop_toggle", {}) + end + return nil + end + end + return 0.2 + end + Timers:CreateTimer(0.2, checkDistance) + return false + end + if data.order_type ~= DOTA_UNIT_ORDER_CAST_TARGET and target and not target:IsNull() and target:IsBaseNPC() and __TS__ArrayIncludes( + QUEST_GIVERS, + target:GetUnitName() + ) then + local unit = EntIndexToHScript(data.units["0"]) + if not unit or unit:IsNull() then + return true + end + local playerID = unit:GetPlayerOwnerID() + local player = PlayerResource:GetPlayer(playerID) + if not player then + return true + end + local npcPos = target:GetAbsOrigin() + local heroPos = unit:GetAbsOrigin() + if npcPos == nil or npcPos == nil or heroPos == nil or heroPos == nil then + return true + end + local openDistance = 400 + local distance = ((npcPos.x - heroPos.x) ^ 2 + (npcPos.y - heroPos.y) ^ 2) ^ 0.5 + unit:MoveToPosition(toVectorWS(nil, npcPos)) + if distance <= openDistance then + CustomGameEventManager:Send_ServerToPlayer( + player, + "quests_show", + {npcName = target:GetUnitName()} + ) + return false + else + local npcName = target:GetUnitName() + local function checkDistance() + if not unit or not IsValidEntity(unit) or not target or not IsValidEntity(target) then + return nil + end + local currentHeroPos = unit:GetAbsOrigin() + local currentNpcPos = target:GetAbsOrigin() + if currentHeroPos ~= nil and currentHeroPos ~= nil and currentNpcPos ~= nil and currentNpcPos ~= nil then + local currentDistance = ((currentNpcPos.x - currentHeroPos.x) ^ 2 + (currentNpcPos.y - currentHeroPos.y) ^ 2) ^ 0.5 + if currentDistance <= openDistance then + local currentPlayer = PlayerResource:GetPlayer(playerID) + if currentPlayer then + CustomGameEventManager:Send_ServerToPlayer(currentPlayer, "quests_show", {npcName = npcName}) + end + return nil + end + end + return 0.2 + end + Timers:CreateTimer(0.2, checkDistance) + return false + end + end + if target and not target:IsNull() and target:IsBaseNPC() and target:GetUnitName() == "npc_campfire" then + local unit = EntIndexToHScript(data.units["0"]) + if not unit or unit:IsNull() then + return true + end + local playerID = unit:GetPlayerOwnerID() + local player = PlayerResource:GetPlayer(playerID) + if not player then + return true + end + local campfireIndex = target:GetEntityIndex() + local cookingSystem = CookingSystem:getInstance() + if cookingSystem:isCampfireOccupied(campfireIndex, playerID) then + CustomGameEventManager:Send_ServerToPlayer(player, "CreateIngameErrorMessage", {message = "#campfire_already_occupied"}) + return false + end + local campfirePos = target:GetAbsOrigin() + if campfirePos ~= nil and campfirePos ~= nil then + unit:MoveToPosition(toVectorWS(nil, campfirePos)) + cookingSystem:onCampfireClick(campfireIndex, playerID) + end + return false + end + if data.order_type == DOTA_UNIT_ORDER_PICKUP_ITEM then + local itemEntity = EntIndexToHScript(data.entindex_target) + if not itemEntity or itemEntity:IsNull() then + return true + end + local unit = EntIndexToHScript(data.units["0"]) + local player = PlayerResource:GetPlayer(unit:GetPlayerOwnerID()) + if not player then + return false + end + local containerItem = itemEntity + if not containerItem or type(containerItem.GetContainedItem) ~= "function" then + return true + end + local item = containerItem:GetContainedItem() + if not item or type(item.GetAbilityName) ~= "function" then + return true + end + local itemName = item:GetAbilityName() + local shopItem = BlackShop:getInstance():getItemInfo(itemName) + if shopItem then + if item._wasPurchased or item:GetPurchaser() ~= nil then + return true + end + local hero = player:GetAssignedHero() + if not hero then + return false + end + local crystalCurrency = CrystalCurrency:getInstance() + local currentCrystals = crystalCurrency:getCrystals(player:GetPlayerID()) + if shopItem.cost > currentCrystals then + CustomGameEventManager:Send_ServerToPlayer(player, "CreateIngameErrorMessage", {crystals = shopItem.cost - currentCrystals, message = "black_shop_not_enough_crystals"}) + return false + end + return true + end + end + return true +end +return ____exports diff --git a/scripts/vscripts/game_setup_lobby_check.lua b/scripts/vscripts/game_setup_lobby_check.lua new file mode 100644 index 0000000..0094f7c --- /dev/null +++ b/scripts/vscripts/game_setup_lobby_check.lua @@ -0,0 +1,178 @@ +local ____lualib = require("lualib_bundle") +local Set = ____lualib.Set +local __TS__New = ____lualib.__TS__New +local __TS__ArrayIsArray = ____lualib.__TS__ArrayIsArray +local ____exports = {} +local ____server_config = require("server_config") +local SERVER_CONFIG = ____server_config.SERVER_CONFIG +local ____api_helper = require("api_helper") +local encodeApiBody = ____api_helper.encodeApiBody +local setApiHeadersLong = ____api_helper.setApiHeadersLong +local ____real_lobby_player = require("utils.real_lobby_player") +local isRealLobbyPlayer = ____real_lobby_player.isRealLobbyPlayer +local ____difficulty_manager = require("difficulty_manager") +local Difficulty = ____difficulty_manager.Difficulty +local setupCheckSentThisPhase = false +local setupCheckInFlight = false +function ____exports.resetGameSetupLobbyCheckPhase(self) + setupCheckSentThisPhase = false + setupCheckInFlight = false +end +local function collectLobbyPlayersForSetupCheck(self) + local players = {} + local seen = __TS__New(Set) + do + local i = 0 + while i < DOTA_MAX_PLAYERS do + do + local pid = i + if not isRealLobbyPlayer(nil, pid) then + goto __continue4 + end + local steam = PlayerResource:GetSteamAccountID(pid) + if not steam or steam == 0 then + goto __continue4 + end + local steamId = tostring(steam) + if seen:has(steamId) then + goto __continue4 + end + seen:add(steamId) + local row = { + steam_id = steamId, + player_name = PlayerResource:GetPlayerName(pid) + } + local dsContract = Difficulty:getDeathSentenceContractPayloadForLossPenalty(pid) + if dsContract then + row.death_sentence_contract = dsContract + end + players[#players + 1] = row + end + ::__continue4:: + i = i + 1 + end + end + return players +end +local function syncContractsForClosedGames(self, closedGames) + if not closedGames or not __TS__ArrayIsArray(closedGames) then + return + end + local steamIdsToSync = __TS__New(Set) + for ____, row in ipairs(closedGames) do + do + if not row or type(row) ~= "table" then + goto __continue11 + end + local steamId = row.steam_id + if type(steamId) == "string" and #steamId > 0 then + steamIdsToSync:add(steamId) + end + end + ::__continue11:: + end + if steamIdsToSync.size == 0 then + return + end + do + local i = 0 + while i < DOTA_MAX_PLAYERS do + do + local pid = i + if not isRealLobbyPlayer(nil, pid) then + goto __continue16 + end + local steamId = tostring(PlayerResource:GetSteamAccountID(pid)) + if not steamIdsToSync:has(steamId) then + goto __continue16 + end + Difficulty:RequestDeathSentenceContractsSync(pid) + end + ::__continue16:: + i = i + 1 + end + end +end +--- Один запрос на фазу CUSTOM_GAME_SETUP (повтор — если в лобби ещё не было игроков). +function ____exports.sendGameSetupLobbyCheck(self) + if setupCheckInFlight then + return + end + if GameRules:State_Get() ~= DOTA_GAMERULES_STATE_CUSTOM_GAME_SETUP then + return + end + local players = collectLobbyPlayersForSetupCheck(nil) + if #players == 0 then + return + end + setupCheckInFlight = true + local request = CreateHTTPRequestScriptVM("POST", SERVER_CONFIG.API_URL .. "/game/setup-check") + setApiHeadersLong(nil, request) + request:SetHTTPRequestRawPostBody( + "application/json", + encodeApiBody(nil, {players = players}) + ) + request:Send(function(result) + setupCheckInFlight = false + if result.StatusCode < 200 or result.StatusCode >= 300 then + print("[GameSetupLobbyCheck] ⚠️ setup-check HTTP " .. tostring(result.StatusCode)) + return + end + setupCheckSentThisPhase = true + do + local function ____catch(e) + print("[GameSetupLobbyCheck] ⚠️ Ошибка разбора ответа:", e) + end + local ____try, ____hasReturned = pcall(function() + local decoded = {json.decode(result.Body)} + local data = decoded + if __TS__ArrayIsArray(decoded) and #decoded > 0 then + data = decoded[1] + elseif decoded and type(decoded) == "table" and decoded.value ~= nil then + data = decoded.value + end + local ____opt_result_2 + if data ~= nil then + ____opt_result_2 = data.closed_games + end + local closed = ____opt_result_2 + local closedCount = closed and __TS__ArrayIsArray(closed) and #closed or 0 + if closedCount > 0 then + print("[GameSetupLobbyCheck] ✅ Закрыто зависших игр: " .. tostring(closedCount)) + syncContractsForClosedGames(nil, closed) + end + do + local i = 0 + while i < DOTA_MAX_PLAYERS do + do + local pid = i + if not isRealLobbyPlayer(nil, pid) then + goto __continue29 + end + Difficulty:forceReloadDeathSentenceContractsFromBackend(pid) + end + ::__continue29:: + i = i + 1 + end + end + end) + if not ____try then + ____catch(____hasReturned) + end + end + end) +end +--- Периодически в сетапе, пока не ушли запросом (поздно зашедшие в лобби). +function ____exports.tryGameSetupLobbyCheckFromTicker(self) + if setupCheckSentThisPhase or setupCheckInFlight then + return + end + if GameRules:State_Get() ~= DOTA_GAMERULES_STATE_CUSTOM_GAME_SETUP then + return + end + if #collectLobbyPlayersForSetupCheck(nil) == 0 then + return + end + ____exports.sendGameSetupLobbyCheck(nil) +end +return ____exports diff --git a/scripts/vscripts/game_stats_tracker.lua b/scripts/vscripts/game_stats_tracker.lua new file mode 100644 index 0000000..e0f076a --- /dev/null +++ b/scripts/vscripts/game_stats_tracker.lua @@ -0,0 +1,1180 @@ +local ____lualib = require("lualib_bundle") +local __TS__Class = ____lualib.__TS__Class +local Map = ____lualib.Map +local __TS__New = ____lualib.__TS__New +local Set = ____lualib.Set +local __TS__ArrayMap = ____lualib.__TS__ArrayMap +local __TS__ArraySort = ____lualib.__TS__ArraySort +local __TS__Iterator = ____lualib.__TS__Iterator +local __TS__ArrayIsArray = ____lualib.__TS__ArrayIsArray +local __TS__Number = ____lualib.__TS__Number +local __TS__ArrayFrom = ____lualib.__TS__ArrayFrom +local ____exports = {} +local ____server_config = require("server_config") +local SERVER_CONFIG = ____server_config.SERVER_CONFIG +local ____api_helper = require("api_helper") +local encodeApiBody = ____api_helper.encodeApiBody +local setApiHeadersLong = ____api_helper.setApiHeadersLong +local ____difficulty_manager = require("difficulty_manager") +local Difficulty = ____difficulty_manager.Difficulty +local ____player_connection_state = require("utils.player_connection_state") +local DOTA_CONNECTION_STATE = ____player_connection_state.DOTA_CONNECTION_STATE +local isConnectionStateActivelyConnected = ____player_connection_state.isConnectionStateActivelyConnected +local isConnectionStateEffectivelyInGame = ____player_connection_state.isConnectionStateEffectivelyInGame +local ____real_lobby_player = require("utils.real_lobby_player") +local collectStatsEligiblePlayerIds = ____real_lobby_player.collectStatsEligiblePlayerIds +local isRealLobbyPlayer = ____real_lobby_player.isRealLobbyPlayer +local ____match_end_combat_stats = require("match_end_combat_stats") +local MatchEndCombatStats = ____match_end_combat_stats.MatchEndCombatStats +local function rawPrintFn(____, ...) + _G:print(...) +end +local ENABLE_VERBOSE_GAME_STATS_LOGS = false +local ____print = ENABLE_VERBOSE_GAME_STATS_LOGS and rawPrintFn or (function(____, ...) return nil end) +--- Секунд подряд «никого в игре» до срабатывания «все вышли» (антидребезг). +local ALL_LEFT_DEBOUNCE_SECONDS = 8 +--- Игровое время после DISCONNECTED до поражения. +local DISCONNECT_TIMEOUT_SECONDS = 90 +--- Игровое время после ABANDONED/FAILED до поражения. +local ABANDON_DISCONNECT_TIMEOUT_SECONDS = 15 +--- Интервал успешного heartbeat во время матча. +local HEARTBEAT_INTERVAL_SECONDS = 60 +--- Повторная отправка, если сервер не принял сигнал. +local HEARTBEAT_RETRY_SECONDS = 5 +local ALLOW_STATS_WITH_CHEATS = false +local ALLOW_STATS_IN_TOOLS_MODE = false +--- Релиз: при читах/sv_cheats — не начислять BP/осколки за матч (см. shouldBlockMatchEndRewards). +local ALLOW_MATCH_END_REWARDS_WITH_CHEATS = false +--- Релиз: Workshop Tools — без наград конца матча (локальная отладка без выдачи на аккаунт). +local ALLOW_MATCH_END_REWARDS_IN_TOOLS = false +____exports.GameStatsTracker = __TS__Class() +local GameStatsTracker = ____exports.GameStatsTracker +GameStatsTracker.name = "GameStatsTracker" +GameStatsTracker.____file_path = "scripts/vscripts/game_stats_tracker.lua" +function GameStatsTracker.prototype.____constructor(self) + self.gameStartTime = 0 + self.isGameStarted = false + self.isGameEnded = false + self.playerGameData = __TS__New(Map) + self.lastKnownPlayers = __TS__New(Set) + self.playerDisconnectTime = __TS__New(Map) + self.pendingPlayers = __TS__New(Set) + self.registerStartInFlight = __TS__New(Set) + self.queuedPlayersForMatchBootstrap = __TS__New(Set) + self.isStatsDisabledForCurrentMatch = false + self.statsDisabledReason = "" + self.sessionParticipantsOrdered = {} + self.sessionParticipantIdSet = __TS__New(Set) + self.sessionParticipantsSteamOrdered = {} + self:setupListeners() +end +function GameStatsTracker.getInstance(self) + if not ____exports.GameStatsTracker.instance then + ____exports.GameStatsTracker.instance = __TS__New(____exports.GameStatsTracker) + end + return ____exports.GameStatsTracker.instance +end +function GameStatsTracker.prototype.isPlayerEffectivelyInGame(self, playerId) + if not isRealLobbyPlayer(nil, playerId) then + return false + end + local hero = PlayerResource:GetSelectedHeroEntity(playerId) + if not hero then + return false + end + local cs = PlayerResource:GetConnectionState(playerId) + return isConnectionStateEffectivelyInGame(nil, cs) +end +function GameStatsTracker.prototype.isPlayerActivelyConnected(self, playerId) + if not isRealLobbyPlayer(nil, playerId) then + return false + end + local hero = PlayerResource:GetSelectedHeroEntity(playerId) + if not hero then + return false + end + local cs = PlayerResource:GetConnectionState(playerId) + return isConnectionStateActivelyConnected(nil, cs) +end +function GameStatsTracker.prototype.collectEffectivePlayersInGame(self) + local set = __TS__New(Set) + do + local i = 0 + while i < DOTA_MAX_PLAYERS do + do + local playerId = i + if not PlayerResource:IsValidPlayerID(playerId) then + goto __continue14 + end + if self:isPlayerEffectivelyInGame(playerId) then + set:add(playerId) + end + end + ::__continue14:: + i = i + 1 + end + end + return set +end +function GameStatsTracker.prototype.collectActivelyConnectedPlayers(self) + local set = __TS__New(Set) + do + local i = 0 + while i < DOTA_MAX_PLAYERS do + do + local playerId = i + if not PlayerResource:IsValidPlayerID(playerId) then + goto __continue18 + end + if self:isPlayerActivelyConnected(playerId) then + set:add(playerId) + end + end + ::__continue18:: + i = i + 1 + end + end + return set +end +function GameStatsTracker.prototype.getDisconnectTimeoutForPlayer(self, playerId) + if not PlayerResource:IsValidPlayer(playerId) then + return ABANDON_DISCONNECT_TIMEOUT_SECONDS + end + local cs = PlayerResource:GetConnectionState(playerId) + if cs == DOTA_CONNECTION_STATE.ABANDONED or cs == DOTA_CONNECTION_STATE.FAILED then + return ABANDON_DISCONNECT_TIMEOUT_SECONDS + end + return DISCONNECT_TIMEOUT_SECONDS +end +function GameStatsTracker.prototype.setupListeners(self) + ListenToGameEvent( + "game_rules_state_change", + function() return self:onGameStateChange() end, + self + ) + ListenToGameEvent( + "npc_spawned", + function(____, event) return self:onNpcSpawned(event) end, + self + ) +end +function GameStatsTracker.prototype.onGameStateChange(self) + local gameState = GameRules:State_Get() + if gameState == 5 and not self.isGameStarted then + self:onGameStart() + end +end +function GameStatsTracker.prototype.onNpcSpawned(self, event) + local unit = EntIndexToHScript(event.entindex) + if not unit or not unit:IsRealHero() then + return + end + local playerId = unit:GetPlayerOwnerID() + if playerId == -1 or not PlayerResource:IsValidPlayer(playerId) or PlayerResource:IsFakeClient(playerId) then + return + end + if self.playerGameData:has(playerId) then + return + end + if self.registerStartInFlight:has(playerId) then + return + end + local hero = unit + local playerName = PlayerResource:GetPlayerName(playerId) + local gameState = GameRules:State_Get() + ____print( + nil, + (((((("[GameStatsTracker] 🔍 npc_spawned: герой " .. hero:GetUnitName()) .. ", игрок ") .. playerName) .. ", gameState: ") .. tostring(gameState)) .. ", isGameStarted: ") .. tostring(self.isGameStarted) + ) + if self.isGameEnded then + ____print(nil, "[GameStatsTracker] ⚠️ npc_spawned: игра уже завершена, пропускаем") + return + end + local eligibleNow = collectStatsEligiblePlayerIds(nil) + local inEligibleRoster = false + for ____, id in ipairs(eligibleNow) do + if id == playerId then + inEligibleRoster = true + break + end + end + if not inEligibleRoster then + ____print( + nil, + ("[GameStatsTracker] ⏭️ npc_spawned: слот " .. tostring(playerId)) .. " не в составе лобби для статистики, пропуск" + ) + return + end + if self.isGameStarted then + if not self.sessionParticipantIdSet:has(playerId) then + ____print(nil, ("[GameStatsTracker] ⏭️ npc_spawned: игрок " .. playerName) .. " вне снимка сессии, пропуск") + return + end + ____print( + nil, + ((((("[GameStatsTracker] 🦸 Герой появился! Регистрируем игрока " .. playerName) .. " (герой: ") .. hero:GetUnitName()) .. ", gameState: ") .. tostring(gameState)) .. ")" + ) + self:registerPlayerGame(playerId, hero) + return + end + ____print( + nil, + ("[GameStatsTracker] ⏳ npc_spawned: игра еще не началась (state: " .. tostring(gameState)) .. "), сохраняем для отложенной регистрации" + ) + self.pendingPlayers:add(playerId) +end +function GameStatsTracker.prototype.onGameStart(self) + Difficulty:ensureHeroSelectionResolvedForMatchStart() + self.isGameStarted = true + self.isGameEnded = false + self.gameStartTime = GameRules:GetGameTime() + self.playerGameData:clear() + self.lastKnownPlayers:clear() + self.playerDisconnectTime:clear() + self.registerStartInFlight:clear() + self.sharedMatchId = nil + local gameTimeMs = math.floor(GameRules:GetGameTime() * 1000) + local rHigh = RandomInt(1, 2147483647) + local rLow = RandomInt(1, 2147483647) + self.currentSessionId = (((("session_" .. tostring(gameTimeMs)) .. "_") .. tostring(rHigh)) .. "_") .. tostring(rLow) + self.sessionParticipantsOrdered = collectStatsEligiblePlayerIds(nil) + self.sessionParticipantIdSet = __TS__New(Set, self.sessionParticipantsOrdered) + self.sessionParticipantsSteamOrdered = __TS__ArrayMap( + self.sessionParticipantsOrdered, + function(____, id) return tostring(PlayerResource:GetSteamAccountID(id)) end + ) + __TS__ArraySort( + self.sessionParticipantsSteamOrdered, + function(____, a, b) return (tonumber(a) or 0) - (tonumber(b) or 0) end + ) + rawPrintFn( + nil, + (("[GameStatsTracker] Участники сессии (" .. tostring(#self.sessionParticipantsOrdered)) .. "): ") .. table.concat(self.sessionParticipantsSteamOrdered, ",") + ) + self.matchBootstrapPlayerId = nil + self.queuedPlayersForMatchBootstrap:clear() + self.allLeftDebounceSinceGameTime = nil + self.isStatsDisabledForCurrentMatch = self:shouldDisableStatsForCurrentMatch() + self.statsDisabledReason = self.isStatsDisabledForCurrentMatch and self:getStatsDisabledReason() or "" + rawPrintFn( + nil, + ((("[GameStatsTracker] Старт матча: session=" .. self.currentSessionId) .. ", gameTime=") .. tostring(math.floor(self.gameStartTime))) .. "s" + ) + if self.isStatsDisabledForCurrentMatch then + ____print(nil, "[GameStatsTracker] 🚫 Трекинг матча отключен: " .. self.statsDisabledReason) + end + if self.pendingPlayers.size > 0 then + ____print( + nil, + ("[GameStatsTracker] 📋 Регистрируем " .. tostring(self.pendingPlayers.size)) .. " игроков, которые появились до начала игры" + ) + for ____, playerId in __TS__Iterator(self.pendingPlayers) do + do + if not self.sessionParticipantIdSet:has(playerId) then + goto __continue46 + end + local hero = PlayerResource:GetSelectedHeroEntity(playerId) + if hero and hero:IsRealHero() then + self:registerPlayerGame(playerId, hero) + end + end + ::__continue46:: + end + self.pendingPlayers:clear() + end + self:startGameForAllPlayers() + self:startDisconnectCheck() + self:startHeartbeat() +end +function GameStatsTracker.prototype.shouldDisableStatsForCurrentMatch(self) + if not ALLOW_STATS_IN_TOOLS_MODE and self:isToolsModeActive() then + return true + end + if not ALLOW_STATS_WITH_CHEATS and self:isCheatsEnabled() then + return true + end + return false +end +function GameStatsTracker.prototype.getStatsDisabledReason(self) + local reasons = {} + if not ALLOW_STATS_IN_TOOLS_MODE and self:isToolsModeActive() then + reasons[#reasons + 1] = "tools_mode" + end + if not ALLOW_STATS_WITH_CHEATS and self:isCheatsEnabled() then + reasons[#reasons + 1] = "cheats_enabled" + end + return table.concat(reasons, "+") +end +function GameStatsTracker.prototype.isToolsModeActive(self) + do + local function ____catch() + return true, false + end + local ____try, ____hasReturned, ____returnValue = pcall(function() + local fn = _G.IsInToolsMode + local ____fn_0 + if fn then + ____fn_0 = fn(nil) + else + ____fn_0 = false + end + return true, ____fn_0 + end) + if not ____try then + ____hasReturned, ____returnValue = ____catch() + end + if ____hasReturned then + return ____returnValue + end + end +end +function GameStatsTracker.prototype.isCheatsEnabled(self) + do + local ____try, ____hasReturned, ____returnValue = pcall(function() + local ____this_2 + ____this_2 = GameRules + local ____opt_1 = ____this_2.IsCheatMode + if ____opt_1 ~= nil then + ____opt_1 = ____opt_1(____this_2) + end + if ____opt_1 then + return true, true + end + end) + if ____try and ____hasReturned then + return ____returnValue + end + end + do + local ____try, ____hasReturned, ____returnValue = pcall(function() + local cv = _G.Convars + if cv and type(cv.GetBool) == "function" and cv:GetBool("sv_cheats") then + return true, true + end + end) + if ____try and ____hasReturned then + return ____returnValue + end + end + return false +end +function GameStatsTracker.prototype.startDisconnectCheck(self) + if self.disconnectCheckInterval ~= nil then + Timers:RemoveTimer(self.disconnectCheckInterval) + end + self.disconnectCheckInterval = Timers:CreateTimer( + 2, + function() + if not self.isGameStarted or self.isGameEnded then + return nil + end + self:checkForDisconnectedPlayers() + return 2 + end + ) +end +function GameStatsTracker.prototype.checkForDisconnectedPlayers(self) + local currentTime = GameRules:GetGameTime() + local activelyConnected = self:collectActivelyConnectedPlayers() + for ____, playerId in __TS__Iterator(activelyConnected) do + if self.playerDisconnectTime:has(playerId) then + self.playerDisconnectTime:delete(playerId) + end + end + for ____, playerId in __TS__Iterator(self.lastKnownPlayers) do + do + if not self.playerGameData:has(playerId) then + goto __continue72 + end + if not PlayerResource:IsValidPlayer(playerId) then + if not self.playerDisconnectTime:has(playerId) then + ____print( + nil, + ("[GameStatsTracker] ⚠️ Игрок " .. tostring(playerId)) .. " больше не валиден (полностью вышел из игры)" + ) + self.playerDisconnectTime:set(playerId, currentTime) + else + local disconnectTime = self.playerDisconnectTime:get(playerId) + if currentTime - disconnectTime >= self:getDisconnectTimeoutForPlayer(playerId) then + self:handlePermanentDisconnect(playerId) + end + end + goto __continue72 + end + if activelyConnected:has(playerId) then + goto __continue72 + end + if not self.playerDisconnectTime:has(playerId) then + local playerName = PlayerResource:GetPlayerName(playerId) + local steamId = tostring(PlayerResource:GetSteamAccountID(playerId)) + local cs = PlayerResource:GetConnectionState(playerId) + ____print( + nil, + ((((("[GameStatsTracker] ⚠️ Игрок " .. playerName) .. " (SteamID: ") .. steamId) .. ") отключился (connectionState: ") .. tostring(cs)) .. ")" + ) + self.playerDisconnectTime:set(playerId, currentTime) + else + local disconnectTime = self.playerDisconnectTime:get(playerId) + if currentTime - disconnectTime >= self:getDisconnectTimeoutForPlayer(playerId) then + self:handlePermanentDisconnect(playerId) + end + end + end + ::__continue72:: + end + for ____, playerId in __TS__Iterator(self:collectEffectivePlayersInGame()) do + self.lastKnownPlayers:add(playerId) + end + for ____, playerId in __TS__Iterator(self.playerGameData:keys()) do + self.lastKnownPlayers:add(playerId) + end + if self.isGameStarted and not self.isGameEnded and self.playerGameData.size > 0 then + if activelyConnected.size == 0 then + if self.allLeftDebounceSinceGameTime == nil then + self.allLeftDebounceSinceGameTime = currentTime + elseif currentTime - self.allLeftDebounceSinceGameTime >= ALL_LEFT_DEBOUNCE_SECONDS then + ____print( + nil, + ("[GameStatsTracker] ⚠️ Все игроки покинули игру (debounce " .. tostring(ALL_LEFT_DEBOUNCE_SECONDS)) .. "s). Сохраняем результаты и завершаем игру." + ) + local playersToHandle = {} + for ____, playerId in __TS__Iterator(self.playerGameData:keys()) do + playersToHandle[#playersToHandle + 1] = playerId + end + for ____, playerId in ipairs(playersToHandle) do + self:handlePermanentDisconnect(playerId) + end + self.isGameEnded = true + self.allLeftDebounceSinceGameTime = nil + self:stopDisconnectCheck() + self:stopHeartbeat() + end + else + self.allLeftDebounceSinceGameTime = nil + end + end +end +function GameStatsTracker.prototype.handlePermanentDisconnect(self, playerId) + local playerName = PlayerResource:GetPlayerName(playerId) + local steamId = tostring(PlayerResource:GetSteamAccountID(playerId)) + local disconnectDuration = GameRules:GetGameTime() - (self.playerDisconnectTime:get(playerId) or self.gameStartTime) + ____print(nil, ((("[GameStatsTracker] ⚠️ Игрок " .. playerName) .. " (SteamID: ") .. steamId) .. ") окончательно отключился") + ____print( + nil, + ("[GameStatsTracker] Время отключения: " .. tostring(math.floor(disconnectDuration))) .. " секунд назад" + ) + self.playerDisconnectTime:delete(playerId) + self.lastKnownPlayers:delete(playerId) + self:onPlayerDisconnect(playerId) +end +function GameStatsTracker.prototype.startGameForAllPlayers(self) + local gameState = GameRules:State_Get() + if gameState ~= 5 and not self.isGameStarted then + ____print( + nil, + ("[GameStatsTracker] ⚠️ Игра не в состоянии IN_PROGRESS (текущее состояние: " .. tostring(gameState)) .. ") и еще не началась, пропускаем регистрацию" + ) + return + end + if gameState ~= 5 and self.isGameStarted then + ____print( + nil, + ("[GameStatsTracker] ⚠️ Игра началась, но состояние не IN_PROGRESS (" .. tostring(gameState)) .. "), продолжаем регистрацию" + ) + end + local currentDifficulty = Difficulty.leader or "normal" + ____print(nil, ("[GameStatsTracker] 📋 Начинаем регистрацию игры для всех игроков (сложность: " .. currentDifficulty) .. ")") + ____print( + nil, + ("[GameStatsTracker] 📊 Снимок лобби: " .. tostring(#self.sessionParticipantsOrdered)) .. " слотов (только реальные игроки)" + ) + local playersCount = 0 + local skippedNoHero = 0 + for ____, playerId in ipairs(self.sessionParticipantsOrdered) do + do + ____print( + nil, + ("[GameStatsTracker] 🔍 Слот " .. tostring(playerId)) .. " (из снимка сессии)..." + ) + local hero = PlayerResource:GetSelectedHeroEntity(playerId) + if not hero then + ____print( + nil, + ("[GameStatsTracker] ⚠️ У игрока " .. tostring(playerId)) .. " ещё нет героя" + ) + skippedNoHero = skippedNoHero + 1 + goto __continue100 + end + if self:registerPlayerGame(playerId, hero) then + playersCount = playersCount + 1 + end + end + ::__continue100:: + end + ____print(nil, "[GameStatsTracker] 📊 Регистрация завершена:") + ____print( + nil, + "[GameStatsTracker] ✅ Запросов на регистрацию отправлено: " .. tostring(playersCount) + ) + ____print( + nil, + "[GameStatsTracker] ⚠️ Пропущено (герой не готов): " .. tostring(skippedNoHero) + ) + if playersCount == 0 then + ____print(nil, "[GameStatsTracker] ⚠️ ВНИМАНИЕ: Не найдено ни одного игрока для регистрации!") + ____print(nil, "[GameStatsTracker] Возможно, игра началась до того, как игроки выбрали героев.") + ____print(nil, "[GameStatsTracker] Будем регистрировать игроков при появлении их героев.") + end +end +function GameStatsTracker.prototype.registerPlayerGame(self, playerId, hero) + if self.isStatsDisabledForCurrentMatch then + return false + end + if not self.sessionParticipantIdSet:has(playerId) then + ____print( + nil, + ("[GameStatsTracker] ⏭️ registerPlayerGame: слот " .. tostring(playerId)) .. " не в снимке сессии" + ) + return false + end + if self.playerGameData:has(playerId) then + return false + end + if self.sharedMatchId == nil and self.matchBootstrapPlayerId ~= nil and self.matchBootstrapPlayerId ~= playerId then + self.queuedPlayersForMatchBootstrap:add(playerId) + local queuedName = PlayerResource:GetPlayerName(playerId) + ____print(nil, "[GameStatsTracker] ⏳ Ожидаем общий match_id, откладываем регистрацию игрока " .. queuedName) + return false + end + if self.registerStartInFlight:has(playerId) then + return false + end + if self.sharedMatchId == nil and self.matchBootstrapPlayerId == nil then + self.matchBootstrapPlayerId = playerId + local bootstrapName = PlayerResource:GetPlayerName(playerId) + ____print(nil, "[GameStatsTracker] 🚀 Bootstrap match_id через игрока " .. bootstrapName) + end + self.registerStartInFlight:add(playerId) + self.lastKnownPlayers:add(playerId) + local steamId = tostring(PlayerResource:GetSteamAccountID(playerId)) + local playerName = PlayerResource:GetPlayerName(playerId) + local heroLevel = hero:GetLevel() + local currentDifficulty = Difficulty.leader or "normal" + ____print( + nil, + (((((("[GameStatsTracker] 👤 Регистрируем игрока " .. playerName) .. " (SteamID: ") .. steamId) .. "), герой: ") .. hero:GetUnitName()) .. ", уровень: ") .. tostring(heroLevel) + ) + local request = CreateHTTPRequest("POST", SERVER_CONFIG.API_URL .. "/game/start") + setApiHeadersLong(nil, request) + local dataToSend = { + steam_id = steamId, + hero = hero:GetUnitName(), + hero_level = heroLevel, + difficulty = currentDifficulty, + player_name = playerName, + match_id = self.sharedMatchId, + session_id = self.currentSessionId, + session_participants = self.sessionParticipantsSteamOrdered + } + local dsContractStart = Difficulty:getActiveDeathSentenceContractPayload() + if dsContractStart then + dataToSend.death_sentence_contract = dsContractStart + end + request:SetHTTPRequestRawPostBody( + "application/json", + encodeApiBody(nil, dataToSend) + ) + request:Send(function(result) + self.registerStartInFlight:delete(playerId) + if result.StatusCode >= 200 and result.StatusCode < 300 then + do + local function ____catch(e) + ____print(nil, ("[GameStatsTracker] ❌ Ошибка парсинга ответа для игрока " .. playerName) .. ":", e) + if not self.playerGameData:has(playerId) then + self.lastKnownPlayers:delete(playerId) + end + self:onRegisterStartFinished(playerId) + end + local ____try, ____hasReturned = pcall(function() + local responseData = {json.decode(result.Body)} + local data = nil + if __TS__ArrayIsArray(responseData) and #responseData > 0 then + data = responseData[1] + elseif responseData.value ~= nil then + data = responseData.value + else + data = responseData + end + if data and data.game_id and data.match_id then + if self.sharedMatchId == nil then + self.sharedMatchId = __TS__Number(data.match_id) or nil + if self.sharedMatchId ~= nil then + ____print( + nil, + "[GameStatsTracker] 🔗 Зафиксирован общий Match ID: " .. tostring(self.sharedMatchId) + ) + end + end + self.playerGameData:set(playerId, {game_id = data.game_id, match_id = data.match_id}) + self.lastKnownPlayers:add(playerId) + ____print(nil, "[GameStatsTracker] ✅ Игра зарегистрирована для игрока " .. playerName) + ____print( + nil, + (("[GameStatsTracker] Game ID: " .. tostring(data.game_id)) .. ", Match ID: ") .. tostring(data.match_id) + ) + else + ____print(nil, ("[GameStatsTracker] ⚠️ Неполный ответ от сервера для игрока " .. playerName) .. ":", data) + if not self.playerGameData:has(playerId) then + self.lastKnownPlayers:delete(playerId) + end + end + self:onRegisterStartFinished(playerId) + end) + if not ____try then + ____catch(____hasReturned) + end + end + else + ____print( + nil, + (("[GameStatsTracker] ❌ Ошибка при регистрации игры для игрока " .. playerName) .. ": StatusCode ") .. tostring(result.StatusCode) + ) + if not self.playerGameData:has(playerId) then + self.lastKnownPlayers:delete(playerId) + end + self:onRegisterStartFinished(playerId) + end + end) + return true +end +function GameStatsTracker.prototype.onRegisterStartFinished(self, playerId) + if self.matchBootstrapPlayerId ~= playerId then + return + end + self.matchBootstrapPlayerId = nil + if self.queuedPlayersForMatchBootstrap.size == 0 then + return + end + local queued = __TS__ArrayFrom(self.queuedPlayersForMatchBootstrap) + self.queuedPlayersForMatchBootstrap:clear() + ____print( + nil, + "[GameStatsTracker] ▶️ Продолжаем регистрацию отложенных игроков: " .. tostring(#queued) + ) + for ____, queuedPlayerId in ipairs(queued) do + do + local hero = PlayerResource:GetSelectedHeroEntity(queuedPlayerId) + if not hero or not hero:IsRealHero() then + goto __continue131 + end + self:registerPlayerGame(queuedPlayerId, hero) + end + ::__continue131:: + end +end +function GameStatsTracker.prototype.onVictory(self, callback) + if self.isGameEnded then + return + end + if not self.isGameStarted then + ____print(nil, "[GameStatsTracker] onVictory: матч не был отмечен как начатый — колбэк для UI, без сохранения API") + self.isGameEnded = true + if callback then + callback(nil) + end + return + end + local duration = GameRules:GetGameTime() - self.gameStartTime + rawPrintFn( + nil, + ("[GameStatsTracker] Завершение матча: result=win, duration=" .. tostring(math.floor(duration))) .. "s" + ) + self:stopDisconnectCheck() + self:stopHeartbeat() + if self.isStatsDisabledForCurrentMatch then + ____print(nil, ("[GameStatsTracker] 🚫 Победа не сохраняется в статистику (" .. self.statsDisabledReason) .. ")") + self.isGameEnded = true + if callback then + callback(nil) + end + return + end + self:saveAllPlayersStats(true, callback) + self.isGameEnded = true +end +function GameStatsTracker.prototype.onDefeat(self, callback) + if self.isGameEnded then + return + end + if not self.isGameStarted then + ____print(nil, "[GameStatsTracker] onDefeat: матч не был отмечен как начатый — колбэк для UI, без сохранения API") + self.isGameEnded = true + if callback then + callback(nil) + end + return + end + local duration = GameRules:GetGameTime() - self.gameStartTime + rawPrintFn( + nil, + ("[GameStatsTracker] Завершение матча: result=loss, duration=" .. tostring(math.floor(duration))) .. "s" + ) + self:stopDisconnectCheck() + self:stopHeartbeat() + if self.isStatsDisabledForCurrentMatch then + ____print(nil, ("[GameStatsTracker] 🚫 Поражение не сохраняется в статистику (" .. self.statsDisabledReason) .. ")") + self.isGameEnded = true + if callback then + callback(nil) + end + return + end + self:saveAllPlayersStats(false, callback) + self.isGameEnded = true +end +function GameStatsTracker.prototype.isCurrentMatchStatsDisabled(self) + return self.isStatsDisabledForCurrentMatch +end +function GameStatsTracker.prototype.shouldBlockMatchEndRewards(self) + if not ALLOW_STATS_IN_TOOLS_MODE and self:isToolsModeActive() and not ALLOW_MATCH_END_REWARDS_IN_TOOLS then + return true + end + if not ALLOW_STATS_WITH_CHEATS and self:isCheatsEnabled() and not ALLOW_MATCH_END_REWARDS_WITH_CHEATS then + return true + end + return false +end +function GameStatsTracker.prototype.startHeartbeat(self) + if self.heartbeatInterval ~= nil then + Timers:RemoveTimer(self.heartbeatInterval) + end + self.heartbeatInterval = Timers:CreateTimer( + HEARTBEAT_INTERVAL_SECONDS, + function() + if not self.isGameStarted or self.isGameEnded then + return nil + end + self:sendHeartbeat() + return HEARTBEAT_INTERVAL_SECONDS + end + ) + self:sendHeartbeat() +end +function GameStatsTracker.prototype.stopHeartbeat(self) + if self.heartbeatInterval ~= nil then + Timers:RemoveTimer(self.heartbeatInterval) + self.heartbeatInterval = nil + end + if self.heartbeatRetryTimer ~= nil then + Timers:RemoveTimer(self.heartbeatRetryTimer) + self.heartbeatRetryTimer = nil + end +end +function GameStatsTracker.prototype.scheduleHeartbeatRetry(self) + if not self.isGameStarted or self.isGameEnded then + return + end + if self.heartbeatRetryTimer ~= nil then + return + end + self.heartbeatRetryTimer = Timers:CreateTimer( + HEARTBEAT_RETRY_SECONDS, + function() + self.heartbeatRetryTimer = nil + if not self.isGameStarted or self.isGameEnded then + return nil + end + self:sendHeartbeat() + return nil + end + ) +end +function GameStatsTracker.prototype.sendHeartbeat(self) + if not self.isGameStarted or self.isGameEnded then + return + end + if self.isStatsDisabledForCurrentMatch then + return + end + do + local i = 0 + while i < DOTA_MAX_PLAYERS do + do + local playerId = i + if not PlayerResource:IsValidPlayerID(playerId) then + goto __continue165 + end + if not self:isPlayerActivelyConnected(playerId) then + goto __continue165 + end + local gameData = self.playerGameData:get(playerId) + if not gameData or not gameData.game_id then + goto __continue165 + end + local steamId = tostring(PlayerResource:GetSteamAccountID(playerId)) + local playerName = PlayerResource:GetPlayerName(playerId) + local requestData = {steam_id = steamId, game_id = gameData.game_id} + local request = CreateHTTPRequestScriptVM("POST", SERVER_CONFIG.API_URL .. "/game/heartbeat") + setApiHeadersLong(nil, request) + request:SetHTTPRequestRawPostBody( + "application/json", + encodeApiBody(nil, requestData) + ) + request:Send(function(result) + local accepted = false + if result.StatusCode >= 200 and result.StatusCode < 300 then + do + local function ____catch(e) + ____print(nil, ("[GameStatsTracker] ⚠️ Ошибка парсинга ответа heartbeat для игрока " .. playerName) .. ":", e) + end + local ____try, ____hasReturned = pcall(function() + local responseData = {json.decode(result.Body)} + local data = nil + if __TS__ArrayIsArray(responseData) and #responseData > 0 then + data = responseData[1] + elseif responseData.value ~= nil then + data = responseData.value + elseif responseData and type(responseData) == "table" then + data = responseData + end + if data and data.success then + accepted = true + ____print( + nil, + ((("[GameStatsTracker] 💓 Heartbeat отправлен для игрока " .. playerName) .. " (Game ID: ") .. tostring(gameData.game_id)) .. ")" + ) + end + end) + if not ____try then + ____catch(____hasReturned) + end + end + else + ____print( + nil, + (("[GameStatsTracker] ⚠️ Ошибка отправки heartbeat для игрока " .. playerName) .. ": StatusCode ") .. tostring(result.StatusCode) + ) + end + if not accepted then + self:scheduleHeartbeatRetry() + end + end) + end + ::__continue165:: + i = i + 1 + end + end +end +function GameStatsTracker.prototype.stopDisconnectCheck(self) + if self.disconnectCheckInterval ~= nil then + Timers:RemoveTimer(self.disconnectCheckInterval) + self.disconnectCheckInterval = nil + end + self.playerDisconnectTime:clear() +end +function GameStatsTracker.prototype.onPlayerDisconnect(self, playerId) + if not self.isGameStarted or self.isGameEnded then + return + end + if self.isStatsDisabledForCurrentMatch then + self.playerGameData:delete(playerId) + return + end + local playerName = PlayerResource:GetPlayerName(playerId) + local steamId = tostring(PlayerResource:GetSteamAccountID(playerId)) + local gameData = self.playerGameData:get(playerId) + if not gameData or not gameData.game_id then + ____print(nil, ("[GameStatsTracker] ⚠️ Игрок " .. playerName) .. " отключился до начала игры, данные не сохраняются") + self.playerGameData:delete(playerId) + return + end + ____print( + nil, + ((("[GameStatsTracker] 💾 Сохраняем статистику отключившегося игрока " .. playerName) .. " (Game ID: ") .. tostring(gameData.game_id)) .. ")" + ) + local hero = PlayerResource:GetSelectedHeroEntity(playerId) + local heroName = hero and hero:GetUnitName() or "unknown" + local heroLevel = hero and hero:GetLevel() or 1 + local ____hero_3 + if hero then + ____hero_3 = hero:HasScepter() + else + ____hero_3 = false + end + local hasAghs = ____hero_3 + local ____hero_4 + if hero then + ____hero_4 = HasShard(nil, hero) + else + ____hero_4 = false + end + local hasShard = ____hero_4 + local duration = GameRules:GetGameTime() - self.gameStartTime + local kills = hero and PlayerResource:GetNearbyCreepDeaths(playerId) or 0 + local deaths = hero and PlayerResource:GetDeaths(playerId) or 0 + local netWorth = hero and PlayerResource:GetNetWorth(playerId) or 0 + local combat = MatchEndCombatStats:getInstance() + local outgoingDamage = combat:getOutgoingDamageSum(playerId) + local incomingDamage = combat:getIncomingDamageSum(playerId) + local items = {} + local permanentModifiers = {} + if hero then + do + local i = 0 + while i < 9 do + local item = hero:GetItemInSlot(i) + if item and item:GetAbilityName() then + items[#items + 1] = item:GetAbilityName() + end + i = i + 1 + end + end + local modifierCounts = {} + do + local i = 0 + while i < hero:GetModifierCount() do + local modifierName = hero:GetModifierNameByIndex(i) + if modifierName ~= nil then + local buff = hero:FindModifierByName(modifierName) + if buff then + local buffDuration = buff:GetDuration() + local remainingTime = buff:GetRemainingTime() + if buffDuration == -1 or buffDuration > 0 and remainingTime > 99999 then + modifierCounts[modifierName] = (modifierCounts[modifierName] or 0) + 1 + end + end + end + i = i + 1 + end + end + for modName in pairs(modifierCounts) do + permanentModifiers[#permanentModifiers + 1] = (modName .. ":") .. tostring(modifierCounts[modName]) + end + end + local currentDifficulty = Difficulty.leader or "normal" + local function finishDisconnectSave() + self:saveGameResult( + steamId, + false, + duration, + kills, + deaths, + netWorth, + outgoingDamage, + incomingDamage, + heroName, + heroLevel, + items, + permanentModifiers, + hasAghs, + hasShard, + currentDifficulty, + gameData.game_id, + function() + ____print(nil, ("[GameStatsTracker] ✅ Статистика отключившегося игрока " .. playerName) .. " сохранена") + self.playerGameData:delete(playerId) + end + ) + end + if currentDifficulty == "death_sentence" then + Difficulty:applyDeathSentenceContractDurabilityOnLoss(finishDisconnectSave) + else + finishDisconnectSave(nil) + end +end +function GameStatsTracker.prototype.saveAllPlayersStats(self, isVictory, callback) + if self.isStatsDisabledForCurrentMatch then + ____print(nil, ("[GameStatsTracker] 🚫 saveAllPlayersStats пропущен (" .. self.statsDisabledReason) .. ")") + if callback then + callback(nil) + end + return + end + local duration = GameRules:GetGameTime() - self.gameStartTime + local savedCount = 0 + local resultText = isVictory and "ПОБЕДА" or "ПОРАЖЕНИЕ" + ____print(nil, ("[GameStatsTracker] 💾 Сохраняем статистику для всех игроков (" .. resultText) .. ")") + local idsToSave = {} + do + local i = 0 + while i < DOTA_MAX_PLAYERS do + do + local playerId = i + if not PlayerResource:IsValidPlayerID(playerId) then + goto __continue201 + end + if not PlayerResource:IsValidPlayer(playerId) or PlayerResource:IsFakeClient(playerId) then + goto __continue201 + end + if not self.playerGameData:has(playerId) then + goto __continue201 + end + local hero = PlayerResource:GetSelectedHeroEntity(playerId) + if hero then + idsToSave[#idsToSave + 1] = playerId + end + end + ::__continue201:: + i = i + 1 + end + end + local totalPlayers = #idsToSave + if totalPlayers == 0 then + ____print(nil, "[GameStatsTracker] ⚠️ Нет активных игроков для сохранения статистики") + if callback then + callback(nil) + end + return + end + ____print( + nil, + ("[GameStatsTracker] 📊 Сохраняем статистику для " .. tostring(totalPlayers)) .. " игроков" + ) + local combat = MatchEndCombatStats:getInstance() + for ____, playerId in ipairs(idsToSave) do + do + local hero = PlayerResource:GetSelectedHeroEntity(playerId) + if not hero then + savedCount = savedCount + 1 + ____print( + nil, + ("[GameStatsTracker] ⚠️ Герой пропал для слота " .. tostring(playerId)) .. " перед save — учитываем чанк" + ) + if savedCount >= totalPlayers and callback then + ____print( + nil, + ((("[GameStatsTracker] 🎉 Все чанки учтены (" .. tostring(savedCount)) .. "/") .. tostring(totalPlayers)) .. ")" + ) + callback(nil) + end + goto __continue208 + end + local steamId = tostring(PlayerResource:GetSteamAccountID(playerId)) + local heroName = hero:GetUnitName() + local gameData = self.playerGameData:get(playerId) + local gameId = gameData and gameData.game_id + local kills = PlayerResource:GetNearbyCreepDeaths(playerId) + local deaths = PlayerResource:GetDeaths(playerId) + local netWorth = PlayerResource:GetNetWorth(playerId) + local outgoingDamage = combat:getOutgoingDamageSum(playerId) + local incomingDamage = combat:getIncomingDamageSum(playerId) + local heroLevel = hero:GetLevel() + local items = {} + local permanentModifiers = {} + local hasAghs = hero:HasScepter() + local hasShard = HasShard(nil, hero) + do + local slot = 0 + while slot < 9 do + local item = hero:GetItemInSlot(slot) + if item and item:GetAbilityName() then + items[#items + 1] = item:GetAbilityName() + end + slot = slot + 1 + end + end + local modifierCounts = {} + do + local j = 0 + while j < hero:GetModifierCount() do + local modifierName = hero:GetModifierNameByIndex(j) + if modifierName ~= nil then + local buff = hero:FindModifierByName(modifierName) + if buff then + local buffDuration = buff:GetDuration() + local remainingTime = buff:GetRemainingTime() + if buffDuration == -1 or buffDuration > 0 and remainingTime > 99999 then + modifierCounts[modifierName] = (modifierCounts[modifierName] or 0) + 1 + end + end + end + j = j + 1 + end + end + for modName in pairs(modifierCounts) do + permanentModifiers[#permanentModifiers + 1] = (modName .. ":") .. tostring(modifierCounts[modName]) + end + local currentDifficulty = Difficulty.leader or "normal" + self:saveGameResult( + steamId, + isVictory, + duration, + kills, + deaths, + netWorth, + outgoingDamage, + incomingDamage, + hero:GetUnitName(), + heroLevel, + items, + permanentModifiers, + hasAghs, + hasShard, + currentDifficulty, + gameId, + function() + savedCount = savedCount + 1 + ____print( + nil, + ((((("[GameStatsTracker] ✅ Статистика сохранена для игрока " .. heroName) .. " (") .. tostring(savedCount)) .. "/") .. tostring(totalPlayers)) .. ")" + ) + if savedCount >= totalPlayers and callback then + ____print( + nil, + ("[GameStatsTracker] 🎉 Все статистики сохранены! (" .. tostring(savedCount)) .. " игроков)" + ) + callback(nil) + end + end + ) + end + ::__continue208:: + end +end +function GameStatsTracker.prototype.saveGameResult(self, steamId, isVictory, duration, kills, deaths, score, outgoingDamage, incomingDamage, hero, heroLevel, items, modifiers, hasAghs, hasShard, difficulty, gameId, callback) + local dataToSend = { + steam_id = steamId, + result = isVictory and "win" or "loss", + duration = math.floor(duration), + kills = kills, + deaths = deaths, + score = score, + outgoing_damage = math.floor(outgoingDamage), + incoming_damage = math.floor(incomingDamage), + hero = hero, + hero_level = heroLevel, + items = table.concat(items, ","), + modifiers = table.concat(modifiers, ","), + aghanim_scepter = hasAghs, + aghanim_shard = hasShard, + gold_earned = score, + difficulty = difficulty, + session_id = self.currentSessionId + } + if gameId ~= nil then + dataToSend.game_id = gameId + end + local dsContractEnd = Difficulty:getActiveDeathSentenceContractPayload() + if dsContractEnd then + dataToSend.death_sentence_contract = dsContractEnd + end + local request = CreateHTTPRequest("POST", SERVER_CONFIG.API_URL .. "/game") + setApiHeadersLong(nil, request) + request:SetHTTPRequestRawPostBody( + "application/json", + encodeApiBody(nil, dataToSend) + ) + request:Send(function(result) + if result.StatusCode < 200 or result.StatusCode >= 300 then + local body = result.Body ~= nil and tostring(result.Body) or "" + rawPrintFn( + nil, + (((("[GameStatsTracker] saveGameResult HTTP " .. tostring(result.StatusCode)) .. " steam_id=") .. steamId) .. " body=") .. body + ) + end + if callback then + callback(nil) + end + end) +end +return ____exports diff --git a/scripts/vscripts/gamemode.lua b/scripts/vscripts/gamemode.lua new file mode 100644 index 0000000..268906a --- /dev/null +++ b/scripts/vscripts/gamemode.lua @@ -0,0 +1,1833 @@ +local ____lualib = require("lualib_bundle") +local __TS__Class = ____lualib.__TS__Class +local __TS__New = ____lualib.__TS__New +local Map = ____lualib.Map +local Set = ____lualib.Set +local __TS__ObjectEntries = ____lualib.__TS__ObjectEntries +local __TS__ArrayForEach = ____lualib.__TS__ArrayForEach +local __TS__ObjectKeys = ____lualib.__TS__ObjectKeys +local __TS__ArrayIncludes = ____lualib.__TS__ArrayIncludes +local __TS__StringTrim = ____lualib.__TS__StringTrim +local __TS__StringStartsWith = ____lualib.__TS__StringStartsWith +local __TS__ArrayJoin = ____lualib.__TS__ArrayJoin +local __TS__NumberToFixed = ____lualib.__TS__NumberToFixed +local __TS__ArrayIsArray = ____lualib.__TS__ArrayIsArray +local __TS__Decorate = ____lualib.__TS__Decorate +local ____exports = {} +local ____tstl_2Dutils = require("lib.tstl-utils") +local reloadable = ____tstl_2Dutils.reloadable +local ____WaveManager = require("WaveManager") +local WaveManager = ____WaveManager.WaveManager +local ____DayNightCycleManager = require("DayNightCycleManager") +local DayNightCycleManager = ____DayNightCycleManager.DayNightCycleManager +local ____skip_night_vote_manager = require("skip_night_vote_manager") +local SkipNightVoteManager = ____skip_night_vote_manager.SkipNightVoteManager +local ____item_detector = require("item_detector") +local ItemDetector = ____item_detector.ItemDetector +local ____item_drops = require("item_drops") +local ItemDropSystem = ____item_drops.ItemDropSystem +local ____random_events = require("events.random_events") +local RandomEventsManager = ____random_events.RandomEventsManager +local ____SpawnManager = require("SpawnManager") +local SpawnManager = ____SpawnManager.SpawnManager +local ____blackshop = require("blackshop") +local BlackShop = ____blackshop.BlackShop +local registerBlackShopCustomGameEvents = ____blackshop.registerBlackShopCustomGameEvents +local ____order_filter = require("filters.order_filter") +local InitOrderFilter = ____order_filter.InitOrderFilter +local ____QuestInitializer = require("quests.QuestInitializer") +local InitializeQuests = ____QuestInitializer.InitializeQuests +local ____kunkka_shovel_anchor_markers = require("quests.kunkka_shovel_anchor_markers") +local initializeKunkkaShovelAnchorMarkers = ____kunkka_shovel_anchor_markers.initializeKunkkaShovelAnchorMarkers +local ____difficulty_manager = require("difficulty_manager") +local Difficulty = ____difficulty_manager.Difficulty +local ____contracts_registry = require("death_sentence.contracts_registry") +local applyDeathSentenceContractOnEnemySpawn = ____contracts_registry.applyDeathSentenceContractOnEnemySpawn +local ____crystal_currency = require("crystal_currency") +local CrystalCurrency = ____crystal_currency.CrystalCurrency +local ____mini_profile_server = require("mini_profile_server") +local MiniProfileServer = ____mini_profile_server.MiniProfileServer +local ____leaderboard_server = require("leaderboard_server") +local LeaderboardServer = ____leaderboard_server.LeaderboardServer +local ____player_profile_manager = require("player_profile_manager") +local PlayerProfileManager = ____player_profile_manager.PlayerProfileManager +local ____store_manager = require("store_manager") +local StoreManager = ____store_manager.StoreManager +local ____ArsenalManager = require("arsenal.ArsenalManager") +local ArsenalManager = ____ArsenalManager.ArsenalManager +local ____MarketplaceManager = require("arsenal.MarketplaceManager") +local MarketplaceManager = ____MarketplaceManager.MarketplaceManager +local ____hero_list_table = require("tables.hero_list_table") +local HERO_LIST_TABLE = ____hero_list_table.HERO_LIST_TABLE +local ____HeroCosmeticManager = require("HeroCosmeticManager") +local HeroCosmeticManager = ____HeroCosmeticManager.HeroCosmeticManager +local ____ability_alt_cast_manager = require("ability_alt_cast_manager") +local AbilityAltCastManager = ____ability_alt_cast_manager.AbilityAltCastManager +local ____admin_menu = require("admin_menu") +local AdminMenuManager = ____admin_menu.AdminMenuManager +local ____CookingSystem = require("cooking.CookingSystem") +local CookingSystem = ____CookingSystem.CookingSystem +local ____battle_pass_server = require("battle_pass_server") +local BattlePassServer = ____battle_pass_server.BattlePassServer +local ____game_stats_tracker = require("game_stats_tracker") +local GameStatsTracker = ____game_stats_tracker.GameStatsTracker +local ____match_end_rewards = require("match_end_rewards") +local MatchEndRewards = ____match_end_rewards.MatchEndRewards +local ____match_end_combat_stats = require("match_end_combat_stats") +local MatchEndCombatStats = ____match_end_combat_stats.MatchEndCombatStats +local ____player_connection_state = require("utils.player_connection_state") +local isConnectionStateEffectivelyInGame = ____player_connection_state.isConnectionStateEffectivelyInGame +local ____real_lobby_player = require("utils.real_lobby_player") +local countRealLobbyPlayers = ____real_lobby_player.countRealLobbyPlayers +local isRealLobbyPlayer = ____real_lobby_player.isRealLobbyPlayer +local ____game_setup_lobby_check = require("game_setup_lobby_check") +local resetGameSetupLobbyCheckPhase = ____game_setup_lobby_check.resetGameSetupLobbyCheckPhase +local sendGameSetupLobbyCheck = ____game_setup_lobby_check.sendGameSetupLobbyCheck +local tryGameSetupLobbyCheckFromTicker = ____game_setup_lobby_check.tryGameSetupLobbyCheckFromTicker +local ____player_info = require("player_info") +local PlayerInfo = ____player_info.PlayerInfo +local ____store_arcade_packs = require("store_arcade_packs") +local loadArcadePityForPlayer = ____store_arcade_packs.loadArcadePityForPlayer +local ____store_item_access = require("store_item_access") +local isStorePurchaseBlockedById = ____store_item_access.isStorePurchaseBlockedById +local ____chat_wheel_grant = require("chat_wheel_grant") +local getChatWheelVideoPathForSound = ____chat_wheel_grant.getChatWheelVideoPathForSound +local ____server_config = require("server_config") +local SERVER_CONFIG = ____server_config.SERVER_CONFIG +local ____api_helper = require("api_helper") +local setApiHeaders = ____api_helper.setApiHeaders +local ____SoundSystem = require("SoundSystem") +local SoundSystemManager = ____SoundSystem.SoundSystemManager +local SoundCooldownSystem = ____SoundSystem.SoundCooldownSystem +local SoundEventSystem = ____SoundSystem.SoundEventSystem +local ____precache_models_from_kv = require("utils.precache_models_from_kv") +local precacheModelsFromNpcUnitsCustomKv = ____precache_models_from_kv.precacheModelsFromNpcUnitsCustomKv +local ____precache_all_npc_units = require("utils.precache_all_npc_units") +local precacheAllCustomUnitsByNameAsync = ____precache_all_npc_units.precacheAllCustomUnitsByNameAsync +local ____precache_misc_particles = require("utils.precache_misc_particles") +local precacheMiscDistributedAndOrphanParticles = ____precache_misc_particles.precacheMiscDistributedAndOrphanParticles +local ____CutsceneManager = require("cutscenes.CutsceneManager") +local CutsceneManager = ____CutsceneManager.CutsceneManager +local ____hero_gravestone_respawn = require("gameplay.hero_gravestone_respawn") +local HeroGravestoneRespawn = ____hero_gravestone_respawn.HeroGravestoneRespawn +local precacheHeroGravestoneParticles = ____hero_gravestone_respawn.precacheHeroGravestoneParticles +local ____light_fix = require("utils.light_fix") +local spawnLightFixForPlayer = ____light_fix.spawnLightFixForPlayer +local ____cards_init = require("cards.cards_init") +local InitCards = ____cards_init.InitCards +local ____CardSystem = require("cards.CardSystem") +local ShowCardSelectionWithForcedCardToPlayer = ____CardSystem.ShowCardSelectionWithForcedCardToPlayer +local ____card_52_effects = require("cards.card_52_effects") +local getEnemyStatsMultiplierFromCard52 = ____card_52_effects.getEnemyStatsMultiplierFromCard52 +local ____utils = require("utils.utils") +local GetAllHeroes = ____utils.GetAllHeroes +local ____hero_rage = require("abilities.system.hero_rage") +local attachHeroRageSystemForConfiguredHero = ____hero_rage.attachHeroRageSystemForConfiguredHero +local function rawPrintFn(____, ...) + _G:print(...) +end +local ENABLE_VERBOSE_GAME_MODE_LOGS = false +local ____print = ENABLE_VERBOSE_GAME_MODE_LOGS and rawPrintFn or (function(____, ...) return nil end) +local heroSelectionTime = 120 +local CHAT_WHEEL_COOLDOWN = 12 +--- Raid-боссы из spawn_manager — победа при убийстве +local function isRaidBossUnitName(self, unitName) + return unitName == "npc_boss_nevermore" +end +____exports.GameMode = __TS__Class() +local GameMode = ____exports.GameMode +GameMode.name = "GameMode" +GameMode.____file_path = "scripts/vscripts/GameMode.lua" +function GameMode.prototype.____constructor(self) + self.isSetupStarting = false + self.playerReadyState = {} + self.chatWheelPreviewSounds = __TS__New(Map) + self.playerWorldLightFixSpawned = __TS__New(Set) + self.homerUnderAttackNotifyNextAt = 0 + self.pendingDayNightSyncBurstByPlayer = __TS__New(Set) + GameRules.CutsceneManager = __TS__New(CutsceneManager) + HeroGravestoneRespawn:initialize() + self:configure() + InitOrderFilter(nil) + self:startSetupStatusTicker() + self:registerSetupHandlers() + GameRules:SetCustomGameSetupAutoLaunchDelay(-1) + GameRules:SetStrategyTime(15) + Difficulty:Init() + self:initializeHeroCosmeticManager() + ListenToGameEvent( + "game_rules_state_change", + function() return self:OnStateChange() end, + nil + ) + ListenToGameEvent( + "npc_spawned", + function(event) return self:OnNpcSpawned(event) end, + nil + ) + ListenToGameEvent( + "entity_killed", + function(event) return self:OnEntityKilled(event) end, + nil + ) + ListenToGameEvent( + "entity_hurt", + function(event) return self:OnEntityHurtHomer(event) end, + nil + ) + ListenToGameEvent( + "dota_item_picked_up", + function(event) return self:OnItemPickedUp(event) end, + nil + ) + ListenToGameEvent( + "player_chat", + function(event) return self:OnPlayerChat(event) end, + nil + ) + ListenToGameEvent( + "tree_cut", + function(event) return self:OnTreeCut(event) end, + nil + ) + ListenToGameEvent( + "player_connect_full", + function(event) + local playerId = event.PlayerID + if playerId ~= nil and playerId >= 0 then + Timers:CreateTimer( + 1.5, + function() + self:loadChatWheelSoundsFromServer(playerId) + loadArcadePityForPlayer(nil, playerId) + return nil + end + ) + Timers:CreateTimer( + 1.5, + function() + ArsenalManager:loadFromServer(playerId) + return nil + end + ) + self:scheduleDayNightAndAltCastResyncBurst(playerId) + end + end, + nil + ) + CustomGameEventManager:RegisterListener( + "on_tiped", + function(_eventSourceIndex, data) return self:OnTiped(data) end + ) + CustomGameEventManager:RegisterListener( + "hero_selection_preview", + function(_eventSourceIndex, data) return self:OnHeroSelectionPreview(data) end + ) + CustomGameEventManager:RegisterListener( + "hero_selection_confirmed", + function(_eventSourceIndex, data) return self:OnHeroSelectionConfirmed(data) end + ) + CustomGameEventManager:RegisterListener( + "hero_selection_random_pick", + function(_eventSourceIndex, data) return self:OnHeroSelectionRandomPick(data) end + ) + CustomGameEventManager:RegisterListener( + "day_night_sync_request", + function(_eventSourceIndex, data) + local playerId = data.PlayerID or data.player_id + if playerId == nil or playerId < 0 then + return + end + if GameRules:State_Get() ~= DOTA_GAMERULES_STATE_GAME_IN_PROGRESS then + return + end + DayNightCycleManager:getInstance():SyncStateToPlayer(playerId) + end + ) + CustomGameEventManager:RegisterListener( + "request_alt_cast_sync", + function(_eventSourceIndex, data) + local playerId = data.PlayerID or data.player_id + if playerId == nil or playerId < 0 then + return + end + if GameRules:State_Get() ~= DOTA_GAMERULES_STATE_GAME_IN_PROGRESS then + return + end + AbilityAltCastManager:getInstance():syncPlayerStatesToClient(playerId) + end + ) + SkipNightVoteManager:getInstance() + CustomGameEventManager:RegisterListener( + "chat_wheel_select", + function(_eventSourceIndex, data) return self:OnChatWheelSelect(data) end + ) + CustomGameEventManager:RegisterListener( + "chat_wheel_cd_start", + function(_eventSourceIndex, data) return self:OnChatWheelStartCooldown(data) end + ) + CustomGameEventManager:RegisterListener( + "chat_wheel_set_sound", + function(_eventSourceIndex, data) return self:OnChatWheelSetSound(data) end + ) + CustomGameEventManager:RegisterListener( + "chat_wheel_buy_sound", + function(_eventSourceIndex, data) return self:OnChatWheelBuySound(data) end + ) + CustomGameEventManager:RegisterListener( + "chat_wheel_preview_sound", + function(_eventSourceIndex, data) return self:OnChatWheelPreviewSound(data) end + ) + CustomGameEventManager:RegisterListener( + "store_subscription_mascot_sound", + function(_eventSourceIndex, data) return self:OnStoreSubscriptionMascotSound(data) end + ) + AbilityAltCastManager:getInstance() + SoundSystemManager:getInstance():initialize() + MatchEndCombatStats:registerDamageFilter() +end +function GameMode.Precache(context) + precacheModelsFromNpcUnitsCustomKv(nil, context) + precacheAllCustomUnitsByNameAsync(nil, context) + PrecacheResource("particle_folder", "particles/units/heroes/hero_slark/", context) + PrecacheResource("particle_folder", "particles/units/heroes/hero_death_prophet/", context) + PrecacheResource("particle_folder", "models/items/death_prophet/", context) + PrecacheResource("particle_folder", "particles/units/heroes/hero_phantom_assassin/", context) + PrecacheResource("particle_folder", "particles/units/heroes/hero_sven/", context) + PrecacheResource("particle_folder", "particles/units/heroes/hero_magnataur/", context) + PrecacheResource("particle_folder", "particles/units/heroes/hero_primal_beast/", context) + PrecacheResource("particle_folder", "particles/units/heroes/hero_venomancer/", context) + PrecacheResource("particle_folder", "particles/units/heroes/hero_viper/", context) + PrecacheResource("particle_folder", "particles/units/heroes/hero_witchdoctor/", context) + PrecacheResource("particle_folder", "particles/units/heroes/hero_largo/", context) + PrecacheResource("particle_folder", "particles/units/heroes/hero_crystalmaiden/", context) + PrecacheResource("particle_folder", "particles/units/heroes/hero_lina/", context) + PrecacheResource("particle_folder", "particles/units/heroes/hero_axe/", context) + PrecacheResource("particle_folder", "particles/units/heroes/hero_beastmaster/", context) + PrecacheResource("particle_folder", "particles/units/heroes/hero_techies/", context) + PrecacheResource("particle_folder", "particles/units/heroes/hero_nevermore", context) + PrecacheResource("particle_folder", "particles/units/heroes/hero_bristleback", context) + PrecacheResource("particle_folder", "particles/units/heroes/hero_legion_commander/", context) + PrecacheResource("particle_folder", "particles/units/heroes/hero_queenofpain/", context) + PrecacheResource("particle_folder", "particles/units/heroes/hero_terrorblade", context) + PrecacheResource("particle_folder", "particles/units/heroes/hero_bloodseeker", context) + PrecacheResource("particle_folder", "particles/units/heroes/hero_phantom_assassin_persona", context) + PrecacheResource("particle_folder", "particles/units/heroes/hero_jakiro", context) + PrecacheResource("particle_folder", "particles/units/heroes/hero_dragon_knight", context) + PrecacheResource("particle_folder", "particles/units/heroes/hero_dragon_knight_persona", context) + PrecacheResource("particle_folder", "particles/units/heroes/hero_undying", context) + PrecacheResource("particle_folder", "particles/units/heroes/hero_centaur/", context) + precacheMiscDistributedAndOrphanParticles(nil, context) + precacheHeroGravestoneParticles(nil, context) + PrecacheResource("particle", "particles/econ/items/wraith_king/wraith_king_ti6_bracer/wraith_king_ti6_hellfireblast_debuff_fire.vpcf", context) + PrecacheResource("particle", "particles/units/heroes/hero_invoker/invoker_emp_explode.vpcf", context) + PrecacheResource("particle", "particles/units/heroes/hero_arc_warden/arc_warden_magnetic_tempest_ring.vpcf", context) + PrecacheResource("soundfile", "soundevents/invasion_sounds.vsndevts", context) + PrecacheResource("soundfile", "soundevents/wave_music.vsndevts", context) + PrecacheResource("sound", "sounds/ui/treasure_01.vsnd", context) + PrecacheResource("sound", "sounds/ui/treasure_02.vsnd", context) + PrecacheResource("sound", "sounds/ui/treasure_03.vsnd", context) + PrecacheResource("sound", "sounds/ui/cards/flip.vsnd", context) + PrecacheResource("particle", "particles/econ/items/centaur/centaur_ti9/centaur_double_edge_ti9_hit_tgt.vpcf", context) + PrecacheResource("sound", "sounds/ui/stingers/enc/muerta_debut_stinger.vsnd", context) + PrecacheResource("sound", "sounds/weapons/hero/skeleton_king/hellfire_blast.vsnd", context) + PrecacheResource("sound", "sounds/weapons/hero/chaos_knight/chaos_strike.vsnd", context) + PrecacheResource("particle", "particles/econ/items/pugna/pugna_ward_ti5/pugna_ward_attack_heavy_ti_5.vpcf", context) + PrecacheResource("particle", "particles/econ/items/dazzle/dazzle_ti9/dazzle_shadow_wave_ti9_crimson_impact_damage.vpcf", context) + PrecacheResource("particle", "particles/econ/items/medusa/medusa_daughters/medusa_daughters_mana_shield_shell_impact_d.vpcf", context) + PrecacheResource("particle", "particles/units/heroes/hero_medusa/medusa_mana_shield.vpcf", context) + PrecacheResource("soundfile", "soundevents/game_sounds_heroes/game_sounds_warlock.vsndevts", context) + PrecacheResource("soundfile", "soundevents/game_sounds_heroes/game_sounds_arc_warden.vsndevts", context) + PrecacheResource("soundfile", "soundevents/game_sounds_heroes/game_sounds_skeletonking.vsndevts", context) + PrecacheResource("soundfile", "soundevents/game_sounds_heroes/game_sounds_abaddon.vsndevts", context) + PrecacheResource("soundfile", "soundevents/game_sounds_heroes/game_sounds_pudge.vsndevts", context) + PrecacheResource("soundfile", "soundevents/game_sounds_heroes/game_sounds_bristleback.vsndevts", context) + PrecacheResource("soundfile", "soundevents/game_sounds_heroes/game_sounds_centaur.vsndevts", context) + PrecacheResource("soundfile", "soundevents/voscripts/game_sounds_vo_bristleback.vsndevts", context) + PrecacheResource("soundfile", "soundevents/game_sounds_heroes/game_sounds_chaos_knight.vsndevts", context) + PrecacheResource("soundfile", "soundevents/game_sounds_heroes/game_sounds_queenofpain.vsndevts", context) + PrecacheResource("soundfile", "soundevents/voscripts/game_sounds_vo_queenofpain.vsndevts", context) + PrecacheResource("soundfile", "soundevents/voscripts/game_sounds_vo_hoodwink.vsndevts", context) + PrecacheResource("soundfile", "soundevents/voscripts/game_sounds_vo_drowranger.vsndevts", context) + PrecacheResource("soundfile", "soundevents/game_sounds_heroes/game_sounds_bloodseeker.vsndevts", context) + PrecacheResource("soundfile", "soundevents/game_sounds_heroes/game_sounds_phantom_assassin.vsndevts", context) + PrecacheResource("soundfile", "soundevents/voscripts/game_sounds_vo_phantom_assassin.vsndevts", context) + PrecacheResource("soundfile", "soundevents/game_sounds_heroes/game_sounds_crystalmaiden.vsndevts", context) + PrecacheResource("soundfile", "soundevents/voscripts/game_sounds_vo_crystalmaiden.vsndevts", context) + PrecacheResource("soundfile", "soundevents/game_sounds_heroes/game_sounds_jakiro.vsndevts", context) + PrecacheResource("soundfile", "soundevents/voscripts/game_sounds_vo_jakiro.vsndevts", context) + PrecacheResource("soundfile", "soundevents/game_sounds_heroes/game_sounds_spectre.vsndevts", context) + PrecacheResource("soundfile", "soundevents/voscripts/game_sounds_vo_spectre.vsndevts", context) + PrecacheResource("sound", "sounds/weapons/hero/arc_warden/magnetic_field.vsnd", context) + PrecacheResource("sound", "sounds/items/skull_basher.vsnd", context) + PrecacheResource("sound", "sounds/ui/compendium_levelup.vsnd", context) + PrecacheResource("sound", "sounds/ui/compendium_points.vsnd", context) + PrecacheResource("sound", "sounds/weapons/hero/techies/remote_mine01.vsnd", context) + PrecacheResource("sound", "sounds/ui/xp_count.vsnd", context) + PrecacheResource("sound", "sounds/weapons/hero/templar_assassin/trap_explode.vsnd", context) + PrecacheResource("sound", "sounds/music/dsadowski_02.music.battle_03_end.vsnd", context) + PrecacheResource("sound", "sounds/music/i_am_sad.vsnd", context) +end +function GameMode.Activate() + GameRules.Addon = __TS__New(____exports.GameMode) + registerBlackShopCustomGameEvents(nil) + InitCards(nil) + InitializeQuests(nil) + MiniProfileServer:getInstance() + LeaderboardServer:getInstance() + GameStatsTracker:getInstance() + PlayerProfileManager:getInstance() + AdminMenuManager:getInstance():initialize() + BattlePassServer:getInstance() + ArsenalManager:initialize() + MarketplaceManager:initialize() +end +function GameMode.prototype.configure(self) + local customMaxLevel = 99 + local expPerLevel = { + 100, + 150, + 200, + 250, + 300, + 350, + 400, + 450, + 500, + 550, + 625, + 700, + 775, + 850, + 925, + 1000, + 1075, + 1150, + 1225, + 1300, + 1375, + 1450, + 1525, + 1600, + 1675, + 1775, + 1875, + 1975, + 2075, + 2175, + 2275, + 2375, + 2475, + 2575, + 2675, + 2775, + 2875, + 2975, + 3075, + 3175, + 3275, + 3375, + 3475, + 3575, + 3675, + 3775, + 3875, + 3975, + 4075, + 4175, + 4325, + 4475, + 4625, + 4775, + 4925, + 5075, + 5225, + 5375, + 5525, + 5675, + 5825, + 5975, + 6125, + 6275, + 6425, + 6575, + 6725, + 6875, + 7025, + 7175, + 7325, + 7475, + 7625, + 7775, + 7925, + 8050, + 8175, + 8300, + 8425, + 8550, + 8675, + 8800, + 8925, + 9050, + 9175, + 9300, + 9425, + 9550, + 9675, + 9800, + 9925, + 10050, + 10175, + 10300, + 10425, + 10550, + 10675, + 10800 + } + GameRules:GetGameModeEntity():SetCustomAttributeDerivedStatValue(DOTA_ATTRIBUTE_AGILITY_ARMOR, 0.05) + GameRules:GetGameModeEntity():SetCustomAttributeDerivedStatValue(DOTA_ATTRIBUTE_AGILITY_ATTACK_SPEED, 0.5) + GameRules:GetGameModeEntity():SetCustomAttributeDerivedStatValue(DOTA_ATTRIBUTE_INTELLIGENCE_MANA, 4) + GameRules:GetGameModeEntity():SetCustomAttributeDerivedStatValue(DOTA_ATTRIBUTE_AGILITY_DAMAGE, 2) + GameRules:GetGameModeEntity():SetCustomAttributeDerivedStatValue(DOTA_ATTRIBUTE_STRENGTH_DAMAGE, 4) + GameRules:GetGameModeEntity():SetCustomAttributeDerivedStatValue(DOTA_ATTRIBUTE_INTELLIGENCE_DAMAGE, 3) + GameRules:GetGameModeEntity():SetCustomAttributeDerivedStatValue(DOTA_ATTRIBUTE_ALL_DAMAGE, 1.25) + local heroExpTable = {} + heroExpTable[2] = 0 + do + local i = 2 + while i <= customMaxLevel do + heroExpTable[i + 1] = (heroExpTable[i] or 0) + (expPerLevel[i - 2 + 1] or expPerLevel[#expPerLevel]) + i = i + 1 + end + end + GameRules:GetGameModeEntity():SetCustomXPRequiredToReachNextLevel(heroExpTable) + GameRules:GetGameModeEntity():SetUseCustomHeroLevels(true) + GameRules:GetGameModeEntity():SetCustomHeroMaxLevel(customMaxLevel) + WaveManager:getInstance():Initialize() + ItemDropSystem:Initialize() + ItemDetector:Initialize() + SpawnManager:getInstance():Initialize() + CookingSystem:initialize() + GameRules:SetCustomGameTeamMaxPlayers(DOTA_TEAM_GOODGUYS, 4) + GameRules:SetCustomGameTeamMaxPlayers(DOTA_TEAM_BADGUYS, 0) + if GameRules:IsCheatMode() then + GameRules:SetCustomGameTeamMaxPlayers(DOTA_TEAM_BADGUYS, 6) + end + GameRules:GetGameModeEntity():SetPlayerHeroAvailabilityFiltered(true) + GameRules:SetPreGameTime(3) + GameRules:GetGameModeEntity():SetDaynightCycleDisabled(true) + GameRules:SetShowcaseTime(0) + GameRules:SetHeroSelectionTime(heroSelectionTime) + GameRules:GetGameModeEntity():SetFreeCourierModeEnabled(true) + GameRules:GetGameModeEntity():SetUseTurboCouriers(true) + GameRules:GetGameModeEntity():SetCustomBuybackCostEnabled(true) + GameRules:GetGameModeEntity():SetLoseGoldOnDeath(false) + GameRules:SetGoldPerTick(0) + GameRules:SetGoldTickTime(0) + GameRules:SetUseUniversalShopMode(true) + PlayerResource:SetCustomBuybackCost(0, 1000) +end +function GameMode.prototype.startSetupStatusTicker(self) + Timers:CreateTimer( + 0.1, + function() + local state = GameRules:State_Get() + if state ~= DOTA_GAMERULES_STATE_CUSTOM_GAME_SETUP then + return 0.1 + end + if self.isSetupStarting then + return 0.5 + end + local totalSlots = GameRules:GetCustomGameTeamMaxPlayers(DOTA_TEAM_GOODGUYS) + GameRules:GetCustomGameTeamMaxPlayers(DOTA_TEAM_BADGUYS) + local connected = 0 + do + local playerId = 0 + while playerId < DOTA_MAX_PLAYERS do + do + local pid = playerId + if not isRealLobbyPlayer(nil, pid) then + goto __continue42 + end + local cs = PlayerResource:GetConnectionState(pid) + if isConnectionStateEffectivelyInGame(nil, cs) then + connected = connected + 1 + end + end + ::__continue42:: + playerId = playerId + 1 + end + end + local realInLobby = countRealLobbyPlayers(nil) + local allConnected = connected >= totalSlots or realInLobby > 0 and connected == realInLobby + local setupTimeout = 10 + local timeLeft = math.max( + 0, + setupTimeout - GameRules:GetDOTATime(false, false) % (setupTimeout + 1) + ) + CustomGameEventManager:Send_ServerToAllClients("setup_status_update", {loading = not allConnected, timeLeft = allConnected and -1 or -1, connected = connected, totalSlots = totalSlots}) + local players = {} + do + local playerId = 0 + while playerId < DOTA_MAX_PLAYERS do + do + local pid = playerId + if not isRealLobbyPlayer(nil, pid) then + goto __continue45 + end + local steamId = tostring(PlayerResource:GetSteamAccountID(pid)) + players[#players + 1] = { + id = playerId, + name = PlayerResource:GetPlayerName(pid), + steamId = steamId + } + end + ::__continue45:: + playerId = playerId + 1 + end + end + CustomGameEventManager:Send_ServerToAllClients("setup_players_update", {players = players}) + CustomGameEventManager:Send_ServerToAllClients("setup_ready_update", {ready = self.playerReadyState}) + tryGameSetupLobbyCheckFromTicker(nil) + return 0.5 + end + ) +end +function GameMode.prototype.registerSetupHandlers(self) + CustomGameEventManager:RegisterListener( + "player_toggle_ready", + function(eventSourceIndex, _data) + local playerController = EntIndexToHScript(eventSourceIndex) + if not playerController then + return + end + local playerID = playerController:GetPlayerID() + if not PlayerResource:IsValidPlayerID(playerID) or not isRealLobbyPlayer(nil, playerID) then + return + end + local isHost = playerID == 0 + if isHost then + return + end + local current = self.playerReadyState[playerID] == true + self.playerReadyState[playerID] = not current + CustomGameEventManager:Send_ServerToAllClients("setup_ready_update", {ready = self.playerReadyState}) + end + ) + CustomGameEventManager:RegisterListener( + "host_pressed_ready", + function(eventSourceIndex, _data) + local playerController = EntIndexToHScript(eventSourceIndex) + if not playerController then + return + end + local playerID = playerController:GetPlayerID() + local isHost = playerID == 0 + if not PlayerResource:IsValidPlayerID(playerID) or not isRealLobbyPlayer(nil, playerID) or not isHost then + return + end + self.isSetupStarting = true + GameRules:SetCustomGameSetupRemainingTime(3) + CustomGameEventManager:Send_ServerToAllClients("start_custom_setup_time", {}) + end + ) + CustomGameEventManager:RegisterListener( + "host_cancel_ready", + function(eventSourceIndex, _data) + local playerController = EntIndexToHScript(eventSourceIndex) + if not playerController then + return + end + local playerID = playerController:GetPlayerID() + local isHost = playerID == 0 + if not PlayerResource:IsValidPlayerID(playerID) or not isRealLobbyPlayer(nil, playerID) or not isHost then + return + end + self.isSetupStarting = false + GameRules:SetCustomGameSetupRemainingTime(300) + CustomGameEventManager:Send_ServerToAllClients("cancel_custom_setup_time", {}) + end + ) + Timers:CreateTimer( + 0.1, + function() + if GameRules:State_Get() ~= DOTA_GAMERULES_STATE_CUSTOM_GAME_SETUP then + return 0.3 + end + do + local playerId = 0 + while playerId < DOTA_MAX_PLAYERS do + do + local pid = playerId + if not isRealLobbyPlayer(nil, pid) then + goto __continue60 + end + local isHost = playerId == 0 + local player = PlayerResource:GetPlayer(pid) + if player then + CustomGameEventManager:Send_ServerToPlayer(player, "setup_host_info", {isHost = isHost}) + CustomGameEventManager:Send_ServerToPlayer(player, "setup_ready_update", {ready = self.playerReadyState}) + end + end + ::__continue60:: + playerId = playerId + 1 + end + end + return 0.5 + end + ) +end +function GameMode.prototype.OnStateChange(self) + local state = GameRules:State_Get() + if state == DOTA_GAMERULES_STATE_CUSTOM_GAME_SETUP then + do + local playerId = 0 + while playerId < DOTA_MAX_PLAYERS do + local pid = playerId + if isRealLobbyPlayer(nil, pid) then + PlayerResource:SetCustomTeamAssignment(pid, DOTA_TEAM_GOODGUYS) + end + playerId = playerId + 1 + end + end + resetGameSetupLobbyCheckPhase(nil) + Timers:CreateTimer( + 0.5, + function() + if GameRules:State_Get() == DOTA_GAMERULES_STATE_CUSTOM_GAME_SETUP then + sendGameSetupLobbyCheck(nil) + end + return nil + end + ) + end + if state == DOTA_GAMERULES_STATE_PRE_GAME then + resetGameSetupLobbyCheckPhase(nil) + self.playerReadyState = {} + self:StartGame() + end + if state == DOTA_GAMERULES_STATE_GAME_IN_PROGRESS then + DayNightCycleManager:getInstance():Initialize() + RandomEventsManager:getInstance() + BlackShop:getInstance() + initializeKunkkaShovelAnchorMarkers(nil) + CrystalCurrency:getInstance() + WaveManager:getInstance():SetGameStarted() + self:GameInProgress() + end + if state == DOTA_GAMERULES_STATE_HERO_SELECTION then + self:CheckPlayerAvailability() + end +end +function GameMode.prototype.StartGame(self) +end +function GameMode.prototype.CheckPlayerAvailability(self) + local storeManager = StoreManager:getInstance() + do + local playerId = 0 + while playerId < DOTA_MAX_TEAM_PLAYERS do + do + local pid = playerId + if not isRealLobbyPlayer(nil, pid) then + goto __continue74 + end + __TS__ArrayForEach( + __TS__ObjectEntries(HERO_LIST_TABLE), + function(____, ____bindingPattern0) + local heroData + local heroName + heroName = ____bindingPattern0[1] + heroData = ____bindingPattern0[2] + local isDonateHero = heroData.isDonate == true + local heroUnlocked = not isDonateHero or storeManager:hasUnlockedHero(pid, heroName, heroData.storeItemId) + if heroUnlocked then + GameRules:AddHeroToPlayerAvailability(pid, heroData.heroId) + end + end + ) + end + ::__continue74:: + playerId = playerId + 1 + end + end +end +function GameMode.prototype.getAvailableHeroNamesForPlayer(self, playerId) + local storeManager = StoreManager:getInstance() + local enabledHeroes = self:getEnabledHeroesFromHeroList() + local availableHeroes = {} + __TS__ArrayForEach( + __TS__ObjectEntries(HERO_LIST_TABLE), + function(____, ____bindingPattern0) + local heroData + local heroName + heroName = ____bindingPattern0[1] + heroData = ____bindingPattern0[2] + if not enabledHeroes[heroName] then + return + end + local isDonateHero = heroData.isDonate == true + local heroUnlocked = not isDonateHero or storeManager:hasUnlockedHero(playerId, heroName, heroData.storeItemId) + if heroUnlocked then + availableHeroes[#availableHeroes + 1] = heroName + end + end + ) + return availableHeroes +end +function GameMode.prototype.getEnabledHeroesFromHeroList(self) + local enabledHeroes = {} + local rawKv = LoadKeyValues("scripts/npc/herolist.txt") + local ____temp_2 = rawKv and rawKv.CustomHeroList + if ____temp_2 == nil then + ____temp_2 = rawKv + end + local heroList = ____temp_2 + if heroList then + for heroName in pairs(heroList) do + local count = tonumber(tostring(heroList[heroName])) or 0 + if count > 0 or count == -1 then + enabledHeroes[tostring(heroName)] = true + end + end + end + if #__TS__ObjectKeys(enabledHeroes) == 0 then + for heroName in pairs(HERO_LIST_TABLE) do + enabledHeroes[tostring(heroName)] = true + end + end + return enabledHeroes +end +function GameMode.prototype.initializeHeroCosmeticManager(self) + local cosmeticManager = HeroCosmeticManager:getInstance() + cosmeticManager:registerHeroCosmetics("npc_dota_hero_nagash", {model = "models/heroes/phantom_assassin_persona/phantom_assassin_persona.vmdl", slots = {weapon = "models/heroes/phantom_assassin_persona/phantom_assassin_persona_weapon.vmdl", head = "models/heroes/phantom_assassin_persona/phantom_assassin_persona_head.vmdl", armor = "models/heroes/phantom_assassin_persona/phantom_assassin_persona_armor.vmdl", legs = "models/heroes/phantom_assassin_persona/phantom_assassin_persona_legs.vmdl"}, scale = 1}) + cosmeticManager:registerHeroCosmetics("npc_dota_hero_bloodhunter", {model = "models/heroes/blood_seeker/blood_seeker.vmdl", slots = {weapon = "models/items/blood_seeker/thirst_of_eztzhok_weapon/thirst_of_eztzhok.vmdl", right_hook4_0 = "models/items/blood_seeker/thirst_of_eztzhok_weapon_offhand/thirst_of_eztzhok_offhand.vmdl", select_high = "models/items/blood_seeker/bloodseeker_crownfall_immortal/bloodseeker_crownfall_immortal.vmdl", head = "models/items/blood_seeker/bloodseeker_immortal_head/bloodseeker_immortal_head.vmdl"}}) + cosmeticManager:registerHeroCosmetics("npc_dota_hero_yuki_onna", {model = "models/heroes/crystal_maiden/crystal_maiden_arcana.vmdl", slots = {weapon = "models/items/crystal_maiden/cm_screeauk/cm_screeauk_weapon.vmdl", select_high = "models/items/crystal_maiden/cm_screeauk/cm_screeauk_shoulder.vmdl", Spine_0 = "models/items/crystal_maiden/cm_screeauk/cm_screeauk_back.vmdl", head = "models/items/crystal_maiden/cm_screeauk/cm_screeauk_head.vmdl"}}) +end +function GameMode.prototype.GameInProgress(self) + Difficulty:OnHeroSelectionState() + do + local playerID = 0 + while playerID < DOTA_MAX_PLAYERS do + if PlayerResource:IsValidPlayerID(playerID) then + local steamID = PlayerResource:GetSteamAccountID(playerID) + local testerIDs = { + "877002179", + "453736017", + "133273043", + "223272113", + "355480974", + "985645109", + "1592242655", + "899783721", + "959290792" + } + if __TS__ArrayIncludes( + testerIDs, + tostring(steamID) + ) then + if steamID == 877002179 then + GameRules:SendCustomMessage("Добро пожаловать в игру хозяин Nasqreal!", 0, 0) + end + return + end + end + return + end + end +end +function GameMode.prototype.Reload(self) +end +function GameMode.prototype.addTestBots(self) + if GameRules:IsCheatMode() then + local botHeroes = {"npc_dota_hero_drow_ranger"} + do + local i = 0 + while i < 1 do + local heroName = botHeroes[i % #botHeroes + 1] + Tutorial:AddBot( + heroName, + "TestBot" .. tostring(i + 1), + "", + true + ) + i = i + 1 + end + end + end +end +function GameMode.prototype.getDifficulter(self) + return type(Difficulter) ~= "nil" and Difficulter or 1 +end +function GameMode.prototype.resetWorldLightFixSpawnForPlayer(self, playerId) + self.playerWorldLightFixSpawned:delete(playerId) +end +function GameMode.prototype.OnPlayerChat(self, event) + if not IsServer() then + return + end + local trimmed = __TS__StringTrim(event.text) + local lower = string.lower(trimmed) + if lower == "-lightfix" then + local playerId = event.playerid + if not PlayerResource:IsValidPlayerID(playerId) or PlayerResource:IsFakeClient(playerId) then + return + end + spawnLightFixForPlayer(nil, playerId) + return + end + if GameRules:IsCheatMode() and GameRules:State_Get() == DOTA_GAMERULES_STATE_GAME_IN_PROGRESS then + if string.len(lower) >= 6 and string.sub(lower, 1, 5) == "-card" then + local rest = (string.gsub( + string.sub(lower, 6), + "^%s+", + "" + )) + if rest ~= "" then + local cardId = tonumber(rest) + if cardId ~= nil and cardId >= 1 then + local playerId = event.playerid + if not PlayerResource:IsValidPlayerID(playerId) or PlayerResource:IsFakeClient(playerId) then + return + end + ShowCardSelectionWithForcedCardToPlayer( + nil, + playerId, + cardId, + 1, + "cheat_chat_card" + ) + return + end + end + end + end +end +function GameMode.prototype.OnNpcSpawned(self, event) + local unit = EntIndexToHScript(event.entindex) + if not unit then + return + end + if unit:IsRealHero() and not unit:IsIllusion() then + local ownerPlayerId = unit:GetPlayerOwnerID() + if ownerPlayerId >= 0 and PlayerResource:IsValidPlayerID(ownerPlayerId) then + self:scheduleDayNightAndAltCastResyncBurst(ownerPlayerId) + end + local lightFixPid = unit:GetPlayerOwnerID() + if lightFixPid >= 0 and PlayerResource:IsValidPlayerID(lightFixPid) and not PlayerResource:IsFakeClient(lightFixPid) and not self.playerWorldLightFixSpawned:has(lightFixPid) then + self.playerWorldLightFixSpawned:add(lightFixPid) + Timers:CreateTimer( + 6.5, + function() + spawnLightFixForPlayer(nil, lightFixPid) + return nil + end + ) + end + attachHeroRageSystemForConfiguredHero(nil, unit) + end + if unit:IsRealHero() and not unit:HasAbility("ability_stacking_crit") then + local playerId = unit:GetPlayerOwnerID() + local steamID = PlayerResource:GetSteamAccountID(playerId) + Timers:CreateTimer( + 1, + function() + self:loadChatWheelSoundsFromServer(playerId) + return nil + end + ) + if steamID == 946902506 then + unit:AddItemByName("item_rofl_for_kaban_pumba") + end + unit:AddAbility("ability_stacking_crit"):SetLevel(1) + unit:AddAbility("ability_stacking_spell_crit"):SetLevel(1) + unit:AddAbility("ability_stats_multiplier"):SetLevel(1) + unit:AddAbility("ability_effects"):SetLevel(1) + if GameRules:IsCheatMode() and not unit:FindItemInInventory("item_blink") then + unit:AddItemByName("item_blink") + unit:AddItemByName("item_test") + end + HeroCosmeticManager:getInstance():onHeroSpawn(unit) + local arsenalPlayerId = unit:GetPlayerOwnerID() + if arsenalPlayerId >= 0 then + local arsenalPlayer = PlayerResource:GetPlayer(arsenalPlayerId) + if arsenalPlayer then + ArsenalManager:applyLoadout(arsenalPlayer, unit) + end + end + local heroPlayerId = unit:GetPlayerOwnerID() + if heroPlayerId >= 0 then + local heroName = unit:GetUnitName() + CustomGameEventManager:Send_ServerToAllClients("hero_spawned", {playerId = heroPlayerId, heroName = heroName}) + end + end + if unit:GetTeam() == DOTA_TEAM_GOODGUYS then + return + end + local requiredPlayers = 4 + local realHeroesCount = math.max( + 1, + countRealLobbyPlayers(nil) + ) + local difficulter = self:getDifficulter() + if realHeroesCount < requiredPlayers and self:getDifficulter() >= 2 then + local missing = requiredPlayers - realHeroesCount + difficulter = difficulter * (1 - 0.1 * missing) + end + local gameTime = GameRules:GetDOTATime(false, false) + local minutesPassed = math.max( + 0, + math.floor(gameTime / 60) + ) + local timeScale = 1 + minutesPassed * 0.02 + local card52EnemyScale = getEnemyStatsMultiplierFromCard52(nil) + local totalScale = difficulter * timeScale * card52EnemyScale + local unitName = unit.GetUnitName and unit:GetUnitName() + if type(unitName) == "string" and __TS__StringStartsWith(unitName, "npc_wave_boss") and WaveManager:getInstance():GetCurrentNight() > 6 then + totalScale = totalScale * 2 + end + unit:SetBaseMaxHealth(unit:GetMaxHealth() * totalScale) + unit:SetMaxHealth(unit:GetMaxHealth() * totalScale) + unit:SetHealth(unit:GetMaxHealth()) + unit:SetBaseHealthRegen(unit:GetBaseHealthRegen() * totalScale) + unit:SetBaseDamageMin(unit:GetBaseDamageMin() * totalScale) + unit:SetBaseDamageMax(unit:GetBaseDamageMax() * totalScale) + local diminishingScale = math.sqrt(totalScale) + local currentArmor = unit:GetPhysicalArmorBaseValue() + if currentArmor < 0 then + unit:SetPhysicalArmorBaseValue(currentArmor - diminishingScale) + else + unit:SetPhysicalArmorBaseValue(currentArmor + diminishingScale) + end + local currentResist = unit:GetBaseMagicalResistanceValue() + if currentResist < 0 then + unit:SetBaseMagicalResistanceValue(currentResist - diminishingScale) + else + unit:SetBaseMagicalResistanceValue(currentResist + diminishingScale) + end + applyDeathSentenceContractOnEnemySpawn( + nil, + unit, + Difficulty:getWinningContractSnapshot() + ) +end +function GameMode.prototype.scheduleDayNightAndAltCastResyncBurst(self, playerId) + if self.pendingDayNightSyncBurstByPlayer:has(playerId) then + return + end + self.pendingDayNightSyncBurstByPlayer:add(playerId) + local delays = {0.2, 1, 2, 4} + local pending = #delays + for ____, delay in ipairs(delays) do + Timers:CreateTimer( + delay, + function() + do + pcall(function() + if GameRules:State_Get() == DOTA_GAMERULES_STATE_GAME_IN_PROGRESS and PlayerResource:IsValidPlayerID(playerId) then + DayNightCycleManager:getInstance():SyncStateToPlayer(playerId) + AbilityAltCastManager:getInstance():syncPlayerStatesToClient(playerId) + end + end) + do + pending = pending - 1 + if pending <= 0 then + self.pendingDayNightSyncBurstByPlayer:delete(playerId) + end + end + end + return nil + end + ) + end +end +function GameMode.prototype.OnEntityHurtHomer(self, event) + if not IsServer() then + return + end + local ev = event + local victimIndex = ev.entindex_victim or ev.entindex_killed + if victimIndex == nil or victimIndex == nil then + return + end + local victim = EntIndexToHScript(victimIndex) + if not victim or victim:IsNull() or not victim:IsAlive() or not IsValidEntity(victim) then + return + end + if victim:GetUnitName() ~= "npc_homer" then + return + end + local damage = ev.damage or 0 + if damage <= 0 then + return + end + local attackerIndex = ev.entindex_attacker + if attackerIndex == nil or attackerIndex == nil then + return + end + local attacker = EntIndexToHScript(attackerIndex) + if not attacker or not IsValidEntity(attacker) then + return + end + if attacker == victim then + return + end + if attacker:GetTeamNumber() == victim:GetTeamNumber() then + return + end + local now = GameRules:GetGameTime() + if now < self.homerUnderAttackNotifyNextAt then + return + end + self.homerUnderAttackNotifyNextAt = now + 4.5 + CustomGameEventManager:Send_ServerToAllClients("homer_under_attack", {}) +end +function GameMode.prototype.applyTieredHeroRespawnOnDeath(self, hero) + if not IsServer() then + return + end + if not hero or not IsValidEntity(hero) then + return + end + if hero:WillReincarnate() or hero:IsReincarnating() then + return + end + local cutscene = GameRules.CutsceneManager + if cutscene ~= nil and cutscene:isCutsceneActive() then + return + end + local level = hero:GetLevel() + local seconds = level < 10 and 30 or (level <= 30 and 60 or 90) + hero:SetTimeUntilRespawn(seconds) +end +function GameMode.prototype.OnEntityKilled(self, event) + local killedUnit = EntIndexToHScript(event.entindex_killed) + if not killedUnit then + return + end + if killedUnit:IsHero() and killedUnit:IsRealHero() and not killedUnit:IsIllusion() then + local heroDead = killedUnit + if heroDead:GetTeamNumber() == DOTA_TEAM_GOODGUYS then + self:applyTieredHeroRespawnOnDeath(heroDead) + end + end + local unitName = killedUnit:GetUnitName() + if unitName == "npc_homer" then + GameStatsTracker:getInstance():onDefeat(function() + MatchEndRewards:dispatch( + "loss", + function() + GameRules:SetGameWinner(DOTA_TEAM_BADGUYS) + end + ) + end) + return + end + if isRaidBossUnitName(nil, unitName) then + GameStatsTracker:getInstance():onVictory(function() + __TS__ArrayForEach( + GetAllHeroes(nil), + function(____, hero) + hero:AddNewModifier( + hero, + getModifierSourceAbility(nil, hero), + "modifier_cutscene_lock", + {} + ) + end + ) + MatchEndRewards:dispatch("win") + end) + end +end +function GameMode.prototype.OnTreeCut(self, event) + local treePosition = Vector(event.tree_x, event.tree_y, 0) + local dropChance = 0.2 + if math.random() < dropChance then + local bananaItem = CreateItem("item_banana", nil, nil) + if bananaItem then + local drop = CreateItemOnPositionForLaunch(treePosition, bananaItem) + local dropRadius = RandomFloat(50, 100) + bananaItem:LaunchLootInitialHeight( + false, + 0, + 150, + 0.5, + treePosition + RandomVector(dropRadius) + ) + end + end +end +function GameMode.prototype.OnItemPickedUp(self, event) + local unitIndex = event.ItemEntityIndex + local unit = EntIndexToHScript(unitIndex) + local hero = unit and unit:GetOwner() + local item = EntIndexToHScript(event.ItemEntityIndex) + local owner = EntIndexToHScript(event.ItemEntityIndex) + local itemName = item:GetAbilityName() + local shopItem = BlackShop:getInstance():getItemInfo(itemName) + if shopItem then + if item._wasPurchased then + return + end + local playerId = hero and hero:GetPlayerOwnerID() or -1 + local blackShopRestockPosition + local physicalParent = item:GetContainer() + if physicalParent and not physicalParent:IsNull() then + blackShopRestockPosition = physicalParent:GetAbsOrigin() + end + local crystalCurrency = CrystalCurrency:getInstance() + local crystalAmount = crystalCurrency:getCrystals(playerId) + if crystalAmount >= shopItem.cost then + crystalCurrency:removeCrystals(playerId, shopItem.cost) + local success = BlackShop:getInstance():OnItemPickup(item, playerId) + if not success then + crystalCurrency:addCrystals(playerId, shopItem.cost) + UTIL_Remove(item) + else + do + pcall(function() + local qualityMap = { + [0] = "common", + [1] = "rare", + [2] = "epic", + [3] = "legendary", + [4] = "cursed", + [5] = "heavenly" + } + local qualityStr = qualityMap[shopItem.quality] or "common" + BattlePassServer:getInstance():onBlackShopPurchase(playerId, qualityStr) + end) + end + local ____opt_7 = PlayerResource:GetPlayer(playerId) + local heroForRestock = ____opt_7 and ____opt_7:GetAssignedHero() + if blackShopRestockPosition and heroForRestock and heroForRestock:HasModifier("modifier_item_restock") then + Timers:CreateTimer( + 0.03, + function() + BlackShop:getInstance():SpawnItemAtPosition(blackShopRestockPosition) + end + ) + end + end + else + BlackShop:getInstance():SpawnItemAtPosition(owner:GetAbsOrigin()) + GameRules:SendCustomMessage("ЧЁ ХОТЕЛ СПИЗИДТЬ ХУЕСОС?!?!?!? ПОСОСАЛ?!?!?!?", 0, 0) + hero:ForceKill(true) + EmitSoundOn("DOTA_Item.Dagon.Activate", hero) + EmitSoundOn("DOTA_Item.Dagon5.Target", owner) + UTIL_Remove(item) + end + end +end +function GameMode.prototype.OnTiped(self, data) + local ____data_9 = data + local target = ____data_9.target + local playerId = ____data_9.playerId + local hero = PlayerResource:GetSelectedHeroEntity(target) + if hero and IsValidEntity(hero) then + EmitSoundOn("General.Ping", hero) + ParticleManager:CreateParticle("particles/ping_player_clown.vpcf", PATTACH_ABSORIGIN_FOLLOW, hero) + end + do + pcall(function() + BattlePassServer:getInstance():onTipped(playerId) + end) + end + CustomGameEventManager:Send_ServerToAllClients("player_tip", data) +end +function GameMode.prototype.OnHeroSelectionPreview(self, data) + CustomGameEventManager:Send_ServerToAllClients("hero_selection_preview", data) +end +function GameMode.prototype.OnHeroSelectionConfirmed(self, data) + CustomGameEventManager:Send_ServerToAllClients("hero_selection_confirmed", data) +end +function GameMode.prototype.OnHeroSelectionRandomPick(self, data) + local playerId = data.playerId or data.PlayerID + if playerId == nil or playerId < 0 then + return + end + local selectedHero = PlayerResource:GetSelectedHeroName(playerId) + if selectedHero and selectedHero ~= "" then + return + end + local player = PlayerResource:GetPlayer(playerId) + if not player then + return + end + local availableHeroes = self:getAvailableHeroNamesForPlayer(playerId) + if #availableHeroes == 0 then + player:MakeRandomHeroSelection() + return + end + local randomIndex = RandomInt(0, #availableHeroes - 1) + local randomHeroName = availableHeroes[randomIndex + 1] + player:SetSelectedHero(randomHeroName) +end +function GameMode.prototype.OnChatWheelSelect(self, data) + local ChatActions = ChatActions or ({}) + ChatActions.PLAYER_NAME = 0 + ChatActions[ChatActions.PLAYER_NAME] = "PLAYER_NAME" + ChatActions.PLAYER_COLOR = 1 + ChatActions[ChatActions.PLAYER_COLOR] = "PLAYER_COLOR" + ChatActions.HERO_NAME = 2 + ChatActions[ChatActions.HERO_NAME] = "HERO_NAME" + local playerInfo = PlayerInfo:GetPlayerInfo(data.PlayerID) + local ____opt_10 = playerInfo and playerInfo.chat_wheel + if ____opt_10 ~= nil then + ____opt_10 = ____opt_10[tostring(data.select)] + end + local chatWheel = ____opt_10 + if not chatWheel then + return + end + local showVideo = false + local videoPath = nil + if chatWheel.sound then + ____print( + nil, + "[CHAT_WHEEL] Ищем видео для звука: " .. tostring(chatWheel.sound) + ) + local soundsWheel = playerInfo and playerInfo.sounds_wheel or ({}) + ____print( + nil, + "[CHAT_WHEEL] sounds_wheel keys: " .. __TS__ArrayJoin( + __TS__ObjectKeys(soundsWheel), + ", " + ) + ) + local soundData = nil + for ____, ____value in ipairs(__TS__ObjectEntries(soundsWheel)) do + local soundId = ____value[1] + local data = ____value[2] + local dataAny = data + ____print( + nil, + (("[CHAT_WHEEL] Проверяем звук: id=" .. tostring(soundId)) .. ", sound=") .. tostring(dataAny.sound) + ) + if dataAny.sound == chatWheel.sound or soundId == chatWheel.sound then + soundData = dataAny + ____print( + nil, + (("[CHAT_WHEEL] Найден звук! video=" .. tostring(dataAny.video)) .. ", video_path=") .. tostring(dataAny.video_path) + ) + break + end + end + if soundData then + videoPath = soundData.video or soundData.video_path + if not videoPath and chatWheel.sound then + videoPath = getChatWheelVideoPathForSound(nil, chatWheel.sound) + end + showVideo = not not videoPath + ____print( + nil, + (("[CHAT_WHEEL] Видео найдено: showVideo=" .. tostring(showVideo)) .. ", videoPath=") .. tostring(videoPath) + ) + ____print( + nil, + (("[CHAT_WHEEL] soundData.video=" .. tostring(soundData.video)) .. ", soundData.video_path=") .. tostring(soundData.video_path) + ) + else + ____print(nil, "[CHAT_WHEEL] Звук не найден в sounds_wheel!") + if chatWheel.sound then + videoPath = getChatWheelVideoPathForSound(nil, chatWheel.sound) + showVideo = not not videoPath + end + end + local soundIdForConfig = chatWheel.sound + if soundData then + local soundsWheel = playerInfo and playerInfo.sounds_wheel or ({}) + for ____, ____value in ipairs(__TS__ObjectEntries(soundsWheel)) do + local id = ____value[1] + local data = ____value[2] + local dataAny = data + if dataAny.sound == chatWheel.sound or id == chatWheel.sound then + soundIdForConfig = tostring(id) + break + end + end + end + local cooldownSystem = SoundCooldownSystem:getInstance() + local soundConfigKey = cooldownSystem:resolveSoundConfigKey(soundIdForConfig, chatWheel.sound) + local steamId = PlayerResource:GetSteamAccountID(data.PlayerID) + local isNoCooldownPlayer = steamId == SoundCooldownSystem.NO_COOLDOWN_STEAM_ID + ____print( + nil, + (((("[CHAT_WHEEL] Воспроизведение звука: chatWheel.sound=" .. tostring(chatWheel.sound)) .. ", soundIdForConfig=") .. soundIdForConfig) .. ", soundConfigKey=") .. soundConfigKey + ) + if cooldownSystem:isAnySoundBlocking() and not isNoCooldownPlayer then + local currentSoundConfig = cooldownSystem:getSoundConfig(soundConfigKey, chatWheel.sound) + local isCurrentSoundBlocking = (currentSoundConfig and currentSoundConfig.blocksOtherSounds) == true + if not isCurrentSoundBlocking then + local player = PlayerResource:GetPlayer(data.PlayerID) + if player ~= nil and player ~= nil then + CustomGameEventManager:Send_ServerToPlayer(player, "create_error_message", {message = "Сейчас играет другой звук, подождите"}) + end + ____print( + nil, + ("[CHAT_WHEEL] Звук " .. tostring(chatWheel.sound)) .. " заблокирован - играет другой звук" + ) + return + end + end + if not cooldownSystem:canPlaySound(soundIdForConfig, data.PlayerID) then + local remaining = cooldownSystem:getRemainingCooldown(soundIdForConfig, data.PlayerID) + local player = PlayerResource:GetPlayer(data.PlayerID) + if player ~= nil and player ~= nil then + local timeText + if remaining >= 60 then + local minutes = math.floor(remaining / 60) + local seconds = math.ceil(remaining % 60) + local secondsStr = seconds < 10 and "0" .. tostring(seconds) or tostring(seconds) + timeText = (tostring(minutes) .. ":") .. secondsStr + else + timeText = tostring(math.ceil(remaining)) .. " сек" + end + CustomGameEventManager:Send_ServerToPlayer(player, "create_error_message", {message = "Звук на кулдауне. Осталось: " .. timeText}) + SoundSystemManager:getInstance():updateSoundCooldownInNetTables(data.PlayerID, soundIdForConfig, remaining) + end + ____print( + nil, + ((((("[CHAT_WHEEL] Звук " .. tostring(chatWheel.sound)) .. " на кулдауне для игрока ") .. tostring(data.PlayerID)) .. ", осталось ") .. __TS__NumberToFixed(remaining, 1)) .. " сек" + ) + return + end + local soundEventName = chatWheel.sound == "i_am_sad" and "I_am_sad" or chatWheel.sound + ____print( + nil, + "[CHAT_WHEEL] Используется soundEventName=" .. tostring(soundEventName) + ) + local hero = PlayerResource:GetSelectedHeroEntity(data.PlayerID) + if hero then + EmitGlobalSound(soundEventName) + else + do + local playerID = 0 + while playerID < PlayerResource:GetPlayerCount() do + if PlayerResource:IsValidPlayerID(playerID) then + local player = PlayerResource:GetPlayer(playerID) + if player then + EmitSoundOnClient(soundEventName, player) + end + end + playerID = playerID + 1 + end + end + end + local soundConfig = cooldownSystem:getSoundConfig(soundConfigKey, chatWheel.sound) + if (soundConfig and soundConfig.blocksOtherSounds) == true then + cooldownSystem:setBlocking(soundConfigKey, true) + local blockDuration = soundConfig.blockDuration or 5.7 + Timers:CreateTimer( + blockDuration, + function() + cooldownSystem:setBlocking(soundConfigKey, false) + ____print( + nil, + "[CHAT_WHEEL] Блокировка других звуков снята после окончания " .. tostring(chatWheel.sound) + ) + return nil + end + ) + end + local foundSoundId = nil + if soundData then + local soundsWheel = playerInfo and playerInfo.sounds_wheel or ({}) + for ____, ____value in ipairs(__TS__ObjectEntries(soundsWheel)) do + local id = ____value[1] + local data = ____value[2] + local dataAny = data + if dataAny.sound == chatWheel.sound or id == chatWheel.sound then + foundSoundId = tostring(id) + break + end + end + end + cooldownSystem:setPlayerCooldown(soundIdForConfig, data.PlayerID) + SoundSystemManager:getInstance():startSoundCooldownTimer(data.PlayerID, soundIdForConfig) + SoundEventSystem:getInstance():triggerSoundEvent(data.PlayerID, chatWheel.sound, chatWheel.sound, soundData) + else + ____print(nil, "[CHAT_WHEEL] chatWheel.sound пустой!") + end + if videoPath and not showVideo then + ____print(nil, "[CHAT_WHEEL] Исправляем: videoPath есть, но showVideo=false. Устанавливаем showVideo=true") + showVideo = true + end + ____print( + nil, + (("[CHAT_WHEEL] Отправляем событие: show_video=" .. tostring(showVideo)) .. ", video_path=") .. tostring(videoPath) + ) + CustomGameEventManager:Send_ServerToAllClients("chat_wheel_send_sound", { + sender_id = data.PlayerID, + main_token = chatWheel.name, + is_team = false, + is_all = true, + sound = chatWheel.sound or "", + show_video = showVideo, + video_path = videoPath, + video = videoPath, + tokens = {players = {[0] = {player_name = ChatActions.PLAYER_NAME, player_color = ChatActions.PLAYER_COLOR, hero_name = ChatActions.HERO_NAME}}}, + add_tag = true + }) +end +function GameMode.prototype.OnChatWheelStartCooldown(self, data) + local playerID = data.PlayerID + local playerInfo = CustomNetTables:GetTableValue( + "cooldown_info", + tostring(playerID) + ) + local steamID = tostring(PlayerResource:GetSteamAccountID(playerID)) + if not playerInfo then + playerInfo = {cooldown_chat = 1} + CustomNetTables:SetTableValue( + "cooldown_info", + tostring(playerID), + playerInfo + ) + Timers:CreateTimer( + CHAT_WHEEL_COOLDOWN, + function() + playerInfo = {cooldown_chat = 0} + CustomNetTables:SetTableValue( + "cooldown_info", + tostring(playerID), + playerInfo + ) + end + ) + else + playerInfo.cooldown_chat = 1 + CustomNetTables:SetTableValue( + "cooldown_info", + tostring(playerID), + playerInfo + ) + Timers:CreateTimer( + CHAT_WHEEL_COOLDOWN, + function() + playerInfo.cooldown_chat = 0 + CustomNetTables:SetTableValue( + "cooldown_info", + tostring(playerID), + playerInfo + ) + end + ) + end +end +function GameMode.prototype.OnChatWheelPreviewSound(self, data) + local playerID = data.PlayerID + local soundId = data.sound_id + if not soundId then + return + end + local soundEventName = soundId == "i_am_sad" and "I_am_sad" or soundId + local player = PlayerResource:GetPlayer(playerID) + if not player then + return + end + local previousSound = self.chatWheelPreviewSounds:get(playerID) + if previousSound then + StopSoundOn(previousSound, player) + self.chatWheelPreviewSounds:delete(playerID) + end + EmitSoundOnClient(soundEventName, player) + self.chatWheelPreviewSounds:set(playerID, soundEventName) +end +function GameMode.prototype.OnStoreSubscriptionMascotSound(self, data) + local playerID = data.PlayerID + local soundId = __TS__StringTrim(tostring(data.sound_id or "")) + if not soundId then + return + end + local player = PlayerResource:GetPlayer(playerID) + if not player then + return + end + EmitSoundOnClient(soundId, player) +end +function GameMode.prototype.OnChatWheelSetSound(self, data) + local playerID = data.PlayerID + local slot = data.slot + local soundId = data.sound_id + ____print( + nil, + (((("[CHAT_WHEEL] Установка звука: soundId=" .. soundId) .. ", slot=") .. tostring(slot)) .. ", playerID=") .. tostring(playerID) + ) + local playerInfoData = PlayerInfo:GetPlayerInfo(playerID) + if not playerInfoData then + playerInfoData = {} + end + local soundsWheel = playerInfoData.sounds_wheel or ({}) + ____print( + nil, + "[CHAT_WHEEL] Доступные звуки в sounds_wheel: " .. __TS__ArrayJoin( + __TS__ObjectKeys(soundsWheel), + ", " + ) + ) + local soundData = soundsWheel[soundId] + if not soundData then + ____print( + nil, + (("[CHAT_WHEEL] Звук " .. soundId) .. " не найден у игрока ") .. tostring(playerID) + ) + return + end + ____print( + nil, + (((("[CHAT_WHEEL] Найден звук: soundId=" .. soundId) .. ", soundData.sound=") .. tostring(soundData.sound)) .. ", soundData.name=") .. tostring(soundData.name) + ) + if not playerInfoData.chat_wheel then + playerInfoData.chat_wheel = {} + end + local ____temp_24 + if soundData.sound and soundData.sound ~= "" then + ____temp_24 = soundData.sound + else + ____temp_24 = soundId + end + local soundToUse = ____temp_24 + playerInfoData.chat_wheel[tostring(slot)] = {name = soundData.name or soundId, type = "sound", sellPrice = 0, sound = soundToUse} + PlayerInfo:UpdatePlayerInfo(playerID, playerInfoData) + self:saveChatWheelToServer(playerID, playerInfoData.chat_wheel) + ____print( + nil, + (((((((("[CHAT_WHEEL] Игрок " .. tostring(playerID)) .. " установил звук ") .. soundId) .. " в слот ") .. tostring(slot)) .. ", sound=") .. tostring(soundToUse)) .. ", soundData.sound=") .. tostring(soundData.sound) + ) +end +function GameMode.prototype.OnChatWheelBuySound(self, data) + local playerID = data.PlayerID + local soundId = data.sound_id + local soundData = data.sound_data + local storeManager = StoreManager:getInstance() + local fullSoundItemId = "chat_wheel_sound_" .. soundId + if isStorePurchaseBlockedById(nil, fullSoundItemId) then + rawPrintFn(nil, ("[CHAT_WHEEL] Покупка отклонена: " .. fullSoundItemId) .. " не продаётся в магазине") + return + end + if not storeManager:hasPurchasedItem(playerID, fullSoundItemId) then + rawPrintFn( + nil, + (("[CHAT_WHEEL] Покупка отклонена: " .. fullSoundItemId) .. " не найден в кэше покупок игрока ") .. tostring(playerID) + ) + storeManager:sendPurchaseResult(playerID, false, "Сначала купи звук в магазине") + return + end + local playerInfoData = PlayerInfo:GetPlayerInfo(playerID) + if not playerInfoData then + playerInfoData = {} + end + local soundsWheel = playerInfoData.sounds_wheel or ({}) + if soundsWheel[soundId] then + ____print( + nil, + (("[CHAT_WHEEL] Звук " .. soundId) .. " уже куплен игроком ") .. tostring(playerID) + ) + return + end + if not playerInfoData.sounds_wheel then + playerInfoData.sounds_wheel = {} + end + local soundDataAny = soundData + local soundToSave = soundData.sound and soundData.sound ~= "" and soundData.sound or soundId + ____print( + nil, + (((((((("[CHAT_WHEEL] Сохраняем звук: id=" .. soundId) .. ", sound=") .. soundToSave) .. ", soundData.sound=") .. soundData.sound) .. ", video=") .. tostring(soundDataAny.video)) .. ", video_path=") .. tostring(soundDataAny.video_path) + ) + playerInfoData.sounds_wheel[soundId] = { + name = soundData.name, + type = "sound", + sellPrice = 0, + sound = soundToSave, + icon = soundData.icon, + video = soundDataAny.video or soundDataAny.video_path, + video_path = soundDataAny.video_path or soundDataAny.video + } + local savedSound = playerInfoData.sounds_wheel[soundId] + ____print( + nil, + (((((("[CHAT_WHEEL] Звук сохранен в sounds_wheel: name=" .. tostring(savedSound.name)) .. ", sound=") .. tostring(savedSound.sound)) .. ", video=") .. tostring(savedSound.video)) .. ", video_path=") .. tostring(savedSound.video_path) + ) + PlayerInfo:UpdatePlayerInfo(playerID, playerInfoData) + self:saveSoundsWheelToServer(playerID, playerInfoData.sounds_wheel) + storeManager:updateCurrencyDisplay(playerID) + storeManager:saveCurrencyToServer(playerID) + rawPrintFn( + nil, + (("[CHAT_WHEEL] Покупка звука: player=" .. tostring(playerID)) .. ", sound=") .. soundId + ) +end +function GameMode.prototype.saveChatWheelToServer(self, playerId, chatWheel) + local steamId = PlayerResource:GetSteamAccountID(playerId) + if not steamId then + return + end + local request = CreateHTTPRequest( + "PUT", + ((SERVER_CONFIG.API_URL .. "/player/") .. tostring(steamId)) .. "/chat_wheel" + ) + setApiHeaders(nil, request) + local dataToSend = {chat_wheel = chatWheel} + request:SetHTTPRequestRawPostBody( + "application/json", + json.encode(dataToSend) + ) + request:Send(function(result) + if result.StatusCode >= 200 and result.StatusCode < 300 then + ____print( + nil, + "[CHAT_WHEEL] Chat wheel сохранен на сервере для игрока " .. tostring(playerId) + ) + else + ____print( + nil, + "[CHAT_WHEEL] Ошибка сохранения chat_wheel: StatusCode=" .. tostring(result.StatusCode) + ) + end + end) +end +function GameMode.prototype.saveSoundsWheelToServer(self, playerId, soundsWheel) + local steamId = PlayerResource:GetSteamAccountID(playerId) + if not steamId then + return + end + local request = CreateHTTPRequest( + "PUT", + ((SERVER_CONFIG.API_URL .. "/player/") .. tostring(steamId)) .. "/sounds_wheel" + ) + setApiHeaders(nil, request) + local dataToSend = {sounds_wheel = soundsWheel} + request:SetHTTPRequestRawPostBody( + "application/json", + json.encode(dataToSend) + ) + request:Send(function(result) + if result.StatusCode >= 200 and result.StatusCode < 300 then + ____print( + nil, + "[CHAT_WHEEL] Sounds wheel сохранен на сервере для игрока " .. tostring(playerId) + ) + else + ____print( + nil, + "[CHAT_WHEEL] Ошибка сохранения sounds_wheel: StatusCode=" .. tostring(result.StatusCode) + ) + end + end) +end +function GameMode.prototype.loadChatWheelSoundsFromServer(self, playerId) + local steamId = PlayerResource:GetSteamAccountID(playerId) + if not steamId then + return + end + local chatWheelRequest = CreateHTTPRequest( + "GET", + ((SERVER_CONFIG.API_URL .. "/player/") .. tostring(steamId)) .. "/chat_wheel" + ) + setApiHeaders(nil, chatWheelRequest) + chatWheelRequest:Send(function(result) + if result.StatusCode >= 200 and result.StatusCode < 300 then + do + local function ____catch(e) + ____print( + nil, + "[CHAT_WHEEL] Ошибка парсинга chat_wheel: " .. tostring(e) + ) + end + local ____try, ____hasReturned = pcall(function() + local decoded = {json.decode(result.Body)} + local responseData = nil + if __TS__ArrayIsArray(decoded) and #decoded > 0 then + responseData = decoded[1] + elseif decoded and type(decoded) == "table" then + responseData = decoded + end + if responseData and type(responseData) == "table" and responseData.chat_wheel ~= nil then + local loadedChatWheel = responseData.chat_wheel or ({}) + local playerInfoData = PlayerInfo:GetPlayerInfo(playerId) + if not playerInfoData then + playerInfoData = {} + end + playerInfoData.chat_wheel = loadedChatWheel + PlayerInfo:UpdatePlayerInfo(playerId, playerInfoData) + ____print( + nil, + "[CHAT_WHEEL] Загружен chat_wheel для игрока " .. tostring(playerId) + ) + end + end) + if not ____try then + ____catch(____hasReturned) + end + end + else + ____print( + nil, + "[CHAT_WHEEL] Ошибка загрузки chat_wheel: StatusCode=" .. tostring(result.StatusCode) + ) + end + end) + local soundsWheelRequest = CreateHTTPRequest( + "GET", + ((SERVER_CONFIG.API_URL .. "/player/") .. tostring(steamId)) .. "/sounds_wheel" + ) + setApiHeaders(nil, soundsWheelRequest) + soundsWheelRequest:Send(function(result) + if result.StatusCode >= 200 and result.StatusCode < 300 then + do + local function ____catch(e) + ____print( + nil, + "[CHAT_WHEEL] Ошибка парсинга sounds_wheel: " .. tostring(e) + ) + end + local ____try, ____hasReturned = pcall(function() + local decoded = {json.decode(result.Body)} + local responseData = nil + if __TS__ArrayIsArray(decoded) and #decoded > 0 then + responseData = decoded[1] + elseif decoded and type(decoded) == "table" then + responseData = decoded + end + if responseData and type(responseData) == "table" and responseData.sounds_wheel ~= nil then + local loadedSoundsWheel = responseData.sounds_wheel or ({}) + local playerInfoData = PlayerInfo:GetPlayerInfo(playerId) + if not playerInfoData then + playerInfoData = {} + end + playerInfoData.sounds_wheel = loadedSoundsWheel + PlayerInfo:UpdatePlayerInfo(playerId, playerInfoData) + ____print( + nil, + ((("[CHAT_WHEEL] Загружен sounds_wheel для игрока " .. tostring(playerId)) .. ": ") .. tostring(#__TS__ObjectKeys(loadedSoundsWheel))) .. " звуков" + ) + else + local playerInfoData = PlayerInfo:GetPlayerInfo(playerId) + if not playerInfoData then + playerInfoData = {} + end + if not playerInfoData.sounds_wheel then + playerInfoData.sounds_wheel = {} + PlayerInfo:UpdatePlayerInfo(playerId, playerInfoData) + ____print( + nil, + "[CHAT_WHEEL] sounds_wheel не найден на сервере, создан пустой объект для игрока " .. tostring(playerId) + ) + end + end + end) + if not ____try then + ____catch(____hasReturned) + end + end + else + ____print( + nil, + "[CHAT_WHEEL] Ошибка загрузки sounds_wheel: StatusCode=" .. tostring(result.StatusCode) + ) + end + end) +end +GameMode = __TS__Decorate(GameMode, GameMode, {reloadable}, {kind = "class", name = "GameMode"}) +____exports.GameMode = GameMode +return ____exports diff --git a/scripts/vscripts/gameplay/hero_gravestone_respawn.lua b/scripts/vscripts/gameplay/hero_gravestone_respawn.lua new file mode 100644 index 0000000..25ee739 --- /dev/null +++ b/scripts/vscripts/gameplay/hero_gravestone_respawn.lua @@ -0,0 +1,594 @@ +local ____lualib = require("lualib_bundle") +local __TS__Class = ____lualib.__TS__Class +local Map = ____lualib.Map +local __TS__New = ____lualib.__TS__New +local __TS__ClassExtends = ____lualib.__TS__ClassExtends +local __TS__Decorate = ____lualib.__TS__Decorate +local ____exports = {} +local modifier_hero_gravestone_charger +local ____dota_ts_adapter = require("lib.dota_ts_adapter") +local BaseModifier = ____dota_ts_adapter.BaseModifier +local registerModifier = ____dota_ts_adapter.registerModifier +local ____invul_box = require("abilities.invul_box") +local invul_box = ____invul_box.invul_box +local ____game_stats_tracker = require("game_stats_tracker") +local GameStatsTracker = ____game_stats_tracker.GameStatsTracker +local ____player_connection_state = require("utils.player_connection_state") +local DOTA_CONNECTION_STATE = ____player_connection_state.DOTA_CONNECTION_STATE +--- Базовый юнит из npc_units_custom (хитбокс/инвул); визуал задаём моделью надгробия. +local GRAVESTONE_UNIT_NAME = "npc_grave" +local GRAVESTONE_MODEL = "models/items/wraith_king/arcana/wk_arcana_tombstone.vmdl" +--- Как в CutsceneFunctions — targetname живого босса на арене. +local NEVERMORE_BOSS_TARGETNAME = "npc_boss_nevermore" +--- infinity_levels: дроп item_tombstone_respawn + PICKUP_ITEM → BeginChannel(5). +-- Здесь то же время ожидания, но без предмета и без поднятия — только стояние в радиусе (как шрайн/defension). +local GRAVESTONE_RADIUS = 300 +local GRAVESTONE_CAPTURE_TIME = 5 +local INTERVAL = 0.03 +--- Маркер на земле как в infinity_levels (GameMode precache Muerta). +local MUERTA_GRAVE_MARKER_PARTICLE = "particles/econ/items/muerta/muerta_gravemarker_lvl1.vpcf" +--- Последовательность модели надгробия при захвате (тряска). Должна быть в .vmdl (часто у tombstone/Muerta). +local GRAVESTONE_CAPTURE_SEQUENCE = "sk_tombstone_reincarnation_no_reform" +--- FindByName — только targetname в карте / SetEntityName; босс из KV — npc_dota_creature, +-- поэтому дублируем поиск по GetUnitName у живых юнитов в большом радиусе от точки арены. +local function findLiveNevermoreBoss(self) + local byTargetName = Entities:FindByName(nil, NEVERMORE_BOSS_TARGETNAME) + if byTargetName ~= nil and IsValidEntity(byTargetName) and byTargetName:IsAlive() then + return byTargetName + end + local searchOrigin = Vector(0, 0, 0) + local spawnPt = Entities:FindByName(nil, "point_boss_spawn_point") + if spawnPt ~= nil then + searchOrigin = spawnPt:GetAbsOrigin() + end + local units = FindUnitsInRadius( + DOTA_TEAM_NEUTRALS, + searchOrigin, + nil, + 25000, + DOTA_UNIT_TARGET_TEAM_BOTH, + DOTA_UNIT_TARGET_ALL, + DOTA_UNIT_TARGET_FLAG_NONE, + FIND_ANY_ORDER, + false + ) + do + local i = 0 + while i < #units do + local u = units[i + 1] + if u ~= nil and IsValidEntity(u) and u:IsAlive() and u:GetUnitName() == NEVERMORE_BOSS_TARGETNAME then + return u + end + i = i + 1 + end + end + return nil +end +local function isNevermoreBossFightActive(self) + return findLiveNevermoreBoss(nil) ~= nil +end +--- Radiant, сейчас реально в матче (CONNECTED). Ливнувших/дропнутых не считаем. +local function isGoodGuysPlayerCountedForGravestoneRules(self, playerId) + if not PlayerResource:IsValidPlayerID(playerId) or not PlayerResource:IsValidPlayer(playerId) then + return false + end + if PlayerResource:GetTeam(playerId) ~= DOTA_TEAM_GOODGUYS then + return false + end + return PlayerResource:GetConnectionState(playerId) == DOTA_CONNECTION_STATE.CONNECTED +end +local function countGoodGuysPlayersInMatch(self) + local n = 0 + do + local i = 0 + while i < DOTA_MAX_PLAYERS do + local pid = i + if isGoodGuysPlayerCountedForGravestoneRules(nil, pid) then + n = n + 1 + end + i = i + 1 + end + end + return n +end +--- Сколько героев Radiant из учитываемых слотов реально живы (истина для проверки вайпа). +local function countAliveGoodGuysHeroesInMatch(self) + local n = 0 + do + local i = 0 + while i < DOTA_MAX_PLAYERS do + do + local pid = i + if not isGoodGuysPlayerCountedForGravestoneRules(nil, pid) then + goto __continue15 + end + local hero = PlayerResource:GetSelectedHeroEntity(pid) + if not hero or not IsValidEntity(hero) then + goto __continue15 + end + if not hero:IsHero() or not hero:IsRealHero() or hero:IsIllusion() then + goto __continue15 + end + if hero:GetTeamNumber() ~= DOTA_TEAM_GOODGUYS then + goto __continue15 + end + if hero:IsAlive() then + n = n + 1 + end + end + ::__continue15:: + i = i + 1 + end + end + return n +end +--- Можно ли вообще поднимать героя респавном (надгробие не ставим и не завершаем заряд, если нет). +local function isHeroEligibleForGravestoneRespawn(self, hero) + if hero:WillReincarnate() then + return false + end + if hero:IsReincarnating() then + return false + end + return true +end +function ____exports.precacheHeroGravestoneParticles(self, context) + PrecacheResource("model", GRAVESTONE_MODEL, context) + PrecacheResource("particle", MUERTA_GRAVE_MARKER_PARTICLE, context) + PrecacheResource("particle", "particles/shrine/capture_point_ring_overthrow.vpcf", context) + PrecacheResource("particle", "particles/shrine/capture_point_ring_clock_overthrow.vpcf", context) + PrecacheResource("particle", "particles/units/heroes/hero_skeletonking/wraith_king_reincarnate.vpcf", context) +end +--- Надгробие: живой союзник стоит в зоне — копится прогресс (не клик по предмету, как в infinity_levels). +____exports.HeroGravestoneRespawn = __TS__Class() +local HeroGravestoneRespawn = ____exports.HeroGravestoneRespawn +HeroGravestoneRespawn.name = "HeroGravestoneRespawn" +HeroGravestoneRespawn.____file_path = "scripts/vscripts/gameplay/hero_gravestone_respawn.lua" +function HeroGravestoneRespawn.prototype.____constructor(self) +end +function HeroGravestoneRespawn.initialize(self) + ListenToGameEvent( + "entity_killed", + function(event) return self:onEntityKilled(event) end, + nil + ) + ListenToGameEvent( + "dota_player_spawned", + function(event) return self:onPlayerSpawned(event) end, + nil + ) +end +function HeroGravestoneRespawn.registerGravestone(self, playerId, entIndex) + local prev = self.graveEntIndexByPlayer:get(playerId) + if prev ~= nil and prev ~= entIndex then + local old = EntIndexToHScript(prev) + if old and IsValidEntity(old) then + UTIL_Remove(old) + end + end + self.graveEntIndexByPlayer:set(playerId, entIndex) +end +function HeroGravestoneRespawn.unregisterGravestone(self, playerId) + self.graveEntIndexByPlayer:delete(playerId) +end +function HeroGravestoneRespawn.hasGravestoneForPlayer(self, playerId) + return self.graveEntIndexByPlayer:has(playerId) +end +function HeroGravestoneRespawn.removeAllGravestones(self) + self.graveEntIndexByPlayer:forEach(function(____, entIndex) + local u = EntIndexToHScript(entIndex) + if u and IsValidEntity(u) then + UTIL_Remove(u) + end + end) + self.graveEntIndexByPlayer:clear() +end +function HeroGravestoneRespawn.pruneGravestonesForPlayersOutOfMatch(self) + local toRemove = {} + self.graveEntIndexByPlayer:forEach(function(____, _entIndex, playerId) + if not isGoodGuysPlayerCountedForGravestoneRules(nil, playerId) then + toRemove[#toRemove + 1] = playerId + end + end) + for ____, pid in ipairs(toRemove) do + local entIndex = self.graveEntIndexByPlayer:get(pid) + self:unregisterGravestone(pid) + if entIndex ~= nil then + local u = EntIndexToHScript(entIndex) + if u and IsValidEntity(u) then + UTIL_Remove(u) + end + end + end +end +function HeroGravestoneRespawn.maybePruneGravestonesForPlayersOutOfMatch(self) + local t = GameRules:GetGameTime() + if t - self.lastOutOfMatchPruneGameTime < 1 then + return + end + self.lastOutOfMatchPruneGameTime = t + self:pruneGravestonesForPlayersOutOfMatch() +end +function HeroGravestoneRespawn.onPlayerSpawned(self, event) + local entIndex = self.graveEntIndexByPlayer:get(event.PlayerID) + if entIndex == nil then + return + end + self.graveEntIndexByPlayer:delete(event.PlayerID) + local unit = EntIndexToHScript(entIndex) + if unit and IsValidEntity(unit) then + UTIL_Remove(unit) + end +end +function HeroGravestoneRespawn.onEntityKilled(self, event) + if not IsServer() then + return + end + if GameRules:State_Get() ~= DOTA_GAMERULES_STATE_GAME_IN_PROGRESS then + return + end + local killed = EntIndexToHScript(event.entindex_killed) + if not killed or not IsValidEntity(killed) then + return + end + if killed:GetUnitName() == NEVERMORE_BOSS_TARGETNAME then + self:removeAllGravestones() + return + end + if not killed:IsHero() then + return + end + if not killed:IsRealHero() then + return + end + if killed:IsIllusion() then + return + end + local playerId = killed:GetPlayerOwnerID() + if playerId < 0 or not PlayerResource:IsValidPlayerID(playerId) then + return + end + if killed:GetTeamNumber() ~= DOTA_TEAM_GOODGUYS then + return + end + local heroDead = killed + local cutscene = GameRules.CutsceneManager + if cutscene ~= nil and cutscene:isCutsceneActive() then + return + end + if not isNevermoreBossFightActive(nil) then + return + end + local origin = killed:GetAbsOrigin() + local ground = GetGroundPosition(origin, killed) + local grave = CreateUnitByName( + GRAVESTONE_UNIT_NAME, + ground, + true, + nil, + nil, + DOTA_TEAM_NEUTRALS + ) + if not grave or not IsValidEntity(grave) then + return + end + FindClearSpaceForUnit(grave, ground, true) + grave:AddAbility(invul_box.name) + grave:SetModel(GRAVESTONE_MODEL) + grave:SetUnitName(GRAVESTONE_UNIT_NAME) + grave:SetEntityName(GRAVESTONE_UNIT_NAME) + grave:AddNewModifier( + grave, + getModifierSourceAbility(nil, grave), + modifier_hero_gravestone_charger.name, + {ownerPlayerId = playerId} + ) + self:registerGravestone( + playerId, + grave:entindex() + ) + self:pruneGravestonesForPlayersOutOfMatch() + self:pruneGravestonesForAliveHeroes() + local players = countGoodGuysPlayersInMatch(nil) + if players > 0 and self.graveEntIndexByPlayer.size == players and countAliveGoodGuysHeroesInMatch(nil) == 0 then + GameStatsTracker:getInstance():onDefeat(function() + GameRules:SetGameWinner(DOTA_TEAM_BADGUYS) + end) + end +end +function HeroGravestoneRespawn.pruneGravestonesForAliveHeroes(self) + local toRemove = {} + self.graveEntIndexByPlayer:forEach(function(____, _entIndex, playerId) + local hero = PlayerResource:GetSelectedHeroEntity(playerId) + if hero and IsValidEntity(hero) and hero:IsHero() and hero:IsRealHero() and not hero:IsIllusion() and hero:IsAlive() then + toRemove[#toRemove + 1] = playerId + end + end) + for ____, pid in ipairs(toRemove) do + local entIndex = self.graveEntIndexByPlayer:get(pid) + self:unregisterGravestone(pid) + if entIndex ~= nil then + local u = EntIndexToHScript(entIndex) + if u and IsValidEntity(u) then + UTIL_Remove(u) + end + end + end +end +HeroGravestoneRespawn.graveEntIndexByPlayer = __TS__New(Map) +HeroGravestoneRespawn.lastOutOfMatchPruneGameTime = -1000000000 +modifier_hero_gravestone_charger = __TS__Class() +modifier_hero_gravestone_charger.name = "modifier_hero_gravestone_charger" +modifier_hero_gravestone_charger.____file_path = "scripts/vscripts/gameplay/hero_gravestone_respawn.lua" +__TS__ClassExtends(modifier_hero_gravestone_charger, BaseModifier) +function modifier_hero_gravestone_charger.prototype.____constructor(self, ...) + BaseModifier.prototype.____constructor(self, ...) + self.vPosition = Vector(0, 0, 0) + self.progress = 0 + self.timer = 0 + self.captureSequenceActive = false +end +function modifier_hero_gravestone_charger.prototype.IsHidden(self) + return true +end +function modifier_hero_gravestone_charger.prototype.IsPurgable(self) + return false +end +function modifier_hero_gravestone_charger.prototype.OnCreated(self, params) + if not IsServer() then + return + end + self.ownerPlayerId = params and params.ownerPlayerId or -1 + self.vPosition = self:GetParent():GetAbsOrigin() + self:StartIntervalThink(INTERVAL) + self.vision = AddFOWViewer( + DOTA_TEAM_GOODGUYS, + self.vPosition, + GRAVESTONE_RADIUS, + 99999, + true + ) + local parent = self:GetParent() + self.muertaAmbient = ParticleManager:CreateParticle(MUERTA_GRAVE_MARKER_PARTICLE, PATTACH_ABSORIGIN_FOLLOW, parent) + ParticleManager:SetParticleControl( + self.muertaAmbient, + 0, + parent:GetAbsOrigin() + ) +end +function modifier_hero_gravestone_charger.prototype.OnDestroy(self) + if not IsServer() then + return + end + if self.vision ~= nil then + RemoveFOWViewer(DOTA_TEAM_GOODGUYS, self.vision) + end + if self.muertaAmbient ~= nil then + ParticleManager:DestroyParticle(self.muertaAmbient, false) + ParticleManager:ReleaseParticleIndex(self.muertaAmbient) + end + if self.baseParticle ~= nil then + ParticleManager:DestroyParticle(self.baseParticle, false) + ParticleManager:ReleaseParticleIndex(self.baseParticle) + end + if self.clockParticle ~= nil then + ParticleManager:DestroyParticle(self.clockParticle, true) + ParticleManager:ReleaseParticleIndex(self.clockParticle) + end +end +function modifier_hero_gravestone_charger.prototype.checkCapturePointParticles(self) + local parent = self:GetParent() + if not self.baseParticle then + self.baseParticle = ParticleManager:CreateParticle("particles/shrine/capture_point_ring_overthrow.vpcf", PATTACH_WORLDORIGIN, parent) + ParticleManager:SetParticleControl( + self.baseParticle, + 0, + parent:GetAbsOrigin() + ) + ParticleManager:SetParticleControl( + self.baseParticle, + 3, + Vector(150, 150, 150) + ) + ParticleManager:SetParticleControl( + self.baseParticle, + 9, + Vector(GRAVESTONE_RADIUS, 0, 0) + ) + else + ParticleManager:SetParticleControl( + self.baseParticle, + 0, + parent:GetAbsOrigin() + ) + end + if self.progress <= 0 or self.progress >= 1 then + if self.clockParticle ~= nil then + ParticleManager:DestroyParticle(self.clockParticle, true) + ParticleManager:ReleaseParticleIndex(self.clockParticle) + self.clockParticle = nil + end + return + end + local team = DOTA_TEAM_GOODGUYS + local origin = parent:GetAbsOrigin() + local z = origin.z + 75 + if not self.clockParticle then + self.clockParticle = ParticleManager:CreateParticleForTeam("particles/shrine/capture_point_ring_clock_overthrow.vpcf", PATTACH_WORLDORIGIN, parent, team) + ParticleManager:SetParticleControl( + self.clockParticle, + 0, + Vector(origin.x, origin.y, z) + ) + ParticleManager:SetParticleControl( + self.clockParticle, + 11, + Vector(0, 0, 1) + ) + end + ParticleManager:SetParticleControl( + self.clockParticle, + 3, + Vector(220, 220, 220) + ) + ParticleManager:SetParticleControl( + self.clockParticle, + 9, + Vector(GRAVESTONE_RADIUS, 0, 0) + ) + ParticleManager:SetParticleControl( + self.clockParticle, + 17, + Vector(self.progress, 0, 0) + ) + ParticleManager:SetParticleControl( + self.clockParticle, + 0, + Vector(origin.x, origin.y, z) + ) +end +function modifier_hero_gravestone_charger.prototype.updateCaptureModelAnimation(self, parent, isCapturing) + if isCapturing then + if not self.captureSequenceActive then + parent:SetSequence(GRAVESTONE_CAPTURE_SEQUENCE) + self.captureSequenceActive = true + elseif parent:IsSequenceFinished() then + parent:ResetSequence(GRAVESTONE_CAPTURE_SEQUENCE) + end + else + if self.captureSequenceActive then + parent:StopAnimation() + self.captureSequenceActive = false + end + end +end +function modifier_hero_gravestone_charger.prototype.OnIntervalThink(self) + if not IsServer() then + return + end + ____exports.HeroGravestoneRespawn:maybePruneGravestonesForPlayersOutOfMatch() + local parent = self:GetParent() + if not parent or not IsValidEntity(parent) then + return + end + if not isNevermoreBossFightActive(nil) then + ____exports.HeroGravestoneRespawn:unregisterGravestone(self.ownerPlayerId) + UTIL_Remove(parent) + return + end + local boundHero = PlayerResource:GetSelectedHeroEntity(self.ownerPlayerId) + if boundHero and IsValidEntity(boundHero) and not boundHero:IsAlive() and not isHeroEligibleForGravestoneRespawn(nil, boundHero) then + ____exports.HeroGravestoneRespawn:unregisterGravestone(self.ownerPlayerId) + UTIL_Remove(parent) + return + end + self.vPosition = parent:GetAbsOrigin() + local heroesInRadius = FindUnitsInRadius( + DOTA_TEAM_NEUTRALS, + self.vPosition, + nil, + GRAVESTONE_RADIUS, + DOTA_UNIT_TARGET_TEAM_ENEMY, + DOTA_UNIT_TARGET_HERO, + bit.bor( + bit.bor( + bit.bor( + bit.bor(DOTA_UNIT_TARGET_FLAG_NOT_CREEP_HERO, DOTA_UNIT_TARGET_FLAG_MAGIC_IMMUNE_ENEMIES), + DOTA_UNIT_TARGET_FLAG_INVULNERABLE + ), + DOTA_UNIT_TARGET_FLAG_NOT_ILLUSIONS + ), + DOTA_UNIT_TARGET_FLAG_OUT_OF_WORLD + ), + FIND_ANY_ORDER, + false + ) + local heroes = {} + local teamRegister = {} + for ____, unit in ipairs(heroesInRadius) do + if unit:IsHero() and unit:IsAlive() and unit:IsRealHero() then + local team = unit:GetTeamNumber() + if not teamRegister[team] then + teamRegister[team] = true + heroes[#heroes + 1] = unit + end + end + end + if #heroes == 1 then + self.timer = self.timer + INTERVAL + self.progress = self.timer / GRAVESTONE_CAPTURE_TIME + if self.progress >= 1 then + self:completeResurrection() + return + end + else + self.timer = math.max(0, self.timer - INTERVAL) + self.progress = self.timer / GRAVESTONE_CAPTURE_TIME + end + local isCapturing = #heroes == 1 and self.progress > 0 and self.progress < 1 + self:updateCaptureModelAnimation(parent, isCapturing) + self:checkCapturePointParticles() +end +function modifier_hero_gravestone_charger.prototype.completeResurrection(self) + local parent = self:GetParent() + if not parent or not IsValidEntity(parent) then + return + end + if not isNevermoreBossFightActive(nil) then + ____exports.HeroGravestoneRespawn:unregisterGravestone(self.ownerPlayerId) + UTIL_Remove(parent) + return + end + local hero = PlayerResource:GetSelectedHeroEntity(self.ownerPlayerId) + if not hero or not IsValidEntity(hero) then + ____exports.HeroGravestoneRespawn:unregisterGravestone(self.ownerPlayerId) + UTIL_Remove(parent) + return + end + if hero:IsAlive() then + ____exports.HeroGravestoneRespawn:unregisterGravestone(self.ownerPlayerId) + UTIL_Remove(parent) + return + end + if not isHeroEligibleForGravestoneRespawn(nil, hero) then + ____exports.HeroGravestoneRespawn:unregisterGravestone(self.ownerPlayerId) + UTIL_Remove(parent) + return + end + local pos = parent:GetAbsOrigin() + hero:SetRespawnPosition(pos) + do + local function ____catch() + ____exports.HeroGravestoneRespawn:unregisterGravestone(self.ownerPlayerId) + UTIL_Remove(parent) + return true + end + local ____try, ____hasReturned, ____returnValue = pcall(function() + hero:RespawnHero(false, false) + end) + if not ____try then + ____hasReturned, ____returnValue = ____catch() + end + if ____hasReturned then + return ____returnValue + end + end + FindClearSpaceForUnit(hero, pos, true) + local pfx = ParticleManager:CreateParticle("particles/units/heroes/hero_skeletonking/wraith_king_reincarnate.vpcf", PATTACH_ABSORIGIN_FOLLOW, hero) + ParticleManager:SetParticleControl( + pfx, + 0, + hero:GetAbsOrigin() + ) + ParticleManager:ReleaseParticleIndex(pfx) + parent:EmitSound("Outpost.Captured") + ____exports.HeroGravestoneRespawn:unregisterGravestone(self.ownerPlayerId) + UTIL_Remove(parent) +end +modifier_hero_gravestone_charger = __TS__Decorate( + modifier_hero_gravestone_charger, + modifier_hero_gravestone_charger, + {registerModifier(nil)}, + {kind = "class", name = "modifier_hero_gravestone_charger"} +) +return ____exports diff --git a/scripts/vscripts/herocosmeticmanager.lua b/scripts/vscripts/herocosmeticmanager.lua new file mode 100644 index 0000000..e4d3182 --- /dev/null +++ b/scripts/vscripts/herocosmeticmanager.lua @@ -0,0 +1,190 @@ +local ____lualib = require("lualib_bundle") +local __TS__Class = ____lualib.__TS__Class +local Map = ____lualib.Map +local __TS__New = ____lualib.__TS__New +local __TS__ObjectKeys = ____lualib.__TS__ObjectKeys +local __TS__ObjectEntries = ____lualib.__TS__ObjectEntries +local __TS__ArrayFrom = ____lualib.__TS__ArrayFrom +local ____exports = {} +local ____modifier_donate_item = require("abilities.modifiers.modifier_donate_item") +local modifier_donate_item = ____modifier_donate_item.modifier_donate_item +--- Менеджер для автоматического надевания косметики (моделек) на кастомных героев при спавне +-- Использует подход с созданием отдельных юнитов и прикреплением их к герою +____exports.HeroCosmeticManager = __TS__Class() +local HeroCosmeticManager = ____exports.HeroCosmeticManager +HeroCosmeticManager.name = "HeroCosmeticManager" +HeroCosmeticManager.____file_path = "scripts/vscripts/HeroCosmeticManager.lua" +function HeroCosmeticManager.prototype.____constructor(self) + self.heroCosmeticsConfig = __TS__New(Map) + self.heroWearables = __TS__New(Map) +end +function HeroCosmeticManager.getInstance(self) + if not ____exports.HeroCosmeticManager.instance then + ____exports.HeroCosmeticManager.instance = __TS__New(____exports.HeroCosmeticManager) + end + return ____exports.HeroCosmeticManager.instance +end +function HeroCosmeticManager.prototype.registerHeroCosmetics(self, heroName, config) + print("[HeroCosmeticManager] Регистрация косметики для героя: " .. heroName) + if config.model then + print("[HeroCosmeticManager] - Основная модель: " .. config.model) + end + if config.slots then + local slotNames = __TS__ObjectKeys(config.slots) + print((("[HeroCosmeticManager] - Слоты косметики (" .. tostring(#slotNames)) .. "): ") .. table.concat(slotNames, ", ")) + for ____, ____value in ipairs(__TS__ObjectEntries(config.slots)) do + local slot = ____value[1] + local model = ____value[2] + if model then + print((("[HeroCosmeticManager] * " .. slot) .. ": ") .. model) + end + end + end + if config.scale ~= nil then + print("[HeroCosmeticManager] - Масштаб: " .. tostring(config.scale)) + end + self.heroCosmeticsConfig:set(heroName, config) + print("[HeroCosmeticManager] ✅ Конфигурация зарегистрирована для " .. heroName) +end +function HeroCosmeticManager.prototype.unregisterHeroCosmetics(self, heroName) + self.heroCosmeticsConfig:delete(heroName) +end +function HeroCosmeticManager.prototype.onHeroSpawn(self, hero) + if not hero or not hero:IsRealHero() then + print("[HeroCosmeticManager] Пропуск: герой не является реальным героем") + return + end + local heroName = hero:GetUnitName() + print("[HeroCosmeticManager] Обработка спавна героя: " .. heroName) + local config = self.heroCosmeticsConfig:get(heroName) + if not config then + print("[HeroCosmeticManager] Конфигурация не найдена для героя: " .. heroName) + return + end + print(("[HeroCosmeticManager] Найдена конфигурация для " .. heroName) .. ", начинаем надевание косметики...") + Timers:CreateTimer( + 0.2, + function() + if not IsValidEntity(hero) or not hero:IsAlive() then + print(("[HeroCosmeticManager] Герой " .. heroName) .. " больше не валиден или мертв, отмена") + return + end + if config.model then + print((("[HeroCosmeticManager] Устанавливаем основную модель для " .. heroName) .. ": ") .. config.model) + hero:SetModel(config.model) + hero:SetOriginalModel(config.model) + end + local heroId = hero:GetEntityIndex() + if not self.heroWearables:has(heroId) then + self.heroWearables:set(heroId, {}) + end + if config.slots then + local slotCount = #__TS__ObjectKeys(config.slots) + print((("[HeroCosmeticManager] Начинаем надевание " .. tostring(slotCount)) .. " элементов косметики для ") .. heroName) + for ____, ____value in ipairs(__TS__ObjectEntries(config.slots)) do + local attachmentPoint = ____value[1] + local modelPath = ____value[2] + if modelPath then + print((("[HeroCosmeticManager] Надеваем модель на attachment point \"" .. attachmentPoint) .. "\": ") .. modelPath) + self:WearModel(hero, modelPath, attachmentPoint) + else + print(("[HeroCosmeticManager] Пропуск слота \"" .. attachmentPoint) .. "\" - модель не указана") + end + end + print("[HeroCosmeticManager] Завершено надевание косметики для " .. heroName) + else + print("[HeroCosmeticManager] Слоты косметики не указаны для " .. heroName) + end + if config.scale ~= nil then + print((("[HeroCosmeticManager] Устанавливаем масштаб модели для " .. heroName) .. ": ") .. tostring(config.scale)) + hero:SetModelScale(config.scale) + end + end + ) +end +function HeroCosmeticManager.prototype.getHeroCosmetics(self, heroName) + return self.heroCosmeticsConfig:get(heroName) +end +function HeroCosmeticManager.prototype.clearAll(self) + self.heroCosmeticsConfig:clear() +end +function HeroCosmeticManager.prototype.getRegisteredHeroes(self) + return __TS__ArrayFrom(self.heroCosmeticsConfig:keys()) +end +function HeroCosmeticManager.prototype.WearModel(self, hero, modelPath, attachmentPoint) + do + local function ____catch(e) + print((((("[HeroCosmeticManager] ❌ КРИТИЧЕСКАЯ ОШИБКА при надевании модели " .. modelPath) .. " на attachment point ") .. attachmentPoint) .. ": ") .. tostring(e)) + end + local ____try, ____hasReturned, ____returnValue = pcall(function() + print(((("[HeroCosmeticManager] WearModel: создаем wearable для " .. modelPath) .. " на attachment point \"") .. attachmentPoint) .. "\"") + local wearable = CreateUnitByName( + "npc_dota_donate_item", + Vector(0, 0, 0), + false, + nil, + nil, + hero:GetTeamNumber() + ) + if not wearable then + print("[HeroCosmeticManager] ❌ ОШИБКА: Не удалось создать wearable для модели " .. modelPath) + return true + end + print("[HeroCosmeticManager] ✅ Wearable создан успешно, EntityIndex: " .. tostring(wearable:GetEntityIndex())) + if IsServer() then + wearable:SetRenderColor(255, 255, 255) + end + print("[HeroCosmeticManager] Цвет рендеринга установлен") + wearable:AddNewModifier( + hero, + getModifierSourceAbility(nil, hero), + modifier_donate_item.name, + {} + ) + print("[HeroCosmeticManager] Модификатор modifier_donate_item добавлен") + wearable:SetModel(modelPath) + wearable:SetOriginalModel(modelPath) + print("[HeroCosmeticManager] Модель установлена: " .. modelPath) + wearable:SetTeam(hero:GetTeamNumber()) + wearable:SetOwner(hero) + print(("[HeroCosmeticManager] Команда и владелец установлены (Team: " .. tostring(hero:GetTeamNumber())) .. ")") + wearable:SetParent(hero, attachmentPoint) + wearable:FollowEntity(hero, true) + print(("[HeroCosmeticManager] ✅ Wearable прикреплен к attachment point \"" .. attachmentPoint) .. "\"") + wearable:StartGesture(ACT_DOTA_IDLE) + print("[HeroCosmeticManager] Анимация установлена: DOTA_IDLE") + local heroId = hero:GetEntityIndex() + local wearables = self.heroWearables:get(heroId) or ({}) + wearables[#wearables + 1] = wearable + self.heroWearables:set(heroId, wearables) + print(("[HeroCosmeticManager] Wearable сохранен в список (всего wearables для героя: " .. tostring(#wearables)) .. ")") + end) + if not ____try then + ____hasReturned, ____returnValue = ____catch(____hasReturned) + end + if ____hasReturned then + return ____returnValue + end + end +end +function HeroCosmeticManager.prototype.RemoveAllCosmetics(self, hero) + local heroId = hero:GetEntityIndex() + local heroName = hero:GetUnitName() + local wearables = self.heroWearables:get(heroId) + if wearables then + print(((("[HeroCosmeticManager] Удаление косметики с героя " .. heroName) .. " (") .. tostring(#wearables)) .. " элементов)") + for ____, wearable in ipairs(wearables) do + if IsValidEntity(wearable) then + UTIL_Remove(wearable) + print(("[HeroCosmeticManager] Wearable удален (EntityIndex: " .. tostring(wearable:GetEntityIndex())) .. ")") + else + print("[HeroCosmeticManager] Wearable уже не валиден, пропуск") + end + end + self.heroWearables:delete(heroId) + print("[HeroCosmeticManager] ✅ Вся косметика удалена с " .. heroName) + else + print(("[HeroCosmeticManager] У героя " .. heroName) .. " нет косметики для удаления") + end +end +return ____exports diff --git a/scripts/vscripts/item_detector.lua b/scripts/vscripts/item_detector.lua new file mode 100644 index 0000000..8451d1a --- /dev/null +++ b/scripts/vscripts/item_detector.lua @@ -0,0 +1,217 @@ +local ____lualib = require("lualib_bundle") +local __TS__Class = ____lualib.__TS__Class +local Map = ____lualib.Map +local __TS__New = ____lualib.__TS__New +local __TS__StringIncludes = ____lualib.__TS__StringIncludes +local __TS__Iterator = ____lualib.__TS__Iterator +local ____exports = {} +local ____entity_radius = require("utils.entity_radius") +local findAllByClassnameInRadius = ____entity_radius.findAllByClassnameInRadius +____exports.ItemDetector = __TS__Class() +local ItemDetector = ____exports.ItemDetector +ItemDetector.name = "ItemDetector" +ItemDetector.____file_path = "scripts/vscripts/item_detector.lua" +function ItemDetector.prototype.____constructor(self) + self.lastKnownItems = __TS__New(Map) + self.lastKnownItems = __TS__New(Map) + self:InitializeItemDetection() + ListenToGameEvent( + "entity_killed", + function(event) return self:OnItemPickup(event) end, + nil + ) +end +function ItemDetector.Initialize(self) + if not ____exports.ItemDetector.instance then + ____exports.ItemDetector.instance = __TS__New(____exports.ItemDetector) + end +end +function ItemDetector.prototype.InitializeItemDetection(self) + GameRules:GetGameModeEntity():SetThink( + function() + self:ScanForNearbyItems() + return 0.25 + end, + "ScanForNearbyItemsThink", + "0", + nil + ) +end +function ItemDetector.prototype.snapshotFromUpdate(self, u) + return { + distanceRounded = math.floor(u.distance + 0.5), + cost = u.cost, + currencyType = u.currencyType, + rarity = u.rarity + } +end +function ItemDetector.prototype.snapshotsEqual(self, a, b) + return a.distanceRounded == b.distanceRounded and a.cost == b.cost and a.currencyType == b.currencyType and a.rarity == b.rarity +end +function ItemDetector.prototype.tryBuildNearbyUpdate(self, heroPos, itemEntity, detectionRadius) + local containedItem = itemEntity:GetContainedItem() + if containedItem == nil or containedItem == nil then + return nil + end + local itemName = containedItem:GetAbilityName() + local itemPos = itemEntity:GetAbsOrigin() + local distance = (heroPos - itemPos):Length2D() + if distance > detectionRadius then + return nil + end + local itemCost = containedItem:GetCost() + local currencyType + local rarity + if __TS__StringIncludes(itemName, "item_blackshop_") then + local blackShop = require("blackshop").BlackShop:getInstance() + local shopItem = blackShop:getItemInfo(itemName) + if shopItem then + itemCost = shopItem.cost + currencyType = "crystal" + repeat + local ____switch15 = shopItem.quality + local ____cond15 = ____switch15 == 0 + if ____cond15 then + rarity = "common" + break + end + ____cond15 = ____cond15 or ____switch15 == 1 + if ____cond15 then + rarity = "rare" + break + end + ____cond15 = ____cond15 or ____switch15 == 2 + if ____cond15 then + rarity = "epic" + break + end + ____cond15 = ____cond15 or ____switch15 == 3 + if ____cond15 then + rarity = "legendary" + break + end + ____cond15 = ____cond15 or ____switch15 == 4 + if ____cond15 then + rarity = "cursed" + break + end + ____cond15 = ____cond15 or ____switch15 == 5 + if ____cond15 then + rarity = "heavenly" + break + end + do + rarity = "common" + break + end + until true + end + else + currencyType = "gold" + end + return { + itemName = itemName, + distance = distance, + cost = itemCost, + currencyType = currencyType, + rarity = rarity + } +end +function ItemDetector.prototype.ScanForNearbyItems(self) + local heroes = HeroList:GetAllHeroes() + local detectionRadius = 400 + for ____, hero in ipairs(heroes) do + do + if not IsValidEntity(hero) or not hero:IsRealHero() then + goto __continue18 + end + local heroPos = hero:GetAbsOrigin() + local playerID = hero:GetPlayerOwnerID() + if playerID == -1 then + goto __continue18 + end + local player = PlayerResource:GetPlayer(playerID) + if not player then + goto __continue18 + end + local nearbyItems = findAllByClassnameInRadius("dota_item_drop", heroPos, detectionRadius) + local currentMap = __TS__New(Map) + for ____, item in ipairs(nearbyItems) do + do + if not IsValidEntity(item) then + goto __continue22 + end + local itemEntity = item + if not itemEntity then + goto __continue22 + end + local update = self:tryBuildNearbyUpdate(heroPos, itemEntity, detectionRadius) + if update then + currentMap:set(update.itemName, update) + end + end + ::__continue22:: + end + local lastMap = self.lastKnownItems:get(playerID) or __TS__New(Map) + for ____, ____value in __TS__Iterator(lastMap) do + local name = ____value[1] + if not currentMap:has(name) then + CustomGameEventManager:Send_ServerToPlayer(player, "nearby_item_remove", {itemName = name}) + end + end + for ____, ____value in __TS__Iterator(currentMap) do + local name = ____value[1] + local update = ____value[2] + local nextSnap = self:snapshotFromUpdate(update) + local prev = lastMap:get(name) + if prev == nil then + CustomGameEventManager:Send_ServerToPlayer(player, "nearby_item_update", update) + elseif not self:snapshotsEqual(prev, nextSnap) then + CustomGameEventManager:Send_ServerToPlayer(player, "nearby_item_update", update) + end + end + local nextKnown = __TS__New(Map) + for ____, ____value in __TS__Iterator(currentMap) do + local u = ____value[2] + nextKnown:set( + u.itemName, + self:snapshotFromUpdate(u) + ) + end + self.lastKnownItems:set(playerID, nextKnown) + end + ::__continue18:: + end +end +function ItemDetector.prototype.OnItemPickup(self, event) + local killedUnit = EntIndexToHScript(event.entindex_killed) + if killedUnit and killedUnit:GetClassname() == "dota_item_drop" then + local itemEntity = killedUnit + local containedItem = itemEntity:GetContainedItem() + if containedItem ~= nil and containedItem ~= nil then + local itemName = containedItem:GetAbilityName() + for ____, ____value in __TS__Iterator(self.lastKnownItems) do + local playerID = ____value[1] + local map = ____value[2] + do + if not map:has(itemName) then + goto __continue40 + end + map:delete(itemName) + local player = PlayerResource:GetPlayer(playerID) + if player then + CustomGameEventManager:Send_ServerToPlayer(player, "nearby_item_remove", {itemName = itemName}) + end + end + ::__continue40:: + end + end + end +end +function ItemDetector.getInstance(self) + if not ____exports.ItemDetector.instance then + ____exports.ItemDetector.instance = __TS__New(____exports.ItemDetector) + end + return ____exports.ItemDetector.instance +end +return ____exports diff --git a/scripts/vscripts/item_drops.lua b/scripts/vscripts/item_drops.lua new file mode 100644 index 0000000..817c08d --- /dev/null +++ b/scripts/vscripts/item_drops.lua @@ -0,0 +1,384 @@ +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 diff --git a/scripts/vscripts/items/blackshop/common/item_blackshop_common_blue_tallow.lua b/scripts/vscripts/items/blackshop/common/item_blackshop_common_blue_tallow.lua new file mode 100644 index 0000000..8bc3480 --- /dev/null +++ b/scripts/vscripts/items/blackshop/common/item_blackshop_common_blue_tallow.lua @@ -0,0 +1,71 @@ +local ____lualib = require("lualib_bundle") +local __TS__Class = ____lualib.__TS__Class +local __TS__ClassExtends = ____lualib.__TS__ClassExtends +local __TS__Decorate = ____lualib.__TS__Decorate +local ____exports = {} +local ____dota_ts_adapter = require("lib.dota_ts_adapter") +local BaseItem = ____dota_ts_adapter.BaseItem +local BaseModifier = ____dota_ts_adapter.BaseModifier +local registerAbility = ____dota_ts_adapter.registerAbility +local registerModifier = ____dota_ts_adapter.registerModifier +--- Синий воск — бонус к восстановлению маны (стакается). +____exports.item_blackshop_common_blue_tallow = __TS__Class() +local item_blackshop_common_blue_tallow = ____exports.item_blackshop_common_blue_tallow +item_blackshop_common_blue_tallow.name = "item_blackshop_common_blue_tallow" +item_blackshop_common_blue_tallow.____file_path = "scripts/vscripts/items/blackshop/common/item_blackshop_common_blue_tallow.lua" +__TS__ClassExtends(item_blackshop_common_blue_tallow, BaseItem) +function item_blackshop_common_blue_tallow.prototype.OnSpellStart(self) + if not IsServer() then + return + end + local caster = self:GetCaster() + if not caster then + return + end + local add = self:GetSpecialValueFor("bonus_mana_regen") + if caster:HasModifier("modifier_item_blue_tallow") then + local m = caster:FindModifierByName("modifier_item_blue_tallow") + m:SetStackCount(m:GetStackCount() + add) + else + caster:AddNewModifier(caster, self, "modifier_item_blue_tallow", {}):SetStackCount(add) + end + UTIL_Remove(self) +end +item_blackshop_common_blue_tallow = __TS__Decorate( + item_blackshop_common_blue_tallow, + item_blackshop_common_blue_tallow, + {registerAbility(nil)}, + {kind = "class", name = "item_blackshop_common_blue_tallow"} +) +____exports.item_blackshop_common_blue_tallow = item_blackshop_common_blue_tallow +____exports.modifier_item_blue_tallow = __TS__Class() +local modifier_item_blue_tallow = ____exports.modifier_item_blue_tallow +modifier_item_blue_tallow.name = "modifier_item_blue_tallow" +modifier_item_blue_tallow.____file_path = "scripts/vscripts/items/blackshop/common/item_blackshop_common_blue_tallow.lua" +__TS__ClassExtends(modifier_item_blue_tallow, BaseModifier) +function modifier_item_blue_tallow.prototype.RemoveOnDeath(self) + return false +end +function modifier_item_blue_tallow.prototype.IsHidden(self) + return true +end +function modifier_item_blue_tallow.prototype.IsPurgable(self) + return false +end +function modifier_item_blue_tallow.prototype.GetTexture(self) + return "../items/blackshop/blue_tallow" +end +function modifier_item_blue_tallow.prototype.DeclareFunctions(self) + return {MODIFIER_PROPERTY_MANA_REGEN_CONSTANT} +end +function modifier_item_blue_tallow.prototype.GetModifierConstantManaRegen(self) + return self:GetStackCount() +end +modifier_item_blue_tallow = __TS__Decorate( + modifier_item_blue_tallow, + modifier_item_blue_tallow, + {registerModifier(nil)}, + {kind = "class", name = "modifier_item_blue_tallow"} +) +____exports.modifier_item_blue_tallow = modifier_item_blue_tallow +return ____exports diff --git a/scripts/vscripts/items/blackshop/common/item_blackshop_common_bonus_stats_agi.lua b/scripts/vscripts/items/blackshop/common/item_blackshop_common_bonus_stats_agi.lua new file mode 100644 index 0000000..76a1a49 --- /dev/null +++ b/scripts/vscripts/items/blackshop/common/item_blackshop_common_bonus_stats_agi.lua @@ -0,0 +1,69 @@ +local ____lualib = require("lualib_bundle") +local __TS__Class = ____lualib.__TS__Class +local __TS__ClassExtends = ____lualib.__TS__ClassExtends +local __TS__Decorate = ____lualib.__TS__Decorate +local ____exports = {} +local ____dota_ts_adapter = require("lib.dota_ts_adapter") +local BaseItem = ____dota_ts_adapter.BaseItem +local BaseModifier = ____dota_ts_adapter.BaseModifier +local registerAbility = ____dota_ts_adapter.registerAbility +local registerModifier = ____dota_ts_adapter.registerModifier +____exports.item_blackshop_common_bonus_stats_agi = __TS__Class() +local item_blackshop_common_bonus_stats_agi = ____exports.item_blackshop_common_bonus_stats_agi +item_blackshop_common_bonus_stats_agi.name = "item_blackshop_common_bonus_stats_agi" +item_blackshop_common_bonus_stats_agi.____file_path = "scripts/vscripts/items/blackshop/common/item_blackshop_common_bonus_stats_agi.lua" +__TS__ClassExtends(item_blackshop_common_bonus_stats_agi, BaseItem) +function item_blackshop_common_bonus_stats_agi.prototype.OnSpellStart(self) + if not IsServer() then + return + end + local caster = self:GetCaster() + if not caster then + return + end + local stats = self:GetSpecialValueFor("bonus_agility") + if caster:HasModifier("modifier_item_bonus_stats_agi") then + caster:FindModifierByName("modifier_item_bonus_stats_agi"):SetStackCount(caster:FindModifierByName("modifier_item_bonus_stats_agi"):GetStackCount() + stats) + else + caster:AddNewModifier(caster, self, "modifier_item_bonus_stats_agi", {}):SetStackCount(stats) + end + UTIL_Remove(self) +end +item_blackshop_common_bonus_stats_agi = __TS__Decorate( + item_blackshop_common_bonus_stats_agi, + item_blackshop_common_bonus_stats_agi, + {registerAbility(nil)}, + {kind = "class", name = "item_blackshop_common_bonus_stats_agi"} +) +____exports.item_blackshop_common_bonus_stats_agi = item_blackshop_common_bonus_stats_agi +____exports.modifier_item_bonus_stats_agi = __TS__Class() +local modifier_item_bonus_stats_agi = ____exports.modifier_item_bonus_stats_agi +modifier_item_bonus_stats_agi.name = "modifier_item_bonus_stats_agi" +modifier_item_bonus_stats_agi.____file_path = "scripts/vscripts/items/blackshop/common/item_blackshop_common_bonus_stats_agi.lua" +__TS__ClassExtends(modifier_item_bonus_stats_agi, BaseModifier) +function modifier_item_bonus_stats_agi.prototype.RemoveOnDeath(self) + return false +end +function modifier_item_bonus_stats_agi.prototype.IsHidden(self) + return true +end +function modifier_item_bonus_stats_agi.prototype.IsPurgable(self) + return false +end +function modifier_item_bonus_stats_agi.prototype.GetTexture(self) + return "../items/blackshop/book_agi" +end +function modifier_item_bonus_stats_agi.prototype.DeclareFunctions(self) + return {MODIFIER_PROPERTY_STATS_AGILITY_BONUS} +end +function modifier_item_bonus_stats_agi.prototype.GetModifierBonusStats_Agility(self) + return self:GetStackCount() +end +modifier_item_bonus_stats_agi = __TS__Decorate( + modifier_item_bonus_stats_agi, + modifier_item_bonus_stats_agi, + {registerModifier(nil)}, + {kind = "class", name = "modifier_item_bonus_stats_agi"} +) +____exports.modifier_item_bonus_stats_agi = modifier_item_bonus_stats_agi +return ____exports diff --git a/scripts/vscripts/items/blackshop/common/item_blackshop_common_bonus_stats_int.lua b/scripts/vscripts/items/blackshop/common/item_blackshop_common_bonus_stats_int.lua new file mode 100644 index 0000000..981bdab --- /dev/null +++ b/scripts/vscripts/items/blackshop/common/item_blackshop_common_bonus_stats_int.lua @@ -0,0 +1,69 @@ +local ____lualib = require("lualib_bundle") +local __TS__Class = ____lualib.__TS__Class +local __TS__ClassExtends = ____lualib.__TS__ClassExtends +local __TS__Decorate = ____lualib.__TS__Decorate +local ____exports = {} +local ____dota_ts_adapter = require("lib.dota_ts_adapter") +local BaseItem = ____dota_ts_adapter.BaseItem +local BaseModifier = ____dota_ts_adapter.BaseModifier +local registerAbility = ____dota_ts_adapter.registerAbility +local registerModifier = ____dota_ts_adapter.registerModifier +____exports.item_blackshop_common_bonus_stats_int = __TS__Class() +local item_blackshop_common_bonus_stats_int = ____exports.item_blackshop_common_bonus_stats_int +item_blackshop_common_bonus_stats_int.name = "item_blackshop_common_bonus_stats_int" +item_blackshop_common_bonus_stats_int.____file_path = "scripts/vscripts/items/blackshop/common/item_blackshop_common_bonus_stats_int.lua" +__TS__ClassExtends(item_blackshop_common_bonus_stats_int, BaseItem) +function item_blackshop_common_bonus_stats_int.prototype.OnSpellStart(self) + if not IsServer() then + return + end + local caster = self:GetCaster() + if not caster then + return + end + local stats = self:GetSpecialValueFor("bonus_intelligence") + if caster:HasModifier("modifier_item_bonus_stats_int") then + caster:FindModifierByName("modifier_item_bonus_stats_int"):SetStackCount(caster:FindModifierByName("modifier_item_bonus_stats_int"):GetStackCount() + stats) + else + caster:AddNewModifier(caster, self, "modifier_item_bonus_stats_int", {}):SetStackCount(stats) + end + UTIL_Remove(self) +end +item_blackshop_common_bonus_stats_int = __TS__Decorate( + item_blackshop_common_bonus_stats_int, + item_blackshop_common_bonus_stats_int, + {registerAbility(nil)}, + {kind = "class", name = "item_blackshop_common_bonus_stats_int"} +) +____exports.item_blackshop_common_bonus_stats_int = item_blackshop_common_bonus_stats_int +____exports.modifier_item_bonus_stats_int = __TS__Class() +local modifier_item_bonus_stats_int = ____exports.modifier_item_bonus_stats_int +modifier_item_bonus_stats_int.name = "modifier_item_bonus_stats_int" +modifier_item_bonus_stats_int.____file_path = "scripts/vscripts/items/blackshop/common/item_blackshop_common_bonus_stats_int.lua" +__TS__ClassExtends(modifier_item_bonus_stats_int, BaseModifier) +function modifier_item_bonus_stats_int.prototype.RemoveOnDeath(self) + return false +end +function modifier_item_bonus_stats_int.prototype.IsHidden(self) + return true +end +function modifier_item_bonus_stats_int.prototype.IsPurgable(self) + return false +end +function modifier_item_bonus_stats_int.prototype.GetTexture(self) + return "../items/blackshop/book_int" +end +function modifier_item_bonus_stats_int.prototype.DeclareFunctions(self) + return {MODIFIER_PROPERTY_STATS_INTELLECT_BONUS} +end +function modifier_item_bonus_stats_int.prototype.GetModifierBonusStats_Intellect(self) + return self:GetStackCount() +end +modifier_item_bonus_stats_int = __TS__Decorate( + modifier_item_bonus_stats_int, + modifier_item_bonus_stats_int, + {registerModifier(nil)}, + {kind = "class", name = "modifier_item_bonus_stats_int"} +) +____exports.modifier_item_bonus_stats_int = modifier_item_bonus_stats_int +return ____exports diff --git a/scripts/vscripts/items/blackshop/common/item_blackshop_common_bonus_stats_str.lua b/scripts/vscripts/items/blackshop/common/item_blackshop_common_bonus_stats_str.lua new file mode 100644 index 0000000..02f907b --- /dev/null +++ b/scripts/vscripts/items/blackshop/common/item_blackshop_common_bonus_stats_str.lua @@ -0,0 +1,69 @@ +local ____lualib = require("lualib_bundle") +local __TS__Class = ____lualib.__TS__Class +local __TS__ClassExtends = ____lualib.__TS__ClassExtends +local __TS__Decorate = ____lualib.__TS__Decorate +local ____exports = {} +local ____dota_ts_adapter = require("lib.dota_ts_adapter") +local BaseItem = ____dota_ts_adapter.BaseItem +local BaseModifier = ____dota_ts_adapter.BaseModifier +local registerAbility = ____dota_ts_adapter.registerAbility +local registerModifier = ____dota_ts_adapter.registerModifier +____exports.item_blackshop_common_bonus_stats_str = __TS__Class() +local item_blackshop_common_bonus_stats_str = ____exports.item_blackshop_common_bonus_stats_str +item_blackshop_common_bonus_stats_str.name = "item_blackshop_common_bonus_stats_str" +item_blackshop_common_bonus_stats_str.____file_path = "scripts/vscripts/items/blackshop/common/item_blackshop_common_bonus_stats_str.lua" +__TS__ClassExtends(item_blackshop_common_bonus_stats_str, BaseItem) +function item_blackshop_common_bonus_stats_str.prototype.OnSpellStart(self) + if not IsServer() then + return + end + local caster = self:GetCaster() + if not caster then + return + end + local stats = self:GetSpecialValueFor("bonus_strength") + if caster:HasModifier("modifier_item_bonus_stats_str") then + caster:FindModifierByName("modifier_item_bonus_stats_str"):SetStackCount(caster:FindModifierByName("modifier_item_bonus_stats_str"):GetStackCount() + stats) + else + caster:AddNewModifier(caster, self, "modifier_item_bonus_stats_str", {}):SetStackCount(stats) + end + UTIL_Remove(self) +end +item_blackshop_common_bonus_stats_str = __TS__Decorate( + item_blackshop_common_bonus_stats_str, + item_blackshop_common_bonus_stats_str, + {registerAbility(nil)}, + {kind = "class", name = "item_blackshop_common_bonus_stats_str"} +) +____exports.item_blackshop_common_bonus_stats_str = item_blackshop_common_bonus_stats_str +____exports.modifier_item_bonus_stats_str = __TS__Class() +local modifier_item_bonus_stats_str = ____exports.modifier_item_bonus_stats_str +modifier_item_bonus_stats_str.name = "modifier_item_bonus_stats_str" +modifier_item_bonus_stats_str.____file_path = "scripts/vscripts/items/blackshop/common/item_blackshop_common_bonus_stats_str.lua" +__TS__ClassExtends(modifier_item_bonus_stats_str, BaseModifier) +function modifier_item_bonus_stats_str.prototype.RemoveOnDeath(self) + return false +end +function modifier_item_bonus_stats_str.prototype.IsHidden(self) + return true +end +function modifier_item_bonus_stats_str.prototype.IsPurgable(self) + return false +end +function modifier_item_bonus_stats_str.prototype.GetTexture(self) + return "../items/blackshop/book_str" +end +function modifier_item_bonus_stats_str.prototype.DeclareFunctions(self) + return {MODIFIER_PROPERTY_STATS_STRENGTH_BONUS} +end +function modifier_item_bonus_stats_str.prototype.GetModifierBonusStats_Strength(self) + return self:GetStackCount() +end +modifier_item_bonus_stats_str = __TS__Decorate( + modifier_item_bonus_stats_str, + modifier_item_bonus_stats_str, + {registerModifier(nil)}, + {kind = "class", name = "modifier_item_bonus_stats_str"} +) +____exports.modifier_item_bonus_stats_str = modifier_item_bonus_stats_str +return ____exports diff --git a/scripts/vscripts/items/blackshop/common/item_blackshop_common_boo_stuff.lua b/scripts/vscripts/items/blackshop/common/item_blackshop_common_boo_stuff.lua new file mode 100644 index 0000000..fdfcca0 --- /dev/null +++ b/scripts/vscripts/items/blackshop/common/item_blackshop_common_boo_stuff.lua @@ -0,0 +1,75 @@ +local ____lualib = require("lualib_bundle") +local __TS__Class = ____lualib.__TS__Class +local __TS__ClassExtends = ____lualib.__TS__ClassExtends +local __TS__Decorate = ____lualib.__TS__Decorate +local ____exports = {} +local ____dota_ts_adapter = require("lib.dota_ts_adapter") +local BaseItem = ____dota_ts_adapter.BaseItem +local BaseModifier = ____dota_ts_adapter.BaseModifier +local registerAbility = ____dota_ts_adapter.registerAbility +local registerModifier = ____dota_ts_adapter.registerModifier +____exports.item_blackshop_common_boo_stuff = __TS__Class() +local item_blackshop_common_boo_stuff = ____exports.item_blackshop_common_boo_stuff +item_blackshop_common_boo_stuff.name = "item_blackshop_common_boo_stuff" +item_blackshop_common_boo_stuff.____file_path = "scripts/vscripts/items/blackshop/common/item_blackshop_common_boo_stuff.lua" +__TS__ClassExtends(item_blackshop_common_boo_stuff, BaseItem) +function item_blackshop_common_boo_stuff.prototype.OnSpellStart(self) + if not IsServer() then + return + end + local caster = self:GetCaster() + if not caster then + return + end + local stats = self:GetSpecialValueFor("bonus_stats") + if caster:HasModifier("modifier_item_boo_stuff") then + caster:FindModifierByName("modifier_item_boo_stuff"):SetStackCount(caster:FindModifierByName("modifier_item_boo_stuff"):GetStackCount() + stats) + else + caster:AddNewModifier(caster, self, "modifier_item_boo_stuff", {}):SetStackCount(stats) + end + UTIL_Remove(self) +end +item_blackshop_common_boo_stuff = __TS__Decorate( + item_blackshop_common_boo_stuff, + item_blackshop_common_boo_stuff, + {registerAbility(nil)}, + {kind = "class", name = "item_blackshop_common_boo_stuff"} +) +____exports.item_blackshop_common_boo_stuff = item_blackshop_common_boo_stuff +____exports.modifier_item_boo_stuff = __TS__Class() +local modifier_item_boo_stuff = ____exports.modifier_item_boo_stuff +modifier_item_boo_stuff.name = "modifier_item_boo_stuff" +modifier_item_boo_stuff.____file_path = "scripts/vscripts/items/blackshop/common/item_blackshop_common_boo_stuff.lua" +__TS__ClassExtends(modifier_item_boo_stuff, BaseModifier) +function modifier_item_boo_stuff.prototype.RemoveOnDeath(self) + return false +end +function modifier_item_boo_stuff.prototype.IsHidden(self) + return true +end +function modifier_item_boo_stuff.prototype.IsPurgable(self) + return false +end +function modifier_item_boo_stuff.prototype.GetTexture(self) + return "../items/blackshop/boo_stuff" +end +function modifier_item_boo_stuff.prototype.DeclareFunctions(self) + return {MODIFIER_PROPERTY_PREATTACK_BONUS_DAMAGE, MODIFIER_PROPERTY_ATTACK_RANGE_BONUS} +end +function modifier_item_boo_stuff.prototype.GetModifierPreAttack_BonusDamage(self) + return self:GetStackCount() +end +function modifier_item_boo_stuff.prototype.GetModifierAttackRangeBonus(self) + if not self:GetParent():IsRangedAttacker() then + return 50 + end + return 0 +end +modifier_item_boo_stuff = __TS__Decorate( + modifier_item_boo_stuff, + modifier_item_boo_stuff, + {registerModifier(nil)}, + {kind = "class", name = "modifier_item_boo_stuff"} +) +____exports.modifier_item_boo_stuff = modifier_item_boo_stuff +return ____exports diff --git a/scripts/vscripts/items/blackshop/common/item_blackshop_common_injector.lua b/scripts/vscripts/items/blackshop/common/item_blackshop_common_injector.lua new file mode 100644 index 0000000..e661144 --- /dev/null +++ b/scripts/vscripts/items/blackshop/common/item_blackshop_common_injector.lua @@ -0,0 +1,72 @@ +local ____lualib = require("lualib_bundle") +local __TS__Class = ____lualib.__TS__Class +local __TS__ClassExtends = ____lualib.__TS__ClassExtends +local __TS__Decorate = ____lualib.__TS__Decorate +local ____exports = {} +local ____dota_ts_adapter = require("lib.dota_ts_adapter") +local BaseItem = ____dota_ts_adapter.BaseItem +local BaseModifier = ____dota_ts_adapter.BaseModifier +local registerAbility = ____dota_ts_adapter.registerAbility +local registerModifier = ____dota_ts_adapter.registerModifier +____exports.item_blackshop_common_injector = __TS__Class() +local item_blackshop_common_injector = ____exports.item_blackshop_common_injector +item_blackshop_common_injector.name = "item_blackshop_common_injector" +item_blackshop_common_injector.____file_path = "scripts/vscripts/items/blackshop/common/item_blackshop_common_injector.lua" +__TS__ClassExtends(item_blackshop_common_injector, BaseItem) +function item_blackshop_common_injector.prototype.OnSpellStart(self) + if not IsServer() then + return + end + local caster = self:GetCaster() + if not caster then + return + end + local stats = self:GetSpecialValueFor("attack_speed") + if caster:HasModifier("modifier_item_injector") then + caster:FindModifierByName("modifier_item_injector"):SetStackCount(caster:FindModifierByName("modifier_item_injector"):GetStackCount() + stats) + else + caster:AddNewModifier(caster, self, "modifier_item_injector", {}):SetStackCount(stats) + end + UTIL_Remove(self) +end +item_blackshop_common_injector = __TS__Decorate( + item_blackshop_common_injector, + item_blackshop_common_injector, + {registerAbility(nil)}, + {kind = "class", name = "item_blackshop_common_injector"} +) +____exports.item_blackshop_common_injector = item_blackshop_common_injector +____exports.modifier_item_injector = __TS__Class() +local modifier_item_injector = ____exports.modifier_item_injector +modifier_item_injector.name = "modifier_item_injector" +modifier_item_injector.____file_path = "scripts/vscripts/items/blackshop/common/item_blackshop_common_injector.lua" +__TS__ClassExtends(modifier_item_injector, BaseModifier) +function modifier_item_injector.prototype.RemoveOnDeath(self) + return false +end +function modifier_item_injector.prototype.IsHidden(self) + return true +end +function modifier_item_injector.prototype.IsPurgable(self) + return false +end +function modifier_item_injector.prototype.GetTexture(self) + return "../items/blackshop/soldier" +end +function modifier_item_injector.prototype.DeclareFunctions(self) + return {MODIFIER_PROPERTY_ATTACKSPEED_BONUS_CONSTANT, MODIFIER_PROPERTY_MOVESPEED_BONUS_CONSTANT} +end +function modifier_item_injector.prototype.GetModifierAttackSpeedBonus_Constant(self) + return self:GetStackCount() +end +function modifier_item_injector.prototype.GetModifierMoveSpeedBonus_Constant(self) + return self:GetStackCount() +end +modifier_item_injector = __TS__Decorate( + modifier_item_injector, + modifier_item_injector, + {registerModifier(nil)}, + {kind = "class", name = "modifier_item_injector"} +) +____exports.modifier_item_injector = modifier_item_injector +return ____exports diff --git a/scripts/vscripts/items/blackshop/common/item_blackshop_common_king_crown.lua b/scripts/vscripts/items/blackshop/common/item_blackshop_common_king_crown.lua new file mode 100644 index 0000000..36ff7f4 --- /dev/null +++ b/scripts/vscripts/items/blackshop/common/item_blackshop_common_king_crown.lua @@ -0,0 +1,72 @@ +local ____lualib = require("lualib_bundle") +local __TS__Class = ____lualib.__TS__Class +local __TS__ClassExtends = ____lualib.__TS__ClassExtends +local __TS__Decorate = ____lualib.__TS__Decorate +local ____exports = {} +local ____dota_ts_adapter = require("lib.dota_ts_adapter") +local BaseItem = ____dota_ts_adapter.BaseItem +local BaseModifier = ____dota_ts_adapter.BaseModifier +local registerAbility = ____dota_ts_adapter.registerAbility +local registerModifier = ____dota_ts_adapter.registerModifier +____exports.item_blackshop_common_king_crown = __TS__Class() +local item_blackshop_common_king_crown = ____exports.item_blackshop_common_king_crown +item_blackshop_common_king_crown.name = "item_blackshop_common_king_crown" +item_blackshop_common_king_crown.____file_path = "scripts/vscripts/items/blackshop/common/item_blackshop_common_king_crown.lua" +__TS__ClassExtends(item_blackshop_common_king_crown, BaseItem) +function item_blackshop_common_king_crown.prototype.OnSpellStart(self) + if not IsServer() then + return + end + local caster = self:GetCaster() + if not caster then + return + end + local stats = self:GetSpecialValueFor("bonus_armor") + if caster:HasModifier("modifier_item_king_crown") then + caster:FindModifierByName("modifier_item_king_crown"):SetStackCount(caster:FindModifierByName("modifier_item_king_crown"):GetStackCount() + stats) + else + caster:AddNewModifier(caster, self, "modifier_item_king_crown", {}):SetStackCount(stats) + end + UTIL_Remove(self) +end +item_blackshop_common_king_crown = __TS__Decorate( + item_blackshop_common_king_crown, + item_blackshop_common_king_crown, + {registerAbility(nil)}, + {kind = "class", name = "item_blackshop_common_king_crown"} +) +____exports.item_blackshop_common_king_crown = item_blackshop_common_king_crown +____exports.modifier_item_king_crown = __TS__Class() +local modifier_item_king_crown = ____exports.modifier_item_king_crown +modifier_item_king_crown.name = "modifier_item_king_crown" +modifier_item_king_crown.____file_path = "scripts/vscripts/items/blackshop/common/item_blackshop_common_king_crown.lua" +__TS__ClassExtends(modifier_item_king_crown, BaseModifier) +function modifier_item_king_crown.prototype.RemoveOnDeath(self) + return false +end +function modifier_item_king_crown.prototype.IsHidden(self) + return false +end +function modifier_item_king_crown.prototype.IsPurgable(self) + return false +end +function modifier_item_king_crown.prototype.GetTexture(self) + return "../items/blackshop/crown" +end +function modifier_item_king_crown.prototype.DeclareFunctions(self) + return {MODIFIER_PROPERTY_HEALTH_BONUS, MODIFIER_PROPERTY_PHYSICAL_ARMOR_BONUS} +end +function modifier_item_king_crown.prototype.GetModifierHealthBonus(self) + return math.floor(self:GetStackCount() * 10) +end +function modifier_item_king_crown.prototype.GetModifierPhysicalArmorBonus(self) + return self:GetStackCount() +end +modifier_item_king_crown = __TS__Decorate( + modifier_item_king_crown, + modifier_item_king_crown, + {registerModifier(nil)}, + {kind = "class", name = "modifier_item_king_crown"} +) +____exports.modifier_item_king_crown = modifier_item_king_crown +return ____exports diff --git a/scripts/vscripts/items/blackshop/common/item_blackshop_common_manaflare.lua b/scripts/vscripts/items/blackshop/common/item_blackshop_common_manaflare.lua new file mode 100644 index 0000000..cf46fd7 --- /dev/null +++ b/scripts/vscripts/items/blackshop/common/item_blackshop_common_manaflare.lua @@ -0,0 +1,72 @@ +local ____lualib = require("lualib_bundle") +local __TS__Class = ____lualib.__TS__Class +local __TS__ClassExtends = ____lualib.__TS__ClassExtends +local __TS__Decorate = ____lualib.__TS__Decorate +local ____exports = {} +local ____dota_ts_adapter = require("lib.dota_ts_adapter") +local BaseItem = ____dota_ts_adapter.BaseItem +local BaseModifier = ____dota_ts_adapter.BaseModifier +local registerAbility = ____dota_ts_adapter.registerAbility +local registerModifier = ____dota_ts_adapter.registerModifier +____exports.item_blackshop_common_manaflare = __TS__Class() +local item_blackshop_common_manaflare = ____exports.item_blackshop_common_manaflare +item_blackshop_common_manaflare.name = "item_blackshop_common_manaflare" +item_blackshop_common_manaflare.____file_path = "scripts/vscripts/items/blackshop/common/item_blackshop_common_manaflare.lua" +__TS__ClassExtends(item_blackshop_common_manaflare, BaseItem) +function item_blackshop_common_manaflare.prototype.OnSpellStart(self) + if not IsServer() then + return + end + local caster = self:GetCaster() + if not caster then + return + end + local stats = self:GetSpecialValueFor("bonus_mana") + if caster:HasModifier("modifier_item_manaflare") then + caster:FindModifierByName("modifier_item_manaflare"):SetStackCount(caster:FindModifierByName("modifier_item_manaflare"):GetStackCount() + stats) + else + caster:AddNewModifier(caster, self, "modifier_item_manaflare", {}):SetStackCount(stats) + end + UTIL_Remove(self) +end +item_blackshop_common_manaflare = __TS__Decorate( + item_blackshop_common_manaflare, + item_blackshop_common_manaflare, + {registerAbility(nil)}, + {kind = "class", name = "item_blackshop_common_manaflare"} +) +____exports.item_blackshop_common_manaflare = item_blackshop_common_manaflare +____exports.modifier_item_manaflare = __TS__Class() +local modifier_item_manaflare = ____exports.modifier_item_manaflare +modifier_item_manaflare.name = "modifier_item_manaflare" +modifier_item_manaflare.____file_path = "scripts/vscripts/items/blackshop/common/item_blackshop_common_manaflare.lua" +__TS__ClassExtends(modifier_item_manaflare, BaseModifier) +function modifier_item_manaflare.prototype.RemoveOnDeath(self) + return false +end +function modifier_item_manaflare.prototype.IsHidden(self) + return false +end +function modifier_item_manaflare.prototype.IsPurgable(self) + return false +end +function modifier_item_manaflare.prototype.GetTexture(self) + return "../items/blackshop/manaflare" +end +function modifier_item_manaflare.prototype.DeclareFunctions(self) + return {MODIFIER_PROPERTY_MANA_BONUS, MODIFIER_PROPERTY_MANA_REGEN_CONSTANT} +end +function modifier_item_manaflare.prototype.GetModifierManaBonus(self) + return self:GetStackCount() +end +function modifier_item_manaflare.prototype.GetModifierConstantManaRegen(self) + return self:GetStackCount() / 100 +end +modifier_item_manaflare = __TS__Decorate( + modifier_item_manaflare, + modifier_item_manaflare, + {registerModifier(nil)}, + {kind = "class", name = "modifier_item_manaflare"} +) +____exports.modifier_item_manaflare = modifier_item_manaflare +return ____exports diff --git a/scripts/vscripts/items/blackshop/common/item_blackshop_common_spell_mask.lua b/scripts/vscripts/items/blackshop/common/item_blackshop_common_spell_mask.lua new file mode 100644 index 0000000..dfef7c7 --- /dev/null +++ b/scripts/vscripts/items/blackshop/common/item_blackshop_common_spell_mask.lua @@ -0,0 +1,72 @@ +local ____lualib = require("lualib_bundle") +local __TS__Class = ____lualib.__TS__Class +local __TS__ClassExtends = ____lualib.__TS__ClassExtends +local __TS__Decorate = ____lualib.__TS__Decorate +local ____exports = {} +local ____dota_ts_adapter = require("lib.dota_ts_adapter") +local BaseItem = ____dota_ts_adapter.BaseItem +local BaseModifier = ____dota_ts_adapter.BaseModifier +local registerAbility = ____dota_ts_adapter.registerAbility +local registerModifier = ____dota_ts_adapter.registerModifier +____exports.item_blackshop_common_spell_mask = __TS__Class() +local item_blackshop_common_spell_mask = ____exports.item_blackshop_common_spell_mask +item_blackshop_common_spell_mask.name = "item_blackshop_common_spell_mask" +item_blackshop_common_spell_mask.____file_path = "scripts/vscripts/items/blackshop/common/item_blackshop_common_spell_mask.lua" +__TS__ClassExtends(item_blackshop_common_spell_mask, BaseItem) +function item_blackshop_common_spell_mask.prototype.OnSpellStart(self) + if not IsServer() then + return + end + local caster = self:GetCaster() + if not caster then + return + end + local stats = self:GetSpecialValueFor("bonus_spell_amp") + if caster:HasModifier("modifier_item_spell_mask") then + caster:FindModifierByName("modifier_item_spell_mask"):SetStackCount(caster:FindModifierByName("modifier_item_spell_mask"):GetStackCount() + stats) + else + caster:AddNewModifier(caster, self, "modifier_item_spell_mask", {}):SetStackCount(stats) + end + UTIL_Remove(self) +end +item_blackshop_common_spell_mask = __TS__Decorate( + item_blackshop_common_spell_mask, + item_blackshop_common_spell_mask, + {registerAbility(nil)}, + {kind = "class", name = "item_blackshop_common_spell_mask"} +) +____exports.item_blackshop_common_spell_mask = item_blackshop_common_spell_mask +____exports.modifier_item_spell_mask = __TS__Class() +local modifier_item_spell_mask = ____exports.modifier_item_spell_mask +modifier_item_spell_mask.name = "modifier_item_spell_mask" +modifier_item_spell_mask.____file_path = "scripts/vscripts/items/blackshop/common/item_blackshop_common_spell_mask.lua" +__TS__ClassExtends(modifier_item_spell_mask, BaseModifier) +function modifier_item_spell_mask.prototype.RemoveOnDeath(self) + return false +end +function modifier_item_spell_mask.prototype.IsHidden(self) + return true +end +function modifier_item_spell_mask.prototype.IsPurgable(self) + return false +end +function modifier_item_spell_mask.prototype.GetTexture(self) + return "../items/blackshop/spell_mask" +end +function modifier_item_spell_mask.prototype.DeclareFunctions(self) + return {MODIFIER_PROPERTY_SPELL_AMPLIFY_PERCENTAGE, MODIFIER_PROPERTY_MANA_REGEN_CONSTANT} +end +function modifier_item_spell_mask.prototype.GetModifierSpellAmplify_Percentage(self) + return self:GetStackCount() +end +function modifier_item_spell_mask.prototype.GetModifierConstantManaRegen(self) + return self:GetStackCount() +end +modifier_item_spell_mask = __TS__Decorate( + modifier_item_spell_mask, + modifier_item_spell_mask, + {registerModifier(nil)}, + {kind = "class", name = "modifier_item_spell_mask"} +) +____exports.modifier_item_spell_mask = modifier_item_spell_mask +return ____exports diff --git a/scripts/vscripts/items/blackshop/common/item_blackshop_common_stone_armor.lua b/scripts/vscripts/items/blackshop/common/item_blackshop_common_stone_armor.lua new file mode 100644 index 0000000..398f700 --- /dev/null +++ b/scripts/vscripts/items/blackshop/common/item_blackshop_common_stone_armor.lua @@ -0,0 +1,69 @@ +local ____lualib = require("lualib_bundle") +local __TS__Class = ____lualib.__TS__Class +local __TS__ClassExtends = ____lualib.__TS__ClassExtends +local __TS__Decorate = ____lualib.__TS__Decorate +local ____exports = {} +local ____dota_ts_adapter = require("lib.dota_ts_adapter") +local BaseItem = ____dota_ts_adapter.BaseItem +local BaseModifier = ____dota_ts_adapter.BaseModifier +local registerAbility = ____dota_ts_adapter.registerAbility +local registerModifier = ____dota_ts_adapter.registerModifier +____exports.item_blackshop_common_stone_armor = __TS__Class() +local item_blackshop_common_stone_armor = ____exports.item_blackshop_common_stone_armor +item_blackshop_common_stone_armor.name = "item_blackshop_common_stone_armor" +item_blackshop_common_stone_armor.____file_path = "scripts/vscripts/items/blackshop/common/item_blackshop_common_stone_armor.lua" +__TS__ClassExtends(item_blackshop_common_stone_armor, BaseItem) +function item_blackshop_common_stone_armor.prototype.OnSpellStart(self) + if not IsServer() then + return + end + local caster = self:GetCaster() + if not caster then + return + end + local stats = self:GetSpecialValueFor("bonus_armor") + if caster:HasModifier("modifier_item_stone_armor") then + caster:FindModifierByName("modifier_item_stone_armor"):SetStackCount(caster:FindModifierByName("modifier_item_stone_armor"):GetStackCount() + stats) + else + caster:AddNewModifier(caster, self, "modifier_item_stone_armor", {}):SetStackCount(stats) + end + UTIL_Remove(self) +end +item_blackshop_common_stone_armor = __TS__Decorate( + item_blackshop_common_stone_armor, + item_blackshop_common_stone_armor, + {registerAbility(nil)}, + {kind = "class", name = "item_blackshop_common_stone_armor"} +) +____exports.item_blackshop_common_stone_armor = item_blackshop_common_stone_armor +____exports.modifier_item_stone_armor = __TS__Class() +local modifier_item_stone_armor = ____exports.modifier_item_stone_armor +modifier_item_stone_armor.name = "modifier_item_stone_armor" +modifier_item_stone_armor.____file_path = "scripts/vscripts/items/blackshop/common/item_blackshop_common_stone_armor.lua" +__TS__ClassExtends(modifier_item_stone_armor, BaseModifier) +function modifier_item_stone_armor.prototype.RemoveOnDeath(self) + return false +end +function modifier_item_stone_armor.prototype.IsHidden(self) + return true +end +function modifier_item_stone_armor.prototype.IsPurgable(self) + return false +end +function modifier_item_stone_armor.prototype.GetTexture(self) + return "../items/blackshop/stone_armor" +end +function modifier_item_stone_armor.prototype.DeclareFunctions(self) + return {MODIFIER_PROPERTY_PHYSICAL_ARMOR_BONUS} +end +function modifier_item_stone_armor.prototype.GetModifierPhysicalArmorBonus(self) + return self:GetStackCount() +end +modifier_item_stone_armor = __TS__Decorate( + modifier_item_stone_armor, + modifier_item_stone_armor, + {registerModifier(nil)}, + {kind = "class", name = "modifier_item_stone_armor"} +) +____exports.modifier_item_stone_armor = modifier_item_stone_armor +return ____exports diff --git a/scripts/vscripts/items/blackshop/common/item_blackshop_common_vigor_tincture.lua b/scripts/vscripts/items/blackshop/common/item_blackshop_common_vigor_tincture.lua new file mode 100644 index 0000000..ec965d4 --- /dev/null +++ b/scripts/vscripts/items/blackshop/common/item_blackshop_common_vigor_tincture.lua @@ -0,0 +1,71 @@ +local ____lualib = require("lualib_bundle") +local __TS__Class = ____lualib.__TS__Class +local __TS__ClassExtends = ____lualib.__TS__ClassExtends +local __TS__Decorate = ____lualib.__TS__Decorate +local ____exports = {} +local ____dota_ts_adapter = require("lib.dota_ts_adapter") +local BaseItem = ____dota_ts_adapter.BaseItem +local BaseModifier = ____dota_ts_adapter.BaseModifier +local registerAbility = ____dota_ts_adapter.registerAbility +local registerModifier = ____dota_ts_adapter.registerModifier +--- Настойка силы — постоянный бонус к регену HP (стакается). +____exports.item_blackshop_common_vigor_tincture = __TS__Class() +local item_blackshop_common_vigor_tincture = ____exports.item_blackshop_common_vigor_tincture +item_blackshop_common_vigor_tincture.name = "item_blackshop_common_vigor_tincture" +item_blackshop_common_vigor_tincture.____file_path = "scripts/vscripts/items/blackshop/common/item_blackshop_common_vigor_tincture.lua" +__TS__ClassExtends(item_blackshop_common_vigor_tincture, BaseItem) +function item_blackshop_common_vigor_tincture.prototype.OnSpellStart(self) + if not IsServer() then + return + end + local caster = self:GetCaster() + if not caster then + return + end + local add = self:GetSpecialValueFor("bonus_hp_regen") + if caster:HasModifier("modifier_item_vigor_tincture") then + local m = caster:FindModifierByName("modifier_item_vigor_tincture") + m:SetStackCount(m:GetStackCount() + add) + else + caster:AddNewModifier(caster, self, "modifier_item_vigor_tincture", {}):SetStackCount(add) + end + UTIL_Remove(self) +end +item_blackshop_common_vigor_tincture = __TS__Decorate( + item_blackshop_common_vigor_tincture, + item_blackshop_common_vigor_tincture, + {registerAbility(nil)}, + {kind = "class", name = "item_blackshop_common_vigor_tincture"} +) +____exports.item_blackshop_common_vigor_tincture = item_blackshop_common_vigor_tincture +____exports.modifier_item_vigor_tincture = __TS__Class() +local modifier_item_vigor_tincture = ____exports.modifier_item_vigor_tincture +modifier_item_vigor_tincture.name = "modifier_item_vigor_tincture" +modifier_item_vigor_tincture.____file_path = "scripts/vscripts/items/blackshop/common/item_blackshop_common_vigor_tincture.lua" +__TS__ClassExtends(modifier_item_vigor_tincture, BaseModifier) +function modifier_item_vigor_tincture.prototype.RemoveOnDeath(self) + return false +end +function modifier_item_vigor_tincture.prototype.IsHidden(self) + return true +end +function modifier_item_vigor_tincture.prototype.IsPurgable(self) + return false +end +function modifier_item_vigor_tincture.prototype.GetTexture(self) + return "../items/blackshop/vigor_salve" +end +function modifier_item_vigor_tincture.prototype.DeclareFunctions(self) + return {MODIFIER_PROPERTY_HEALTH_REGEN_CONSTANT} +end +function modifier_item_vigor_tincture.prototype.GetModifierConstantHealthRegen(self) + return self:GetStackCount() +end +modifier_item_vigor_tincture = __TS__Decorate( + modifier_item_vigor_tincture, + modifier_item_vigor_tincture, + {registerModifier(nil)}, + {kind = "class", name = "modifier_item_vigor_tincture"} +) +____exports.modifier_item_vigor_tincture = modifier_item_vigor_tincture +return ____exports diff --git a/scripts/vscripts/items/blackshop/common/item_blackshop_common_wind_dust.lua b/scripts/vscripts/items/blackshop/common/item_blackshop_common_wind_dust.lua new file mode 100644 index 0000000..bb4ed5e --- /dev/null +++ b/scripts/vscripts/items/blackshop/common/item_blackshop_common_wind_dust.lua @@ -0,0 +1,71 @@ +local ____lualib = require("lualib_bundle") +local __TS__Class = ____lualib.__TS__Class +local __TS__ClassExtends = ____lualib.__TS__ClassExtends +local __TS__Decorate = ____lualib.__TS__Decorate +local ____exports = {} +local ____dota_ts_adapter = require("lib.dota_ts_adapter") +local BaseItem = ____dota_ts_adapter.BaseItem +local BaseModifier = ____dota_ts_adapter.BaseModifier +local registerAbility = ____dota_ts_adapter.registerAbility +local registerModifier = ____dota_ts_adapter.registerModifier +--- Пыль ветра — бонус к скорости передвижения (стакается). +____exports.item_blackshop_common_wind_dust = __TS__Class() +local item_blackshop_common_wind_dust = ____exports.item_blackshop_common_wind_dust +item_blackshop_common_wind_dust.name = "item_blackshop_common_wind_dust" +item_blackshop_common_wind_dust.____file_path = "scripts/vscripts/items/blackshop/common/item_blackshop_common_wind_dust.lua" +__TS__ClassExtends(item_blackshop_common_wind_dust, BaseItem) +function item_blackshop_common_wind_dust.prototype.OnSpellStart(self) + if not IsServer() then + return + end + local caster = self:GetCaster() + if not caster then + return + end + local add = self:GetSpecialValueFor("bonus_movement_speed") + if caster:HasModifier("modifier_item_wind_dust") then + local m = caster:FindModifierByName("modifier_item_wind_dust") + m:SetStackCount(m:GetStackCount() + add) + else + caster:AddNewModifier(caster, self, "modifier_item_wind_dust", {}):SetStackCount(add) + end + UTIL_Remove(self) +end +item_blackshop_common_wind_dust = __TS__Decorate( + item_blackshop_common_wind_dust, + item_blackshop_common_wind_dust, + {registerAbility(nil)}, + {kind = "class", name = "item_blackshop_common_wind_dust"} +) +____exports.item_blackshop_common_wind_dust = item_blackshop_common_wind_dust +____exports.modifier_item_wind_dust = __TS__Class() +local modifier_item_wind_dust = ____exports.modifier_item_wind_dust +modifier_item_wind_dust.name = "modifier_item_wind_dust" +modifier_item_wind_dust.____file_path = "scripts/vscripts/items/blackshop/common/item_blackshop_common_wind_dust.lua" +__TS__ClassExtends(modifier_item_wind_dust, BaseModifier) +function modifier_item_wind_dust.prototype.RemoveOnDeath(self) + return false +end +function modifier_item_wind_dust.prototype.IsHidden(self) + return true +end +function modifier_item_wind_dust.prototype.IsPurgable(self) + return false +end +function modifier_item_wind_dust.prototype.GetTexture(self) + return "../items/blackshop/granite_boots" +end +function modifier_item_wind_dust.prototype.DeclareFunctions(self) + return {MODIFIER_PROPERTY_MOVESPEED_BONUS_CONSTANT} +end +function modifier_item_wind_dust.prototype.GetModifierMoveSpeedBonus_Constant(self) + return self:GetStackCount() +end +modifier_item_wind_dust = __TS__Decorate( + modifier_item_wind_dust, + modifier_item_wind_dust, + {registerModifier(nil)}, + {kind = "class", name = "modifier_item_wind_dust"} +) +____exports.modifier_item_wind_dust = modifier_item_wind_dust +return ____exports diff --git a/scripts/vscripts/items/blackshop/common/item_bonus_stats_agi.lua b/scripts/vscripts/items/blackshop/common/item_bonus_stats_agi.lua new file mode 100644 index 0000000..6aa2187 --- /dev/null +++ b/scripts/vscripts/items/blackshop/common/item_bonus_stats_agi.lua @@ -0,0 +1,66 @@ +local ____lualib = require("lualib_bundle") +local __TS__Class = ____lualib.__TS__Class +local __TS__ClassExtends = ____lualib.__TS__ClassExtends +local __TS__Decorate = ____lualib.__TS__Decorate +local __TS__SourceMapTraceBack = ____lualib.__TS__SourceMapTraceBack +__TS__SourceMapTraceBack(debug.getinfo(1).short_src, {["8"] = 1,["9"] = 1,["10"] = 1,["11"] = 1,["12"] = 1,["13"] = 3,["14"] = 4,["15"] = 3,["16"] = 4,["17"] = 5,["18"] = 6,["21"] = 8,["22"] = 9,["25"] = 11,["26"] = 13,["27"] = 14,["29"] = 18,["31"] = 20,["32"] = 5,["33"] = 4,["34"] = 4,["35"] = 4,["36"] = 3,["39"] = 4,["40"] = 24,["41"] = 25,["42"] = 24,["43"] = 25,["44"] = 26,["45"] = 27,["46"] = 26,["47"] = 30,["48"] = 31,["49"] = 30,["50"] = 34,["51"] = 35,["52"] = 34,["53"] = 38,["54"] = 39,["55"] = 38,["56"] = 42,["57"] = 43,["58"] = 42,["59"] = 25,["60"] = 25,["61"] = 25,["62"] = 24,["65"] = 25}); +local ____exports = {} +local ____dota_ts_adapter = require("lib.dota_ts_adapter") +local BaseItem = ____dota_ts_adapter.BaseItem +local BaseModifier = ____dota_ts_adapter.BaseModifier +local registerAbility = ____dota_ts_adapter.registerAbility +local registerModifier = ____dota_ts_adapter.registerModifier +____exports.item_bonus_stats_agi = __TS__Class() +local item_bonus_stats_agi = ____exports.item_bonus_stats_agi +item_bonus_stats_agi.name = "item_bonus_stats_agi" +__TS__ClassExtends(item_bonus_stats_agi, BaseItem) +function item_bonus_stats_agi.prototype.OnSpellStart(self) + if not IsServer() then + return + end + local caster = self:GetCaster() + if not caster then + return + end + local stats = self:GetSpecialValueFor("bonus_agility") + if caster:HasModifier("modifier_item_bonus_stats_agi") then + caster:FindModifierByName("modifier_item_bonus_stats_agi"):SetStackCount(caster:FindModifierByName("modifier_item_bonus_stats_agi"):GetStackCount() + stats) + else + caster:AddNewModifier(caster, self, "modifier_item_bonus_stats_agi", {}):SetStackCount(stats) + end + UTIL_Remove(self) +end +item_bonus_stats_agi = __TS__Decorate( + item_bonus_stats_agi, + item_bonus_stats_agi, + {registerAbility(nil)}, + {kind = "class", name = "item_bonus_stats_agi"} +) +____exports.item_bonus_stats_agi = item_bonus_stats_agi +____exports.modifier_item_bonus_stats_agi = __TS__Class() +local modifier_item_bonus_stats_agi = ____exports.modifier_item_bonus_stats_agi +modifier_item_bonus_stats_agi.name = "modifier_item_bonus_stats_agi" +__TS__ClassExtends(modifier_item_bonus_stats_agi, BaseModifier) +function modifier_item_bonus_stats_agi.prototype.RemoveOnDeath(self) + return false +end +function modifier_item_bonus_stats_agi.prototype.IsHidden(self) + return true +end +function modifier_item_bonus_stats_agi.prototype.IsPurgable(self) + return false +end +function modifier_item_bonus_stats_agi.prototype.DeclareFunctions(self) + return {MODIFIER_PROPERTY_STATS_AGILITY_BONUS} +end +function modifier_item_bonus_stats_agi.prototype.GetModifierBonusStats_Agility(self) + return self:GetStackCount() +end +modifier_item_bonus_stats_agi = __TS__Decorate( + modifier_item_bonus_stats_agi, + modifier_item_bonus_stats_agi, + {registerModifier(nil)}, + {kind = "class", name = "modifier_item_bonus_stats_agi"} +) +____exports.modifier_item_bonus_stats_agi = modifier_item_bonus_stats_agi +return ____exports diff --git a/scripts/vscripts/items/blackshop/common/item_bonus_stats_int.lua b/scripts/vscripts/items/blackshop/common/item_bonus_stats_int.lua new file mode 100644 index 0000000..487b1e1 --- /dev/null +++ b/scripts/vscripts/items/blackshop/common/item_bonus_stats_int.lua @@ -0,0 +1,66 @@ +local ____lualib = require("lualib_bundle") +local __TS__Class = ____lualib.__TS__Class +local __TS__ClassExtends = ____lualib.__TS__ClassExtends +local __TS__Decorate = ____lualib.__TS__Decorate +local __TS__SourceMapTraceBack = ____lualib.__TS__SourceMapTraceBack +__TS__SourceMapTraceBack(debug.getinfo(1).short_src, {["8"] = 1,["9"] = 1,["10"] = 1,["11"] = 1,["12"] = 1,["13"] = 3,["14"] = 4,["15"] = 3,["16"] = 4,["17"] = 5,["18"] = 6,["21"] = 8,["22"] = 9,["25"] = 11,["26"] = 13,["27"] = 14,["29"] = 18,["31"] = 20,["32"] = 5,["33"] = 4,["34"] = 4,["35"] = 4,["36"] = 3,["39"] = 4,["40"] = 24,["41"] = 25,["42"] = 24,["43"] = 25,["44"] = 26,["45"] = 27,["46"] = 26,["47"] = 30,["48"] = 31,["49"] = 30,["50"] = 34,["51"] = 35,["52"] = 34,["53"] = 38,["54"] = 39,["55"] = 38,["56"] = 42,["57"] = 43,["58"] = 42,["59"] = 25,["60"] = 25,["61"] = 25,["62"] = 24,["65"] = 25}); +local ____exports = {} +local ____dota_ts_adapter = require("lib.dota_ts_adapter") +local BaseItem = ____dota_ts_adapter.BaseItem +local BaseModifier = ____dota_ts_adapter.BaseModifier +local registerAbility = ____dota_ts_adapter.registerAbility +local registerModifier = ____dota_ts_adapter.registerModifier +____exports.item_bonus_stats_int = __TS__Class() +local item_bonus_stats_int = ____exports.item_bonus_stats_int +item_bonus_stats_int.name = "item_bonus_stats_int" +__TS__ClassExtends(item_bonus_stats_int, BaseItem) +function item_bonus_stats_int.prototype.OnSpellStart(self) + if not IsServer() then + return + end + local caster = self:GetCaster() + if not caster then + return + end + local stats = self:GetSpecialValueFor("bonus_intelligence") + if caster:HasModifier("modifier_item_bonus_stats_int") then + caster:FindModifierByName("modifier_item_bonus_stats_int"):SetStackCount(caster:FindModifierByName("modifier_item_bonus_stats_int"):GetStackCount() + stats) + else + caster:AddNewModifier(caster, self, "modifier_item_bonus_stats_int", {}):SetStackCount(stats) + end + UTIL_Remove(self) +end +item_bonus_stats_int = __TS__Decorate( + item_bonus_stats_int, + item_bonus_stats_int, + {registerAbility(nil)}, + {kind = "class", name = "item_bonus_stats_int"} +) +____exports.item_bonus_stats_int = item_bonus_stats_int +____exports.modifier_item_bonus_stats_int = __TS__Class() +local modifier_item_bonus_stats_int = ____exports.modifier_item_bonus_stats_int +modifier_item_bonus_stats_int.name = "modifier_item_bonus_stats_int" +__TS__ClassExtends(modifier_item_bonus_stats_int, BaseModifier) +function modifier_item_bonus_stats_int.prototype.RemoveOnDeath(self) + return false +end +function modifier_item_bonus_stats_int.prototype.IsHidden(self) + return true +end +function modifier_item_bonus_stats_int.prototype.IsPurgable(self) + return false +end +function modifier_item_bonus_stats_int.prototype.DeclareFunctions(self) + return {MODIFIER_PROPERTY_STATS_INTELLECT_BONUS} +end +function modifier_item_bonus_stats_int.prototype.GetModifierBonusStats_Intellect(self) + return self:GetStackCount() +end +modifier_item_bonus_stats_int = __TS__Decorate( + modifier_item_bonus_stats_int, + modifier_item_bonus_stats_int, + {registerModifier(nil)}, + {kind = "class", name = "modifier_item_bonus_stats_int"} +) +____exports.modifier_item_bonus_stats_int = modifier_item_bonus_stats_int +return ____exports diff --git a/scripts/vscripts/items/blackshop/common/item_bonus_stats_str.lua b/scripts/vscripts/items/blackshop/common/item_bonus_stats_str.lua new file mode 100644 index 0000000..d5fa81f --- /dev/null +++ b/scripts/vscripts/items/blackshop/common/item_bonus_stats_str.lua @@ -0,0 +1,66 @@ +local ____lualib = require("lualib_bundle") +local __TS__Class = ____lualib.__TS__Class +local __TS__ClassExtends = ____lualib.__TS__ClassExtends +local __TS__Decorate = ____lualib.__TS__Decorate +local __TS__SourceMapTraceBack = ____lualib.__TS__SourceMapTraceBack +__TS__SourceMapTraceBack(debug.getinfo(1).short_src, {["8"] = 1,["9"] = 1,["10"] = 1,["11"] = 1,["12"] = 1,["13"] = 3,["14"] = 4,["15"] = 3,["16"] = 4,["17"] = 5,["18"] = 6,["21"] = 8,["22"] = 9,["25"] = 11,["26"] = 13,["27"] = 14,["29"] = 18,["31"] = 20,["32"] = 5,["33"] = 4,["34"] = 4,["35"] = 4,["36"] = 3,["39"] = 4,["40"] = 24,["41"] = 25,["42"] = 24,["43"] = 25,["44"] = 26,["45"] = 27,["46"] = 26,["47"] = 30,["48"] = 31,["49"] = 30,["50"] = 34,["51"] = 35,["52"] = 34,["53"] = 38,["54"] = 39,["55"] = 38,["56"] = 42,["57"] = 43,["58"] = 42,["59"] = 25,["60"] = 25,["61"] = 25,["62"] = 24,["65"] = 25}); +local ____exports = {} +local ____dota_ts_adapter = require("lib.dota_ts_adapter") +local BaseItem = ____dota_ts_adapter.BaseItem +local BaseModifier = ____dota_ts_adapter.BaseModifier +local registerAbility = ____dota_ts_adapter.registerAbility +local registerModifier = ____dota_ts_adapter.registerModifier +____exports.item_bonus_stats_str = __TS__Class() +local item_bonus_stats_str = ____exports.item_bonus_stats_str +item_bonus_stats_str.name = "item_bonus_stats_str" +__TS__ClassExtends(item_bonus_stats_str, BaseItem) +function item_bonus_stats_str.prototype.OnSpellStart(self) + if not IsServer() then + return + end + local caster = self:GetCaster() + if not caster then + return + end + local stats = self:GetSpecialValueFor("bonus_strength") + if caster:HasModifier("modifier_item_bonus_stats_str") then + caster:FindModifierByName("modifier_item_bonus_stats_str"):SetStackCount(caster:FindModifierByName("modifier_item_bonus_stats_str"):GetStackCount() + stats) + else + caster:AddNewModifier(caster, self, "modifier_item_bonus_stats_str", {}):SetStackCount(stats) + end + UTIL_Remove(self) +end +item_bonus_stats_str = __TS__Decorate( + item_bonus_stats_str, + item_bonus_stats_str, + {registerAbility(nil)}, + {kind = "class", name = "item_bonus_stats_str"} +) +____exports.item_bonus_stats_str = item_bonus_stats_str +____exports.modifier_item_bonus_stats_str = __TS__Class() +local modifier_item_bonus_stats_str = ____exports.modifier_item_bonus_stats_str +modifier_item_bonus_stats_str.name = "modifier_item_bonus_stats_str" +__TS__ClassExtends(modifier_item_bonus_stats_str, BaseModifier) +function modifier_item_bonus_stats_str.prototype.RemoveOnDeath(self) + return false +end +function modifier_item_bonus_stats_str.prototype.IsHidden(self) + return true +end +function modifier_item_bonus_stats_str.prototype.IsPurgable(self) + return false +end +function modifier_item_bonus_stats_str.prototype.DeclareFunctions(self) + return {MODIFIER_PROPERTY_STATS_STRENGTH_BONUS} +end +function modifier_item_bonus_stats_str.prototype.GetModifierBonusStats_Strength(self) + return self:GetStackCount() +end +modifier_item_bonus_stats_str = __TS__Decorate( + modifier_item_bonus_stats_str, + modifier_item_bonus_stats_str, + {registerModifier(nil)}, + {kind = "class", name = "modifier_item_bonus_stats_str"} +) +____exports.modifier_item_bonus_stats_str = modifier_item_bonus_stats_str +return ____exports diff --git a/scripts/vscripts/items/blackshop/common/item_boo_stuff.lua b/scripts/vscripts/items/blackshop/common/item_boo_stuff.lua new file mode 100644 index 0000000..a461b18 --- /dev/null +++ b/scripts/vscripts/items/blackshop/common/item_boo_stuff.lua @@ -0,0 +1,72 @@ +local ____lualib = require("lualib_bundle") +local __TS__Class = ____lualib.__TS__Class +local __TS__ClassExtends = ____lualib.__TS__ClassExtends +local __TS__Decorate = ____lualib.__TS__Decorate +local __TS__SourceMapTraceBack = ____lualib.__TS__SourceMapTraceBack +__TS__SourceMapTraceBack(debug.getinfo(1).short_src, {["8"] = 1,["9"] = 1,["10"] = 1,["11"] = 1,["12"] = 1,["13"] = 3,["14"] = 4,["15"] = 3,["16"] = 4,["17"] = 5,["18"] = 6,["21"] = 8,["22"] = 9,["25"] = 11,["26"] = 13,["27"] = 14,["29"] = 16,["31"] = 18,["32"] = 5,["33"] = 4,["34"] = 4,["35"] = 4,["36"] = 3,["39"] = 4,["40"] = 22,["41"] = 23,["42"] = 22,["43"] = 23,["44"] = 24,["45"] = 25,["46"] = 24,["47"] = 28,["48"] = 29,["49"] = 28,["50"] = 32,["51"] = 33,["52"] = 32,["53"] = 36,["54"] = 37,["55"] = 36,["56"] = 40,["57"] = 41,["58"] = 40,["59"] = 43,["60"] = 44,["61"] = 45,["63"] = 47,["64"] = 43,["65"] = 23,["66"] = 23,["67"] = 23,["68"] = 22,["71"] = 23}); +local ____exports = {} +local ____dota_ts_adapter = require("lib.dota_ts_adapter") +local BaseItem = ____dota_ts_adapter.BaseItem +local BaseModifier = ____dota_ts_adapter.BaseModifier +local registerAbility = ____dota_ts_adapter.registerAbility +local registerModifier = ____dota_ts_adapter.registerModifier +____exports.item_boo_stuff = __TS__Class() +local item_boo_stuff = ____exports.item_boo_stuff +item_boo_stuff.name = "item_boo_stuff" +__TS__ClassExtends(item_boo_stuff, BaseItem) +function item_boo_stuff.prototype.OnSpellStart(self) + if not IsServer() then + return + end + local caster = self:GetCaster() + if not caster then + return + end + local stats = self:GetSpecialValueFor("bonus_stats") + if caster:HasModifier("modifier_item_boo_stuff") then + caster:FindModifierByName("modifier_item_boo_stuff"):SetStackCount(caster:FindModifierByName("modifier_item_boo_stuff"):GetStackCount() + stats) + else + caster:AddNewModifier(caster, self, "modifier_item_boo_stuff", {}):SetStackCount(stats) + end + UTIL_Remove(self) +end +item_boo_stuff = __TS__Decorate( + item_boo_stuff, + item_boo_stuff, + {registerAbility(nil)}, + {kind = "class", name = "item_boo_stuff"} +) +____exports.item_boo_stuff = item_boo_stuff +____exports.modifier_item_boo_stuff = __TS__Class() +local modifier_item_boo_stuff = ____exports.modifier_item_boo_stuff +modifier_item_boo_stuff.name = "modifier_item_boo_stuff" +__TS__ClassExtends(modifier_item_boo_stuff, BaseModifier) +function modifier_item_boo_stuff.prototype.RemoveOnDeath(self) + return false +end +function modifier_item_boo_stuff.prototype.IsHidden(self) + return true +end +function modifier_item_boo_stuff.prototype.IsPurgable(self) + return false +end +function modifier_item_boo_stuff.prototype.DeclareFunctions(self) + return {MODIFIER_PROPERTY_PREATTACK_BONUS_DAMAGE, MODIFIER_PROPERTY_ATTACK_RANGE_BONUS} +end +function modifier_item_boo_stuff.prototype.GetModifierPreAttack_BonusDamage(self) + return self:GetStackCount() +end +function modifier_item_boo_stuff.prototype.GetModifierAttackRangeBonus(self) + if not self:GetParent():IsRangedAttacker() then + return 50 + end + return 0 +end +modifier_item_boo_stuff = __TS__Decorate( + modifier_item_boo_stuff, + modifier_item_boo_stuff, + {registerModifier(nil)}, + {kind = "class", name = "modifier_item_boo_stuff"} +) +____exports.modifier_item_boo_stuff = modifier_item_boo_stuff +return ____exports diff --git a/scripts/vscripts/items/blackshop/common/item_injector.lua b/scripts/vscripts/items/blackshop/common/item_injector.lua new file mode 100644 index 0000000..eec00fb --- /dev/null +++ b/scripts/vscripts/items/blackshop/common/item_injector.lua @@ -0,0 +1,69 @@ +local ____lualib = require("lualib_bundle") +local __TS__Class = ____lualib.__TS__Class +local __TS__ClassExtends = ____lualib.__TS__ClassExtends +local __TS__Decorate = ____lualib.__TS__Decorate +local __TS__SourceMapTraceBack = ____lualib.__TS__SourceMapTraceBack +__TS__SourceMapTraceBack(debug.getinfo(1).short_src, {["8"] = 1,["9"] = 1,["10"] = 1,["11"] = 1,["12"] = 1,["13"] = 3,["14"] = 4,["15"] = 3,["16"] = 4,["17"] = 5,["18"] = 6,["21"] = 8,["22"] = 9,["25"] = 11,["26"] = 13,["27"] = 14,["29"] = 16,["31"] = 18,["32"] = 5,["33"] = 4,["34"] = 4,["35"] = 4,["36"] = 3,["39"] = 4,["40"] = 22,["41"] = 23,["42"] = 22,["43"] = 23,["44"] = 24,["45"] = 25,["46"] = 24,["47"] = 27,["48"] = 28,["49"] = 27,["50"] = 31,["51"] = 32,["52"] = 31,["53"] = 35,["54"] = 36,["55"] = 35,["56"] = 39,["57"] = 40,["58"] = 39,["59"] = 43,["60"] = 44,["61"] = 43,["62"] = 23,["63"] = 23,["64"] = 23,["65"] = 22,["68"] = 23}); +local ____exports = {} +local ____dota_ts_adapter = require("lib.dota_ts_adapter") +local BaseItem = ____dota_ts_adapter.BaseItem +local BaseModifier = ____dota_ts_adapter.BaseModifier +local registerAbility = ____dota_ts_adapter.registerAbility +local registerModifier = ____dota_ts_adapter.registerModifier +____exports.item_injector = __TS__Class() +local item_injector = ____exports.item_injector +item_injector.name = "item_injector" +__TS__ClassExtends(item_injector, BaseItem) +function item_injector.prototype.OnSpellStart(self) + if not IsServer() then + return + end + local caster = self:GetCaster() + if not caster then + return + end + local stats = self:GetSpecialValueFor("attack_speed") + if caster:HasModifier("modifier_item_injector") then + caster:FindModifierByName("modifier_item_injector"):SetStackCount(caster:FindModifierByName("modifier_item_injector"):GetStackCount() + stats) + else + caster:AddNewModifier(caster, self, "modifier_item_injector", {}):SetStackCount(stats) + end + UTIL_Remove(self) +end +item_injector = __TS__Decorate( + item_injector, + item_injector, + {registerAbility(nil)}, + {kind = "class", name = "item_injector"} +) +____exports.item_injector = item_injector +____exports.modifier_item_injector = __TS__Class() +local modifier_item_injector = ____exports.modifier_item_injector +modifier_item_injector.name = "modifier_item_injector" +__TS__ClassExtends(modifier_item_injector, BaseModifier) +function modifier_item_injector.prototype.RemoveOnDeath(self) + return false +end +function modifier_item_injector.prototype.IsHidden(self) + return true +end +function modifier_item_injector.prototype.IsPurgable(self) + return false +end +function modifier_item_injector.prototype.DeclareFunctions(self) + return {MODIFIER_PROPERTY_ATTACKSPEED_BONUS_CONSTANT, MODIFIER_PROPERTY_MOVESPEED_BONUS_CONSTANT} +end +function modifier_item_injector.prototype.GetModifierAttackSpeedBonus_Constant(self) + return self:GetStackCount() +end +function modifier_item_injector.prototype.GetModifierMoveSpeedBonus_Constant(self) + return self:GetStackCount() +end +modifier_item_injector = __TS__Decorate( + modifier_item_injector, + modifier_item_injector, + {registerModifier(nil)}, + {kind = "class", name = "modifier_item_injector"} +) +____exports.modifier_item_injector = modifier_item_injector +return ____exports diff --git a/scripts/vscripts/items/blackshop/common/item_king_crown.lua b/scripts/vscripts/items/blackshop/common/item_king_crown.lua new file mode 100644 index 0000000..ab8bf79 --- /dev/null +++ b/scripts/vscripts/items/blackshop/common/item_king_crown.lua @@ -0,0 +1,69 @@ +local ____lualib = require("lualib_bundle") +local __TS__Class = ____lualib.__TS__Class +local __TS__ClassExtends = ____lualib.__TS__ClassExtends +local __TS__Decorate = ____lualib.__TS__Decorate +local __TS__SourceMapTraceBack = ____lualib.__TS__SourceMapTraceBack +__TS__SourceMapTraceBack(debug.getinfo(1).short_src, {["8"] = 1,["9"] = 1,["10"] = 1,["11"] = 1,["12"] = 1,["13"] = 3,["14"] = 4,["15"] = 3,["16"] = 4,["17"] = 5,["18"] = 6,["21"] = 8,["22"] = 9,["25"] = 11,["26"] = 13,["27"] = 14,["29"] = 16,["31"] = 18,["32"] = 5,["33"] = 4,["34"] = 4,["35"] = 4,["36"] = 3,["39"] = 4,["40"] = 22,["41"] = 23,["42"] = 22,["43"] = 23,["44"] = 24,["45"] = 25,["46"] = 24,["47"] = 28,["48"] = 29,["49"] = 28,["50"] = 32,["51"] = 33,["52"] = 32,["53"] = 36,["54"] = 37,["55"] = 36,["56"] = 40,["57"] = 41,["58"] = 40,["59"] = 44,["60"] = 45,["61"] = 44,["62"] = 23,["63"] = 23,["64"] = 23,["65"] = 22,["68"] = 23}); +local ____exports = {} +local ____dota_ts_adapter = require("lib.dota_ts_adapter") +local BaseItem = ____dota_ts_adapter.BaseItem +local BaseModifier = ____dota_ts_adapter.BaseModifier +local registerAbility = ____dota_ts_adapter.registerAbility +local registerModifier = ____dota_ts_adapter.registerModifier +____exports.item_king_crown = __TS__Class() +local item_king_crown = ____exports.item_king_crown +item_king_crown.name = "item_king_crown" +__TS__ClassExtends(item_king_crown, BaseItem) +function item_king_crown.prototype.OnSpellStart(self) + if not IsServer() then + return + end + local caster = self:GetCaster() + if not caster then + return + end + local stats = self:GetSpecialValueFor("bonus_armor") + if caster:HasModifier("modifier_item_king_crown") then + caster:FindModifierByName("modifier_item_king_crown"):SetStackCount(caster:FindModifierByName("modifier_item_king_crown"):GetStackCount() + stats) + else + caster:AddNewModifier(caster, self, "modifier_item_king_crown", {}):SetStackCount(stats) + end + UTIL_Remove(self) +end +item_king_crown = __TS__Decorate( + item_king_crown, + item_king_crown, + {registerAbility(nil)}, + {kind = "class", name = "item_king_crown"} +) +____exports.item_king_crown = item_king_crown +____exports.modifier_item_king_crown = __TS__Class() +local modifier_item_king_crown = ____exports.modifier_item_king_crown +modifier_item_king_crown.name = "modifier_item_king_crown" +__TS__ClassExtends(modifier_item_king_crown, BaseModifier) +function modifier_item_king_crown.prototype.RemoveOnDeath(self) + return false +end +function modifier_item_king_crown.prototype.IsHidden(self) + return false +end +function modifier_item_king_crown.prototype.IsPurgable(self) + return false +end +function modifier_item_king_crown.prototype.DeclareFunctions(self) + return {MODIFIER_PROPERTY_HEALTH_BONUS, MODIFIER_PROPERTY_PHYSICAL_ARMOR_BONUS} +end +function modifier_item_king_crown.prototype.GetModifierHealthBonus(self) + return math.floor(self:GetStackCount() * 10) +end +function modifier_item_king_crown.prototype.GetModifierPhysicalArmorBonus(self) + return self:GetStackCount() +end +modifier_item_king_crown = __TS__Decorate( + modifier_item_king_crown, + modifier_item_king_crown, + {registerModifier(nil)}, + {kind = "class", name = "modifier_item_king_crown"} +) +____exports.modifier_item_king_crown = modifier_item_king_crown +return ____exports diff --git a/scripts/vscripts/items/blackshop/common/item_manaflare.lua b/scripts/vscripts/items/blackshop/common/item_manaflare.lua new file mode 100644 index 0000000..2dc2350 --- /dev/null +++ b/scripts/vscripts/items/blackshop/common/item_manaflare.lua @@ -0,0 +1,69 @@ +local ____lualib = require("lualib_bundle") +local __TS__Class = ____lualib.__TS__Class +local __TS__ClassExtends = ____lualib.__TS__ClassExtends +local __TS__Decorate = ____lualib.__TS__Decorate +local __TS__SourceMapTraceBack = ____lualib.__TS__SourceMapTraceBack +__TS__SourceMapTraceBack(debug.getinfo(1).short_src, {["8"] = 1,["9"] = 1,["10"] = 1,["11"] = 1,["12"] = 1,["13"] = 3,["14"] = 4,["15"] = 3,["16"] = 4,["17"] = 5,["18"] = 6,["21"] = 8,["22"] = 9,["25"] = 11,["26"] = 13,["27"] = 14,["29"] = 16,["31"] = 18,["32"] = 5,["33"] = 4,["34"] = 4,["35"] = 4,["36"] = 3,["39"] = 4,["40"] = 22,["41"] = 23,["42"] = 22,["43"] = 23,["44"] = 24,["45"] = 25,["46"] = 24,["47"] = 27,["48"] = 28,["49"] = 27,["50"] = 31,["51"] = 32,["52"] = 31,["53"] = 35,["54"] = 36,["55"] = 35,["56"] = 39,["57"] = 40,["58"] = 39,["59"] = 43,["60"] = 44,["61"] = 43,["62"] = 23,["63"] = 23,["64"] = 23,["65"] = 22,["68"] = 23}); +local ____exports = {} +local ____dota_ts_adapter = require("lib.dota_ts_adapter") +local BaseItem = ____dota_ts_adapter.BaseItem +local BaseModifier = ____dota_ts_adapter.BaseModifier +local registerAbility = ____dota_ts_adapter.registerAbility +local registerModifier = ____dota_ts_adapter.registerModifier +____exports.item_manaflare = __TS__Class() +local item_manaflare = ____exports.item_manaflare +item_manaflare.name = "item_manaflare" +__TS__ClassExtends(item_manaflare, BaseItem) +function item_manaflare.prototype.OnSpellStart(self) + if not IsServer() then + return + end + local caster = self:GetCaster() + if not caster then + return + end + local stats = self:GetSpecialValueFor("bonus_mana") + if caster:HasModifier("modifier_item_manaflare") then + caster:FindModifierByName("modifier_item_manaflare"):SetStackCount(caster:FindModifierByName("modifier_item_manaflare"):GetStackCount() + stats) + else + caster:AddNewModifier(caster, self, "modifier_item_manaflare", {}):SetStackCount(stats) + end + UTIL_Remove(self) +end +item_manaflare = __TS__Decorate( + item_manaflare, + item_manaflare, + {registerAbility(nil)}, + {kind = "class", name = "item_manaflare"} +) +____exports.item_manaflare = item_manaflare +____exports.modifier_item_manaflare = __TS__Class() +local modifier_item_manaflare = ____exports.modifier_item_manaflare +modifier_item_manaflare.name = "modifier_item_manaflare" +__TS__ClassExtends(modifier_item_manaflare, BaseModifier) +function modifier_item_manaflare.prototype.RemoveOnDeath(self) + return false +end +function modifier_item_manaflare.prototype.IsHidden(self) + return false +end +function modifier_item_manaflare.prototype.IsPurgable(self) + return false +end +function modifier_item_manaflare.prototype.DeclareFunctions(self) + return {MODIFIER_PROPERTY_MANA_BONUS, MODIFIER_PROPERTY_MANA_REGEN_CONSTANT} +end +function modifier_item_manaflare.prototype.GetModifierManaBonus(self) + return self:GetStackCount() +end +function modifier_item_manaflare.prototype.GetModifierConstantManaRegen(self) + return self:GetStackCount() / 100 +end +modifier_item_manaflare = __TS__Decorate( + modifier_item_manaflare, + modifier_item_manaflare, + {registerModifier(nil)}, + {kind = "class", name = "modifier_item_manaflare"} +) +____exports.modifier_item_manaflare = modifier_item_manaflare +return ____exports diff --git a/scripts/vscripts/items/blackshop/common/item_spell_mask.lua b/scripts/vscripts/items/blackshop/common/item_spell_mask.lua new file mode 100644 index 0000000..1aadd03 --- /dev/null +++ b/scripts/vscripts/items/blackshop/common/item_spell_mask.lua @@ -0,0 +1,69 @@ +local ____lualib = require("lualib_bundle") +local __TS__Class = ____lualib.__TS__Class +local __TS__ClassExtends = ____lualib.__TS__ClassExtends +local __TS__Decorate = ____lualib.__TS__Decorate +local __TS__SourceMapTraceBack = ____lualib.__TS__SourceMapTraceBack +__TS__SourceMapTraceBack(debug.getinfo(1).short_src, {["8"] = 1,["9"] = 1,["10"] = 1,["11"] = 1,["12"] = 1,["13"] = 3,["14"] = 4,["15"] = 3,["16"] = 4,["17"] = 5,["18"] = 6,["21"] = 8,["22"] = 9,["25"] = 11,["26"] = 13,["27"] = 14,["29"] = 16,["31"] = 18,["32"] = 5,["33"] = 4,["34"] = 4,["35"] = 4,["36"] = 3,["39"] = 4,["40"] = 22,["41"] = 23,["42"] = 22,["43"] = 23,["44"] = 24,["45"] = 25,["46"] = 24,["47"] = 27,["48"] = 28,["49"] = 27,["50"] = 31,["51"] = 32,["52"] = 31,["53"] = 35,["54"] = 36,["55"] = 35,["56"] = 39,["57"] = 40,["58"] = 39,["59"] = 43,["60"] = 44,["61"] = 43,["62"] = 23,["63"] = 23,["64"] = 23,["65"] = 22,["68"] = 23}); +local ____exports = {} +local ____dota_ts_adapter = require("lib.dota_ts_adapter") +local BaseItem = ____dota_ts_adapter.BaseItem +local BaseModifier = ____dota_ts_adapter.BaseModifier +local registerAbility = ____dota_ts_adapter.registerAbility +local registerModifier = ____dota_ts_adapter.registerModifier +____exports.item_spell_mask = __TS__Class() +local item_spell_mask = ____exports.item_spell_mask +item_spell_mask.name = "item_spell_mask" +__TS__ClassExtends(item_spell_mask, BaseItem) +function item_spell_mask.prototype.OnSpellStart(self) + if not IsServer() then + return + end + local caster = self:GetCaster() + if not caster then + return + end + local stats = self:GetSpecialValueFor("bonus_spell_amp") + if caster:HasModifier("modifier_item_spell_mask") then + caster:FindModifierByName("modifier_item_spell_mask"):SetStackCount(caster:FindModifierByName("modifier_item_spell_mask"):GetStackCount() + stats) + else + caster:AddNewModifier(caster, self, "modifier_item_spell_mask", {}):SetStackCount(stats) + end + UTIL_Remove(self) +end +item_spell_mask = __TS__Decorate( + item_spell_mask, + item_spell_mask, + {registerAbility(nil)}, + {kind = "class", name = "item_spell_mask"} +) +____exports.item_spell_mask = item_spell_mask +____exports.modifier_item_spell_mask = __TS__Class() +local modifier_item_spell_mask = ____exports.modifier_item_spell_mask +modifier_item_spell_mask.name = "modifier_item_spell_mask" +__TS__ClassExtends(modifier_item_spell_mask, BaseModifier) +function modifier_item_spell_mask.prototype.RemoveOnDeath(self) + return false +end +function modifier_item_spell_mask.prototype.IsHidden(self) + return true +end +function modifier_item_spell_mask.prototype.IsPurgable(self) + return false +end +function modifier_item_spell_mask.prototype.DeclareFunctions(self) + return {MODIFIER_PROPERTY_SPELL_AMPLIFY_PERCENTAGE, MODIFIER_PROPERTY_MANA_REGEN_CONSTANT} +end +function modifier_item_spell_mask.prototype.GetModifierSpellAmplify_Percentage(self) + return self:GetStackCount() +end +function modifier_item_spell_mask.prototype.GetModifierConstantManaRegen(self) + return self:GetStackCount() +end +modifier_item_spell_mask = __TS__Decorate( + modifier_item_spell_mask, + modifier_item_spell_mask, + {registerModifier(nil)}, + {kind = "class", name = "modifier_item_spell_mask"} +) +____exports.modifier_item_spell_mask = modifier_item_spell_mask +return ____exports diff --git a/scripts/vscripts/items/blackshop/common/item_stone_armor.lua b/scripts/vscripts/items/blackshop/common/item_stone_armor.lua new file mode 100644 index 0000000..93467c1 --- /dev/null +++ b/scripts/vscripts/items/blackshop/common/item_stone_armor.lua @@ -0,0 +1,66 @@ +local ____lualib = require("lualib_bundle") +local __TS__Class = ____lualib.__TS__Class +local __TS__ClassExtends = ____lualib.__TS__ClassExtends +local __TS__Decorate = ____lualib.__TS__Decorate +local __TS__SourceMapTraceBack = ____lualib.__TS__SourceMapTraceBack +__TS__SourceMapTraceBack(debug.getinfo(1).short_src, {["8"] = 1,["9"] = 1,["10"] = 1,["11"] = 1,["12"] = 1,["13"] = 3,["14"] = 4,["15"] = 3,["16"] = 4,["17"] = 5,["18"] = 6,["21"] = 8,["22"] = 9,["25"] = 11,["26"] = 13,["27"] = 14,["29"] = 16,["31"] = 18,["32"] = 5,["33"] = 4,["34"] = 4,["35"] = 4,["36"] = 3,["39"] = 4,["40"] = 22,["41"] = 23,["42"] = 22,["43"] = 23,["44"] = 24,["45"] = 25,["46"] = 24,["47"] = 27,["48"] = 28,["49"] = 27,["50"] = 31,["51"] = 32,["52"] = 31,["53"] = 35,["54"] = 36,["55"] = 35,["56"] = 39,["57"] = 40,["58"] = 39,["59"] = 23,["60"] = 23,["61"] = 23,["62"] = 22,["65"] = 23}); +local ____exports = {} +local ____dota_ts_adapter = require("lib.dota_ts_adapter") +local BaseItem = ____dota_ts_adapter.BaseItem +local BaseModifier = ____dota_ts_adapter.BaseModifier +local registerAbility = ____dota_ts_adapter.registerAbility +local registerModifier = ____dota_ts_adapter.registerModifier +____exports.item_stone_armor = __TS__Class() +local item_stone_armor = ____exports.item_stone_armor +item_stone_armor.name = "item_stone_armor" +__TS__ClassExtends(item_stone_armor, BaseItem) +function item_stone_armor.prototype.OnSpellStart(self) + if not IsServer() then + return + end + local caster = self:GetCaster() + if not caster then + return + end + local stats = self:GetSpecialValueFor("bonus_armor") + if caster:HasModifier("modifier_item_stone_armor") then + caster:FindModifierByName("modifier_item_stone_armor"):SetStackCount(caster:FindModifierByName("modifier_item_stone_armor"):GetStackCount() + stats) + else + caster:AddNewModifier(caster, self, "modifier_item_stone_armor", {}):SetStackCount(stats) + end + UTIL_Remove(self) +end +item_stone_armor = __TS__Decorate( + item_stone_armor, + item_stone_armor, + {registerAbility(nil)}, + {kind = "class", name = "item_stone_armor"} +) +____exports.item_stone_armor = item_stone_armor +____exports.modifier_item_stone_armor = __TS__Class() +local modifier_item_stone_armor = ____exports.modifier_item_stone_armor +modifier_item_stone_armor.name = "modifier_item_stone_armor" +__TS__ClassExtends(modifier_item_stone_armor, BaseModifier) +function modifier_item_stone_armor.prototype.RemoveOnDeath(self) + return false +end +function modifier_item_stone_armor.prototype.IsHidden(self) + return true +end +function modifier_item_stone_armor.prototype.IsPurgable(self) + return false +end +function modifier_item_stone_armor.prototype.DeclareFunctions(self) + return {MODIFIER_PROPERTY_PHYSICAL_ARMOR_BONUS} +end +function modifier_item_stone_armor.prototype.GetModifierPhysicalArmorBonus(self) + return self:GetStackCount() +end +modifier_item_stone_armor = __TS__Decorate( + modifier_item_stone_armor, + modifier_item_stone_armor, + {registerModifier(nil)}, + {kind = "class", name = "modifier_item_stone_armor"} +) +____exports.modifier_item_stone_armor = modifier_item_stone_armor +return ____exports diff --git a/scripts/vscripts/items/blackshop/cursed/item_blackshop_cursed_glass_pact.lua b/scripts/vscripts/items/blackshop/cursed/item_blackshop_cursed_glass_pact.lua new file mode 100644 index 0000000..52840d1 --- /dev/null +++ b/scripts/vscripts/items/blackshop/cursed/item_blackshop_cursed_glass_pact.lua @@ -0,0 +1,123 @@ +local ____lualib = require("lualib_bundle") +local __TS__Class = ____lualib.__TS__Class +local __TS__ClassExtends = ____lualib.__TS__ClassExtends +local __TS__Decorate = ____lualib.__TS__Decorate +local ____exports = {} +local ____dota_ts_adapter = require("lib.dota_ts_adapter") +local BaseItem = ____dota_ts_adapter.BaseItem +local BaseModifier = ____dota_ts_adapter.BaseModifier +local registerAbility = ____dota_ts_adapter.registerAbility +local registerModifier = ____dota_ts_adapter.registerModifier +--- Скрытый стак исходящего урона — стак = %%. +____exports.modifier_item_glass_pact_outgoing = __TS__Class() +local modifier_item_glass_pact_outgoing = ____exports.modifier_item_glass_pact_outgoing +modifier_item_glass_pact_outgoing.name = "modifier_item_glass_pact_outgoing" +modifier_item_glass_pact_outgoing.____file_path = "scripts/vscripts/items/blackshop/cursed/item_blackshop_cursed_glass_pact.lua" +__TS__ClassExtends(modifier_item_glass_pact_outgoing, BaseModifier) +function modifier_item_glass_pact_outgoing.prototype.RemoveOnDeath(self) + return false +end +function modifier_item_glass_pact_outgoing.prototype.IsHidden(self) + return true +end +function modifier_item_glass_pact_outgoing.prototype.IsPurgable(self) + return false +end +function modifier_item_glass_pact_outgoing.prototype.DeclareFunctions(self) + return {MODIFIER_PROPERTY_DAMAGEOUTGOING_PERCENTAGE} +end +function modifier_item_glass_pact_outgoing.prototype.GetModifierDamageOutgoing_Percentage(self) + return self:GetStackCount() +end +modifier_item_glass_pact_outgoing = __TS__Decorate( + modifier_item_glass_pact_outgoing, + modifier_item_glass_pact_outgoing, + {registerModifier(nil)}, + {kind = "class", name = "modifier_item_glass_pact_outgoing"} +) +____exports.modifier_item_glass_pact_outgoing = modifier_item_glass_pact_outgoing +--- Скрытый стак входящего урона — стак = %%. +____exports.modifier_item_glass_pact_incoming = __TS__Class() +local modifier_item_glass_pact_incoming = ____exports.modifier_item_glass_pact_incoming +modifier_item_glass_pact_incoming.name = "modifier_item_glass_pact_incoming" +modifier_item_glass_pact_incoming.____file_path = "scripts/vscripts/items/blackshop/cursed/item_blackshop_cursed_glass_pact.lua" +__TS__ClassExtends(modifier_item_glass_pact_incoming, BaseModifier) +function modifier_item_glass_pact_incoming.prototype.RemoveOnDeath(self) + return false +end +function modifier_item_glass_pact_incoming.prototype.IsHidden(self) + return true +end +function modifier_item_glass_pact_incoming.prototype.IsPurgable(self) + return false +end +function modifier_item_glass_pact_incoming.prototype.DeclareFunctions(self) + return {MODIFIER_PROPERTY_INCOMING_DAMAGE_PERCENTAGE} +end +function modifier_item_glass_pact_incoming.prototype.GetModifierIncomingDamage_Percentage(self) + return self:GetStackCount() +end +modifier_item_glass_pact_incoming = __TS__Decorate( + modifier_item_glass_pact_incoming, + modifier_item_glass_pact_incoming, + {registerModifier(nil)}, + {kind = "class", name = "modifier_item_glass_pact_incoming"} +) +____exports.modifier_item_glass_pact_incoming = modifier_item_glass_pact_incoming +--- Видимый маркер пакта. +____exports.modifier_item_glass_pact = __TS__Class() +local modifier_item_glass_pact = ____exports.modifier_item_glass_pact +modifier_item_glass_pact.name = "modifier_item_glass_pact" +modifier_item_glass_pact.____file_path = "scripts/vscripts/items/blackshop/cursed/item_blackshop_cursed_glass_pact.lua" +__TS__ClassExtends(modifier_item_glass_pact, BaseModifier) +function modifier_item_glass_pact.prototype.RemoveOnDeath(self) + return false +end +function modifier_item_glass_pact.prototype.IsHidden(self) + return false +end +function modifier_item_glass_pact.prototype.IsPurgable(self) + return false +end +function modifier_item_glass_pact.prototype.GetTexture(self) + return "../items/blackshop/glass_pact" +end +modifier_item_glass_pact = __TS__Decorate( + modifier_item_glass_pact, + modifier_item_glass_pact, + {registerModifier(nil)}, + {kind = "class", name = "modifier_item_glass_pact"} +) +____exports.modifier_item_glass_pact = modifier_item_glass_pact +--- Стеклянный пакт — больше входящего и исходящего урона (один раз на героя). +____exports.item_blackshop_cursed_glass_pact = __TS__Class() +local item_blackshop_cursed_glass_pact = ____exports.item_blackshop_cursed_glass_pact +item_blackshop_cursed_glass_pact.name = "item_blackshop_cursed_glass_pact" +item_blackshop_cursed_glass_pact.____file_path = "scripts/vscripts/items/blackshop/cursed/item_blackshop_cursed_glass_pact.lua" +__TS__ClassExtends(item_blackshop_cursed_glass_pact, BaseItem) +function item_blackshop_cursed_glass_pact.prototype.OnSpellStart(self) + if not IsServer() then + return + end + local caster = self:GetCaster() + if not caster then + return + end + if caster:HasModifier(____exports.modifier_item_glass_pact.name) then + return + end + local outPct = self:GetSpecialValueFor("outgoing_damage_pct") + local inPct = self:GetSpecialValueFor("incoming_damage_pct") + caster:AddNewModifier(caster, self, ____exports.modifier_item_glass_pact_outgoing.name, {}):SetStackCount(outPct) + caster:AddNewModifier(caster, self, ____exports.modifier_item_glass_pact_incoming.name, {}):SetStackCount(inPct) + caster:AddNewModifier(caster, self, ____exports.modifier_item_glass_pact.name, {}):SetStackCount(1) + UTIL_Remove(self) +end +item_blackshop_cursed_glass_pact = __TS__Decorate( + item_blackshop_cursed_glass_pact, + item_blackshop_cursed_glass_pact, + {registerAbility(nil)}, + {kind = "class", name = "item_blackshop_cursed_glass_pact"} +) +____exports.item_blackshop_cursed_glass_pact = item_blackshop_cursed_glass_pact +return ____exports diff --git a/scripts/vscripts/items/blackshop/cursed/item_blackshop_cursed_martyrs_brand.lua b/scripts/vscripts/items/blackshop/cursed/item_blackshop_cursed_martyrs_brand.lua new file mode 100644 index 0000000..70d24bb --- /dev/null +++ b/scripts/vscripts/items/blackshop/cursed/item_blackshop_cursed_martyrs_brand.lua @@ -0,0 +1,141 @@ +local ____lualib = require("lualib_bundle") +local __TS__Class = ____lualib.__TS__Class +local __TS__ClassExtends = ____lualib.__TS__ClassExtends +local __TS__Decorate = ____lualib.__TS__Decorate +local ____exports = {} +local ____dota_ts_adapter = require("lib.dota_ts_adapter") +local BaseItem = ____dota_ts_adapter.BaseItem +local BaseModifier = ____dota_ts_adapter.BaseModifier +local registerAbility = ____dota_ts_adapter.registerAbility +local registerModifier = ____dota_ts_adapter.registerModifier +--- Скрытый стак бонуса урона к атаке — сумма из KV за все слои. +____exports.modifier_item_martyrs_damage = __TS__Class() +local modifier_item_martyrs_damage = ____exports.modifier_item_martyrs_damage +modifier_item_martyrs_damage.name = "modifier_item_martyrs_damage" +modifier_item_martyrs_damage.____file_path = "scripts/vscripts/items/blackshop/cursed/item_blackshop_cursed_martyrs_brand.lua" +__TS__ClassExtends(modifier_item_martyrs_damage, BaseModifier) +function modifier_item_martyrs_damage.prototype.RemoveOnDeath(self) + return false +end +function modifier_item_martyrs_damage.prototype.IsHidden(self) + return true +end +function modifier_item_martyrs_damage.prototype.IsPurgable(self) + return false +end +function modifier_item_martyrs_damage.prototype.DeclareFunctions(self) + return {MODIFIER_PROPERTY_DAMAGEOUTGOING_PERCENTAGE} +end +function modifier_item_martyrs_damage.prototype.GetModifierDamageOutgoing_Percentage(self) + return self:GetStackCount() +end +modifier_item_martyrs_damage = __TS__Decorate( + modifier_item_martyrs_damage, + modifier_item_martyrs_damage, + {registerModifier(nil)}, + {kind = "class", name = "modifier_item_martyrs_damage"} +) +____exports.modifier_item_martyrs_damage = modifier_item_martyrs_damage +--- Скрытый стак %% входящего урона — сумма из KV за все слои. +____exports.modifier_item_martyrs_incoming = __TS__Class() +local modifier_item_martyrs_incoming = ____exports.modifier_item_martyrs_incoming +modifier_item_martyrs_incoming.name = "modifier_item_martyrs_incoming" +modifier_item_martyrs_incoming.____file_path = "scripts/vscripts/items/blackshop/cursed/item_blackshop_cursed_martyrs_brand.lua" +__TS__ClassExtends(modifier_item_martyrs_incoming, BaseModifier) +function modifier_item_martyrs_incoming.prototype.RemoveOnDeath(self) + return false +end +function modifier_item_martyrs_incoming.prototype.IsHidden(self) + return true +end +function modifier_item_martyrs_incoming.prototype.IsPurgable(self) + return false +end +function modifier_item_martyrs_incoming.prototype.DeclareFunctions(self) + return {MODIFIER_PROPERTY_INCOMING_DAMAGE_PERCENTAGE} +end +function modifier_item_martyrs_incoming.prototype.GetModifierIncomingDamage_Percentage(self) + return self:GetStackCount() +end +modifier_item_martyrs_incoming = __TS__Decorate( + modifier_item_martyrs_incoming, + modifier_item_martyrs_incoming, + {registerModifier(nil)}, + {kind = "class", name = "modifier_item_martyrs_incoming"} +) +____exports.modifier_item_martyrs_incoming = modifier_item_martyrs_incoming +--- Видимый маркер клейма (стак = число слоёв). +____exports.modifier_item_martyrs_brand = __TS__Class() +local modifier_item_martyrs_brand = ____exports.modifier_item_martyrs_brand +modifier_item_martyrs_brand.name = "modifier_item_martyrs_brand" +modifier_item_martyrs_brand.____file_path = "scripts/vscripts/items/blackshop/cursed/item_blackshop_cursed_martyrs_brand.lua" +__TS__ClassExtends(modifier_item_martyrs_brand, BaseModifier) +function modifier_item_martyrs_brand.prototype.RemoveOnDeath(self) + return false +end +function modifier_item_martyrs_brand.prototype.IsHidden(self) + return false +end +function modifier_item_martyrs_brand.prototype.IsDebuff(self) + return false +end +function modifier_item_martyrs_brand.prototype.IsPurgable(self) + return false +end +function modifier_item_martyrs_brand.prototype.GetTexture(self) + return "../items/blackshop/martyrs_brand" +end +modifier_item_martyrs_brand = __TS__Decorate( + modifier_item_martyrs_brand, + modifier_item_martyrs_brand, + {registerModifier(nil)}, + {kind = "class", name = "modifier_item_martyrs_brand"} +) +____exports.modifier_item_martyrs_brand = modifier_item_martyrs_brand +--- Клеймо мученика — урон растёт, но ты получаешь больше входящего (стакается). +____exports.item_blackshop_cursed_martyrs_brand = __TS__Class() +local item_blackshop_cursed_martyrs_brand = ____exports.item_blackshop_cursed_martyrs_brand +item_blackshop_cursed_martyrs_brand.name = "item_blackshop_cursed_martyrs_brand" +item_blackshop_cursed_martyrs_brand.____file_path = "scripts/vscripts/items/blackshop/cursed/item_blackshop_cursed_martyrs_brand.lua" +__TS__ClassExtends(item_blackshop_cursed_martyrs_brand, BaseItem) +function item_blackshop_cursed_martyrs_brand.prototype.OnSpellStart(self) + if not IsServer() then + return + end + local caster = self:GetCaster() + if not caster then + return + end + local dmgAdd = self:GetSpecialValueFor("bonus_damage_per") + local incAdd = self:GetSpecialValueFor("incoming_damage_per_stack") + local dMod = ____exports.modifier_item_martyrs_damage.name + local iMod = ____exports.modifier_item_martyrs_incoming.name + local disp = ____exports.modifier_item_martyrs_brand.name + local m = caster:FindModifierByName(dMod) + if m then + m:SetStackCount(m:GetStackCount() + dmgAdd) + else + caster:AddNewModifier(caster, self, dMod, {}):SetStackCount(dmgAdd) + end + m = caster:FindModifierByName(iMod) + if m then + m:SetStackCount(m:GetStackCount() + incAdd) + else + caster:AddNewModifier(caster, self, iMod, {}):SetStackCount(incAdd) + end + m = caster:FindModifierByName(disp) + if m then + m:SetStackCount(m:GetStackCount() + 1) + else + caster:AddNewModifier(caster, self, disp, {}):SetStackCount(1) + end + UTIL_Remove(self) +end +item_blackshop_cursed_martyrs_brand = __TS__Decorate( + item_blackshop_cursed_martyrs_brand, + item_blackshop_cursed_martyrs_brand, + {registerAbility(nil)}, + {kind = "class", name = "item_blackshop_cursed_martyrs_brand"} +) +____exports.item_blackshop_cursed_martyrs_brand = item_blackshop_cursed_martyrs_brand +return ____exports diff --git a/scripts/vscripts/items/blackshop/cursed/item_blackshop_cursed_the_hand_of_gluttony.lua b/scripts/vscripts/items/blackshop/cursed/item_blackshop_cursed_the_hand_of_gluttony.lua new file mode 100644 index 0000000..da244e1 --- /dev/null +++ b/scripts/vscripts/items/blackshop/cursed/item_blackshop_cursed_the_hand_of_gluttony.lua @@ -0,0 +1,196 @@ +local ____lualib = require("lualib_bundle") +local __TS__Class = ____lualib.__TS__Class +local __TS__ClassExtends = ____lualib.__TS__ClassExtends +local __TS__Decorate = ____lualib.__TS__Decorate +local __TS__ArrayFilter = ____lualib.__TS__ArrayFilter +local ____exports = {} +local ____dota_ts_adapter = require("lib.dota_ts_adapter") +local BaseItem = ____dota_ts_adapter.BaseItem +local BaseModifier = ____dota_ts_adapter.BaseModifier +local registerAbility = ____dota_ts_adapter.registerAbility +local registerModifier = ____dota_ts_adapter.registerModifier +____exports.item_blackshop_cursed_the_hand_of_gluttony = __TS__Class() +local item_blackshop_cursed_the_hand_of_gluttony = ____exports.item_blackshop_cursed_the_hand_of_gluttony +item_blackshop_cursed_the_hand_of_gluttony.name = "item_blackshop_cursed_the_hand_of_gluttony" +item_blackshop_cursed_the_hand_of_gluttony.____file_path = "scripts/vscripts/items/blackshop/cursed/item_blackshop_cursed_the_hand_of_gluttony.lua" +__TS__ClassExtends(item_blackshop_cursed_the_hand_of_gluttony, BaseItem) +function item_blackshop_cursed_the_hand_of_gluttony.prototype.OnSpellStart(self) + if not IsServer() then + return + end + local caster = self:GetCaster() + if not caster then + return + end + local stats = self:GetSpecialValueFor("bonus_stats") + if caster:HasModifier("modifier_item_the_hand_of_gluttony") then + caster:FindModifierByName("modifier_item_the_hand_of_gluttony"):SetStackCount(caster:FindModifierByName("modifier_item_the_hand_of_gluttony"):GetStackCount() + stats) + else + caster:AddNewModifier(caster, self, "modifier_item_the_hand_of_gluttony", {}):SetStackCount(stats) + end + UTIL_Remove(self) +end +item_blackshop_cursed_the_hand_of_gluttony = __TS__Decorate( + item_blackshop_cursed_the_hand_of_gluttony, + item_blackshop_cursed_the_hand_of_gluttony, + {registerAbility(nil)}, + {kind = "class", name = "item_blackshop_cursed_the_hand_of_gluttony"} +) +____exports.item_blackshop_cursed_the_hand_of_gluttony = item_blackshop_cursed_the_hand_of_gluttony +____exports.modifier_item_the_hand_of_gluttony = __TS__Class() +local modifier_item_the_hand_of_gluttony = ____exports.modifier_item_the_hand_of_gluttony +modifier_item_the_hand_of_gluttony.name = "modifier_item_the_hand_of_gluttony" +modifier_item_the_hand_of_gluttony.____file_path = "scripts/vscripts/items/blackshop/cursed/item_blackshop_cursed_the_hand_of_gluttony.lua" +__TS__ClassExtends(modifier_item_the_hand_of_gluttony, BaseModifier) +function modifier_item_the_hand_of_gluttony.prototype.RemoveOnDeath(self) + return false +end +function modifier_item_the_hand_of_gluttony.prototype.IsHidden(self) + return false +end +function modifier_item_the_hand_of_gluttony.prototype.IsPurgable(self) + return false +end +function modifier_item_the_hand_of_gluttony.prototype.GetTexture(self) + return "../items/blackshop/the_hand_of_gluttony" +end +function modifier_item_the_hand_of_gluttony.prototype.GetAuraRadius(self) + return self:GetParent():GetBaseAttackRange() + 235 +end +function modifier_item_the_hand_of_gluttony.prototype.GetAuraSearchTeam(self) + return DOTA_UNIT_TARGET_TEAM_FRIENDLY +end +function modifier_item_the_hand_of_gluttony.prototype.GetAuraSearchType(self) + return DOTA_UNIT_TARGET_HERO + DOTA_UNIT_TARGET_BASIC +end +function modifier_item_the_hand_of_gluttony.prototype.GetModifierAura(self) + return "modifier_item_the_hand_of_gluttony_aura" +end +function modifier_item_the_hand_of_gluttony.prototype.IsAura(self) + return true +end +function modifier_item_the_hand_of_gluttony.prototype.DeclareFunctions(self) + return {MODIFIER_EVENT_ON_ATTACK_LANDED, MODIFIER_EVENT_ON_ATTACK_START} +end +function modifier_item_the_hand_of_gluttony.prototype.OnAttackStart(self, event) + if not IsServer() then + return + end + if event.attacker ~= self:GetParent() then + return + end + local parent = self:GetParent() + local units = FindUnitsInRadius( + parent:GetTeamNumber(), + parent:GetAbsOrigin(), + nil, + self:GetAuraRadius(), + DOTA_UNIT_TARGET_TEAM_FRIENDLY, + DOTA_UNIT_TARGET_HERO + DOTA_UNIT_TARGET_BASIC, + DOTA_UNIT_TARGET_FLAG_NONE, + FIND_ANY_ORDER, + false + ) + if #units > 1 and RollPseudoRandomPercentage( + self:GetStackCount() / 12, + 1, + self:GetParent() + ) then + if self:GetParent():HasModifier("modifier_lose_control") then + return + end + local allies = __TS__ArrayFilter( + units, + function(____, unit) return unit ~= parent end + ) + local randomAlly = allies[RandomInt(0, #allies - 1) + 1] + parent:MoveToTargetToAttack(randomAlly) + parent:AddNewModifier( + parent, + self:GetAbility(), + "modifier_lose_control", + {duration = 3} + ) + end +end +function modifier_item_the_hand_of_gluttony.prototype.OnAttackLanded(self, event) + if not IsServer() then + return + end + if event.attacker ~= self:GetParent() then + return + end + local damage = event.damage + local heal = damage * (self:GetStackCount() / 100) + SendOverheadEventMessage( + nil, + OVERHEAD_ALERT_HEAL, + self:GetParent(), + heal, + nil + ) + local healParticle = ParticleManager:CreateParticle( + "particles/generic_gameplay/generic_lifesteal.vpcf", + PATTACH_ABSORIGIN_FOLLOW, + self:GetParent() + ) + ParticleManager:ReleaseParticleIndex(healParticle) + local parent = self:GetParent() + parent:Heal( + heal, + self:GetAbility() + ) +end +modifier_item_the_hand_of_gluttony = __TS__Decorate( + modifier_item_the_hand_of_gluttony, + modifier_item_the_hand_of_gluttony, + {registerModifier(nil)}, + {kind = "class", name = "modifier_item_the_hand_of_gluttony"} +) +____exports.modifier_item_the_hand_of_gluttony = modifier_item_the_hand_of_gluttony +____exports.modifier_item_the_hand_of_gluttony_aura = __TS__Class() +local modifier_item_the_hand_of_gluttony_aura = ____exports.modifier_item_the_hand_of_gluttony_aura +modifier_item_the_hand_of_gluttony_aura.name = "modifier_item_the_hand_of_gluttony_aura" +modifier_item_the_hand_of_gluttony_aura.____file_path = "scripts/vscripts/items/blackshop/cursed/item_blackshop_cursed_the_hand_of_gluttony.lua" +__TS__ClassExtends(modifier_item_the_hand_of_gluttony_aura, BaseModifier) +function modifier_item_the_hand_of_gluttony_aura.prototype.IsHidden(self) + return true +end +function modifier_item_the_hand_of_gluttony_aura.prototype.IsDebuff(self) + return true +end +function modifier_item_the_hand_of_gluttony_aura.prototype.IsPurgable(self) + return false +end +function modifier_item_the_hand_of_gluttony_aura.prototype.CheckState(self) + return {[MODIFIER_STATE_SPECIALLY_DENIABLE] = true} +end +function modifier_item_the_hand_of_gluttony_aura.prototype.GetTexture(self) + return "../items/blackshop/the_hand_of_gluttony" +end +modifier_item_the_hand_of_gluttony_aura = __TS__Decorate( + modifier_item_the_hand_of_gluttony_aura, + modifier_item_the_hand_of_gluttony_aura, + {registerModifier(nil)}, + {kind = "class", name = "modifier_item_the_hand_of_gluttony_aura"} +) +____exports.modifier_item_the_hand_of_gluttony_aura = modifier_item_the_hand_of_gluttony_aura +____exports.modifier_lose_control = __TS__Class() +local modifier_lose_control = ____exports.modifier_lose_control +modifier_lose_control.name = "modifier_lose_control" +modifier_lose_control.____file_path = "scripts/vscripts/items/blackshop/cursed/item_blackshop_cursed_the_hand_of_gluttony.lua" +__TS__ClassExtends(modifier_lose_control, BaseModifier) +function modifier_lose_control.prototype.IsHidden(self) + return false +end +function modifier_lose_control.prototype.CheckState(self) + return {[MODIFIER_STATE_COMMAND_RESTRICTED] = true} +end +modifier_lose_control = __TS__Decorate( + modifier_lose_control, + modifier_lose_control, + {registerModifier(nil)}, + {kind = "class", name = "modifier_lose_control"} +) +____exports.modifier_lose_control = modifier_lose_control +return ____exports diff --git a/scripts/vscripts/items/blackshop/cursed/item_blackshop_cursed_widow_chain.lua b/scripts/vscripts/items/blackshop/cursed/item_blackshop_cursed_widow_chain.lua new file mode 100644 index 0000000..2cc1751 --- /dev/null +++ b/scripts/vscripts/items/blackshop/cursed/item_blackshop_cursed_widow_chain.lua @@ -0,0 +1,201 @@ +local ____lualib = require("lualib_bundle") +local __TS__Class = ____lualib.__TS__Class +local __TS__ClassExtends = ____lualib.__TS__ClassExtends +local __TS__Decorate = ____lualib.__TS__Decorate +local ____exports = {} +local ____dota_ts_adapter = require("lib.dota_ts_adapter") +local BaseItem = ____dota_ts_adapter.BaseItem +local BaseModifier = ____dota_ts_adapter.BaseModifier +local registerAbility = ____dota_ts_adapter.registerAbility +local registerModifier = ____dota_ts_adapter.registerModifier +--- Короткий само-рут после удара (цепь вдовы). +____exports.modifier_item_widow_chain_root = __TS__Class() +local modifier_item_widow_chain_root = ____exports.modifier_item_widow_chain_root +modifier_item_widow_chain_root.name = "modifier_item_widow_chain_root" +modifier_item_widow_chain_root.____file_path = "scripts/vscripts/items/blackshop/cursed/item_blackshop_cursed_widow_chain.lua" +__TS__ClassExtends(modifier_item_widow_chain_root, BaseModifier) +function modifier_item_widow_chain_root.prototype.IsHidden(self) + return false +end +function modifier_item_widow_chain_root.prototype.IsDebuff(self) + return true +end +function modifier_item_widow_chain_root.prototype.IsPurgable(self) + return true +end +function modifier_item_widow_chain_root.prototype.GetTexture(self) + return "../items/blackshop/widow_chain" +end +function modifier_item_widow_chain_root.prototype.CheckState(self) + return {[MODIFIER_STATE_ROOTED] = true} +end +function modifier_item_widow_chain_root.prototype.GetEffectName(self) + return "particles/units/heroes/hero_treant/treant_bramble_root.vpcf" +end +function modifier_item_widow_chain_root.prototype.GetEffectAttachType(self) + return PATTACH_ABSORIGIN_FOLLOW +end +modifier_item_widow_chain_root = __TS__Decorate( + modifier_item_widow_chain_root, + modifier_item_widow_chain_root, + {registerModifier(nil)}, + {kind = "class", name = "modifier_item_widow_chain_root"} +) +____exports.modifier_item_widow_chain_root = modifier_item_widow_chain_root +--- Скрытый стак %% исходящего урона — сумма за слои. +____exports.modifier_item_widow_outgoing = __TS__Class() +local modifier_item_widow_outgoing = ____exports.modifier_item_widow_outgoing +modifier_item_widow_outgoing.name = "modifier_item_widow_outgoing" +modifier_item_widow_outgoing.____file_path = "scripts/vscripts/items/blackshop/cursed/item_blackshop_cursed_widow_chain.lua" +__TS__ClassExtends(modifier_item_widow_outgoing, BaseModifier) +function modifier_item_widow_outgoing.prototype.RemoveOnDeath(self) + return false +end +function modifier_item_widow_outgoing.prototype.IsHidden(self) + return true +end +function modifier_item_widow_outgoing.prototype.IsPurgable(self) + return false +end +function modifier_item_widow_outgoing.prototype.DeclareFunctions(self) + return {MODIFIER_PROPERTY_DAMAGEOUTGOING_PERCENTAGE} +end +function modifier_item_widow_outgoing.prototype.GetModifierDamageOutgoing_Percentage(self) + return self:GetStackCount() +end +modifier_item_widow_outgoing = __TS__Decorate( + modifier_item_widow_outgoing, + modifier_item_widow_outgoing, + {registerModifier(nil)}, + {kind = "class", name = "modifier_item_widow_outgoing"} +) +____exports.modifier_item_widow_outgoing = modifier_item_widow_outgoing +--- Скрытый стак штрафа скорости атаки — сумма %% за слои. +____exports.modifier_item_widow_as = __TS__Class() +local modifier_item_widow_as = ____exports.modifier_item_widow_as +modifier_item_widow_as.name = "modifier_item_widow_as" +modifier_item_widow_as.____file_path = "scripts/vscripts/items/blackshop/cursed/item_blackshop_cursed_widow_chain.lua" +__TS__ClassExtends(modifier_item_widow_as, BaseModifier) +function modifier_item_widow_as.prototype.RemoveOnDeath(self) + return false +end +function modifier_item_widow_as.prototype.IsHidden(self) + return true +end +function modifier_item_widow_as.prototype.IsPurgable(self) + return false +end +function modifier_item_widow_as.prototype.DeclareFunctions(self) + return {MODIFIER_PROPERTY_ATTACKSPEED_PERCENTAGE} +end +function modifier_item_widow_as.prototype.GetModifierAttackSpeedPercentage(self) + return -self:GetStackCount() +end +modifier_item_widow_as = __TS__Decorate( + modifier_item_widow_as, + modifier_item_widow_as, + {registerModifier(nil)}, + {kind = "class", name = "modifier_item_widow_as"} +) +____exports.modifier_item_widow_as = modifier_item_widow_as +--- Видимый маркер цепи (стак = слои; рут по длительности из params при первом создании). +____exports.modifier_item_widow_chain = __TS__Class() +local modifier_item_widow_chain = ____exports.modifier_item_widow_chain +modifier_item_widow_chain.name = "modifier_item_widow_chain" +modifier_item_widow_chain.____file_path = "scripts/vscripts/items/blackshop/cursed/item_blackshop_cursed_widow_chain.lua" +__TS__ClassExtends(modifier_item_widow_chain, BaseModifier) +function modifier_item_widow_chain.prototype.____constructor(self, ...) + BaseModifier.prototype.____constructor(self, ...) + self.rootDuration = 0 +end +function modifier_item_widow_chain.prototype.OnCreated(self, params) + self.rootDuration = params.root_duration or 0 +end +function modifier_item_widow_chain.prototype.RemoveOnDeath(self) + return false +end +function modifier_item_widow_chain.prototype.IsHidden(self) + return false +end +function modifier_item_widow_chain.prototype.IsDebuff(self) + return false +end +function modifier_item_widow_chain.prototype.IsPurgable(self) + return false +end +function modifier_item_widow_chain.prototype.GetTexture(self) + return "../items/blackshop/widow_chain" +end +function modifier_item_widow_chain.prototype.DeclareFunctions(self) + return {MODIFIER_EVENT_ON_ATTACK_LANDED} +end +function modifier_item_widow_chain.prototype.OnAttackLanded(self, event) + if not IsServer() then + return + end + local parent = self:GetParent() + if event.attacker ~= parent or not parent or not parent:IsAlive() then + return + end + parent:AddNewModifier( + parent, + getModifierSourceAbility(nil, parent), + ____exports.modifier_item_widow_chain_root.name, + {duration = self.rootDuration} + ) +end +modifier_item_widow_chain = __TS__Decorate( + modifier_item_widow_chain, + modifier_item_widow_chain, + {registerModifier(nil)}, + {kind = "class", name = "modifier_item_widow_chain"} +) +____exports.modifier_item_widow_chain = modifier_item_widow_chain +--- Цепь вдовы — огромный урон, медленные атаки; каждый удар коренит героя. +____exports.item_blackshop_cursed_widow_chain = __TS__Class() +local item_blackshop_cursed_widow_chain = ____exports.item_blackshop_cursed_widow_chain +item_blackshop_cursed_widow_chain.name = "item_blackshop_cursed_widow_chain" +item_blackshop_cursed_widow_chain.____file_path = "scripts/vscripts/items/blackshop/cursed/item_blackshop_cursed_widow_chain.lua" +__TS__ClassExtends(item_blackshop_cursed_widow_chain, BaseItem) +function item_blackshop_cursed_widow_chain.prototype.OnSpellStart(self) + if not IsServer() then + return + end + local caster = self:GetCaster() + if not caster then + return + end + local dmgAdd = self:GetSpecialValueFor("bonus_outgoing_damage_pct") + local asAdd = self:GetSpecialValueFor("attack_speed_loss_pct") + local rootDur = self:GetSpecialValueFor("root_duration") + local outMod = ____exports.modifier_item_widow_outgoing.name + local asMod = ____exports.modifier_item_widow_as.name + local disp = ____exports.modifier_item_widow_chain.name + local m = caster:FindModifierByName(outMod) + if m then + m:SetStackCount(m:GetStackCount() + dmgAdd) + else + caster:AddNewModifier(caster, self, outMod, {}):SetStackCount(dmgAdd) + end + m = caster:FindModifierByName(asMod) + if m then + m:SetStackCount(m:GetStackCount() + asAdd) + else + caster:AddNewModifier(caster, self, asMod, {}):SetStackCount(asAdd) + end + m = caster:FindModifierByName(disp) + if m then + m:SetStackCount(m:GetStackCount() + 1) + else + caster:AddNewModifier(caster, self, disp, {root_duration = rootDur}):SetStackCount(1) + end + UTIL_Remove(self) +end +item_blackshop_cursed_widow_chain = __TS__Decorate( + item_blackshop_cursed_widow_chain, + item_blackshop_cursed_widow_chain, + {registerAbility(nil)}, + {kind = "class", name = "item_blackshop_cursed_widow_chain"} +) +____exports.item_blackshop_cursed_widow_chain = item_blackshop_cursed_widow_chain +return ____exports diff --git a/scripts/vscripts/items/blackshop/cursed/item_the_hand_of_gluttony.lua b/scripts/vscripts/items/blackshop/cursed/item_the_hand_of_gluttony.lua new file mode 100644 index 0000000..dc9bf63 --- /dev/null +++ b/scripts/vscripts/items/blackshop/cursed/item_the_hand_of_gluttony.lua @@ -0,0 +1,191 @@ +local ____lualib = require("lualib_bundle") +local __TS__Class = ____lualib.__TS__Class +local __TS__ClassExtends = ____lualib.__TS__ClassExtends +local __TS__Decorate = ____lualib.__TS__Decorate +local __TS__ArrayFilter = ____lualib.__TS__ArrayFilter +local __TS__SourceMapTraceBack = ____lualib.__TS__SourceMapTraceBack +__TS__SourceMapTraceBack(debug.getinfo(1).short_src, {["9"] = 1,["10"] = 1,["11"] = 1,["12"] = 1,["13"] = 1,["14"] = 3,["15"] = 4,["16"] = 3,["17"] = 4,["18"] = 5,["19"] = 6,["22"] = 8,["23"] = 9,["26"] = 11,["27"] = 13,["28"] = 14,["30"] = 18,["32"] = 20,["33"] = 5,["34"] = 4,["35"] = 4,["36"] = 4,["37"] = 3,["40"] = 4,["41"] = 24,["42"] = 25,["43"] = 24,["44"] = 25,["45"] = 26,["46"] = 27,["47"] = 26,["48"] = 30,["49"] = 31,["50"] = 30,["51"] = 34,["52"] = 35,["53"] = 34,["54"] = 38,["55"] = 39,["56"] = 38,["57"] = 42,["58"] = 43,["59"] = 42,["60"] = 46,["61"] = 47,["62"] = 46,["63"] = 50,["64"] = 51,["65"] = 50,["66"] = 54,["67"] = 55,["68"] = 54,["69"] = 58,["70"] = 59,["71"] = 58,["72"] = 62,["73"] = 63,["76"] = 64,["79"] = 66,["80"] = 67,["81"] = 67,["82"] = 67,["83"] = 67,["84"] = 67,["85"] = 67,["86"] = 67,["87"] = 67,["88"] = 67,["89"] = 67,["90"] = 67,["91"] = 79,["92"] = 79,["93"] = 79,["94"] = 79,["95"] = 79,["96"] = 80,["99"] = 81,["100"] = 81,["101"] = 81,["102"] = 81,["103"] = 82,["104"] = 83,["105"] = 85,["106"] = 85,["107"] = 85,["108"] = 85,["109"] = 85,["110"] = 85,["112"] = 62,["113"] = 89,["114"] = 90,["117"] = 91,["120"] = 93,["121"] = 94,["122"] = 95,["123"] = 95,["124"] = 95,["125"] = 95,["126"] = 95,["127"] = 95,["128"] = 95,["129"] = 96,["130"] = 96,["131"] = 96,["132"] = 96,["133"] = 96,["134"] = 101,["135"] = 103,["136"] = 104,["137"] = 104,["138"] = 104,["139"] = 104,["140"] = 89,["141"] = 25,["142"] = 25,["143"] = 25,["144"] = 24,["147"] = 25,["148"] = 108,["149"] = 109,["150"] = 108,["151"] = 109,["152"] = 110,["153"] = 111,["154"] = 110,["155"] = 114,["156"] = 115,["157"] = 114,["158"] = 118,["159"] = 119,["160"] = 118,["161"] = 122,["162"] = 123,["163"] = 122,["164"] = 128,["165"] = 129,["166"] = 128,["167"] = 109,["168"] = 109,["169"] = 109,["170"] = 108,["173"] = 109,["174"] = 133,["175"] = 134,["176"] = 133,["177"] = 134,["178"] = 135,["179"] = 136,["180"] = 135,["181"] = 138,["182"] = 139,["183"] = 138,["184"] = 134,["185"] = 134,["186"] = 134,["187"] = 133,["190"] = 134}); +local ____exports = {} +local ____dota_ts_adapter = require("lib.dota_ts_adapter") +local BaseItem = ____dota_ts_adapter.BaseItem +local BaseModifier = ____dota_ts_adapter.BaseModifier +local registerAbility = ____dota_ts_adapter.registerAbility +local registerModifier = ____dota_ts_adapter.registerModifier +____exports.item_the_hand_of_gluttony = __TS__Class() +local item_the_hand_of_gluttony = ____exports.item_the_hand_of_gluttony +item_the_hand_of_gluttony.name = "item_the_hand_of_gluttony" +__TS__ClassExtends(item_the_hand_of_gluttony, BaseItem) +function item_the_hand_of_gluttony.prototype.OnSpellStart(self) + if not IsServer() then + return + end + local caster = self:GetCaster() + if not caster then + return + end + local stats = self:GetSpecialValueFor("bonus_stats") + if caster:HasModifier("modifier_item_the_hand_of_gluttony") then + caster:FindModifierByName("modifier_item_the_hand_of_gluttony"):SetStackCount(caster:FindModifierByName("modifier_item_the_hand_of_gluttony"):GetStackCount() + stats) + else + caster:AddNewModifier(caster, self, "modifier_item_the_hand_of_gluttony", {}):SetStackCount(stats) + end + UTIL_Remove(self) +end +item_the_hand_of_gluttony = __TS__Decorate( + item_the_hand_of_gluttony, + item_the_hand_of_gluttony, + {registerAbility(nil)}, + {kind = "class", name = "item_the_hand_of_gluttony"} +) +____exports.item_the_hand_of_gluttony = item_the_hand_of_gluttony +____exports.modifier_item_the_hand_of_gluttony = __TS__Class() +local modifier_item_the_hand_of_gluttony = ____exports.modifier_item_the_hand_of_gluttony +modifier_item_the_hand_of_gluttony.name = "modifier_item_the_hand_of_gluttony" +__TS__ClassExtends(modifier_item_the_hand_of_gluttony, BaseModifier) +function modifier_item_the_hand_of_gluttony.prototype.RemoveOnDeath(self) + return false +end +function modifier_item_the_hand_of_gluttony.prototype.IsHidden(self) + return false +end +function modifier_item_the_hand_of_gluttony.prototype.IsPurgable(self) + return false +end +function modifier_item_the_hand_of_gluttony.prototype.GetAuraRadius(self) + return self:GetParent():GetBaseAttackRange() + 235 +end +function modifier_item_the_hand_of_gluttony.prototype.GetAuraSearchTeam(self) + return DOTA_UNIT_TARGET_TEAM_FRIENDLY +end +function modifier_item_the_hand_of_gluttony.prototype.GetAuraSearchType(self) + return DOTA_UNIT_TARGET_HERO + DOTA_UNIT_TARGET_BASIC +end +function modifier_item_the_hand_of_gluttony.prototype.GetModifierAura(self) + return "modifier_item_the_hand_of_gluttony_aura" +end +function modifier_item_the_hand_of_gluttony.prototype.IsAura(self) + return true +end +function modifier_item_the_hand_of_gluttony.prototype.DeclareFunctions(self) + return {MODIFIER_EVENT_ON_ATTACK_LANDED, MODIFIER_EVENT_ON_ATTACK_START} +end +function modifier_item_the_hand_of_gluttony.prototype.OnAttackStart(self, event) + if not IsServer() then + return + end + if event.attacker ~= self:GetParent() then + return + end + local parent = self:GetParent() + local units = FindUnitsInRadius( + parent:GetTeamNumber(), + parent:GetAbsOrigin(), + nil, + self:GetAuraRadius(), + DOTA_UNIT_TARGET_TEAM_FRIENDLY, + DOTA_UNIT_TARGET_HERO + DOTA_UNIT_TARGET_BASIC, + DOTA_UNIT_TARGET_FLAG_NONE, + FIND_ANY_ORDER, + false + ) + if #units > 1 and RollPseudoRandomPercentage( + self:GetStackCount() / 12, + 1, + self:GetParent() + ) then + if self:GetParent():HasModifier("modifier_lose_control") then + return + end + local allies = __TS__ArrayFilter( + units, + function(____, unit) return unit ~= parent end + ) + local randomAlly = allies[RandomInt(0, #allies - 1) + 1] + parent:MoveToTargetToAttack(randomAlly) + parent:AddNewModifier( + parent, + self:GetAbility(), + "modifier_lose_control", + {duration = 3} + ) + end +end +function modifier_item_the_hand_of_gluttony.prototype.OnAttackLanded(self, event) + if not IsServer() then + return + end + if event.attacker ~= self:GetParent() then + return + end + local damage = event.damage + local heal = damage * (self:GetStackCount() / 100) + SendOverheadEventMessage( + nil, + OVERHEAD_ALERT_HEAL, + self:GetParent(), + heal, + nil + ) + local healParticle = ParticleManager:CreateParticle( + "particles/generic_gameplay/generic_lifesteal.vpcf", + PATTACH_ABSORIGIN_FOLLOW, + self:GetParent() + ) + ParticleManager:ReleaseParticleIndex(healParticle) + local parent = self:GetParent() + parent:Heal( + heal, + self:GetAbility() + ) +end +modifier_item_the_hand_of_gluttony = __TS__Decorate( + modifier_item_the_hand_of_gluttony, + modifier_item_the_hand_of_gluttony, + {registerModifier(nil)}, + {kind = "class", name = "modifier_item_the_hand_of_gluttony"} +) +____exports.modifier_item_the_hand_of_gluttony = modifier_item_the_hand_of_gluttony +____exports.modifier_item_the_hand_of_gluttony_aura = __TS__Class() +local modifier_item_the_hand_of_gluttony_aura = ____exports.modifier_item_the_hand_of_gluttony_aura +modifier_item_the_hand_of_gluttony_aura.name = "modifier_item_the_hand_of_gluttony_aura" +__TS__ClassExtends(modifier_item_the_hand_of_gluttony_aura, BaseModifier) +function modifier_item_the_hand_of_gluttony_aura.prototype.IsHidden(self) + return true +end +function modifier_item_the_hand_of_gluttony_aura.prototype.IsDebuff(self) + return true +end +function modifier_item_the_hand_of_gluttony_aura.prototype.IsPurgable(self) + return false +end +function modifier_item_the_hand_of_gluttony_aura.prototype.CheckState(self) + return {[MODIFIER_STATE_SPECIALLY_DENIABLE] = true} +end +function modifier_item_the_hand_of_gluttony_aura.prototype.GetTexture(self) + return "item_the_hand_of_gluttony" +end +modifier_item_the_hand_of_gluttony_aura = __TS__Decorate( + modifier_item_the_hand_of_gluttony_aura, + modifier_item_the_hand_of_gluttony_aura, + {registerModifier(nil)}, + {kind = "class", name = "modifier_item_the_hand_of_gluttony_aura"} +) +____exports.modifier_item_the_hand_of_gluttony_aura = modifier_item_the_hand_of_gluttony_aura +____exports.modifier_lose_control = __TS__Class() +local modifier_lose_control = ____exports.modifier_lose_control +modifier_lose_control.name = "modifier_lose_control" +__TS__ClassExtends(modifier_lose_control, BaseModifier) +function modifier_lose_control.prototype.IsHidden(self) + return false +end +function modifier_lose_control.prototype.CheckState(self) + return {[MODIFIER_STATE_COMMAND_RESTRICTED] = true} +end +modifier_lose_control = __TS__Decorate( + modifier_lose_control, + modifier_lose_control, + {registerModifier(nil)}, + {kind = "class", name = "modifier_lose_control"} +) +____exports.modifier_lose_control = modifier_lose_control +return ____exports diff --git a/scripts/vscripts/items/blackshop/epic/item_blackshop_epic_bulwark_plate.lua b/scripts/vscripts/items/blackshop/epic/item_blackshop_epic_bulwark_plate.lua new file mode 100644 index 0000000..8a4e304 --- /dev/null +++ b/scripts/vscripts/items/blackshop/epic/item_blackshop_epic_bulwark_plate.lua @@ -0,0 +1,138 @@ +local ____lualib = require("lualib_bundle") +local __TS__Class = ____lualib.__TS__Class +local __TS__ClassExtends = ____lualib.__TS__ClassExtends +local __TS__Decorate = ____lualib.__TS__Decorate +local ____exports = {} +local ____dota_ts_adapter = require("lib.dota_ts_adapter") +local BaseItem = ____dota_ts_adapter.BaseItem +local BaseModifier = ____dota_ts_adapter.BaseModifier +local registerAbility = ____dota_ts_adapter.registerAbility +local registerModifier = ____dota_ts_adapter.registerModifier +--- Скрытый стак брони — число в стаке = суммарный бонус из KV (как синий воск). +____exports.modifier_item_bulwark_plate_armor = __TS__Class() +local modifier_item_bulwark_plate_armor = ____exports.modifier_item_bulwark_plate_armor +modifier_item_bulwark_plate_armor.name = "modifier_item_bulwark_plate_armor" +modifier_item_bulwark_plate_armor.____file_path = "scripts/vscripts/items/blackshop/epic/item_blackshop_epic_bulwark_plate.lua" +__TS__ClassExtends(modifier_item_bulwark_plate_armor, BaseModifier) +function modifier_item_bulwark_plate_armor.prototype.RemoveOnDeath(self) + return false +end +function modifier_item_bulwark_plate_armor.prototype.IsHidden(self) + return true +end +function modifier_item_bulwark_plate_armor.prototype.IsPurgable(self) + return false +end +function modifier_item_bulwark_plate_armor.prototype.DeclareFunctions(self) + return {MODIFIER_PROPERTY_PHYSICAL_ARMOR_BONUS} +end +function modifier_item_bulwark_plate_armor.prototype.GetModifierPhysicalArmorBonus(self) + return self:GetStackCount() +end +modifier_item_bulwark_plate_armor = __TS__Decorate( + modifier_item_bulwark_plate_armor, + modifier_item_bulwark_plate_armor, + {registerModifier(nil)}, + {kind = "class", name = "modifier_item_bulwark_plate_armor"} +) +____exports.modifier_item_bulwark_plate_armor = modifier_item_bulwark_plate_armor +--- Скрытый стак магического сопротивления — стак = сумма процентов из KV. +____exports.modifier_item_bulwark_plate_mr = __TS__Class() +local modifier_item_bulwark_plate_mr = ____exports.modifier_item_bulwark_plate_mr +modifier_item_bulwark_plate_mr.name = "modifier_item_bulwark_plate_mr" +modifier_item_bulwark_plate_mr.____file_path = "scripts/vscripts/items/blackshop/epic/item_blackshop_epic_bulwark_plate.lua" +__TS__ClassExtends(modifier_item_bulwark_plate_mr, BaseModifier) +function modifier_item_bulwark_plate_mr.prototype.RemoveOnDeath(self) + return false +end +function modifier_item_bulwark_plate_mr.prototype.IsHidden(self) + return true +end +function modifier_item_bulwark_plate_mr.prototype.IsPurgable(self) + return false +end +function modifier_item_bulwark_plate_mr.prototype.DeclareFunctions(self) + return {MODIFIER_PROPERTY_MAGICAL_RESISTANCE_BONUS} +end +function modifier_item_bulwark_plate_mr.prototype.GetModifierMagicalResistanceBonus(self) + return self:GetStackCount() +end +modifier_item_bulwark_plate_mr = __TS__Decorate( + modifier_item_bulwark_plate_mr, + modifier_item_bulwark_plate_mr, + {registerModifier(nil)}, + {kind = "class", name = "modifier_item_bulwark_plate_mr"} +) +____exports.modifier_item_bulwark_plate_mr = modifier_item_bulwark_plate_mr +--- Видимый маркер пластины (без статов — только иконка/слои для игрока). +____exports.modifier_item_bulwark_plate_display = __TS__Class() +local modifier_item_bulwark_plate_display = ____exports.modifier_item_bulwark_plate_display +modifier_item_bulwark_plate_display.name = "modifier_item_bulwark_plate_display" +modifier_item_bulwark_plate_display.____file_path = "scripts/vscripts/items/blackshop/epic/item_blackshop_epic_bulwark_plate.lua" +__TS__ClassExtends(modifier_item_bulwark_plate_display, BaseModifier) +function modifier_item_bulwark_plate_display.prototype.RemoveOnDeath(self) + return false +end +function modifier_item_bulwark_plate_display.prototype.IsHidden(self) + return false +end +function modifier_item_bulwark_plate_display.prototype.IsPurgable(self) + return false +end +function modifier_item_bulwark_plate_display.prototype.GetTexture(self) + return "../items/blackshop/iron_plate" +end +modifier_item_bulwark_plate_display = __TS__Decorate( + modifier_item_bulwark_plate_display, + modifier_item_bulwark_plate_display, + {registerModifier(nil)}, + {kind = "class", name = "modifier_item_bulwark_plate_display"} +) +____exports.modifier_item_bulwark_plate_display = modifier_item_bulwark_plate_display +--- Пластина бастиона — за применение добавляет в стаки брони и МР величины из AbilityValues предмета. +____exports.item_blackshop_epic_bulwark_plate = __TS__Class() +local item_blackshop_epic_bulwark_plate = ____exports.item_blackshop_epic_bulwark_plate +item_blackshop_epic_bulwark_plate.name = "item_blackshop_epic_bulwark_plate" +item_blackshop_epic_bulwark_plate.____file_path = "scripts/vscripts/items/blackshop/epic/item_blackshop_epic_bulwark_plate.lua" +__TS__ClassExtends(item_blackshop_epic_bulwark_plate, BaseItem) +function item_blackshop_epic_bulwark_plate.prototype.OnSpellStart(self) + if not IsServer() then + return + end + local caster = self:GetCaster() + if not caster then + return + end + local armorAdd = self:GetSpecialValueFor("bonus_armor_per") + local mrAdd = self:GetSpecialValueFor("bonus_mr_per") + local armorMod = ____exports.modifier_item_bulwark_plate_armor.name + local mrMod = ____exports.modifier_item_bulwark_plate_mr.name + local disp = ____exports.modifier_item_bulwark_plate_display.name + local m = caster:FindModifierByName(armorMod) + if m then + m:SetStackCount(m:GetStackCount() + armorAdd) + else + caster:AddNewModifier(caster, self, armorMod, {}):SetStackCount(armorAdd) + end + m = caster:FindModifierByName(mrMod) + if m then + m:SetStackCount(m:GetStackCount() + mrAdd) + else + caster:AddNewModifier(caster, self, mrMod, {}):SetStackCount(mrAdd) + end + local d = caster:FindModifierByName(disp) + if d then + d:SetStackCount(d:GetStackCount() + 1) + else + caster:AddNewModifier(caster, self, disp, {}):SetStackCount(1) + end + UTIL_Remove(self) +end +item_blackshop_epic_bulwark_plate = __TS__Decorate( + item_blackshop_epic_bulwark_plate, + item_blackshop_epic_bulwark_plate, + {registerAbility(nil)}, + {kind = "class", name = "item_blackshop_epic_bulwark_plate"} +) +____exports.item_blackshop_epic_bulwark_plate = item_blackshop_epic_bulwark_plate +return ____exports diff --git a/scripts/vscripts/items/blackshop/epic/item_blackshop_epic_critical_paladin_sword.lua b/scripts/vscripts/items/blackshop/epic/item_blackshop_epic_critical_paladin_sword.lua new file mode 100644 index 0000000..bc1e9d5 --- /dev/null +++ b/scripts/vscripts/items/blackshop/epic/item_blackshop_epic_critical_paladin_sword.lua @@ -0,0 +1,33 @@ +local ____lualib = require("lualib_bundle") +local __TS__Class = ____lualib.__TS__Class +local __TS__ClassExtends = ____lualib.__TS__ClassExtends +local __TS__Decorate = ____lualib.__TS__Decorate +local ____exports = {} +local ____dota_ts_adapter = require("lib.dota_ts_adapter") +local BaseItem = ____dota_ts_adapter.BaseItem +local registerAbility = ____dota_ts_adapter.registerAbility +____exports.item_blackshop_epic_critical_paladin_sword = __TS__Class() +local item_blackshop_epic_critical_paladin_sword = ____exports.item_blackshop_epic_critical_paladin_sword +item_blackshop_epic_critical_paladin_sword.name = "item_blackshop_epic_critical_paladin_sword" +item_blackshop_epic_critical_paladin_sword.____file_path = "scripts/vscripts/items/blackshop/epic/item_blackshop_epic_critical_paladin_sword.lua" +__TS__ClassExtends(item_blackshop_epic_critical_paladin_sword, BaseItem) +function item_blackshop_epic_critical_paladin_sword.prototype.OnSpellStart(self) + if not IsServer() then + return + end + local mult = self:GetSpecialValueFor("crit_multiplier") + local stackingCritMod = self:GetCaster():FindModifierByName("modifier_stacking_crit") + if stackingCritMod then + local ability = self + stackingCritMod:AddCustomCrit(0, mult, "item_critical_paladin_sword", ability) + end + UTIL_Remove(self) +end +item_blackshop_epic_critical_paladin_sword = __TS__Decorate( + item_blackshop_epic_critical_paladin_sword, + item_blackshop_epic_critical_paladin_sword, + {registerAbility(nil)}, + {kind = "class", name = "item_blackshop_epic_critical_paladin_sword"} +) +____exports.item_blackshop_epic_critical_paladin_sword = item_blackshop_epic_critical_paladin_sword +return ____exports diff --git a/scripts/vscripts/items/blackshop/epic/item_blackshop_epic_power_of_grow.lua b/scripts/vscripts/items/blackshop/epic/item_blackshop_epic_power_of_grow.lua new file mode 100644 index 0000000..7ac44f8 --- /dev/null +++ b/scripts/vscripts/items/blackshop/epic/item_blackshop_epic_power_of_grow.lua @@ -0,0 +1,105 @@ +local ____lualib = require("lualib_bundle") +local __TS__Class = ____lualib.__TS__Class +local __TS__ClassExtends = ____lualib.__TS__ClassExtends +local __TS__Decorate = ____lualib.__TS__Decorate +local ____exports = {} +local ____dota_ts_adapter = require("lib.dota_ts_adapter") +local BaseItem = ____dota_ts_adapter.BaseItem +local BaseModifier = ____dota_ts_adapter.BaseModifier +local registerAbility = ____dota_ts_adapter.registerAbility +local registerModifier = ____dota_ts_adapter.registerModifier +____exports.item_blackshop_epic_power_of_grow = __TS__Class() +local item_blackshop_epic_power_of_grow = ____exports.item_blackshop_epic_power_of_grow +item_blackshop_epic_power_of_grow.name = "item_blackshop_epic_power_of_grow" +item_blackshop_epic_power_of_grow.____file_path = "scripts/vscripts/items/blackshop/epic/item_blackshop_epic_power_of_grow.lua" +__TS__ClassExtends(item_blackshop_epic_power_of_grow, BaseItem) +function item_blackshop_epic_power_of_grow.prototype.OnSpellStart(self) + if not IsServer() then + return + end + local caster = self:GetCaster() + if not caster then + return + end + local stats = self:GetSpecialValueFor("bonus_stats") + if caster:HasModifier("modifier_item_power_of_grow") then + caster:FindModifierByName("modifier_item_power_of_grow_scale"):SetStackCount(caster:FindModifierByName("modifier_item_power_of_grow_scale"):GetStackCount() + stats) + else + caster:AddNewModifier(caster, self, "modifier_item_power_of_grow", {}) + caster:AddNewModifier(caster, self, "modifier_item_power_of_grow_scale", {}):SetStackCount(stats) + end + UTIL_Remove(self) +end +item_blackshop_epic_power_of_grow = __TS__Decorate( + item_blackshop_epic_power_of_grow, + item_blackshop_epic_power_of_grow, + {registerAbility(nil)}, + {kind = "class", name = "item_blackshop_epic_power_of_grow"} +) +____exports.item_blackshop_epic_power_of_grow = item_blackshop_epic_power_of_grow +____exports.modifier_item_power_of_grow = __TS__Class() +local modifier_item_power_of_grow = ____exports.modifier_item_power_of_grow +modifier_item_power_of_grow.name = "modifier_item_power_of_grow" +modifier_item_power_of_grow.____file_path = "scripts/vscripts/items/blackshop/epic/item_blackshop_epic_power_of_grow.lua" +__TS__ClassExtends(modifier_item_power_of_grow, BaseModifier) +function modifier_item_power_of_grow.prototype.RemoveOnDeath(self) + return false +end +function modifier_item_power_of_grow.prototype.IsHidden(self) + return true +end +function modifier_item_power_of_grow.prototype.IsPurgable(self) + return false +end +function modifier_item_power_of_grow.prototype.DeclareFunctions(self) + return {MODIFIER_PROPERTY_DAMAGEOUTGOING_PERCENTAGE, MODIFIER_PROPERTY_MODEL_SCALE} +end +function modifier_item_power_of_grow.prototype.OnCreated(self) + self:StartIntervalThink(0.1) +end +function modifier_item_power_of_grow.prototype.OnIntervalThink(self) + if not IsServer() then + return + end + local parent = self:GetParent() + local scale = (parent:GetModelScale() - 1) * 100 + self:SetStackCount(scale) +end +function modifier_item_power_of_grow.prototype.GetModifierDamageOutgoing_Percentage(self) + return self:GetStackCount() +end +modifier_item_power_of_grow = __TS__Decorate( + modifier_item_power_of_grow, + modifier_item_power_of_grow, + {registerModifier(nil)}, + {kind = "class", name = "modifier_item_power_of_grow"} +) +____exports.modifier_item_power_of_grow = modifier_item_power_of_grow +____exports.modifier_item_power_of_grow_scale = __TS__Class() +local modifier_item_power_of_grow_scale = ____exports.modifier_item_power_of_grow_scale +modifier_item_power_of_grow_scale.name = "modifier_item_power_of_grow_scale" +modifier_item_power_of_grow_scale.____file_path = "scripts/vscripts/items/blackshop/epic/item_blackshop_epic_power_of_grow.lua" +__TS__ClassExtends(modifier_item_power_of_grow_scale, BaseModifier) +function modifier_item_power_of_grow_scale.prototype.IsHidden(self) + return false +end +function modifier_item_power_of_grow_scale.prototype.RemoveOnDeath(self) + return false +end +function modifier_item_power_of_grow_scale.prototype.DeclareFunctions(self) + return {MODIFIER_PROPERTY_MODEL_SCALE} +end +function modifier_item_power_of_grow_scale.prototype.GetTexture(self) + return "../items/blackshop/magic_mushroom" +end +function modifier_item_power_of_grow_scale.prototype.GetModifierModelScale(self) + return self:GetStackCount() +end +modifier_item_power_of_grow_scale = __TS__Decorate( + modifier_item_power_of_grow_scale, + modifier_item_power_of_grow_scale, + {registerModifier(nil)}, + {kind = "class", name = "modifier_item_power_of_grow_scale"} +) +____exports.modifier_item_power_of_grow_scale = modifier_item_power_of_grow_scale +return ____exports diff --git a/scripts/vscripts/items/blackshop/epic/item_blackshop_epic_trinity_seal.lua b/scripts/vscripts/items/blackshop/epic/item_blackshop_epic_trinity_seal.lua new file mode 100644 index 0000000..37e54e2 --- /dev/null +++ b/scripts/vscripts/items/blackshop/epic/item_blackshop_epic_trinity_seal.lua @@ -0,0 +1,77 @@ +local ____lualib = require("lualib_bundle") +local __TS__Class = ____lualib.__TS__Class +local __TS__ClassExtends = ____lualib.__TS__ClassExtends +local __TS__Decorate = ____lualib.__TS__Decorate +local ____exports = {} +local ____dota_ts_adapter = require("lib.dota_ts_adapter") +local BaseItem = ____dota_ts_adapter.BaseItem +local BaseModifier = ____dota_ts_adapter.BaseModifier +local registerAbility = ____dota_ts_adapter.registerAbility +local registerModifier = ____dota_ts_adapter.registerModifier +--- Печать тройственности — эпик: ко всем атрибутам за применение (стакается). +____exports.item_blackshop_epic_trinity_seal = __TS__Class() +local item_blackshop_epic_trinity_seal = ____exports.item_blackshop_epic_trinity_seal +item_blackshop_epic_trinity_seal.name = "item_blackshop_epic_trinity_seal" +item_blackshop_epic_trinity_seal.____file_path = "scripts/vscripts/items/blackshop/epic/item_blackshop_epic_trinity_seal.lua" +__TS__ClassExtends(item_blackshop_epic_trinity_seal, BaseItem) +function item_blackshop_epic_trinity_seal.prototype.OnSpellStart(self) + if not IsServer() then + return + end + local caster = self:GetCaster() + if not caster then + return + end + local stats = self:GetSpecialValueFor("bonus_all") + if caster:HasModifier("modifier_item_trinity_seal") then + local m = caster:FindModifierByName("modifier_item_trinity_seal") + m:SetStackCount(m:GetStackCount() + stats) + else + caster:AddNewModifier(caster, self, "modifier_item_trinity_seal", {}):SetStackCount(stats) + end + UTIL_Remove(self) +end +item_blackshop_epic_trinity_seal = __TS__Decorate( + item_blackshop_epic_trinity_seal, + item_blackshop_epic_trinity_seal, + {registerAbility(nil)}, + {kind = "class", name = "item_blackshop_epic_trinity_seal"} +) +____exports.item_blackshop_epic_trinity_seal = item_blackshop_epic_trinity_seal +____exports.modifier_item_trinity_seal = __TS__Class() +local modifier_item_trinity_seal = ____exports.modifier_item_trinity_seal +modifier_item_trinity_seal.name = "modifier_item_trinity_seal" +modifier_item_trinity_seal.____file_path = "scripts/vscripts/items/blackshop/epic/item_blackshop_epic_trinity_seal.lua" +__TS__ClassExtends(modifier_item_trinity_seal, BaseModifier) +function modifier_item_trinity_seal.prototype.RemoveOnDeath(self) + return false +end +function modifier_item_trinity_seal.prototype.IsHidden(self) + return true +end +function modifier_item_trinity_seal.prototype.IsPurgable(self) + return false +end +function modifier_item_trinity_seal.prototype.GetTexture(self) + return "../items/blackshop/trinity_seal" +end +function modifier_item_trinity_seal.prototype.DeclareFunctions(self) + return {MODIFIER_PROPERTY_STATS_AGILITY_BONUS, MODIFIER_PROPERTY_STATS_INTELLECT_BONUS, MODIFIER_PROPERTY_STATS_STRENGTH_BONUS} +end +function modifier_item_trinity_seal.prototype.GetModifierBonusStats_Agility(self) + return self:GetStackCount() +end +function modifier_item_trinity_seal.prototype.GetModifierBonusStats_Intellect(self) + return self:GetStackCount() +end +function modifier_item_trinity_seal.prototype.GetModifierBonusStats_Strength(self) + return self:GetStackCount() +end +modifier_item_trinity_seal = __TS__Decorate( + modifier_item_trinity_seal, + modifier_item_trinity_seal, + {registerModifier(nil)}, + {kind = "class", name = "modifier_item_trinity_seal"} +) +____exports.modifier_item_trinity_seal = modifier_item_trinity_seal +return ____exports diff --git a/scripts/vscripts/items/blackshop/epic/item_critical_paladin_sword.lua b/scripts/vscripts/items/blackshop/epic/item_critical_paladin_sword.lua new file mode 100644 index 0000000..dcba362 --- /dev/null +++ b/scripts/vscripts/items/blackshop/epic/item_critical_paladin_sword.lua @@ -0,0 +1,35 @@ +local ____lualib = require("lualib_bundle") +local __TS__Class = ____lualib.__TS__Class +local __TS__ClassExtends = ____lualib.__TS__ClassExtends +local __TS__Decorate = ____lualib.__TS__Decorate +local __TS__SourceMapTraceBack = ____lualib.__TS__SourceMapTraceBack +__TS__SourceMapTraceBack(debug.getinfo(1).short_src, {["8"] = 1,["9"] = 1,["10"] = 1,["11"] = 3,["12"] = 4,["13"] = 3,["14"] = 4,["15"] = 5,["16"] = 6,["19"] = 7,["20"] = 8,["21"] = 9,["22"] = 10,["23"] = 11,["24"] = 12,["26"] = 14,["27"] = 5,["28"] = 4,["29"] = 4,["30"] = 4,["31"] = 3,["34"] = 4}); +local ____exports = {} +local ____dota_ts_adapter = require("lib.dota_ts_adapter") +local BaseItem = ____dota_ts_adapter.BaseItem +local registerAbility = ____dota_ts_adapter.registerAbility +____exports.item_critical_paladin_sword = __TS__Class() +local item_critical_paladin_sword = ____exports.item_critical_paladin_sword +item_critical_paladin_sword.name = "item_critical_paladin_sword" +__TS__ClassExtends(item_critical_paladin_sword, BaseItem) +function item_critical_paladin_sword.prototype.OnSpellStart(self) + if not IsServer() then + return + end + local mult = self:GetSpecialValueFor("crit_multiplier") + print("[Item Test] Adding crit modifier") + local stackingCritMod = self:GetCaster():FindModifierByName("modifier_stacking_crit") + if stackingCritMod then + local ability = self + stackingCritMod:AddCustomCrit(0, mult, "item_critical_paladin_sword", ability) + end + UTIL_Remove(self) +end +item_critical_paladin_sword = __TS__Decorate( + item_critical_paladin_sword, + item_critical_paladin_sword, + {registerAbility(nil)}, + {kind = "class", name = "item_critical_paladin_sword"} +) +____exports.item_critical_paladin_sword = item_critical_paladin_sword +return ____exports diff --git a/scripts/vscripts/items/blackshop/epic/item_power_of_grow.lua b/scripts/vscripts/items/blackshop/epic/item_power_of_grow.lua new file mode 100644 index 0000000..1820174 --- /dev/null +++ b/scripts/vscripts/items/blackshop/epic/item_power_of_grow.lua @@ -0,0 +1,104 @@ +local ____lualib = require("lualib_bundle") +local __TS__Class = ____lualib.__TS__Class +local __TS__ClassExtends = ____lualib.__TS__ClassExtends +local __TS__Decorate = ____lualib.__TS__Decorate +local __TS__SourceMapTraceBack = ____lualib.__TS__SourceMapTraceBack +__TS__SourceMapTraceBack(debug.getinfo(1).short_src, {["8"] = 1,["9"] = 1,["10"] = 1,["11"] = 1,["12"] = 1,["13"] = 3,["14"] = 4,["15"] = 3,["16"] = 4,["17"] = 5,["18"] = 6,["21"] = 8,["22"] = 9,["25"] = 11,["26"] = 13,["27"] = 14,["29"] = 18,["30"] = 19,["32"] = 21,["33"] = 5,["34"] = 4,["35"] = 4,["36"] = 4,["37"] = 3,["40"] = 4,["41"] = 25,["42"] = 26,["43"] = 25,["44"] = 26,["45"] = 27,["46"] = 28,["47"] = 27,["48"] = 31,["49"] = 32,["50"] = 31,["51"] = 35,["52"] = 36,["53"] = 35,["54"] = 39,["55"] = 40,["56"] = 39,["57"] = 43,["58"] = 44,["59"] = 43,["60"] = 47,["61"] = 48,["64"] = 49,["65"] = 50,["66"] = 52,["67"] = 47,["68"] = 55,["69"] = 56,["70"] = 55,["71"] = 26,["72"] = 26,["73"] = 26,["74"] = 25,["77"] = 26,["78"] = 60,["79"] = 61,["80"] = 60,["81"] = 61,["82"] = 62,["83"] = 63,["84"] = 62,["85"] = 65,["86"] = 66,["87"] = 65,["88"] = 68,["89"] = 69,["90"] = 68,["91"] = 71,["92"] = 72,["93"] = 71,["94"] = 74,["95"] = 75,["96"] = 74,["97"] = 61,["98"] = 61,["99"] = 61,["100"] = 60,["103"] = 61}); +local ____exports = {} +local ____dota_ts_adapter = require("lib.dota_ts_adapter") +local BaseItem = ____dota_ts_adapter.BaseItem +local BaseModifier = ____dota_ts_adapter.BaseModifier +local registerAbility = ____dota_ts_adapter.registerAbility +local registerModifier = ____dota_ts_adapter.registerModifier +____exports.item_power_of_grow = __TS__Class() +local item_power_of_grow = ____exports.item_power_of_grow +item_power_of_grow.name = "item_power_of_grow" +__TS__ClassExtends(item_power_of_grow, BaseItem) +function item_power_of_grow.prototype.OnSpellStart(self) + if not IsServer() then + return + end + local caster = self:GetCaster() + if not caster then + return + end + local stats = self:GetSpecialValueFor("bonus_stats") + if caster:HasModifier("modifier_item_power_of_grow") then + caster:FindModifierByName("modifier_item_power_of_grow_scale"):SetStackCount(caster:FindModifierByName("modifier_item_power_of_grow_scale"):GetStackCount() + stats) + else + caster:AddNewModifier(caster, self, "modifier_item_power_of_grow", {}) + caster:AddNewModifier(caster, self, "modifier_item_power_of_grow_scale", {}):SetStackCount(stats) + end + UTIL_Remove(self) +end +item_power_of_grow = __TS__Decorate( + item_power_of_grow, + item_power_of_grow, + {registerAbility(nil)}, + {kind = "class", name = "item_power_of_grow"} +) +____exports.item_power_of_grow = item_power_of_grow +____exports.modifier_item_power_of_grow = __TS__Class() +local modifier_item_power_of_grow = ____exports.modifier_item_power_of_grow +modifier_item_power_of_grow.name = "modifier_item_power_of_grow" +__TS__ClassExtends(modifier_item_power_of_grow, BaseModifier) +function modifier_item_power_of_grow.prototype.RemoveOnDeath(self) + return false +end +function modifier_item_power_of_grow.prototype.IsHidden(self) + return true +end +function modifier_item_power_of_grow.prototype.IsPurgable(self) + return false +end +function modifier_item_power_of_grow.prototype.DeclareFunctions(self) + return {MODIFIER_PROPERTY_DAMAGEOUTGOING_PERCENTAGE, MODIFIER_PROPERTY_MODEL_SCALE} +end +function modifier_item_power_of_grow.prototype.OnCreated(self) + self:StartIntervalThink(0.1) +end +function modifier_item_power_of_grow.prototype.OnIntervalThink(self) + if not IsServer() then + return + end + local parent = self:GetParent() + local scale = (parent:GetModelScale() - 1) * 100 + self:SetStackCount(scale) +end +function modifier_item_power_of_grow.prototype.GetModifierDamageOutgoing_Percentage(self) + return self:GetStackCount() +end +modifier_item_power_of_grow = __TS__Decorate( + modifier_item_power_of_grow, + modifier_item_power_of_grow, + {registerModifier(nil)}, + {kind = "class", name = "modifier_item_power_of_grow"} +) +____exports.modifier_item_power_of_grow = modifier_item_power_of_grow +____exports.modifier_item_power_of_grow_scale = __TS__Class() +local modifier_item_power_of_grow_scale = ____exports.modifier_item_power_of_grow_scale +modifier_item_power_of_grow_scale.name = "modifier_item_power_of_grow_scale" +__TS__ClassExtends(modifier_item_power_of_grow_scale, BaseModifier) +function modifier_item_power_of_grow_scale.prototype.IsHidden(self) + return false +end +function modifier_item_power_of_grow_scale.prototype.RemoveOnDeath(self) + return false +end +function modifier_item_power_of_grow_scale.prototype.DeclareFunctions(self) + return {MODIFIER_PROPERTY_MODEL_SCALE} +end +function modifier_item_power_of_grow_scale.prototype.GetTexture(self) + return "blackshop/magic_mushroom" +end +function modifier_item_power_of_grow_scale.prototype.GetModifierModelScale(self) + return self:GetStackCount() +end +modifier_item_power_of_grow_scale = __TS__Decorate( + modifier_item_power_of_grow_scale, + modifier_item_power_of_grow_scale, + {registerModifier(nil)}, + {kind = "class", name = "modifier_item_power_of_grow_scale"} +) +____exports.modifier_item_power_of_grow_scale = modifier_item_power_of_grow_scale +return ____exports diff --git a/scripts/vscripts/items/blackshop/heavenly/item_blackshop_heavenly_dawn_chorus.lua b/scripts/vscripts/items/blackshop/heavenly/item_blackshop_heavenly_dawn_chorus.lua new file mode 100644 index 0000000..f01981c --- /dev/null +++ b/scripts/vscripts/items/blackshop/heavenly/item_blackshop_heavenly_dawn_chorus.lua @@ -0,0 +1,161 @@ +local ____lualib = require("lualib_bundle") +local __TS__Class = ____lualib.__TS__Class +local __TS__ClassExtends = ____lualib.__TS__ClassExtends +local __TS__Decorate = ____lualib.__TS__Decorate +local ____exports = {} +local ____dota_ts_adapter = require("lib.dota_ts_adapter") +local BaseItem = ____dota_ts_adapter.BaseItem +local BaseModifier = ____dota_ts_adapter.BaseModifier +local registerAbility = ____dota_ts_adapter.registerAbility +local registerModifier = ____dota_ts_adapter.registerModifier +____exports.modifier_item_heavenly_dawn_chorus_status = __TS__Class() +local modifier_item_heavenly_dawn_chorus_status = ____exports.modifier_item_heavenly_dawn_chorus_status +modifier_item_heavenly_dawn_chorus_status.name = "modifier_item_heavenly_dawn_chorus_status" +modifier_item_heavenly_dawn_chorus_status.____file_path = "scripts/vscripts/items/blackshop/heavenly/item_blackshop_heavenly_dawn_chorus.lua" +__TS__ClassExtends(modifier_item_heavenly_dawn_chorus_status, BaseModifier) +function modifier_item_heavenly_dawn_chorus_status.prototype.RemoveOnDeath(self) + return false +end +function modifier_item_heavenly_dawn_chorus_status.prototype.IsHidden(self) + return true +end +function modifier_item_heavenly_dawn_chorus_status.prototype.IsPurgable(self) + return false +end +function modifier_item_heavenly_dawn_chorus_status.prototype.DeclareFunctions(self) + return {MODIFIER_PROPERTY_STATUS_RESISTANCE_STACKING} +end +function modifier_item_heavenly_dawn_chorus_status.prototype.GetModifierStatusResistanceStacking(self) + return self:GetStackCount() +end +modifier_item_heavenly_dawn_chorus_status = __TS__Decorate( + modifier_item_heavenly_dawn_chorus_status, + modifier_item_heavenly_dawn_chorus_status, + {registerModifier(nil)}, + {kind = "class", name = "modifier_item_heavenly_dawn_chorus_status"} +) +____exports.modifier_item_heavenly_dawn_chorus_status = modifier_item_heavenly_dawn_chorus_status +____exports.modifier_item_heavenly_dawn_chorus_mr = __TS__Class() +local modifier_item_heavenly_dawn_chorus_mr = ____exports.modifier_item_heavenly_dawn_chorus_mr +modifier_item_heavenly_dawn_chorus_mr.name = "modifier_item_heavenly_dawn_chorus_mr" +modifier_item_heavenly_dawn_chorus_mr.____file_path = "scripts/vscripts/items/blackshop/heavenly/item_blackshop_heavenly_dawn_chorus.lua" +__TS__ClassExtends(modifier_item_heavenly_dawn_chorus_mr, BaseModifier) +function modifier_item_heavenly_dawn_chorus_mr.prototype.RemoveOnDeath(self) + return false +end +function modifier_item_heavenly_dawn_chorus_mr.prototype.IsHidden(self) + return true +end +function modifier_item_heavenly_dawn_chorus_mr.prototype.IsPurgable(self) + return false +end +function modifier_item_heavenly_dawn_chorus_mr.prototype.DeclareFunctions(self) + return {MODIFIER_PROPERTY_MAGICAL_RESISTANCE_BONUS} +end +function modifier_item_heavenly_dawn_chorus_mr.prototype.GetModifierMagicalResistanceBonus(self) + return self:GetStackCount() +end +modifier_item_heavenly_dawn_chorus_mr = __TS__Decorate( + modifier_item_heavenly_dawn_chorus_mr, + modifier_item_heavenly_dawn_chorus_mr, + {registerModifier(nil)}, + {kind = "class", name = "modifier_item_heavenly_dawn_chorus_mr"} +) +____exports.modifier_item_heavenly_dawn_chorus_mr = modifier_item_heavenly_dawn_chorus_mr +____exports.modifier_item_heavenly_dawn_chorus_display = __TS__Class() +local modifier_item_heavenly_dawn_chorus_display = ____exports.modifier_item_heavenly_dawn_chorus_display +modifier_item_heavenly_dawn_chorus_display.name = "modifier_item_heavenly_dawn_chorus_display" +modifier_item_heavenly_dawn_chorus_display.____file_path = "scripts/vscripts/items/blackshop/heavenly/item_blackshop_heavenly_dawn_chorus.lua" +__TS__ClassExtends(modifier_item_heavenly_dawn_chorus_display, BaseModifier) +function modifier_item_heavenly_dawn_chorus_display.prototype.RemoveOnDeath(self) + return false +end +function modifier_item_heavenly_dawn_chorus_display.prototype.IsHidden(self) + return false +end +function modifier_item_heavenly_dawn_chorus_display.prototype.IsPurgable(self) + return false +end +function modifier_item_heavenly_dawn_chorus_display.prototype.GetTexture(self) + return "../items/blackshop/dawn_chorus" +end +modifier_item_heavenly_dawn_chorus_display = __TS__Decorate( + modifier_item_heavenly_dawn_chorus_display, + modifier_item_heavenly_dawn_chorus_display, + {registerModifier(nil)}, + {kind = "class", name = "modifier_item_heavenly_dawn_chorus_display"} +) +____exports.modifier_item_heavenly_dawn_chorus_display = modifier_item_heavenly_dawn_chorus_display +--- Рассветный хор — каждому союзнику в радиусе в стаки добавляет значения из KV. +____exports.item_blackshop_heavenly_dawn_chorus = __TS__Class() +local item_blackshop_heavenly_dawn_chorus = ____exports.item_blackshop_heavenly_dawn_chorus +item_blackshop_heavenly_dawn_chorus.name = "item_blackshop_heavenly_dawn_chorus" +item_blackshop_heavenly_dawn_chorus.____file_path = "scripts/vscripts/items/blackshop/heavenly/item_blackshop_heavenly_dawn_chorus.lua" +__TS__ClassExtends(item_blackshop_heavenly_dawn_chorus, BaseItem) +function item_blackshop_heavenly_dawn_chorus.prototype.GetAOERadius(self) + return self:GetSpecialValueFor("radius") +end +function item_blackshop_heavenly_dawn_chorus.prototype.OnSpellStart(self) + if not IsServer() then + return + end + local caster = self:GetCaster() + if not caster or not caster:IsRealHero() then + return + end + local radius = self:GetSpecialValueFor("radius") + local srAdd = self:GetSpecialValueFor("status_resist") + local mrAdd = self:GetSpecialValueFor("magic_resist") + local ms = ____exports.modifier_item_heavenly_dawn_chorus_status.name + local mm = ____exports.modifier_item_heavenly_dawn_chorus_mr.name + local md = ____exports.modifier_item_heavenly_dawn_chorus_display.name + EmitSoundOn("Hero_Chen.HandOfGod", caster) + local allies = FindUnitsInRadius( + caster:GetTeamNumber(), + caster:GetAbsOrigin(), + nil, + radius, + DOTA_UNIT_TARGET_TEAM_FRIENDLY, + DOTA_UNIT_TARGET_HERO, + DOTA_UNIT_TARGET_FLAG_NOT_ILLUSIONS, + FIND_ANY_ORDER, + false + ) + for ____, hero in ipairs(allies) do + do + if not hero or not IsValidEntity(hero) or not hero:IsAlive() then + goto __continue20 + end + local m = hero:FindModifierByName(ms) + if m then + m:SetStackCount(m:GetStackCount() + srAdd) + else + hero:AddNewModifier(caster, self, ms, {}):SetStackCount(srAdd) + end + m = hero:FindModifierByName(mm) + if m then + m:SetStackCount(m:GetStackCount() + mrAdd) + else + hero:AddNewModifier(caster, self, mm, {}):SetStackCount(mrAdd) + end + local d = hero:FindModifierByName(md) + if d then + d:SetStackCount(d:GetStackCount() + 1) + else + hero:AddNewModifier(caster, self, md, {}):SetStackCount(1) + end + local pfx = ParticleManager:CreateParticle("particles/items_fx/healing_clarity.vpcf", PATTACH_ABSORIGIN_FOLLOW, hero) + ParticleManager:ReleaseParticleIndex(pfx) + end + ::__continue20:: + end + UTIL_Remove(self) +end +item_blackshop_heavenly_dawn_chorus = __TS__Decorate( + item_blackshop_heavenly_dawn_chorus, + item_blackshop_heavenly_dawn_chorus, + {registerAbility(nil)}, + {kind = "class", name = "item_blackshop_heavenly_dawn_chorus"} +) +____exports.item_blackshop_heavenly_dawn_chorus = item_blackshop_heavenly_dawn_chorus +return ____exports diff --git a/scripts/vscripts/items/blackshop/heavenly/item_blackshop_heavenly_font_of_mercy.lua b/scripts/vscripts/items/blackshop/heavenly/item_blackshop_heavenly_font_of_mercy.lua new file mode 100644 index 0000000..5580b63 --- /dev/null +++ b/scripts/vscripts/items/blackshop/heavenly/item_blackshop_heavenly_font_of_mercy.lua @@ -0,0 +1,345 @@ +local ____lualib = require("lualib_bundle") +local __TS__Class = ____lualib.__TS__Class +local __TS__ClassExtends = ____lualib.__TS__ClassExtends +local __TS__Decorate = ____lualib.__TS__Decorate +local __TS__ObjectAssign = ____lualib.__TS__ObjectAssign +local ____exports = {} +local ____dota_ts_adapter = require("lib.dota_ts_adapter") +local BaseItem = ____dota_ts_adapter.BaseItem +local BaseModifier = ____dota_ts_adapter.BaseModifier +local registerAbility = ____dota_ts_adapter.registerAbility +local registerModifier = ____dota_ts_adapter.registerModifier +local ____heal_tracker = require("utils.heal_tracker") +local HealWithBattlePass = ____heal_tracker.HealWithBattlePass +local function fontMercyReadParams(self, raw) + if not raw then + return nil + end + local t = raw + local heal_flat = t.heal_flat + local heal_max_hp_pct = t.heal_max_hp_pct + local radius = t.radius + local crisis_hp_pct = t.crisis_hp_pct + local hero_cooldown = t.hero_cooldown + if heal_flat == nil or heal_max_hp_pct == nil or radius == nil or crisis_hp_pct == nil or hero_cooldown == nil then + return nil + end + return { + heal_flat = heal_flat, + heal_max_hp_pct = heal_max_hp_pct, + radius = radius, + crisis_hp_pct = crisis_hp_pct, + hero_cooldown = hero_cooldown + } +end +local function fontMercyHasCrisisAlly(self, caster, v) + local allies = FindUnitsInRadius( + caster:GetTeamNumber(), + caster:GetAbsOrigin(), + nil, + v.radius, + DOTA_UNIT_TARGET_TEAM_FRIENDLY, + DOTA_UNIT_TARGET_HERO, + DOTA_UNIT_TARGET_FLAG_NOT_ILLUSIONS, + FIND_ANY_ORDER, + false + ) + for ____, hero in ipairs(allies) do + do + if not hero or not IsValidEntity(hero) or not hero:IsAlive() then + goto __continue6 + end + if hero:GetHealthPercent() < v.crisis_hp_pct then + return true + end + end + ::__continue6:: + end + return false +end +local function fontMercyPerformEffect(self, caster, v, refAbility) + local pctHeal = v.heal_max_hp_pct / 100 + EmitSoundOn("Hero_Omniknight.GuardianAngel", caster) + local allies = FindUnitsInRadius( + caster:GetTeamNumber(), + caster:GetAbsOrigin(), + nil, + v.radius, + DOTA_UNIT_TARGET_TEAM_FRIENDLY, + DOTA_UNIT_TARGET_HERO, + DOTA_UNIT_TARGET_FLAG_NOT_ILLUSIONS, + FIND_ANY_ORDER, + false + ) + for ____, hero in ipairs(allies) do + do + if not hero or not IsValidEntity(hero) or not hero:IsAlive() then + goto __continue11 + end + local amount = v.heal_flat + hero:GetMaxHealth() * pctHeal + if amount > 0 then + if hero == caster then + hero:Heal(amount, refAbility) + else + HealWithBattlePass( + nil, + hero, + amount, + refAbility, + caster, + true + ) + end + end + hero:Purge( + false, + true, + false, + false, + false + ) + local pfx = ParticleManager:CreateParticle("particles/units/heroes/hero_omniknight/omniknight_purification_cast.vpcf", PATTACH_ABSORIGIN_FOLLOW, hero) + ParticleManager:ReleaseParticleIndex(pfx) + end + ::__continue11:: + end +end +--- Скрытая логика (как status/mr у рассветного хора): тикер и KV-заначка; без иконки в столбце бафов. +-- После каждого срабатывания купели следующий интервал: hero_cooldown / 2^(N−1), N — номер уже случившегося прока. +____exports.modifier_item_heavenly_font_of_mercy_engine = __TS__Class() +local modifier_item_heavenly_font_of_mercy_engine = ____exports.modifier_item_heavenly_font_of_mercy_engine +modifier_item_heavenly_font_of_mercy_engine.name = "modifier_item_heavenly_font_of_mercy_engine" +modifier_item_heavenly_font_of_mercy_engine.____file_path = "scripts/vscripts/items/blackshop/heavenly/item_blackshop_heavenly_font_of_mercy.lua" +__TS__ClassExtends(modifier_item_heavenly_font_of_mercy_engine, BaseModifier) +function modifier_item_heavenly_font_of_mercy_engine.prototype.____constructor(self, ...) + BaseModifier.prototype.____constructor(self, ...) + self.cdUntil = 0 + self.mercyProcCount = 0 +end +function modifier_item_heavenly_font_of_mercy_engine.prototype.RemoveOnDeath(self) + return false +end +function modifier_item_heavenly_font_of_mercy_engine.prototype.IsHidden(self) + return true +end +function modifier_item_heavenly_font_of_mercy_engine.prototype.IsPurgable(self) + return false +end +function modifier_item_heavenly_font_of_mercy_engine.prototype.OnCreated(self, params) + if not IsServer() then + return + end + local v = fontMercyReadParams(nil, params) + if not v then + return + end + self.values = v + local t = params + self.cdUntil = t.cd_until or 0 + self.mercyProcCount = t.mercy_proc_count or 0 + self:StartIntervalThink(____exports.modifier_item_heavenly_font_of_mercy_engine.thinkInterval) + self:refreshCooldownVisual() +end +function modifier_item_heavenly_font_of_mercy_engine.prototype.nextCooldownDuration(self) + local base = self.values.hero_cooldown + local exp = math.max(0, self.mercyProcCount - 1) + return math.max(____exports.modifier_item_heavenly_font_of_mercy_engine.cooldownFloor, base / 2 ^ exp) +end +function modifier_item_heavenly_font_of_mercy_engine.prototype.refreshCooldownVisual(self) + if not IsServer() then + return + end + local hero = self:GetParent() + if not hero or not IsValidEntity(hero) then + return + end + local name = ____exports.modifier_item_heavenly_font_of_mercy_cooldown.name + local now = GameRules:GetGameTime() + local remaining = self.cdUntil - now + local existing = hero:FindModifierByName(name) + if remaining <= 0.05 then + if existing then + existing:Destroy() + end + return + end + if existing then + existing:SetDuration(remaining, true) + else + local created = hero:AddNewModifier( + hero, + getModifierSourceAbility(nil, hero), + name, + {} + ) + created:SetDuration(remaining, true) + end +end +function modifier_item_heavenly_font_of_mercy_engine.prototype.OnIntervalThink(self) + local hero = self:GetParent() + if not hero or not hero:IsRealHero() or not IsValidEntity(hero) or not hero:IsAlive() then + return + end + if not self.values then + return + end + local now = GameRules:GetGameTime() + if now < self.cdUntil then + return + end + if not fontMercyHasCrisisAlly(nil, hero, self.values) then + return + end + fontMercyPerformEffect(nil, hero, self.values, nil) + self.mercyProcCount = self.mercyProcCount + 1 + self.cdUntil = now + self:nextCooldownDuration() + self:refreshCooldownVisual() + local disp = hero:FindModifierByName(____exports.modifier_item_heavenly_font_of_mercy_display.name) + if disp then + disp:SetStackCount(disp:GetStackCount() + 1) + end +end +modifier_item_heavenly_font_of_mercy_engine.thinkInterval = 0.35 +modifier_item_heavenly_font_of_mercy_engine.cooldownFloor = 12 +modifier_item_heavenly_font_of_mercy_engine = __TS__Decorate( + modifier_item_heavenly_font_of_mercy_engine, + modifier_item_heavenly_font_of_mercy_engine, + {registerModifier(nil)}, + {kind = "class", name = "modifier_item_heavenly_font_of_mercy_engine"} +) +____exports.modifier_item_heavenly_font_of_mercy_engine = modifier_item_heavenly_font_of_mercy_engine +--- Отображение оставшегося КД автопрока (круг на иконке бафа). +____exports.modifier_item_heavenly_font_of_mercy_cooldown = __TS__Class() +local modifier_item_heavenly_font_of_mercy_cooldown = ____exports.modifier_item_heavenly_font_of_mercy_cooldown +modifier_item_heavenly_font_of_mercy_cooldown.name = "modifier_item_heavenly_font_of_mercy_cooldown" +modifier_item_heavenly_font_of_mercy_cooldown.____file_path = "scripts/vscripts/items/blackshop/heavenly/item_blackshop_heavenly_font_of_mercy.lua" +__TS__ClassExtends(modifier_item_heavenly_font_of_mercy_cooldown, BaseModifier) +function modifier_item_heavenly_font_of_mercy_cooldown.prototype.RemoveOnDeath(self) + return false +end +function modifier_item_heavenly_font_of_mercy_cooldown.prototype.IsHidden(self) + return false +end +function modifier_item_heavenly_font_of_mercy_cooldown.prototype.IsDebuff(self) + return false +end +function modifier_item_heavenly_font_of_mercy_cooldown.prototype.IsPurgable(self) + return false +end +function modifier_item_heavenly_font_of_mercy_cooldown.prototype.GetTexture(self) + return "../items/blackshop/holy_mercy" +end +function modifier_item_heavenly_font_of_mercy_cooldown.prototype.DeclareFunctions(self) + return {MODIFIER_EVENT_ON_DEATH} +end +function modifier_item_heavenly_font_of_mercy_cooldown.prototype.OnDeath(self, keys) + local parent = self:GetParent() + if not parent then + return + end + local eng = ____exports.modifier_item_heavenly_font_of_mercy_engine.name + local disp = ____exports.modifier_item_heavenly_font_of_mercy_display.name + local m = parent:FindModifierByName(eng) + if m then + m:Destroy() + end + local d = parent:FindModifierByName(disp) + if d then + d:Destroy() + end +end +modifier_item_heavenly_font_of_mercy_cooldown = __TS__Decorate( + modifier_item_heavenly_font_of_mercy_cooldown, + modifier_item_heavenly_font_of_mercy_cooldown, + {registerModifier(nil)}, + {kind = "class", name = "modifier_item_heavenly_font_of_mercy_cooldown"} +) +____exports.modifier_item_heavenly_font_of_mercy_cooldown = modifier_item_heavenly_font_of_mercy_cooldown +--- Иконка в UI (как modifier_item_heavenly_dawn_chorus_display). +____exports.modifier_item_heavenly_font_of_mercy_display = __TS__Class() +local modifier_item_heavenly_font_of_mercy_display = ____exports.modifier_item_heavenly_font_of_mercy_display +modifier_item_heavenly_font_of_mercy_display.name = "modifier_item_heavenly_font_of_mercy_display" +modifier_item_heavenly_font_of_mercy_display.____file_path = "scripts/vscripts/items/blackshop/heavenly/item_blackshop_heavenly_font_of_mercy.lua" +__TS__ClassExtends(modifier_item_heavenly_font_of_mercy_display, BaseModifier) +function modifier_item_heavenly_font_of_mercy_display.prototype.RemoveOnDeath(self) + return false +end +function modifier_item_heavenly_font_of_mercy_display.prototype.IsHidden(self) + return false +end +function modifier_item_heavenly_font_of_mercy_display.prototype.IsPurgable(self) + return false +end +function modifier_item_heavenly_font_of_mercy_display.prototype.GetTexture(self) + return "../items/blackshop/holy_mercy" +end +modifier_item_heavenly_font_of_mercy_display = __TS__Decorate( + modifier_item_heavenly_font_of_mercy_display, + modifier_item_heavenly_font_of_mercy_display, + {registerModifier(nil)}, + {kind = "class", name = "modifier_item_heavenly_font_of_mercy_display"} +) +____exports.modifier_item_heavenly_font_of_mercy_display = modifier_item_heavenly_font_of_mercy_display +--- Купель милости — расходник; после применения как покров: вечные модификаторы + автопрок по условиям из KV. +____exports.item_blackshop_heavenly_font_of_mercy = __TS__Class() +local item_blackshop_heavenly_font_of_mercy = ____exports.item_blackshop_heavenly_font_of_mercy +item_blackshop_heavenly_font_of_mercy.name = "item_blackshop_heavenly_font_of_mercy" +item_blackshop_heavenly_font_of_mercy.____file_path = "scripts/vscripts/items/blackshop/heavenly/item_blackshop_heavenly_font_of_mercy.lua" +__TS__ClassExtends(item_blackshop_heavenly_font_of_mercy, BaseItem) +function item_blackshop_heavenly_font_of_mercy.prototype.Precache(context) + PrecacheResource("particle", "particles/units/heroes/hero_omniknight/omniknight_purification_cast.vpcf", context) +end +function item_blackshop_heavenly_font_of_mercy.prototype.GetAOERadius(self) + return self:GetSpecialValueFor("radius") +end +function item_blackshop_heavenly_font_of_mercy.prototype.HasEngine(self, hero) + return hero:HasModifier(____exports.modifier_item_heavenly_font_of_mercy_engine.name) +end +function item_blackshop_heavenly_font_of_mercy.prototype.BuildStoredFromKv(self) + return { + heal_flat = self:GetSpecialValueFor("heal_flat"), + heal_max_hp_pct = self:GetSpecialValueFor("heal_max_hp_pct"), + radius = self:GetSpecialValueFor("radius"), + crisis_hp_pct = self:GetSpecialValueFor("crisis_hp_pct"), + hero_cooldown = self:GetSpecialValueFor("hero_cooldown") + } +end +function item_blackshop_heavenly_font_of_mercy.prototype.OnSpellStart(self) + if not IsServer() then + return + end + local caster = self:GetCaster() + if not caster or not caster:IsRealHero() then + return + end + if self:HasEngine(caster) then + return + end + local v = self:BuildStoredFromKv() + local heroCd = v.hero_cooldown + local now = GameRules:GetGameTime() + fontMercyPerformEffect(nil, caster, v, self) + local eng = ____exports.modifier_item_heavenly_font_of_mercy_engine.name + local dis = ____exports.modifier_item_heavenly_font_of_mercy_display.name + caster:AddNewModifier( + caster, + self, + eng, + __TS__ObjectAssign({}, v, {cd_until = now + heroCd, mercy_proc_count = 1}) + ) + local d = caster:FindModifierByName(dis) + if d then + d:SetStackCount(d:GetStackCount() + 1) + else + caster:AddNewModifier(caster, self, dis, {}):SetStackCount(1) + end + UTIL_Remove(self) +end +item_blackshop_heavenly_font_of_mercy = __TS__Decorate( + item_blackshop_heavenly_font_of_mercy, + item_blackshop_heavenly_font_of_mercy, + {registerAbility(nil)}, + {kind = "class", name = "item_blackshop_heavenly_font_of_mercy"} +) +____exports.item_blackshop_heavenly_font_of_mercy = item_blackshop_heavenly_font_of_mercy +return ____exports diff --git a/scripts/vscripts/items/blackshop/heavenly/item_blackshop_heavenly_reset_to_zero.lua b/scripts/vscripts/items/blackshop/heavenly/item_blackshop_heavenly_reset_to_zero.lua new file mode 100644 index 0000000..722448e --- /dev/null +++ b/scripts/vscripts/items/blackshop/heavenly/item_blackshop_heavenly_reset_to_zero.lua @@ -0,0 +1,37 @@ +local ____lualib = require("lualib_bundle") +local __TS__Class = ____lualib.__TS__Class +local __TS__ClassExtends = ____lualib.__TS__ClassExtends +local __TS__Decorate = ____lualib.__TS__Decorate +local ____exports = {} +local ____dota_ts_adapter = require("lib.dota_ts_adapter") +local BaseItem = ____dota_ts_adapter.BaseItem +local registerAbility = ____dota_ts_adapter.registerAbility +local ____blackshop = require("blackshop") +local BlackShop = ____blackshop.BlackShop +____exports.item_blackshop_heavenly_reset_to_zero = __TS__Class() +local item_blackshop_heavenly_reset_to_zero = ____exports.item_blackshop_heavenly_reset_to_zero +item_blackshop_heavenly_reset_to_zero.name = "item_blackshop_heavenly_reset_to_zero" +item_blackshop_heavenly_reset_to_zero.____file_path = "scripts/vscripts/items/blackshop/heavenly/item_blackshop_heavenly_reset_to_zero.lua" +__TS__ClassExtends(item_blackshop_heavenly_reset_to_zero, BaseItem) +function item_blackshop_heavenly_reset_to_zero.prototype.OnSpellStart(self) + if not IsServer() then + return + end + local caster = self:GetCaster() + if not caster then + return + end + BlackShop:getInstance():ResetUniqueItems() + EmitSoundOn("Hero_Phoenix.SuperNova.Explode", caster) + local particle = ParticleManager:CreateParticle("particles/units/heroes/hero_phoenix/phoenix_supernova_reborn.vpcf", PATTACH_ABSORIGIN_FOLLOW, caster) + ParticleManager:ReleaseParticleIndex(particle) + UTIL_Remove(self) +end +item_blackshop_heavenly_reset_to_zero = __TS__Decorate( + item_blackshop_heavenly_reset_to_zero, + item_blackshop_heavenly_reset_to_zero, + {registerAbility(nil)}, + {kind = "class", name = "item_blackshop_heavenly_reset_to_zero"} +) +____exports.item_blackshop_heavenly_reset_to_zero = item_blackshop_heavenly_reset_to_zero +return ____exports diff --git a/scripts/vscripts/items/blackshop/heavenly/item_blackshop_heavenly_sanctuary_veil.lua b/scripts/vscripts/items/blackshop/heavenly/item_blackshop_heavenly_sanctuary_veil.lua new file mode 100644 index 0000000..02ce98f --- /dev/null +++ b/scripts/vscripts/items/blackshop/heavenly/item_blackshop_heavenly_sanctuary_veil.lua @@ -0,0 +1,160 @@ +local ____lualib = require("lualib_bundle") +local __TS__Class = ____lualib.__TS__Class +local __TS__ClassExtends = ____lualib.__TS__ClassExtends +local __TS__Decorate = ____lualib.__TS__Decorate +local ____exports = {} +local ____dota_ts_adapter = require("lib.dota_ts_adapter") +local BaseItem = ____dota_ts_adapter.BaseItem +local BaseModifier = ____dota_ts_adapter.BaseModifier +local registerAbility = ____dota_ts_adapter.registerAbility +local registerModifier = ____dota_ts_adapter.registerModifier +____exports.modifier_item_heavenly_sanctuary_veil_status = __TS__Class() +local modifier_item_heavenly_sanctuary_veil_status = ____exports.modifier_item_heavenly_sanctuary_veil_status +modifier_item_heavenly_sanctuary_veil_status.name = "modifier_item_heavenly_sanctuary_veil_status" +modifier_item_heavenly_sanctuary_veil_status.____file_path = "scripts/vscripts/items/blackshop/heavenly/item_blackshop_heavenly_sanctuary_veil.lua" +__TS__ClassExtends(modifier_item_heavenly_sanctuary_veil_status, BaseModifier) +function modifier_item_heavenly_sanctuary_veil_status.prototype.RemoveOnDeath(self) + return false +end +function modifier_item_heavenly_sanctuary_veil_status.prototype.IsHidden(self) + return true +end +function modifier_item_heavenly_sanctuary_veil_status.prototype.IsPurgable(self) + return false +end +function modifier_item_heavenly_sanctuary_veil_status.prototype.DeclareFunctions(self) + return {MODIFIER_PROPERTY_STATUS_RESISTANCE_STACKING} +end +function modifier_item_heavenly_sanctuary_veil_status.prototype.GetModifierStatusResistanceStacking(self) + return self:GetStackCount() +end +modifier_item_heavenly_sanctuary_veil_status = __TS__Decorate( + modifier_item_heavenly_sanctuary_veil_status, + modifier_item_heavenly_sanctuary_veil_status, + {registerModifier(nil)}, + {kind = "class", name = "modifier_item_heavenly_sanctuary_veil_status"} +) +____exports.modifier_item_heavenly_sanctuary_veil_status = modifier_item_heavenly_sanctuary_veil_status +____exports.modifier_item_heavenly_sanctuary_veil_mr = __TS__Class() +local modifier_item_heavenly_sanctuary_veil_mr = ____exports.modifier_item_heavenly_sanctuary_veil_mr +modifier_item_heavenly_sanctuary_veil_mr.name = "modifier_item_heavenly_sanctuary_veil_mr" +modifier_item_heavenly_sanctuary_veil_mr.____file_path = "scripts/vscripts/items/blackshop/heavenly/item_blackshop_heavenly_sanctuary_veil.lua" +__TS__ClassExtends(modifier_item_heavenly_sanctuary_veil_mr, BaseModifier) +function modifier_item_heavenly_sanctuary_veil_mr.prototype.____constructor(self, ...) + BaseModifier.prototype.____constructor(self, ...) + self.totalMagicResistBonus = 0 +end +function modifier_item_heavenly_sanctuary_veil_mr.prototype.RemoveOnDeath(self) + return false +end +function modifier_item_heavenly_sanctuary_veil_mr.prototype.IsHidden(self) + return true +end +function modifier_item_heavenly_sanctuary_veil_mr.prototype.IsPurgable(self) + return false +end +function modifier_item_heavenly_sanctuary_veil_mr.prototype.addMagicResistFromAbilityValue(self, amount) + if not IsServer() then + return + end + local n = math.floor(amount) + if n <= 0 then + return + end + self.totalMagicResistBonus = self.totalMagicResistBonus + n +end +function modifier_item_heavenly_sanctuary_veil_mr.prototype.DeclareFunctions(self) + return {MODIFIER_PROPERTY_MAGICAL_RESISTANCE_BONUS} +end +function modifier_item_heavenly_sanctuary_veil_mr.prototype.GetModifierMagicalResistanceBonus(self) + return self.totalMagicResistBonus +end +modifier_item_heavenly_sanctuary_veil_mr = __TS__Decorate( + modifier_item_heavenly_sanctuary_veil_mr, + modifier_item_heavenly_sanctuary_veil_mr, + {registerModifier(nil)}, + {kind = "class", name = "modifier_item_heavenly_sanctuary_veil_mr"} +) +____exports.modifier_item_heavenly_sanctuary_veil_mr = modifier_item_heavenly_sanctuary_veil_mr +____exports.modifier_item_heavenly_sanctuary_veil_display = __TS__Class() +local modifier_item_heavenly_sanctuary_veil_display = ____exports.modifier_item_heavenly_sanctuary_veil_display +modifier_item_heavenly_sanctuary_veil_display.name = "modifier_item_heavenly_sanctuary_veil_display" +modifier_item_heavenly_sanctuary_veil_display.____file_path = "scripts/vscripts/items/blackshop/heavenly/item_blackshop_heavenly_sanctuary_veil.lua" +__TS__ClassExtends(modifier_item_heavenly_sanctuary_veil_display, BaseModifier) +function modifier_item_heavenly_sanctuary_veil_display.prototype.RemoveOnDeath(self) + return false +end +function modifier_item_heavenly_sanctuary_veil_display.prototype.IsHidden(self) + return false +end +function modifier_item_heavenly_sanctuary_veil_display.prototype.IsPurgable(self) + return false +end +function modifier_item_heavenly_sanctuary_veil_display.prototype.GetTexture(self) + return "../items/blackshop/sanctuary_veil" +end +modifier_item_heavenly_sanctuary_veil_display = __TS__Decorate( + modifier_item_heavenly_sanctuary_veil_display, + modifier_item_heavenly_sanctuary_veil_display, + {registerModifier(nil)}, + {kind = "class", name = "modifier_item_heavenly_sanctuary_veil_display"} +) +____exports.modifier_item_heavenly_sanctuary_veil_display = modifier_item_heavenly_sanctuary_veil_display +--- Покров святилища — status_resist суммируется в стеке статус-модификатора; magic_resist копится в поле MR-модификатора при каждом касте (константы из KV). +____exports.item_blackshop_heavenly_sanctuary_veil = __TS__Class() +local item_blackshop_heavenly_sanctuary_veil = ____exports.item_blackshop_heavenly_sanctuary_veil +item_blackshop_heavenly_sanctuary_veil.name = "item_blackshop_heavenly_sanctuary_veil" +item_blackshop_heavenly_sanctuary_veil.____file_path = "scripts/vscripts/items/blackshop/heavenly/item_blackshop_heavenly_sanctuary_veil.lua" +__TS__ClassExtends(item_blackshop_heavenly_sanctuary_veil, BaseItem) +function item_blackshop_heavenly_sanctuary_veil.prototype.OnSpellStart(self) + if not IsServer() then + return + end + local caster = self:GetCaster() + if not caster or not caster:IsRealHero() then + return + end + caster:Purge( + false, + true, + false, + false, + false + ) + local srAdd = self:GetSpecialValueFor("status_resist") + local mrAdd = self:GetSpecialValueFor("magic_resist") + local ms = ____exports.modifier_item_heavenly_sanctuary_veil_status.name + local mm = ____exports.modifier_item_heavenly_sanctuary_veil_mr.name + local md = ____exports.modifier_item_heavenly_sanctuary_veil_display.name + local m = caster:FindModifierByName(ms) + if m then + m:SetStackCount(m:GetStackCount() + srAdd) + else + caster:AddNewModifier(caster, self, ms, {}):SetStackCount(srAdd) + end + m = caster:FindModifierByName(mm) + if m then + m:addMagicResistFromAbilityValue(mrAdd) + else + local mrMod = caster:AddNewModifier(caster, self, mm, {}) + mrMod:addMagicResistFromAbilityValue(mrAdd) + end + local d = caster:FindModifierByName(md) + if d then + d:SetStackCount(d:GetStackCount() + 1) + else + caster:AddNewModifier(caster, self, md, {}):SetStackCount(1) + local pfx = ParticleManager:CreateParticle("particles/items_fx/black_king_bar_avatar.vpcf", PATTACH_ABSORIGIN_FOLLOW, caster) + ParticleManager:ReleaseParticleIndex(pfx) + end + EmitSoundOn("Hero_Omniknight.Repel", caster) + UTIL_Remove(self) +end +item_blackshop_heavenly_sanctuary_veil = __TS__Decorate( + item_blackshop_heavenly_sanctuary_veil, + item_blackshop_heavenly_sanctuary_veil, + {registerAbility(nil)}, + {kind = "class", name = "item_blackshop_heavenly_sanctuary_veil"} +) +____exports.item_blackshop_heavenly_sanctuary_veil = item_blackshop_heavenly_sanctuary_veil +return ____exports diff --git a/scripts/vscripts/items/blackshop/heavenly/item_reset_to_zero.lua b/scripts/vscripts/items/blackshop/heavenly/item_reset_to_zero.lua new file mode 100644 index 0000000..a3baa8c --- /dev/null +++ b/scripts/vscripts/items/blackshop/heavenly/item_reset_to_zero.lua @@ -0,0 +1,38 @@ +local ____lualib = require("lualib_bundle") +local __TS__Class = ____lualib.__TS__Class +local __TS__ClassExtends = ____lualib.__TS__ClassExtends +local __TS__Decorate = ____lualib.__TS__Decorate +local __TS__SourceMapTraceBack = ____lualib.__TS__SourceMapTraceBack +__TS__SourceMapTraceBack(debug.getinfo(1).short_src, {["8"] = 1,["9"] = 1,["10"] = 1,["11"] = 2,["12"] = 2,["13"] = 4,["14"] = 5,["15"] = 4,["16"] = 5,["17"] = 6,["18"] = 7,["21"] = 9,["22"] = 10,["25"] = 13,["26"] = 16,["27"] = 17,["28"] = 22,["29"] = 25,["30"] = 6,["31"] = 5,["32"] = 5,["33"] = 5,["34"] = 4,["37"] = 5}); +local ____exports = {} +local ____dota_ts_adapter = require("lib.dota_ts_adapter") +local BaseItem = ____dota_ts_adapter.BaseItem +local registerAbility = ____dota_ts_adapter.registerAbility +local ____blackshop = require("blackshop") +local BlackShop = ____blackshop.BlackShop +____exports.item_reset_to_zero = __TS__Class() +local item_reset_to_zero = ____exports.item_reset_to_zero +item_reset_to_zero.name = "item_reset_to_zero" +__TS__ClassExtends(item_reset_to_zero, BaseItem) +function item_reset_to_zero.prototype.OnSpellStart(self) + if not IsServer() then + return + end + local caster = self:GetCaster() + if not caster then + return + end + BlackShop:getInstance():ResetUniqueItems() + EmitSoundOn("Hero_Phoenix.SuperNova.Explode", caster) + local particle = ParticleManager:CreateParticle("particles/units/heroes/hero_phoenix/phoenix_supernova_reborn.vpcf", PATTACH_ABSORIGIN_FOLLOW, caster) + ParticleManager:ReleaseParticleIndex(particle) + UTIL_Remove(self) +end +item_reset_to_zero = __TS__Decorate( + item_reset_to_zero, + item_reset_to_zero, + {registerAbility(nil)}, + {kind = "class", name = "item_reset_to_zero"} +) +____exports.item_reset_to_zero = item_reset_to_zero +return ____exports diff --git a/scripts/vscripts/items/blackshop/legendary/item_blackshop_legendary_astral_anchor.lua b/scripts/vscripts/items/blackshop/legendary/item_blackshop_legendary_astral_anchor.lua new file mode 100644 index 0000000..a6c5855 --- /dev/null +++ b/scripts/vscripts/items/blackshop/legendary/item_blackshop_legendary_astral_anchor.lua @@ -0,0 +1,138 @@ +local ____lualib = require("lualib_bundle") +local __TS__Class = ____lualib.__TS__Class +local __TS__ClassExtends = ____lualib.__TS__ClassExtends +local __TS__Decorate = ____lualib.__TS__Decorate +local ____exports = {} +local ____dota_ts_adapter = require("lib.dota_ts_adapter") +local BaseItem = ____dota_ts_adapter.BaseItem +local BaseModifier = ____dota_ts_adapter.BaseModifier +local registerAbility = ____dota_ts_adapter.registerAbility +local registerModifier = ____dota_ts_adapter.registerModifier +--- Скрытый стак дальности каста — число в стаке = суммарный бонус (реплицируется на клиент для HUD). +____exports.modifier_item_astral_anchor_cast = __TS__Class() +local modifier_item_astral_anchor_cast = ____exports.modifier_item_astral_anchor_cast +modifier_item_astral_anchor_cast.name = "modifier_item_astral_anchor_cast" +modifier_item_astral_anchor_cast.____file_path = "scripts/vscripts/items/blackshop/legendary/item_blackshop_legendary_astral_anchor.lua" +__TS__ClassExtends(modifier_item_astral_anchor_cast, BaseModifier) +function modifier_item_astral_anchor_cast.prototype.RemoveOnDeath(self) + return false +end +function modifier_item_astral_anchor_cast.prototype.IsHidden(self) + return true +end +function modifier_item_astral_anchor_cast.prototype.IsPurgable(self) + return false +end +function modifier_item_astral_anchor_cast.prototype.DeclareFunctions(self) + return {MODIFIER_PROPERTY_CAST_RANGE_BONUS_STACKING} +end +function modifier_item_astral_anchor_cast.prototype.GetModifierCastRangeBonusStacking(self) + return self:GetStackCount() +end +modifier_item_astral_anchor_cast = __TS__Decorate( + modifier_item_astral_anchor_cast, + modifier_item_astral_anchor_cast, + {registerModifier(nil)}, + {kind = "class", name = "modifier_item_astral_anchor_cast"} +) +____exports.modifier_item_astral_anchor_cast = modifier_item_astral_anchor_cast +--- Скрытый стак штрафа к скорости — стак = сумма процентов из KV. +____exports.modifier_item_astral_anchor_slow = __TS__Class() +local modifier_item_astral_anchor_slow = ____exports.modifier_item_astral_anchor_slow +modifier_item_astral_anchor_slow.name = "modifier_item_astral_anchor_slow" +modifier_item_astral_anchor_slow.____file_path = "scripts/vscripts/items/blackshop/legendary/item_blackshop_legendary_astral_anchor.lua" +__TS__ClassExtends(modifier_item_astral_anchor_slow, BaseModifier) +function modifier_item_astral_anchor_slow.prototype.RemoveOnDeath(self) + return false +end +function modifier_item_astral_anchor_slow.prototype.IsHidden(self) + return true +end +function modifier_item_astral_anchor_slow.prototype.IsPurgable(self) + return false +end +function modifier_item_astral_anchor_slow.prototype.DeclareFunctions(self) + return {MODIFIER_PROPERTY_MOVESPEED_BONUS_PERCENTAGE} +end +function modifier_item_astral_anchor_slow.prototype.GetModifierMoveSpeedBonus_Percentage(self) + return -self:GetStackCount() +end +modifier_item_astral_anchor_slow = __TS__Decorate( + modifier_item_astral_anchor_slow, + modifier_item_astral_anchor_slow, + {registerModifier(nil)}, + {kind = "class", name = "modifier_item_astral_anchor_slow"} +) +____exports.modifier_item_astral_anchor_slow = modifier_item_astral_anchor_slow +--- Видимый маркер якоря (иконка; статы в скрытых модификаторах выше). +____exports.modifier_item_astral_anchor = __TS__Class() +local modifier_item_astral_anchor = ____exports.modifier_item_astral_anchor +modifier_item_astral_anchor.name = "modifier_item_astral_anchor" +modifier_item_astral_anchor.____file_path = "scripts/vscripts/items/blackshop/legendary/item_blackshop_legendary_astral_anchor.lua" +__TS__ClassExtends(modifier_item_astral_anchor, BaseModifier) +function modifier_item_astral_anchor.prototype.RemoveOnDeath(self) + return false +end +function modifier_item_astral_anchor.prototype.IsHidden(self) + return false +end +function modifier_item_astral_anchor.prototype.IsPurgable(self) + return false +end +function modifier_item_astral_anchor.prototype.GetTexture(self) + return "../items/blackshop/astral_anchor" +end +modifier_item_astral_anchor = __TS__Decorate( + modifier_item_astral_anchor, + modifier_item_astral_anchor, + {registerModifier(nil)}, + {kind = "class", name = "modifier_item_astral_anchor"} +) +____exports.modifier_item_astral_anchor = modifier_item_astral_anchor +--- Якорь звёздной тверди — дальность кастов ценой скорости (каждое применение суммируется). +____exports.item_blackshop_legendary_astral_anchor = __TS__Class() +local item_blackshop_legendary_astral_anchor = ____exports.item_blackshop_legendary_astral_anchor +item_blackshop_legendary_astral_anchor.name = "item_blackshop_legendary_astral_anchor" +item_blackshop_legendary_astral_anchor.____file_path = "scripts/vscripts/items/blackshop/legendary/item_blackshop_legendary_astral_anchor.lua" +__TS__ClassExtends(item_blackshop_legendary_astral_anchor, BaseItem) +function item_blackshop_legendary_astral_anchor.prototype.OnSpellStart(self) + if not IsServer() then + return + end + local caster = self:GetCaster() + if not caster then + return + end + local castRange = self:GetSpecialValueFor("cast_range_bonus") + local moveSlowPct = self:GetSpecialValueFor("move_speed_loss_pct") + local castMod = ____exports.modifier_item_astral_anchor_cast.name + local slowMod = ____exports.modifier_item_astral_anchor_slow.name + local dispMod = ____exports.modifier_item_astral_anchor.name + local m = caster:FindModifierByName(castMod) + if m then + m:SetStackCount(m:GetStackCount() + castRange) + else + caster:AddNewModifier(caster, self, castMod, {}):SetStackCount(castRange) + end + m = caster:FindModifierByName(slowMod) + if m then + m:SetStackCount(m:GetStackCount() + moveSlowPct) + else + caster:AddNewModifier(caster, self, slowMod, {}):SetStackCount(moveSlowPct) + end + m = caster:FindModifierByName(dispMod) + if m then + m:SetStackCount(m:GetStackCount() + 1) + else + caster:AddNewModifier(caster, self, dispMod, {}):SetStackCount(1) + end + UTIL_Remove(self) +end +item_blackshop_legendary_astral_anchor = __TS__Decorate( + item_blackshop_legendary_astral_anchor, + item_blackshop_legendary_astral_anchor, + {registerAbility(nil)}, + {kind = "class", name = "item_blackshop_legendary_astral_anchor"} +) +____exports.item_blackshop_legendary_astral_anchor = item_blackshop_legendary_astral_anchor +return ____exports diff --git a/scripts/vscripts/items/blackshop/legendary/item_blackshop_legendary_fated_die.lua b/scripts/vscripts/items/blackshop/legendary/item_blackshop_legendary_fated_die.lua new file mode 100644 index 0000000..a07e879 --- /dev/null +++ b/scripts/vscripts/items/blackshop/legendary/item_blackshop_legendary_fated_die.lua @@ -0,0 +1,39 @@ +local ____lualib = require("lualib_bundle") +local __TS__Class = ____lualib.__TS__Class +local __TS__ClassExtends = ____lualib.__TS__ClassExtends +local __TS__Decorate = ____lualib.__TS__Decorate +local ____exports = {} +local ____dota_ts_adapter = require("lib.dota_ts_adapter") +local BaseItem = ____dota_ts_adapter.BaseItem +local registerAbility = ____dota_ts_adapter.registerAbility +local ____luck = require("utils.luck") +local addLuck = ____luck.addLuck +--- Жребий судьбы — навсегда повышает удачу героя (расходуется). +____exports.item_blackshop_legendary_fated_die = __TS__Class() +local item_blackshop_legendary_fated_die = ____exports.item_blackshop_legendary_fated_die +item_blackshop_legendary_fated_die.name = "item_blackshop_legendary_fated_die" +item_blackshop_legendary_fated_die.____file_path = "scripts/vscripts/items/blackshop/legendary/item_blackshop_legendary_fated_die.lua" +__TS__ClassExtends(item_blackshop_legendary_fated_die, BaseItem) +function item_blackshop_legendary_fated_die.prototype.OnSpellStart(self) + if not IsServer() then + return + end + local caster = self:GetCaster() + if not caster or not caster:IsRealHero() then + return + end + local luck = self:GetSpecialValueFor("luck_bonus") + addLuck(nil, caster, luck) + EmitSoundOn("DOTA_Item.Hand_Of_Midas", caster) + local pfx = ParticleManager:CreateParticle("particles/items2_fx/veil_of_discord.vpcf", PATTACH_ABSORIGIN_FOLLOW, caster) + ParticleManager:ReleaseParticleIndex(pfx) + UTIL_Remove(self) +end +item_blackshop_legendary_fated_die = __TS__Decorate( + item_blackshop_legendary_fated_die, + item_blackshop_legendary_fated_die, + {registerAbility(nil)}, + {kind = "class", name = "item_blackshop_legendary_fated_die"} +) +____exports.item_blackshop_legendary_fated_die = item_blackshop_legendary_fated_die +return ____exports diff --git a/scripts/vscripts/items/blackshop/legendary/item_blackshop_legendary_fire_summoner.lua b/scripts/vscripts/items/blackshop/legendary/item_blackshop_legendary_fire_summoner.lua new file mode 100644 index 0000000..642e989 --- /dev/null +++ b/scripts/vscripts/items/blackshop/legendary/item_blackshop_legendary_fire_summoner.lua @@ -0,0 +1,167 @@ +local ____lualib = require("lualib_bundle") +local __TS__Class = ____lualib.__TS__Class +local __TS__ClassExtends = ____lualib.__TS__ClassExtends +local __TS__Decorate = ____lualib.__TS__Decorate +local ____exports = {} +local ____dota_ts_adapter = require("lib.dota_ts_adapter") +local BaseItem = ____dota_ts_adapter.BaseItem +local BaseModifier = ____dota_ts_adapter.BaseModifier +local registerAbility = ____dota_ts_adapter.registerAbility +local registerModifier = ____dota_ts_adapter.registerModifier +____exports.item_blackshop_legendary_fire_summoner = __TS__Class() +local item_blackshop_legendary_fire_summoner = ____exports.item_blackshop_legendary_fire_summoner +item_blackshop_legendary_fire_summoner.name = "item_blackshop_legendary_fire_summoner" +item_blackshop_legendary_fire_summoner.____file_path = "scripts/vscripts/items/blackshop/legendary/item_blackshop_legendary_fire_summoner.lua" +__TS__ClassExtends(item_blackshop_legendary_fire_summoner, BaseItem) +function item_blackshop_legendary_fire_summoner.prototype.OnSpellStart(self) + if not IsServer() then + return + end + local caster = self:GetCaster() + if not caster then + return + end + local demon = CreateUnitByName( + "npc_dota_fire_summon", + caster:GetAbsOrigin() + RandomVector(100), + true, + caster, + caster, + caster:GetTeamNumber() + ) + demon:SetOwner(caster) + demon:SetBaseDamageMin(caster:GetBaseDamageMin()) + demon:SetBaseDamageMax(caster:GetBaseDamageMax()) + demon:SetBaseMoveSpeed(caster:GetBaseMoveSpeed() + 65) + demon:AddNewModifier( + caster, + getModifierSourceAbility(nil, caster), + "modifier_fire_summoner_demon", + {} + ) + demon:AddAbility("ability_stacking_crit"):SetLevel(1) + caster:EmitSound("Item.TomeOfKnowledge") + if self:GetCurrentCharges() <= self:GetInitialCharges() then + UTIL_Remove(self) + return + end + self:SetCurrentCharges(self:GetCurrentCharges() - self:GetInitialCharges()) +end +item_blackshop_legendary_fire_summoner = __TS__Decorate( + item_blackshop_legendary_fire_summoner, + item_blackshop_legendary_fire_summoner, + {registerAbility(nil)}, + {kind = "class", name = "item_blackshop_legendary_fire_summoner"} +) +____exports.item_blackshop_legendary_fire_summoner = item_blackshop_legendary_fire_summoner +____exports.modifier_fire_summoner_demon = __TS__Class() +local modifier_fire_summoner_demon = ____exports.modifier_fire_summoner_demon +modifier_fire_summoner_demon.name = "modifier_fire_summoner_demon" +modifier_fire_summoner_demon.____file_path = "scripts/vscripts/items/blackshop/legendary/item_blackshop_legendary_fire_summoner.lua" +__TS__ClassExtends(modifier_fire_summoner_demon, BaseModifier) +function modifier_fire_summoner_demon.prototype.OnCreated(self) + if not IsServer() then + return + end + self:StartIntervalThink(0.1) + self:OnIntervalThink() +end +function modifier_fire_summoner_demon.prototype.IsPurgable(self) + return false +end +function modifier_fire_summoner_demon.prototype.GetTexture(self) + return "../items/blackshop/fire_summoner" +end +function modifier_fire_summoner_demon.prototype.GetRealOwner(self) + local parent = self:GetParent() + if not parent then + return nil + end + if parent:IsRealHero() then + return parent + end + local owner = parent:GetOwner() + if not owner then + return nil + end + local ownerUnit = owner + if ownerUnit:IsRealHero() then + return ownerUnit + end + return nil +end +function modifier_fire_summoner_demon.prototype.OnIntervalThink(self) + if not IsServer() then + return + end + local parent = self:GetParent() + if not parent then + return + end + local realOwner = self:GetRealOwner() + if not realOwner then + return + end + local critModifier = realOwner:FindModifierByName("modifier_stacking_crit") + if critModifier then + local parentCritModifier = parent:FindModifierByName("modifier_stacking_crit") + if parentCritModifier then + parentCritModifier.critModifiers = critModifier.critModifiers + end + end + parent:SetBaseAttackTime(realOwner:GetBaseAttackTime()) + parent:SetBaseDamageMin(realOwner:GetBaseDamageMin()) + parent:SetBaseDamageMax(realOwner:GetBaseDamageMax()) + parent:SetBaseMoveSpeed(realOwner:GetBaseMoveSpeed()) + local distanceToOwner = (parent:GetAbsOrigin() - realOwner:GetAbsOrigin()):Length2D() + if distanceToOwner > 160 then + parent:MoveToPosition(toVectorWS( + nil, + realOwner:GetAbsOrigin() + RandomVector(200) + )) + end + if distanceToOwner > 400 then + FindClearSpaceForUnit( + parent, + toVectorWS( + nil, + realOwner:GetAbsOrigin() + RandomVector(200) + ), + true + ) + end + if distanceToOwner <= 300 and realOwner:IsAlive() then + local enemies = FindUnitsInRadius( + parent:GetTeamNumber(), + parent:GetAbsOrigin(), + nil, + 500, + DOTA_UNIT_TARGET_TEAM_ENEMY, + bit.bor(DOTA_UNIT_TARGET_BASIC, DOTA_UNIT_TARGET_HERO), + DOTA_UNIT_TARGET_FLAG_NONE, + FIND_CLOSEST, + false + ) + if #enemies > 0 then + enemies[1]:MoveToTargetToAttack(realOwner) + parent:MoveToTargetToAttack(enemies[1]) + end + end +end +function modifier_fire_summoner_demon.prototype.CheckState(self) + return { + [MODIFIER_STATE_NO_HEALTH_BAR] = true, + [MODIFIER_STATE_INVULNERABLE] = true, + [MODIFIER_STATE_NO_UNIT_COLLISION] = true, + [MODIFIER_STATE_NO_TEAM_SELECT] = true, + [MODIFIER_STATE_NO_TEAM_MOVE_TO] = true + } +end +modifier_fire_summoner_demon = __TS__Decorate( + modifier_fire_summoner_demon, + modifier_fire_summoner_demon, + {registerModifier(nil)}, + {kind = "class", name = "modifier_fire_summoner_demon"} +) +____exports.modifier_fire_summoner_demon = modifier_fire_summoner_demon +return ____exports diff --git a/scripts/vscripts/items/blackshop/legendary/item_blackshop_legendary_primordial_shard.lua b/scripts/vscripts/items/blackshop/legendary/item_blackshop_legendary_primordial_shard.lua new file mode 100644 index 0000000..507efd4 --- /dev/null +++ b/scripts/vscripts/items/blackshop/legendary/item_blackshop_legendary_primordial_shard.lua @@ -0,0 +1,77 @@ +local ____lualib = require("lualib_bundle") +local __TS__Class = ____lualib.__TS__Class +local __TS__ClassExtends = ____lualib.__TS__ClassExtends +local __TS__Decorate = ____lualib.__TS__Decorate +local ____exports = {} +local ____dota_ts_adapter = require("lib.dota_ts_adapter") +local BaseItem = ____dota_ts_adapter.BaseItem +local BaseModifier = ____dota_ts_adapter.BaseModifier +local registerAbility = ____dota_ts_adapter.registerAbility +local registerModifier = ____dota_ts_adapter.registerModifier +--- Осколок прародины — легендарка: крупный прирост ко всем атрибутам за применение (стакается). +____exports.item_blackshop_legendary_primordial_shard = __TS__Class() +local item_blackshop_legendary_primordial_shard = ____exports.item_blackshop_legendary_primordial_shard +item_blackshop_legendary_primordial_shard.name = "item_blackshop_legendary_primordial_shard" +item_blackshop_legendary_primordial_shard.____file_path = "scripts/vscripts/items/blackshop/legendary/item_blackshop_legendary_primordial_shard.lua" +__TS__ClassExtends(item_blackshop_legendary_primordial_shard, BaseItem) +function item_blackshop_legendary_primordial_shard.prototype.OnSpellStart(self) + if not IsServer() then + return + end + local caster = self:GetCaster() + if not caster then + return + end + local stats = self:GetSpecialValueFor("bonus_all") + if caster:HasModifier("modifier_item_primordial_shard") then + local m = caster:FindModifierByName("modifier_item_primordial_shard") + m:SetStackCount(m:GetStackCount() + stats) + else + caster:AddNewModifier(caster, self, "modifier_item_primordial_shard", {}):SetStackCount(stats) + end + UTIL_Remove(self) +end +item_blackshop_legendary_primordial_shard = __TS__Decorate( + item_blackshop_legendary_primordial_shard, + item_blackshop_legendary_primordial_shard, + {registerAbility(nil)}, + {kind = "class", name = "item_blackshop_legendary_primordial_shard"} +) +____exports.item_blackshop_legendary_primordial_shard = item_blackshop_legendary_primordial_shard +____exports.modifier_item_primordial_shard = __TS__Class() +local modifier_item_primordial_shard = ____exports.modifier_item_primordial_shard +modifier_item_primordial_shard.name = "modifier_item_primordial_shard" +modifier_item_primordial_shard.____file_path = "scripts/vscripts/items/blackshop/legendary/item_blackshop_legendary_primordial_shard.lua" +__TS__ClassExtends(modifier_item_primordial_shard, BaseModifier) +function modifier_item_primordial_shard.prototype.RemoveOnDeath(self) + return false +end +function modifier_item_primordial_shard.prototype.IsHidden(self) + return false +end +function modifier_item_primordial_shard.prototype.IsPurgable(self) + return false +end +function modifier_item_primordial_shard.prototype.GetTexture(self) + return "../items/blackshop/primordial_shard" +end +function modifier_item_primordial_shard.prototype.DeclareFunctions(self) + return {MODIFIER_PROPERTY_STATS_AGILITY_BONUS, MODIFIER_PROPERTY_STATS_INTELLECT_BONUS, MODIFIER_PROPERTY_STATS_STRENGTH_BONUS} +end +function modifier_item_primordial_shard.prototype.GetModifierBonusStats_Agility(self) + return self:GetStackCount() +end +function modifier_item_primordial_shard.prototype.GetModifierBonusStats_Intellect(self) + return self:GetStackCount() +end +function modifier_item_primordial_shard.prototype.GetModifierBonusStats_Strength(self) + return self:GetStackCount() +end +modifier_item_primordial_shard = __TS__Decorate( + modifier_item_primordial_shard, + modifier_item_primordial_shard, + {registerModifier(nil)}, + {kind = "class", name = "modifier_item_primordial_shard"} +) +____exports.modifier_item_primordial_shard = modifier_item_primordial_shard +return ____exports diff --git a/scripts/vscripts/items/blackshop/legendary/item_blackshop_legendary_restock.lua b/scripts/vscripts/items/blackshop/legendary/item_blackshop_legendary_restock.lua new file mode 100644 index 0000000..d61cf78 --- /dev/null +++ b/scripts/vscripts/items/blackshop/legendary/item_blackshop_legendary_restock.lua @@ -0,0 +1,63 @@ +local ____lualib = require("lualib_bundle") +local __TS__Class = ____lualib.__TS__Class +local __TS__ClassExtends = ____lualib.__TS__ClassExtends +local __TS__Decorate = ____lualib.__TS__Decorate +local ____exports = {} +local ____dota_ts_adapter = require("lib.dota_ts_adapter") +local BaseItem = ____dota_ts_adapter.BaseItem +local BaseModifier = ____dota_ts_adapter.BaseModifier +local registerAbility = ____dota_ts_adapter.registerAbility +local registerModifier = ____dota_ts_adapter.registerModifier +____exports.item_blackshop_legendary_restock = __TS__Class() +local item_blackshop_legendary_restock = ____exports.item_blackshop_legendary_restock +item_blackshop_legendary_restock.name = "item_blackshop_legendary_restock" +item_blackshop_legendary_restock.____file_path = "scripts/vscripts/items/blackshop/legendary/item_blackshop_legendary_restock.lua" +__TS__ClassExtends(item_blackshop_legendary_restock, BaseItem) +function item_blackshop_legendary_restock.prototype.OnSpellStart(self) + if not IsServer() then + return + end + local caster = self:GetCaster() + if not caster then + return + end + local heroes = HeroList:GetAllHeroes() + for ____, hero in ipairs(heroes) do + if hero:IsRealHero() then + hero:AddNewModifier(caster, self, "modifier_item_restock", {}) + end + end + UTIL_Remove(self) +end +item_blackshop_legendary_restock = __TS__Decorate( + item_blackshop_legendary_restock, + item_blackshop_legendary_restock, + {registerAbility(nil)}, + {kind = "class", name = "item_blackshop_legendary_restock"} +) +____exports.item_blackshop_legendary_restock = item_blackshop_legendary_restock +____exports.modifier_item_restock = __TS__Class() +local modifier_item_restock = ____exports.modifier_item_restock +modifier_item_restock.name = "modifier_item_restock" +modifier_item_restock.____file_path = "scripts/vscripts/items/blackshop/legendary/item_blackshop_legendary_restock.lua" +__TS__ClassExtends(modifier_item_restock, BaseModifier) +function modifier_item_restock.prototype.RemoveOnDeath(self) + return false +end +function modifier_item_restock.prototype.IsPurgable(self) + return false +end +function modifier_item_restock.prototype.IsHidden(self) + return false +end +function modifier_item_restock.prototype.GetTexture(self) + return "../items/blackshop/restock" +end +modifier_item_restock = __TS__Decorate( + modifier_item_restock, + modifier_item_restock, + {registerModifier(nil)}, + {kind = "class", name = "modifier_item_restock"} +) +____exports.modifier_item_restock = modifier_item_restock +return ____exports diff --git a/scripts/vscripts/items/blackshop/legendary/item_blackshop_legendary_twilight_mirror.lua b/scripts/vscripts/items/blackshop/legendary/item_blackshop_legendary_twilight_mirror.lua new file mode 100644 index 0000000..6a16c2a --- /dev/null +++ b/scripts/vscripts/items/blackshop/legendary/item_blackshop_legendary_twilight_mirror.lua @@ -0,0 +1,123 @@ +local ____lualib = require("lualib_bundle") +local __TS__Class = ____lualib.__TS__Class +local __TS__ClassExtends = ____lualib.__TS__ClassExtends +local __TS__Decorate = ____lualib.__TS__Decorate +local ____exports = {} +local ____dota_ts_adapter = require("lib.dota_ts_adapter") +local BaseItem = ____dota_ts_adapter.BaseItem +local BaseModifier = ____dota_ts_adapter.BaseModifier +local registerAbility = ____dota_ts_adapter.registerAbility +local registerModifier = ____dota_ts_adapter.registerModifier +--- Скрытый стак усиления магии — стак = проценты spell amp (реплицируется на клиент). +____exports.modifier_item_twilight_mirror_spell = __TS__Class() +local modifier_item_twilight_mirror_spell = ____exports.modifier_item_twilight_mirror_spell +modifier_item_twilight_mirror_spell.name = "modifier_item_twilight_mirror_spell" +modifier_item_twilight_mirror_spell.____file_path = "scripts/vscripts/items/blackshop/legendary/item_blackshop_legendary_twilight_mirror.lua" +__TS__ClassExtends(modifier_item_twilight_mirror_spell, BaseModifier) +function modifier_item_twilight_mirror_spell.prototype.RemoveOnDeath(self) + return false +end +function modifier_item_twilight_mirror_spell.prototype.IsHidden(self) + return true +end +function modifier_item_twilight_mirror_spell.prototype.IsPurgable(self) + return false +end +function modifier_item_twilight_mirror_spell.prototype.DeclareFunctions(self) + return {MODIFIER_PROPERTY_SPELL_AMPLIFY_PERCENTAGE} +end +function modifier_item_twilight_mirror_spell.prototype.GetModifierSpellAmplify_Percentage(self) + return self:GetStackCount() +end +modifier_item_twilight_mirror_spell = __TS__Decorate( + modifier_item_twilight_mirror_spell, + modifier_item_twilight_mirror_spell, + {registerModifier(nil)}, + {kind = "class", name = "modifier_item_twilight_mirror_spell"} +) +____exports.modifier_item_twilight_mirror_spell = modifier_item_twilight_mirror_spell +--- Скрытый стак входящего урона — стак = %% доп. получаемого урона. +____exports.modifier_item_twilight_mirror_incoming = __TS__Class() +local modifier_item_twilight_mirror_incoming = ____exports.modifier_item_twilight_mirror_incoming +modifier_item_twilight_mirror_incoming.name = "modifier_item_twilight_mirror_incoming" +modifier_item_twilight_mirror_incoming.____file_path = "scripts/vscripts/items/blackshop/legendary/item_blackshop_legendary_twilight_mirror.lua" +__TS__ClassExtends(modifier_item_twilight_mirror_incoming, BaseModifier) +function modifier_item_twilight_mirror_incoming.prototype.RemoveOnDeath(self) + return false +end +function modifier_item_twilight_mirror_incoming.prototype.IsHidden(self) + return true +end +function modifier_item_twilight_mirror_incoming.prototype.IsPurgable(self) + return false +end +function modifier_item_twilight_mirror_incoming.prototype.DeclareFunctions(self) + return {MODIFIER_PROPERTY_INCOMING_DAMAGE_PERCENTAGE} +end +function modifier_item_twilight_mirror_incoming.prototype.GetModifierIncomingDamage_Percentage(self) + return self:GetStackCount() +end +modifier_item_twilight_mirror_incoming = __TS__Decorate( + modifier_item_twilight_mirror_incoming, + modifier_item_twilight_mirror_incoming, + {registerModifier(nil)}, + {kind = "class", name = "modifier_item_twilight_mirror_incoming"} +) +____exports.modifier_item_twilight_mirror_incoming = modifier_item_twilight_mirror_incoming +--- Видимый маркер зеркала (без статов). +____exports.modifier_item_twilight_mirror = __TS__Class() +local modifier_item_twilight_mirror = ____exports.modifier_item_twilight_mirror +modifier_item_twilight_mirror.name = "modifier_item_twilight_mirror" +modifier_item_twilight_mirror.____file_path = "scripts/vscripts/items/blackshop/legendary/item_blackshop_legendary_twilight_mirror.lua" +__TS__ClassExtends(modifier_item_twilight_mirror, BaseModifier) +function modifier_item_twilight_mirror.prototype.RemoveOnDeath(self) + return false +end +function modifier_item_twilight_mirror.prototype.IsHidden(self) + return false +end +function modifier_item_twilight_mirror.prototype.IsPurgable(self) + return false +end +function modifier_item_twilight_mirror.prototype.GetTexture(self) + return "../items/blackshop/twilight_mirror" +end +modifier_item_twilight_mirror = __TS__Decorate( + modifier_item_twilight_mirror, + modifier_item_twilight_mirror, + {registerModifier(nil)}, + {kind = "class", name = "modifier_item_twilight_mirror"} +) +____exports.modifier_item_twilight_mirror = modifier_item_twilight_mirror +--- Зеркало сумерек — огромный усилитель магии ценой уязвимости (одно применение, навсегда). +____exports.item_blackshop_legendary_twilight_mirror = __TS__Class() +local item_blackshop_legendary_twilight_mirror = ____exports.item_blackshop_legendary_twilight_mirror +item_blackshop_legendary_twilight_mirror.name = "item_blackshop_legendary_twilight_mirror" +item_blackshop_legendary_twilight_mirror.____file_path = "scripts/vscripts/items/blackshop/legendary/item_blackshop_legendary_twilight_mirror.lua" +__TS__ClassExtends(item_blackshop_legendary_twilight_mirror, BaseItem) +function item_blackshop_legendary_twilight_mirror.prototype.OnSpellStart(self) + if not IsServer() then + return + end + local caster = self:GetCaster() + if not caster then + return + end + if caster:HasModifier(____exports.modifier_item_twilight_mirror.name) then + return + end + local spell = self:GetSpecialValueFor("spell_amp_pct") + local inc = self:GetSpecialValueFor("incoming_damage_pct") + caster:AddNewModifier(caster, self, ____exports.modifier_item_twilight_mirror_spell.name, {}):SetStackCount(spell) + caster:AddNewModifier(caster, self, ____exports.modifier_item_twilight_mirror_incoming.name, {}):SetStackCount(inc) + caster:AddNewModifier(caster, self, ____exports.modifier_item_twilight_mirror.name, {}):SetStackCount(1) + UTIL_Remove(self) +end +item_blackshop_legendary_twilight_mirror = __TS__Decorate( + item_blackshop_legendary_twilight_mirror, + item_blackshop_legendary_twilight_mirror, + {registerAbility(nil)}, + {kind = "class", name = "item_blackshop_legendary_twilight_mirror"} +) +____exports.item_blackshop_legendary_twilight_mirror = item_blackshop_legendary_twilight_mirror +return ____exports diff --git a/scripts/vscripts/items/blackshop/legendary/item_fire_summoner.lua b/scripts/vscripts/items/blackshop/legendary/item_fire_summoner.lua new file mode 100644 index 0000000..eaf657d --- /dev/null +++ b/scripts/vscripts/items/blackshop/legendary/item_fire_summoner.lua @@ -0,0 +1,150 @@ +local ____lualib = require("lualib_bundle") +local __TS__Class = ____lualib.__TS__Class +local __TS__ClassExtends = ____lualib.__TS__ClassExtends +local __TS__Decorate = ____lualib.__TS__Decorate +local __TS__SourceMapTraceBack = ____lualib.__TS__SourceMapTraceBack +__TS__SourceMapTraceBack(debug.getinfo(1).short_src, {["8"] = 1,["9"] = 1,["10"] = 1,["11"] = 1,["12"] = 1,["13"] = 3,["14"] = 4,["15"] = 3,["16"] = 4,["17"] = 5,["18"] = 6,["21"] = 8,["22"] = 9,["25"] = 12,["26"] = 12,["27"] = 12,["28"] = 12,["29"] = 12,["30"] = 12,["31"] = 12,["32"] = 12,["33"] = 13,["34"] = 14,["35"] = 15,["36"] = 16,["37"] = 18,["38"] = 19,["39"] = 20,["40"] = 22,["41"] = 23,["44"] = 26,["45"] = 5,["46"] = 4,["47"] = 4,["48"] = 4,["49"] = 3,["52"] = 4,["53"] = 30,["54"] = 31,["55"] = 30,["56"] = 31,["57"] = 32,["58"] = 33,["61"] = 34,["62"] = 35,["63"] = 32,["64"] = 38,["65"] = 39,["66"] = 40,["67"] = 40,["69"] = 43,["70"] = 43,["72"] = 46,["73"] = 47,["74"] = 47,["76"] = 50,["77"] = 51,["78"] = 51,["80"] = 53,["81"] = 38,["82"] = 56,["83"] = 57,["86"] = 59,["87"] = 60,["90"] = 63,["91"] = 64,["94"] = 67,["95"] = 68,["96"] = 69,["97"] = 70,["98"] = 72,["101"] = 76,["102"] = 77,["103"] = 78,["104"] = 79,["105"] = 82,["106"] = 83,["107"] = 84,["109"] = 86,["110"] = 87,["111"] = 87,["112"] = 87,["113"] = 87,["114"] = 87,["116"] = 91,["117"] = 92,["118"] = 92,["119"] = 92,["120"] = 92,["121"] = 92,["122"] = 92,["123"] = 92,["124"] = 92,["125"] = 92,["126"] = 92,["127"] = 92,["128"] = 104,["129"] = 105,["130"] = 106,["133"] = 56,["134"] = 111,["135"] = 112,["136"] = 112,["137"] = 112,["138"] = 112,["139"] = 112,["140"] = 112,["141"] = 112,["142"] = 111,["143"] = 31,["144"] = 31,["145"] = 31,["146"] = 30,["149"] = 31}); +local ____exports = {} +local ____dota_ts_adapter = require("lib.dota_ts_adapter") +local BaseItem = ____dota_ts_adapter.BaseItem +local BaseModifier = ____dota_ts_adapter.BaseModifier +local registerAbility = ____dota_ts_adapter.registerAbility +local registerModifier = ____dota_ts_adapter.registerModifier +____exports.item_fire_summoner = __TS__Class() +local item_fire_summoner = ____exports.item_fire_summoner +item_fire_summoner.name = "item_fire_summoner" +__TS__ClassExtends(item_fire_summoner, BaseItem) +function item_fire_summoner.prototype.OnSpellStart(self) + if not IsServer() then + return + end + local caster = self:GetCaster() + if not caster then + return + end + local demon = CreateUnitByName( + "npc_dota_fire_summon", + caster:GetAbsOrigin() + RandomVector(100), + true, + caster, + caster, + caster:GetTeamNumber() + ) + demon:SetOwner(caster) + demon:SetBaseDamageMin(caster:GetBaseDamageMin()) + demon:SetBaseDamageMax(caster:GetBaseDamageMax()) + demon:SetBaseMoveSpeed(caster:GetBaseMoveSpeed() + 65) + demon:AddNewModifier(caster, nil, "modifier_fire_summoner_demon", {}) + demon:AddAbility("ability_stacking_crit"):SetLevel(1) + caster:EmitSound("Item.TomeOfKnowledge") + if self:GetCurrentCharges() <= self:GetInitialCharges() then + UTIL_Remove(self) + return + end + self:SetCurrentCharges(self:GetCurrentCharges() - self:GetInitialCharges()) +end +item_fire_summoner = __TS__Decorate( + item_fire_summoner, + item_fire_summoner, + {registerAbility(nil)}, + {kind = "class", name = "item_fire_summoner"} +) +____exports.item_fire_summoner = item_fire_summoner +____exports.modifier_fire_summoner_demon = __TS__Class() +local modifier_fire_summoner_demon = ____exports.modifier_fire_summoner_demon +modifier_fire_summoner_demon.name = "modifier_fire_summoner_demon" +__TS__ClassExtends(modifier_fire_summoner_demon, BaseModifier) +function modifier_fire_summoner_demon.prototype.OnCreated(self) + if not IsServer() then + return + end + self:StartIntervalThink(0.1) + self:OnIntervalThink() +end +function modifier_fire_summoner_demon.prototype.GetRealOwner(self) + local parent = self:GetParent() + if not parent then + return nil + end + if parent:IsRealHero() then + return parent + end + local owner = parent:GetOwner() + if not owner then + return nil + end + local ownerUnit = owner + if ownerUnit:IsRealHero() then + return ownerUnit + end + return nil +end +function modifier_fire_summoner_demon.prototype.OnIntervalThink(self) + if not IsServer() then + return + end + local parent = self:GetParent() + if not parent then + return + end + local realOwner = self:GetRealOwner() + if not realOwner then + return + end + local critModifier = realOwner:FindModifierByName("modifier_stacking_crit") + if critModifier then + local parentCritModifier = parent:FindModifierByName("modifier_stacking_crit") + if parentCritModifier then + parentCritModifier.critModifiers = critModifier.critModifiers + end + end + parent:SetBaseAttackTime(realOwner:GetBaseAttackTime()) + parent:SetBaseDamageMin(realOwner:GetBaseDamageMin()) + parent:SetBaseDamageMax(realOwner:GetBaseDamageMax()) + parent:SetBaseMoveSpeed(realOwner:GetBaseMoveSpeed()) + local distanceToOwner = (parent:GetAbsOrigin() - realOwner:GetAbsOrigin()):Length2D() + if distanceToOwner > 160 then + parent:MoveToPosition(realOwner:GetAbsOrigin() + RandomVector(200)) + end + if distanceToOwner > 400 then + FindClearSpaceForUnit( + parent, + realOwner:GetAbsOrigin() + RandomVector(200), + true + ) + end + if distanceToOwner <= 300 and realOwner:IsAlive() then + local enemies = FindUnitsInRadius( + parent:GetTeamNumber(), + parent:GetAbsOrigin(), + nil, + 500, + DOTA_UNIT_TARGET_TEAM_ENEMY, + bit.bor(DOTA_UNIT_TARGET_BASIC, DOTA_UNIT_TARGET_HERO), + DOTA_UNIT_TARGET_FLAG_NONE, + FIND_CLOSEST, + false + ) + if #enemies > 0 then + enemies[1]:MoveToTargetToAttack(realOwner) + parent:MoveToTargetToAttack(enemies[1]) + end + end +end +function modifier_fire_summoner_demon.prototype.CheckState(self) + return { + [MODIFIER_STATE_NO_HEALTH_BAR] = true, + [MODIFIER_STATE_INVULNERABLE] = true, + [MODIFIER_STATE_NO_UNIT_COLLISION] = true, + [MODIFIER_STATE_NO_TEAM_SELECT] = true, + [MODIFIER_STATE_NO_TEAM_MOVE_TO] = true + } +end +modifier_fire_summoner_demon = __TS__Decorate( + modifier_fire_summoner_demon, + modifier_fire_summoner_demon, + {registerModifier(nil)}, + {kind = "class", name = "modifier_fire_summoner_demon"} +) +____exports.modifier_fire_summoner_demon = modifier_fire_summoner_demon +return ____exports diff --git a/scripts/vscripts/items/blackshop/legendary/item_restock.lua b/scripts/vscripts/items/blackshop/legendary/item_restock.lua new file mode 100644 index 0000000..0e04331 --- /dev/null +++ b/scripts/vscripts/items/blackshop/legendary/item_restock.lua @@ -0,0 +1,60 @@ +local ____lualib = require("lualib_bundle") +local __TS__Class = ____lualib.__TS__Class +local __TS__ClassExtends = ____lualib.__TS__ClassExtends +local __TS__Decorate = ____lualib.__TS__Decorate +local __TS__SourceMapTraceBack = ____lualib.__TS__SourceMapTraceBack +__TS__SourceMapTraceBack(debug.getinfo(1).short_src, {["8"] = 1,["9"] = 1,["10"] = 1,["11"] = 1,["12"] = 1,["13"] = 4,["14"] = 5,["15"] = 4,["16"] = 5,["17"] = 6,["18"] = 7,["21"] = 9,["22"] = 10,["25"] = 13,["26"] = 16,["27"] = 17,["28"] = 19,["31"] = 22,["32"] = 6,["33"] = 5,["34"] = 5,["35"] = 5,["36"] = 4,["39"] = 5,["40"] = 26,["41"] = 27,["42"] = 26,["43"] = 27,["44"] = 28,["45"] = 29,["46"] = 28,["47"] = 31,["48"] = 32,["49"] = 31,["50"] = 34,["51"] = 35,["52"] = 34,["53"] = 27,["54"] = 27,["55"] = 27,["56"] = 26,["59"] = 27}); +local ____exports = {} +local ____dota_ts_adapter = require("lib.dota_ts_adapter") +local BaseItem = ____dota_ts_adapter.BaseItem +local BaseModifier = ____dota_ts_adapter.BaseModifier +local registerAbility = ____dota_ts_adapter.registerAbility +local registerModifier = ____dota_ts_adapter.registerModifier +____exports.item_restock = __TS__Class() +local item_restock = ____exports.item_restock +item_restock.name = "item_restock" +__TS__ClassExtends(item_restock, BaseItem) +function item_restock.prototype.OnSpellStart(self) + if not IsServer() then + return + end + local caster = self:GetCaster() + if not caster then + return + end + local heroes = HeroList:GetAllHeroes() + for ____, hero in ipairs(heroes) do + if hero:IsRealHero() then + hero:AddNewModifier(caster, self, "modifier_item_restock", {}) + end + end + UTIL_Remove(self) +end +item_restock = __TS__Decorate( + item_restock, + item_restock, + {registerAbility(nil)}, + {kind = "class", name = "item_restock"} +) +____exports.item_restock = item_restock +____exports.modifier_item_restock = __TS__Class() +local modifier_item_restock = ____exports.modifier_item_restock +modifier_item_restock.name = "modifier_item_restock" +__TS__ClassExtends(modifier_item_restock, BaseModifier) +function modifier_item_restock.prototype.RemoveOnDeath(self) + return false +end +function modifier_item_restock.prototype.IsHidden(self) + return false +end +function modifier_item_restock.prototype.GetTexture(self) + return "blackshop/restock" +end +modifier_item_restock = __TS__Decorate( + modifier_item_restock, + modifier_item_restock, + {registerModifier(nil)}, + {kind = "class", name = "modifier_item_restock"} +) +____exports.modifier_item_restock = modifier_item_restock +return ____exports diff --git a/scripts/vscripts/items/blackshop/rare/item_agility_cape.lua b/scripts/vscripts/items/blackshop/rare/item_agility_cape.lua new file mode 100644 index 0000000..e6f2c03 --- /dev/null +++ b/scripts/vscripts/items/blackshop/rare/item_agility_cape.lua @@ -0,0 +1,66 @@ +local ____lualib = require("lualib_bundle") +local __TS__Class = ____lualib.__TS__Class +local __TS__ClassExtends = ____lualib.__TS__ClassExtends +local __TS__Decorate = ____lualib.__TS__Decorate +local __TS__SourceMapTraceBack = ____lualib.__TS__SourceMapTraceBack +__TS__SourceMapTraceBack(debug.getinfo(1).short_src, {["8"] = 1,["9"] = 1,["10"] = 1,["11"] = 1,["12"] = 1,["13"] = 3,["14"] = 4,["15"] = 3,["16"] = 4,["17"] = 5,["18"] = 6,["21"] = 8,["22"] = 9,["25"] = 11,["26"] = 13,["27"] = 14,["29"] = 18,["31"] = 20,["32"] = 5,["33"] = 4,["34"] = 4,["35"] = 4,["36"] = 3,["39"] = 4,["40"] = 24,["41"] = 25,["42"] = 24,["43"] = 25,["44"] = 26,["45"] = 27,["46"] = 26,["47"] = 30,["48"] = 31,["49"] = 30,["50"] = 34,["51"] = 35,["52"] = 34,["53"] = 38,["54"] = 39,["55"] = 38,["56"] = 42,["57"] = 43,["58"] = 42,["59"] = 25,["60"] = 25,["61"] = 25,["62"] = 24,["65"] = 25}); +local ____exports = {} +local ____dota_ts_adapter = require("lib.dota_ts_adapter") +local BaseItem = ____dota_ts_adapter.BaseItem +local BaseModifier = ____dota_ts_adapter.BaseModifier +local registerAbility = ____dota_ts_adapter.registerAbility +local registerModifier = ____dota_ts_adapter.registerModifier +____exports.item_agility_cape = __TS__Class() +local item_agility_cape = ____exports.item_agility_cape +item_agility_cape.name = "item_agility_cape" +__TS__ClassExtends(item_agility_cape, BaseItem) +function item_agility_cape.prototype.OnSpellStart(self) + if not IsServer() then + return + end + local caster = self:GetCaster() + if not caster then + return + end + local agility = self:GetSpecialValueFor("bonus_agility") + if caster:HasModifier("modifier_item_agility_cape") then + caster:FindModifierByName("modifier_item_agility_cape"):SetStackCount(caster:FindModifierByName("modifier_item_agility_cape"):GetStackCount() + agility) + else + caster:AddNewModifier(caster, self, "modifier_item_agility_cape", {}):SetStackCount(agility) + end + UTIL_Remove(self) +end +item_agility_cape = __TS__Decorate( + item_agility_cape, + item_agility_cape, + {registerAbility(nil)}, + {kind = "class", name = "item_agility_cape"} +) +____exports.item_agility_cape = item_agility_cape +____exports.modifier_item_agility_cape = __TS__Class() +local modifier_item_agility_cape = ____exports.modifier_item_agility_cape +modifier_item_agility_cape.name = "modifier_item_agility_cape" +__TS__ClassExtends(modifier_item_agility_cape, BaseModifier) +function modifier_item_agility_cape.prototype.RemoveOnDeath(self) + return false +end +function modifier_item_agility_cape.prototype.IsHidden(self) + return true +end +function modifier_item_agility_cape.prototype.IsPurgable(self) + return false +end +function modifier_item_agility_cape.prototype.DeclareFunctions(self) + return {MODIFIER_PROPERTY_STATS_AGILITY_BONUS} +end +function modifier_item_agility_cape.prototype.GetModifierBonusStats_Agility(self) + return self:GetStackCount() +end +modifier_item_agility_cape = __TS__Decorate( + modifier_item_agility_cape, + modifier_item_agility_cape, + {registerModifier(nil)}, + {kind = "class", name = "modifier_item_agility_cape"} +) +____exports.modifier_item_agility_cape = modifier_item_agility_cape +return ____exports diff --git a/scripts/vscripts/items/blackshop/rare/item_blackshop_rare_agility_cape.lua b/scripts/vscripts/items/blackshop/rare/item_blackshop_rare_agility_cape.lua new file mode 100644 index 0000000..ecc40a6 --- /dev/null +++ b/scripts/vscripts/items/blackshop/rare/item_blackshop_rare_agility_cape.lua @@ -0,0 +1,69 @@ +local ____lualib = require("lualib_bundle") +local __TS__Class = ____lualib.__TS__Class +local __TS__ClassExtends = ____lualib.__TS__ClassExtends +local __TS__Decorate = ____lualib.__TS__Decorate +local ____exports = {} +local ____dota_ts_adapter = require("lib.dota_ts_adapter") +local BaseItem = ____dota_ts_adapter.BaseItem +local BaseModifier = ____dota_ts_adapter.BaseModifier +local registerAbility = ____dota_ts_adapter.registerAbility +local registerModifier = ____dota_ts_adapter.registerModifier +____exports.item_blackshop_rare_agility_cape = __TS__Class() +local item_blackshop_rare_agility_cape = ____exports.item_blackshop_rare_agility_cape +item_blackshop_rare_agility_cape.name = "item_blackshop_rare_agility_cape" +item_blackshop_rare_agility_cape.____file_path = "scripts/vscripts/items/blackshop/rare/item_blackshop_rare_agility_cape.lua" +__TS__ClassExtends(item_blackshop_rare_agility_cape, BaseItem) +function item_blackshop_rare_agility_cape.prototype.OnSpellStart(self) + if not IsServer() then + return + end + local caster = self:GetCaster() + if not caster then + return + end + local agility = self:GetSpecialValueFor("bonus_agility") + if caster:HasModifier("modifier_item_agility_cape") then + caster:FindModifierByName("modifier_item_agility_cape"):SetStackCount(caster:FindModifierByName("modifier_item_agility_cape"):GetStackCount() + agility) + else + caster:AddNewModifier(caster, self, "modifier_item_agility_cape", {}):SetStackCount(agility) + end + UTIL_Remove(self) +end +item_blackshop_rare_agility_cape = __TS__Decorate( + item_blackshop_rare_agility_cape, + item_blackshop_rare_agility_cape, + {registerAbility(nil)}, + {kind = "class", name = "item_blackshop_rare_agility_cape"} +) +____exports.item_blackshop_rare_agility_cape = item_blackshop_rare_agility_cape +____exports.modifier_item_agility_cape = __TS__Class() +local modifier_item_agility_cape = ____exports.modifier_item_agility_cape +modifier_item_agility_cape.name = "modifier_item_agility_cape" +modifier_item_agility_cape.____file_path = "scripts/vscripts/items/blackshop/rare/item_blackshop_rare_agility_cape.lua" +__TS__ClassExtends(modifier_item_agility_cape, BaseModifier) +function modifier_item_agility_cape.prototype.RemoveOnDeath(self) + return false +end +function modifier_item_agility_cape.prototype.IsHidden(self) + return true +end +function modifier_item_agility_cape.prototype.IsPurgable(self) + return false +end +function modifier_item_agility_cape.prototype.GetTexture(self) + return "../items/blackshop/agility_cape" +end +function modifier_item_agility_cape.prototype.DeclareFunctions(self) + return {MODIFIER_PROPERTY_STATS_AGILITY_BONUS} +end +function modifier_item_agility_cape.prototype.GetModifierBonusStats_Agility(self) + return self:GetStackCount() +end +modifier_item_agility_cape = __TS__Decorate( + modifier_item_agility_cape, + modifier_item_agility_cape, + {registerModifier(nil)}, + {kind = "class", name = "modifier_item_agility_cape"} +) +____exports.modifier_item_agility_cape = modifier_item_agility_cape +return ____exports diff --git a/scripts/vscripts/items/blackshop/rare/item_blackshop_rare_critical_havoc.lua b/scripts/vscripts/items/blackshop/rare/item_blackshop_rare_critical_havoc.lua new file mode 100644 index 0000000..2d722c1 --- /dev/null +++ b/scripts/vscripts/items/blackshop/rare/item_blackshop_rare_critical_havoc.lua @@ -0,0 +1,68 @@ +local ____lualib = require("lualib_bundle") +local __TS__Class = ____lualib.__TS__Class +local __TS__ClassExtends = ____lualib.__TS__ClassExtends +local __TS__Decorate = ____lualib.__TS__Decorate +local ____exports = {} +local ____dota_ts_adapter = require("lib.dota_ts_adapter") +local BaseItem = ____dota_ts_adapter.BaseItem +local BaseModifier = ____dota_ts_adapter.BaseModifier +local registerAbility = ____dota_ts_adapter.registerAbility +local registerModifier = ____dota_ts_adapter.registerModifier +____exports.item_blackshop_rare_critical_havoc = __TS__Class() +local item_blackshop_rare_critical_havoc = ____exports.item_blackshop_rare_critical_havoc +item_blackshop_rare_critical_havoc.name = "item_blackshop_rare_critical_havoc" +item_blackshop_rare_critical_havoc.____file_path = "scripts/vscripts/items/blackshop/rare/item_blackshop_rare_critical_havoc.lua" +__TS__ClassExtends(item_blackshop_rare_critical_havoc, BaseItem) +function item_blackshop_rare_critical_havoc.prototype.OnSpellStart(self) + if not IsServer() then + return + end + local caster = self:GetCaster() + if not caster then + return + end + caster:AddNewModifier(caster, self, "modifier_item_critical_havoc", {}) + UTIL_Remove(self) +end +item_blackshop_rare_critical_havoc = __TS__Decorate( + item_blackshop_rare_critical_havoc, + item_blackshop_rare_critical_havoc, + {registerAbility(nil)}, + {kind = "class", name = "item_blackshop_rare_critical_havoc"} +) +____exports.item_blackshop_rare_critical_havoc = item_blackshop_rare_critical_havoc +____exports.modifier_item_critical_havoc = __TS__Class() +local modifier_item_critical_havoc = ____exports.modifier_item_critical_havoc +modifier_item_critical_havoc.name = "modifier_item_critical_havoc" +modifier_item_critical_havoc.____file_path = "scripts/vscripts/items/blackshop/rare/item_blackshop_rare_critical_havoc.lua" +__TS__ClassExtends(modifier_item_critical_havoc, BaseModifier) +function modifier_item_critical_havoc.prototype.IsHidden(self) + return true +end +function modifier_item_critical_havoc.prototype.IsPurgable(self) + return false +end +function modifier_item_critical_havoc.prototype.GetTexture(self) + return "../items/blackshop/critical_havoc" +end +function modifier_item_critical_havoc.prototype.RemoveOnDeath(self) + return false +end +function modifier_item_critical_havoc.prototype.OnCreated(self) + if not IsServer() then + return + end + local stackingCritMod = self:GetParent():FindModifierByName("modifier_stacking_crit") + if stackingCritMod then + local ability = self:GetAbility() + stackingCritMod:AddCustomCrit(10, 200, "item_critical_havoc", ability) + end +end +modifier_item_critical_havoc = __TS__Decorate( + modifier_item_critical_havoc, + modifier_item_critical_havoc, + {registerModifier(nil)}, + {kind = "class", name = "modifier_item_critical_havoc"} +) +____exports.modifier_item_critical_havoc = modifier_item_critical_havoc +return ____exports diff --git a/scripts/vscripts/items/blackshop/rare/item_blackshop_rare_damage_dagger.lua b/scripts/vscripts/items/blackshop/rare/item_blackshop_rare_damage_dagger.lua new file mode 100644 index 0000000..256e464 --- /dev/null +++ b/scripts/vscripts/items/blackshop/rare/item_blackshop_rare_damage_dagger.lua @@ -0,0 +1,135 @@ +local ____lualib = require("lualib_bundle") +local __TS__Class = ____lualib.__TS__Class +local __TS__ClassExtends = ____lualib.__TS__ClassExtends +local __TS__Decorate = ____lualib.__TS__Decorate +local ____exports = {} +local ____dota_ts_adapter = require("lib.dota_ts_adapter") +local BaseItem = ____dota_ts_adapter.BaseItem +local BaseModifier = ____dota_ts_adapter.BaseModifier +local registerAbility = ____dota_ts_adapter.registerAbility +local registerModifier = ____dota_ts_adapter.registerModifier +____exports.item_blackshop_rare_damage_dagger = __TS__Class() +local item_blackshop_rare_damage_dagger = ____exports.item_blackshop_rare_damage_dagger +item_blackshop_rare_damage_dagger.name = "item_blackshop_rare_damage_dagger" +item_blackshop_rare_damage_dagger.____file_path = "scripts/vscripts/items/blackshop/rare/item_blackshop_rare_damage_dagger.lua" +__TS__ClassExtends(item_blackshop_rare_damage_dagger, BaseItem) +function item_blackshop_rare_damage_dagger.prototype.OnSpellStart(self) + if not IsServer() then + return + end + local caster = self:GetCaster() + if not caster then + return + end + local stats = self:GetSpecialValueFor("bonus_damage") + if caster:HasModifier("modifier_item_damage_dagger") then + caster:FindModifierByName("modifier_item_damage_dagger"):SetStackCount(caster:FindModifierByName("modifier_item_damage_dagger"):GetStackCount() + stats) + else + caster:AddNewModifier(caster, self, "modifier_item_damage_dagger", {}):SetStackCount(stats) + end + UTIL_Remove(self) +end +item_blackshop_rare_damage_dagger = __TS__Decorate( + item_blackshop_rare_damage_dagger, + item_blackshop_rare_damage_dagger, + {registerAbility(nil)}, + {kind = "class", name = "item_blackshop_rare_damage_dagger"} +) +____exports.item_blackshop_rare_damage_dagger = item_blackshop_rare_damage_dagger +____exports.modifier_item_damage_dagger = __TS__Class() +local modifier_item_damage_dagger = ____exports.modifier_item_damage_dagger +modifier_item_damage_dagger.name = "modifier_item_damage_dagger" +modifier_item_damage_dagger.____file_path = "scripts/vscripts/items/blackshop/rare/item_blackshop_rare_damage_dagger.lua" +__TS__ClassExtends(modifier_item_damage_dagger, BaseModifier) +function modifier_item_damage_dagger.prototype.RemoveOnDeath(self) + return false +end +function modifier_item_damage_dagger.prototype.IsHidden(self) + return false +end +function modifier_item_damage_dagger.prototype.IsPurgable(self) + return false +end +function modifier_item_damage_dagger.prototype.GetTexture(self) + return "../items/blackshop/damage_dagger" +end +function modifier_item_damage_dagger.prototype.DeclareFunctions(self) + return {MODIFIER_PROPERTY_PREATTACK_BONUS_DAMAGE, MODIFIER_EVENT_ON_ATTACK_LANDED} +end +function modifier_item_damage_dagger.prototype.GetModifierPreAttack_BonusDamage(self) + return self:GetStackCount() +end +function modifier_item_damage_dagger.prototype.OnAttackLanded(self, keys) + if not IsServer() then + return + end + local attacker = keys.attacker + if attacker ~= self:GetParent() then + return + end + local parent = self:GetParent() + if not parent then + return + end + if not parent:HasModifier("modifier_item_damage_dagger_timer") then + parent:AddNewModifier( + parent, + getModifierSourceAbility(nil, parent), + "modifier_item_damage_dagger_timer", + {duration = 4} + ):SetStackCount(self:GetStackCount()) + parent:RemoveModifierByName("modifier_item_damage_dagger") + end +end +modifier_item_damage_dagger = __TS__Decorate( + modifier_item_damage_dagger, + modifier_item_damage_dagger, + {registerModifier(nil)}, + {kind = "class", name = "modifier_item_damage_dagger"} +) +____exports.modifier_item_damage_dagger = modifier_item_damage_dagger +____exports.modifier_item_damage_dagger_timer = __TS__Class() +local modifier_item_damage_dagger_timer = ____exports.modifier_item_damage_dagger_timer +modifier_item_damage_dagger_timer.name = "modifier_item_damage_dagger_timer" +modifier_item_damage_dagger_timer.____file_path = "scripts/vscripts/items/blackshop/rare/item_blackshop_rare_damage_dagger.lua" +__TS__ClassExtends(modifier_item_damage_dagger_timer, BaseModifier) +function modifier_item_damage_dagger_timer.prototype.RemoveOnDeath(self) + return false +end +function modifier_item_damage_dagger_timer.prototype.IsPurgable(self) + return false +end +function modifier_item_damage_dagger_timer.prototype.GetTexture(self) + return "../items/blackshop/damage_dagger" +end +function modifier_item_damage_dagger_timer.prototype.OnDestroy(self) + if IsClient() then + return + end + local parent = self:GetParent() + if not parent or not parent:IsAlive() then + return + end + local stacks = self:GetStackCount() + local modifier = parent:AddNewModifier( + parent, + getModifierSourceAbility(nil, parent), + "modifier_item_damage_dagger", + {} + ) + if modifier ~= nil then + if self:GetParent():HasModifier("modifier_item_damage_dagger") then + modifier:SetStackCount(self:GetParent():FindModifierByName("modifier_item_damage_dagger"):GetStackCount() + stacks) + else + modifier:SetStackCount(stacks) + end + end +end +modifier_item_damage_dagger_timer = __TS__Decorate( + modifier_item_damage_dagger_timer, + modifier_item_damage_dagger_timer, + {registerModifier(nil)}, + {kind = "class", name = "modifier_item_damage_dagger_timer"} +) +____exports.modifier_item_damage_dagger_timer = modifier_item_damage_dagger_timer +return ____exports diff --git a/scripts/vscripts/items/blackshop/rare/item_blackshop_rare_egg_of_death.lua b/scripts/vscripts/items/blackshop/rare/item_blackshop_rare_egg_of_death.lua new file mode 100644 index 0000000..a0b9c81 --- /dev/null +++ b/scripts/vscripts/items/blackshop/rare/item_blackshop_rare_egg_of_death.lua @@ -0,0 +1,75 @@ +local ____lualib = require("lualib_bundle") +local __TS__Class = ____lualib.__TS__Class +local __TS__ClassExtends = ____lualib.__TS__ClassExtends +local __TS__Decorate = ____lualib.__TS__Decorate +local ____exports = {} +local ____dota_ts_adapter = require("lib.dota_ts_adapter") +local BaseItem = ____dota_ts_adapter.BaseItem +local BaseModifier = ____dota_ts_adapter.BaseModifier +local registerAbility = ____dota_ts_adapter.registerAbility +local registerModifier = ____dota_ts_adapter.registerModifier +____exports.item_blackshop_rare_egg_of_death = __TS__Class() +local item_blackshop_rare_egg_of_death = ____exports.item_blackshop_rare_egg_of_death +item_blackshop_rare_egg_of_death.name = "item_blackshop_rare_egg_of_death" +item_blackshop_rare_egg_of_death.____file_path = "scripts/vscripts/items/blackshop/rare/item_blackshop_rare_egg_of_death.lua" +__TS__ClassExtends(item_blackshop_rare_egg_of_death, BaseItem) +function item_blackshop_rare_egg_of_death.prototype.OnSpellStart(self) + if not IsServer() then + return + end + local caster = self:GetCaster() + if not caster then + return + end + local stats = self:GetSpecialValueFor("bonus_all") + if caster:HasModifier("modifier_item_egg_of_death") then + caster:FindModifierByName("modifier_item_egg_of_death"):SetStackCount(caster:FindModifierByName("modifier_item_egg_of_death"):GetStackCount() + stats) + else + caster:AddNewModifier(caster, self, "modifier_item_egg_of_death", {}):SetStackCount(stats) + end + UTIL_Remove(self) +end +item_blackshop_rare_egg_of_death = __TS__Decorate( + item_blackshop_rare_egg_of_death, + item_blackshop_rare_egg_of_death, + {registerAbility(nil)}, + {kind = "class", name = "item_blackshop_rare_egg_of_death"} +) +____exports.item_blackshop_rare_egg_of_death = item_blackshop_rare_egg_of_death +____exports.modifier_item_egg_of_death = __TS__Class() +local modifier_item_egg_of_death = ____exports.modifier_item_egg_of_death +modifier_item_egg_of_death.name = "modifier_item_egg_of_death" +modifier_item_egg_of_death.____file_path = "scripts/vscripts/items/blackshop/rare/item_blackshop_rare_egg_of_death.lua" +__TS__ClassExtends(modifier_item_egg_of_death, BaseModifier) +function modifier_item_egg_of_death.prototype.RemoveOnDeath(self) + return false +end +function modifier_item_egg_of_death.prototype.IsHidden(self) + return true +end +function modifier_item_egg_of_death.prototype.IsPurgable(self) + return false +end +function modifier_item_egg_of_death.prototype.GetTexture(self) + return "../items/blackshop/egg_of_death" +end +function modifier_item_egg_of_death.prototype.DeclareFunctions(self) + return {MODIFIER_PROPERTY_STATS_AGILITY_BONUS, MODIFIER_PROPERTY_STATS_INTELLECT_BONUS, MODIFIER_PROPERTY_STATS_STRENGTH_BONUS} +end +function modifier_item_egg_of_death.prototype.GetModifierBonusStats_Agility(self) + return self:GetStackCount() +end +function modifier_item_egg_of_death.prototype.GetModifierBonusStats_Intellect(self) + return self:GetStackCount() +end +function modifier_item_egg_of_death.prototype.GetModifierBonusStats_Strength(self) + return self:GetStackCount() +end +modifier_item_egg_of_death = __TS__Decorate( + modifier_item_egg_of_death, + modifier_item_egg_of_death, + {registerModifier(nil)}, + {kind = "class", name = "modifier_item_egg_of_death"} +) +____exports.modifier_item_egg_of_death = modifier_item_egg_of_death +return ____exports diff --git a/scripts/vscripts/items/blackshop/rare/item_blackshop_rare_granite_badge.lua b/scripts/vscripts/items/blackshop/rare/item_blackshop_rare_granite_badge.lua new file mode 100644 index 0000000..f9dd06f --- /dev/null +++ b/scripts/vscripts/items/blackshop/rare/item_blackshop_rare_granite_badge.lua @@ -0,0 +1,71 @@ +local ____lualib = require("lualib_bundle") +local __TS__Class = ____lualib.__TS__Class +local __TS__ClassExtends = ____lualib.__TS__ClassExtends +local __TS__Decorate = ____lualib.__TS__Decorate +local ____exports = {} +local ____dota_ts_adapter = require("lib.dota_ts_adapter") +local BaseItem = ____dota_ts_adapter.BaseItem +local BaseModifier = ____dota_ts_adapter.BaseModifier +local registerAbility = ____dota_ts_adapter.registerAbility +local registerModifier = ____dota_ts_adapter.registerModifier +--- Гранитный жетон — запас здоровья (стакается). +____exports.item_blackshop_rare_granite_badge = __TS__Class() +local item_blackshop_rare_granite_badge = ____exports.item_blackshop_rare_granite_badge +item_blackshop_rare_granite_badge.name = "item_blackshop_rare_granite_badge" +item_blackshop_rare_granite_badge.____file_path = "scripts/vscripts/items/blackshop/rare/item_blackshop_rare_granite_badge.lua" +__TS__ClassExtends(item_blackshop_rare_granite_badge, BaseItem) +function item_blackshop_rare_granite_badge.prototype.OnSpellStart(self) + if not IsServer() then + return + end + local caster = self:GetCaster() + if not caster then + return + end + local add = self:GetSpecialValueFor("bonus_health") + if caster:HasModifier("modifier_item_granite_badge") then + local m = caster:FindModifierByName("modifier_item_granite_badge") + m:SetStackCount(m:GetStackCount() + add) + else + caster:AddNewModifier(caster, self, "modifier_item_granite_badge", {}):SetStackCount(add) + end + UTIL_Remove(self) +end +item_blackshop_rare_granite_badge = __TS__Decorate( + item_blackshop_rare_granite_badge, + item_blackshop_rare_granite_badge, + {registerAbility(nil)}, + {kind = "class", name = "item_blackshop_rare_granite_badge"} +) +____exports.item_blackshop_rare_granite_badge = item_blackshop_rare_granite_badge +____exports.modifier_item_granite_badge = __TS__Class() +local modifier_item_granite_badge = ____exports.modifier_item_granite_badge +modifier_item_granite_badge.name = "modifier_item_granite_badge" +modifier_item_granite_badge.____file_path = "scripts/vscripts/items/blackshop/rare/item_blackshop_rare_granite_badge.lua" +__TS__ClassExtends(modifier_item_granite_badge, BaseModifier) +function modifier_item_granite_badge.prototype.RemoveOnDeath(self) + return false +end +function modifier_item_granite_badge.prototype.IsHidden(self) + return false +end +function modifier_item_granite_badge.prototype.IsPurgable(self) + return false +end +function modifier_item_granite_badge.prototype.GetTexture(self) + return "../items/blackshop/granite_stone" +end +function modifier_item_granite_badge.prototype.DeclareFunctions(self) + return {MODIFIER_PROPERTY_HEALTH_BONUS} +end +function modifier_item_granite_badge.prototype.GetModifierHealthBonus(self) + return self:GetStackCount() +end +modifier_item_granite_badge = __TS__Decorate( + modifier_item_granite_badge, + modifier_item_granite_badge, + {registerModifier(nil)}, + {kind = "class", name = "modifier_item_granite_badge"} +) +____exports.modifier_item_granite_badge = modifier_item_granite_badge +return ____exports diff --git a/scripts/vscripts/items/blackshop/rare/item_blackshop_rare_iron_resolve.lua b/scripts/vscripts/items/blackshop/rare/item_blackshop_rare_iron_resolve.lua new file mode 100644 index 0000000..369da33 --- /dev/null +++ b/scripts/vscripts/items/blackshop/rare/item_blackshop_rare_iron_resolve.lua @@ -0,0 +1,71 @@ +local ____lualib = require("lualib_bundle") +local __TS__Class = ____lualib.__TS__Class +local __TS__ClassExtends = ____lualib.__TS__ClassExtends +local __TS__Decorate = ____lualib.__TS__Decorate +local ____exports = {} +local ____dota_ts_adapter = require("lib.dota_ts_adapter") +local BaseItem = ____dota_ts_adapter.BaseItem +local BaseModifier = ____dota_ts_adapter.BaseModifier +local registerAbility = ____dota_ts_adapter.registerAbility +local registerModifier = ____dota_ts_adapter.registerModifier +--- Железная воля — стойкость к эффектам (стакается). +____exports.item_blackshop_rare_iron_resolve = __TS__Class() +local item_blackshop_rare_iron_resolve = ____exports.item_blackshop_rare_iron_resolve +item_blackshop_rare_iron_resolve.name = "item_blackshop_rare_iron_resolve" +item_blackshop_rare_iron_resolve.____file_path = "scripts/vscripts/items/blackshop/rare/item_blackshop_rare_iron_resolve.lua" +__TS__ClassExtends(item_blackshop_rare_iron_resolve, BaseItem) +function item_blackshop_rare_iron_resolve.prototype.OnSpellStart(self) + if not IsServer() then + return + end + local caster = self:GetCaster() + if not caster then + return + end + local add = self:GetSpecialValueFor("bonus_status_resist") + if caster:HasModifier("modifier_item_iron_resolve") then + local m = caster:FindModifierByName("modifier_item_iron_resolve") + m:SetStackCount(m:GetStackCount() + add) + else + caster:AddNewModifier(caster, self, "modifier_item_iron_resolve", {}):SetStackCount(add) + end + UTIL_Remove(self) +end +item_blackshop_rare_iron_resolve = __TS__Decorate( + item_blackshop_rare_iron_resolve, + item_blackshop_rare_iron_resolve, + {registerAbility(nil)}, + {kind = "class", name = "item_blackshop_rare_iron_resolve"} +) +____exports.item_blackshop_rare_iron_resolve = item_blackshop_rare_iron_resolve +____exports.modifier_item_iron_resolve = __TS__Class() +local modifier_item_iron_resolve = ____exports.modifier_item_iron_resolve +modifier_item_iron_resolve.name = "modifier_item_iron_resolve" +modifier_item_iron_resolve.____file_path = "scripts/vscripts/items/blackshop/rare/item_blackshop_rare_iron_resolve.lua" +__TS__ClassExtends(modifier_item_iron_resolve, BaseModifier) +function modifier_item_iron_resolve.prototype.RemoveOnDeath(self) + return false +end +function modifier_item_iron_resolve.prototype.IsHidden(self) + return false +end +function modifier_item_iron_resolve.prototype.IsPurgable(self) + return false +end +function modifier_item_iron_resolve.prototype.GetTexture(self) + return "../items/blackshop/ironwood_tree" +end +function modifier_item_iron_resolve.prototype.DeclareFunctions(self) + return {MODIFIER_PROPERTY_STATUS_RESISTANCE_STACKING} +end +function modifier_item_iron_resolve.prototype.GetModifierStatusResistanceStacking(self) + return self:GetStackCount() +end +modifier_item_iron_resolve = __TS__Decorate( + modifier_item_iron_resolve, + modifier_item_iron_resolve, + {registerModifier(nil)}, + {kind = "class", name = "modifier_item_iron_resolve"} +) +____exports.modifier_item_iron_resolve = modifier_item_iron_resolve +return ____exports diff --git a/scripts/vscripts/items/blackshop/rare/item_blackshop_rare_silver_eye.lua b/scripts/vscripts/items/blackshop/rare/item_blackshop_rare_silver_eye.lua new file mode 100644 index 0000000..ae9e283 --- /dev/null +++ b/scripts/vscripts/items/blackshop/rare/item_blackshop_rare_silver_eye.lua @@ -0,0 +1,81 @@ +local ____lualib = require("lualib_bundle") +local __TS__Class = ____lualib.__TS__Class +local __TS__ClassExtends = ____lualib.__TS__ClassExtends +local __TS__Decorate = ____lualib.__TS__Decorate +local ____exports = {} +local ____dota_ts_adapter = require("lib.dota_ts_adapter") +local BaseItem = ____dota_ts_adapter.BaseItem +local BaseModifier = ____dota_ts_adapter.BaseModifier +local registerAbility = ____dota_ts_adapter.registerAbility +local registerModifier = ____dota_ts_adapter.registerModifier +____exports.item_blackshop_rare_silver_eye = __TS__Class() +local item_blackshop_rare_silver_eye = ____exports.item_blackshop_rare_silver_eye +item_blackshop_rare_silver_eye.name = "item_blackshop_rare_silver_eye" +item_blackshop_rare_silver_eye.____file_path = "scripts/vscripts/items/blackshop/rare/item_blackshop_rare_silver_eye.lua" +__TS__ClassExtends(item_blackshop_rare_silver_eye, BaseItem) +function item_blackshop_rare_silver_eye.prototype.OnSpellStart(self) + if not IsServer() then + return + end + local caster = self:GetCaster() + if not caster then + return + end + local vision = self:GetSpecialValueFor("bonus_vision") + if caster:HasModifier("modifier_item_silver_eye") then + caster:FindModifierByName("modifier_item_silver_eye"):SetStackCount(caster:FindModifierByName("modifier_item_silver_eye"):GetStackCount() + vision) + else + caster:AddNewModifier(caster, self, "modifier_item_silver_eye", {}):SetStackCount(vision) + end + UTIL_Remove(self) +end +item_blackshop_rare_silver_eye = __TS__Decorate( + item_blackshop_rare_silver_eye, + item_blackshop_rare_silver_eye, + {registerAbility(nil)}, + {kind = "class", name = "item_blackshop_rare_silver_eye"} +) +____exports.item_blackshop_rare_silver_eye = item_blackshop_rare_silver_eye +____exports.modifier_item_silver_eye = __TS__Class() +local modifier_item_silver_eye = ____exports.modifier_item_silver_eye +modifier_item_silver_eye.name = "modifier_item_silver_eye" +modifier_item_silver_eye.____file_path = "scripts/vscripts/items/blackshop/rare/item_blackshop_rare_silver_eye.lua" +__TS__ClassExtends(modifier_item_silver_eye, BaseModifier) +function modifier_item_silver_eye.prototype.RemoveOnDeath(self) + return false +end +function modifier_item_silver_eye.prototype.IsHidden(self) + return true +end +function modifier_item_silver_eye.prototype.IsPurgable(self) + return false +end +function modifier_item_silver_eye.prototype.GetTexture(self) + return "../items/blackshop/silver_eye" +end +function modifier_item_silver_eye.prototype.DeclareFunctions(self) + return {MODIFIER_PROPERTY_BONUS_NIGHT_VISION, MODIFIER_PROPERTY_BONUS_DAY_VISION, MODIFIER_PROPERTY_CAST_RANGE_BONUS_STACKING, MODIFIER_PROPERTY_ATTACK_RANGE_BONUS} +end +function modifier_item_silver_eye.prototype.GetBonusNightVision(self) + return self:GetStackCount() +end +function modifier_item_silver_eye.prototype.GetBonusDayVision(self) + return self:GetStackCount() +end +function modifier_item_silver_eye.prototype.GetModifierCastRangeBonusStacking(self) + return self:GetStackCount() +end +function modifier_item_silver_eye.prototype.GetModifierAttackRangeBonus(self) + if self:GetParent():IsRangedAttacker() then + return self:GetStackCount() + end + return 0 +end +modifier_item_silver_eye = __TS__Decorate( + modifier_item_silver_eye, + modifier_item_silver_eye, + {registerModifier(nil)}, + {kind = "class", name = "modifier_item_silver_eye"} +) +____exports.modifier_item_silver_eye = modifier_item_silver_eye +return ____exports diff --git a/scripts/vscripts/items/blackshop/rare/item_critical_havoc.lua b/scripts/vscripts/items/blackshop/rare/item_critical_havoc.lua new file mode 100644 index 0000000..9d42d1c --- /dev/null +++ b/scripts/vscripts/items/blackshop/rare/item_critical_havoc.lua @@ -0,0 +1,66 @@ +local ____lualib = require("lualib_bundle") +local __TS__Class = ____lualib.__TS__Class +local __TS__ClassExtends = ____lualib.__TS__ClassExtends +local __TS__Decorate = ____lualib.__TS__Decorate +local __TS__SourceMapTraceBack = ____lualib.__TS__SourceMapTraceBack +__TS__SourceMapTraceBack(debug.getinfo(1).short_src, {["8"] = 1,["9"] = 1,["10"] = 1,["11"] = 1,["12"] = 1,["13"] = 4,["14"] = 5,["15"] = 4,["16"] = 5,["17"] = 6,["18"] = 7,["21"] = 9,["22"] = 10,["25"] = 11,["26"] = 13,["27"] = 6,["28"] = 5,["29"] = 5,["30"] = 5,["31"] = 4,["34"] = 5,["35"] = 17,["36"] = 18,["37"] = 17,["38"] = 18,["39"] = 19,["40"] = 20,["41"] = 19,["42"] = 23,["43"] = 24,["44"] = 23,["45"] = 27,["46"] = 28,["47"] = 27,["48"] = 31,["49"] = 32,["52"] = 33,["53"] = 34,["54"] = 35,["55"] = 36,["56"] = 37,["58"] = 31,["59"] = 18,["60"] = 18,["61"] = 18,["62"] = 17,["65"] = 18}); +local ____exports = {} +local ____dota_ts_adapter = require("lib.dota_ts_adapter") +local BaseItem = ____dota_ts_adapter.BaseItem +local BaseModifier = ____dota_ts_adapter.BaseModifier +local registerAbility = ____dota_ts_adapter.registerAbility +local registerModifier = ____dota_ts_adapter.registerModifier +____exports.item_critical_havoc = __TS__Class() +local item_critical_havoc = ____exports.item_critical_havoc +item_critical_havoc.name = "item_critical_havoc" +__TS__ClassExtends(item_critical_havoc, BaseItem) +function item_critical_havoc.prototype.OnSpellStart(self) + if not IsServer() then + return + end + local caster = self:GetCaster() + if not caster then + return + end + caster:AddNewModifier(caster, self, "modifier_item_critical_havoc", {}) + UTIL_Remove(self) +end +item_critical_havoc = __TS__Decorate( + item_critical_havoc, + item_critical_havoc, + {registerAbility(nil)}, + {kind = "class", name = "item_critical_havoc"} +) +____exports.item_critical_havoc = item_critical_havoc +____exports.modifier_item_critical_havoc = __TS__Class() +local modifier_item_critical_havoc = ____exports.modifier_item_critical_havoc +modifier_item_critical_havoc.name = "modifier_item_critical_havoc" +__TS__ClassExtends(modifier_item_critical_havoc, BaseModifier) +function modifier_item_critical_havoc.prototype.IsHidden(self) + return true +end +function modifier_item_critical_havoc.prototype.IsPurgable(self) + return false +end +function modifier_item_critical_havoc.prototype.RemoveOnDeath(self) + return false +end +function modifier_item_critical_havoc.prototype.OnCreated(self) + if not IsServer() then + return + end + print("[Item Test] Adding crit modifier") + local stackingCritMod = self:GetParent():FindModifierByName("modifier_stacking_crit") + if stackingCritMod then + local ability = self:GetAbility() + stackingCritMod:AddCustomCrit(10, 200, "item_critical_havoc", ability) + end +end +modifier_item_critical_havoc = __TS__Decorate( + modifier_item_critical_havoc, + modifier_item_critical_havoc, + {registerModifier(nil)}, + {kind = "class", name = "modifier_item_critical_havoc"} +) +____exports.modifier_item_critical_havoc = modifier_item_critical_havoc +return ____exports diff --git a/scripts/vscripts/items/blackshop/rare/item_damage_dagger.lua b/scripts/vscripts/items/blackshop/rare/item_damage_dagger.lua new file mode 100644 index 0000000..8f94c63 --- /dev/null +++ b/scripts/vscripts/items/blackshop/rare/item_damage_dagger.lua @@ -0,0 +1,124 @@ +local ____lualib = require("lualib_bundle") +local __TS__Class = ____lualib.__TS__Class +local __TS__ClassExtends = ____lualib.__TS__ClassExtends +local __TS__Decorate = ____lualib.__TS__Decorate +local __TS__SourceMapTraceBack = ____lualib.__TS__SourceMapTraceBack +__TS__SourceMapTraceBack(debug.getinfo(1).short_src, {["8"] = 1,["9"] = 1,["10"] = 1,["11"] = 1,["12"] = 1,["13"] = 3,["14"] = 4,["15"] = 3,["16"] = 4,["17"] = 5,["18"] = 6,["21"] = 8,["22"] = 9,["25"] = 11,["26"] = 13,["27"] = 14,["29"] = 18,["31"] = 20,["32"] = 5,["33"] = 4,["34"] = 4,["35"] = 4,["36"] = 3,["39"] = 4,["40"] = 24,["41"] = 25,["42"] = 24,["43"] = 25,["44"] = 26,["45"] = 27,["46"] = 26,["47"] = 30,["48"] = 31,["49"] = 30,["50"] = 34,["51"] = 35,["52"] = 34,["53"] = 37,["54"] = 38,["55"] = 37,["56"] = 41,["57"] = 42,["58"] = 41,["59"] = 45,["60"] = 46,["61"] = 45,["62"] = 49,["63"] = 50,["66"] = 52,["67"] = 53,["70"] = 54,["71"] = 55,["74"] = 57,["75"] = 58,["76"] = 59,["78"] = 49,["79"] = 25,["80"] = 25,["81"] = 25,["82"] = 24,["85"] = 25,["86"] = 64,["87"] = 65,["88"] = 64,["89"] = 65,["90"] = 66,["91"] = 67,["92"] = 66,["93"] = 69,["94"] = 70,["95"] = 69,["96"] = 72,["97"] = 73,["98"] = 72,["99"] = 75,["100"] = 76,["103"] = 78,["104"] = 79,["107"] = 81,["108"] = 82,["109"] = 83,["110"] = 84,["111"] = 85,["113"] = 87,["116"] = 75,["117"] = 65,["118"] = 65,["119"] = 65,["120"] = 64,["123"] = 65}); +local ____exports = {} +local ____dota_ts_adapter = require("lib.dota_ts_adapter") +local BaseItem = ____dota_ts_adapter.BaseItem +local BaseModifier = ____dota_ts_adapter.BaseModifier +local registerAbility = ____dota_ts_adapter.registerAbility +local registerModifier = ____dota_ts_adapter.registerModifier +____exports.item_damage_dagger = __TS__Class() +local item_damage_dagger = ____exports.item_damage_dagger +item_damage_dagger.name = "item_damage_dagger" +__TS__ClassExtends(item_damage_dagger, BaseItem) +function item_damage_dagger.prototype.OnSpellStart(self) + if not IsServer() then + return + end + local caster = self:GetCaster() + if not caster then + return + end + local stats = self:GetSpecialValueFor("bonus_damage") + if caster:HasModifier("modifier_item_damage_dagger") then + caster:FindModifierByName("modifier_item_damage_dagger"):SetStackCount(caster:FindModifierByName("modifier_item_damage_dagger"):GetStackCount() + stats) + else + caster:AddNewModifier(caster, self, "modifier_item_damage_dagger", {}):SetStackCount(stats) + end + UTIL_Remove(self) +end +item_damage_dagger = __TS__Decorate( + item_damage_dagger, + item_damage_dagger, + {registerAbility(nil)}, + {kind = "class", name = "item_damage_dagger"} +) +____exports.item_damage_dagger = item_damage_dagger +____exports.modifier_item_damage_dagger = __TS__Class() +local modifier_item_damage_dagger = ____exports.modifier_item_damage_dagger +modifier_item_damage_dagger.name = "modifier_item_damage_dagger" +__TS__ClassExtends(modifier_item_damage_dagger, BaseModifier) +function modifier_item_damage_dagger.prototype.RemoveOnDeath(self) + return false +end +function modifier_item_damage_dagger.prototype.IsHidden(self) + return false +end +function modifier_item_damage_dagger.prototype.IsPurgable(self) + return false +end +function modifier_item_damage_dagger.prototype.GetTexture(self) + return "blackshop/damage_dagger" +end +function modifier_item_damage_dagger.prototype.DeclareFunctions(self) + return {MODIFIER_PROPERTY_PREATTACK_BONUS_DAMAGE, MODIFIER_EVENT_ON_ATTACK_LANDED} +end +function modifier_item_damage_dagger.prototype.GetModifierPreAttack_BonusDamage(self) + return self:GetStackCount() +end +function modifier_item_damage_dagger.prototype.OnAttackLanded(self, keys) + if not IsServer() then + return + end + local attacker = keys.attacker + if attacker ~= self:GetParent() then + return + end + local parent = self:GetParent() + if not parent then + return + end + if not parent:HasModifier("modifier_item_damage_dagger_timer") then + parent:AddNewModifier(parent, nil, "modifier_item_damage_dagger_timer", {duration = 4}):SetStackCount(self:GetStackCount()) + parent:RemoveModifierByName("modifier_item_damage_dagger") + end +end +modifier_item_damage_dagger = __TS__Decorate( + modifier_item_damage_dagger, + modifier_item_damage_dagger, + {registerModifier(nil)}, + {kind = "class", name = "modifier_item_damage_dagger"} +) +____exports.modifier_item_damage_dagger = modifier_item_damage_dagger +____exports.modifier_item_damage_dagger_timer = __TS__Class() +local modifier_item_damage_dagger_timer = ____exports.modifier_item_damage_dagger_timer +modifier_item_damage_dagger_timer.name = "modifier_item_damage_dagger_timer" +__TS__ClassExtends(modifier_item_damage_dagger_timer, BaseModifier) +function modifier_item_damage_dagger_timer.prototype.RemoveOnDeath(self) + return false +end +function modifier_item_damage_dagger_timer.prototype.IsPurgable(self) + return false +end +function modifier_item_damage_dagger_timer.prototype.GetTexture(self) + return "blackshop/damage_dagger" +end +function modifier_item_damage_dagger_timer.prototype.OnDestroy(self) + if not IsServer() then + return + end + local parent = self:GetParent() + if not parent or not parent:IsAlive() then + return + end + local stacks = self:GetStackCount() + local modifier = parent:AddNewModifier(parent, nil, "modifier_item_damage_dagger", {}) + if modifier ~= nil then + if self:GetParent():HasModifier("modifier_item_damage_dagger") then + modifier:SetStackCount(self:GetParent():FindModifierByName("modifier_item_damage_dagger"):GetStackCount() + stacks) + else + modifier:SetStackCount(stacks) + end + end +end +modifier_item_damage_dagger_timer = __TS__Decorate( + modifier_item_damage_dagger_timer, + modifier_item_damage_dagger_timer, + {registerModifier(nil)}, + {kind = "class", name = "modifier_item_damage_dagger_timer"} +) +____exports.modifier_item_damage_dagger_timer = modifier_item_damage_dagger_timer +return ____exports diff --git a/scripts/vscripts/items/blackshop/rare/item_egg_of_death.lua b/scripts/vscripts/items/blackshop/rare/item_egg_of_death.lua new file mode 100644 index 0000000..ce7014f --- /dev/null +++ b/scripts/vscripts/items/blackshop/rare/item_egg_of_death.lua @@ -0,0 +1,72 @@ +local ____lualib = require("lualib_bundle") +local __TS__Class = ____lualib.__TS__Class +local __TS__ClassExtends = ____lualib.__TS__ClassExtends +local __TS__Decorate = ____lualib.__TS__Decorate +local __TS__SourceMapTraceBack = ____lualib.__TS__SourceMapTraceBack +__TS__SourceMapTraceBack(debug.getinfo(1).short_src, {["8"] = 1,["9"] = 1,["10"] = 1,["11"] = 1,["12"] = 1,["13"] = 3,["14"] = 4,["15"] = 3,["16"] = 4,["17"] = 5,["18"] = 6,["21"] = 8,["22"] = 9,["25"] = 11,["26"] = 13,["27"] = 14,["29"] = 16,["31"] = 18,["32"] = 5,["33"] = 4,["34"] = 4,["35"] = 4,["36"] = 3,["39"] = 4,["40"] = 22,["41"] = 23,["42"] = 22,["43"] = 23,["44"] = 24,["45"] = 25,["46"] = 24,["47"] = 28,["48"] = 29,["49"] = 28,["50"] = 32,["51"] = 33,["52"] = 32,["53"] = 36,["54"] = 37,["55"] = 36,["56"] = 40,["57"] = 41,["58"] = 40,["59"] = 44,["60"] = 45,["61"] = 44,["62"] = 48,["63"] = 49,["64"] = 48,["65"] = 23,["66"] = 23,["67"] = 23,["68"] = 22,["71"] = 23}); +local ____exports = {} +local ____dota_ts_adapter = require("lib.dota_ts_adapter") +local BaseItem = ____dota_ts_adapter.BaseItem +local BaseModifier = ____dota_ts_adapter.BaseModifier +local registerAbility = ____dota_ts_adapter.registerAbility +local registerModifier = ____dota_ts_adapter.registerModifier +____exports.item_egg_of_death = __TS__Class() +local item_egg_of_death = ____exports.item_egg_of_death +item_egg_of_death.name = "item_egg_of_death" +__TS__ClassExtends(item_egg_of_death, BaseItem) +function item_egg_of_death.prototype.OnSpellStart(self) + if not IsServer() then + return + end + local caster = self:GetCaster() + if not caster then + return + end + local stats = self:GetSpecialValueFor("bonus_all") + if caster:HasModifier("modifier_item_egg_of_death") then + caster:FindModifierByName("modifier_item_egg_of_death"):SetStackCount(caster:FindModifierByName("modifier_item_egg_of_death"):GetStackCount() + stats) + else + caster:AddNewModifier(caster, self, "modifier_item_egg_of_death", {}):SetStackCount(stats) + end + UTIL_Remove(self) +end +item_egg_of_death = __TS__Decorate( + item_egg_of_death, + item_egg_of_death, + {registerAbility(nil)}, + {kind = "class", name = "item_egg_of_death"} +) +____exports.item_egg_of_death = item_egg_of_death +____exports.modifier_item_egg_of_death = __TS__Class() +local modifier_item_egg_of_death = ____exports.modifier_item_egg_of_death +modifier_item_egg_of_death.name = "modifier_item_egg_of_death" +__TS__ClassExtends(modifier_item_egg_of_death, BaseModifier) +function modifier_item_egg_of_death.prototype.RemoveOnDeath(self) + return false +end +function modifier_item_egg_of_death.prototype.IsHidden(self) + return true +end +function modifier_item_egg_of_death.prototype.IsPurgable(self) + return false +end +function modifier_item_egg_of_death.prototype.DeclareFunctions(self) + return {MODIFIER_PROPERTY_STATS_AGILITY_BONUS, MODIFIER_PROPERTY_STATS_INTELLECT_BONUS, MODIFIER_PROPERTY_STATS_STRENGTH_BONUS} +end +function modifier_item_egg_of_death.prototype.GetModifierBonusStats_Agility(self) + return self:GetStackCount() +end +function modifier_item_egg_of_death.prototype.GetModifierBonusStats_Intellect(self) + return self:GetStackCount() +end +function modifier_item_egg_of_death.prototype.GetModifierBonusStats_Strength(self) + return self:GetStackCount() +end +modifier_item_egg_of_death = __TS__Decorate( + modifier_item_egg_of_death, + modifier_item_egg_of_death, + {registerModifier(nil)}, + {kind = "class", name = "modifier_item_egg_of_death"} +) +____exports.modifier_item_egg_of_death = modifier_item_egg_of_death +return ____exports diff --git a/scripts/vscripts/items/blackshop/rare/item_silver_eye.lua b/scripts/vscripts/items/blackshop/rare/item_silver_eye.lua new file mode 100644 index 0000000..a42565c --- /dev/null +++ b/scripts/vscripts/items/blackshop/rare/item_silver_eye.lua @@ -0,0 +1,78 @@ +local ____lualib = require("lualib_bundle") +local __TS__Class = ____lualib.__TS__Class +local __TS__ClassExtends = ____lualib.__TS__ClassExtends +local __TS__Decorate = ____lualib.__TS__Decorate +local __TS__SourceMapTraceBack = ____lualib.__TS__SourceMapTraceBack +__TS__SourceMapTraceBack(debug.getinfo(1).short_src, {["8"] = 1,["9"] = 1,["10"] = 1,["11"] = 1,["12"] = 1,["13"] = 3,["14"] = 4,["15"] = 3,["16"] = 4,["17"] = 5,["18"] = 6,["21"] = 8,["22"] = 9,["25"] = 11,["26"] = 13,["27"] = 14,["29"] = 16,["31"] = 18,["32"] = 5,["33"] = 4,["34"] = 4,["35"] = 4,["36"] = 3,["39"] = 4,["40"] = 22,["41"] = 23,["42"] = 22,["43"] = 23,["44"] = 24,["45"] = 25,["46"] = 24,["47"] = 28,["48"] = 29,["49"] = 28,["50"] = 32,["51"] = 33,["52"] = 32,["53"] = 36,["54"] = 37,["55"] = 36,["56"] = 45,["57"] = 46,["58"] = 45,["59"] = 48,["60"] = 49,["61"] = 48,["62"] = 51,["63"] = 52,["64"] = 51,["65"] = 54,["66"] = 55,["67"] = 56,["69"] = 58,["70"] = 54,["71"] = 23,["72"] = 23,["73"] = 23,["74"] = 22,["77"] = 23}); +local ____exports = {} +local ____dota_ts_adapter = require("lib.dota_ts_adapter") +local BaseItem = ____dota_ts_adapter.BaseItem +local BaseModifier = ____dota_ts_adapter.BaseModifier +local registerAbility = ____dota_ts_adapter.registerAbility +local registerModifier = ____dota_ts_adapter.registerModifier +____exports.item_silver_eye = __TS__Class() +local item_silver_eye = ____exports.item_silver_eye +item_silver_eye.name = "item_silver_eye" +__TS__ClassExtends(item_silver_eye, BaseItem) +function item_silver_eye.prototype.OnSpellStart(self) + if not IsServer() then + return + end + local caster = self:GetCaster() + if not caster then + return + end + local vision = self:GetSpecialValueFor("bonus_vision") + if caster:HasModifier("modifier_item_silver_eye") then + caster:FindModifierByName("modifier_item_silver_eye"):SetStackCount(caster:FindModifierByName("modifier_item_silver_eye"):GetStackCount() + vision) + else + caster:AddNewModifier(caster, self, "modifier_item_silver_eye", {}):SetStackCount(vision) + end + UTIL_Remove(self) +end +item_silver_eye = __TS__Decorate( + item_silver_eye, + item_silver_eye, + {registerAbility(nil)}, + {kind = "class", name = "item_silver_eye"} +) +____exports.item_silver_eye = item_silver_eye +____exports.modifier_item_silver_eye = __TS__Class() +local modifier_item_silver_eye = ____exports.modifier_item_silver_eye +modifier_item_silver_eye.name = "modifier_item_silver_eye" +__TS__ClassExtends(modifier_item_silver_eye, BaseModifier) +function modifier_item_silver_eye.prototype.RemoveOnDeath(self) + return false +end +function modifier_item_silver_eye.prototype.IsHidden(self) + return true +end +function modifier_item_silver_eye.prototype.IsPurgable(self) + return false +end +function modifier_item_silver_eye.prototype.DeclareFunctions(self) + return {MODIFIER_PROPERTY_BONUS_NIGHT_VISION, MODIFIER_PROPERTY_BONUS_DAY_VISION, MODIFIER_PROPERTY_CAST_RANGE_BONUS_STACKING, MODIFIER_PROPERTY_ATTACK_RANGE_BONUS} +end +function modifier_item_silver_eye.prototype.GetBonusNightVision(self) + return self:GetStackCount() +end +function modifier_item_silver_eye.prototype.GetBonusDayVision(self) + return self:GetStackCount() +end +function modifier_item_silver_eye.prototype.GetModifierCastRangeBonusStacking(self) + return self:GetStackCount() +end +function modifier_item_silver_eye.prototype.GetModifierAttackRangeBonus(self) + if self:GetParent():IsRangedAttacker() then + return self:GetStackCount() + end + return 0 +end +modifier_item_silver_eye = __TS__Decorate( + modifier_item_silver_eye, + modifier_item_silver_eye, + {registerModifier(nil)}, + {kind = "class", name = "modifier_item_silver_eye"} +) +____exports.modifier_item_silver_eye = modifier_item_silver_eye +return ____exports diff --git a/scripts/vscripts/items/default_items/item_armlet_of_eternal_hunger.lua b/scripts/vscripts/items/default_items/item_armlet_of_eternal_hunger.lua new file mode 100644 index 0000000..4d1691c --- /dev/null +++ b/scripts/vscripts/items/default_items/item_armlet_of_eternal_hunger.lua @@ -0,0 +1,180 @@ +local ____lualib = require("lualib_bundle") +local __TS__Class = ____lualib.__TS__Class +local __TS__ClassExtends = ____lualib.__TS__ClassExtends +local __TS__Decorate = ____lualib.__TS__Decorate +local ____exports = {} +local ____dota_ts_adapter = require("lib.dota_ts_adapter") +local BaseAbility = ____dota_ts_adapter.BaseAbility +local BaseModifier = ____dota_ts_adapter.BaseModifier +local registerAbility = ____dota_ts_adapter.registerAbility +local registerModifier = ____dota_ts_adapter.registerModifier +____exports.item_armlet_of_eternal_hunger = __TS__Class() +local item_armlet_of_eternal_hunger = ____exports.item_armlet_of_eternal_hunger +item_armlet_of_eternal_hunger.name = "item_armlet_of_eternal_hunger" +item_armlet_of_eternal_hunger.____file_path = "scripts/vscripts/items/default_items/item_armlet_of_eternal_hunger.lua" +__TS__ClassExtends(item_armlet_of_eternal_hunger, BaseAbility) +function item_armlet_of_eternal_hunger.prototype.GetIntrinsicModifierName(self) + return "modifier_item_armlet_of_eternal_hunger" +end +function item_armlet_of_eternal_hunger.prototype.GetAbilityTextureName(self) + local caster = self:GetCaster() + if caster and caster:HasModifier("modifier_item_armlet_of_eternal_hunger_buff") then + return "default_items/armlet_of_eternal_hunger/armlet2" + else + return "default_items/armlet_of_eternal_hunger/armlet2_off" + end +end +function item_armlet_of_eternal_hunger.prototype.OnToggle(self) + local caster = self:GetCaster() + local toggle = self:GetToggleState() + if not IsServer() then + return + end + if toggle then + self:EndCooldown() + caster:AddNewModifier(caster, self, "modifier_item_armlet_of_eternal_hunger_buff", {}) + else + local mod = caster:FindModifierByName("modifier_item_armlet_of_eternal_hunger_buff") + if mod then + mod:Destroy() + self:UseResources(false, false, false, true) + end + end +end +item_armlet_of_eternal_hunger = __TS__Decorate( + item_armlet_of_eternal_hunger, + item_armlet_of_eternal_hunger, + {registerAbility(nil)}, + {kind = "class", name = "item_armlet_of_eternal_hunger"} +) +____exports.item_armlet_of_eternal_hunger = item_armlet_of_eternal_hunger +____exports.modifier_item_armlet_of_eternal_hunger = __TS__Class() +local modifier_item_armlet_of_eternal_hunger = ____exports.modifier_item_armlet_of_eternal_hunger +modifier_item_armlet_of_eternal_hunger.name = "modifier_item_armlet_of_eternal_hunger" +modifier_item_armlet_of_eternal_hunger.____file_path = "scripts/vscripts/items/default_items/item_armlet_of_eternal_hunger.lua" +__TS__ClassExtends(modifier_item_armlet_of_eternal_hunger, BaseModifier) +function modifier_item_armlet_of_eternal_hunger.prototype.IsHidden(self) + return true +end +function modifier_item_armlet_of_eternal_hunger.prototype.GetAttributes(self) + return MODIFIER_ATTRIBUTE_PERMANENT +end +function modifier_item_armlet_of_eternal_hunger.prototype.IsPurgable(self) + return false +end +function modifier_item_armlet_of_eternal_hunger.prototype.OnCreated(self, params) + addPhysicalVampirism( + nil, + self:GetParent(), + self:GetAbility():GetSpecialValueFor("lifesteal") + ) +end +function modifier_item_armlet_of_eternal_hunger.prototype.OnDestroy(self) + if IsClient() then + return + end + reducePhysicalVampirism( + nil, + self:GetParent(), + self:GetAbility():GetSpecialValueFor("lifesteal") + ) + local mod = self:GetParent():FindModifierByName("modifier_item_armlet_of_eternal_hunger_buff") + if mod then + mod:Destroy() + end +end +function modifier_item_armlet_of_eternal_hunger.prototype.DeclareFunctions(self) + return {MODIFIER_PROPERTY_PREATTACK_BONUS_DAMAGE, MODIFIER_PROPERTY_ATTACKSPEED_BONUS_CONSTANT, MODIFIER_PROPERTY_PHYSICAL_ARMOR_BONUS, MODIFIER_PROPERTY_HEALTH_REGEN_CONSTANT} +end +function modifier_item_armlet_of_eternal_hunger.prototype.GetModifierPhysicalArmorBonus(self, event) + return self:GetAbility():GetSpecialValueFor("bonus_armor") +end +function modifier_item_armlet_of_eternal_hunger.prototype.GetModifierPreAttack_BonusDamage(self) + return self:GetAbility():GetSpecialValueFor("bonus_damage") +end +function modifier_item_armlet_of_eternal_hunger.prototype.GetModifierAttackSpeedBonus_Constant(self) + return self:GetAbility():GetSpecialValueFor("bonus_attack_speed") +end +function modifier_item_armlet_of_eternal_hunger.prototype.GetModifierConstantHealthRegen(self) + return self:GetAbility():GetSpecialValueFor("bonus_hp_regen") +end +modifier_item_armlet_of_eternal_hunger = __TS__Decorate( + modifier_item_armlet_of_eternal_hunger, + modifier_item_armlet_of_eternal_hunger, + {registerModifier(nil)}, + {kind = "class", name = "modifier_item_armlet_of_eternal_hunger"} +) +____exports.modifier_item_armlet_of_eternal_hunger = modifier_item_armlet_of_eternal_hunger +____exports.modifier_item_armlet_of_eternal_hunger_buff = __TS__Class() +local modifier_item_armlet_of_eternal_hunger_buff = ____exports.modifier_item_armlet_of_eternal_hunger_buff +modifier_item_armlet_of_eternal_hunger_buff.name = "modifier_item_armlet_of_eternal_hunger_buff" +modifier_item_armlet_of_eternal_hunger_buff.____file_path = "scripts/vscripts/items/default_items/item_armlet_of_eternal_hunger.lua" +__TS__ClassExtends(modifier_item_armlet_of_eternal_hunger_buff, BaseModifier) +function modifier_item_armlet_of_eternal_hunger_buff.prototype.IsPurgable(self) + return false +end +function modifier_item_armlet_of_eternal_hunger_buff.prototype.OnCreated(self) + if not IsServer() then + return + end + addPhysicalVampirism( + nil, + self:GetParent(), + self:GetAbility():GetSpecialValueFor("lifesteal_active") + ) + self:StartIntervalThink(0.25) +end +function modifier_item_armlet_of_eternal_hunger_buff.prototype.OnDestroy(self) + if IsClient() then + return + end + reducePhysicalVampirism( + nil, + self:GetParent(), + self:GetAbility():GetSpecialValueFor("lifesteal_active") + ) +end +function modifier_item_armlet_of_eternal_hunger_buff.prototype.OnIntervalThink(self) + if not IsServer() then + return + end + self:GetParent():SetHealth(math.max( + self:GetParent():GetHealth() - self:GetAbility():GetSpecialValueFor("health_per_sec") * 0.25, + 1 + )) +end +function modifier_item_armlet_of_eternal_hunger_buff.prototype.DeclareFunctions(self) + return {MODIFIER_PROPERTY_PREATTACK_BONUS_DAMAGE, MODIFIER_PROPERTY_MOVESPEED_BONUS_CONSTANT, MODIFIER_PROPERTY_ATTACKSPEED_BONUS_CONSTANT, MODIFIER_PROPERTY_STATS_STRENGTH_BONUS} +end +function modifier_item_armlet_of_eternal_hunger_buff.prototype.CheckState(self) + return {[MODIFIER_STATE_SILENCED] = true} +end +function modifier_item_armlet_of_eternal_hunger_buff.prototype.GetModifierPreAttack_BonusDamage(self) + return self:GetAbility():GetSpecialValueFor("bonus_damage_active") +end +function modifier_item_armlet_of_eternal_hunger_buff.prototype.GetModifierAttackSpeedBonus_Constant(self) + return self:GetAbility():GetSpecialValueFor("bonus_attack_speed_active") +end +function modifier_item_armlet_of_eternal_hunger_buff.prototype.GetModifierMoveSpeedBonus_Constant(self) + return self:GetAbility():GetSpecialValueFor("movespeed_active") +end +function modifier_item_armlet_of_eternal_hunger_buff.prototype.GetModifierBonusStats_Strength(self) + return self:GetAbility():GetSpecialValueFor("bonus_str_active") +end +function modifier_item_armlet_of_eternal_hunger_buff.prototype.GetAbilityTextureName(self) + return "default_items/armlet_of_eternal_hunger/armlet2" +end +function modifier_item_armlet_of_eternal_hunger_buff.prototype.GetEffectName(self) + return "particles/units/heroes/hero_bloodseeker/bloodseeker_bloodrage.vpcf" +end +function modifier_item_armlet_of_eternal_hunger_buff.prototype.GetEffectAttachType(self) + return PATTACH_ABSORIGIN_FOLLOW +end +modifier_item_armlet_of_eternal_hunger_buff = __TS__Decorate( + modifier_item_armlet_of_eternal_hunger_buff, + modifier_item_armlet_of_eternal_hunger_buff, + {registerModifier(nil)}, + {kind = "class", name = "modifier_item_armlet_of_eternal_hunger_buff"} +) +____exports.modifier_item_armlet_of_eternal_hunger_buff = modifier_item_armlet_of_eternal_hunger_buff +return ____exports diff --git a/scripts/vscripts/items/default_items/item_bag_of_gold.lua b/scripts/vscripts/items/default_items/item_bag_of_gold.lua new file mode 100644 index 0000000..6a94984 --- /dev/null +++ b/scripts/vscripts/items/default_items/item_bag_of_gold.lua @@ -0,0 +1,55 @@ +local ____lualib = require("lualib_bundle") +local __TS__Class = ____lualib.__TS__Class +local __TS__ClassExtends = ____lualib.__TS__ClassExtends +local __TS__Decorate = ____lualib.__TS__Decorate +local ____exports = {} +local ____dota_ts_adapter = require("lib.dota_ts_adapter") +local BaseItem = ____dota_ts_adapter.BaseItem +local registerAbility = ____dota_ts_adapter.registerAbility +____exports.item_bag_of_gold = __TS__Class() +local item_bag_of_gold = ____exports.item_bag_of_gold +item_bag_of_gold.name = "item_bag_of_gold" +item_bag_of_gold.____file_path = "scripts/vscripts/items/default_items/item_bag_of_gold.lua" +__TS__ClassExtends(item_bag_of_gold, BaseItem) +function item_bag_of_gold.prototype.____constructor(self, ...) + BaseItem.prototype.____constructor(self, ...) + self.GOLD_AMOUNT = RandomInt(10, 50) +end +function item_bag_of_gold.prototype.OnSpellStart(self) + if not IsServer() then + return + end + local caster = self:GetCaster() + if not caster then + return + end + local playerID = caster:GetPlayerOwnerID() + if not PlayerResource:IsValidPlayerID(playerID) then + return + end + PlayerResource:ModifyGold(playerID, self.GOLD_AMOUNT, true, DOTA_ModifyGold_SharedGold) + EmitSoundOnClient( + "General.Coins", + PlayerResource:GetPlayer(playerID) + ) + SendOverheadEventMessage( + PlayerResource:GetPlayer(playerID), + OVERHEAD_ALERT_GOLD, + caster, + self.GOLD_AMOUNT, + nil + ) + local container = self:GetContainer() + if container then + container:RemoveSelf() + end + self:RemoveSelf() +end +item_bag_of_gold = __TS__Decorate( + item_bag_of_gold, + item_bag_of_gold, + {registerAbility(nil)}, + {kind = "class", name = "item_bag_of_gold"} +) +____exports.item_bag_of_gold = item_bag_of_gold +return ____exports diff --git a/scripts/vscripts/items/default_items/item_balist_custom.lua b/scripts/vscripts/items/default_items/item_balist_custom.lua new file mode 100644 index 0000000..983cf95 --- /dev/null +++ b/scripts/vscripts/items/default_items/item_balist_custom.lua @@ -0,0 +1,298 @@ +local ____lualib = require("lualib_bundle") +local Map = ____lualib.Map +local __TS__New = ____lualib.__TS__New +local __TS__Class = ____lualib.__TS__Class +local __TS__ClassExtends = ____lualib.__TS__ClassExtends +local __TS__Decorate = ____lualib.__TS__Decorate +local Set = ____lualib.Set +local ____exports = {} +local ____dota_ts_adapter = require("lib.dota_ts_adapter") +local BaseItem = ____dota_ts_adapter.BaseItem +local BaseModifier = ____dota_ts_adapter.BaseModifier +local registerAbility = ____dota_ts_adapter.registerAbility +local registerModifier = ____dota_ts_adapter.registerModifier +local chainHitTargets = __TS__New(Map) +local chainIdCounter = 0 +____exports.item_balist_custom = __TS__Class() +local item_balist_custom = ____exports.item_balist_custom +item_balist_custom.name = "item_balist_custom" +item_balist_custom.____file_path = "scripts/vscripts/items/default_items/item_balist_custom.lua" +__TS__ClassExtends(item_balist_custom, BaseItem) +function item_balist_custom.prototype.GetIntrinsicModifierName(self) + return "modifier_balist_custom" +end +function item_balist_custom.prototype.Precache(self, context) + PrecacheResource("particle", "particles/items_fx/chain_lightning.vpcf", context) +end +item_balist_custom = __TS__Decorate( + item_balist_custom, + item_balist_custom, + {registerAbility(nil)}, + {kind = "class", name = "item_balist_custom"} +) +____exports.item_balist_custom = item_balist_custom +____exports.modifier_balist_custom = __TS__Class() +local modifier_balist_custom = ____exports.modifier_balist_custom +modifier_balist_custom.name = "modifier_balist_custom" +modifier_balist_custom.____file_path = "scripts/vscripts/items/default_items/item_balist_custom.lua" +__TS__ClassExtends(modifier_balist_custom, BaseModifier) +function modifier_balist_custom.prototype.IsHidden(self) + return true +end +function modifier_balist_custom.prototype.IsDebuff(self) + return false +end +function modifier_balist_custom.prototype.IsPurgable(self) + return false +end +function modifier_balist_custom.prototype.GetAttributes(self) + return MODIFIER_ATTRIBUTE_MULTIPLE +end +function modifier_balist_custom.prototype.DeclareFunctions(self) + return {MODIFIER_PROPERTY_PREATTACK_BONUS_DAMAGE, MODIFIER_EVENT_ON_ATTACK_LANDED} +end +function modifier_balist_custom.prototype.GetModifierPreAttack_BonusDamage(self) + return self:GetAbility():GetSpecialValueFor("bonus_damage") +end +function modifier_balist_custom.prototype.OnAttackLanded(self, event) + if event.attacker ~= self:GetParent() then + return + end + if RandomInt(1, 100) > self:GetAbility():GetSpecialValueFor("chance") then + return + end + local ____opt_0 = self:GetAbility() + if (____opt_0 and ____opt_0:IsCooldownReady()) == false then + return + end + local mainTarget = event.target + local attacker = self:GetCaster() + local ability = self:GetAbility() + local boltRadius = ability:GetSpecialValueFor("bolt_radius") + chainIdCounter = chainIdCounter + 1 + local chainId = chainIdCounter + chainHitTargets:set( + chainId, + __TS__New( + Set, + {mainTarget:entindex()} + ) + ) + mainTarget:AddNewModifier( + self:GetParent(), + ability, + "modifier_balist_custom_dps", + {duration = 0.26, initial_stacks = 3, chain_id = chainId, chain_radius = boltRadius} + ) + ability:StartCooldown(ability:GetSpecialValueFor("ability_cooldown")) +end +function modifier_balist_custom.prototype.OnCreated(self) + if not IsServer() then + return + end + local stackingCritMod = self:GetParent():FindModifierByName("modifier_stacking_crit") + local ____addCritMult_5 = addCritMult + local ____temp_4 = self:GetCaster() + local ____opt_2 = self:GetAbility() + ____addCritMult_5( + nil, + ____temp_4, + ____opt_2 and ____opt_2:GetSpecialValueFor("crit_multiplier") or 0 + ) + if stackingCritMod then + local ability = self:GetAbility() + local ____self_11 = stackingCritMod + local ____self_11_AddCustomCrit_12 = ____self_11.AddCustomCrit + local ____opt_6 = self:GetAbility() + local ____temp_10 = ____opt_6 and ____opt_6:GetSpecialValueFor("crit_chance") or 0 + local ____opt_8 = self:GetAbility() + ____self_11_AddCustomCrit_12( + ____self_11, + ____temp_10, + ____opt_8 and ____opt_8:GetSpecialValueFor("crit_mult") or 0, + "item_balist_custom", + ability + ) + end +end +function modifier_balist_custom.prototype.OnDestroy(self) + if IsClient() then + return + end + local parent = self:GetParent() + if not parent or not IsValidEntity(parent) then + return + end + local stackingCritModifier = parent:FindModifierByName("modifier_stacking_crit") + if not stackingCritModifier then + return + end + local ____reduceCritMult_16 = reduceCritMult + local ____temp_15 = self:GetCaster() + local ____opt_13 = self:GetAbility() + ____reduceCritMult_16( + nil, + ____temp_15, + ____opt_13 and ____opt_13:GetSpecialValueFor("crit_multiplier") or 0 + ) + local ability = self:GetAbility() + stackingCritModifier:RemoveCrit("item_balist_custom", ability) +end +modifier_balist_custom = __TS__Decorate( + modifier_balist_custom, + modifier_balist_custom, + {registerModifier(nil)}, + {kind = "class", name = "modifier_balist_custom"} +) +____exports.modifier_balist_custom = modifier_balist_custom +local function hitWithBolt(self, attacker, victim, ability, sourceForParticle) + local particle = ParticleManager:CreateParticle("particles/items_fx/chain_lightning.vpcf", PATTACH_ABSORIGIN_FOLLOW, victim) + if sourceForParticle and IsValidEntity(sourceForParticle) then + ParticleManager:SetParticleControlEnt( + particle, + 0, + sourceForParticle, + PATTACH_POINT_FOLLOW, + "attach_hitloc", + sourceForParticle:GetAbsOrigin(), + true + ) + else + ParticleManager:SetParticleControl( + particle, + 0, + victim:GetAbsOrigin() + ) + end + ParticleManager:SetParticleControlEnt( + particle, + 1, + victim, + PATTACH_POINT_FOLLOW, + "attach_hitloc", + victim:GetAbsOrigin(), + true + ) + ParticleManager:SetParticleControl( + particle, + 2, + Vector(1, 1, 1) + ) + ParticleManager:ReleaseParticleIndex(particle) + if not IsServer() then + return + end + attacker:PerformAttack( + victim, + true, + true, + true, + false, + false, + false, + true + ) + ApplyDamage({ + victim = victim, + attacker = attacker, + damage = ability:GetSpecialValueFor("bolt_damage"), + damage_type = DAMAGE_TYPE_MAGICAL, + damage_flags = DOTA_DAMAGE_FLAG_NONE, + ability = ability + }) + victim:AddNewModifier( + victim, + ability, + "modifier_stunned", + {duration = ability:GetSpecialValueFor("stun")} + ) +end +____exports.modifier_balist_custom_dps = __TS__Class() +local modifier_balist_custom_dps = ____exports.modifier_balist_custom_dps +modifier_balist_custom_dps.name = "modifier_balist_custom_dps" +modifier_balist_custom_dps.____file_path = "scripts/vscripts/items/default_items/item_balist_custom.lua" +__TS__ClassExtends(modifier_balist_custom_dps, BaseModifier) +function modifier_balist_custom_dps.prototype.IsHidden(self) + return false +end +function modifier_balist_custom_dps.prototype.IsDebuff(self) + return true +end +function modifier_balist_custom_dps.prototype.IsPurgable(self) + return true +end +function modifier_balist_custom_dps.prototype.OnCreated(self, params) + local stacks = params and params.initial_stacks or 3 + self:SetStackCount(stacks) + if (params and params.chain_id) ~= nil and (params and params.chain_radius) ~= nil then + self.chainParams = {chain_id = params.chain_id, chain_radius = params.chain_radius} + end + self:StartIntervalThink(0.25) +end +function modifier_balist_custom_dps.prototype.OnIntervalThink(self) + if self:GetStackCount() == 0 then + return + end + local mainTarget = self:GetParent() + local attacker = self:GetCaster() + local ability = self:GetAbility() + local params = self.chainParams + if not attacker or not ability then + return + end + self:SetDuration(0.26, true) + self:SetStackCount(self:GetStackCount() - 1) + hitWithBolt( + nil, + attacker, + mainTarget, + ability, + attacker + ) + if params and IsServer() then + local hitSet = chainHitTargets:get(params.chain_id) + if hitSet then + local nearbyEnemies = FindUnitsInRadius( + attacker:GetTeamNumber(), + mainTarget:GetAbsOrigin(), + nil, + params.chain_radius, + DOTA_UNIT_TARGET_TEAM_ENEMY, + bit.bor( + bit.bor(DOTA_UNIT_TARGET_HERO, DOTA_UNIT_TARGET_BASIC), + DOTA_UNIT_TARGET_BUILDING + ), + DOTA_UNIT_TARGET_FLAG_NONE, + FIND_CLOSEST, + false + ) + for ____, enemy in ipairs(nearbyEnemies) do + if enemy and enemy:IsAlive() and not hitSet:has(enemy:entindex()) then + hitSet:add(enemy:entindex()) + hitWithBolt( + nil, + attacker, + enemy, + ability, + mainTarget + ) + break + end + end + if self:GetStackCount() == 0 then + chainHitTargets:delete(params.chain_id) + end + end + end +end +function modifier_balist_custom_dps.prototype.GetTexture(self) + return "default_items/balist_custom" +end +modifier_balist_custom_dps = __TS__Decorate( + modifier_balist_custom_dps, + modifier_balist_custom_dps, + {registerModifier(nil)}, + {kind = "class", name = "modifier_balist_custom_dps"} +) +____exports.modifier_balist_custom_dps = modifier_balist_custom_dps +return ____exports diff --git a/scripts/vscripts/items/default_items/item_bandage.lua b/scripts/vscripts/items/default_items/item_bandage.lua new file mode 100644 index 0000000..9c10b4f --- /dev/null +++ b/scripts/vscripts/items/default_items/item_bandage.lua @@ -0,0 +1 @@ +--[[ Generated with https://github.com/TypeScriptToLua/TypeScriptToLua ]] diff --git a/scripts/vscripts/items/default_items/item_battle_fury_custom.lua b/scripts/vscripts/items/default_items/item_battle_fury_custom.lua new file mode 100644 index 0000000..2dd88f5 --- /dev/null +++ b/scripts/vscripts/items/default_items/item_battle_fury_custom.lua @@ -0,0 +1,110 @@ +local ____lualib = require("lualib_bundle") +local __TS__Class = ____lualib.__TS__Class +local __TS__ClassExtends = ____lualib.__TS__ClassExtends +local __TS__Decorate = ____lualib.__TS__Decorate +local ____exports = {} +local ____dota_ts_adapter = require("lib.dota_ts_adapter") +local BaseItem = ____dota_ts_adapter.BaseItem +local BaseModifier = ____dota_ts_adapter.BaseModifier +local registerAbility = ____dota_ts_adapter.registerAbility +local registerModifier = ____dota_ts_adapter.registerModifier +____exports.item_battle_fury_custom = __TS__Class() +local item_battle_fury_custom = ____exports.item_battle_fury_custom +item_battle_fury_custom.name = "item_battle_fury_custom" +item_battle_fury_custom.____file_path = "scripts/vscripts/items/default_items/item_battle_fury_custom.lua" +__TS__ClassExtends(item_battle_fury_custom, BaseItem) +function item_battle_fury_custom.prototype.GetIntrinsicModifierName(self) + return "modifier_battle_fury_custom" +end +function item_battle_fury_custom.prototype.GetAOERadius(self) + return self:GetSpecialValueFor("tree") +end +function item_battle_fury_custom.prototype.OnSpellStart(self) + if not IsServer() then + return + end + local caster = self:GetCaster() + local point = self:GetCursorPosition() + local trees = GridNav:GetAllTreesAroundPoint( + point, + self:GetSpecialValueFor("tree"), + true + ) + if trees and #trees > 0 then + self:CutTree(caster, point) + end +end +function item_battle_fury_custom.prototype.CutTree(self, caster, position) + GridNav:DestroyTreesAroundPoint( + position, + self:GetSpecialValueFor("tree"), + true + ) +end +item_battle_fury_custom = __TS__Decorate( + item_battle_fury_custom, + item_battle_fury_custom, + {registerAbility(nil)}, + {kind = "class", name = "item_battle_fury_custom"} +) +____exports.item_battle_fury_custom = item_battle_fury_custom +____exports.modifier_battle_fury_custom = __TS__Class() +local modifier_battle_fury_custom = ____exports.modifier_battle_fury_custom +modifier_battle_fury_custom.name = "modifier_battle_fury_custom" +modifier_battle_fury_custom.____file_path = "scripts/vscripts/items/default_items/item_battle_fury_custom.lua" +__TS__ClassExtends(modifier_battle_fury_custom, BaseModifier) +function modifier_battle_fury_custom.prototype.IsHidden(self) + return true +end +function modifier_battle_fury_custom.prototype.IsDebuff(self) + return false +end +function modifier_battle_fury_custom.prototype.IsPurgable(self) + return false +end +function modifier_battle_fury_custom.prototype.GetAttributes(self) + return MODIFIER_ATTRIBUTE_MULTIPLE +end +function modifier_battle_fury_custom.prototype.DeclareFunctions(self) + return {MODIFIER_PROPERTY_PREATTACK_BONUS_DAMAGE, MODIFIER_EVENT_ON_ATTACK_LANDED} +end +function modifier_battle_fury_custom.prototype.OnAttackLanded(self, event) + if not IsServer() then + return + end + if self:GetParent():IsRangedAttacker() then + return + end + local parent = event.attacker + if parent ~= self:GetParent() then + return + end + local ability = self:GetAbility() + if not ability then + return + end + local target = event.target + local attackDamage = parent:GetAverageTrueAttackDamage(target) + local cleaveDamage = attackDamage * (self:GetAbility():GetSpecialValueFor("cleave_damage") / 100) + DoCleaveAttack( + parent, + target, + ability, + cleaveDamage, + self:GetAbility():GetSpecialValueFor("cleave_starting_width"), + self:GetAbility():GetSpecialValueFor("cleave_ending_width"), + self:GetAbility():GetSpecialValueFor("cleave_distance"), + "particles/items_fx/battlefury_cleave.vpcf" + ) +end +function modifier_battle_fury_custom.prototype.GetModifierPreAttack_BonusDamage(self) + return self:GetAbility():GetSpecialValueFor("bonus_damage") +end +modifier_battle_fury_custom = __TS__Decorate( + modifier_battle_fury_custom, + modifier_battle_fury_custom, + {registerModifier(nil)}, + {kind = "class", name = "modifier_battle_fury_custom"} +) +____exports.modifier_battle_fury_custom = modifier_battle_fury_custom +return ____exports diff --git a/scripts/vscripts/items/default_items/item_bearstbow.lua b/scripts/vscripts/items/default_items/item_bearstbow.lua new file mode 100644 index 0000000..6c5b715 --- /dev/null +++ b/scripts/vscripts/items/default_items/item_bearstbow.lua @@ -0,0 +1,138 @@ +local ____lualib = require("lualib_bundle") +local __TS__Class = ____lualib.__TS__Class +local __TS__ClassExtends = ____lualib.__TS__ClassExtends +local __TS__Decorate = ____lualib.__TS__Decorate +local ____exports = {} +local ____dota_ts_adapter = require("lib.dota_ts_adapter") +local BaseItem = ____dota_ts_adapter.BaseItem +local registerAbility = ____dota_ts_adapter.registerAbility +local registerModifier = ____dota_ts_adapter.registerModifier +local BaseModifier = ____dota_ts_adapter.BaseModifier +____exports.item_bearstbow = __TS__Class() +local item_bearstbow = ____exports.item_bearstbow +item_bearstbow.name = "item_bearstbow" +item_bearstbow.____file_path = "scripts/vscripts/items/default_items/item_bearstbow.lua" +__TS__ClassExtends(item_bearstbow, BaseItem) +function item_bearstbow.prototype.GetIntrinsicModifierName(self) + return "modifier_item_bearstbow" +end +item_bearstbow = __TS__Decorate( + item_bearstbow, + item_bearstbow, + {registerAbility(nil)}, + {kind = "class", name = "item_bearstbow"} +) +____exports.item_bearstbow = item_bearstbow +____exports.modifier_item_bearstbow = __TS__Class() +local modifier_item_bearstbow = ____exports.modifier_item_bearstbow +modifier_item_bearstbow.name = "modifier_item_bearstbow" +modifier_item_bearstbow.____file_path = "scripts/vscripts/items/default_items/item_bearstbow.lua" +__TS__ClassExtends(modifier_item_bearstbow, BaseModifier) +function modifier_item_bearstbow.prototype.IsHidden(self) + return true +end +function modifier_item_bearstbow.prototype.IsDebuff(self) + return false +end +function modifier_item_bearstbow.prototype.IsPurgable(self) + return false +end +function modifier_item_bearstbow.prototype.OnCreated(self) + if not IsServer() then + return + end +end +function modifier_item_bearstbow.prototype.DeclareFunctions(self) + return { + MODIFIER_EVENT_ON_ATTACK, + MODIFIER_PROPERTY_ATTACKSPEED_BONUS_CONSTANT, + MODIFIER_PROPERTY_ATTACK_RANGE_BONUS, + MODIFIER_PROPERTY_PREATTACK_BONUS_DAMAGE, + MODIFIER_PROPERTY_STATS_AGILITY_BONUS, + MODIFIER_PROPERTY_STATS_STRENGTH_BONUS, + MODIFIER_PROPERTY_STATS_INTELLECT_BONUS, + MODIFIER_PROPERTY_HEALTH_BONUS + } +end +function modifier_item_bearstbow.prototype.GetModifierAttackSpeedBonus_Constant(self) + return self:GetAbility():GetSpecialValueFor("attack_speed") +end +function modifier_item_bearstbow.prototype.GetModifierAttackRangeBonus(self) + if not self:GetParent():IsRangedAttacker() then + return 0 + end + return self:GetAbility():GetSpecialValueFor("attack_range") +end +function modifier_item_bearstbow.prototype.GetModifierBonusStats_Strength(self) + return self:GetAbility():GetSpecialValueFor("strength") +end +function modifier_item_bearstbow.prototype.GetModifierBonusStats_Agility(self) + return self:GetAbility():GetSpecialValueFor("agility") +end +function modifier_item_bearstbow.prototype.GetModifierBonusStats_Intellect(self) + return self:GetAbility():GetSpecialValueFor("intellect") +end +function modifier_item_bearstbow.prototype.GetModifierHealthBonus(self) + return self:GetAbility():GetSpecialValueFor("health") +end +function modifier_item_bearstbow.prototype.OnAttack(self, event) + if not IsServer() then + return + end + local attacker = event.attacker + local target = event.target + if not attacker or not target then + return + end + if not attacker:IsRealHero() then + return + end + if attacker ~= self:GetParent() then + return + end + if not attacker:IsRangedAttacker() then + return + end + if event.no_attack_cooldown then + return + end + local radius = self:GetParent():GetAttackRangeBuffer() + self:GetParent():GetBaseAttackRange() + local units = FindUnitsInRadius( + attacker:GetTeamNumber(), + attacker:GetAbsOrigin(), + nil, + radius, + DOTA_UNIT_TARGET_TEAM_ENEMY, + DOTA_UNIT_TARGET_HERO + DOTA_UNIT_TARGET_BASIC + DOTA_UNIT_TARGET_BUILDING, + DOTA_UNIT_TARGET_FLAG_MAGIC_IMMUNE_ENEMIES + DOTA_UNIT_TARGET_FLAG_FOW_VISIBLE, + FIND_CLOSEST, + false + ) + local attackCount = 0 + for ____, unit in ipairs(units) do + if unit and not unit:IsNull() and unit ~= target and unit:IsAlive() then + attacker:PerformAttack( + unit, + true, + true, + true, + false, + true, + false, + false + ) + attackCount = attackCount + 1 + end + if attackCount >= self:GetAbility():GetSpecialValueFor("attack_count") then + break + end + end +end +modifier_item_bearstbow = __TS__Decorate( + modifier_item_bearstbow, + modifier_item_bearstbow, + {registerModifier(nil)}, + {kind = "class", name = "modifier_item_bearstbow"} +) +____exports.modifier_item_bearstbow = modifier_item_bearstbow +return ____exports diff --git a/scripts/vscripts/items/default_items/item_blademail_2.lua b/scripts/vscripts/items/default_items/item_blademail_2.lua new file mode 100644 index 0000000..ead9226 --- /dev/null +++ b/scripts/vscripts/items/default_items/item_blademail_2.lua @@ -0,0 +1,260 @@ +local ____lualib = require("lualib_bundle") +local __TS__Class = ____lualib.__TS__Class +local __TS__ClassExtends = ____lualib.__TS__ClassExtends +local __TS__Decorate = ____lualib.__TS__Decorate +local ____exports = {} +local ____dota_ts_adapter = require("lib.dota_ts_adapter") +local BaseItem = ____dota_ts_adapter.BaseItem +local registerAbility = ____dota_ts_adapter.registerAbility +local registerModifier = ____dota_ts_adapter.registerModifier +local BaseModifier = ____dota_ts_adapter.BaseModifier +local PARTICLE_BLADEMAIL = "particles/econ/items/spectre/spectre_arcana/spectre_arcana_blademail.vpcf" +____exports.item_blademail_2 = __TS__Class() +local item_blademail_2 = ____exports.item_blademail_2 +item_blademail_2.name = "item_blademail_2" +item_blademail_2.____file_path = "scripts/vscripts/items/default_items/item_blademail_2.lua" +__TS__ClassExtends(item_blademail_2, BaseItem) +function item_blademail_2.prototype.Precache(self, context) + PrecacheResource("particle", PARTICLE_BLADEMAIL, context) +end +function item_blademail_2.prototype.GetIntrinsicModifierName(self) + return "modifier_item_blademail_2" +end +function item_blademail_2.prototype.GetAOERadius(self) + return self:GetSpecialValueFor("radius") +end +function item_blademail_2.prototype.OnSpellStart(self) + if not IsServer() then + return + end + local caster = self:GetCaster() + if not caster then + return + end + local duration = self:GetSpecialValueFor("active_duration") + caster:AddNewModifier(caster, self, ____exports.modifier_item_blademail_2_active.name, {duration = duration}) + EmitSoundOn("DOTA_Item.BladeMail.Activate", caster) +end +item_blademail_2 = __TS__Decorate( + item_blademail_2, + item_blademail_2, + {registerAbility(nil)}, + {kind = "class", name = "item_blademail_2"} +) +____exports.item_blademail_2 = item_blademail_2 +--- Носитель: отражение части входящего урона; аура на союзников и врагов в радиусе. +____exports.modifier_item_blademail_2 = __TS__Class() +local modifier_item_blademail_2 = ____exports.modifier_item_blademail_2 +modifier_item_blademail_2.name = "modifier_item_blademail_2" +modifier_item_blademail_2.____file_path = "scripts/vscripts/items/default_items/item_blademail_2.lua" +__TS__ClassExtends(modifier_item_blademail_2, BaseModifier) +function modifier_item_blademail_2.prototype.RemoveOnDeath(self) + return false +end +function modifier_item_blademail_2.prototype.IsHidden(self) + return true +end +function modifier_item_blademail_2.prototype.IsPurgable(self) + return false +end +function modifier_item_blademail_2.prototype.IsAura(self) + return true +end +function modifier_item_blademail_2.prototype.GetModifierAura(self) + return "modifier_item_blademail_2_aura" +end +function modifier_item_blademail_2.prototype.GetAuraRadius(self) + return self:GetAbility():GetSpecialValueFor("radius") +end +function modifier_item_blademail_2.prototype.GetAuraDuration(self) + return 0.5 +end +function modifier_item_blademail_2.prototype.GetAuraSearchTeam(self) + return DOTA_UNIT_TARGET_TEAM_BOTH +end +function modifier_item_blademail_2.prototype.GetAuraSearchType(self) + return DOTA_UNIT_TARGET_HERO + DOTA_UNIT_TARGET_BASIC +end +function modifier_item_blademail_2.prototype.GetAuraSearchFlags(self) + return DOTA_UNIT_TARGET_FLAG_NONE +end +function modifier_item_blademail_2.prototype.DeclareFunctions(self) + return {MODIFIER_EVENT_ON_TAKEDAMAGE, MODIFIER_PROPERTY_PHYSICAL_ARMOR_BONUS, MODIFIER_PROPERTY_ATTACKSPEED_BONUS_CONSTANT} +end +function modifier_item_blademail_2.prototype.GetModifierPhysicalArmorBonus(self) + return self:GetAbility():GetSpecialValueFor("bonus_armor") +end +function modifier_item_blademail_2.prototype.GetModifierAttackSpeedBonus_Constant(self) + return self:GetAbility():GetSpecialValueFor("attack_speed") +end +function modifier_item_blademail_2.prototype.OnTakeDamage(self, event) + if not IsServer() then + return + end + local parent = self:GetParent() + local ability = self:GetAbility() + if not ability or event.unit ~= parent then + return + end + if event.damage <= 0 then + return + end + if bit.band(event.damage_flags, DOTA_DAMAGE_FLAG_HPLOSS) == DOTA_DAMAGE_FLAG_HPLOSS then + return + end + if bit.band(event.damage_flags, DOTA_DAMAGE_FLAG_REFLECTION) == DOTA_DAMAGE_FLAG_REFLECTION then + return + end + local attacker = event.attacker + if not attacker or attacker:IsNull() or not attacker:IsAlive() then + return + end + if attacker == parent then + return + end + if attacker:GetTeamNumber() == parent:GetTeamNumber() then + return + end + if attacker:IsBuilding() or attacker:IsOther() then + return + end + local isActive = parent:HasModifier(____exports.modifier_item_blademail_2_active.name) + local reflectPct = ability:GetSpecialValueFor("reflect_pct") * 0.01 + if isActive then + reflectPct = ability:GetSpecialValueFor("active_reflect_pct") * 0.01 + parent:HealWithParams( + event.damage, + ability, + false, + false, + parent, + false + ) + SendOverheadEventMessage( + nil, + OVERHEAD_ALERT_HEAL, + parent, + event.damage, + nil + ) + end + if reflectPct <= 0 then + return + end + local reflectDamage = event.damage * reflectPct + if reflectDamage <= 0 then + return + end + ApplyDamage({ + victim = attacker, + attacker = parent, + damage = reflectDamage, + damage_type = event.damage_type, + ability = ability, + damage_flags = DOTA_DAMAGE_FLAG_REFLECTION + DOTA_DAMAGE_FLAG_NO_SPELL_AMPLIFICATION + }) +end +modifier_item_blademail_2 = __TS__Decorate( + modifier_item_blademail_2, + modifier_item_blademail_2, + {registerModifier(nil)}, + {kind = "class", name = "modifier_item_blademail_2"} +) +____exports.modifier_item_blademail_2 = modifier_item_blademail_2 +--- Актив: ванильный визуал и 100% отражения из KV. +____exports.modifier_item_blademail_2_active = __TS__Class() +local modifier_item_blademail_2_active = ____exports.modifier_item_blademail_2_active +modifier_item_blademail_2_active.name = "modifier_item_blademail_2_active" +modifier_item_blademail_2_active.____file_path = "scripts/vscripts/items/default_items/item_blademail_2.lua" +__TS__ClassExtends(modifier_item_blademail_2_active, BaseModifier) +function modifier_item_blademail_2_active.prototype.RemoveOnDeath(self) + return true +end +function modifier_item_blademail_2_active.prototype.IsHidden(self) + return false +end +function modifier_item_blademail_2_active.prototype.IsDebuff(self) + return false +end +function modifier_item_blademail_2_active.prototype.IsPurgable(self) + return true +end +function modifier_item_blademail_2_active.prototype.GetEffectName(self) + return PARTICLE_BLADEMAIL +end +function modifier_item_blademail_2_active.prototype.GetEffectAttachType(self) + return PATTACH_ABSORIGIN_FOLLOW +end +function modifier_item_blademail_2_active.prototype.DeclareFunctions(self) + return {MODIFIER_PROPERTY_MIN_HEALTH} +end +function modifier_item_blademail_2_active.prototype.GetMinHealth(self) + return 1 +end +function modifier_item_blademail_2_active.prototype.GetTexture(self) + return "../items/default_items/blademail_2" +end +modifier_item_blademail_2_active = __TS__Decorate( + modifier_item_blademail_2_active, + modifier_item_blademail_2_active, + {registerModifier(nil)}, + {kind = "class", name = "modifier_item_blademail_2_active"} +) +____exports.modifier_item_blademail_2_active = modifier_item_blademail_2_active +--- Аура: союзникам +броня и +скорость атаки, врагам столько же со знаком минус. +____exports.modifier_item_blademail_2_aura = __TS__Class() +local modifier_item_blademail_2_aura = ____exports.modifier_item_blademail_2_aura +modifier_item_blademail_2_aura.name = "modifier_item_blademail_2_aura" +modifier_item_blademail_2_aura.____file_path = "scripts/vscripts/items/default_items/item_blademail_2.lua" +__TS__ClassExtends(modifier_item_blademail_2_aura, BaseModifier) +function modifier_item_blademail_2_aura.prototype.RemoveOnDeath(self) + return true +end +function modifier_item_blademail_2_aura.prototype.IsHidden(self) + return false +end +function modifier_item_blademail_2_aura.prototype.IsPurgable(self) + return false +end +function modifier_item_blademail_2_aura.prototype.IsDebuff(self) + local caster = self:GetCaster() + if not caster then + return true + end + return self:GetParent():GetTeamNumber() ~= caster:GetTeamNumber() +end +function modifier_item_blademail_2_aura.prototype.IsBuff(self) + return not self:IsDebuff() +end +function modifier_item_blademail_2_aura.prototype.DeclareFunctions(self) + return {MODIFIER_PROPERTY_PHYSICAL_ARMOR_BONUS, MODIFIER_PROPERTY_ATTACKSPEED_BONUS_CONSTANT} +end +function modifier_item_blademail_2_aura.prototype.GetModifierPhysicalArmorBonus(self) + local ability = self:GetAbility() + local caster = self:GetCaster() + if not ability or not caster then + return 0 + end + local v = ability:GetSpecialValueFor("aura_armor") + return self:GetParent():GetTeamNumber() == caster:GetTeamNumber() and v or -v +end +function modifier_item_blademail_2_aura.prototype.GetModifierAttackSpeedBonus_Constant(self) + local ability = self:GetAbility() + local caster = self:GetCaster() + if not ability or not caster then + return 0 + end + local v = ability:GetSpecialValueFor("aura_attack_speed") + return self:GetParent():GetTeamNumber() == caster:GetTeamNumber() and v or -v +end +function modifier_item_blademail_2_aura.prototype.GetTexture(self) + return "../items/default_items/blademail_2" +end +modifier_item_blademail_2_aura = __TS__Decorate( + modifier_item_blademail_2_aura, + modifier_item_blademail_2_aura, + {registerModifier(nil)}, + {kind = "class", name = "modifier_item_blademail_2_aura"} +) +____exports.modifier_item_blademail_2_aura = modifier_item_blademail_2_aura +return ____exports diff --git a/scripts/vscripts/items/default_items/item_blazing_radiance_custom.lua b/scripts/vscripts/items/default_items/item_blazing_radiance_custom.lua new file mode 100644 index 0000000..e0225c3 --- /dev/null +++ b/scripts/vscripts/items/default_items/item_blazing_radiance_custom.lua @@ -0,0 +1,272 @@ +local ____lualib = require("lualib_bundle") +local __TS__Class = ____lualib.__TS__Class +local __TS__ClassExtends = ____lualib.__TS__ClassExtends +local __TS__Decorate = ____lualib.__TS__Decorate +local ____exports = {} +local ____dota_ts_adapter = require("lib.dota_ts_adapter") +local BaseItem = ____dota_ts_adapter.BaseItem +local BaseModifier = ____dota_ts_adapter.BaseModifier +local registerAbility = ____dota_ts_adapter.registerAbility +local registerModifier = ____dota_ts_adapter.registerModifier +local ____modifier_general_fired = require("abilities.modifiers.modifier_general_fired") +local modifier_general_fired = ____modifier_general_fired.modifier_general_fired +local ____modifier_stats_multiplier = require("modifiers.modifier_stats_multiplier") +local removeStatsMultiplierSource = ____modifier_stats_multiplier.removeStatsMultiplierSource +local setStatsMultiplierSource = ____modifier_stats_multiplier.setStatsMultiplierSource +local BLAZING_RADIANCE_SOURCE_PREFIX = "item_blazing_radiance_custom" +local RADIANCE_PARTICLE = "particles/items2_fx/radiance_owner.vpcf" +local function getOwnerHealthRegenPerSecond(self, unit) + local u = unit + if u.GetHealthRegen ~= nil and type(u.GetHealthRegen) == "function" then + return math.max( + 0, + u:GetHealthRegen() + ) + end + return math.max( + 0, + unit:GetBaseHealthRegen() + ) +end +____exports.item_blazing_radiance_custom = __TS__Class() +local item_blazing_radiance_custom = ____exports.item_blazing_radiance_custom +item_blazing_radiance_custom.name = "item_blazing_radiance_custom" +item_blazing_radiance_custom.____file_path = "scripts/vscripts/items/default_items/item_blazing_radiance_custom.lua" +__TS__ClassExtends(item_blazing_radiance_custom, BaseItem) +function item_blazing_radiance_custom.prototype.Precache(self, context) + PrecacheResource("particle", RADIANCE_PARTICLE, context) +end +function item_blazing_radiance_custom.prototype.GetIntrinsicModifierName(self) + return ____exports.modifier_item_blazing_radiance_custom.name +end +function item_blazing_radiance_custom.prototype.GetAOERadius(self) + return self:GetSpecialValueFor("radius") +end +item_blazing_radiance_custom = __TS__Decorate( + item_blazing_radiance_custom, + item_blazing_radiance_custom, + {registerAbility(nil)}, + {kind = "class", name = "item_blazing_radiance_custom"} +) +____exports.item_blazing_radiance_custom = item_blazing_radiance_custom +____exports.modifier_item_blazing_radiance_custom = __TS__Class() +local modifier_item_blazing_radiance_custom = ____exports.modifier_item_blazing_radiance_custom +modifier_item_blazing_radiance_custom.name = "modifier_item_blazing_radiance_custom" +modifier_item_blazing_radiance_custom.____file_path = "scripts/vscripts/items/default_items/item_blazing_radiance_custom.lua" +__TS__ClassExtends(modifier_item_blazing_radiance_custom, BaseModifier) +function modifier_item_blazing_radiance_custom.prototype.____constructor(self, ...) + BaseModifier.prototype.____constructor(self, ...) + self.statsSourceId = "" +end +function modifier_item_blazing_radiance_custom.prototype.IsHidden(self) + return true +end +function modifier_item_blazing_radiance_custom.prototype.IsPurgable(self) + return false +end +function modifier_item_blazing_radiance_custom.prototype.RemoveOnDeath(self) + return false +end +function modifier_item_blazing_radiance_custom.prototype.GetAttributes(self) + return MODIFIER_ATTRIBUTE_MULTIPLE +end +function modifier_item_blazing_radiance_custom.prototype.IsAura(self) + return true +end +function modifier_item_blazing_radiance_custom.prototype.GetModifierAura(self) + return ____exports.modifier_item_blazing_radiance_custom_enemy_aura.name +end +function modifier_item_blazing_radiance_custom.prototype.GetAuraRadius(self) + return self:GetAbility():GetSpecialValueFor("radius") +end +function modifier_item_blazing_radiance_custom.prototype.GetAuraDuration(self) + return 1 +end +function modifier_item_blazing_radiance_custom.prototype.GetAuraSearchTeam(self) + return DOTA_UNIT_TARGET_TEAM_ENEMY +end +function modifier_item_blazing_radiance_custom.prototype.GetAuraSearchType(self) + return DOTA_UNIT_TARGET_HERO + DOTA_UNIT_TARGET_BASIC +end +function modifier_item_blazing_radiance_custom.prototype.GetAuraSearchFlags(self) + return DOTA_UNIT_TARGET_FLAG_NONE +end +function modifier_item_blazing_radiance_custom.prototype.DeclareFunctions(self) + return { + MODIFIER_PROPERTY_PREATTACK_BONUS_DAMAGE, + MODIFIER_PROPERTY_EVASION_CONSTANT, + MODIFIER_PROPERTY_ATTACKSPEED_BONUS_CONSTANT, + MODIFIER_PROPERTY_MAGICAL_RESISTANCE_BONUS, + MODIFIER_PROPERTY_PHYSICAL_ARMOR_BONUS, + MODIFIER_PROPERTY_HEALTH_REGEN_CONSTANT, + MODIFIER_PROPERTY_MANA_REGEN_CONSTANT, + MODIFIER_PROPERTY_STATS_STRENGTH_BONUS, + MODIFIER_PROPERTY_STATS_AGILITY_BONUS, + MODIFIER_PROPERTY_STATS_INTELLECT_BONUS + } +end +function modifier_item_blazing_radiance_custom.prototype.GetModifierBonusStats_Strength(self) + return self:GetAbility():GetSpecialValueFor("bonus_all_stats") +end +function modifier_item_blazing_radiance_custom.prototype.GetModifierBonusStats_Agility(self) + return self:GetAbility():GetSpecialValueFor("bonus_all_stats") +end +function modifier_item_blazing_radiance_custom.prototype.GetModifierBonusStats_Intellect(self) + return self:GetAbility():GetSpecialValueFor("bonus_all_stats") +end +function modifier_item_blazing_radiance_custom.prototype.GetModifierPreAttack_BonusDamage(self) + return self:GetAbility():GetSpecialValueFor("bonus_damage") +end +function modifier_item_blazing_radiance_custom.prototype.GetModifierEvasion_Constant(self) + return self:GetAbility():GetSpecialValueFor("evasion") +end +function modifier_item_blazing_radiance_custom.prototype.GetModifierAttackSpeedBonus_Constant(self) + return self:GetAbility():GetSpecialValueFor("bonus_attack_speed") +end +function modifier_item_blazing_radiance_custom.prototype.GetModifierMagicalResistanceBonus(self) + return self:GetAbility():GetSpecialValueFor("bonus_magic_resistance") +end +function modifier_item_blazing_radiance_custom.prototype.GetModifierPhysicalArmorBonus(self) + return self:GetAbility():GetSpecialValueFor("bonus_physical_armor") +end +function modifier_item_blazing_radiance_custom.prototype.GetModifierConstantHealthRegen(self) + return self:GetAbility():GetSpecialValueFor("bonus_health_regen") +end +function modifier_item_blazing_radiance_custom.prototype.GetModifierConstantManaRegen(self) + return self:GetAbility():GetSpecialValueFor("bonus_mana_regen") +end +function modifier_item_blazing_radiance_custom.prototype.OnCreated(self) + if not IsServer() then + return + end + local ability = self:GetAbility() + self:StartIntervalThink(ability:GetSpecialValueFor("tick_interval")) + self.particleId = ParticleManager:CreateParticle( + RADIANCE_PARTICLE, + PATTACH_ABSORIGIN_FOLLOW, + self:GetParent() + ) + ParticleManager:SetParticleShouldCheckFoW(self.particleId, false) + local hero = self:GetParent() + if hero and hero:IsRealHero() then + self.statsSourceId = (BLAZING_RADIANCE_SOURCE_PREFIX .. "_") .. tostring(hero:entindex()) + setStatsMultiplierSource( + nil, + hero, + self.statsSourceId, + function() + if not hero or not IsValidEntity(hero) or not hero:IsRealHero() then + return 0 + end + return ability and ability:GetSpecialValueFor("all_stats_pct") or 0 + end, + "all_stats_pct" + ) + end +end +function modifier_item_blazing_radiance_custom.prototype.OnDestroy(self) + if not IsServer() then + return + end + if self.particleId ~= nil then + ParticleManager:DestroyParticle(self.particleId, false) + ParticleManager:ReleaseParticleIndex(self.particleId) + self.particleId = nil + end + if self.statsSourceId ~= "" then + local hero = self:GetParent() + if hero and IsValidEntity(hero) then + removeStatsMultiplierSource(nil, hero, self.statsSourceId) + end + self.statsSourceId = "" + end +end +function modifier_item_blazing_radiance_custom.prototype.OnIntervalThink(self) + if not IsServer() then + return + end + if not self:GetParent():IsAlive() then + return + end + local ____opt_4 = self:GetCaster() + if ____opt_4 and ____opt_4:IsIllusion() then + return + end + local ability = self:GetAbility() + local owner = self:GetParent() + local radius = ability:GetSpecialValueFor("radius") + local tickInterval = ability:GetSpecialValueFor("tick_interval") + local healthRegen = getOwnerHealthRegenPerSecond(nil, owner) + local damage = (ability:GetSpecialValueFor("damage") + owner:GetMaxHealth() * (ability:GetSpecialValueFor("health_damage_pct") / 100) + healthRegen * ability:GetSpecialValueFor("health_regen_damage") + owner:GetMaxMana() * (ability:GetSpecialValueFor("mana_damage_pct") / 100)) * tickInterval + local enemies = FindUnitsInRadius( + owner:GetTeamNumber(), + owner:GetAbsOrigin(), + nil, + radius, + DOTA_UNIT_TARGET_TEAM_ENEMY, + DOTA_UNIT_TARGET_HERO + DOTA_UNIT_TARGET_BASIC, + DOTA_UNIT_TARGET_FLAG_NONE, + FIND_ANY_ORDER, + false + ) + local stacksPerTick = ability:GetSpecialValueFor("fire_stack") + for ____, enemy in ipairs(enemies) do + local burnModifier = enemy:AddNewModifier(owner, ability, modifier_general_fired.name, {}) + if burnModifier then + do + local i = 0 + while i < stacksPerTick do + burnModifier:IncrementStackCount() + i = i + 1 + end + end + end + ApplyDamage({ + victim = enemy, + attacker = owner, + damage = damage, + damage_type = DAMAGE_TYPE_MAGICAL, + ability = ability, + damage_flags = DOTA_DAMAGE_FLAG_NO_SPELL_AMPLIFICATION + }) + end +end +modifier_item_blazing_radiance_custom = __TS__Decorate( + modifier_item_blazing_radiance_custom, + modifier_item_blazing_radiance_custom, + {registerModifier(nil)}, + {kind = "class", name = "modifier_item_blazing_radiance_custom"} +) +____exports.modifier_item_blazing_radiance_custom = modifier_item_blazing_radiance_custom +--- Дебафф ауры: подавление магии врагов (Blazing Shroud). +____exports.modifier_item_blazing_radiance_custom_enemy_aura = __TS__Class() +local modifier_item_blazing_radiance_custom_enemy_aura = ____exports.modifier_item_blazing_radiance_custom_enemy_aura +modifier_item_blazing_radiance_custom_enemy_aura.name = "modifier_item_blazing_radiance_custom_enemy_aura" +modifier_item_blazing_radiance_custom_enemy_aura.____file_path = "scripts/vscripts/items/default_items/item_blazing_radiance_custom.lua" +__TS__ClassExtends(modifier_item_blazing_radiance_custom_enemy_aura, BaseModifier) +function modifier_item_blazing_radiance_custom_enemy_aura.prototype.IsHidden(self) + return true +end +function modifier_item_blazing_radiance_custom_enemy_aura.prototype.IsDebuff(self) + return true +end +function modifier_item_blazing_radiance_custom_enemy_aura.prototype.IsPurgable(self) + return false +end +function modifier_item_blazing_radiance_custom_enemy_aura.prototype.DeclareFunctions(self) + return {MODIFIER_PROPERTY_MAGICAL_RESISTANCE_BONUS, MODIFIER_PROPERTY_SPELL_AMPLIFY_PERCENTAGE} +end +function modifier_item_blazing_radiance_custom_enemy_aura.prototype.GetModifierMagicalResistanceBonus(self) + return -self:GetAbility():GetSpecialValueFor("aura_magic_resistance_reduction") +end +function modifier_item_blazing_radiance_custom_enemy_aura.prototype.GetModifierSpellAmplify_Percentage(self) + return -self:GetAbility():GetSpecialValueFor("aura_spell_amp_reduction") +end +modifier_item_blazing_radiance_custom_enemy_aura = __TS__Decorate( + modifier_item_blazing_radiance_custom_enemy_aura, + modifier_item_blazing_radiance_custom_enemy_aura, + {registerModifier(nil)}, + {kind = "class", name = "modifier_item_blazing_radiance_custom_enemy_aura"} +) +____exports.modifier_item_blazing_radiance_custom_enemy_aura = modifier_item_blazing_radiance_custom_enemy_aura +return ____exports diff --git a/scripts/vscripts/items/default_items/item_bloodstone_magical.lua b/scripts/vscripts/items/default_items/item_bloodstone_magical.lua new file mode 100644 index 0000000..3a4cc47 --- /dev/null +++ b/scripts/vscripts/items/default_items/item_bloodstone_magical.lua @@ -0,0 +1,207 @@ +local ____lualib = require("lualib_bundle") +local __TS__Class = ____lualib.__TS__Class +local __TS__ClassExtends = ____lualib.__TS__ClassExtends +local __TS__Decorate = ____lualib.__TS__Decorate +local ____exports = {} +local ____dota_ts_adapter = require("lib.dota_ts_adapter") +local BaseItem = ____dota_ts_adapter.BaseItem +local registerAbility = ____dota_ts_adapter.registerAbility +local registerModifier = ____dota_ts_adapter.registerModifier +local BaseModifier = ____dota_ts_adapter.BaseModifier +____exports.item_bloodstone_magical = __TS__Class() +local item_bloodstone_magical = ____exports.item_bloodstone_magical +item_bloodstone_magical.name = "item_bloodstone_magical" +item_bloodstone_magical.____file_path = "scripts/vscripts/items/default_items/item_bloodstone_magical.lua" +__TS__ClassExtends(item_bloodstone_magical, BaseItem) +function item_bloodstone_magical.prototype.GetIntrinsicModifierName(self) + return "modifier_item_bloodstone_magical" +end +function item_bloodstone_magical.prototype.OnSpellStart(self) + if not IsServer() then + return + end + self:GetCaster():AddNewModifier( + self:GetCaster(), + self, + "modifier_item_bloodstone_magical_vampirism", + {duration = self:GetSpecialValueFor("duration")} + ) +end +item_bloodstone_magical = __TS__Decorate( + item_bloodstone_magical, + item_bloodstone_magical, + {registerAbility(nil)}, + {kind = "class", name = "item_bloodstone_magical"} +) +____exports.item_bloodstone_magical = item_bloodstone_magical +____exports.modifier_item_bloodstone_magical = __TS__Class() +local modifier_item_bloodstone_magical = ____exports.modifier_item_bloodstone_magical +modifier_item_bloodstone_magical.name = "modifier_item_bloodstone_magical" +modifier_item_bloodstone_magical.____file_path = "scripts/vscripts/items/default_items/item_bloodstone_magical.lua" +__TS__ClassExtends(modifier_item_bloodstone_magical, BaseModifier) +function modifier_item_bloodstone_magical.prototype.IsHidden(self) + return true +end +function modifier_item_bloodstone_magical.prototype.IsDebuff(self) + return false +end +function modifier_item_bloodstone_magical.prototype.IsPurgable(self) + return false +end +function modifier_item_bloodstone_magical.prototype.DeclareFunctions(self) + return { + MODIFIER_PROPERTY_HEALTH_BONUS, + MODIFIER_PROPERTY_MANA_BONUS, + MODIFIER_PROPERTY_HEALTH_REGEN_CONSTANT, + MODIFIER_PROPERTY_MANA_REGEN_CONSTANT, + MODIFIER_PROPERTY_SPELL_AMPLIFY_PERCENTAGE, + MODIFIER_PROPERTY_COOLDOWN_PERCENTAGE + } +end +function modifier_item_bloodstone_magical.prototype.OnCreated(self, params) + local caster = self:GetCaster() + if not caster then + return + end + addMagicalVampirism( + nil, + caster, + self:GetAbility():GetSpecialValueFor("spell_lifesteal") + ) +end +function modifier_item_bloodstone_magical.prototype.OnDestroy(self) + local caster = self:GetCaster() + if not caster then + return + end + reduceMagicalVampirism( + nil, + caster, + self:GetAbility():GetSpecialValueFor("spell_lifesteal") + ) +end +function modifier_item_bloodstone_magical.prototype.GetModifierHealthBonus(self) + local item = self:GetAbility() + if not item then + return 0 + end + local ____opt_0 = self:GetCaster() + if not (____opt_0 and ____opt_0:HasItemInInventory("item_bloodstone_magical")) then + return 0 + end + return item:GetSpecialValueFor("bonus_health") +end +function modifier_item_bloodstone_magical.prototype.GetModifierManaBonus(self) + local item = self:GetAbility() + if not item then + return 0 + end + local ____opt_2 = self:GetCaster() + if not (____opt_2 and ____opt_2:HasItemInInventory("item_bloodstone_magical")) then + return 0 + end + return item:GetSpecialValueFor("bonus_mana") +end +function modifier_item_bloodstone_magical.prototype.GetModifierConstantHealthRegen(self) + local item = self:GetAbility() + if not item then + return 0 + end + local ____opt_4 = self:GetCaster() + if not (____opt_4 and ____opt_4:HasItemInInventory("item_bloodstone_magical")) then + return 0 + end + return item:GetSpecialValueFor("bonus_health_regen") +end +function modifier_item_bloodstone_magical.prototype.GetModifierConstantManaRegen(self) + local item = self:GetAbility() + if not item then + return 0 + end + local ____opt_6 = self:GetCaster() + if not (____opt_6 and ____opt_6:HasItemInInventory("item_bloodstone_magical")) then + return 0 + end + return item:GetSpecialValueFor("bonus_mana_regen") +end +modifier_item_bloodstone_magical = __TS__Decorate( + modifier_item_bloodstone_magical, + modifier_item_bloodstone_magical, + {registerModifier(nil)}, + {kind = "class", name = "modifier_item_bloodstone_magical"} +) +____exports.modifier_item_bloodstone_magical = modifier_item_bloodstone_magical +____exports.modifier_item_bloodstone_magical_vampirism = __TS__Class() +local modifier_item_bloodstone_magical_vampirism = ____exports.modifier_item_bloodstone_magical_vampirism +modifier_item_bloodstone_magical_vampirism.name = "modifier_item_bloodstone_magical_vampirism" +modifier_item_bloodstone_magical_vampirism.____file_path = "scripts/vscripts/items/default_items/item_bloodstone_magical.lua" +__TS__ClassExtends(modifier_item_bloodstone_magical_vampirism, BaseModifier) +function modifier_item_bloodstone_magical_vampirism.prototype.IsHidden(self) + return false +end +function modifier_item_bloodstone_magical_vampirism.prototype.IsDebuff(self) + return false +end +function modifier_item_bloodstone_magical_vampirism.prototype.IsPurgable(self) + return false +end +function modifier_item_bloodstone_magical_vampirism.prototype.GetTexture(self) + return "default_items/bloodstone2" +end +function modifier_item_bloodstone_magical_vampirism.prototype.OnCreated(self, params) + local caster = self:GetCaster() + if not caster then + return + end + if IsClient() then + return + end + local player = caster:GetPlayerOwner() + if not player then + return + end + addMagicalVampirism( + nil, + caster, + self:GetAbility():GetSpecialValueFor("spell_lifesteal_active") + ) + self.particle = ParticleManager:CreateParticleForPlayer("particles/bloodstone_full_screen_effect.vpcf", PATTACH_ABSORIGIN_FOLLOW, caster, player) + EmitSoundOn("bloodstone_magical_sound", caster) +end +function modifier_item_bloodstone_magical_vampirism.prototype.OnRefresh(self, params) + if self.particle then + ParticleManager:DestroyParticle(self.particle, false) + end + self:OnCreated(params) +end +function modifier_item_bloodstone_magical_vampirism.prototype.OnDestroy(self) + if IsClient() then + return + end + local caster = self:GetCaster() + if self.particle then + ParticleManager:DestroyParticle(self.particle, false) + end + reduceMagicalVampirism( + nil, + caster, + self:GetAbility():GetSpecialValueFor("spell_lifesteal_active") + ) +end +function modifier_item_bloodstone_magical_vampirism.prototype.DeclareFunctions(self) + return {MODIFIER_PROPERTY_CASTTIME_PERCENTAGE, MODIFIER_PROPERTY_COOLDOWN_PERCENTAGE} +end +function modifier_item_bloodstone_magical_vampirism.prototype.GetModifierPercentageCasttime(self) + return self:GetAbility():GetSpecialValueFor("casttime_reduction") +end +function modifier_item_bloodstone_magical_vampirism.prototype.GetModifierPercentageCooldown(self) + return self:GetAbility():GetSpecialValueFor("cooldown_reduction_active") +end +modifier_item_bloodstone_magical_vampirism = __TS__Decorate( + modifier_item_bloodstone_magical_vampirism, + modifier_item_bloodstone_magical_vampirism, + {registerModifier(nil)}, + {kind = "class", name = "modifier_item_bloodstone_magical_vampirism"} +) +____exports.modifier_item_bloodstone_magical_vampirism = modifier_item_bloodstone_magical_vampirism +return ____exports diff --git a/scripts/vscripts/items/default_items/item_clean_harbor.lua b/scripts/vscripts/items/default_items/item_clean_harbor.lua new file mode 100644 index 0000000..c6b56b8 --- /dev/null +++ b/scripts/vscripts/items/default_items/item_clean_harbor.lua @@ -0,0 +1,266 @@ +local ____lualib = require("lualib_bundle") +local __TS__Class = ____lualib.__TS__Class +local __TS__ClassExtends = ____lualib.__TS__ClassExtends +local __TS__Decorate = ____lualib.__TS__Decorate +local __TS__StringIncludes = ____lualib.__TS__StringIncludes +local __TS__ArrayForEach = ____lualib.__TS__ArrayForEach +local ____exports = {} +local ____dota_ts_adapter = require("lib.dota_ts_adapter") +local BaseItem = ____dota_ts_adapter.BaseItem +local BaseModifier = ____dota_ts_adapter.BaseModifier +local registerAbility = ____dota_ts_adapter.registerAbility +local registerModifier = ____dota_ts_adapter.registerModifier +local ____clean_harbor_flag_markers = require("quests.clean_harbor_flag_markers") +local hideCleanHarborFlagPlacementMarkers = ____clean_harbor_flag_markers.hideCleanHarborFlagPlacementMarkers +local isNearCleanHarborFlagPlacement = ____clean_harbor_flag_markers.isNearCleanHarborFlagPlacement +local ____SpawnManager = require("SpawnManager") +local SpawnManager = ____SpawnManager.SpawnManager +local ____QuestSystem = require("quests.QuestSystem") +local QuestSystem = ____QuestSystem.QuestSystem +____exports.item_clean_harbor = __TS__Class() +local item_clean_harbor = ____exports.item_clean_harbor +item_clean_harbor.name = "item_clean_harbor" +item_clean_harbor.____file_path = "scripts/vscripts/items/default_items/item_clean_harbor.lua" +__TS__ClassExtends(item_clean_harbor, BaseItem) +function item_clean_harbor.prototype.canPlaceFlagHere(self, caster) + return isNearCleanHarborFlagPlacement( + nil, + caster:GetAbsOrigin() + ) +end +function item_clean_harbor.prototype.CastFilterResult(self) + if IsServer() then + local caster = self:GetCaster() + if not self:canPlaceFlagHere(caster) then + return UF_FAIL_CUSTOM + end + return UF_SUCCESS + end + return UF_SUCCESS +end +function item_clean_harbor.prototype.GetCustomCastError(self) + if IsServer() then + local caster = self:GetCaster() + if not self:canPlaceFlagHere(caster) then + return "#dota_hud_error_not_in_zone" + end + end + return "" +end +function item_clean_harbor.prototype.OnSpellStart(self) + if not IsServer() then + return + end + local caster = self:GetCaster() + if not self:canPlaceFlagHere(caster) then + self:EndCooldown() + return + end + hideCleanHarborFlagPlacementMarkers(nil) + local duration = 60 + CreateModifierThinker( + caster, + self, + ____exports.modifier_clean_harbor_flag.name, + {duration = duration}, + caster:GetAbsOrigin(), + caster:GetTeamNumber(), + false + ) + self:RemoveSelf() +end +item_clean_harbor = __TS__Decorate( + item_clean_harbor, + item_clean_harbor, + {registerAbility(nil)}, + {kind = "class", name = "item_clean_harbor"} +) +____exports.item_clean_harbor = item_clean_harbor +____exports.modifier_clean_harbor_flag = __TS__Class() +local modifier_clean_harbor_flag = ____exports.modifier_clean_harbor_flag +modifier_clean_harbor_flag.name = "modifier_clean_harbor_flag" +modifier_clean_harbor_flag.____file_path = "scripts/vscripts/items/default_items/item_clean_harbor.lua" +__TS__ClassExtends(modifier_clean_harbor_flag, BaseModifier) +function modifier_clean_harbor_flag.prototype.____constructor(self, ...) + BaseModifier.prototype.____constructor(self, ...) + self.lastHeroInRangeTime = 0 + self.lastBuffTime = 0 +end +function modifier_clean_harbor_flag.prototype.IsHidden(self) + return true +end +function modifier_clean_harbor_flag.prototype.IsPurgable(self) + return false +end +function modifier_clean_harbor_flag.prototype.OnCreated(self) + if not IsServer() then + return + end + local parent = self:GetParent() + local pos = parent:GetAbsOrigin() + self.flagEntity = SpawnEntityFromTableSynchronous("prop_dynamic", {model = "models/props_gameplay/roshans_banner.vmdl", targetname = "clean_harbor_flag", origin = pos}) + if self.flagParticleId ~= nil then + ParticleManager:DestroyParticle(self.flagParticleId, false) + ParticleManager:ReleaseParticleIndex(self.flagParticleId) + end + self.flagParticleId = ParticleManager:CreateParticle("particles/bloodbath_circle.vpcf", PATTACH_WORLDORIGIN, nil) + ParticleManager:SetParticleControl(self.flagParticleId, 0, pos) + ParticleManager:SetParticleControl( + self.flagParticleId, + 1, + Vector(350, 0, 0) + ) + self.lastHeroInRangeTime = GameRules:GetGameTime() + self.lastBuffTime = self.lastHeroInRangeTime + self:StartIntervalThink(0.25) +end +function modifier_clean_harbor_flag.prototype.OnIntervalThink(self) + if not IsServer() then + return + end + local parent = self:GetParent() + if not parent or parent:IsNull() then + return + end + local pos = parent:GetAbsOrigin() + local heroes = FindUnitsInRadius( + parent:GetTeamNumber(), + pos, + nil, + 350, + DOTA_UNIT_TARGET_TEAM_FRIENDLY, + DOTA_UNIT_TARGET_HERO, + DOTA_UNIT_TARGET_FLAG_NOT_ILLUSIONS, + FIND_ANY_ORDER, + false + ) + local now = GameRules:GetGameTime() + if #heroes > 0 then + self.lastHeroInRangeTime = now + local targetHero = heroes[1] + local spawnManager = SpawnManager:getInstance() + local skeletonZoneIds = spawnManager:GetZoneIdsByPrefixes({"zone_skeletons_", "zone_skeletons2_"}) + local shouldBuff = now - self.lastBuffTime >= 1 + __TS__ArrayForEach( + skeletonZoneIds, + function(____, zoneId) + local units = spawnManager:GetSpawnedUnits(zoneId) + __TS__ArrayForEach( + units, + function(____, unit) + if unit and IsValidEntity(unit) and unit.IsAlive and unit:IsAlive() then + local creep = unit + creep:MoveToTargetToAttack(targetHero) + if shouldBuff then + local name + do + local function ____catch() + name = "" + end + local ____try = pcall(function() + name = creep:GetUnitName() + end) + if not ____try then + ____catch() + end + end + if __TS__StringIncludes(name, "skeleton") then + local anyCreep = creep + if anyCreep._cleanHarborBaseMin == nil then + anyCreep._cleanHarborBaseMin = creep:GetBaseDamageMin() + anyCreep._cleanHarborBaseMax = creep:GetBaseDamageMax() + anyCreep._cleanHarborBuffStacks = 0 + end + anyCreep._cleanHarborBuffStacks = (anyCreep._cleanHarborBuffStacks or 0) + 1 + local stacks = anyCreep._cleanHarborBuffStacks + local mult = 1 + 0.02 * stacks + local newMin = math.floor(anyCreep._cleanHarborBaseMin * mult) + local newMax = math.floor(anyCreep._cleanHarborBaseMax * mult) + creep:SetBaseDamageMin(newMin) + creep:SetBaseDamageMax(newMax) + end + end + end + end + ) + end + ) + if shouldBuff then + self.lastBuffTime = now + end + else + if now - self.lastHeroInRangeTime >= 1 then + CustomGameEventManager:Send_ServerToAllClients("CreateIngameErrorMessage", {reason = 80, message = "#clean_harbor_no_hero_near_flag"}) + end + if now - self.lastHeroInRangeTime >= 5 then + local questSystem = QuestSystem:getInstance() + questSystem:failQuest("kunkka_quest_clean_harbor") + self:Destroy() + return + end + end + local enemies = FindUnitsInRadius( + DOTA_TEAM_NEUTRALS, + pos, + nil, + 400, + DOTA_UNIT_TARGET_TEAM_ENEMY, + DOTA_UNIT_TARGET_BASIC, + DOTA_UNIT_TARGET_FLAG_NONE, + FIND_ANY_ORDER, + false + ) + if #enemies > 0 then + local questSystem = QuestSystem:getInstance() + questSystem:failQuest("kunkka_quest_clean_harbor") + self:Destroy() + end +end +function modifier_clean_harbor_flag.prototype.OnDestroy(self) + if not IsServer() then + return + end + local parent = self:GetParent() + if not parent or parent:IsNull() then + return + end + if self.flagParticleId ~= nil then + ParticleManager:DestroyParticle(self.flagParticleId, false) + ParticleManager:ReleaseParticleIndex(self.flagParticleId) + self.flagParticleId = nil + end + if self.flagEntity and IsValidEntity(self.flagEntity) then + UTIL_Remove(self.flagEntity) + self.flagEntity = nil + end + if self:GetRemainingTime() <= 0 then + local spawnManager = SpawnManager:getInstance() + spawnManager:RemoveSpawnZonesByType("skeletons") + spawnManager:RemoveSpawnZonesByType("skeletons2") + local skeletonZoneIds = spawnManager:GetZoneIdsByPrefixes({"zone_skeletons_", "zone_skeletons2_"}) + __TS__ArrayForEach( + skeletonZoneIds, + function(____, zoneId) + local units = spawnManager:GetSpawnedUnits(zoneId) + __TS__ArrayForEach( + units, + function(____, unit) + if unit and IsValidEntity(unit) and unit.IsAlive and unit:IsAlive() then + unit:ForceKill(false) + end + end + ) + end + ) + local questSystem = QuestSystem:getInstance() + questSystem:addQuestProgress("kunkka_quest_clean_harbor", "clean_harbor", 1) + end +end +modifier_clean_harbor_flag = __TS__Decorate( + modifier_clean_harbor_flag, + modifier_clean_harbor_flag, + {registerModifier(nil)}, + {kind = "class", name = "modifier_clean_harbor_flag"} +) +____exports.modifier_clean_harbor_flag = modifier_clean_harbor_flag +return ____exports diff --git a/scripts/vscripts/items/default_items/item_crimson_shivas_custom.lua b/scripts/vscripts/items/default_items/item_crimson_shivas_custom.lua new file mode 100644 index 0000000..3b1d48a --- /dev/null +++ b/scripts/vscripts/items/default_items/item_crimson_shivas_custom.lua @@ -0,0 +1,643 @@ +local ____lualib = require("lualib_bundle") +local __TS__Class = ____lualib.__TS__Class +local __TS__ClassExtends = ____lualib.__TS__ClassExtends +local __TS__Decorate = ____lualib.__TS__Decorate +local ____exports = {} +local ____dota_ts_adapter = require("lib.dota_ts_adapter") +local BaseItem = ____dota_ts_adapter.BaseItem +local BaseModifier = ____dota_ts_adapter.BaseModifier +local registerAbility = ____dota_ts_adapter.registerAbility +local registerModifier = ____dota_ts_adapter.registerModifier +local ____modifier_stats_multiplier = require("modifiers.modifier_stats_multiplier") +local removeStatsMultiplierSource = ____modifier_stats_multiplier.removeStatsMultiplierSource +local setStatsMultiplierSource = ____modifier_stats_multiplier.setStatsMultiplierSource +local ____luck = require("utils.luck") +local rollLuckChance = ____luck.rollLuckChance +local ____incoming_damage_reduction_combine = require("utils.incoming_damage_reduction_combine") +local removeIncomingDamageReductionSource = ____incoming_damage_reduction_combine.removeIncomingDamageReductionSource +local setIncomingDamageReductionSource = ____incoming_damage_reduction_combine.setIncomingDamageReductionSource +local CRIMSON_SHIVAS_SOURCE_PREFIX = "item_crimson_shivas_custom" +local CRIMSON_SHIVAS_ALLY_AURA_INCOMING_SOURCE = "modifier_item_crimson_shivas_custom_ally_aura_buff" +local CRIMSON_SHIVAS_GUARD_BUFF_INCOMING_SOURCE = "modifier_item_crimson_shivas_custom_guard_buff" +local SHIVAS_BLAST_PARTICLE = "particles/items2_fx/shivas_guard_active.vpcf" +local SHIVAS_IMPACT_PARTICLE = "particles/items2_fx/shivas_guard_impact.vpcf" +local SHIVAS_SLOW_PARTICLE = "particles/generic_gameplay/generic_slowed_cold.vpcf" +local GUARD_BUFF_PARTICLE = "particles/units/heroes/hero_grimstroke/grimstroke_ink_swell_buff.vpcf" +local MELEE_STRENGTH_BLOCK_PCT = 50 +____exports.item_crimson_shivas_custom = __TS__Class() +local item_crimson_shivas_custom = ____exports.item_crimson_shivas_custom +item_crimson_shivas_custom.name = "item_crimson_shivas_custom" +item_crimson_shivas_custom.____file_path = "scripts/vscripts/items/default_items/item_crimson_shivas_custom.lua" +__TS__ClassExtends(item_crimson_shivas_custom, BaseItem) +function item_crimson_shivas_custom.prototype.Precache(self, context) + PrecacheResource("particle", SHIVAS_BLAST_PARTICLE, context) + PrecacheResource("particle", SHIVAS_IMPACT_PARTICLE, context) + PrecacheResource("particle", SHIVAS_SLOW_PARTICLE, context) + PrecacheResource("particle", GUARD_BUFF_PARTICLE, context) + PrecacheResource("soundfile", "soundevents/game_sounds_items.vsndevts", context) +end +function item_crimson_shivas_custom.prototype.GetIntrinsicModifierName(self) + return ____exports.modifier_item_crimson_shivas_custom.name +end +function item_crimson_shivas_custom.prototype.GetAOERadius(self) + return self:GetSpecialValueFor("guard_radius") +end +function item_crimson_shivas_custom.prototype.GetCastRange(self) + local blastRadius = self:GetSpecialValueFor("blast_radius") + if IsServer() then + return blastRadius + end + local ____opt_0 = self:GetCaster() + local bonus = ____opt_0 and ____opt_0:GetCastRangeBonus() or 0 + return blastRadius - bonus +end +function item_crimson_shivas_custom.prototype.OnSpellStart(self) + if not IsServer() then + return + end + local caster = self:GetCaster() + local blastRadius = self:GetSpecialValueFor("blast_radius") + local blastSpeed = self:GetSpecialValueFor("blast_speed") + local guardRadius = self:GetSpecialValueFor("guard_radius") + local guardDuration = self:GetSpecialValueFor("guard_duration") + caster:EmitSound("DOTA_Item.ShivasGuard.Activate") + caster:AddNewModifier(caster, self, ____exports.modifier_item_crimson_shivas_custom_blast_thinker.name, {duration = blastRadius / blastSpeed}) + local allies = FindUnitsInRadius( + caster:GetTeamNumber(), + caster:GetAbsOrigin(), + nil, + guardRadius, + DOTA_UNIT_TARGET_TEAM_FRIENDLY, + DOTA_UNIT_TARGET_HERO, + DOTA_UNIT_TARGET_FLAG_MAGIC_IMMUNE_ENEMIES, + FIND_CLOSEST, + false + ) + for ____, ally in ipairs(allies) do + ally:AddNewModifier(caster, self, ____exports.modifier_item_crimson_shivas_custom_guard_buff.name, {duration = guardDuration}) + ally:EmitSound("Hero_LegionCommander.Overwhelming.Cast") + end +end +item_crimson_shivas_custom = __TS__Decorate( + item_crimson_shivas_custom, + item_crimson_shivas_custom, + {registerAbility(nil)}, + {kind = "class", name = "item_crimson_shivas_custom"} +) +____exports.item_crimson_shivas_custom = item_crimson_shivas_custom +--- Пассив владельца: статы, блок, аура замедления атаки врагам. +____exports.modifier_item_crimson_shivas_custom = __TS__Class() +local modifier_item_crimson_shivas_custom = ____exports.modifier_item_crimson_shivas_custom +modifier_item_crimson_shivas_custom.name = "modifier_item_crimson_shivas_custom" +modifier_item_crimson_shivas_custom.____file_path = "scripts/vscripts/items/default_items/item_crimson_shivas_custom.lua" +__TS__ClassExtends(modifier_item_crimson_shivas_custom, BaseModifier) +function modifier_item_crimson_shivas_custom.prototype.____constructor(self, ...) + BaseModifier.prototype.____constructor(self, ...) + self.statsSourceId = "" +end +function modifier_item_crimson_shivas_custom.prototype.IsHidden(self) + return true +end +function modifier_item_crimson_shivas_custom.prototype.IsPurgable(self) + return false +end +function modifier_item_crimson_shivas_custom.prototype.GetAttributes(self) + return MODIFIER_ATTRIBUTE_MULTIPLE +end +function modifier_item_crimson_shivas_custom.prototype.DeclareFunctions(self) + return { + MODIFIER_PROPERTY_STATS_STRENGTH_BONUS, + MODIFIER_PROPERTY_STATS_AGILITY_BONUS, + MODIFIER_PROPERTY_STATS_INTELLECT_BONUS, + MODIFIER_PROPERTY_HEALTH_BONUS, + MODIFIER_PROPERTY_MANA_BONUS, + MODIFIER_PROPERTY_HEALTH_REGEN_CONSTANT, + MODIFIER_PROPERTY_MANA_REGEN_CONSTANT, + MODIFIER_PROPERTY_PHYSICAL_ARMOR_BONUS, + MODIFIER_PROPERTY_PHYSICAL_CONSTANT_BLOCK + } +end +function modifier_item_crimson_shivas_custom.prototype.IsAura(self) + return true +end +function modifier_item_crimson_shivas_custom.prototype.GetModifierAura(self) + return ____exports.modifier_item_crimson_shivas_custom_enemy_aura.name +end +function modifier_item_crimson_shivas_custom.prototype.GetAuraRadius(self) + return self:GetAbility():GetSpecialValueFor("aura_radius") +end +function modifier_item_crimson_shivas_custom.prototype.GetAuraDuration(self) + return 1 +end +function modifier_item_crimson_shivas_custom.prototype.GetAuraSearchTeam(self) + return DOTA_UNIT_TARGET_TEAM_ENEMY +end +function modifier_item_crimson_shivas_custom.prototype.GetAuraSearchType(self) + return DOTA_UNIT_TARGET_HERO + DOTA_UNIT_TARGET_BASIC +end +function modifier_item_crimson_shivas_custom.prototype.GetAuraSearchFlags(self) + return DOTA_UNIT_TARGET_FLAG_NONE +end +function modifier_item_crimson_shivas_custom.prototype.GetModifierBonusStats_Strength(self) + local ability = self:GetAbility() + return ability:GetSpecialValueFor("bonus_stats") +end +function modifier_item_crimson_shivas_custom.prototype.GetModifierBonusStats_Agility(self) + local ability = self:GetAbility() + return ability:GetSpecialValueFor("bonus_stats") +end +function modifier_item_crimson_shivas_custom.prototype.GetModifierBonusStats_Intellect(self) + local ability = self:GetAbility() + return ability:GetSpecialValueFor("bonus_stats") +end +function modifier_item_crimson_shivas_custom.prototype.GetModifierHealthBonus(self) + return self:GetAbility():GetSpecialValueFor("bonus_health") +end +function modifier_item_crimson_shivas_custom.prototype.GetModifierManaBonus(self) + return self:GetAbility():GetSpecialValueFor("bonus_mana") +end +function modifier_item_crimson_shivas_custom.prototype.GetModifierConstantHealthRegen(self) + return self:GetAbility():GetSpecialValueFor("bonus_hp_regen") +end +function modifier_item_crimson_shivas_custom.prototype.GetModifierConstantManaRegen(self) + return self:GetAbility():GetSpecialValueFor("bonus_mana_regen") +end +function modifier_item_crimson_shivas_custom.prototype.GetModifierPhysicalArmorBonus(self) + return self:GetAbility():GetSpecialValueFor("bonus_armor") +end +function modifier_item_crimson_shivas_custom.prototype.GetModifierPhysical_ConstantBlock(self) + if not IsServer() then + return 0 + end + local ability = self:GetAbility() + if not ability then + return 0 + end + local chancePct = ability:GetSpecialValueFor("damage_block_chance") + local parent = self:GetParent() + local ____parent_IsRealHero_result_2 + if parent:IsRealHero() then + ____parent_IsRealHero_result_2 = rollLuckChance(nil, parent, chancePct / 100) + else + ____parent_IsRealHero_result_2 = RollPercentage(chancePct) + end + local blocked = ____parent_IsRealHero_result_2 + if not blocked then + return 0 + end + local blockAmount = ability:GetSpecialValueFor("damage_block") + local baseBlock = parent:IsRangedAttacker() and blockAmount * 0.5 or blockAmount + if not parent:IsRealHero() or parent:IsRangedAttacker() then + return baseBlock + end + return baseBlock + parent:GetStrength() * (MELEE_STRENGTH_BLOCK_PCT / 100) +end +function modifier_item_crimson_shivas_custom.prototype.OnCreated(self) + if not IsServer() then + return + end + local ability = self:GetAbility() + if not ability then + return + end + self:GetParent():AddNewModifier( + self:GetParent(), + ability, + ____exports.modifier_item_crimson_shivas_custom_ally_aura.name, + {} + ) + local hero = self:GetParent() + if hero and hero:IsRealHero() then + self.statsSourceId = (CRIMSON_SHIVAS_SOURCE_PREFIX .. "_") .. tostring(hero:entindex()) + setStatsMultiplierSource( + nil, + hero, + self.statsSourceId, + function() + if not hero or not IsValidEntity(hero) or not hero:IsRealHero() then + return 0 + end + return ability:GetSpecialValueFor("all_stats_pct") + end, + "all_stats_pct" + ) + end +end +function modifier_item_crimson_shivas_custom.prototype.OnDestroy(self) + if not IsServer() then + return + end + self:GetParent():RemoveModifierByName(____exports.modifier_item_crimson_shivas_custom_ally_aura.name) + if self.statsSourceId ~= "" then + local hero = self:GetParent() + if hero and IsValidEntity(hero) then + removeStatsMultiplierSource(nil, hero, self.statsSourceId) + end + self.statsSourceId = "" + end +end +modifier_item_crimson_shivas_custom = __TS__Decorate( + modifier_item_crimson_shivas_custom, + modifier_item_crimson_shivas_custom, + {registerModifier(nil)}, + {kind = "class", name = "modifier_item_crimson_shivas_custom"} +) +____exports.modifier_item_crimson_shivas_custom = modifier_item_crimson_shivas_custom +--- Пассивная аура на союзников вокруг владельца. +____exports.modifier_item_crimson_shivas_custom_ally_aura = __TS__Class() +local modifier_item_crimson_shivas_custom_ally_aura = ____exports.modifier_item_crimson_shivas_custom_ally_aura +modifier_item_crimson_shivas_custom_ally_aura.name = "modifier_item_crimson_shivas_custom_ally_aura" +modifier_item_crimson_shivas_custom_ally_aura.____file_path = "scripts/vscripts/items/default_items/item_crimson_shivas_custom.lua" +__TS__ClassExtends(modifier_item_crimson_shivas_custom_ally_aura, BaseModifier) +function modifier_item_crimson_shivas_custom_ally_aura.prototype.IsHidden(self) + return true +end +function modifier_item_crimson_shivas_custom_ally_aura.prototype.IsPurgable(self) + return false +end +function modifier_item_crimson_shivas_custom_ally_aura.prototype.IsAura(self) + return true +end +function modifier_item_crimson_shivas_custom_ally_aura.prototype.GetModifierAura(self) + return ____exports.modifier_item_crimson_shivas_custom_ally_aura_buff.name +end +function modifier_item_crimson_shivas_custom_ally_aura.prototype.GetAuraRadius(self) + return self:GetAbility():GetSpecialValueFor("aura_radius") +end +function modifier_item_crimson_shivas_custom_ally_aura.prototype.GetAuraDuration(self) + return 1 +end +function modifier_item_crimson_shivas_custom_ally_aura.prototype.GetAuraSearchTeam(self) + return DOTA_UNIT_TARGET_TEAM_FRIENDLY +end +function modifier_item_crimson_shivas_custom_ally_aura.prototype.GetAuraSearchType(self) + return DOTA_UNIT_TARGET_HERO + DOTA_UNIT_TARGET_BASIC +end +function modifier_item_crimson_shivas_custom_ally_aura.prototype.GetAuraSearchFlags(self) + return DOTA_UNIT_TARGET_FLAG_NONE +end +modifier_item_crimson_shivas_custom_ally_aura = __TS__Decorate( + modifier_item_crimson_shivas_custom_ally_aura, + modifier_item_crimson_shivas_custom_ally_aura, + {registerModifier(nil)}, + {kind = "class", name = "modifier_item_crimson_shivas_custom_ally_aura"} +) +____exports.modifier_item_crimson_shivas_custom_ally_aura = modifier_item_crimson_shivas_custom_ally_aura +--- Аура Шивы: снижение скорости атаки врагам. +____exports.modifier_item_crimson_shivas_custom_enemy_aura = __TS__Class() +local modifier_item_crimson_shivas_custom_enemy_aura = ____exports.modifier_item_crimson_shivas_custom_enemy_aura +modifier_item_crimson_shivas_custom_enemy_aura.name = "modifier_item_crimson_shivas_custom_enemy_aura" +modifier_item_crimson_shivas_custom_enemy_aura.____file_path = "scripts/vscripts/items/default_items/item_crimson_shivas_custom.lua" +__TS__ClassExtends(modifier_item_crimson_shivas_custom_enemy_aura, BaseModifier) +function modifier_item_crimson_shivas_custom_enemy_aura.prototype.IsDebuff(self) + return true +end +function modifier_item_crimson_shivas_custom_enemy_aura.prototype.IsHidden(self) + return false +end +function modifier_item_crimson_shivas_custom_enemy_aura.prototype.IsPurgable(self) + return false +end +function modifier_item_crimson_shivas_custom_enemy_aura.prototype.DeclareFunctions(self) + return {MODIFIER_PROPERTY_ATTACKSPEED_BONUS_CONSTANT, MODIFIER_PROPERTY_INCOMING_DAMAGE_PERCENTAGE} +end +function modifier_item_crimson_shivas_custom_enemy_aura.prototype.GetModifierAttackSpeedBonus_Constant(self) + return -self:GetAbility():GetSpecialValueFor("aura_as_reduction") +end +function modifier_item_crimson_shivas_custom_enemy_aura.prototype.GetModifierIncomingDamage_Percentage(self) + return self:GetAbility():GetSpecialValueFor("aura_enemy_incoming_amp") +end +function modifier_item_crimson_shivas_custom_enemy_aura.prototype.GetTexture(self) + return "item_shivas_guard" +end +modifier_item_crimson_shivas_custom_enemy_aura = __TS__Decorate( + modifier_item_crimson_shivas_custom_enemy_aura, + modifier_item_crimson_shivas_custom_enemy_aura, + {registerModifier(nil)}, + {kind = "class", name = "modifier_item_crimson_shivas_custom_enemy_aura"} +) +____exports.modifier_item_crimson_shivas_custom_enemy_aura = modifier_item_crimson_shivas_custom_enemy_aura +--- Аура на союзников: снижение входящего урона. +____exports.modifier_item_crimson_shivas_custom_ally_aura_buff = __TS__Class() +local modifier_item_crimson_shivas_custom_ally_aura_buff = ____exports.modifier_item_crimson_shivas_custom_ally_aura_buff +modifier_item_crimson_shivas_custom_ally_aura_buff.name = "modifier_item_crimson_shivas_custom_ally_aura_buff" +modifier_item_crimson_shivas_custom_ally_aura_buff.____file_path = "scripts/vscripts/items/default_items/item_crimson_shivas_custom.lua" +__TS__ClassExtends(modifier_item_crimson_shivas_custom_ally_aura_buff, BaseModifier) +function modifier_item_crimson_shivas_custom_ally_aura_buff.prototype.IsHidden(self) + return false +end +function modifier_item_crimson_shivas_custom_ally_aura_buff.prototype.IsPurgable(self) + return false +end +function modifier_item_crimson_shivas_custom_ally_aura_buff.prototype.OnCreated(self) + if not IsServer() then + return + end + setIncomingDamageReductionSource( + nil, + self:GetParent(), + CRIMSON_SHIVAS_ALLY_AURA_INCOMING_SOURCE, + function() + local ability = self:GetAbility() + if not ability then + return 0 + end + return math.max( + 0, + ability:GetSpecialValueFor("aura_ally_incoming_reduction") + ) + end + ) +end +function modifier_item_crimson_shivas_custom_ally_aura_buff.prototype.OnDestroy(self) + if not IsServer() then + return + end + removeIncomingDamageReductionSource( + nil, + self:GetParent(), + CRIMSON_SHIVAS_ALLY_AURA_INCOMING_SOURCE + ) +end +function modifier_item_crimson_shivas_custom_ally_aura_buff.prototype.GetTexture(self) + return "item_crimson_guard" +end +modifier_item_crimson_shivas_custom_ally_aura_buff = __TS__Decorate( + modifier_item_crimson_shivas_custom_ally_aura_buff, + modifier_item_crimson_shivas_custom_ally_aura_buff, + {registerModifier(nil)}, + {kind = "class", name = "modifier_item_crimson_shivas_custom_ally_aura_buff"} +) +____exports.modifier_item_crimson_shivas_custom_ally_aura_buff = modifier_item_crimson_shivas_custom_ally_aura_buff +--- Расходящаяся волна Шивы. +____exports.modifier_item_crimson_shivas_custom_blast_thinker = __TS__Class() +local modifier_item_crimson_shivas_custom_blast_thinker = ____exports.modifier_item_crimson_shivas_custom_blast_thinker +modifier_item_crimson_shivas_custom_blast_thinker.name = "modifier_item_crimson_shivas_custom_blast_thinker" +modifier_item_crimson_shivas_custom_blast_thinker.____file_path = "scripts/vscripts/items/default_items/item_crimson_shivas_custom.lua" +__TS__ClassExtends(modifier_item_crimson_shivas_custom_blast_thinker, BaseModifier) +function modifier_item_crimson_shivas_custom_blast_thinker.prototype.____constructor(self, ...) + BaseModifier.prototype.____constructor(self, ...) + self.hitted = {} +end +function modifier_item_crimson_shivas_custom_blast_thinker.prototype.IsHidden(self) + return true +end +function modifier_item_crimson_shivas_custom_blast_thinker.prototype.IsPurgable(self) + return false +end +function modifier_item_crimson_shivas_custom_blast_thinker.prototype.GetAttributes(self) + return MODIFIER_ATTRIBUTE_MULTIPLE +end +function modifier_item_crimson_shivas_custom_blast_thinker.prototype.OnCreated(self) + if not IsServer() then + return + end + local ability = self:GetAbility() + self.hitted = {} + self:StartIntervalThink(FrameTime()) + local particle = ParticleManager:CreateParticle( + SHIVAS_BLAST_PARTICLE, + PATTACH_ABSORIGIN_FOLLOW, + self:GetParent() + ) + ParticleManager:SetParticleControl( + particle, + 1, + Vector( + ability:GetSpecialValueFor("blast_radius"), + self:GetDuration() * 1.33, + ability:GetSpecialValueFor("blast_speed") + ) + ) + self:AddParticle( + particle, + false, + false, + 15, + false, + false + ) +end +function modifier_item_crimson_shivas_custom_blast_thinker.prototype.OnIntervalThink(self) + local ability = self:GetAbility() + local caster = self:GetCaster() + if not ability or not caster then + return + end + local radiusIncrease = ability:GetSpecialValueFor("blast_speed") / (1 / FrameTime()) * 100 + self:SetStackCount(self:GetStackCount() + radiusIncrease) + local radius = self:GetStackCount() / 100 + AddFOWViewer( + caster:GetTeamNumber(), + self:GetParent():GetAbsOrigin(), + radius, + FrameTime(), + false + ) + local enemies = FindUnitsInRadius( + caster:GetTeamNumber(), + self:GetParent():GetAbsOrigin(), + nil, + radius, + DOTA_UNIT_TARGET_TEAM_ENEMY, + DOTA_UNIT_TARGET_HERO + DOTA_UNIT_TARGET_BASIC, + DOTA_UNIT_TARGET_FLAG_NONE, + FIND_ANY_ORDER, + false + ) + local blastDamage = ability:GetSpecialValueFor("blast_damage") + local slowDuration = ability:GetSpecialValueFor("slow_duration") + for ____, enemy in ipairs(enemies) do + do + local entIndex = enemy:entindex() + if self.hitted[entIndex] then + goto __continue77 + end + self.hitted[entIndex] = true + local impact = ParticleManager:CreateParticle(SHIVAS_IMPACT_PARTICLE, PATTACH_ABSORIGIN_FOLLOW, enemy) + ParticleManager:SetParticleControl( + impact, + 1, + self:GetParent():GetAbsOrigin() + ) + ParticleManager:ReleaseParticleIndex(impact) + ApplyDamage({ + victim = enemy, + attacker = caster, + damage = blastDamage, + damage_type = DAMAGE_TYPE_MAGICAL, + ability = ability + }) + local duration = slowDuration * (1 - enemy:GetStatusResistance()) + enemy:AddNewModifier(caster, ability, ____exports.modifier_item_crimson_shivas_custom_blast_debuff.name, {duration = duration}) + end + ::__continue77:: + end +end +function modifier_item_crimson_shivas_custom_blast_thinker.prototype.OnDestroy(self) + if not IsServer() then + return + end + local ability = self:GetAbility() + local caster = self:GetCaster() + if ability and caster then + local radius = self:GetStackCount() / 100 + AddFOWViewer( + caster:GetTeamNumber(), + self:GetParent():GetAbsOrigin(), + radius, + ability:GetSpecialValueFor("slow_duration"), + false + ) + end + self.hitted = {} +end +modifier_item_crimson_shivas_custom_blast_thinker = __TS__Decorate( + modifier_item_crimson_shivas_custom_blast_thinker, + modifier_item_crimson_shivas_custom_blast_thinker, + {registerModifier(nil)}, + {kind = "class", name = "modifier_item_crimson_shivas_custom_blast_thinker"} +) +____exports.modifier_item_crimson_shivas_custom_blast_thinker = modifier_item_crimson_shivas_custom_blast_thinker +--- Замедление от волны Шивы. +____exports.modifier_item_crimson_shivas_custom_blast_debuff = __TS__Class() +local modifier_item_crimson_shivas_custom_blast_debuff = ____exports.modifier_item_crimson_shivas_custom_blast_debuff +modifier_item_crimson_shivas_custom_blast_debuff.name = "modifier_item_crimson_shivas_custom_blast_debuff" +modifier_item_crimson_shivas_custom_blast_debuff.____file_path = "scripts/vscripts/items/default_items/item_crimson_shivas_custom.lua" +__TS__ClassExtends(modifier_item_crimson_shivas_custom_blast_debuff, BaseModifier) +function modifier_item_crimson_shivas_custom_blast_debuff.prototype.IsDebuff(self) + return true +end +function modifier_item_crimson_shivas_custom_blast_debuff.prototype.IsHidden(self) + return false +end +function modifier_item_crimson_shivas_custom_blast_debuff.prototype.IsPurgable(self) + return true +end +function modifier_item_crimson_shivas_custom_blast_debuff.prototype.DeclareFunctions(self) + return {MODIFIER_PROPERTY_MOVESPEED_BONUS_PERCENTAGE, MODIFIER_PROPERTY_ATTACKSPEED_BONUS_CONSTANT, MODIFIER_PROPERTY_INCOMING_DAMAGE_PERCENTAGE} +end +function modifier_item_crimson_shivas_custom_blast_debuff.prototype.GetModifierMoveSpeedBonus_Percentage(self) + return self:GetAbility():GetSpecialValueFor("slow_move_pct") +end +function modifier_item_crimson_shivas_custom_blast_debuff.prototype.GetModifierAttackSpeedBonus_Constant(self) + return self:GetAbility():GetSpecialValueFor("active_as_reduction") +end +function modifier_item_crimson_shivas_custom_blast_debuff.prototype.GetModifierIncomingDamage_Percentage(self) + return self:GetAbility():GetSpecialValueFor("aura_enemy_incoming_amp") +end +function modifier_item_crimson_shivas_custom_blast_debuff.prototype.GetEffectName(self) + return SHIVAS_SLOW_PARTICLE +end +function modifier_item_crimson_shivas_custom_blast_debuff.prototype.GetEffectAttachType(self) + return PATTACH_ABSORIGIN_FOLLOW +end +function modifier_item_crimson_shivas_custom_blast_debuff.prototype.GetTexture(self) + return "item_shivas_guard" +end +modifier_item_crimson_shivas_custom_blast_debuff = __TS__Decorate( + modifier_item_crimson_shivas_custom_blast_debuff, + modifier_item_crimson_shivas_custom_blast_debuff, + {registerModifier(nil)}, + {kind = "class", name = "modifier_item_crimson_shivas_custom_blast_debuff"} +) +____exports.modifier_item_crimson_shivas_custom_blast_debuff = modifier_item_crimson_shivas_custom_blast_debuff +--- Актив Crimson Guard: усиление союзников. +____exports.modifier_item_crimson_shivas_custom_guard_buff = __TS__Class() +local modifier_item_crimson_shivas_custom_guard_buff = ____exports.modifier_item_crimson_shivas_custom_guard_buff +modifier_item_crimson_shivas_custom_guard_buff.name = "modifier_item_crimson_shivas_custom_guard_buff" +modifier_item_crimson_shivas_custom_guard_buff.____file_path = "scripts/vscripts/items/default_items/item_crimson_shivas_custom.lua" +__TS__ClassExtends(modifier_item_crimson_shivas_custom_guard_buff, BaseModifier) +function modifier_item_crimson_shivas_custom_guard_buff.prototype.IsHidden(self) + return false +end +function modifier_item_crimson_shivas_custom_guard_buff.prototype.IsPurgable(self) + return true +end +function modifier_item_crimson_shivas_custom_guard_buff.prototype.OnCreated(self) + if not IsServer() then + return + end + local parent = self:GetParent() + self.buffParticle = ParticleManager:CreateParticle(GUARD_BUFF_PARTICLE, PATTACH_ABSORIGIN, parent) + if parent:ScriptLookupAttachment("attach_hitloc") == 0 then + ParticleManager:SetParticleControl( + self.buffParticle, + 1, + parent:GetAbsOrigin():__add(Vector(0, 0, 120)) + ) + else + ParticleManager:SetParticleControlEnt( + self.buffParticle, + 1, + parent, + PATTACH_POINT_FOLLOW, + "attach_hitloc", + Vector(0, 0, 0), + true + ) + end + setIncomingDamageReductionSource( + nil, + parent, + CRIMSON_SHIVAS_GUARD_BUFF_INCOMING_SOURCE, + function() + local ability = self:GetAbility() + if not ability then + return 0 + end + return math.max( + 0, + ability:GetSpecialValueFor("aura_ally_incoming_reduction") + ) + end + ) +end +function modifier_item_crimson_shivas_custom_guard_buff.prototype.OnDestroy(self) + if not IsServer() then + return + end + removeIncomingDamageReductionSource( + nil, + self:GetParent(), + CRIMSON_SHIVAS_GUARD_BUFF_INCOMING_SOURCE + ) + if self.buffParticle ~= nil then + ParticleManager:DestroyParticle(self.buffParticle, false) + ParticleManager:ReleaseParticleIndex(self.buffParticle) + self.buffParticle = nil + end +end +function modifier_item_crimson_shivas_custom_guard_buff.prototype.DeclareFunctions(self) + return {MODIFIER_PROPERTY_PHYSICAL_ARMOR_BONUS, MODIFIER_PROPERTY_ATTACKSPEED_BONUS_CONSTANT, MODIFIER_PROPERTY_MOVESPEED_BONUS_PERCENTAGE, MODIFIER_PROPERTY_PHYSICAL_CONSTANT_BLOCK} +end +function modifier_item_crimson_shivas_custom_guard_buff.prototype.GetModifierPhysicalArmorBonus(self) + return self:GetAbility():GetSpecialValueFor("bonus_armor_active") +end +function modifier_item_crimson_shivas_custom_guard_buff.prototype.GetModifierAttackSpeedBonus_Constant(self) + return self:GetAbility():GetSpecialValueFor("bonus_attack_speed_active") +end +function modifier_item_crimson_shivas_custom_guard_buff.prototype.GetModifierMoveSpeedBonus_Percentage(self) + return self:GetAbility():GetSpecialValueFor("bonus_movespeed_active") +end +function modifier_item_crimson_shivas_custom_guard_buff.prototype.GetModifierPhysical_ConstantBlock(self) + local ability = self:GetAbility() + local caster = self:GetCaster() + if not ability or not caster then + return 0 + end + local baseBlock = ability:GetSpecialValueFor("damage_block_active") + local strBonus = caster:GetStrength() * (ability:GetSpecialValueFor("block_strength_pct") / 100) + return baseBlock + strBonus +end +function modifier_item_crimson_shivas_custom_guard_buff.prototype.GetTexture(self) + return "item_crimson_guard" +end +modifier_item_crimson_shivas_custom_guard_buff = __TS__Decorate( + modifier_item_crimson_shivas_custom_guard_buff, + modifier_item_crimson_shivas_custom_guard_buff, + {registerModifier(nil)}, + {kind = "class", name = "modifier_item_crimson_shivas_custom_guard_buff"} +) +____exports.modifier_item_crimson_shivas_custom_guard_buff = modifier_item_crimson_shivas_custom_guard_buff +return ____exports diff --git a/scripts/vscripts/items/default_items/item_crystalys.lua b/scripts/vscripts/items/default_items/item_crystalys.lua new file mode 100644 index 0000000..0cd0fff --- /dev/null +++ b/scripts/vscripts/items/default_items/item_crystalys.lua @@ -0,0 +1,92 @@ +local ____lualib = require("lualib_bundle") +local __TS__Class = ____lualib.__TS__Class +local __TS__ClassExtends = ____lualib.__TS__ClassExtends +local __TS__Decorate = ____lualib.__TS__Decorate +local ____exports = {} +local ____dota_ts_adapter = require("lib.dota_ts_adapter") +local BaseItem = ____dota_ts_adapter.BaseItem +local BaseModifier = ____dota_ts_adapter.BaseModifier +local registerAbility = ____dota_ts_adapter.registerAbility +local registerModifier = ____dota_ts_adapter.registerModifier +____exports.item_crystalys = __TS__Class() +local item_crystalys = ____exports.item_crystalys +item_crystalys.name = "item_crystalys" +item_crystalys.____file_path = "scripts/vscripts/items/default_items/item_crystalys.lua" +__TS__ClassExtends(item_crystalys, BaseItem) +function item_crystalys.prototype.GetIntrinsicModifierName(self) + return "modifier_crystalys" +end +item_crystalys = __TS__Decorate( + item_crystalys, + item_crystalys, + {registerAbility(nil)}, + {kind = "class", name = "item_crystalys"} +) +____exports.item_crystalys = item_crystalys +____exports.modifier_crystalys = __TS__Class() +local modifier_crystalys = ____exports.modifier_crystalys +modifier_crystalys.name = "modifier_crystalys" +modifier_crystalys.____file_path = "scripts/vscripts/items/default_items/item_crystalys.lua" +__TS__ClassExtends(modifier_crystalys, BaseModifier) +function modifier_crystalys.prototype.IsHidden(self) + return true +end +function modifier_crystalys.prototype.IsDebuff(self) + return false +end +function modifier_crystalys.prototype.IsPurgable(self) + return false +end +function modifier_crystalys.prototype.GetAttributes(self) + return MODIFIER_ATTRIBUTE_MULTIPLE +end +function modifier_crystalys.prototype.DeclareFunctions(self) + return {MODIFIER_PROPERTY_PREATTACK_BONUS_DAMAGE} +end +function modifier_crystalys.prototype.GetModifierPreAttack_BonusDamage(self) + return self:GetAbility():GetSpecialValueFor("bonus_damage") +end +function modifier_crystalys.prototype.OnCreated(self) + if not IsServer() then + return + end + local stackingCritMod = self:GetParent():FindModifierByName("modifier_stacking_crit") + if stackingCritMod then + local ability = self:GetAbility() + local ____self_5 = stackingCritMod + local ____self_5_AddCustomCrit_6 = ____self_5.AddCustomCrit + local ____opt_0 = self:GetAbility() + local ____temp_4 = ____opt_0 and ____opt_0:GetSpecialValueFor("crit_chance") or 0 + local ____opt_2 = self:GetAbility() + ____self_5_AddCustomCrit_6( + ____self_5, + ____temp_4, + ____opt_2 and ____opt_2:GetSpecialValueFor("crit_mult") or 0, + "item_crystalys", + ability + ) + end +end +function modifier_crystalys.prototype.OnDestroy(self) + if IsClient() then + return + end + local parent = self:GetParent() + if not parent or not IsValidEntity(parent) then + return + end + local stackingCritModifier = parent:FindModifierByName("modifier_stacking_crit") + if not stackingCritModifier then + return + end + local ability = self:GetAbility() + stackingCritModifier:RemoveCrit("item_crystalys", ability) +end +modifier_crystalys = __TS__Decorate( + modifier_crystalys, + modifier_crystalys, + {registerModifier(nil)}, + {kind = "class", name = "modifier_crystalys"} +) +____exports.modifier_crystalys = modifier_crystalys +return ____exports diff --git a/scripts/vscripts/items/default_items/item_dark_crystalys.lua b/scripts/vscripts/items/default_items/item_dark_crystalys.lua new file mode 100644 index 0000000..45aa532 --- /dev/null +++ b/scripts/vscripts/items/default_items/item_dark_crystalys.lua @@ -0,0 +1,92 @@ +local ____lualib = require("lualib_bundle") +local __TS__Class = ____lualib.__TS__Class +local __TS__ClassExtends = ____lualib.__TS__ClassExtends +local __TS__Decorate = ____lualib.__TS__Decorate +local ____exports = {} +local ____dota_ts_adapter = require("lib.dota_ts_adapter") +local BaseItem = ____dota_ts_adapter.BaseItem +local BaseModifier = ____dota_ts_adapter.BaseModifier +local registerAbility = ____dota_ts_adapter.registerAbility +local registerModifier = ____dota_ts_adapter.registerModifier +____exports.item_dark_crystalys = __TS__Class() +local item_dark_crystalys = ____exports.item_dark_crystalys +item_dark_crystalys.name = "item_dark_crystalys" +item_dark_crystalys.____file_path = "scripts/vscripts/items/default_items/item_dark_crystalys.lua" +__TS__ClassExtends(item_dark_crystalys, BaseItem) +function item_dark_crystalys.prototype.GetIntrinsicModifierName(self) + return "modifier_dark_crystalys" +end +item_dark_crystalys = __TS__Decorate( + item_dark_crystalys, + item_dark_crystalys, + {registerAbility(nil)}, + {kind = "class", name = "item_dark_crystalys"} +) +____exports.item_dark_crystalys = item_dark_crystalys +____exports.modifier_dark_crystalys = __TS__Class() +local modifier_dark_crystalys = ____exports.modifier_dark_crystalys +modifier_dark_crystalys.name = "modifier_dark_crystalys" +modifier_dark_crystalys.____file_path = "scripts/vscripts/items/default_items/item_dark_crystalys.lua" +__TS__ClassExtends(modifier_dark_crystalys, BaseModifier) +function modifier_dark_crystalys.prototype.IsHidden(self) + return true +end +function modifier_dark_crystalys.prototype.IsDebuff(self) + return false +end +function modifier_dark_crystalys.prototype.IsPurgable(self) + return false +end +function modifier_dark_crystalys.prototype.GetAttributes(self) + return MODIFIER_ATTRIBUTE_MULTIPLE +end +function modifier_dark_crystalys.prototype.DeclareFunctions(self) + return {MODIFIER_PROPERTY_PREATTACK_BONUS_DAMAGE} +end +function modifier_dark_crystalys.prototype.GetModifierPreAttack_BonusDamage(self) + return self:GetAbility():GetSpecialValueFor("bonus_damage") +end +function modifier_dark_crystalys.prototype.OnCreated(self) + if not IsServer() then + return + end + local stackingCritMod = self:GetParent():FindModifierByName("modifier_stacking_crit") + if stackingCritMod then + local ability = self:GetAbility() + local ____self_5 = stackingCritMod + local ____self_5_AddCustomCrit_6 = ____self_5.AddCustomCrit + local ____opt_0 = self:GetAbility() + local ____temp_4 = ____opt_0 and ____opt_0:GetSpecialValueFor("crit_chance") or 0 + local ____opt_2 = self:GetAbility() + ____self_5_AddCustomCrit_6( + ____self_5, + ____temp_4, + ____opt_2 and ____opt_2:GetSpecialValueFor("crit_mult") or 0, + "item_dark_crystalys", + ability + ) + end +end +function modifier_dark_crystalys.prototype.OnDestroy(self) + if IsClient() then + return + end + local parent = self:GetParent() + if not parent or not IsValidEntity(parent) then + return + end + local stackingCritModifier = parent:FindModifierByName("modifier_stacking_crit") + if not stackingCritModifier then + return + end + local ability = self:GetAbility() + stackingCritModifier:RemoveCrit("item_dark_crystalys", ability) +end +modifier_dark_crystalys = __TS__Decorate( + modifier_dark_crystalys, + modifier_dark_crystalys, + {registerModifier(nil)}, + {kind = "class", name = "modifier_dark_crystalys"} +) +____exports.modifier_dark_crystalys = modifier_dark_crystalys +return ____exports diff --git a/scripts/vscripts/items/default_items/item_demon_slayer.lua b/scripts/vscripts/items/default_items/item_demon_slayer.lua new file mode 100644 index 0000000..a7ab12e --- /dev/null +++ b/scripts/vscripts/items/default_items/item_demon_slayer.lua @@ -0,0 +1,299 @@ +local ____lualib = require("lualib_bundle") +local __TS__Class = ____lualib.__TS__Class +local __TS__ClassExtends = ____lualib.__TS__ClassExtends +local __TS__Decorate = ____lualib.__TS__Decorate +local __TS__StringStartsWith = ____lualib.__TS__StringStartsWith +local ____exports = {} +local ____dota_ts_adapter = require("lib.dota_ts_adapter") +local BaseItem = ____dota_ts_adapter.BaseItem +local BaseModifier = ____dota_ts_adapter.BaseModifier +local registerAbility = ____dota_ts_adapter.registerAbility +local registerModifier = ____dota_ts_adapter.registerModifier +local ____incoming_damage_reduction_combine = require("utils.incoming_damage_reduction_combine") +local removeIncomingDamageReductionSource = ____incoming_damage_reduction_combine.removeIncomingDamageReductionSource +local setIncomingDamageReductionSource = ____incoming_damage_reduction_combine.setIncomingDamageReductionSource +local DEMON_SLAYER_OWNER_INCOMING_SOURCE = "modifier_item_demon_slayer" +--- Demon Slayer: пассивы Zombie Slayer + статы/эффект Mage Slayer (маг. сопр., регены, DoT + штраф spell amp на цели), +-- плюс снижение входящего и бонус ко всему исходящему урону у носителя. +____exports.item_demon_slayer = __TS__Class() +local item_demon_slayer = ____exports.item_demon_slayer +item_demon_slayer.name = "item_demon_slayer" +item_demon_slayer.____file_path = "scripts/vscripts/items/default_items/item_demon_slayer.lua" +__TS__ClassExtends(item_demon_slayer, BaseItem) +function item_demon_slayer.prototype.Precache(self, context) + PrecacheResource("particle", "particles/demon_slayer_slash.vpcf", context) + PrecacheResource("particle", "particles/items2_fx/mage_slayer_debuff.vpcf", context) + PrecacheResource("particle", "particles/units/heroes/hero_dark_willow/dark_willow_wisp_spell_debuff.vpcf", context) +end +function item_demon_slayer.prototype.GetIntrinsicModifierName(self) + return "modifier_item_demon_slayer" +end +item_demon_slayer = __TS__Decorate( + item_demon_slayer, + item_demon_slayer, + {registerAbility(nil)}, + {kind = "class", name = "item_demon_slayer"} +) +____exports.item_demon_slayer = item_demon_slayer +____exports.modifier_item_demon_slayer = __TS__Class() +local modifier_item_demon_slayer = ____exports.modifier_item_demon_slayer +modifier_item_demon_slayer.name = "modifier_item_demon_slayer" +modifier_item_demon_slayer.____file_path = "scripts/vscripts/items/default_items/item_demon_slayer.lua" +__TS__ClassExtends(modifier_item_demon_slayer, BaseModifier) +function modifier_item_demon_slayer.prototype.IsHidden(self) + return true +end +function modifier_item_demon_slayer.prototype.IsPurgable(self) + return false +end +function modifier_item_demon_slayer.prototype.DeclareFunctions(self) + return { + MODIFIER_PROPERTY_PREATTACK_BONUS_DAMAGE, + MODIFIER_PROPERTY_STATS_AGILITY_BONUS, + MODIFIER_PROPERTY_MANA_REGEN_CONSTANT, + MODIFIER_PROPERTY_HEALTH_BONUS, + MODIFIER_PROPERTY_HEALTH_REGEN_CONSTANT, + MODIFIER_PROPERTY_MAGICAL_RESISTANCE_BONUS, + MODIFIER_EVENT_ON_ATTACK_LANDED, + MODIFIER_PROPERTY_DAMAGEOUTGOING_PERCENTAGE + } +end +function modifier_item_demon_slayer.prototype.OnCreated(self) + if not IsServer() then + return + end + setIncomingDamageReductionSource( + nil, + self:GetParent(), + DEMON_SLAYER_OWNER_INCOMING_SOURCE, + function() + local ability = self:GetAbility() + if not ability then + return 0 + end + return math.max( + 0, + math.abs(ability:GetSpecialValueFor("incoming_damage_reduction")) + ) + end + ) +end +function modifier_item_demon_slayer.prototype.OnDestroy(self) + if not IsServer() then + return + end + removeIncomingDamageReductionSource( + nil, + self:GetParent(), + DEMON_SLAYER_OWNER_INCOMING_SOURCE + ) +end +function modifier_item_demon_slayer.prototype.GetModifierHealthBonus(self) + return self:GetAbility():GetSpecialValueFor("bonus_health") +end +function modifier_item_demon_slayer.prototype.GetModifierConstantManaRegen(self) + return self:GetAbility():GetSpecialValueFor("bonus_mana_regen") +end +function modifier_item_demon_slayer.prototype.GetModifierConstantHealthRegen(self) + return self:GetAbility():GetSpecialValueFor("bonus_health_regen") +end +function modifier_item_demon_slayer.prototype.GetModifierMagicalResistanceBonus(self) + return self:GetAbility():GetSpecialValueFor("bonus_magic_resistance") +end +function modifier_item_demon_slayer.prototype.GetModifierPreAttack_BonusDamage(self) + return self:GetAbility():GetSpecialValueFor("bonus_damage") +end +function modifier_item_demon_slayer.prototype.GetModifierBonusStats_Agility(self) + return self:GetAbility():GetSpecialValueFor("bonus_agility") +end +function modifier_item_demon_slayer.prototype.GetModifierDamageOutgoing_Percentage(self) + return self:GetAbility():GetSpecialValueFor("outgoing_damage_bonus") +end +function modifier_item_demon_slayer.prototype.OnAttackLanded(self, event) + if not IsServer() then + return + end + if event.attacker ~= self:GetParent() then + return + end + if not event.target or event.target:IsBuilding() or event.target:IsOther() then + return + end + if event.target ~= nil then + local ability = self:GetAbility() + local durCorrosion = ability and ability:GetSpecialValueFor("debuff_duration") or 4 + local durMage = ability and ability:GetSpecialValueFor("duration") or 3 + event.target:AddNewModifier( + self:GetParent(), + ability, + "modifier_item_demon_slayer_debuff_2", + {duration = durCorrosion} + ) + event.target:AddNewModifier( + self:GetParent(), + ability, + "modifier_item_demon_slayer_mage", + {duration = durMage} + ) + local ____this_5 + ____this_5 = event.target + local ____opt_4 = ____this_5.GetUnitName + local targetName = ____opt_4 and ____opt_4(____this_5) or "" + if __TS__StringStartsWith(targetName, "npc_wave") then + ParticleManager:CreateParticle("particles/demon_slayer_slash.vpcf", PATTACH_ABSORIGIN_FOLLOW, event.target) + event.target:AddNewModifier( + self:GetParent(), + ability, + "modifier_item_demon_slayer_debuff", + {duration = durCorrosion} + ) + return + end + end +end +modifier_item_demon_slayer = __TS__Decorate( + modifier_item_demon_slayer, + modifier_item_demon_slayer, + {registerModifier(nil)}, + {kind = "class", name = "modifier_item_demon_slayer"} +) +____exports.modifier_item_demon_slayer = modifier_item_demon_slayer +--- Доп. урон по нежити волн (как Zombie Slayer). +____exports.modifier_item_demon_slayer_debuff = __TS__Class() +local modifier_item_demon_slayer_debuff = ____exports.modifier_item_demon_slayer_debuff +modifier_item_demon_slayer_debuff.name = "modifier_item_demon_slayer_debuff" +modifier_item_demon_slayer_debuff.____file_path = "scripts/vscripts/items/default_items/item_demon_slayer.lua" +__TS__ClassExtends(modifier_item_demon_slayer_debuff, BaseModifier) +function modifier_item_demon_slayer_debuff.prototype.IsDebuff(self) + return true +end +function modifier_item_demon_slayer_debuff.prototype.IsPurgable(self) + return true +end +function modifier_item_demon_slayer_debuff.prototype.IsHidden(self) + return true +end +function modifier_item_demon_slayer_debuff.prototype.GetEffectName(self) + return "particles/items2_fx/mage_slayer_debuff.vpcf" +end +function modifier_item_demon_slayer_debuff.prototype.GetEffectAttachType(self) + return PATTACH_ABSORIGIN_FOLLOW +end +function modifier_item_demon_slayer_debuff.prototype.DeclareFunctions(self) + return {MODIFIER_PROPERTY_INCOMING_DAMAGE_PERCENTAGE} +end +function modifier_item_demon_slayer_debuff.prototype.GetModifierIncomingDamage_Percentage(self, event) + return self:GetAbility():GetSpecialValueFor("incress_damage") +end +modifier_item_demon_slayer_debuff = __TS__Decorate( + modifier_item_demon_slayer_debuff, + modifier_item_demon_slayer_debuff, + {registerModifier(nil)}, + {kind = "class", name = "modifier_item_demon_slayer_debuff"} +) +____exports.modifier_item_demon_slayer_debuff = modifier_item_demon_slayer_debuff +--- Коррозия (броня / хил / скорость), без дублирования штрафа spell amp — его даёт Mage Slayer-дебафф. +____exports.modifier_item_demon_slayer_debuff_2 = __TS__Class() +local modifier_item_demon_slayer_debuff_2 = ____exports.modifier_item_demon_slayer_debuff_2 +modifier_item_demon_slayer_debuff_2.name = "modifier_item_demon_slayer_debuff_2" +modifier_item_demon_slayer_debuff_2.____file_path = "scripts/vscripts/items/default_items/item_demon_slayer.lua" +__TS__ClassExtends(modifier_item_demon_slayer_debuff_2, BaseModifier) +function modifier_item_demon_slayer_debuff_2.prototype.IsDebuff(self) + return true +end +function modifier_item_demon_slayer_debuff_2.prototype.IsPurgable(self) + return true +end +function modifier_item_demon_slayer_debuff_2.prototype.IsHidden(self) + return true +end +function modifier_item_demon_slayer_debuff_2.prototype.GetEffectName(self) + return "particles/units/heroes/hero_dark_willow/dark_willow_wisp_spell_debuff.vpcf" +end +function modifier_item_demon_slayer_debuff_2.prototype.GetEffectAttachType(self) + return PATTACH_OVERHEAD_FOLLOW +end +function modifier_item_demon_slayer_debuff_2.prototype.DeclareFunctions(self) + return {MODIFIER_PROPERTY_PHYSICAL_ARMOR_BONUS, MODIFIER_PROPERTY_HEALTH_REGEN_PERCENTAGE, MODIFIER_PROPERTY_MOVESPEED_BONUS_PERCENTAGE} +end +function modifier_item_demon_slayer_debuff_2.prototype.GetModifierMoveSpeedBonus_Percentage(self) + return self:GetAbility():GetSpecialValueFor("movespeed_reduction") +end +function modifier_item_demon_slayer_debuff_2.prototype.GetModifierHealthRegenPercentage(self) + return 1 + (self:GetAbility():GetSpecialValueFor("health_reduction") - 100) * 0.01 +end +function modifier_item_demon_slayer_debuff_2.prototype.GetModifierPhysicalArmorBonus(self, event) + return self:GetAbility():GetSpecialValueFor("armor_reduction") +end +modifier_item_demon_slayer_debuff_2 = __TS__Decorate( + modifier_item_demon_slayer_debuff_2, + modifier_item_demon_slayer_debuff_2, + {registerModifier(nil)}, + {kind = "class", name = "modifier_item_demon_slayer_debuff_2"} +) +____exports.modifier_item_demon_slayer_debuff_2 = modifier_item_demon_slayer_debuff_2 +--- Эффект как у Mage Slayer: −spell amp и магический DoT раз в секунду. +____exports.modifier_item_demon_slayer_mage = __TS__Class() +local modifier_item_demon_slayer_mage = ____exports.modifier_item_demon_slayer_mage +modifier_item_demon_slayer_mage.name = "modifier_item_demon_slayer_mage" +modifier_item_demon_slayer_mage.____file_path = "scripts/vscripts/items/default_items/item_demon_slayer.lua" +__TS__ClassExtends(modifier_item_demon_slayer_mage, BaseModifier) +function modifier_item_demon_slayer_mage.prototype.IsDebuff(self) + return true +end +function modifier_item_demon_slayer_mage.prototype.IsPurgable(self) + return true +end +function modifier_item_demon_slayer_mage.prototype.IsHidden(self) + return true +end +function modifier_item_demon_slayer_mage.prototype.GetEffectName(self) + return "particles/items2_fx/mage_slayer_debuff.vpcf" +end +function modifier_item_demon_slayer_mage.prototype.GetEffectAttachType(self) + return PATTACH_ABSORIGIN_FOLLOW +end +function modifier_item_demon_slayer_mage.prototype.DeclareFunctions(self) + return {MODIFIER_PROPERTY_SPELL_AMPLIFY_PERCENTAGE} +end +function modifier_item_demon_slayer_mage.prototype.GetModifierSpellAmplify_Percentage(self) + return -math.abs(self:GetAbility():GetSpecialValueFor("spell_amp_debuff")) +end +function modifier_item_demon_slayer_mage.prototype.OnCreated(self) + if not IsServer() then + return + end + self:PulseDps() + self:StartIntervalThink(1) +end +function modifier_item_demon_slayer_mage.prototype.OnIntervalThink(self) + if not IsServer() then + return + end + self:PulseDps() +end +function modifier_item_demon_slayer_mage.prototype.PulseDps(self) + local parent = self:GetParent() + local ability = self:GetAbility() + local caster = self:GetCaster() + if not ability or not caster or not (parent and parent:IsAlive()) then + return + end + local d = ability:GetSpecialValueFor("dps") + ApplyDamage({ + victim = parent, + attacker = caster, + damage = d, + damage_type = DAMAGE_TYPE_MAGICAL, + ability = ability, + damage_flags = DOTA_DAMAGE_FLAG_NO_SPELL_AMPLIFICATION + }) +end +modifier_item_demon_slayer_mage = __TS__Decorate( + modifier_item_demon_slayer_mage, + modifier_item_demon_slayer_mage, + {registerModifier(nil)}, + {kind = "class", name = "modifier_item_demon_slayer_mage"} +) +____exports.modifier_item_demon_slayer_mage = modifier_item_demon_slayer_mage +return ____exports diff --git a/scripts/vscripts/items/default_items/item_demonic_bow.lua b/scripts/vscripts/items/default_items/item_demonic_bow.lua new file mode 100644 index 0000000..c473ef5 --- /dev/null +++ b/scripts/vscripts/items/default_items/item_demonic_bow.lua @@ -0,0 +1,148 @@ +local ____lualib = require("lualib_bundle") +local __TS__Class = ____lualib.__TS__Class +local __TS__ClassExtends = ____lualib.__TS__ClassExtends +local __TS__Decorate = ____lualib.__TS__Decorate +local ____exports = {} +local ____dota_ts_adapter = require("lib.dota_ts_adapter") +local BaseItem = ____dota_ts_adapter.BaseItem +local registerAbility = ____dota_ts_adapter.registerAbility +local registerModifier = ____dota_ts_adapter.registerModifier +local BaseModifier = ____dota_ts_adapter.BaseModifier +____exports.item_demonic_bow = __TS__Class() +local item_demonic_bow = ____exports.item_demonic_bow +item_demonic_bow.name = "item_demonic_bow" +item_demonic_bow.____file_path = "scripts/vscripts/items/default_items/item_demonic_bow.lua" +__TS__ClassExtends(item_demonic_bow, BaseItem) +function item_demonic_bow.prototype.GetIntrinsicModifierName(self) + return "modifier_item_demonic_bow" +end +item_demonic_bow = __TS__Decorate( + item_demonic_bow, + item_demonic_bow, + {registerAbility(nil)}, + {kind = "class", name = "item_demonic_bow"} +) +____exports.item_demonic_bow = item_demonic_bow +____exports.modifier_item_demonic_bow = __TS__Class() +local modifier_item_demonic_bow = ____exports.modifier_item_demonic_bow +modifier_item_demonic_bow.name = "modifier_item_demonic_bow" +modifier_item_demonic_bow.____file_path = "scripts/vscripts/items/default_items/item_demonic_bow.lua" +__TS__ClassExtends(modifier_item_demonic_bow, BaseModifier) +function modifier_item_demonic_bow.prototype.IsHidden(self) + return true +end +function modifier_item_demonic_bow.prototype.IsDebuff(self) + return false +end +function modifier_item_demonic_bow.prototype.IsPurgable(self) + return false +end +function modifier_item_demonic_bow.prototype.OnCreated(self) + if not IsServer() then + return + end +end +function modifier_item_demonic_bow.prototype.CheckState(self) + return {[MODIFIER_STATE_CANNOT_MISS] = true} +end +function modifier_item_demonic_bow.prototype.DeclareFunctions(self) + return { + MODIFIER_EVENT_ON_ATTACK, + MODIFIER_PROPERTY_ATTACKSPEED_BONUS_CONSTANT, + MODIFIER_PROPERTY_ATTACK_RANGE_BONUS, + MODIFIER_PROPERTY_PREATTACK_BONUS_DAMAGE, + MODIFIER_PROPERTY_STATS_AGILITY_BONUS, + MODIFIER_PROPERTY_STATS_STRENGTH_BONUS, + MODIFIER_PROPERTY_STATS_INTELLECT_BONUS, + MODIFIER_PROPERTY_HEALTH_BONUS, + MODIFIER_PROPERTY_PROCATTACK_BONUS_DAMAGE_MAGICAL + } +end +function modifier_item_demonic_bow.prototype.GetModifierProcAttack_BonusDamage_Magical(self, event) + if self:GetParent():IsIllusion() or not self:GetParent():IsRangedAttacker() then + return 0 + end + return self:GetAbility():GetSpecialValueFor("proc_damage_magical") +end +function modifier_item_demonic_bow.prototype.GetModifierAttackSpeedBonus_Constant(self) + return self:GetAbility():GetSpecialValueFor("attack_speed") +end +function modifier_item_demonic_bow.prototype.GetModifierAttackRangeBonus(self) + if not self:GetParent():IsRangedAttacker() then + return 0 + end + return self:GetAbility():GetSpecialValueFor("attack_range") +end +function modifier_item_demonic_bow.prototype.GetModifierBonusStats_Strength(self) + return self:GetAbility():GetSpecialValueFor("strength") +end +function modifier_item_demonic_bow.prototype.GetModifierBonusStats_Agility(self) + return self:GetAbility():GetSpecialValueFor("agility") +end +function modifier_item_demonic_bow.prototype.GetModifierBonusStats_Intellect(self) + return self:GetAbility():GetSpecialValueFor("intellect") +end +function modifier_item_demonic_bow.prototype.GetModifierHealthBonus(self) + return self:GetAbility():GetSpecialValueFor("health") +end +function modifier_item_demonic_bow.prototype.OnAttack(self, event) + if not IsServer() then + return + end + local attacker = event.attacker + local target = event.target + if not attacker or not target then + return + end + if not attacker:IsRealHero() then + return + end + if attacker ~= self:GetParent() then + return + end + if not attacker:IsRangedAttacker() then + return + end + if event.no_attack_cooldown then + return + end + local radius = self:GetParent():GetAttackRangeBuffer() + self:GetParent():GetBaseAttackRange() + local units = FindUnitsInRadius( + attacker:GetTeamNumber(), + attacker:GetAbsOrigin(), + nil, + radius, + DOTA_UNIT_TARGET_TEAM_ENEMY, + DOTA_UNIT_TARGET_HERO + DOTA_UNIT_TARGET_BASIC + DOTA_UNIT_TARGET_BUILDING, + DOTA_UNIT_TARGET_FLAG_MAGIC_IMMUNE_ENEMIES + DOTA_UNIT_TARGET_FLAG_FOW_VISIBLE, + FIND_CLOSEST, + false + ) + local attackCount = 0 + for ____, unit in ipairs(units) do + if unit and not unit:IsNull() and unit ~= target and unit:IsAlive() then + attacker:PerformAttack( + unit, + true, + true, + true, + false, + true, + false, + false + ) + attackCount = attackCount + 1 + end + if attackCount >= self:GetAbility():GetSpecialValueFor("attack_count") then + break + end + end +end +modifier_item_demonic_bow = __TS__Decorate( + modifier_item_demonic_bow, + modifier_item_demonic_bow, + {registerModifier(nil)}, + {kind = "class", name = "modifier_item_demonic_bow"} +) +____exports.modifier_item_demonic_bow = modifier_item_demonic_bow +return ____exports diff --git a/scripts/vscripts/items/default_items/item_desolator_custom.lua b/scripts/vscripts/items/default_items/item_desolator_custom.lua new file mode 100644 index 0000000..748e9b5 --- /dev/null +++ b/scripts/vscripts/items/default_items/item_desolator_custom.lua @@ -0,0 +1,414 @@ +local ____lualib = require("lualib_bundle") +local __TS__Class = ____lualib.__TS__Class +local __TS__ClassExtends = ____lualib.__TS__ClassExtends +local __TS__Decorate = ____lualib.__TS__Decorate +local ____exports = {} +local ____dota_ts_adapter = require("lib.dota_ts_adapter") +local BaseItem = ____dota_ts_adapter.BaseItem +local BaseModifier = ____dota_ts_adapter.BaseModifier +local registerAbility = ____dota_ts_adapter.registerAbility +local registerModifier = ____dota_ts_adapter.registerModifier +local ____luck = require("utils.luck") +local rollLuckChance = ____luck.rollLuckChance +local DESOLATOR_PROJECTILE = "particles/items_fx/desolator_projectile.vpcf" +--- Кастомный Desolator (порт zombie_invasion): урон, HP, заряды с убийств, коррупция брони, снаряд. +____exports.item_desolator_custom = __TS__Class() +local item_desolator_custom = ____exports.item_desolator_custom +item_desolator_custom.name = "item_desolator_custom" +item_desolator_custom.____file_path = "scripts/vscripts/items/default_items/item_desolator_custom.lua" +__TS__ClassExtends(item_desolator_custom, BaseItem) +function item_desolator_custom.prototype.Precache(self, context) + PrecacheResource("particle", DESOLATOR_PROJECTILE, context) + PrecacheResource("particle", "particles/units/heroes/hero_spectre/spectre_desolate_debuff.vpcf", context) +end +function item_desolator_custom.prototype.GetIntrinsicModifierName(self) + return ____exports.modifier_item_desolator_custom.name +end +item_desolator_custom = __TS__Decorate( + item_desolator_custom, + item_desolator_custom, + {registerAbility(nil)}, + {kind = "class", name = "item_desolator_custom"} +) +____exports.item_desolator_custom = item_desolator_custom +____exports.modifier_item_desolator_custom = __TS__Class() +local modifier_item_desolator_custom = ____exports.modifier_item_desolator_custom +modifier_item_desolator_custom.name = "modifier_item_desolator_custom" +modifier_item_desolator_custom.____file_path = "scripts/vscripts/items/default_items/item_desolator_custom.lua" +__TS__ClassExtends(modifier_item_desolator_custom, BaseModifier) +function modifier_item_desolator_custom.prototype.IsHidden(self) + return true +end +function modifier_item_desolator_custom.prototype.IsPurgable(self) + return false +end +function modifier_item_desolator_custom.prototype.GetAttributes(self) + return MODIFIER_ATTRIBUTE_MULTIPLE +end +function modifier_item_desolator_custom.prototype.GetPriority(self) + return MODIFIER_PRIORITY_ULTRA +end +function modifier_item_desolator_custom.prototype.DeclareFunctions(self) + return { + MODIFIER_PROPERTY_PREATTACK_BONUS_DAMAGE, + MODIFIER_PROPERTY_EXTRA_HEALTH_BONUS, + MODIFIER_EVENT_ON_ATTACK_LANDED, + MODIFIER_EVENT_ON_DEATH, + MODIFIER_PROPERTY_PROJECTILE_NAME + } +end +function modifier_item_desolator_custom.prototype.GetModifierProjectileName(self) + return DESOLATOR_PROJECTILE +end +function modifier_item_desolator_custom.prototype.GetModifierPreAttack_BonusDamage(self) + local ability = self:GetAbility() + if not ability then + return 0 + end + return ability:GetSpecialValueFor("bonus_damage") + ability:GetCurrentCharges() +end +function modifier_item_desolator_custom.prototype.GetModifierExtraHealthBonus(self) + local ability = self:GetAbility() + if not ability then + return 0 + end + return ability:GetSpecialValueFor("bonus_health") + ability:GetCurrentCharges() +end +function modifier_item_desolator_custom.prototype.OnAttackLanded(self, event) + if not IsServer() then + return + end + local parent = self:GetParent() + if event.attacker ~= parent or not event.target then + return + end + local ability = self:GetAbility() + if not ability then + return + end + local duration = ability:GetSpecialValueFor("corruption_duration") + event.target:AddNewModifier(parent, ability, ____exports.modifier_item_desolator_custom_debuff.name, {duration = duration}) +end +function modifier_item_desolator_custom.prototype.OnDeath(self, event) + if not IsServer() then + return + end + local parent = self:GetParent() + local ability = self:GetAbility() + if not ability or not (parent and parent:IsAlive()) then + return + end + if event.attacker ~= parent then + return + end + local victim = event.unit + if not victim or victim == parent or not IsValidEntity(victim) then + return + end + if not victim:HasModifier(____exports.modifier_item_desolator_custom_debuff.name) then + return + end + local chancePct = ability:GetSpecialValueFor("chance_to_stack") + if chancePct <= 0 then + return + end + local ____parent_IsRealHero_result_2 + if parent:IsRealHero() then + ____parent_IsRealHero_result_2 = rollLuckChance(nil, parent, chancePct / 100) + else + ____parent_IsRealHero_result_2 = RollPercentage(chancePct) + end + local stacked = ____parent_IsRealHero_result_2 + if not stacked then + return + end + local maxCap = ability:GetSpecialValueFor("max_damage") + local perKill = ability:GetSpecialValueFor("bonus_damage_per_kill") + local next = math.min( + ability:GetCurrentCharges() + perKill, + maxCap + ) + ability:SetCurrentCharges(next) + if parent:IsRealHero() then + parent:CalculateStatBonus(true) + end +end +function modifier_item_desolator_custom.prototype.GetTexture(self) + return "item_desolator" +end +modifier_item_desolator_custom = __TS__Decorate( + modifier_item_desolator_custom, + modifier_item_desolator_custom, + {registerModifier(nil)}, + {kind = "class", name = "modifier_item_desolator_custom"} +) +____exports.modifier_item_desolator_custom = modifier_item_desolator_custom +____exports.modifier_item_desolator_custom_debuff = __TS__Class() +local modifier_item_desolator_custom_debuff = ____exports.modifier_item_desolator_custom_debuff +modifier_item_desolator_custom_debuff.name = "modifier_item_desolator_custom_debuff" +modifier_item_desolator_custom_debuff.____file_path = "scripts/vscripts/items/default_items/item_desolator_custom.lua" +__TS__ClassExtends(modifier_item_desolator_custom_debuff, BaseModifier) +function modifier_item_desolator_custom_debuff.prototype.____constructor(self, ...) + BaseModifier.prototype.____constructor(self, ...) + self.armorDebuff = 0 +end +function modifier_item_desolator_custom_debuff.prototype.IsHidden(self) + return false +end +function modifier_item_desolator_custom_debuff.prototype.IsDebuff(self) + return true +end +function modifier_item_desolator_custom_debuff.prototype.IsPurgable(self) + return true +end +function modifier_item_desolator_custom_debuff.prototype.OnCreated(self) + self:RefreshVals() +end +function modifier_item_desolator_custom_debuff.prototype.OnRefresh(self) + self:RefreshVals() +end +function modifier_item_desolator_custom_debuff.prototype.RefreshVals(self) + local ability = self:GetAbility() + self.armorDebuff = ability and ability:GetSpecialValueFor("corruption_armor") or 0 +end +function modifier_item_desolator_custom_debuff.prototype.DeclareFunctions(self) + return {MODIFIER_PROPERTY_PHYSICAL_ARMOR_BONUS, MODIFIER_PROPERTY_HEALTH_REGEN_PERCENTAGE, MODIFIER_PROPERTY_MOVESPEED_BONUS_PERCENTAGE} +end +function modifier_item_desolator_custom_debuff.prototype.GetModifierPhysicalArmorBonus(self) + return self.armorDebuff +end +function modifier_item_desolator_custom_debuff.prototype.GetModifierMoveSpeedBonus_Percentage(self) + local ability = self:GetAbility() + return ability and ability:GetSpecialValueFor("corruption_movespeed_slow") or 0 +end +function modifier_item_desolator_custom_debuff.prototype.GetModifierHealthRegenPercentage(self) + local ability = self:GetAbility() + if not ability then + return 1 + end + local v = ability:GetSpecialValueFor("corruption_heal_reduction") + return 1 + (v - 100) * 0.01 +end +function modifier_item_desolator_custom_debuff.prototype.GetEffectName(self) + return "particles/units/heroes/hero_spectre/spectre_desolate_debuff.vpcf" +end +function modifier_item_desolator_custom_debuff.prototype.GetEffectAttachType(self) + return PATTACH_ABSORIGIN_FOLLOW +end +function modifier_item_desolator_custom_debuff.prototype.GetTexture(self) + return "item_desolator" +end +modifier_item_desolator_custom_debuff = __TS__Decorate( + modifier_item_desolator_custom_debuff, + modifier_item_desolator_custom_debuff, + {registerModifier(nil)}, + {kind = "class", name = "modifier_item_desolator_custom_debuff"} +) +____exports.modifier_item_desolator_custom_debuff = modifier_item_desolator_custom_debuff +____exports.item_desolator_custom_2 = __TS__Class() +local item_desolator_custom_2 = ____exports.item_desolator_custom_2 +item_desolator_custom_2.name = "item_desolator_custom_2" +item_desolator_custom_2.____file_path = "scripts/vscripts/items/default_items/item_desolator_custom.lua" +__TS__ClassExtends(item_desolator_custom_2, BaseItem) +function item_desolator_custom_2.prototype.Precache(self, context) + PrecacheResource("particle", DESOLATOR_PROJECTILE, context) + PrecacheResource("particle", "particles/units/heroes/hero_spectre/spectre_desolate_debuff.vpcf", context) +end +function item_desolator_custom_2.prototype.GetIntrinsicModifierName(self) + return ____exports.modifier_item_desolator_custom_2.name +end +item_desolator_custom_2 = __TS__Decorate( + item_desolator_custom_2, + item_desolator_custom_2, + {registerAbility(nil)}, + {kind = "class", name = "item_desolator_custom_2"} +) +____exports.item_desolator_custom_2 = item_desolator_custom_2 +____exports.modifier_item_desolator_custom_2 = __TS__Class() +local modifier_item_desolator_custom_2 = ____exports.modifier_item_desolator_custom_2 +modifier_item_desolator_custom_2.name = "modifier_item_desolator_custom_2" +modifier_item_desolator_custom_2.____file_path = "scripts/vscripts/items/default_items/item_desolator_custom.lua" +__TS__ClassExtends(modifier_item_desolator_custom_2, BaseModifier) +function modifier_item_desolator_custom_2.prototype.IsHidden(self) + return true +end +function modifier_item_desolator_custom_2.prototype.IsPurgable(self) + return false +end +function modifier_item_desolator_custom_2.prototype.GetAttributes(self) + return MODIFIER_ATTRIBUTE_MULTIPLE +end +function modifier_item_desolator_custom_2.prototype.GetPriority(self) + return MODIFIER_PRIORITY_ULTRA +end +function modifier_item_desolator_custom_2.prototype.DeclareFunctions(self) + return { + MODIFIER_PROPERTY_PREATTACK_BONUS_DAMAGE, + MODIFIER_PROPERTY_EXTRA_HEALTH_BONUS, + MODIFIER_EVENT_ON_ATTACK_LANDED, + MODIFIER_EVENT_ON_DEATH, + MODIFIER_PROPERTY_PROJECTILE_NAME + } +end +function modifier_item_desolator_custom_2.prototype.GetModifierProjectileName(self) + return DESOLATOR_PROJECTILE +end +function modifier_item_desolator_custom_2.prototype.GetModifierPreAttack_BonusDamage(self) + local ability = self:GetAbility() + if not ability then + return 0 + end + return ability:GetSpecialValueFor("bonus_damage") + ability:GetCurrentCharges() +end +function modifier_item_desolator_custom_2.prototype.GetModifierExtraHealthBonus(self) + local ability = self:GetAbility() + if not ability then + return 0 + end + return ability:GetSpecialValueFor("bonus_health") + ability:GetCurrentCharges() +end +function modifier_item_desolator_custom_2.prototype.OnAttackLanded(self, event) + if not IsServer() then + return + end + local parent = self:GetParent() + if event.attacker ~= parent or not event.target then + return + end + local ability = self:GetAbility() + if not ability then + return + end + local duration = ability:GetSpecialValueFor("corruption_duration") + local perStack = ability:GetSpecialValueFor("corruption_armor_per_stack") + local cap = ability:GetSpecialValueFor("corruption_armor_cap") + local maxStacks = math.max( + 1, + math.ceil(cap / math.max(1, perStack)) + ) + local target = event.target + local existing = target:FindModifierByName(____exports.modifier_item_desolator_custom_2_debuff.name) + if existing then + if existing:GetStackCount() < maxStacks then + existing:IncrementStackCount() + end + existing:SetDuration(duration, true) + else + local m = target:AddNewModifier(parent, ability, ____exports.modifier_item_desolator_custom_2_debuff.name, {duration = duration}) + if m ~= nil then + m:SetStackCount(1) + end + end +end +function modifier_item_desolator_custom_2.prototype.OnDeath(self, event) + if not IsServer() then + return + end + local parent = self:GetParent() + local ability = self:GetAbility() + if not ability or not (parent and parent:IsAlive()) then + return + end + if event.attacker ~= parent then + return + end + local victim = event.unit + if not victim or victim == parent or not IsValidEntity(victim) then + return + end + if not victim:HasModifier(____exports.modifier_item_desolator_custom_2_debuff.name) then + return + end + local chancePct = ability:GetSpecialValueFor("chance_to_stack") + if chancePct <= 0 then + return + end + local ____parent_IsRealHero_result_7 + if parent:IsRealHero() then + ____parent_IsRealHero_result_7 = rollLuckChance(nil, parent, chancePct / 100) + else + ____parent_IsRealHero_result_7 = RollPercentage(chancePct) + end + local stacked = ____parent_IsRealHero_result_7 + if not stacked then + return + end + local maxCap = ability:GetSpecialValueFor("max_damage") + local perKill = ability:GetSpecialValueFor("bonus_damage_per_kill") + local next = math.min( + ability:GetCurrentCharges() + perKill, + maxCap + ) + ability:SetCurrentCharges(next) + if parent:IsRealHero() then + parent:CalculateStatBonus(true) + end +end +function modifier_item_desolator_custom_2.prototype.GetTexture(self) + return "item_desolator_2" +end +modifier_item_desolator_custom_2 = __TS__Decorate( + modifier_item_desolator_custom_2, + modifier_item_desolator_custom_2, + {registerModifier(nil)}, + {kind = "class", name = "modifier_item_desolator_custom_2"} +) +____exports.modifier_item_desolator_custom_2 = modifier_item_desolator_custom_2 +____exports.modifier_item_desolator_custom_2_debuff = __TS__Class() +local modifier_item_desolator_custom_2_debuff = ____exports.modifier_item_desolator_custom_2_debuff +modifier_item_desolator_custom_2_debuff.name = "modifier_item_desolator_custom_2_debuff" +modifier_item_desolator_custom_2_debuff.____file_path = "scripts/vscripts/items/default_items/item_desolator_custom.lua" +__TS__ClassExtends(modifier_item_desolator_custom_2_debuff, BaseModifier) +function modifier_item_desolator_custom_2_debuff.prototype.IsHidden(self) + return false +end +function modifier_item_desolator_custom_2_debuff.prototype.IsDebuff(self) + return true +end +function modifier_item_desolator_custom_2_debuff.prototype.IsPurgable(self) + return true +end +function modifier_item_desolator_custom_2_debuff.prototype.DeclareFunctions(self) + return {MODIFIER_PROPERTY_PHYSICAL_ARMOR_BONUS, MODIFIER_PROPERTY_HEALTH_REGEN_PERCENTAGE, MODIFIER_PROPERTY_MOVESPEED_BONUS_PERCENTAGE} +end +function modifier_item_desolator_custom_2_debuff.prototype.GetModifierPhysicalArmorBonus(self) + local ability = self:GetAbility() + if not ability then + return 0 + end + local perStack = ability:GetSpecialValueFor("corruption_armor_per_stack") + local cap = ability:GetSpecialValueFor("corruption_armor_cap") + return -math.min( + self:GetStackCount() * perStack, + cap + ) +end +function modifier_item_desolator_custom_2_debuff.prototype.GetModifierMoveSpeedBonus_Percentage(self) + local ability = self:GetAbility() + return ability and ability:GetSpecialValueFor("corruption_movespeed_slow") or 0 +end +function modifier_item_desolator_custom_2_debuff.prototype.GetModifierHealthRegenPercentage(self) + local ability = self:GetAbility() + if not ability then + return 1 + end + local v = ability:GetSpecialValueFor("corruption_heal_reduction") + return 1 + (v - 100) * 0.01 +end +function modifier_item_desolator_custom_2_debuff.prototype.GetEffectName(self) + return "particles/units/heroes/hero_spectre/spectre_desolate_debuff.vpcf" +end +function modifier_item_desolator_custom_2_debuff.prototype.GetEffectAttachType(self) + return PATTACH_ABSORIGIN_FOLLOW +end +function modifier_item_desolator_custom_2_debuff.prototype.GetTexture(self) + return "item_desolator_2" +end +modifier_item_desolator_custom_2_debuff = __TS__Decorate( + modifier_item_desolator_custom_2_debuff, + modifier_item_desolator_custom_2_debuff, + {registerModifier(nil)}, + {kind = "class", name = "modifier_item_desolator_custom_2_debuff"} +) +____exports.modifier_item_desolator_custom_2_debuff = modifier_item_desolator_custom_2_debuff +return ____exports diff --git a/scripts/vscripts/items/default_items/item_divine_rapier_custom.lua b/scripts/vscripts/items/default_items/item_divine_rapier_custom.lua new file mode 100644 index 0000000..4187f51 --- /dev/null +++ b/scripts/vscripts/items/default_items/item_divine_rapier_custom.lua @@ -0,0 +1,76 @@ +local ____lualib = require("lualib_bundle") +local __TS__Class = ____lualib.__TS__Class +local __TS__ClassExtends = ____lualib.__TS__ClassExtends +local __TS__Decorate = ____lualib.__TS__Decorate +local ____exports = {} +local ____dota_ts_adapter = require("lib.dota_ts_adapter") +local BaseItem = ____dota_ts_adapter.BaseItem +local BaseModifier = ____dota_ts_adapter.BaseModifier +local registerAbility = ____dota_ts_adapter.registerAbility +local registerModifier = ____dota_ts_adapter.registerModifier +____exports.item_divine_rapier_custom = __TS__Class() +local item_divine_rapier_custom = ____exports.item_divine_rapier_custom +item_divine_rapier_custom.name = "item_divine_rapier_custom" +item_divine_rapier_custom.____file_path = "scripts/vscripts/items/default_items/item_divine_rapier_custom.lua" +__TS__ClassExtends(item_divine_rapier_custom, BaseItem) +function item_divine_rapier_custom.prototype.GetIntrinsicModifierName(self) + return "modifier_divine_rapier_custom" +end +item_divine_rapier_custom = __TS__Decorate( + item_divine_rapier_custom, + item_divine_rapier_custom, + {registerAbility(nil)}, + {kind = "class", name = "item_divine_rapier_custom"} +) +____exports.item_divine_rapier_custom = item_divine_rapier_custom +____exports.modifier_divine_rapier_custom = __TS__Class() +local modifier_divine_rapier_custom = ____exports.modifier_divine_rapier_custom +modifier_divine_rapier_custom.name = "modifier_divine_rapier_custom" +modifier_divine_rapier_custom.____file_path = "scripts/vscripts/items/default_items/item_divine_rapier_custom.lua" +__TS__ClassExtends(modifier_divine_rapier_custom, BaseModifier) +function modifier_divine_rapier_custom.prototype.IsHidden(self) + return true +end +function modifier_divine_rapier_custom.prototype.IsDebuff(self) + return false +end +function modifier_divine_rapier_custom.prototype.IsPurgable(self) + return false +end +function modifier_divine_rapier_custom.prototype.GetAttributes(self) + return MODIFIER_ATTRIBUTE_MULTIPLE +end +function modifier_divine_rapier_custom.prototype.DeclareFunctions(self) + return {MODIFIER_PROPERTY_PREATTACK_BONUS_DAMAGE, MODIFIER_PROPERTY_PROCATTACK_BONUS_DAMAGE_PURE, MODIFIER_EVENT_ON_ATTACK_LANDED} +end +function modifier_divine_rapier_custom.prototype.OnAttackLanded(self, event) + if self:GetParent() == event.attacker then + local damage = self:GetAbility():GetSpecialValueFor("proc_damage") / 100 * self:GetParent():GetAverageTrueAttackDamage(event.target) + SendOverheadEventMessage( + nil, + OVERHEAD_ALERT_DAMAGE, + event.target, + damage, + nil + ) + local particle = ParticleManager:CreateParticle("models/heroes/phantom_assassin_persona/debut/particles/pa_debutdash/pa_debutdash_fragments.vpcf", PATTACH_CUSTOMORIGIN, nil) + ParticleManager:SetParticleControl( + particle, + 0, + event.target:GetAbsOrigin() + ) + ParticleManager:ReleaseParticleIndex(particle) + ApplyDamage({victim = event.target, attacker = event.attacker, damage = damage, damage_type = DAMAGE_TYPE_PURE}) + end +end +function modifier_divine_rapier_custom.prototype.GetModifierPreAttack_BonusDamage(self) + return self:GetAbility():GetSpecialValueFor("bonus_damage") +end +modifier_divine_rapier_custom = __TS__Decorate( + modifier_divine_rapier_custom, + modifier_divine_rapier_custom, + {registerModifier(nil)}, + {kind = "class", name = "modifier_divine_rapier_custom"} +) +____exports.modifier_divine_rapier_custom = modifier_divine_rapier_custom +return ____exports diff --git a/scripts/vscripts/items/default_items/item_ethereal_blade_custom.lua b/scripts/vscripts/items/default_items/item_ethereal_blade_custom.lua new file mode 100644 index 0000000..d4d8046 --- /dev/null +++ b/scripts/vscripts/items/default_items/item_ethereal_blade_custom.lua @@ -0,0 +1,324 @@ +local ____lualib = require("lualib_bundle") +local __TS__Class = ____lualib.__TS__Class +local __TS__ClassExtends = ____lualib.__TS__ClassExtends +local __TS__Decorate = ____lualib.__TS__Decorate +local __TS__ArrayPush = ____lualib.__TS__ArrayPush +local ____exports = {} +local ____dota_ts_adapter = require("lib.dota_ts_adapter") +local BaseItem = ____dota_ts_adapter.BaseItem +local BaseModifier = ____dota_ts_adapter.BaseModifier +local registerAbility = ____dota_ts_adapter.registerAbility +local registerModifier = ____dota_ts_adapter.registerModifier +local ETHEREAL_BLADE_PARTICLE = "particles/items_fx/ethereal_blade.vpcf" +local ETHEREAL_STATUS_PARTICLE = "particles/status_fx/status_effect_ghost.vpcf" +____exports.item_ethereal_blade_custom = __TS__Class() +local item_ethereal_blade_custom = ____exports.item_ethereal_blade_custom +item_ethereal_blade_custom.name = "item_ethereal_blade_custom" +item_ethereal_blade_custom.____file_path = "scripts/vscripts/items/default_items/item_ethereal_blade_custom.lua" +__TS__ClassExtends(item_ethereal_blade_custom, BaseItem) +function item_ethereal_blade_custom.prototype.Precache(self, context) + PrecacheResource("particle", ETHEREAL_BLADE_PARTICLE, context) + PrecacheResource("particle", ETHEREAL_STATUS_PARTICLE, context) + PrecacheResource("soundfile", "soundevents/game_sounds_items.vsndevts", context) +end +function item_ethereal_blade_custom.prototype.GetIntrinsicModifierName(self) + return ____exports.modifier_item_ethereal_blade_custom.name +end +function item_ethereal_blade_custom.prototype.GetAOERadius(self) + return self:GetSpecialValueFor("splash_radius") +end +function item_ethereal_blade_custom.prototype.GetCastRange(self) + local range = self:GetSpecialValueFor("abilitycastrange") + if IsServer() then + return range + end + local ____opt_0 = self:GetCaster() + local bonus = ____opt_0 and ____opt_0:GetCastRangeBonus() or 0 + return range - bonus +end +function item_ethereal_blade_custom.prototype.OnSpellStart(self) + if not IsServer() then + return + end + local caster = self:GetCaster() + local target = self:GetCursorTarget() + if not target then + return + end + local splashRadius = self:GetSpecialValueFor("splash_radius") + local projectileSpeed = self:GetSpecialValueFor("projectile_speed") + local sourceLoc = caster:GetAbsOrigin() + caster:EmitSound("DOTA_Item.EtherealBlade.Activate") + local isAlly = target:GetTeamNumber() == caster:GetTeamNumber() + local units = {} + if isAlly then + units = {target} + else + units = FindUnitsInRadius( + caster:GetTeamNumber(), + target:GetAbsOrigin(), + nil, + splashRadius, + DOTA_UNIT_TARGET_TEAM_ENEMY, + DOTA_UNIT_TARGET_HERO + DOTA_UNIT_TARGET_BASIC, + DOTA_UNIT_TARGET_FLAG_NONE, + FIND_ANY_ORDER, + false + ) + if #units == 0 then + units = {target} + end + end + for ____, enemy in ipairs(units) do + ProjectileManager:CreateTrackingProjectile({ + Target = enemy, + Source = caster, + Ability = self, + EffectName = ETHEREAL_BLADE_PARTICLE, + iMoveSpeed = projectileSpeed, + vSourceLoc = sourceLoc, + bDodgeable = true, + bProvidesVision = false + }) + end +end +function item_ethereal_blade_custom.prototype.OnProjectileHit(self, target) + if not IsServer() or not target then + return + end + local caster = self:GetCaster() + if target:IsMagicImmune() then + return + end + if target:TriggerSpellAbsorb(self) then + return + end + target:EmitSound("DOTA_Item.EtherealBlade.Target") + local durationEnemy = self:GetSpecialValueFor("duration") + local durationAlly = self:GetSpecialValueFor("duration_ally") + local isAlly = target:GetTeamNumber() == caster:GetTeamNumber() + if isAlly then + target:AddNewModifier(caster, self, ____exports.modifier_item_ethereal_blade_custom_ethereal.name, {duration = durationAlly, is_ally = 1}) + target:AddNewModifier(caster, self, ____exports.modifier_item_ethereal_blade_custom_ally_haste.name, {duration = durationAlly}) + return + end + local resistDuration = durationEnemy * (1 - target:GetStatusResistance()) + target:AddNewModifier(caster, self, ____exports.modifier_item_ethereal_blade_custom_ethereal.name, {duration = resistDuration, is_ally = 0}) + local hero = caster + local statMult = self:GetSpecialValueFor("blast_stat_multiplier") + local baseDamage = self:GetSpecialValueFor("blast_damage_base") + local damage = (hero:IsHero() and hero:GetPrimaryStatValue() or 0) * statMult + baseDamage + ApplyDamage({ + victim = target, + attacker = caster, + damage = damage, + damage_type = DAMAGE_TYPE_MAGICAL, + ability = self + }) +end +item_ethereal_blade_custom = __TS__Decorate( + item_ethereal_blade_custom, + item_ethereal_blade_custom, + {registerAbility(nil)}, + {kind = "class", name = "item_ethereal_blade_custom"} +) +____exports.item_ethereal_blade_custom = item_ethereal_blade_custom +____exports.modifier_item_ethereal_blade_custom = __TS__Class() +local modifier_item_ethereal_blade_custom = ____exports.modifier_item_ethereal_blade_custom +modifier_item_ethereal_blade_custom.name = "modifier_item_ethereal_blade_custom" +modifier_item_ethereal_blade_custom.____file_path = "scripts/vscripts/items/default_items/item_ethereal_blade_custom.lua" +__TS__ClassExtends(modifier_item_ethereal_blade_custom, BaseModifier) +function modifier_item_ethereal_blade_custom.prototype.IsHidden(self) + return true +end +function modifier_item_ethereal_blade_custom.prototype.IsPurgable(self) + return false +end +function modifier_item_ethereal_blade_custom.prototype.RemoveOnDeath(self) + return false +end +function modifier_item_ethereal_blade_custom.prototype.GetAttributes(self) + return MODIFIER_ATTRIBUTE_MULTIPLE +end +function modifier_item_ethereal_blade_custom.prototype.DeclareFunctions(self) + return { + MODIFIER_PROPERTY_STATS_STRENGTH_BONUS, + MODIFIER_PROPERTY_STATS_AGILITY_BONUS, + MODIFIER_PROPERTY_STATS_INTELLECT_BONUS, + MODIFIER_PROPERTY_STATUS_RESISTANCE_STACKING, + MODIFIER_PROPERTY_ATTACKSPEED_BONUS_CONSTANT, + MODIFIER_PROPERTY_MOVESPEED_BONUS_PERCENTAGE, + MODIFIER_PROPERTY_HP_REGEN_AMPLIFY_PERCENTAGE, + MODIFIER_PROPERTY_MP_REGEN_AMPLIFY_PERCENTAGE, + MODIFIER_PROPERTY_SPELL_AMPLIFY_PERCENTAGE, + MODIFIER_PROPERTY_PROCATTACK_BONUS_DAMAGE_MAGICAL + } +end +function modifier_item_ethereal_blade_custom.prototype.getValue(self, name) + local ____opt_2 = self:GetAbility() + return ____opt_2 and ____opt_2:GetSpecialValueFor(name) or 0 +end +function modifier_item_ethereal_blade_custom.prototype.GetModifierBonusStats_Strength(self) + return self:getValue("bonus_strength") +end +function modifier_item_ethereal_blade_custom.prototype.GetModifierBonusStats_Agility(self) + return self:getValue("bonus_agility") +end +function modifier_item_ethereal_blade_custom.prototype.GetModifierBonusStats_Intellect(self) + return self:getValue("bonus_intellect") +end +function modifier_item_ethereal_blade_custom.prototype.GetModifierStatusResistanceStacking(self) + return self:getValue("status_resistance") +end +function modifier_item_ethereal_blade_custom.prototype.GetModifierAttackSpeedBonus_Constant(self) + return self:getValue("bonus_attack_speed") +end +function modifier_item_ethereal_blade_custom.prototype.GetModifierMoveSpeedBonus_Percentage(self) + return self:getValue("movement_speed_percent_bonus") +end +function modifier_item_ethereal_blade_custom.prototype.GetModifierHPRegenAmplify_Percentage(self) + return self:getValue("hp_regen_amp") +end +function modifier_item_ethereal_blade_custom.prototype.GetModifierMPRegenAmplify_Percentage(self) + return self:getValue("mana_regen_multiplier") +end +function modifier_item_ethereal_blade_custom.prototype.GetModifierSpellAmplify_Percentage(self) + return self:getValue("spell_amp") +end +function modifier_item_ethereal_blade_custom.prototype.GetModifierProcAttack_BonusDamage_Magical(self) + return self:getValue("magic_damage_attack") +end +modifier_item_ethereal_blade_custom = __TS__Decorate( + modifier_item_ethereal_blade_custom, + modifier_item_ethereal_blade_custom, + {registerModifier(nil)}, + {kind = "class", name = "modifier_item_ethereal_blade_custom"} +) +____exports.modifier_item_ethereal_blade_custom = modifier_item_ethereal_blade_custom +____exports.modifier_item_ethereal_blade_custom_ethereal = __TS__Class() +local modifier_item_ethereal_blade_custom_ethereal = ____exports.modifier_item_ethereal_blade_custom_ethereal +modifier_item_ethereal_blade_custom_ethereal.name = "modifier_item_ethereal_blade_custom_ethereal" +modifier_item_ethereal_blade_custom_ethereal.____file_path = "scripts/vscripts/items/default_items/item_ethereal_blade_custom.lua" +__TS__ClassExtends(modifier_item_ethereal_blade_custom_ethereal, BaseModifier) +function modifier_item_ethereal_blade_custom_ethereal.prototype.____constructor(self, ...) + BaseModifier.prototype.____constructor(self, ...) + self.blastMovementSlow = 0 + self.etherealDamageBonus = 0 + self.turnRateReduction = 0 + self.isAlly = false +end +function modifier_item_ethereal_blade_custom_ethereal.prototype.IsHidden(self) + return false +end +function modifier_item_ethereal_blade_custom_ethereal.prototype.IsDebuff(self) + return not self.isAlly +end +function modifier_item_ethereal_blade_custom_ethereal.prototype.IsPurgable(self) + return true +end +function modifier_item_ethereal_blade_custom_ethereal.prototype.GetStatusEffectName(self) + return ETHEREAL_STATUS_PARTICLE +end +function modifier_item_ethereal_blade_custom_ethereal.prototype.StatusEffectPriority(self) + return MODIFIER_PRIORITY_NORMAL +end +function modifier_item_ethereal_blade_custom_ethereal.prototype.OnCreated(self, params) + local ability = self:GetAbility() + if not ability then + self:Destroy() + return + end + self.isAlly = (params.is_ally or 0) == 1 + self.blastMovementSlow = ability:GetSpecialValueFor("blast_movement_slow") + self.etherealDamageBonus = ability:GetSpecialValueFor("ethereal_damage_bonus") + self.turnRateReduction = ability:GetSpecialValueFor("turn_rate_reduction") +end +function modifier_item_ethereal_blade_custom_ethereal.prototype.CheckState(self) + return {[MODIFIER_STATE_ATTACK_IMMUNE] = true, [MODIFIER_STATE_DISARMED] = true} +end +function modifier_item_ethereal_blade_custom_ethereal.prototype.DeclareFunctions(self) + local funcs = {MODIFIER_PROPERTY_MAGICAL_RESISTANCE_DECREPIFY_UNIQUE, MODIFIER_PROPERTY_ABSOLUTE_NO_DAMAGE_PHYSICAL} + if self.isAlly then + funcs[#funcs + 1] = MODIFIER_PROPERTY_IGNORE_CAST_ANGLE + else + __TS__ArrayPush(funcs, MODIFIER_PROPERTY_MOVESPEED_BONUS_PERCENTAGE, MODIFIER_PROPERTY_TURN_RATE_PERCENTAGE) + end + return funcs +end +function modifier_item_ethereal_blade_custom_ethereal.prototype.GetModifierMoveSpeedBonus_Percentage(self) + if self.isAlly then + return 0 + end + return self.blastMovementSlow +end +function modifier_item_ethereal_blade_custom_ethereal.prototype.GetModifierMagicalResistanceDecrepifyUnique(self) + return self.etherealDamageBonus +end +function modifier_item_ethereal_blade_custom_ethereal.prototype.GetAbsoluteNoDamagePhysical(self) + return 1 +end +function modifier_item_ethereal_blade_custom_ethereal.prototype.GetModifierTurnRate_Percentage(self) + return self.turnRateReduction +end +function modifier_item_ethereal_blade_custom_ethereal.prototype.GetModifierIgnoreCastAngle(self) + return self.isAlly and 1 or 0 +end +function modifier_item_ethereal_blade_custom_ethereal.prototype.GetTexture(self) + return "item_ethereal_blade" +end +modifier_item_ethereal_blade_custom_ethereal = __TS__Decorate( + modifier_item_ethereal_blade_custom_ethereal, + modifier_item_ethereal_blade_custom_ethereal, + {registerModifier(nil)}, + {kind = "class", name = "modifier_item_ethereal_blade_custom_ethereal"} +) +____exports.modifier_item_ethereal_blade_custom_ethereal = modifier_item_ethereal_blade_custom_ethereal +--- Ускорение при касте по союзнику (движение и атака). +____exports.modifier_item_ethereal_blade_custom_ally_haste = __TS__Class() +local modifier_item_ethereal_blade_custom_ally_haste = ____exports.modifier_item_ethereal_blade_custom_ally_haste +modifier_item_ethereal_blade_custom_ally_haste.name = "modifier_item_ethereal_blade_custom_ally_haste" +modifier_item_ethereal_blade_custom_ally_haste.____file_path = "scripts/vscripts/items/default_items/item_ethereal_blade_custom.lua" +__TS__ClassExtends(modifier_item_ethereal_blade_custom_ally_haste, BaseModifier) +function modifier_item_ethereal_blade_custom_ally_haste.prototype.____constructor(self, ...) + BaseModifier.prototype.____constructor(self, ...) + self.bonusMoveSpeedPct = 0 + self.bonusAttackSpeedPct = 0 +end +function modifier_item_ethereal_blade_custom_ally_haste.prototype.IsHidden(self) + return false +end +function modifier_item_ethereal_blade_custom_ally_haste.prototype.IsDebuff(self) + return false +end +function modifier_item_ethereal_blade_custom_ally_haste.prototype.IsPurgable(self) + return true +end +function modifier_item_ethereal_blade_custom_ally_haste.prototype.OnCreated(self) + local ability = self:GetAbility() + if not ability then + self:Destroy() + return + end + self.bonusMoveSpeedPct = ability:GetSpecialValueFor("ally_bonus_movespeed") + self.bonusAttackSpeedPct = ability:GetSpecialValueFor("ally_bonus_attack_speed") +end +function modifier_item_ethereal_blade_custom_ally_haste.prototype.DeclareFunctions(self) + return {MODIFIER_PROPERTY_MOVESPEED_BONUS_PERCENTAGE, MODIFIER_PROPERTY_ATTACKSPEED_PERCENTAGE} +end +function modifier_item_ethereal_blade_custom_ally_haste.prototype.GetModifierMoveSpeedBonus_Percentage(self) + return self.bonusMoveSpeedPct +end +function modifier_item_ethereal_blade_custom_ally_haste.prototype.GetModifierAttackSpeedPercentage(self) + return self.bonusAttackSpeedPct +end +function modifier_item_ethereal_blade_custom_ally_haste.prototype.GetTexture(self) + return "item_ethereal_blade" +end +modifier_item_ethereal_blade_custom_ally_haste = __TS__Decorate( + modifier_item_ethereal_blade_custom_ally_haste, + modifier_item_ethereal_blade_custom_ally_haste, + {registerModifier(nil)}, + {kind = "class", name = "modifier_item_ethereal_blade_custom_ally_haste"} +) +____exports.modifier_item_ethereal_blade_custom_ally_haste = modifier_item_ethereal_blade_custom_ally_haste +return ____exports diff --git a/scripts/vscripts/items/default_items/item_fire_cape.lua b/scripts/vscripts/items/default_items/item_fire_cape.lua new file mode 100644 index 0000000..c09b5f2 --- /dev/null +++ b/scripts/vscripts/items/default_items/item_fire_cape.lua @@ -0,0 +1,184 @@ +local ____lualib = require("lualib_bundle") +local __TS__Class = ____lualib.__TS__Class +local __TS__ClassExtends = ____lualib.__TS__ClassExtends +local __TS__Decorate = ____lualib.__TS__Decorate +local ____exports = {} +local ____dota_ts_adapter = require("lib.dota_ts_adapter") +local BaseItem = ____dota_ts_adapter.BaseItem +local registerAbility = ____dota_ts_adapter.registerAbility +local registerModifier = ____dota_ts_adapter.registerModifier +local BaseModifier = ____dota_ts_adapter.BaseModifier +local ____modifier_general_fired = require("abilities.modifiers.modifier_general_fired") +local modifier_general_fired = ____modifier_general_fired.modifier_general_fired +____exports.item_fire_cape = __TS__Class() +local item_fire_cape = ____exports.item_fire_cape +item_fire_cape.name = "item_fire_cape" +item_fire_cape.____file_path = "scripts/vscripts/items/default_items/item_fire_cape.lua" +__TS__ClassExtends(item_fire_cape, BaseItem) +function item_fire_cape.prototype.GetIntrinsicModifierName(self) + return "modifier_item_fire_cape" +end +function item_fire_cape.prototype.GetAOERadius(self) + return self:GetSpecialValueFor("radius") +end +item_fire_cape = __TS__Decorate( + item_fire_cape, + item_fire_cape, + {registerAbility(nil)}, + {kind = "class", name = "item_fire_cape"} +) +____exports.item_fire_cape = item_fire_cape +____exports.modifier_item_fire_cape = __TS__Class() +local modifier_item_fire_cape = ____exports.modifier_item_fire_cape +modifier_item_fire_cape.name = "modifier_item_fire_cape" +modifier_item_fire_cape.____file_path = "scripts/vscripts/items/default_items/item_fire_cape.lua" +__TS__ClassExtends(modifier_item_fire_cape, BaseModifier) +function modifier_item_fire_cape.prototype.RemoveOnDeath(self) + return false +end +function modifier_item_fire_cape.prototype.IsHidden(self) + return true +end +function modifier_item_fire_cape.prototype.IsPurgable(self) + return false +end +function modifier_item_fire_cape.prototype.GetAuraRadius(self) + return self:GetAbility():GetSpecialValueFor("radius") +end +function modifier_item_fire_cape.prototype.GetAuraSearchTeam(self) + return DOTA_UNIT_TARGET_TEAM_ENEMY +end +function modifier_item_fire_cape.prototype.GetAuraSearchType(self) + return DOTA_UNIT_TARGET_HERO + DOTA_UNIT_TARGET_BASIC +end +function modifier_item_fire_cape.prototype.GetModifierAura(self) + return "modifier_item_fire_cape_aura" +end +function modifier_item_fire_cape.prototype.IsAura(self) + return true +end +function modifier_item_fire_cape.prototype.OnCreated(self, params) + self:StartIntervalThink(1) +end +function modifier_item_fire_cape.prototype.GetTexture(self) + return "default_items/fire_cape" +end +function modifier_item_fire_cape.prototype.DeclareFunctions(self) + return { + MODIFIER_PROPERTY_ATTACKSPEED_BONUS_CONSTANT, + MODIFIER_PROPERTY_MAGICAL_RESISTANCE_BONUS, + MODIFIER_PROPERTY_PHYSICAL_ARMOR_BONUS, + MODIFIER_PROPERTY_PREATTACK_BONUS_DAMAGE, + MODIFIER_PROPERTY_MANA_REGEN_CONSTANT, + MODIFIER_PROPERTY_HEALTH_REGEN_CONSTANT + } +end +function modifier_item_fire_cape.prototype.GetModifierPreAttack_BonusDamage(self) + return self:GetAbility():GetSpecialValueFor("bonus_damage") +end +function modifier_item_fire_cape.prototype.GetModifierConstantManaRegen(self) + return self:GetAbility():GetSpecialValueFor("bonus_mana_regen") +end +function modifier_item_fire_cape.prototype.GetModifierMagicalResistanceBonus(self, event) + return self:GetAbility():GetSpecialValueFor("bonus_magic_resistance") +end +function modifier_item_fire_cape.prototype.GetModifierConstantHealthRegen(self) + return self:GetAbility():GetSpecialValueFor("bonus_health_regen") +end +function modifier_item_fire_cape.prototype.GetModifierPhysicalArmorBonus(self, event) + return self:GetAbility():GetSpecialValueFor("bonus_physical_armor") +end +function modifier_item_fire_cape.prototype.GetModifierAttackSpeedBonus_Constant(self) + return self:GetAbility():GetSpecialValueFor("bonus_attack_speed") +end +function modifier_item_fire_cape.prototype.OnIntervalThink(self) + if not IsServer() then + return + end + if not self:GetParent():IsAlive() then + return + end + local ____opt_0 = self:GetCaster() + if ____opt_0 and ____opt_0:IsIllusion() then + return + end + local caster = self:GetCaster() + local radius = self:GetAbility():GetSpecialValueFor("radius") + local damage = self:GetAbility():GetSpecialValueFor("damage") + local enemies = FindUnitsInRadius( + caster:GetTeamNumber(), + caster:GetAbsOrigin(), + nil, + radius, + DOTA_UNIT_TARGET_TEAM_ENEMY, + bit.bor(DOTA_UNIT_TARGET_HERO, DOTA_UNIT_TARGET_BASIC), + DOTA_UNIT_TARGET_FLAG_NONE, + FIND_ANY_ORDER, + false + ) + for ____, enemy in ipairs(enemies) do + local damage_table = { + victim = enemy, + attacker = caster, + damage = damage + self:GetParent():GetMaxMana() * (self:GetAbility():GetSpecialValueFor("mana_damage_pct") / 100), + damage_type = DAMAGE_TYPE_MAGICAL, + ability = self:GetAbility(), + damage_flags = DOTA_DAMAGE_FLAG_NONE + } + local modifier = enemy:AddNewModifier( + self:GetCaster(), + self:GetAbility(), + modifier_general_fired.name, + {} + ) + if modifier then + local stacksPerLevel = self:GetAbility():GetSpecialValueFor("fire_stack") + do + local i = 0 + while i < stacksPerLevel do + modifier:IncrementStackCount() + i = i + 1 + end + end + end + ApplyDamage(damage_table) + end +end +modifier_item_fire_cape = __TS__Decorate( + modifier_item_fire_cape, + modifier_item_fire_cape, + {registerModifier(nil)}, + {kind = "class", name = "modifier_item_fire_cape"} +) +____exports.modifier_item_fire_cape = modifier_item_fire_cape +____exports.modifier_item_fire_cape_aura = __TS__Class() +local modifier_item_fire_cape_aura = ____exports.modifier_item_fire_cape_aura +modifier_item_fire_cape_aura.name = "modifier_item_fire_cape_aura" +modifier_item_fire_cape_aura.____file_path = "scripts/vscripts/items/default_items/item_fire_cape.lua" +__TS__ClassExtends(modifier_item_fire_cape_aura, BaseModifier) +function modifier_item_fire_cape_aura.prototype.RemoveOnDeath(self) + return true +end +function modifier_item_fire_cape_aura.prototype.IsHidden(self) + return true +end +function modifier_item_fire_cape_aura.prototype.IsPurgable(self) + return false +end +function modifier_item_fire_cape_aura.prototype.DeclareFunctions(self) + return {MODIFIER_PROPERTY_MAGICAL_RESISTANCE_BONUS, MODIFIER_PROPERTY_SPELL_AMPLIFY_PERCENTAGE} +end +function modifier_item_fire_cape_aura.prototype.GetModifierMagicalResistanceBonus(self) + return -self:GetAbility():GetSpecialValueFor("bonus_magic_resistance") +end +function modifier_item_fire_cape_aura.prototype.GetModifierSpellAmplify_Percentage(self) + return -self:GetAbility():GetSpecialValueFor("bonus_spell_amp") +end +modifier_item_fire_cape_aura = __TS__Decorate( + modifier_item_fire_cape_aura, + modifier_item_fire_cape_aura, + {registerModifier(nil)}, + {kind = "class", name = "modifier_item_fire_cape_aura"} +) +____exports.modifier_item_fire_cape_aura = modifier_item_fire_cape_aura +return ____exports diff --git a/scripts/vscripts/items/default_items/item_fishing_rod.lua b/scripts/vscripts/items/default_items/item_fishing_rod.lua new file mode 100644 index 0000000..357d354 --- /dev/null +++ b/scripts/vscripts/items/default_items/item_fishing_rod.lua @@ -0,0 +1,615 @@ +local ____lualib = require("lualib_bundle") +local __TS__Class = ____lualib.__TS__Class +local __TS__ClassExtends = ____lualib.__TS__ClassExtends +local __TS__ArrayIncludes = ____lualib.__TS__ArrayIncludes +local __TS__Decorate = ____lualib.__TS__Decorate +local ____exports = {} +local ____fish_basic = require("abilities.fish_basic") +local modifier_fish_basic = ____fish_basic.modifier_fish_basic +local ____dota_ts_adapter = require("lib.dota_ts_adapter") +local BaseItem = ____dota_ts_adapter.BaseItem +local BaseModifier = ____dota_ts_adapter.BaseModifier +local registerAbility = ____dota_ts_adapter.registerAbility +local registerModifier = ____dota_ts_adapter.registerModifier +--- Цепь из клиента Dota 2 (кастомный .vpcf без файла в content/particles не появится в игре) +local PARTICLE_MEATHOOK = "particles/units/heroes/hero_pudge/pudge_meathook.vpcf" +--- Юнит рыбы (npc_units_custom) — при успешном вываживании удаляется, дроп летит к герою +local BOMB_UNIT_NAMES = {"npc_bomb"} +local FISH_UNIT_NAMES = {"npc_fish_1", "npc_fish_2"} +local FISH_CATCH_ITEM_DEFAULT = "item_meat" +--- У Пуджа есть attach_weapon_chain_rt; у большинства героев — attach_attack1 +local function getMeathookAttachName(self, unit) + local candidates = {"attach_attack1", "attach_attack2"} + for ____, name in ipairs(candidates) do + if unit:ScriptLookupAttachment(name) >= 0 then + return name + end + end + return "attach_hitloc" +end +____exports.item_fishing_rod = __TS__Class() +local item_fishing_rod = ____exports.item_fishing_rod +item_fishing_rod.name = "item_fishing_rod" +item_fishing_rod.____file_path = "scripts/vscripts/items/default_items/item_fishing_rod.lua" +__TS__ClassExtends(item_fishing_rod, BaseItem) +function item_fishing_rod.prototype.____constructor(self, ...) + BaseItem.prototype.____constructor(self, ...) + self.vHookOffset = Vector(0, 0, 96) + self.bRetracting = false + self.vTargetPosition = Vector(0, 0, 0) + self.vStartPosition = Vector(0, 0, 0) +end +function item_fishing_rod.prototype.GetAbilityDamageType(self) + return DAMAGE_TYPE_PHYSICAL +end +function item_fishing_rod.prototype.GetAbilityDamage(self) + return self:GetSpecialValueFor("Damage") +end +function item_fishing_rod.prototype.Precache(self, context) + PrecacheResource("particle", PARTICLE_MEATHOOK, context) + PrecacheResource("particle", "particles/units/heroes/hero_pudge/pudge_meathook_impact.vpcf", context) +end +function item_fishing_rod.prototype.GetCastRange(self, location, target) + return self:GetSpecialValueFor("hook_distance") +end +function item_fishing_rod.prototype.OnAbilityPhaseStart(self) + self:GetCaster():StartGesture(ACT_DOTA_ATTACK) + return true +end +function item_fishing_rod.prototype.OnAbilityPhaseInterrupted(self) + self:GetCaster():RemoveGesture(ACT_DOTA_ATTACK) + return true +end +function item_fishing_rod.prototype.clearHookedVictimPull(self) + if self.hookedVictimEntIndex == nil then + return + end + local ent = EntIndexToHScript(self.hookedVictimEntIndex) + self.hookedVictimEntIndex = nil + if ent and IsValidEntity(ent) and ent:IsBaseNPC() then + ent:RemoveModifierByName(____exports.modifier_item_fishing_rod_hook_pull.name) + end +end +function item_fishing_rod.prototype.releaseCasterMovementLock(self, caster) + caster:RemoveModifierByName(____exports.modifier_item_fishing_rod_caster_root.name) +end +function item_fishing_rod.prototype.getFishCatchItemName(self) + local raw = self:GetAbilityKeyValues() + local ____opt_result_4 + if raw ~= nil then + ____opt_result_4 = raw.AbilityValues + end + local ____opt_result_5 + if ____opt_result_4 ~= nil then + ____opt_result_5 = ____opt_result_4.fish_catch_item + end + local ____opt_result_5_9 = ____opt_result_5 + if ____opt_result_5_9 == nil then + local ____opt_result_8 + if raw ~= nil then + ____opt_result_8 = raw.fish_catch_item + end + ____opt_result_5_9 = ____opt_result_8 + end + local name = ____opt_result_5_9 + if type(name) == "string" and #name > 0 then + return name + end + return FISH_CATCH_ITEM_DEFAULT +end +function item_fishing_rod.prototype.trySpawnFishCatchReward(self, caster, caughtEntIndex) + if caughtEntIndex == nil then + return + end + local ent = EntIndexToHScript(caughtEntIndex) + if not ent or not IsValidEntity(ent) or not ent:IsBaseNPC() then + return + end + local unit = ent + local parent = self:GetCaster() + if not caster then + return + end + if __TS__ArrayIncludes( + BOMB_UNIT_NAMES, + unit:GetUnitName() + ) and unit:IsAlive() then + local debuffs = {{name = "modifier_bad_cast_debuff", minDuration = 10, maxDuration = 20}, {name = "modifier_blind_debuff", minDuration = 1, maxDuration = 3.5}, {name = "modifier_stunned", minDuration = 1.5, maxDuration = 3.5}} + ApplyDamage({ + victim = parent, + attacker = caster, + damage = self:GetCaster():GetMaxHealth() * 0.25, + damage_type = DAMAGE_TYPE_PURE, + ability = self + }) + local randomDebuff = debuffs[RandomInt(0, #debuffs - 1) + 1] + local duration = RandomFloat(randomDebuff.minDuration, randomDebuff.maxDuration) + caster:AddNewModifier(caster, self, randomDebuff.name, {duration = duration}) + EmitSoundOn("Hero_TemplarAssassin.Trap.Explode", parent) + unit:RemoveModifierByName(modifier_fish_basic.name) + unit:Kill(self, caster) + UTIL_Remove(unit) + return + end + if not __TS__ArrayIncludes( + FISH_UNIT_NAMES, + unit:GetUnitName() + ) or not unit:IsAlive() then + return + end + local fromPos = unit:GetAbsOrigin() + unit:RemoveModifierByName(modifier_fish_basic.name) + unit:Kill(self, caster) + UTIL_Remove(unit) + local itemName = self:getFishCatchItemName() + local launchH = self:GetSpecialValueFor("fish_loot_launch_height") + local launchDist = self:GetSpecialValueFor("fish_loot_launch_distance") + local launchDur = self:GetSpecialValueFor("fish_loot_launch_duration") + if launchH <= 0 then + launchH = 100 + end + if launchDist <= 0 then + launchDist = 150 + end + if launchDur <= 0 then + launchDur = 0.5 + end + local item = CreateItem(itemName, nil, nil) + if not item then + return + end + CreateItemOnPositionForLaunch(fromPos, item) + item:LaunchLootInitialHeight( + false, + launchH, + launchDist, + launchDur, + caster:GetAbsOrigin() + ) +end +function item_fishing_rod.prototype.OnSpellStart(self) + local caster = self:GetCaster() + self:clearHookedVictimPull() + if not caster:IsInstance(CDOTA_BaseNPC_Hero) then + return + end + local hero = caster + self:releaseCasterMovementLock(hero) + local hookSpeed = self:GetSpecialValueFor("hook_speed") + local hookDistance = self:GetCastRange( + caster:GetOrigin(), + nil + ) + local hookWidth = self:GetSpecialValueFor("hook_width") + local attachName = getMeathookAttachName(nil, caster) + local ____opt_10 = caster.clothes + local hook = ____opt_10 and ____opt_10.weapon + if not not hook then + hook:AddEffects(EF_NODRAW) + end + local casterPoint = caster:GetOrigin() + self.vStartPosition = casterPoint + local vDirection = (casterPoint == self:GetCursorPosition() and self:GetCursorPosition() + Vector(1, 1, 0) or self:GetCursorPosition()) - casterPoint + vDirection.z = 0 + if caster:HasModifier(____exports.modifier_bad_cast_debuff.name) then + local angle = RandomFloat(0, 2 * math.pi) + local randomDir = Vector( + math.cos(angle), + math.sin(angle), + 0 + ) + vDirection = randomDir * vDirection:Length2D() + end + vDirection = vDirection:Normalized() * hookDistance + self.vTargetPosition = casterPoint + vDirection + local vHookTarget = self.vTargetPosition + Vector(0, 0, 96) + local vKillswitch = Vector(hookDistance / hookSpeed * 2, 0, 0) + EmitSoundOn("Hero_Pudge.AttackHookExtend", caster) + self.nChainParticleFXIndex = ParticleManager:CreateParticle(PARTICLE_MEATHOOK, PATTACH_CUSTOMORIGIN, caster) + ParticleManager:SetParticleAlwaysSimulate(self.nChainParticleFXIndex) + ParticleManager:SetParticleControlEnt( + self.nChainParticleFXIndex, + 0, + caster, + PATTACH_POINT_FOLLOW, + attachName, + casterPoint + self.vHookOffset, + true + ) + ParticleManager:SetParticleControl(self.nChainParticleFXIndex, 1, vHookTarget) + ParticleManager:SetParticleControl( + self.nChainParticleFXIndex, + 2, + Vector(hookSpeed, hookDistance, hookWidth) + ) + ParticleManager:SetParticleControl(self.nChainParticleFXIndex, 3, vKillswitch) + ParticleManager:SetParticleControl( + self.nChainParticleFXIndex, + 4, + Vector(1, 0, 0) + ) + ParticleManager:SetParticleControl( + self.nChainParticleFXIndex, + 5, + Vector(0, 0, 0) + ) + if hook then + ParticleManager:SetParticleControlEnt( + self.nChainParticleFXIndex, + 7, + hook, + PATTACH_CUSTOMORIGIN, + nil, + hook:GetOrigin(), + true + ) + end + ProjectileManager:CreateLinearProjectile({ + Ability = self, + vSpawnOrigin = casterPoint, + vVelocity = vDirection:Normalized() * hookSpeed, + fDistance = hookDistance, + fStartRadius = hookWidth, + fEndRadius = hookWidth, + Source = caster, + iUnitTargetTeam = DOTA_UNIT_TARGET_TEAM_ENEMY, + iUnitTargetType = bit.bor(DOTA_UNIT_TARGET_HERO, DOTA_UNIT_TARGET_BASIC), + iUnitTargetFlags = DOTA_UNIT_TARGET_FLAG_MAGIC_IMMUNE_ENEMIES + }) + self.bRetracting = false + ____exports.modifier_item_fishing_rod_caster_root:apply(hero, hero, self, {}) + self:SetActivated(false) +end +function item_fishing_rod.prototype.OnProjectileHit(self, target, location) + local caster = self:GetCaster() + local attachName = getMeathookAttachName(nil, caster) + if target == caster then + return false + end + if self.bRetracting == false then + if target then + local isFish = __TS__ArrayIncludes( + FISH_UNIT_NAMES, + target:GetUnitName() + ) + EmitSoundOn("Hero_Pudge.AttackHookImpact", target) + if not isFish then + local damage = self:GetAbilityDamage() + self:GetCaster():GetAverageTrueAttackDamage(caster) + ApplyDamage({ + victim = target, + attacker = caster, + damage = damage, + damage_type = self:GetAbilityDamageType(), + ability = self + }) + end + local nFXIndex = ParticleManager:CreateParticle("particles/econ/items/pudge/pudge_ti6_immortal/pudge_meathook_impact_ti6.vpcf", PATTACH_CUSTOMORIGIN, target) + ParticleManager:SetParticleControlEnt( + nFXIndex, + 0, + target, + PATTACH_POINT_FOLLOW, + "attach_hitloc", + caster:GetOrigin(), + true + ) + AddFOWViewer( + caster:GetTeamNumber(), + target:GetOrigin(), + self:GetSpecialValueFor("vision_radius"), + self:GetSpecialValueFor("vision_duration"), + false + ) + target:InterruptChannel() + self.hookedVictimEntIndex = target:entindex() + ____exports.modifier_item_fishing_rod_hook_pull:apply(target, caster, self, {}) + if not isFish then + ____exports.modifier_item_fishing_rod_debuff:apply( + target, + caster, + self, + {duration = self:GetSpecialValueFor("duration")} + ) + end + end + local flPad = caster:GetPaddedCollisionRadius() + local vHookPos = self.vTargetPosition + if target then + vHookPos = target:GetOrigin() + flPad = flPad + target:GetPaddedCollisionRadius() + end + local vVelocity = self.vStartPosition - vHookPos + vVelocity.z = 0 + local flDistance = vVelocity:Length2D() - flPad + vVelocity = vVelocity:Normalized() * self:GetSpecialValueFor("hook_speed") + ProjectileManager:CreateLinearProjectile({ + Ability = self, + vSpawnOrigin = vHookPos, + vVelocity = vVelocity, + fDistance = flDistance, + Source = caster + }) + if target then + ParticleManager:SetParticleControlEnt( + self.nChainParticleFXIndex, + 1, + caster, + PATTACH_POINT_FOLLOW, + attachName, + target:GetOrigin() + self.vHookOffset, + true + ) + else + ParticleManager:SetParticleControlEnt( + self.nChainParticleFXIndex, + 1, + caster, + PATTACH_POINT_FOLLOW, + attachName, + caster:GetOrigin() + self.vHookOffset, + true + ) + end + StopSoundOn("Hero_Pudge.AttackHookExtend", caster) + EmitSoundOn("Hero_Pudge.AttackHookRetract", caster) + if caster:IsAlive() then + caster:FadeGesture(ACT_DOTA_ATTACK) + caster:StartGesture(ACT_DOTA_ATTACK) + end + local casterAny = caster + local ____opt_12 = casterAny.SetRageTimeIncrease + if ____opt_12 ~= nil then + ____opt_12(casterAny) + end + self.bRetracting = true + else + local caughtEntIndex = self.hookedVictimEntIndex + self:clearHookedVictimPull() + self:releaseCasterMovementLock(caster) + self:trySpawnFishCatchReward(caster, caughtEntIndex) + local ____opt_14 = caster.clothes + local w = ____opt_14 and ____opt_14.weapon + if not not w then + w:RemoveEffects(EF_NODRAW) + end + ParticleManager:DestroyParticle(self.nChainParticleFXIndex, true) + self:SetActivated(true) + EmitSoundOn("Hero_Pudge.AttackHookRetractStop", caster) + end + return true +end +item_fishing_rod = __TS__Decorate( + item_fishing_rod, + item_fishing_rod, + {registerAbility(nil)}, + {kind = "class", name = "item_fishing_rod"} +) +____exports.item_fishing_rod = item_fishing_rod +--- Кастер стоит на месте, пока крюк не вернётся (откат цепи завершён) +____exports.modifier_item_fishing_rod_caster_root = __TS__Class() +local modifier_item_fishing_rod_caster_root = ____exports.modifier_item_fishing_rod_caster_root +modifier_item_fishing_rod_caster_root.name = "modifier_item_fishing_rod_caster_root" +modifier_item_fishing_rod_caster_root.____file_path = "scripts/vscripts/items/default_items/item_fishing_rod.lua" +__TS__ClassExtends(modifier_item_fishing_rod_caster_root, BaseModifier) +function modifier_item_fishing_rod_caster_root.prototype.IsHidden(self) + return true +end +function modifier_item_fishing_rod_caster_root.prototype.IsDebuff(self) + return false +end +function modifier_item_fishing_rod_caster_root.prototype.IsPurgable(self) + return false +end +function modifier_item_fishing_rod_caster_root.prototype.RemoveOnDeath(self) + return true +end +function modifier_item_fishing_rod_caster_root.prototype.CheckState(self) + return {[MODIFIER_STATE_ROOTED] = true, [MODIFIER_STATE_SILENCED] = true} +end +modifier_item_fishing_rod_caster_root = __TS__Decorate( + modifier_item_fishing_rod_caster_root, + modifier_item_fishing_rod_caster_root, + {registerModifier(nil)}, + {kind = "class", name = "modifier_item_fishing_rod_caster_root"} +) +____exports.modifier_item_fishing_rod_caster_root = modifier_item_fishing_rod_caster_root +--- Притягивание к кастеру на скорости hook_speed (синхронно с откатом цепи) +____exports.modifier_item_fishing_rod_hook_pull = __TS__Class() +local modifier_item_fishing_rod_hook_pull = ____exports.modifier_item_fishing_rod_hook_pull +modifier_item_fishing_rod_hook_pull.name = "modifier_item_fishing_rod_hook_pull" +modifier_item_fishing_rod_hook_pull.____file_path = "scripts/vscripts/items/default_items/item_fishing_rod.lua" +__TS__ClassExtends(modifier_item_fishing_rod_hook_pull, BaseModifier) +function modifier_item_fishing_rod_hook_pull.prototype.____constructor(self, ...) + BaseModifier.prototype.____constructor(self, ...) + self.thinkInterval = 0.03 +end +function modifier_item_fishing_rod_hook_pull.prototype.IsHidden(self) + return true +end +function modifier_item_fishing_rod_hook_pull.prototype.IsDebuff(self) + return true +end +function modifier_item_fishing_rod_hook_pull.prototype.IsPurgable(self) + return false +end +function modifier_item_fishing_rod_hook_pull.prototype.CheckState(self) + return {[MODIFIER_STATE_STUNNED] = true} +end +function modifier_item_fishing_rod_hook_pull.prototype.OnCreated(self) + if not IsServer() then + return + end + self:StartIntervalThink(self.thinkInterval) +end +function modifier_item_fishing_rod_hook_pull.prototype.OnIntervalThink(self) + if not IsServer() then + return + end + local caster = self:GetCaster() + local parent = self:GetParent() + if not caster or not caster:IsAlive() or not parent or not parent:IsAlive() then + self:Destroy() + return + end + local ability = self:GetAbility() + local speed = ability and ability:GetSpecialValueFor("hook_speed") or 1600 + local step = speed * self.thinkInterval + local pos = parent:GetAbsOrigin() + local casterPos = caster:GetAbsOrigin() + local flatDelta = Vector(casterPos.x - pos.x, casterPos.y - pos.y, 0) + local dist2d = flatDelta:Length2D() + local stopDist = caster:GetPaddedCollisionRadius() + parent:GetPaddedCollisionRadius() + 32 + if dist2d <= stopDist then + FindClearSpaceForUnit( + parent, + caster:GetAbsOrigin(), + true + ) + self:Destroy() + return + end + local dir = flatDelta:Normalized() + local move = math.min(step, dist2d) + local newPos = Vector(pos.x + dir.x * move, pos.y + dir.y * move, pos.z) + parent:SetAbsOrigin(GetGroundPosition(newPos, nil)) +end +function modifier_item_fishing_rod_hook_pull.prototype.OnDestroy(self) + if not IsServer() then + return + end + local parent = self:GetParent() + if parent and IsValidEntity(parent) and parent:IsAlive() then + FindClearSpaceForUnit( + parent, + parent:GetAbsOrigin(), + true + ) + end +end +modifier_item_fishing_rod_hook_pull = __TS__Decorate( + modifier_item_fishing_rod_hook_pull, + modifier_item_fishing_rod_hook_pull, + {registerModifier(nil)}, + {kind = "class", name = "modifier_item_fishing_rod_hook_pull"} +) +____exports.modifier_item_fishing_rod_hook_pull = modifier_item_fishing_rod_hook_pull +____exports.modifier_item_fishing_rod_debuff = __TS__Class() +local modifier_item_fishing_rod_debuff = ____exports.modifier_item_fishing_rod_debuff +modifier_item_fishing_rod_debuff.name = "modifier_item_fishing_rod_debuff" +modifier_item_fishing_rod_debuff.____file_path = "scripts/vscripts/items/default_items/item_fishing_rod.lua" +__TS__ClassExtends(modifier_item_fishing_rod_debuff, BaseModifier) +function modifier_item_fishing_rod_debuff.prototype.____constructor(self, ...) + BaseModifier.prototype.____constructor(self, ...) + self.slow_movespeed = 0 + self.damage_per_tick = 0 +end +function modifier_item_fishing_rod_debuff.prototype.IsHidden(self) + return true +end +function modifier_item_fishing_rod_debuff.prototype.GetEffectName(self) + return "particles/units/heroes/hero_bloodseeker/bloodseeker_rupture.vpcf" +end +function modifier_item_fishing_rod_debuff.prototype.OnCreated(self, params) + local ability = self:GetAbility() + self.slow_movespeed = ability:GetSpecialValueFor("slow_movespeed") + self.damage_per_tick = ability:GetSpecialValueFor("damage_per_tick") + self:StartIntervalThink(ability:GetSpecialValueFor("tick")) +end +function modifier_item_fishing_rod_debuff.prototype.DeclareFunctions(self) + return {MODIFIER_PROPERTY_MOVESPEED_BONUS_PERCENTAGE} +end +function modifier_item_fishing_rod_debuff.prototype.OnIntervalThink(self) + if IsClient() then + return + end + local ability = self:GetAbility() + ApplyDamage({ + victim = self:GetParent(), + attacker = self:GetCaster(), + damage_type = ability:GetAbilityDamageType(), + damage = self.damage_per_tick, + ability = ability + }) +end +function modifier_item_fishing_rod_debuff.prototype.GetModifierMoveSpeedBonus_Percentage(self) + return self.slow_movespeed +end +modifier_item_fishing_rod_debuff = __TS__Decorate( + modifier_item_fishing_rod_debuff, + modifier_item_fishing_rod_debuff, + {registerModifier(nil)}, + {kind = "class", name = "modifier_item_fishing_rod_debuff"} +) +____exports.modifier_item_fishing_rod_debuff = modifier_item_fishing_rod_debuff +____exports.modifier_blind_debuff = __TS__Class() +local modifier_blind_debuff = ____exports.modifier_blind_debuff +modifier_blind_debuff.name = "modifier_blind_debuff" +modifier_blind_debuff.____file_path = "scripts/vscripts/items/default_items/item_fishing_rod.lua" +__TS__ClassExtends(modifier_blind_debuff, BaseModifier) +function modifier_blind_debuff.prototype.IsHidden(self) + return false +end +function modifier_blind_debuff.prototype.IsDebuff(self) + return true +end +function modifier_blind_debuff.prototype.IsPurgable(self) + return true +end +function modifier_blind_debuff.prototype.OnCreated(self, params) + local particle = ParticleManager:CreateParticleForPlayer( + "particles/fish_screen_effect.vpcf", + PATTACH_ABSORIGIN_FOLLOW, + self:GetCaster(), + self:GetCaster():GetPlayerOwner() + ) + self.particleId = particle +end +function modifier_blind_debuff.prototype.OnRefresh(self, params) + if self.particleId then + ParticleManager:DestroyParticle(self.particleId, false) + end + self:OnCreated(params) +end +function modifier_blind_debuff.prototype.OnDestroy(self) + if IsClient() then + return + end + if self.particleId then + ParticleManager:DestroyParticle(self.particleId, false) + end +end +function modifier_blind_debuff.prototype.GetTexture(self) + return "templar_assassin_trap" +end +modifier_blind_debuff = __TS__Decorate( + modifier_blind_debuff, + modifier_blind_debuff, + {registerModifier(nil)}, + {kind = "class", name = "modifier_blind_debuff"} +) +____exports.modifier_blind_debuff = modifier_blind_debuff +____exports.modifier_bad_cast_debuff = __TS__Class() +local modifier_bad_cast_debuff = ____exports.modifier_bad_cast_debuff +modifier_bad_cast_debuff.name = "modifier_bad_cast_debuff" +modifier_bad_cast_debuff.____file_path = "scripts/vscripts/items/default_items/item_fishing_rod.lua" +__TS__ClassExtends(modifier_bad_cast_debuff, BaseModifier) +function modifier_bad_cast_debuff.prototype.IsHidden(self) + return false +end +function modifier_bad_cast_debuff.prototype.IsDebuff(self) + return true +end +function modifier_bad_cast_debuff.prototype.IsPurgable(self) + return true +end +function modifier_bad_cast_debuff.prototype.GetTexture(self) + return "templar_assassin_trap" +end +modifier_bad_cast_debuff = __TS__Decorate( + modifier_bad_cast_debuff, + modifier_bad_cast_debuff, + {registerModifier(nil)}, + {kind = "class", name = "modifier_bad_cast_debuff"} +) +____exports.modifier_bad_cast_debuff = modifier_bad_cast_debuff +return ____exports diff --git a/scripts/vscripts/items/default_items/item_ice_spine.lua b/scripts/vscripts/items/default_items/item_ice_spine.lua new file mode 100644 index 0000000..ce9e76c --- /dev/null +++ b/scripts/vscripts/items/default_items/item_ice_spine.lua @@ -0,0 +1,59 @@ +local ____lualib = require("lualib_bundle") +local __TS__Class = ____lualib.__TS__Class +local __TS__ClassExtends = ____lualib.__TS__ClassExtends +local __TS__Decorate = ____lualib.__TS__Decorate +local ____exports = {} +local ____dota_ts_adapter = require("lib.dota_ts_adapter") +local BaseItem = ____dota_ts_adapter.BaseItem +local BaseModifier = ____dota_ts_adapter.BaseModifier +local registerAbility = ____dota_ts_adapter.registerAbility +local registerModifier = ____dota_ts_adapter.registerModifier +____exports.item_ice_spine = __TS__Class() +local item_ice_spine = ____exports.item_ice_spine +item_ice_spine.name = "item_ice_spine" +item_ice_spine.____file_path = "scripts/vscripts/items/default_items/item_ice_spine.lua" +__TS__ClassExtends(item_ice_spine, BaseItem) +function item_ice_spine.prototype.GetIntrinsicModifierName(self) + return "modifier_ice_spine" +end +item_ice_spine = __TS__Decorate( + item_ice_spine, + item_ice_spine, + {registerAbility(nil)}, + {kind = "class", name = "item_ice_spine"} +) +____exports.item_ice_spine = item_ice_spine +____exports.modifier_ice_spine = __TS__Class() +local modifier_ice_spine = ____exports.modifier_ice_spine +modifier_ice_spine.name = "modifier_ice_spine" +modifier_ice_spine.____file_path = "scripts/vscripts/items/default_items/item_ice_spine.lua" +__TS__ClassExtends(modifier_ice_spine, BaseModifier) +function modifier_ice_spine.prototype.IsHidden(self) + return true +end +function modifier_ice_spine.prototype.IsPurgable(self) + return false +end +function modifier_ice_spine.prototype.DeclareFunctions(self) + return {MODIFIER_PROPERTY_SPELL_AMPLIFY_PERCENTAGE, MODIFIER_PROPERTY_INCOMING_DAMAGE_PERCENTAGE, MODIFIER_PROPERTY_HEAL_AMPLIFY_PERCENTAGE_SOURCE, MODIFIER_PROPERTY_MOVESPEED_BONUS_CONSTANT} +end +function modifier_ice_spine.prototype.GetModifierSpellAmplify_Percentage(self, event) + return self:GetAbility():GetSpecialValueFor("spell_amplify") +end +function modifier_ice_spine.prototype.GetModifierHealAmplify_PercentageSource(self) + return self:GetAbility():GetSpecialValueFor("heal_amplify") +end +function modifier_ice_spine.prototype.GetModifierMoveSpeedBonus_Constant(self) + return self:GetAbility():GetSpecialValueFor("movespeed_const") +end +function modifier_ice_spine.prototype.GetModifierIncomingDamage_Percentage(self, event) + return self:GetAbility():GetSpecialValueFor("incoming_dmg_pct") +end +modifier_ice_spine = __TS__Decorate( + modifier_ice_spine, + modifier_ice_spine, + {registerModifier(nil)}, + {kind = "class", name = "modifier_ice_spine"} +) +____exports.modifier_ice_spine = modifier_ice_spine +return ____exports diff --git a/scripts/vscripts/items/default_items/item_lifesteal_custom.lua b/scripts/vscripts/items/default_items/item_lifesteal_custom.lua new file mode 100644 index 0000000..79e8292 --- /dev/null +++ b/scripts/vscripts/items/default_items/item_lifesteal_custom.lua @@ -0,0 +1,70 @@ +local ____lualib = require("lualib_bundle") +local __TS__Class = ____lualib.__TS__Class +local __TS__ClassExtends = ____lualib.__TS__ClassExtends +local __TS__Decorate = ____lualib.__TS__Decorate +local ____exports = {} +local ____dota_ts_adapter = require("lib.dota_ts_adapter") +local BaseAbility = ____dota_ts_adapter.BaseAbility +local BaseModifier = ____dota_ts_adapter.BaseModifier +local registerAbility = ____dota_ts_adapter.registerAbility +local registerModifier = ____dota_ts_adapter.registerModifier +local ____vampirism = require("utils.vampirism") +local addPhysicalVampirism = ____vampirism.addPhysicalVampirism +local reducePhysicalVampirism = ____vampirism.reducePhysicalVampirism +____exports.item_lifesteal_custom = __TS__Class() +local item_lifesteal_custom = ____exports.item_lifesteal_custom +item_lifesteal_custom.name = "item_lifesteal_custom" +item_lifesteal_custom.____file_path = "scripts/vscripts/items/default_items/item_lifesteal_custom.lua" +__TS__ClassExtends(item_lifesteal_custom, BaseAbility) +function item_lifesteal_custom.prototype.GetIntrinsicModifierName(self) + return "modifier_item_lifesteal_custom" +end +item_lifesteal_custom = __TS__Decorate( + item_lifesteal_custom, + item_lifesteal_custom, + {registerAbility(nil)}, + {kind = "class", name = "item_lifesteal_custom"} +) +____exports.item_lifesteal_custom = item_lifesteal_custom +____exports.modifier_item_lifesteal_custom = __TS__Class() +local modifier_item_lifesteal_custom = ____exports.modifier_item_lifesteal_custom +modifier_item_lifesteal_custom.name = "modifier_item_lifesteal_custom" +modifier_item_lifesteal_custom.____file_path = "scripts/vscripts/items/default_items/item_lifesteal_custom.lua" +__TS__ClassExtends(modifier_item_lifesteal_custom, BaseModifier) +function modifier_item_lifesteal_custom.prototype.IsHidden(self) + return true +end +function modifier_item_lifesteal_custom.prototype.IsPurgable(self) + return false +end +function modifier_item_lifesteal_custom.prototype.GetAttributes(self) + return MODIFIER_ATTRIBUTE_MULTIPLE +end +function modifier_item_lifesteal_custom.prototype.OnCreated(self) + if not IsServer() then + return + end + local hero = self:GetParent() + local lifesteal = self:GetAbility():GetSpecialValueFor("lifesteal") + if lifesteal > 0 then + addPhysicalVampirism(nil, hero, lifesteal) + end +end +function modifier_item_lifesteal_custom.prototype.OnDestroy(self) + if not IsServer() then + return + end + local hero = self:GetParent() + local lifesteal = self:GetAbility():GetSpecialValueFor("lifesteal") + if lifesteal > 0 then + reducePhysicalVampirism(nil, hero, lifesteal) + end +end +modifier_item_lifesteal_custom = __TS__Decorate( + modifier_item_lifesteal_custom, + modifier_item_lifesteal_custom, + {registerModifier(nil)}, + {kind = "class", name = "modifier_item_lifesteal_custom"} +) +____exports.modifier_item_lifesteal_custom = modifier_item_lifesteal_custom +return ____exports diff --git a/scripts/vscripts/items/default_items/item_magic_rapier.lua b/scripts/vscripts/items/default_items/item_magic_rapier.lua new file mode 100644 index 0000000..0080920 --- /dev/null +++ b/scripts/vscripts/items/default_items/item_magic_rapier.lua @@ -0,0 +1,223 @@ +local ____lualib = require("lualib_bundle") +local __TS__Class = ____lualib.__TS__Class +local __TS__ClassExtends = ____lualib.__TS__ClassExtends +local __TS__Decorate = ____lualib.__TS__Decorate +local ____exports = {} +local ____dota_ts_adapter = require("lib.dota_ts_adapter") +local BaseItem = ____dota_ts_adapter.BaseItem +local BaseModifier = ____dota_ts_adapter.BaseModifier +local registerAbility = ____dota_ts_adapter.registerAbility +local registerModifier = ____dota_ts_adapter.registerModifier +____exports.item_magic_rapier = __TS__Class() +local item_magic_rapier = ____exports.item_magic_rapier +item_magic_rapier.name = "item_magic_rapier" +item_magic_rapier.____file_path = "scripts/vscripts/items/default_items/item_magic_rapier.lua" +__TS__ClassExtends(item_magic_rapier, BaseItem) +function item_magic_rapier.prototype.GetIntrinsicModifierName(self) + return "modifier_magic_rapier" +end +item_magic_rapier = __TS__Decorate( + item_magic_rapier, + item_magic_rapier, + {registerAbility(nil)}, + {kind = "class", name = "item_magic_rapier"} +) +____exports.item_magic_rapier = item_magic_rapier +____exports.modifier_magic_rapier = __TS__Class() +local modifier_magic_rapier = ____exports.modifier_magic_rapier +modifier_magic_rapier.name = "modifier_magic_rapier" +modifier_magic_rapier.____file_path = "scripts/vscripts/items/default_items/item_magic_rapier.lua" +__TS__ClassExtends(modifier_magic_rapier, BaseModifier) +function modifier_magic_rapier.prototype.____constructor(self, ...) + BaseModifier.prototype.____constructor(self, ...) + self.Lock = false +end +function modifier_magic_rapier.prototype.IsHidden(self) + return true +end +function modifier_magic_rapier.prototype.IsDebuff(self) + return false +end +function modifier_magic_rapier.prototype.IsPurgable(self) + return false +end +function modifier_magic_rapier.prototype.DeclareFunctions(self) + return { + MODIFIER_PROPERTY_SPELL_AMPLIFY_PERCENTAGE, + MODIFIER_PROPERTY_STATS_INTELLECT_BONUS, + MODIFIER_PROPERTY_STATS_STRENGTH_BONUS, + MODIFIER_PROPERTY_STATS_AGILITY_BONUS, + MODIFIER_PROPERTY_MANA_REGEN_CONSTANT, + MODIFIER_PROPERTY_MANA_BONUS, + MODIFIER_PROPERTY_FORCE_MAX_MANA, + MODIFIER_EVENT_ON_TAKEDAMAGE + } +end +function modifier_magic_rapier.prototype.GetModifierBonusStats_Intellect(self) + return self:GetAbility():GetSpecialValueFor("bonus_intellect") +end +function modifier_magic_rapier.prototype.GetModifierSpellAmplify_Percentage(self, event) + return self:GetAbility():GetSpecialValueFor("bonus_spell_amplify") +end +function modifier_magic_rapier.prototype.GetModifierBonusStats_Strength(self) + return self:GetAbility():GetSpecialValueFor("bonus_strength") +end +function modifier_magic_rapier.prototype.GetModifierBonusStats_Agility(self) + return self:GetAbility():GetSpecialValueFor("bonus_agility") +end +function modifier_magic_rapier.prototype.GetModifierConstantManaRegen(self) + return self:GetAbility():GetSpecialValueFor("bonus_mana_regen") +end +function modifier_magic_rapier.prototype.GetModifierManaBonus(self) + if not IsServer() then + return 0 + end + if self.Lock then + return 0 + end + self.Lock = true + local mana = self:GetParent():GetMaxMana() + self.Lock = false + local bonus = self:GetAbility():GetSpecialValueFor("bonus_mana_pct") * mana / 100 + return bonus +end +function modifier_magic_rapier.prototype.OnTakeDamage(self, event) + if not IsServer() then + return + end + local caster = self:GetParent() + local rapierAbility = self:GetAbility() + if not caster:IsRealHero() or not rapierAbility then + return + end + local target = event.unit + if not target then + return + end + if target:GetTeamNumber() == caster:GetTeamNumber() then + return + end + if event.attacker ~= caster then + return + end + if not event.inflictor then + return + end + if event.inflictor == rapierAbility then + return + end + if event.damage <= 0 then + return + end + if event.damage <= 300 then + return + end + local bonusDamage = caster:GetMana() * (rapierAbility:GetSpecialValueFor("damage_per_cast_hand") / 100) + rapierAbility:GetSpecialValueFor("damage_per_cast") + local spellAmpFrac = math.max( + 0, + caster:GetSpellAmplification(false) + ) + local damageToApply = bonusDamage / math.max(0.000001, 1 + spellAmpFrac) + SendOverheadEventMessage( + nil, + OVERHEAD_ALERT_BONUS_SPELL_DAMAGE, + target, + math.floor(bonusDamage), + nil + ) + rapierAbility:UseResources(true, false, false, true) + ApplyDamage({ + victim = target, + attacker = caster, + damage = damageToApply, + damage_type = DAMAGE_TYPE_MAGICAL, + damage_flags = DOTA_DAMAGE_FLAG_NO_SPELL_AMPLIFICATION, + ability = nil + }) + target:AddNewModifier( + caster, + rapierAbility, + "modifier_magic_rapier_debuff", + {duration = rapierAbility:GetSpecialValueFor("slow_duration")} + ) + local particle = ParticleManager:CreateParticle("particles/items_fx/phylactery_target.vpcf", PATTACH_ABSORIGIN_FOLLOW, target) + ParticleManager:SetParticleControlEnt( + particle, + 0, + target, + PATTACH_POINT_FOLLOW, + "attach_hitloc", + target:GetAbsOrigin(), + true + ) + ParticleManager:ReleaseParticleIndex(particle) + local particle_2 = ParticleManager:CreateParticle("particles/items_fx/phylactery.vpcf", PATTACH_ABSORIGIN_FOLLOW, caster) + ParticleManager:SetParticleControlEnt( + particle_2, + 0, + caster, + PATTACH_POINT_FOLLOW, + "attach_hitloc", + caster:GetAbsOrigin(), + true + ) + ParticleManager:SetParticleControlEnt( + particle_2, + 1, + target, + PATTACH_POINT_FOLLOW, + "attach_hitloc", + target:GetAbsOrigin(), + true + ) + ParticleManager:ReleaseParticleIndex(particle_2) + target:EmitSound("Item.Phylactery.Target") +end +modifier_magic_rapier = __TS__Decorate( + modifier_magic_rapier, + modifier_magic_rapier, + {registerModifier(nil)}, + {kind = "class", name = "modifier_magic_rapier"} +) +____exports.modifier_magic_rapier = modifier_magic_rapier +____exports.modifier_magic_rapier_debuff = __TS__Class() +local modifier_magic_rapier_debuff = ____exports.modifier_magic_rapier_debuff +modifier_magic_rapier_debuff.name = "modifier_magic_rapier_debuff" +modifier_magic_rapier_debuff.____file_path = "scripts/vscripts/items/default_items/item_magic_rapier.lua" +__TS__ClassExtends(modifier_magic_rapier_debuff, BaseModifier) +function modifier_magic_rapier_debuff.prototype.IsHidden(self) + return false +end +function modifier_magic_rapier_debuff.prototype.IsDebuff(self) + return true +end +function modifier_magic_rapier_debuff.prototype.IsPurgable(self) + return true +end +function modifier_magic_rapier_debuff.prototype.GetAttributes(self) + return MODIFIER_ATTRIBUTE_MULTIPLE +end +function modifier_magic_rapier_debuff.prototype.DeclareFunctions(self) + return {MODIFIER_PROPERTY_MOVESPEED_BONUS_PERCENTAGE, MODIFIER_PROPERTY_MAGICAL_RESISTANCE_BONUS} +end +function modifier_magic_rapier_debuff.prototype.CheckState(self) + local state = {[MODIFIER_STATE_PASSIVES_DISABLED] = true} + return state +end +function modifier_magic_rapier_debuff.prototype.GetModifierMoveSpeedBonus_Percentage(self) + return -self:GetAbility():GetSpecialValueFor("slow_pct") +end +function modifier_magic_rapier_debuff.prototype.GetModifierMagicalResistanceBonus(self, event) + return self:GetAbility():GetSpecialValueFor("magical_resist_reduction") +end +function modifier_magic_rapier_debuff.prototype.GetTexture(self) + return "default_items/magicpier" +end +modifier_magic_rapier_debuff = __TS__Decorate( + modifier_magic_rapier_debuff, + modifier_magic_rapier_debuff, + {registerModifier(nil)}, + {kind = "class", name = "modifier_magic_rapier_debuff"} +) +____exports.modifier_magic_rapier_debuff = modifier_magic_rapier_debuff +return ____exports diff --git a/scripts/vscripts/items/default_items/item_magic_stone.lua b/scripts/vscripts/items/default_items/item_magic_stone.lua new file mode 100644 index 0000000..212b8a3 --- /dev/null +++ b/scripts/vscripts/items/default_items/item_magic_stone.lua @@ -0,0 +1,93 @@ +local ____lualib = require("lualib_bundle") +local __TS__Class = ____lualib.__TS__Class +local __TS__ClassExtends = ____lualib.__TS__ClassExtends +local __TS__Decorate = ____lualib.__TS__Decorate +local ____exports = {} +local ____dota_ts_adapter = require("lib.dota_ts_adapter") +local BaseItem = ____dota_ts_adapter.BaseItem +local BaseModifier = ____dota_ts_adapter.BaseModifier +local registerAbility = ____dota_ts_adapter.registerAbility +local registerModifier = ____dota_ts_adapter.registerModifier +____exports.item_magic_stone = __TS__Class() +local item_magic_stone = ____exports.item_magic_stone +item_magic_stone.name = "item_magic_stone" +item_magic_stone.____file_path = "scripts/vscripts/items/default_items/item_magic_stone.lua" +__TS__ClassExtends(item_magic_stone, BaseItem) +function item_magic_stone.prototype.GetIntrinsicModifierName(self) + return "modifier_magic_stone" +end +item_magic_stone = __TS__Decorate( + item_magic_stone, + item_magic_stone, + {registerAbility(nil)}, + {kind = "class", name = "item_magic_stone"} +) +____exports.item_magic_stone = item_magic_stone +____exports.modifier_magic_stone = __TS__Class() +local modifier_magic_stone = ____exports.modifier_magic_stone +modifier_magic_stone.name = "modifier_magic_stone" +modifier_magic_stone.____file_path = "scripts/vscripts/items/default_items/item_magic_stone.lua" +__TS__ClassExtends(modifier_magic_stone, BaseModifier) +function modifier_magic_stone.prototype.____constructor(self, ...) + BaseModifier.prototype.____constructor(self, ...) + self.Lock = false +end +function modifier_magic_stone.prototype.IsHidden(self) + return true +end +function modifier_magic_stone.prototype.IsDebuff(self) + return false +end +function modifier_magic_stone.prototype.IsPurgable(self) + return false +end +function modifier_magic_stone.prototype.GetAttributes(self) + return MODIFIER_ATTRIBUTE_MULTIPLE +end +function modifier_magic_stone.prototype.DeclareFunctions(self) + return { + MODIFIER_PROPERTY_SPELL_AMPLIFY_PERCENTAGE, + MODIFIER_PROPERTY_STATS_INTELLECT_BONUS, + MODIFIER_PROPERTY_STATS_STRENGTH_BONUS, + MODIFIER_PROPERTY_STATS_AGILITY_BONUS, + MODIFIER_PROPERTY_MANA_REGEN_CONSTANT, + MODIFIER_PROPERTY_MANA_BONUS, + MODIFIER_PROPERTY_FORCE_MAX_MANA + } +end +function modifier_magic_stone.prototype.GetModifierBonusStats_Intellect(self) + return self:GetAbility():GetSpecialValueFor("bonus_intellect") +end +function modifier_magic_stone.prototype.GetModifierSpellAmplify_Percentage(self, event) + return self:GetAbility():GetSpecialValueFor("bonus_spell_amplify") +end +function modifier_magic_stone.prototype.GetModifierBonusStats_Strength(self) + return self:GetAbility():GetSpecialValueFor("bonus_strength") +end +function modifier_magic_stone.prototype.GetModifierBonusStats_Agility(self) + return self:GetAbility():GetSpecialValueFor("bonus_agility") +end +function modifier_magic_stone.prototype.GetModifierConstantManaRegen(self) + return self:GetAbility():GetSpecialValueFor("bonus_mana_regen") +end +function modifier_magic_stone.prototype.GetModifierManaBonus(self) + if not IsServer() then + return 0 + end + if self.Lock then + return 0 + end + self.Lock = true + local mana = self:GetParent():GetMaxMana() + self.Lock = false + local bonus = self:GetAbility():GetSpecialValueFor("bonus_mana_pct") * mana / 100 + return bonus +end +modifier_magic_stone = __TS__Decorate( + modifier_magic_stone, + modifier_magic_stone, + {registerModifier(nil)}, + {kind = "class", name = "modifier_magic_stone"} +) +____exports.modifier_magic_stone = modifier_magic_stone +return ____exports diff --git a/scripts/vscripts/items/default_items/item_magical_crit.lua b/scripts/vscripts/items/default_items/item_magical_crit.lua new file mode 100644 index 0000000..9505aee --- /dev/null +++ b/scripts/vscripts/items/default_items/item_magical_crit.lua @@ -0,0 +1,148 @@ +local ____lualib = require("lualib_bundle") +local __TS__Class = ____lualib.__TS__Class +local __TS__ClassExtends = ____lualib.__TS__ClassExtends +local __TS__Decorate = ____lualib.__TS__Decorate +local ____exports = {} +local ____ability_stacking_spell_crit = require("abilities.modifiers.ability_stacking_spell_crit") +local modifier_stacking_spell_crit = ____ability_stacking_spell_crit.modifier_stacking_spell_crit +local ____dota_ts_adapter = require("lib.dota_ts_adapter") +local BaseItem = ____dota_ts_adapter.BaseItem +local BaseModifier = ____dota_ts_adapter.BaseModifier +local registerAbility = ____dota_ts_adapter.registerAbility +local registerModifier = ____dota_ts_adapter.registerModifier +local ____vampirism = require("utils.vampirism") +local addMagicalVampirism = ____vampirism.addMagicalVampirism +local reduceMagicalVampirism = ____vampirism.reduceMagicalVampirism +____exports.item_magical_crit = __TS__Class() +local item_magical_crit = ____exports.item_magical_crit +item_magical_crit.name = "item_magical_crit" +item_magical_crit.____file_path = "scripts/vscripts/items/default_items/item_magical_crit.lua" +__TS__ClassExtends(item_magical_crit, BaseItem) +function item_magical_crit.prototype.GetIntrinsicModifierName(self) + return "modifier_item_magical_crit" +end +item_magical_crit = __TS__Decorate( + item_magical_crit, + item_magical_crit, + {registerAbility(nil)}, + {kind = "class", name = "item_magical_crit"} +) +____exports.item_magical_crit = item_magical_crit +____exports.modifier_item_magical_crit = __TS__Class() +local modifier_item_magical_crit = ____exports.modifier_item_magical_crit +modifier_item_magical_crit.name = "modifier_item_magical_crit" +modifier_item_magical_crit.____file_path = "scripts/vscripts/items/default_items/item_magical_crit.lua" +__TS__ClassExtends(modifier_item_magical_crit, BaseModifier) +function modifier_item_magical_crit.prototype.____constructor(self, ...) + BaseModifier.prototype.____constructor(self, ...) + self.manaLock = false +end +function modifier_item_magical_crit.prototype.IsHidden(self) + return true +end +function modifier_item_magical_crit.prototype.IsDebuff(self) + return false +end +function modifier_item_magical_crit.prototype.IsPurgable(self) + return false +end +function modifier_item_magical_crit.prototype.GetAttributes(self) + return MODIFIER_ATTRIBUTE_MULTIPLE +end +function modifier_item_magical_crit.prototype.DeclareFunctions(self) + return { + MODIFIER_PROPERTY_SPELL_AMPLIFY_PERCENTAGE, + MODIFIER_PROPERTY_STATS_INTELLECT_BONUS, + MODIFIER_PROPERTY_STATS_STRENGTH_BONUS, + MODIFIER_PROPERTY_STATS_AGILITY_BONUS, + MODIFIER_PROPERTY_MANA_REGEN_CONSTANT, + MODIFIER_PROPERTY_MANA_BONUS, + MODIFIER_PROPERTY_FORCE_MAX_MANA + } +end +function modifier_item_magical_crit.prototype.getValue(self, name) + local ____opt_0 = self:GetAbility() + return ____opt_0 and ____opt_0:GetSpecialValueFor(name) or 0 +end +function modifier_item_magical_crit.prototype.GetModifierSpellAmplify_Percentage(self) + return self:getValue("bonus_spell_amplify") +end +function modifier_item_magical_crit.prototype.GetModifierBonusStats_Intellect(self) + return self:getValue("bonus_intellect") +end +function modifier_item_magical_crit.prototype.GetModifierBonusStats_Strength(self) + return self:getValue("bonus_strength") +end +function modifier_item_magical_crit.prototype.GetModifierBonusStats_Agility(self) + return self:getValue("bonus_agility") +end +function modifier_item_magical_crit.prototype.GetModifierConstantManaRegen(self) + return self:getValue("bonus_mana_regen") +end +function modifier_item_magical_crit.prototype.GetModifierManaBonus(self) + if not IsServer() then + return 0 + end + if self.manaLock then + return 0 + end + local parent = self:GetParent() + if not parent:IsHero() then + return 0 + end + self.manaLock = true + local maxMana = parent:GetMaxMana() + self.manaLock = false + return self:getValue("bonus_mana_pct") * maxMana / 100 +end +function modifier_item_magical_crit.prototype.OnCreated(self) + if not IsServer() then + return + end + local parent = self:GetParent() + local ability = self:GetAbility() + if not ability then + return + end + local vamp = self:getValue("magical_vampirism") + if vamp > 0 and parent:IsRealHero() then + addMagicalVampirism(nil, parent, vamp) + end + local stackingSpellCrit = modifier_stacking_spell_crit:GetForUnit(parent) + if stackingSpellCrit then + stackingSpellCrit:AddCustomCrit( + self:getValue("spell_crit_chance"), + self:getValue("spell_crit_mult"), + "item_magical_crit", + ability + ) + end +end +function modifier_item_magical_crit.prototype.OnDestroy(self) + if IsClient() then + return + end + local parent = self:GetParent() + if not parent or not IsValidEntity(parent) then + return + end + local ability = self:GetAbility() + if ability and parent:IsRealHero() then + local vamp = ability:GetSpecialValueFor("magical_vampirism") + if vamp > 0 then + reduceMagicalVampirism(nil, parent, vamp) + end + end + local stackingSpellCrit = modifier_stacking_spell_crit:GetForUnit(parent) + if stackingSpellCrit and ability then + stackingSpellCrit:RemoveCrit("item_magical_crit", ability) + end +end +modifier_item_magical_crit = __TS__Decorate( + modifier_item_magical_crit, + modifier_item_magical_crit, + {registerModifier(nil)}, + {kind = "class", name = "modifier_item_magical_crit"} +) +____exports.modifier_item_magical_crit = modifier_item_magical_crit +return ____exports diff --git a/scripts/vscripts/items/default_items/item_magical_divine_rapier.lua b/scripts/vscripts/items/default_items/item_magical_divine_rapier.lua new file mode 100644 index 0000000..1aeb480 --- /dev/null +++ b/scripts/vscripts/items/default_items/item_magical_divine_rapier.lua @@ -0,0 +1,223 @@ +local ____lualib = require("lualib_bundle") +local __TS__Class = ____lualib.__TS__Class +local __TS__ClassExtends = ____lualib.__TS__ClassExtends +local __TS__Decorate = ____lualib.__TS__Decorate +local ____exports = {} +local ____dota_ts_adapter = require("lib.dota_ts_adapter") +local BaseItem = ____dota_ts_adapter.BaseItem +local BaseModifier = ____dota_ts_adapter.BaseModifier +local registerAbility = ____dota_ts_adapter.registerAbility +local registerModifier = ____dota_ts_adapter.registerModifier +____exports.item_magical_divine_rapier = __TS__Class() +local item_magical_divine_rapier = ____exports.item_magical_divine_rapier +item_magical_divine_rapier.name = "item_magical_divine_rapier" +item_magical_divine_rapier.____file_path = "scripts/vscripts/items/default_items/item_magical_divine_rapier.lua" +__TS__ClassExtends(item_magical_divine_rapier, BaseItem) +function item_magical_divine_rapier.prototype.GetIntrinsicModifierName(self) + return "modifier_magical_divine_rapier" +end +item_magical_divine_rapier = __TS__Decorate( + item_magical_divine_rapier, + item_magical_divine_rapier, + {registerAbility(nil)}, + {kind = "class", name = "item_magical_divine_rapier"} +) +____exports.item_magical_divine_rapier = item_magical_divine_rapier +____exports.modifier_magical_divine_rapier = __TS__Class() +local modifier_magical_divine_rapier = ____exports.modifier_magical_divine_rapier +modifier_magical_divine_rapier.name = "modifier_magical_divine_rapier" +modifier_magical_divine_rapier.____file_path = "scripts/vscripts/items/default_items/item_magical_divine_rapier.lua" +__TS__ClassExtends(modifier_magical_divine_rapier, BaseModifier) +function modifier_magical_divine_rapier.prototype.____constructor(self, ...) + BaseModifier.prototype.____constructor(self, ...) + self.Lock = false +end +function modifier_magical_divine_rapier.prototype.IsHidden(self) + return true +end +function modifier_magical_divine_rapier.prototype.IsDebuff(self) + return false +end +function modifier_magical_divine_rapier.prototype.IsPurgable(self) + return false +end +function modifier_magical_divine_rapier.prototype.DeclareFunctions(self) + return { + MODIFIER_PROPERTY_SPELL_AMPLIFY_PERCENTAGE, + MODIFIER_PROPERTY_STATS_INTELLECT_BONUS, + MODIFIER_PROPERTY_STATS_STRENGTH_BONUS, + MODIFIER_PROPERTY_STATS_AGILITY_BONUS, + MODIFIER_PROPERTY_MANA_REGEN_CONSTANT, + MODIFIER_PROPERTY_MANA_BONUS, + MODIFIER_PROPERTY_FORCE_MAX_MANA, + MODIFIER_EVENT_ON_TAKEDAMAGE + } +end +function modifier_magical_divine_rapier.prototype.GetModifierBonusStats_Intellect(self) + return self:GetAbility():GetSpecialValueFor("bonus_intellect") +end +function modifier_magical_divine_rapier.prototype.GetModifierSpellAmplify_Percentage(self, event) + return self:GetAbility():GetSpecialValueFor("bonus_spell_amplify") +end +function modifier_magical_divine_rapier.prototype.GetModifierBonusStats_Strength(self) + return self:GetAbility():GetSpecialValueFor("bonus_strength") +end +function modifier_magical_divine_rapier.prototype.GetModifierBonusStats_Agility(self) + return self:GetAbility():GetSpecialValueFor("bonus_agility") +end +function modifier_magical_divine_rapier.prototype.GetModifierConstantManaRegen(self) + return self:GetAbility():GetSpecialValueFor("bonus_mana_regen") +end +function modifier_magical_divine_rapier.prototype.GetModifierManaBonus(self) + if not IsServer() then + return 0 + end + if self.Lock then + return 0 + end + self.Lock = true + local mana = self:GetParent():GetMaxMana() + self.Lock = false + local bonus = self:GetAbility():GetSpecialValueFor("bonus_mana_pct") * mana / 100 + return bonus +end +function modifier_magical_divine_rapier.prototype.OnTakeDamage(self, event) + if not IsServer() then + return + end + local caster = self:GetParent() + local rapierAbility = self:GetAbility() + if not caster:IsRealHero() or not rapierAbility then + return + end + local target = event.unit + if not target then + return + end + if target:GetTeamNumber() == caster:GetTeamNumber() then + return + end + if event.attacker ~= caster then + return + end + if not event.inflictor then + return + end + if event.inflictor == rapierAbility then + return + end + if event.damage <= 0 then + return + end + if event.damage <= 300 then + return + end + local bonusDamage = caster:GetMana() * (rapierAbility:GetSpecialValueFor("damage_per_cast_hand") / 100) + rapierAbility:GetSpecialValueFor("damage_per_cast") + local spellAmpFrac = math.max( + 0, + caster:GetSpellAmplification(false) + ) + local damageToApply = bonusDamage / math.max(0.000001, 1 + spellAmpFrac) + SendOverheadEventMessage( + nil, + OVERHEAD_ALERT_BONUS_SPELL_DAMAGE, + target, + math.floor(bonusDamage), + nil + ) + rapierAbility:UseResources(true, false, false, true) + ApplyDamage({ + victim = target, + attacker = caster, + damage = damageToApply, + damage_type = DAMAGE_TYPE_MAGICAL, + damage_flags = DOTA_DAMAGE_FLAG_NO_SPELL_AMPLIFICATION, + ability = nil + }) + target:AddNewModifier( + caster, + rapierAbility, + "modifier_magic_rapier_debuff", + {duration = rapierAbility:GetSpecialValueFor("slow_duration")} + ) + local particle = ParticleManager:CreateParticle("particles/items_fx/phylactery_target.vpcf", PATTACH_ABSORIGIN_FOLLOW, target) + ParticleManager:SetParticleControlEnt( + particle, + 0, + target, + PATTACH_POINT_FOLLOW, + "attach_hitloc", + target:GetAbsOrigin(), + true + ) + ParticleManager:ReleaseParticleIndex(particle) + local particle_2 = ParticleManager:CreateParticle("particles/items_fx/phylactery.vpcf", PATTACH_ABSORIGIN_FOLLOW, caster) + ParticleManager:SetParticleControlEnt( + particle_2, + 0, + caster, + PATTACH_POINT_FOLLOW, + "attach_hitloc", + caster:GetAbsOrigin(), + true + ) + ParticleManager:SetParticleControlEnt( + particle_2, + 1, + target, + PATTACH_POINT_FOLLOW, + "attach_hitloc", + target:GetAbsOrigin(), + true + ) + ParticleManager:ReleaseParticleIndex(particle_2) + target:EmitSound("Item.Phylactery.Target") +end +modifier_magical_divine_rapier = __TS__Decorate( + modifier_magical_divine_rapier, + modifier_magical_divine_rapier, + {registerModifier(nil)}, + {kind = "class", name = "modifier_magical_divine_rapier"} +) +____exports.modifier_magical_divine_rapier = modifier_magical_divine_rapier +____exports.modifier_magic_rapier_debuff = __TS__Class() +local modifier_magic_rapier_debuff = ____exports.modifier_magic_rapier_debuff +modifier_magic_rapier_debuff.name = "modifier_magic_rapier_debuff" +modifier_magic_rapier_debuff.____file_path = "scripts/vscripts/items/default_items/item_magical_divine_rapier.lua" +__TS__ClassExtends(modifier_magic_rapier_debuff, BaseModifier) +function modifier_magic_rapier_debuff.prototype.IsHidden(self) + return false +end +function modifier_magic_rapier_debuff.prototype.IsDebuff(self) + return true +end +function modifier_magic_rapier_debuff.prototype.IsPurgable(self) + return true +end +function modifier_magic_rapier_debuff.prototype.GetAttributes(self) + return MODIFIER_ATTRIBUTE_MULTIPLE +end +function modifier_magic_rapier_debuff.prototype.DeclareFunctions(self) + return {MODIFIER_PROPERTY_MOVESPEED_BONUS_PERCENTAGE, MODIFIER_PROPERTY_MAGICAL_RESISTANCE_BONUS} +end +function modifier_magic_rapier_debuff.prototype.CheckState(self) + local state = {[MODIFIER_STATE_PASSIVES_DISABLED] = true} + return state +end +function modifier_magic_rapier_debuff.prototype.GetModifierMoveSpeedBonus_Percentage(self) + return -self:GetAbility():GetSpecialValueFor("slow_pct") +end +function modifier_magic_rapier_debuff.prototype.GetModifierMagicalResistanceBonus(self, event) + return self:GetAbility():GetSpecialValueFor("magical_resist_reduction") +end +function modifier_magic_rapier_debuff.prototype.GetTexture(self) + return "default_items/magicpier" +end +modifier_magic_rapier_debuff = __TS__Decorate( + modifier_magic_rapier_debuff, + modifier_magic_rapier_debuff, + {registerModifier(nil)}, + {kind = "class", name = "modifier_magic_rapier_debuff"} +) +____exports.modifier_magic_rapier_debuff = modifier_magic_rapier_debuff +return ____exports diff --git a/scripts/vscripts/items/default_items/item_magical_quiver.lua b/scripts/vscripts/items/default_items/item_magical_quiver.lua new file mode 100644 index 0000000..bb7df4a --- /dev/null +++ b/scripts/vscripts/items/default_items/item_magical_quiver.lua @@ -0,0 +1,61 @@ +local ____lualib = require("lualib_bundle") +local __TS__Class = ____lualib.__TS__Class +local __TS__ClassExtends = ____lualib.__TS__ClassExtends +local __TS__Decorate = ____lualib.__TS__Decorate +local ____exports = {} +local ____dota_ts_adapter = require("lib.dota_ts_adapter") +local BaseItem = ____dota_ts_adapter.BaseItem +local registerAbility = ____dota_ts_adapter.registerAbility +local registerModifier = ____dota_ts_adapter.registerModifier +local BaseModifier = ____dota_ts_adapter.BaseModifier +____exports.item_magical_quiver = __TS__Class() +local item_magical_quiver = ____exports.item_magical_quiver +item_magical_quiver.name = "item_magical_quiver" +item_magical_quiver.____file_path = "scripts/vscripts/items/default_items/item_magical_quiver.lua" +__TS__ClassExtends(item_magical_quiver, BaseItem) +function item_magical_quiver.prototype.GetIntrinsicModifierName(self) + return "modifier_item_magical_quiver" +end +item_magical_quiver = __TS__Decorate( + item_magical_quiver, + item_magical_quiver, + {registerAbility(nil)}, + {kind = "class", name = "item_magical_quiver"} +) +____exports.item_magical_quiver = item_magical_quiver +____exports.modifier_item_magical_quiver = __TS__Class() +local modifier_item_magical_quiver = ____exports.modifier_item_magical_quiver +modifier_item_magical_quiver.name = "modifier_item_magical_quiver" +modifier_item_magical_quiver.____file_path = "scripts/vscripts/items/default_items/item_magical_quiver.lua" +__TS__ClassExtends(modifier_item_magical_quiver, BaseModifier) +function modifier_item_magical_quiver.prototype.IsHidden(self) + return true +end +function modifier_item_magical_quiver.prototype.IsDebuff(self) + return false +end +function modifier_item_magical_quiver.prototype.IsPurgable(self) + return false +end +function modifier_item_magical_quiver.prototype.OnCreated(self) + if not IsServer() then + return + end +end +function modifier_item_magical_quiver.prototype.DeclareFunctions(self) + return {MODIFIER_PROPERTY_PROCATTACK_BONUS_DAMAGE_MAGICAL} +end +function modifier_item_magical_quiver.prototype.GetModifierProcAttack_BonusDamage_Magical(self, event) + if self:GetParent():IsIllusion() or not self:GetParent():IsRangedAttacker() then + return 0 + end + return self:GetAbility():GetSpecialValueFor("proc_damage_magical") +end +modifier_item_magical_quiver = __TS__Decorate( + modifier_item_magical_quiver, + modifier_item_magical_quiver, + {registerModifier(nil)}, + {kind = "class", name = "modifier_item_magical_quiver"} +) +____exports.modifier_item_magical_quiver = modifier_item_magical_quiver +return ____exports diff --git a/scripts/vscripts/items/default_items/item_mask_of_madness_custom.lua b/scripts/vscripts/items/default_items/item_mask_of_madness_custom.lua new file mode 100644 index 0000000..c4041ec --- /dev/null +++ b/scripts/vscripts/items/default_items/item_mask_of_madness_custom.lua @@ -0,0 +1,135 @@ +local ____lualib = require("lualib_bundle") +local __TS__Class = ____lualib.__TS__Class +local __TS__ClassExtends = ____lualib.__TS__ClassExtends +local __TS__Decorate = ____lualib.__TS__Decorate +local ____exports = {} +local ____dota_ts_adapter = require("lib.dota_ts_adapter") +local BaseAbility = ____dota_ts_adapter.BaseAbility +local BaseModifier = ____dota_ts_adapter.BaseModifier +local registerAbility = ____dota_ts_adapter.registerAbility +local registerModifier = ____dota_ts_adapter.registerModifier +local ____vampirism = require("utils.vampirism") +local addPhysicalVampirism = ____vampirism.addPhysicalVampirism +local reducePhysicalVampirism = ____vampirism.reducePhysicalVampirism +____exports.item_mask_of_madness_custom = __TS__Class() +local item_mask_of_madness_custom = ____exports.item_mask_of_madness_custom +item_mask_of_madness_custom.name = "item_mask_of_madness_custom" +item_mask_of_madness_custom.____file_path = "scripts/vscripts/items/default_items/item_mask_of_madness_custom.lua" +__TS__ClassExtends(item_mask_of_madness_custom, BaseAbility) +function item_mask_of_madness_custom.prototype.GetIntrinsicModifierName(self) + return "modifier_item_mask_of_madness_custom" +end +function item_mask_of_madness_custom.prototype.OnSpellStart(self) + self:GetCaster():AddNewModifier( + self:GetCaster(), + self, + "modifier_item_mask_of_madness_custom_buff", + {duration = self:GetSpecialValueFor("duration")} + ) +end +item_mask_of_madness_custom = __TS__Decorate( + item_mask_of_madness_custom, + item_mask_of_madness_custom, + {registerAbility(nil)}, + {kind = "class", name = "item_mask_of_madness_custom"} +) +____exports.item_mask_of_madness_custom = item_mask_of_madness_custom +____exports.modifier_item_mask_of_madness_custom = __TS__Class() +local modifier_item_mask_of_madness_custom = ____exports.modifier_item_mask_of_madness_custom +modifier_item_mask_of_madness_custom.name = "modifier_item_mask_of_madness_custom" +modifier_item_mask_of_madness_custom.____file_path = "scripts/vscripts/items/default_items/item_mask_of_madness_custom.lua" +__TS__ClassExtends(modifier_item_mask_of_madness_custom, BaseModifier) +function modifier_item_mask_of_madness_custom.prototype.IsHidden(self) + return true +end +function modifier_item_mask_of_madness_custom.prototype.IsPurgable(self) + return false +end +function modifier_item_mask_of_madness_custom.prototype.GetAttributes(self) + return MODIFIER_ATTRIBUTE_MULTIPLE +end +function modifier_item_mask_of_madness_custom.prototype.DeclareFunctions(self) + return {MODIFIER_PROPERTY_PREATTACK_BONUS_DAMAGE} +end +function modifier_item_mask_of_madness_custom.prototype.GetModifierPreAttack_BonusDamage(self) + return self:GetAbility():GetSpecialValueFor("bonus_damage") +end +function modifier_item_mask_of_madness_custom.prototype.OnCreated(self) + if not IsServer() then + return + end + local hero = self:GetParent() + local lifesteal = self:GetAbility():GetSpecialValueFor("lifesteal") + if lifesteal > 0 then + addPhysicalVampirism(nil, hero, lifesteal) + end +end +function modifier_item_mask_of_madness_custom.prototype.OnDestroy(self) + if not IsServer() then + return + end + local hero = self:GetParent() + local lifesteal = self:GetAbility():GetSpecialValueFor("lifesteal") + if lifesteal > 0 then + reducePhysicalVampirism(nil, hero, lifesteal) + end +end +modifier_item_mask_of_madness_custom = __TS__Decorate( + modifier_item_mask_of_madness_custom, + modifier_item_mask_of_madness_custom, + {registerModifier(nil)}, + {kind = "class", name = "modifier_item_mask_of_madness_custom"} +) +____exports.modifier_item_mask_of_madness_custom = modifier_item_mask_of_madness_custom +____exports.modifier_item_mask_of_madness_custom_buff = __TS__Class() +local modifier_item_mask_of_madness_custom_buff = ____exports.modifier_item_mask_of_madness_custom_buff +modifier_item_mask_of_madness_custom_buff.name = "modifier_item_mask_of_madness_custom_buff" +modifier_item_mask_of_madness_custom_buff.____file_path = "scripts/vscripts/items/default_items/item_mask_of_madness_custom.lua" +__TS__ClassExtends(modifier_item_mask_of_madness_custom_buff, BaseModifier) +function modifier_item_mask_of_madness_custom_buff.prototype.____constructor(self, ...) + BaseModifier.prototype.____constructor(self, ...) + self.bonus_damage = 0 + self.bonus_attackspeed_active = 0 + self.armor_reduction_active = 0 + self.move_speed_active = 0 + self.duration = 0 +end +function modifier_item_mask_of_madness_custom_buff.prototype.IsHidden(self) + return false +end +function modifier_item_mask_of_madness_custom_buff.prototype.IsPurgable(self) + return true +end +function modifier_item_mask_of_madness_custom_buff.prototype.OnCreated(self) + self.bonus_damage = self:GetAbility():GetSpecialValueFor("bonus_damage") + self.bonus_attackspeed_active = self:GetAbility():GetSpecialValueFor("bonus_attackspeed_active") + self.armor_reduction_active = self:GetAbility():GetSpecialValueFor("armor_reduction_active") + self.move_speed_active = self:GetAbility():GetSpecialValueFor("move_speed_active") + self.duration = self:GetAbility():GetSpecialValueFor("duration") +end +function modifier_item_mask_of_madness_custom_buff.prototype.DeclareFunctions(self) + return {MODIFIER_PROPERTY_PREATTACK_BONUS_DAMAGE, MODIFIER_PROPERTY_ATTACKSPEED_BONUS_CONSTANT, MODIFIER_PROPERTY_PHYSICAL_ARMOR_BONUS, MODIFIER_PROPERTY_MOVESPEED_BONUS_PERCENTAGE} +end +function modifier_item_mask_of_madness_custom_buff.prototype.CheckState(self) + return {[MODIFIER_STATE_SILENCED] = true} +end +function modifier_item_mask_of_madness_custom_buff.prototype.GetModifierPreAttack_BonusDamage(self) + return self.bonus_damage +end +function modifier_item_mask_of_madness_custom_buff.prototype.GetModifierAttackSpeedBonus_Constant(self) + return self.bonus_attackspeed_active +end +function modifier_item_mask_of_madness_custom_buff.prototype.GetModifierPhysicalArmorBonus(self) + return self.armor_reduction_active +end +function modifier_item_mask_of_madness_custom_buff.prototype.GetModifierMoveSpeedBonus_Percentage(self) + return self.move_speed_active +end +modifier_item_mask_of_madness_custom_buff = __TS__Decorate( + modifier_item_mask_of_madness_custom_buff, + modifier_item_mask_of_madness_custom_buff, + {registerModifier(nil)}, + {kind = "class", name = "modifier_item_mask_of_madness_custom_buff"} +) +____exports.modifier_item_mask_of_madness_custom_buff = modifier_item_mask_of_madness_custom_buff +return ____exports diff --git a/scripts/vscripts/items/default_items/item_mega_fury.lua b/scripts/vscripts/items/default_items/item_mega_fury.lua new file mode 100644 index 0000000..82a195c --- /dev/null +++ b/scripts/vscripts/items/default_items/item_mega_fury.lua @@ -0,0 +1,122 @@ +local ____lualib = require("lualib_bundle") +local __TS__Class = ____lualib.__TS__Class +local __TS__ClassExtends = ____lualib.__TS__ClassExtends +local __TS__Decorate = ____lualib.__TS__Decorate +local ____exports = {} +local ____dota_ts_adapter = require("lib.dota_ts_adapter") +local BaseItem = ____dota_ts_adapter.BaseItem +local BaseModifier = ____dota_ts_adapter.BaseModifier +local registerAbility = ____dota_ts_adapter.registerAbility +local registerModifier = ____dota_ts_adapter.registerModifier +____exports.item_mega_fury = __TS__Class() +local item_mega_fury = ____exports.item_mega_fury +item_mega_fury.name = "item_mega_fury" +item_mega_fury.____file_path = "scripts/vscripts/items/default_items/item_mega_fury.lua" +__TS__ClassExtends(item_mega_fury, BaseItem) +function item_mega_fury.prototype.GetIntrinsicModifierName(self) + return "modifier_mega_fury" +end +function item_mega_fury.prototype.GetAOERadius(self) + return self:GetSpecialValueFor("tree") +end +function item_mega_fury.prototype.OnSpellStart(self) + if not IsServer() then + return + end + local caster = self:GetCaster() + local point = self:GetCursorPosition() + local trees = GridNav:GetAllTreesAroundPoint( + point, + self:GetSpecialValueFor("tree"), + true + ) + if trees and #trees > 0 then + self:CutTree(caster, point) + end +end +function item_mega_fury.prototype.CutTree(self, caster, position) + GridNav:DestroyTreesAroundPoint( + position, + self:GetSpecialValueFor("tree"), + true + ) +end +item_mega_fury = __TS__Decorate( + item_mega_fury, + item_mega_fury, + {registerAbility(nil)}, + {kind = "class", name = "item_mega_fury"} +) +____exports.item_mega_fury = item_mega_fury +____exports.modifier_mega_fury = __TS__Class() +local modifier_mega_fury = ____exports.modifier_mega_fury +modifier_mega_fury.name = "modifier_mega_fury" +modifier_mega_fury.____file_path = "scripts/vscripts/items/default_items/item_mega_fury.lua" +__TS__ClassExtends(modifier_mega_fury, BaseModifier) +function modifier_mega_fury.prototype.IsHidden(self) + return true +end +function modifier_mega_fury.prototype.IsDebuff(self) + return false +end +function modifier_mega_fury.prototype.IsPurgable(self) + return false +end +function modifier_mega_fury.prototype.CheckState(self) + return {[MODIFIER_STATE_CANNOT_MISS] = true} +end +function modifier_mega_fury.prototype.GetAttributes(self) + return MODIFIER_ATTRIBUTE_MULTIPLE +end +function modifier_mega_fury.prototype.DeclareFunctions(self) + return {MODIFIER_PROPERTY_PREATTACK_BONUS_DAMAGE, MODIFIER_EVENT_ON_ATTACK_LANDED, MODIFIER_PROPERTY_ATTACKSPEED_BONUS_CONSTANT, MODIFIER_PROPERTY_ATTACK_RANGE_BONUS} +end +function modifier_mega_fury.prototype.OnAttackLanded(self, event) + if not IsServer() then + return + end + if self:GetParent():IsRangedAttacker() then + return + end + local parent = event.attacker + if parent ~= self:GetParent() then + return + end + local ability = self:GetAbility() + if not ability then + return + end + local target = event.target + local attackDamage = parent:GetAverageTrueAttackDamage(target) + local cleaveDamage = attackDamage * (self:GetAbility():GetSpecialValueFor("cleave_damage") / 100) + DoCleaveAttack( + parent, + target, + ability, + cleaveDamage, + self:GetAbility():GetSpecialValueFor("cleave_starting_width"), + self:GetAbility():GetSpecialValueFor("cleave_ending_width"), + self:GetAbility():GetSpecialValueFor("cleave_distance"), + "particles/items_fx/battlefury_cleave.vpcf" + ) +end +function modifier_mega_fury.prototype.GetModifierPreAttack_BonusDamage(self) + return self:GetAbility():GetSpecialValueFor("bonus_damage") +end +function modifier_mega_fury.prototype.GetModifierAttackSpeedBonus_Constant(self) + return self:GetAbility():GetSpecialValueFor("attack_speed") +end +function modifier_mega_fury.prototype.GetModifierAttackRangeBonus(self) + if self:GetParent():IsRangedAttacker() then + return 0 + end + return self:GetAbility():GetSpecialValueFor("attack_range") +end +modifier_mega_fury = __TS__Decorate( + modifier_mega_fury, + modifier_mega_fury, + {registerModifier(nil)}, + {kind = "class", name = "modifier_mega_fury"} +) +____exports.modifier_mega_fury = modifier_mega_fury +return ____exports diff --git a/scripts/vscripts/items/default_items/item_mega_treads.lua b/scripts/vscripts/items/default_items/item_mega_treads.lua new file mode 100644 index 0000000..5597216 --- /dev/null +++ b/scripts/vscripts/items/default_items/item_mega_treads.lua @@ -0,0 +1,137 @@ +local ____lualib = require("lualib_bundle") +local __TS__Class = ____lualib.__TS__Class +local __TS__ClassExtends = ____lualib.__TS__ClassExtends +local __TS__Decorate = ____lualib.__TS__Decorate +local ____exports = {} +local ____dota_ts_adapter = require("lib.dota_ts_adapter") +local BaseItem = ____dota_ts_adapter.BaseItem +local registerAbility = ____dota_ts_adapter.registerAbility +local registerModifier = ____dota_ts_adapter.registerModifier +local BaseModifier = ____dota_ts_adapter.BaseModifier +____exports.item_mega_treads = __TS__Class() +local item_mega_treads = ____exports.item_mega_treads +item_mega_treads.name = "item_mega_treads" +item_mega_treads.____file_path = "scripts/vscripts/items/default_items/item_mega_treads.lua" +__TS__ClassExtends(item_mega_treads, BaseItem) +function item_mega_treads.prototype.OnSpellStart(self) + local caster = self:GetCaster() + local modifier = caster:FindModifierByName("modifier_item_mega_treads") + if not modifier then + return + end + modifier:SetStackCount((modifier:GetStackCount() + 1) % 3) +end +function item_mega_treads.prototype.GetAbilityTextureName(self) + local iconItem = {strength = "default_items/item_mega_treads_0", agility = "default_items/item_mega_treads_1", intellect = "default_items/item_mega_treads_2"} + return iconItem[self.activeStat] or "default_items/item_mega_treads_0" +end +function item_mega_treads.prototype.GetIntrinsicModifierName(self) + return "modifier_item_mega_treads" +end +item_mega_treads = __TS__Decorate( + item_mega_treads, + item_mega_treads, + {registerAbility(nil)}, + {kind = "class", name = "item_mega_treads"} +) +____exports.item_mega_treads = item_mega_treads +____exports.modifier_item_mega_treads = __TS__Class() +local modifier_item_mega_treads = ____exports.modifier_item_mega_treads +modifier_item_mega_treads.name = "modifier_item_mega_treads" +modifier_item_mega_treads.____file_path = "scripts/vscripts/items/default_items/item_mega_treads.lua" +__TS__ClassExtends(modifier_item_mega_treads, BaseModifier) +function modifier_item_mega_treads.prototype.____constructor(self, ...) + BaseModifier.prototype.____constructor(self, ...) + self.ability = self:GetAbility() + self.bonusActiveStat = 0 + self.bonusMovementSpeed = 0 + self.bonusOtherStat = 0 + self.bonusAttackSpeed = 0 + self.bonusMagicalResistance = 0 + self.bonusAgilityBaseDamagePct = 0 + self.bonusIntellectSpellAmp = 0 +end +function modifier_item_mega_treads.prototype.IsHidden(self) + return true +end +function modifier_item_mega_treads.prototype.IsPurgable(self) + return false +end +function modifier_item_mega_treads.prototype.IsPurgeException(self) + return false +end +function modifier_item_mega_treads.prototype.RemoveOnDeath(self) + return false +end +function modifier_item_mega_treads.prototype.GetAttributes(self) + return MODIFIER_ATTRIBUTE_MULTIPLE +end +function modifier_item_mega_treads.prototype.OnStackCountChanged(self, oldCount) + local stackCount = self:GetStackCount() + local stats = {[0] = "strength", [1] = "intellect", [2] = "agility"} + self.activeStat = stats[stackCount] or "agility" + self.ability.activeStat = stats[stackCount] or "agility" + if IsServer() then + local parent = self:GetParent() + parent:CalculateStatBonus(true) + self.ability:SetSecondaryCharges(stackCount) + end +end +function modifier_item_mega_treads.prototype.OnCreated(self) + self.bonusActiveStat = self.ability:GetSpecialValueFor("bonus_stat") + self.bonusOtherStat = self.ability:GetSpecialValueFor("bonus_other_stat") + self.bonusMovementSpeed = self.ability:GetSpecialValueFor("bonus_movement_speed") + self.bonusAttackSpeed = self.ability:GetSpecialValueFor("bonus_attackspeed") + self.bonusMagicalResistance = self.ability:GetSpecialValueFor("bonus_magical_resistance") + self.bonusIntellectSpellAmp = self.ability:GetSpecialValueFor("spell_amplify") + local stats = {[0] = "strength", [1] = "intellect", [2] = "agility"} + if IsClient() then + return + end + Timers:CreateTimer( + 0.03, + function() + self:SetStackCount(self.ability:GetSecondaryCharges()) + end + ) +end +function modifier_item_mega_treads.prototype.DeclareFunctions(self) + return { + MODIFIER_PROPERTY_STATS_STRENGTH_BONUS, + MODIFIER_PROPERTY_STATS_AGILITY_BONUS, + MODIFIER_PROPERTY_STATS_INTELLECT_BONUS, + MODIFIER_PROPERTY_MOVESPEED_BONUS_UNIQUE, + MODIFIER_PROPERTY_MAGICAL_RESISTANCE_BONUS, + MODIFIER_PROPERTY_SPELL_AMPLIFY_PERCENTAGE, + MODIFIER_PROPERTY_ATTACKSPEED_BONUS_CONSTANT + } +end +function modifier_item_mega_treads.prototype.GetModifierMoveSpeedBonus_Special_Boots(self) + return self.bonusMovementSpeed +end +function modifier_item_mega_treads.prototype.GetModifierSpellAmplify_Percentage(self) + return self.activeStat == "intellect" and self.bonusIntellectSpellAmp or 0 +end +function modifier_item_mega_treads.prototype.GetModifierMagicalResistanceBonus(self) + return self.activeStat == "strength" and self.bonusMagicalResistance or 0 +end +function modifier_item_mega_treads.prototype.GetModifierAttackSpeedBonus_Constant(self) + return self.activeStat == "agility" and self.bonusAttackSpeed or 0 +end +function modifier_item_mega_treads.prototype.GetModifierBonusStats_Strength(self) + return self.activeStat == "strength" and self.bonusActiveStat or self.bonusOtherStat +end +function modifier_item_mega_treads.prototype.GetModifierBonusStats_Agility(self) + return self.activeStat == "agility" and self.bonusActiveStat or self.bonusOtherStat +end +function modifier_item_mega_treads.prototype.GetModifierBonusStats_Intellect(self) + return self.activeStat == "intellect" and self.bonusActiveStat or self.bonusOtherStat +end +modifier_item_mega_treads = __TS__Decorate( + modifier_item_mega_treads, + modifier_item_mega_treads, + {registerModifier(nil)}, + {kind = "class", name = "modifier_item_mega_treads"} +) +____exports.modifier_item_mega_treads = modifier_item_mega_treads +return ____exports diff --git a/scripts/vscripts/items/default_items/item_mini_bfury.lua b/scripts/vscripts/items/default_items/item_mini_bfury.lua new file mode 100644 index 0000000..33c9bf9 --- /dev/null +++ b/scripts/vscripts/items/default_items/item_mini_bfury.lua @@ -0,0 +1,110 @@ +local ____lualib = require("lualib_bundle") +local __TS__Class = ____lualib.__TS__Class +local __TS__ClassExtends = ____lualib.__TS__ClassExtends +local __TS__Decorate = ____lualib.__TS__Decorate +local ____exports = {} +local ____dota_ts_adapter = require("lib.dota_ts_adapter") +local BaseItem = ____dota_ts_adapter.BaseItem +local BaseModifier = ____dota_ts_adapter.BaseModifier +local registerAbility = ____dota_ts_adapter.registerAbility +local registerModifier = ____dota_ts_adapter.registerModifier +____exports.item_mini_bfury = __TS__Class() +local item_mini_bfury = ____exports.item_mini_bfury +item_mini_bfury.name = "item_mini_bfury" +item_mini_bfury.____file_path = "scripts/vscripts/items/default_items/item_mini_bfury.lua" +__TS__ClassExtends(item_mini_bfury, BaseItem) +function item_mini_bfury.prototype.GetIntrinsicModifierName(self) + return "modifier_mini_bfury" +end +function item_mini_bfury.prototype.GetAOERadius(self) + return self:GetSpecialValueFor("tree") +end +function item_mini_bfury.prototype.OnSpellStart(self) + if not IsServer() then + return + end + local caster = self:GetCaster() + local point = self:GetCursorPosition() + local trees = GridNav:GetAllTreesAroundPoint( + point, + self:GetSpecialValueFor("tree"), + true + ) + if trees and #trees > 0 then + self:CutTree(caster, point) + end +end +function item_mini_bfury.prototype.CutTree(self, caster, position) + GridNav:DestroyTreesAroundPoint( + position, + self:GetSpecialValueFor("tree"), + true + ) +end +item_mini_bfury = __TS__Decorate( + item_mini_bfury, + item_mini_bfury, + {registerAbility(nil)}, + {kind = "class", name = "item_mini_bfury"} +) +____exports.item_mini_bfury = item_mini_bfury +____exports.modifier_mini_bfury = __TS__Class() +local modifier_mini_bfury = ____exports.modifier_mini_bfury +modifier_mini_bfury.name = "modifier_mini_bfury" +modifier_mini_bfury.____file_path = "scripts/vscripts/items/default_items/item_mini_bfury.lua" +__TS__ClassExtends(modifier_mini_bfury, BaseModifier) +function modifier_mini_bfury.prototype.IsHidden(self) + return true +end +function modifier_mini_bfury.prototype.IsDebuff(self) + return false +end +function modifier_mini_bfury.prototype.IsPurgable(self) + return false +end +function modifier_mini_bfury.prototype.GetAttributes(self) + return MODIFIER_ATTRIBUTE_MULTIPLE +end +function modifier_mini_bfury.prototype.DeclareFunctions(self) + return {MODIFIER_PROPERTY_PREATTACK_BONUS_DAMAGE, MODIFIER_EVENT_ON_ATTACK_LANDED} +end +function modifier_mini_bfury.prototype.OnAttackLanded(self, event) + if not IsServer() then + return + end + if self:GetParent():IsRangedAttacker() then + return + end + local parent = event.attacker + if parent ~= self:GetParent() then + return + end + local ability = self:GetAbility() + if not ability then + return + end + local target = event.target + local attackDamage = parent:GetAverageTrueAttackDamage(target) + local cleaveDamage = attackDamage * (self:GetAbility():GetSpecialValueFor("cleave_damage") / 100) + DoCleaveAttack( + parent, + target, + ability, + cleaveDamage, + self:GetAbility():GetSpecialValueFor("cleave_starting_width"), + self:GetAbility():GetSpecialValueFor("cleave_ending_width"), + self:GetAbility():GetSpecialValueFor("cleave_distance"), + "particles/items_fx/battlefury_cleave.vpcf" + ) +end +function modifier_mini_bfury.prototype.GetModifierPreAttack_BonusDamage(self) + return self:GetAbility():GetSpecialValueFor("bonus_damage") +end +modifier_mini_bfury = __TS__Decorate( + modifier_mini_bfury, + modifier_mini_bfury, + {registerModifier(nil)}, + {kind = "class", name = "modifier_mini_bfury"} +) +____exports.modifier_mini_bfury = modifier_mini_bfury +return ____exports diff --git a/scripts/vscripts/items/default_items/item_mjolnir_custom.lua b/scripts/vscripts/items/default_items/item_mjolnir_custom.lua new file mode 100644 index 0000000..83eeed0 --- /dev/null +++ b/scripts/vscripts/items/default_items/item_mjolnir_custom.lua @@ -0,0 +1,337 @@ +local ____lualib = require("lualib_bundle") +local __TS__Class = ____lualib.__TS__Class +local __TS__ClassExtends = ____lualib.__TS__ClassExtends +local __TS__Decorate = ____lualib.__TS__Decorate +local ____exports = {} +local ____dota_ts_adapter = require("lib.dota_ts_adapter") +local BaseItem = ____dota_ts_adapter.BaseItem +local BaseModifier = ____dota_ts_adapter.BaseModifier +local registerAbility = ____dota_ts_adapter.registerAbility +local registerModifier = ____dota_ts_adapter.registerModifier +local ____incoming_damage_reduction_combine = require("utils.incoming_damage_reduction_combine") +local removeIncomingDamageReductionSource = ____incoming_damage_reduction_combine.removeIncomingDamageReductionSource +local setIncomingDamageReductionSource = ____incoming_damage_reduction_combine.setIncomingDamageReductionSource +local MJOLNIR_ACTIVE_INCOMING_SOURCE = "modifier_item_mjolnir_active" +____exports.item_mjolnir_custom = __TS__Class() +local item_mjolnir_custom = ____exports.item_mjolnir_custom +item_mjolnir_custom.name = "item_mjolnir_custom" +item_mjolnir_custom.____file_path = "scripts/vscripts/items/default_items/item_mjolnir_custom.lua" +__TS__ClassExtends(item_mjolnir_custom, BaseItem) +function item_mjolnir_custom.prototype.GetIntrinsicModifierName(self) + return "modifier_item_mjolnir_custom" +end +function item_mjolnir_custom.prototype.OnSpellStart(self) + local target = self:GetCursorTarget() + if not target then + return + end + target:EmitSound("DOTA_Item.Mjollnir.Activate") + target:AddNewModifier( + self:GetCaster(), + self, + "modifier_item_mjolnir_active", + {duration = self:GetSpecialValueFor("duration")} + ) +end +item_mjolnir_custom = __TS__Decorate( + item_mjolnir_custom, + item_mjolnir_custom, + {registerAbility(nil)}, + {kind = "class", name = "item_mjolnir_custom"} +) +____exports.item_mjolnir_custom = item_mjolnir_custom +____exports.modifier_item_mjolnir_active = __TS__Class() +local modifier_item_mjolnir_active = ____exports.modifier_item_mjolnir_active +modifier_item_mjolnir_active.name = "modifier_item_mjolnir_active" +modifier_item_mjolnir_active.____file_path = "scripts/vscripts/items/default_items/item_mjolnir_custom.lua" +__TS__ClassExtends(modifier_item_mjolnir_active, BaseModifier) +function modifier_item_mjolnir_active.prototype.GetEffectName(self) + return "particles/econ/events/ti6/mjollnir_shield_ti6.vpcf" +end +function modifier_item_mjolnir_active.prototype.GetEffectAttachType(self) + return PATTACH_ABSORIGIN_FOLLOW +end +function modifier_item_mjolnir_active.prototype.DeclareFunctions(self) + return {MODIFIER_PROPERTY_DAMAGEOUTGOING_PERCENTAGE} +end +function modifier_item_mjolnir_active.prototype.OnCreated(self) + if not IsServer() then + return + end + setIncomingDamageReductionSource( + nil, + self:GetParent(), + MJOLNIR_ACTIVE_INCOMING_SOURCE, + function() + local ability = self:GetAbility() + if not ability then + return 0 + end + return math.max( + 0, + ability:GetSpecialValueFor("incoming_pct") + ) + end + ) +end +function modifier_item_mjolnir_active.prototype.OnDestroy(self) + if not IsServer() then + return + end + removeIncomingDamageReductionSource( + nil, + self:GetParent(), + MJOLNIR_ACTIVE_INCOMING_SOURCE + ) +end +function modifier_item_mjolnir_active.prototype.GetModifierDamageOutgoing_Percentage(self, event) + return self:GetAbility():GetSpecialValueFor("outgoing_pct") +end +function modifier_item_mjolnir_active.prototype.GetTexture(self) + return "item_mjollnir" +end +modifier_item_mjolnir_active = __TS__Decorate( + modifier_item_mjolnir_active, + modifier_item_mjolnir_active, + {registerModifier(nil)}, + {kind = "class", name = "modifier_item_mjolnir_active"} +) +____exports.modifier_item_mjolnir_active = modifier_item_mjolnir_active +____exports.modifier_item_mjolnir_custom = __TS__Class() +local modifier_item_mjolnir_custom = ____exports.modifier_item_mjolnir_custom +modifier_item_mjolnir_custom.name = "modifier_item_mjolnir_custom" +modifier_item_mjolnir_custom.____file_path = "scripts/vscripts/items/default_items/item_mjolnir_custom.lua" +__TS__ClassExtends(modifier_item_mjolnir_custom, BaseModifier) +function modifier_item_mjolnir_custom.prototype.____constructor(self, ...) + BaseModifier.prototype.____constructor(self, ...) + self.bonus_damage = 0 + self.bonus_attack_speed = 0 + self.chain_chance = 0 + self.chain_cooldown = 0 + self.bChainCooldown = false +end +function modifier_item_mjolnir_custom.prototype.IsHidden(self) + return true +end +function modifier_item_mjolnir_custom.prototype.IsPurgable(self) + return false +end +function modifier_item_mjolnir_custom.prototype.RemoveOnDeath(self) + return false +end +function modifier_item_mjolnir_custom.prototype.GetAttributes(self) + return MODIFIER_ATTRIBUTE_MULTIPLE +end +function modifier_item_mjolnir_custom.prototype.OnCreated(self, params) + if IsServer() then + if not self:GetAbility() then + self:Destroy() + return + end + end + local ability = self:GetAbility() + if ability then + self.bonus_damage = ability:GetSpecialValueFor("bonus_damage") + self.bonus_attack_speed = ability:GetSpecialValueFor("bonus_attack_speed") + self.chain_chance = ability:GetSpecialValueFor("chain_chance") + self.chain_cooldown = ability:GetSpecialValueFor("chain_cooldown") + else + self.bonus_damage = 0 + self.bonus_attack_speed = 0 + self.chain_chance = 0 + self.chain_cooldown = 0 + end + self.bChainCooldown = false +end +function modifier_item_mjolnir_custom.prototype.OnIntervalThink(self) + self.bChainCooldown = false + self:StartIntervalThink(-1) +end +function modifier_item_mjolnir_custom.prototype.DeclareFunctions(self) + return {MODIFIER_PROPERTY_PREATTACK_BONUS_DAMAGE, MODIFIER_PROPERTY_ATTACKSPEED_BONUS_CONSTANT, MODIFIER_EVENT_ON_ATTACK_LANDED, MODIFIER_EVENT_ON_ORDER} +end +function modifier_item_mjolnir_custom.prototype.GetModifierPreAttack_BonusDamage(self) + return self.bonus_damage +end +function modifier_item_mjolnir_custom.prototype.GetModifierAttackSpeedBonus_Constant(self) + return self.bonus_attack_speed +end +function modifier_item_mjolnir_custom.prototype.OnAttackLanded(self, event) + if IsServer() and event.attacker == self:GetParent() and self:GetParent():IsAlive() and not self.bChainCooldown and not self:GetParent():IsIllusion() and not event.target:IsMagicImmune() and not event.target:IsBuilding() and not event.target:IsOther() and self:GetParent():GetTeamNumber() ~= event.target:GetTeamNumber() and RollPseudoRandomPercentage( + self.chain_chance, + 4, + self:GetParent() + ) then + self:GetParent():EmitSound("Item.Maelstrom.Chain_Lightning") + self:GetParent():AddNewModifier( + self:GetCaster(), + self:GetAbility(), + "modifier_chain_lightning", + {starting_unit_entindex = event.target:entindex()} + ) + self.bChainCooldown = true + self:StartIntervalThink(self.chain_cooldown) + end +end +modifier_item_mjolnir_custom = __TS__Decorate( + modifier_item_mjolnir_custom, + modifier_item_mjolnir_custom, + {registerModifier(nil)}, + {kind = "class", name = "modifier_item_mjolnir_custom"} +) +____exports.modifier_item_mjolnir_custom = modifier_item_mjolnir_custom +____exports.modifier_chain_lightning = __TS__Class() +local modifier_chain_lightning = ____exports.modifier_chain_lightning +modifier_chain_lightning.name = "modifier_chain_lightning" +modifier_chain_lightning.____file_path = "scripts/vscripts/items/default_items/item_mjolnir_custom.lua" +__TS__ClassExtends(modifier_chain_lightning, BaseModifier) +function modifier_chain_lightning.prototype.____constructor(self, ...) + BaseModifier.prototype.____constructor(self, ...) + self.bonus_damage = 0 + self.chain_damage = 0 + self.chain_damage_self = 0 + self.chain_strikes = 0 + self.chain_radius = 0 + self.chain_delay = 0 + self.units_affected = {} + self.unit_counter = 0 + self.zapped = false +end +function modifier_chain_lightning.prototype.IsHidden(self) + return true +end +function modifier_chain_lightning.prototype.IsDebuff(self) + return false +end +function modifier_chain_lightning.prototype.IsPurgable(self) + return false +end +function modifier_chain_lightning.prototype.RemoveOnDeath(self) + return false +end +function modifier_chain_lightning.prototype.GetAttributes(self) + return MODIFIER_ATTRIBUTE_MULTIPLE +end +function modifier_chain_lightning.prototype.OnCreated(self, params) + if IsServer() then + if not self:GetAbility() then + self:Destroy() + return + end + end + if not IsServer() then + return + end + local ability = self:GetAbility() + if ability then + self.bonus_damage = ability:GetSpecialValueFor("bonus_damage") + self.chain_damage = ability:GetSpecialValueFor("chain_damage") + self.chain_damage_self = ability:GetSpecialValueFor("chain_damage_self") + self.chain_strikes = ability:GetSpecialValueFor("chain_strikes") + self.chain_radius = ability:GetSpecialValueFor("chain_radius") + self.chain_delay = ability:GetSpecialValueFor("chain_delay") + end + self.starting_unit_entindex = params.starting_unit_entindex + if self.starting_unit_entindex then + local unit = EntIndexToHScript(self.starting_unit_entindex) + if unit and unit:IsBaseNPC() then + self.current_unit = unit + else + self:Destroy() + return + end + else + self:Destroy() + return + end + self.units_affected = {} + self.unit_counter = 0 + self:OnIntervalThink() + self:StartIntervalThink(self.chain_delay) +end +function modifier_chain_lightning.prototype.OnIntervalThink(self) + self.zapped = false + local caster = self:GetCaster() + if not self.current_unit or not caster then + return + end + local enemies = FindUnitsInRadius( + caster:GetTeamNumber(), + self.current_unit:GetAbsOrigin(), + nil, + self.chain_radius, + DOTA_UNIT_TARGET_TEAM_ENEMY, + DOTA_UNIT_TARGET_HERO + DOTA_UNIT_TARGET_BASIC, + DOTA_UNIT_TARGET_FLAG_FOW_VISIBLE + DOTA_UNIT_TARGET_FLAG_NO_INVIS, + FIND_CLOSEST, + false + ) + for ____, enemy in ipairs(enemies) do + if not self.units_affected[enemy:entindex()] then + enemy:EmitSound("Item.Maelstrom.Chain_Lightning.Jump") + local zap_particle = ParticleManager:CreateParticle("particles/items_fx/chain_lightning.vpcf", PATTACH_ABSORIGIN_FOLLOW, self.current_unit) + if self.unit_counter == 0 then + ParticleManager:SetParticleControlEnt( + zap_particle, + 0, + self:GetParent(), + PATTACH_POINT_FOLLOW, + "attach_hitloc", + self:GetParent():GetAbsOrigin(), + true + ) + else + ParticleManager:SetParticleControlEnt( + zap_particle, + 0, + self.current_unit, + PATTACH_POINT_FOLLOW, + "attach_hitloc", + self.current_unit:GetAbsOrigin(), + true + ) + end + ParticleManager:SetParticleControlEnt( + zap_particle, + 1, + enemy, + PATTACH_POINT_FOLLOW, + "attach_hitloc", + enemy:GetAbsOrigin(), + true + ) + ParticleManager:SetParticleControl( + zap_particle, + 2, + Vector(1, 1, 1) + ) + ParticleManager:ReleaseParticleIndex(zap_particle) + self.unit_counter = self.unit_counter + 1 + self.current_unit = enemy + self.units_affected[self.current_unit:entindex()] = true + self.zapped = true + local damage = caster:GetAverageTrueAttackDamage(caster) * (self.chain_damage_self / 100) + self.chain_damage + ApplyDamage({ + victim = enemy, + damage = damage, + damage_type = DAMAGE_TYPE_MAGICAL, + damage_flags = DOTA_DAMAGE_FLAG_NONE, + attacker = caster, + ability = self:GetAbility() + }) + break + end + end + if self.unit_counter >= self.chain_strikes and self.chain_strikes > 0 or not self.zapped then + self:StartIntervalThink(-1) + self:Destroy() + end +end +modifier_chain_lightning = __TS__Decorate( + modifier_chain_lightning, + modifier_chain_lightning, + {registerModifier(nil)}, + {kind = "class", name = "modifier_chain_lightning"} +) +____exports.modifier_chain_lightning = modifier_chain_lightning +return ____exports diff --git a/scripts/vscripts/items/default_items/item_orb_of_fire copy.lua b/scripts/vscripts/items/default_items/item_orb_of_fire copy.lua new file mode 100644 index 0000000..64c147d --- /dev/null +++ b/scripts/vscripts/items/default_items/item_orb_of_fire copy.lua @@ -0,0 +1,107 @@ +local ____lualib = require("lualib_bundle") +local __TS__Class = ____lualib.__TS__Class +local __TS__ClassExtends = ____lualib.__TS__ClassExtends +local __TS__Decorate = ____lualib.__TS__Decorate +local ____exports = {} +local ____dota_ts_adapter = require("lib.dota_ts_adapter") +local BaseItem = ____dota_ts_adapter.BaseItem +local registerAbility = ____dota_ts_adapter.registerAbility +local registerModifier = ____dota_ts_adapter.registerModifier +local BaseModifier = ____dota_ts_adapter.BaseModifier +local ____modifier_general_fired = require("abilities.modifiers.modifier_general_fired") +local modifier_general_fired = ____modifier_general_fired.modifier_general_fired +____exports.item_orb_of_fire = __TS__Class() +local item_orb_of_fire = ____exports.item_orb_of_fire +item_orb_of_fire.name = "item_orb_of_fire" +item_orb_of_fire.____file_path = "scripts/vscripts/items/default_items/item_orb_of_fire copy.lua" +__TS__ClassExtends(item_orb_of_fire, BaseItem) +function item_orb_of_fire.prototype.GetIntrinsicModifierName(self) + return "modifier_item_orb_of_fire" +end +function item_orb_of_fire.prototype.GetAOERadius(self) + return self:GetSpecialValueFor("radius") +end +item_orb_of_fire = __TS__Decorate( + item_orb_of_fire, + item_orb_of_fire, + {registerAbility(nil)}, + {kind = "class", name = "item_orb_of_fire"} +) +____exports.item_orb_of_fire = item_orb_of_fire +____exports.modifier_item_orb_of_fire = __TS__Class() +local modifier_item_orb_of_fire = ____exports.modifier_item_orb_of_fire +modifier_item_orb_of_fire.name = "modifier_item_orb_of_fire" +modifier_item_orb_of_fire.____file_path = "scripts/vscripts/items/default_items/item_orb_of_fire copy.lua" +__TS__ClassExtends(modifier_item_orb_of_fire, BaseModifier) +function modifier_item_orb_of_fire.prototype.IsHidden(self) + return true +end +function modifier_item_orb_of_fire.prototype.IsDebuff(self) + return false +end +function modifier_item_orb_of_fire.prototype.IsPurgable(self) + return false +end +function modifier_item_orb_of_fire.prototype.OnCreated(self, params) + self:StartIntervalThink(3) +end +function modifier_item_orb_of_fire.prototype.GetTexture(self) + return "default_items/orb_of_fire" +end +function modifier_item_orb_of_fire.prototype.OnIntervalThink(self) + if not IsServer() then + return + end + if not self:GetParent():IsAlive() then + return + end + local caster = self:GetCaster() + local radius = self:GetAbility():GetSpecialValueFor("radius") + local damage = self:GetAbility():GetSpecialValueFor("damage") + local enemies = FindUnitsInRadius( + caster:GetTeamNumber(), + caster:GetAbsOrigin(), + nil, + radius, + DOTA_UNIT_TARGET_TEAM_ENEMY, + bit.bor(DOTA_UNIT_TARGET_HERO, DOTA_UNIT_TARGET_BASIC), + DOTA_UNIT_TARGET_FLAG_NONE, + FIND_ANY_ORDER, + false + ) + for ____, enemy in ipairs(enemies) do + local damage_table = { + victim = enemy, + attacker = caster, + damage = damage, + damage_type = DAMAGE_TYPE_MAGICAL, + ability = self:GetAbility(), + damage_flags = DOTA_DAMAGE_FLAG_NO_SPELL_AMPLIFICATION + } + local modifier = enemy:AddNewModifier( + self:GetCaster(), + self:GetAbility(), + modifier_general_fired.name, + {} + ) + if modifier then + local stacksPerLevel = self:GetAbility():GetSpecialValueFor("fire_stack") + do + local i = 0 + while i < stacksPerLevel do + modifier:IncrementStackCount() + i = i + 1 + end + end + end + ApplyDamage(damage_table) + end +end +modifier_item_orb_of_fire = __TS__Decorate( + modifier_item_orb_of_fire, + modifier_item_orb_of_fire, + {registerModifier(nil)}, + {kind = "class", name = "modifier_item_orb_of_fire"} +) +____exports.modifier_item_orb_of_fire = modifier_item_orb_of_fire +return ____exports diff --git a/scripts/vscripts/items/default_items/item_orb_of_fire.lua b/scripts/vscripts/items/default_items/item_orb_of_fire.lua new file mode 100644 index 0000000..50a3325 --- /dev/null +++ b/scripts/vscripts/items/default_items/item_orb_of_fire.lua @@ -0,0 +1,107 @@ +local ____lualib = require("lualib_bundle") +local __TS__Class = ____lualib.__TS__Class +local __TS__ClassExtends = ____lualib.__TS__ClassExtends +local __TS__Decorate = ____lualib.__TS__Decorate +local ____exports = {} +local ____dota_ts_adapter = require("lib.dota_ts_adapter") +local BaseItem = ____dota_ts_adapter.BaseItem +local registerAbility = ____dota_ts_adapter.registerAbility +local registerModifier = ____dota_ts_adapter.registerModifier +local BaseModifier = ____dota_ts_adapter.BaseModifier +local ____modifier_general_fired = require("abilities.modifiers.modifier_general_fired") +local modifier_general_fired = ____modifier_general_fired.modifier_general_fired +____exports.item_orb_of_fire = __TS__Class() +local item_orb_of_fire = ____exports.item_orb_of_fire +item_orb_of_fire.name = "item_orb_of_fire" +item_orb_of_fire.____file_path = "scripts/vscripts/items/default_items/item_orb_of_fire.lua" +__TS__ClassExtends(item_orb_of_fire, BaseItem) +function item_orb_of_fire.prototype.GetIntrinsicModifierName(self) + return "modifier_item_orb_of_fire" +end +function item_orb_of_fire.prototype.GetAOERadius(self) + return self:GetSpecialValueFor("radius") +end +item_orb_of_fire = __TS__Decorate( + item_orb_of_fire, + item_orb_of_fire, + {registerAbility(nil)}, + {kind = "class", name = "item_orb_of_fire"} +) +____exports.item_orb_of_fire = item_orb_of_fire +____exports.modifier_item_orb_of_fire = __TS__Class() +local modifier_item_orb_of_fire = ____exports.modifier_item_orb_of_fire +modifier_item_orb_of_fire.name = "modifier_item_orb_of_fire" +modifier_item_orb_of_fire.____file_path = "scripts/vscripts/items/default_items/item_orb_of_fire.lua" +__TS__ClassExtends(modifier_item_orb_of_fire, BaseModifier) +function modifier_item_orb_of_fire.prototype.IsHidden(self) + return true +end +function modifier_item_orb_of_fire.prototype.IsDebuff(self) + return false +end +function modifier_item_orb_of_fire.prototype.IsPurgable(self) + return false +end +function modifier_item_orb_of_fire.prototype.OnCreated(self, params) + self:StartIntervalThink(3) +end +function modifier_item_orb_of_fire.prototype.GetTexture(self) + return "default_items/orb_of_fire" +end +function modifier_item_orb_of_fire.prototype.OnIntervalThink(self) + if not IsServer() then + return + end + if not self:GetParent():IsAlive() then + return + end + local caster = self:GetCaster() + local radius = self:GetAbility():GetSpecialValueFor("radius") + local damage = self:GetAbility():GetSpecialValueFor("damage") + local enemies = FindUnitsInRadius( + caster:GetTeamNumber(), + caster:GetAbsOrigin(), + nil, + radius, + DOTA_UNIT_TARGET_TEAM_ENEMY, + bit.bor(DOTA_UNIT_TARGET_HERO, DOTA_UNIT_TARGET_BASIC), + DOTA_UNIT_TARGET_FLAG_NONE, + FIND_ANY_ORDER, + false + ) + for ____, enemy in ipairs(enemies) do + local damage_table = { + victim = enemy, + attacker = caster, + damage = damage, + damage_type = DAMAGE_TYPE_MAGICAL, + ability = self:GetAbility(), + damage_flags = DOTA_DAMAGE_FLAG_NO_SPELL_AMPLIFICATION + } + local modifier = enemy:AddNewModifier( + self:GetCaster(), + self:GetAbility(), + modifier_general_fired.name, + {} + ) + if modifier then + local stacksPerLevel = self:GetAbility():GetSpecialValueFor("fire_stack") + do + local i = 0 + while i < stacksPerLevel do + modifier:IncrementStackCount() + i = i + 1 + end + end + end + ApplyDamage(damage_table) + end +end +modifier_item_orb_of_fire = __TS__Decorate( + modifier_item_orb_of_fire, + modifier_item_orb_of_fire, + {registerModifier(nil)}, + {kind = "class", name = "modifier_item_orb_of_fire"} +) +____exports.modifier_item_orb_of_fire = modifier_item_orb_of_fire +return ____exports diff --git a/scripts/vscripts/items/default_items/item_poor_shield.lua b/scripts/vscripts/items/default_items/item_poor_shield.lua new file mode 100644 index 0000000..db7b7f4 --- /dev/null +++ b/scripts/vscripts/items/default_items/item_poor_shield.lua @@ -0,0 +1,75 @@ +local ____lualib = require("lualib_bundle") +local __TS__Class = ____lualib.__TS__Class +local __TS__ClassExtends = ____lualib.__TS__ClassExtends +local __TS__Decorate = ____lualib.__TS__Decorate +local ____exports = {} +local ____dota_ts_adapter = require("lib.dota_ts_adapter") +local BaseItem = ____dota_ts_adapter.BaseItem +local BaseModifier = ____dota_ts_adapter.BaseModifier +local registerAbility = ____dota_ts_adapter.registerAbility +local registerModifier = ____dota_ts_adapter.registerModifier +local ____luck = require("utils.luck") +local rollLuckChance = ____luck.rollLuckChance +--- Бедный щит: броня и шанс заблокировать физический урон от атак; шанс для героев с Luck. +____exports.item_poor_shield = __TS__Class() +local item_poor_shield = ____exports.item_poor_shield +item_poor_shield.name = "item_poor_shield" +item_poor_shield.____file_path = "scripts/vscripts/items/default_items/item_poor_shield.lua" +__TS__ClassExtends(item_poor_shield, BaseItem) +function item_poor_shield.prototype.GetIntrinsicModifierName(self) + return ____exports.modifier_item_poor_shield.name +end +item_poor_shield = __TS__Decorate( + item_poor_shield, + item_poor_shield, + {registerAbility(nil)}, + {kind = "class", name = "item_poor_shield"} +) +____exports.item_poor_shield = item_poor_shield +____exports.modifier_item_poor_shield = __TS__Class() +local modifier_item_poor_shield = ____exports.modifier_item_poor_shield +modifier_item_poor_shield.name = "modifier_item_poor_shield" +modifier_item_poor_shield.____file_path = "scripts/vscripts/items/default_items/item_poor_shield.lua" +__TS__ClassExtends(modifier_item_poor_shield, BaseModifier) +function modifier_item_poor_shield.prototype.IsHidden(self) + return true +end +function modifier_item_poor_shield.prototype.IsPurgable(self) + return false +end +function modifier_item_poor_shield.prototype.DeclareFunctions(self) + return {MODIFIER_PROPERTY_PHYSICAL_CONSTANT_BLOCK, MODIFIER_PROPERTY_PHYSICAL_ARMOR_BONUS} +end +function modifier_item_poor_shield.prototype.GetModifierPhysicalArmorBonus(self) + return self:GetAbility():GetSpecialValueFor("bonus_physical_armor") +end +function modifier_item_poor_shield.prototype.GetModifierPhysical_ConstantBlock(self) + if not IsServer() then + return 0 + end + local ability = self:GetAbility() + if not ability then + return 0 + end + local chancePct = ability:GetSpecialValueFor("damage_block_chance") + local parent = self:GetParent() + local ____parent_IsRealHero_result_0 + if parent:IsRealHero() then + ____parent_IsRealHero_result_0 = rollLuckChance(nil, parent, chancePct / 100) + else + ____parent_IsRealHero_result_0 = RollPercentage(chancePct) + end + local blocked = ____parent_IsRealHero_result_0 + if blocked then + return self:GetCaster():IsRangedAttacker() and ability:GetSpecialValueFor("damage_block") * 0.5 or ability:GetSpecialValueFor("damage_block") + end + return 0 +end +modifier_item_poor_shield = __TS__Decorate( + modifier_item_poor_shield, + modifier_item_poor_shield, + {registerModifier(nil)}, + {kind = "class", name = "modifier_item_poor_shield"} +) +____exports.modifier_item_poor_shield = modifier_item_poor_shield +return ____exports diff --git a/scripts/vscripts/items/default_items/item_radiance_custom.lua b/scripts/vscripts/items/default_items/item_radiance_custom.lua new file mode 100644 index 0000000..1643901 --- /dev/null +++ b/scripts/vscripts/items/default_items/item_radiance_custom.lua @@ -0,0 +1,155 @@ +local ____lualib = require("lualib_bundle") +local __TS__Class = ____lualib.__TS__Class +local __TS__ClassExtends = ____lualib.__TS__ClassExtends +local __TS__Decorate = ____lualib.__TS__Decorate +local ____exports = {} +local ____dota_ts_adapter = require("lib.dota_ts_adapter") +local BaseItem = ____dota_ts_adapter.BaseItem +local registerAbility = ____dota_ts_adapter.registerAbility +local registerModifier = ____dota_ts_adapter.registerModifier +local BaseModifier = ____dota_ts_adapter.BaseModifier +local ____modifier_general_fired = require("abilities.modifiers.modifier_general_fired") +local modifier_general_fired = ____modifier_general_fired.modifier_general_fired +local RADIANCE_PARTICLE = "particles/items2_fx/radiance_owner.vpcf" +--- Суммарный регген HP/сек владельца (с предметами и баффами). +local function getOwnerHealthRegenPerSecond(self, unit) + local u = unit + if u.GetHealthRegen ~= nil and type(u.GetHealthRegen) == "function" then + return math.max( + 0, + u:GetHealthRegen() + ) + end + return math.max( + 0, + unit:GetBaseHealthRegen() + ) +end +____exports.item_radiance_custom = __TS__Class() +local item_radiance_custom = ____exports.item_radiance_custom +item_radiance_custom.name = "item_radiance_custom" +item_radiance_custom.____file_path = "scripts/vscripts/items/default_items/item_radiance_custom.lua" +__TS__ClassExtends(item_radiance_custom, BaseItem) +function item_radiance_custom.prototype.Precache(self, context) + PrecacheResource("particle", RADIANCE_PARTICLE, context) +end +function item_radiance_custom.prototype.GetIntrinsicModifierName(self) + return ____exports.modifier_item_radiance_custom.name +end +function item_radiance_custom.prototype.GetAOERadius(self) + return self:GetSpecialValueFor("radius") +end +item_radiance_custom = __TS__Decorate( + item_radiance_custom, + item_radiance_custom, + {registerAbility(nil)}, + {kind = "class", name = "item_radiance_custom"} +) +____exports.item_radiance_custom = item_radiance_custom +____exports.modifier_item_radiance_custom = __TS__Class() +local modifier_item_radiance_custom = ____exports.modifier_item_radiance_custom +modifier_item_radiance_custom.name = "modifier_item_radiance_custom" +modifier_item_radiance_custom.____file_path = "scripts/vscripts/items/default_items/item_radiance_custom.lua" +__TS__ClassExtends(modifier_item_radiance_custom, BaseModifier) +function modifier_item_radiance_custom.prototype.IsHidden(self) + return true +end +function modifier_item_radiance_custom.prototype.IsDebuff(self) + return false +end +function modifier_item_radiance_custom.prototype.IsPurgable(self) + return false +end +function modifier_item_radiance_custom.prototype.RemoveOnDeath(self) + return false +end +function modifier_item_radiance_custom.prototype.DeclareFunctions(self) + return {MODIFIER_PROPERTY_PREATTACK_BONUS_DAMAGE, MODIFIER_PROPERTY_EVASION_CONSTANT} +end +function modifier_item_radiance_custom.prototype.GetModifierPreAttack_BonusDamage(self) + return self:GetAbility():GetSpecialValueFor("bonus_damage") +end +function modifier_item_radiance_custom.prototype.GetModifierEvasion_Constant(self) + return self:GetAbility():GetSpecialValueFor("evasion") +end +function modifier_item_radiance_custom.prototype.OnCreated(self) + if not IsServer() then + return + end + local tickInterval = self:GetAbility():GetSpecialValueFor("tick_interval") + self:StartIntervalThink(tickInterval) + self.particleId = ParticleManager:CreateParticle( + RADIANCE_PARTICLE, + PATTACH_ABSORIGIN_FOLLOW, + self:GetParent() + ) + ParticleManager:SetParticleShouldCheckFoW(self.particleId, false) +end +function modifier_item_radiance_custom.prototype.OnDestroy(self) + if not IsServer() then + return + end + if self.particleId ~= nil then + ParticleManager:DestroyParticle(self.particleId, false) + ParticleManager:ReleaseParticleIndex(self.particleId) + self.particleId = nil + end +end +function modifier_item_radiance_custom.prototype.OnIntervalThink(self) + if not IsServer() then + return + end + if not self:GetParent():IsAlive() then + return + end + local ____opt_0 = self:GetCaster() + if ____opt_0 and ____opt_0:IsIllusion() then + return + end + local ability = self:GetAbility() + local caster = self:GetCaster() + local radius = ability:GetSpecialValueFor("radius") + local tickInterval = ability:GetSpecialValueFor("tick_interval") + local healthRegen = getOwnerHealthRegenPerSecond(nil, caster) + local damage = (ability:GetSpecialValueFor("damage") + caster:GetMaxHealth() * (ability:GetSpecialValueFor("health_damage_pct") / 100) + healthRegen * ability:GetSpecialValueFor("health_regen_damage")) * tickInterval + local enemies = FindUnitsInRadius( + caster:GetTeamNumber(), + caster:GetAbsOrigin(), + nil, + radius, + DOTA_UNIT_TARGET_TEAM_ENEMY, + bit.bor(DOTA_UNIT_TARGET_HERO, DOTA_UNIT_TARGET_BASIC), + DOTA_UNIT_TARGET_FLAG_NONE, + FIND_ANY_ORDER, + false + ) + local stacksPerTick = ability:GetSpecialValueFor("fire_stack") + for ____, enemy in ipairs(enemies) do + local burnModifier = enemy:AddNewModifier(caster, ability, modifier_general_fired.name, {}) + if burnModifier then + do + local i = 0 + while i < stacksPerTick do + burnModifier:IncrementStackCount() + i = i + 1 + end + end + end + ApplyDamage({ + victim = enemy, + attacker = caster, + damage = damage, + damage_type = DAMAGE_TYPE_MAGICAL, + ability = ability, + damage_flags = DOTA_DAMAGE_FLAG_NO_SPELL_AMPLIFICATION + }) + end +end +modifier_item_radiance_custom = __TS__Decorate( + modifier_item_radiance_custom, + modifier_item_radiance_custom, + {registerModifier(nil)}, + {kind = "class", name = "modifier_item_radiance_custom"} +) +____exports.modifier_item_radiance_custom = modifier_item_radiance_custom +return ____exports diff --git a/scripts/vscripts/items/default_items/item_rapier_custom.lua b/scripts/vscripts/items/default_items/item_rapier_custom.lua new file mode 100644 index 0000000..8b62af9 --- /dev/null +++ b/scripts/vscripts/items/default_items/item_rapier_custom.lua @@ -0,0 +1,76 @@ +local ____lualib = require("lualib_bundle") +local __TS__Class = ____lualib.__TS__Class +local __TS__ClassExtends = ____lualib.__TS__ClassExtends +local __TS__Decorate = ____lualib.__TS__Decorate +local ____exports = {} +local ____dota_ts_adapter = require("lib.dota_ts_adapter") +local BaseItem = ____dota_ts_adapter.BaseItem +local BaseModifier = ____dota_ts_adapter.BaseModifier +local registerAbility = ____dota_ts_adapter.registerAbility +local registerModifier = ____dota_ts_adapter.registerModifier +____exports.item_rapier_custom = __TS__Class() +local item_rapier_custom = ____exports.item_rapier_custom +item_rapier_custom.name = "item_rapier_custom" +item_rapier_custom.____file_path = "scripts/vscripts/items/default_items/item_rapier_custom.lua" +__TS__ClassExtends(item_rapier_custom, BaseItem) +function item_rapier_custom.prototype.GetIntrinsicModifierName(self) + return "modifier_rapier_custom" +end +item_rapier_custom = __TS__Decorate( + item_rapier_custom, + item_rapier_custom, + {registerAbility(nil)}, + {kind = "class", name = "item_rapier_custom"} +) +____exports.item_rapier_custom = item_rapier_custom +____exports.modifier_rapier_custom = __TS__Class() +local modifier_rapier_custom = ____exports.modifier_rapier_custom +modifier_rapier_custom.name = "modifier_rapier_custom" +modifier_rapier_custom.____file_path = "scripts/vscripts/items/default_items/item_rapier_custom.lua" +__TS__ClassExtends(modifier_rapier_custom, BaseModifier) +function modifier_rapier_custom.prototype.IsHidden(self) + return true +end +function modifier_rapier_custom.prototype.IsDebuff(self) + return false +end +function modifier_rapier_custom.prototype.IsPurgable(self) + return false +end +function modifier_rapier_custom.prototype.GetAttributes(self) + return MODIFIER_ATTRIBUTE_MULTIPLE +end +function modifier_rapier_custom.prototype.DeclareFunctions(self) + return {MODIFIER_PROPERTY_PREATTACK_BONUS_DAMAGE, MODIFIER_PROPERTY_PROCATTACK_BONUS_DAMAGE_PURE, MODIFIER_EVENT_ON_ATTACK_LANDED} +end +function modifier_rapier_custom.prototype.OnAttackLanded(self, event) + if self:GetParent() == event.attacker then + local damage = self:GetAbility():GetSpecialValueFor("proc_damage") / 100 * self:GetParent():GetAverageTrueAttackDamage(event.target) + SendOverheadEventMessage( + nil, + OVERHEAD_ALERT_DAMAGE, + event.target, + damage, + nil + ) + local particle = ParticleManager:CreateParticle("models/heroes/phantom_assassin_persona/debut/particles/pa_debutdash/pa_debutdash_fragments.vpcf", PATTACH_CUSTOMORIGIN, nil) + ParticleManager:SetParticleControl( + particle, + 0, + event.target:GetAbsOrigin() + ) + ParticleManager:ReleaseParticleIndex(particle) + ApplyDamage({victim = event.target, attacker = event.attacker, damage = damage, damage_type = DAMAGE_TYPE_PURE}) + end +end +function modifier_rapier_custom.prototype.GetModifierPreAttack_BonusDamage(self) + return self:GetAbility():GetSpecialValueFor("bonus_damage") +end +modifier_rapier_custom = __TS__Decorate( + modifier_rapier_custom, + modifier_rapier_custom, + {registerModifier(nil)}, + {kind = "class", name = "modifier_rapier_custom"} +) +____exports.modifier_rapier_custom = modifier_rapier_custom +return ____exports diff --git a/scripts/vscripts/items/default_items/item_satanic_custom.lua b/scripts/vscripts/items/default_items/item_satanic_custom.lua new file mode 100644 index 0000000..7f3e2da --- /dev/null +++ b/scripts/vscripts/items/default_items/item_satanic_custom.lua @@ -0,0 +1,159 @@ +local ____lualib = require("lualib_bundle") +local __TS__Class = ____lualib.__TS__Class +local __TS__ClassExtends = ____lualib.__TS__ClassExtends +local __TS__Decorate = ____lualib.__TS__Decorate +local ____exports = {} +local ____dota_ts_adapter = require("lib.dota_ts_adapter") +local BaseItem = ____dota_ts_adapter.BaseItem +local BaseModifier = ____dota_ts_adapter.BaseModifier +local registerAbility = ____dota_ts_adapter.registerAbility +local registerModifier = ____dota_ts_adapter.registerModifier +local ____vampirism = require("utils.vampirism") +local addPhysicalVampirism = ____vampirism.addPhysicalVampirism +local precacheVampirismParticle = ____vampirism.precacheVampirismParticle +local reducePhysicalVampirism = ____vampirism.reducePhysicalVampirism +local SATANIC_BUFF_PARTICLE = "particles/items2_fx/satanic_buff.vpcf" +____exports.item_satanic_custom = __TS__Class() +local item_satanic_custom = ____exports.item_satanic_custom +item_satanic_custom.name = "item_satanic_custom" +item_satanic_custom.____file_path = "scripts/vscripts/items/default_items/item_satanic_custom.lua" +__TS__ClassExtends(item_satanic_custom, BaseItem) +function item_satanic_custom.prototype.Precache(self, context) + precacheVampirismParticle(nil, context) + PrecacheResource("particle", SATANIC_BUFF_PARTICLE, context) + PrecacheResource("soundfile", "soundevents/game_sounds_items.vsndevts", context) +end +function item_satanic_custom.prototype.GetIntrinsicModifierName(self) + return ____exports.modifier_item_satanic_custom.name +end +function item_satanic_custom.prototype.OnSpellStart(self) + if not IsServer() then + return + end + local caster = self:GetCaster() + local healthCost = self:GetSpecialValueFor("health_cost") + if healthCost > 0 then + caster:SetHealth(math.max( + 1, + caster:GetHealth() - healthCost + )) + end + caster:EmitSound("DOTA_Item.Satanic.Activate") + caster:AddNewModifier( + caster, + self, + ____exports.modifier_item_satanic_custom_unholy.name, + {duration = self:GetSpecialValueFor("unholy_duration")} + ) +end +item_satanic_custom = __TS__Decorate( + item_satanic_custom, + item_satanic_custom, + {registerAbility(nil)}, + {kind = "class", name = "item_satanic_custom"} +) +____exports.item_satanic_custom = item_satanic_custom +____exports.modifier_item_satanic_custom = __TS__Class() +local modifier_item_satanic_custom = ____exports.modifier_item_satanic_custom +modifier_item_satanic_custom.name = "modifier_item_satanic_custom" +modifier_item_satanic_custom.____file_path = "scripts/vscripts/items/default_items/item_satanic_custom.lua" +__TS__ClassExtends(modifier_item_satanic_custom, BaseModifier) +function modifier_item_satanic_custom.prototype.IsHidden(self) + return true +end +function modifier_item_satanic_custom.prototype.IsPurgable(self) + return false +end +function modifier_item_satanic_custom.prototype.RemoveOnDeath(self) + return false +end +function modifier_item_satanic_custom.prototype.GetAttributes(self) + return MODIFIER_ATTRIBUTE_MULTIPLE +end +function modifier_item_satanic_custom.prototype.DeclareFunctions(self) + return {MODIFIER_PROPERTY_PREATTACK_BONUS_DAMAGE, MODIFIER_PROPERTY_STATS_STRENGTH_BONUS} +end +function modifier_item_satanic_custom.prototype.GetModifierPreAttack_BonusDamage(self) + return self:GetAbility():GetSpecialValueFor("bonus_damage") +end +function modifier_item_satanic_custom.prototype.GetModifierBonusStats_Strength(self) + return self:GetAbility():GetSpecialValueFor("bonus_strength") +end +function modifier_item_satanic_custom.prototype.OnCreated(self) + if not IsServer() then + return + end + local hero = self:GetParent() + local lifesteal = self:GetAbility():GetSpecialValueFor("lifesteal") + if lifesteal > 0 then + addPhysicalVampirism(nil, hero, lifesteal) + end +end +function modifier_item_satanic_custom.prototype.OnDestroy(self) + if not IsServer() then + return + end + local hero = self:GetParent() + local lifesteal = self:GetAbility():GetSpecialValueFor("lifesteal") + if lifesteal > 0 then + reducePhysicalVampirism(nil, hero, lifesteal) + end +end +modifier_item_satanic_custom = __TS__Decorate( + modifier_item_satanic_custom, + modifier_item_satanic_custom, + {registerModifier(nil)}, + {kind = "class", name = "modifier_item_satanic_custom"} +) +____exports.modifier_item_satanic_custom = modifier_item_satanic_custom +____exports.modifier_item_satanic_custom_unholy = __TS__Class() +local modifier_item_satanic_custom_unholy = ____exports.modifier_item_satanic_custom_unholy +modifier_item_satanic_custom_unholy.name = "modifier_item_satanic_custom_unholy" +modifier_item_satanic_custom_unholy.____file_path = "scripts/vscripts/items/default_items/item_satanic_custom.lua" +__TS__ClassExtends(modifier_item_satanic_custom_unholy, BaseModifier) +function modifier_item_satanic_custom_unholy.prototype.IsHidden(self) + return false +end +function modifier_item_satanic_custom_unholy.prototype.IsPurgable(self) + return true +end +function modifier_item_satanic_custom_unholy.prototype.IsDebuff(self) + return false +end +function modifier_item_satanic_custom_unholy.prototype.GetTexture(self) + return "item_satanic" +end +function modifier_item_satanic_custom_unholy.prototype.GetEffectName(self) + return SATANIC_BUFF_PARTICLE +end +function modifier_item_satanic_custom_unholy.prototype.GetEffectAttachType(self) + return PATTACH_ABSORIGIN_FOLLOW +end +function modifier_item_satanic_custom_unholy.prototype.OnCreated(self) + if not IsServer() then + return + end + local hero = self:GetParent() + local bonus = self:GetAbility():GetSpecialValueFor("unholy_lifesteal") + if bonus > 0 then + addPhysicalVampirism(nil, hero, bonus) + end +end +function modifier_item_satanic_custom_unholy.prototype.OnDestroy(self) + if not IsServer() then + return + end + local hero = self:GetParent() + local bonus = self:GetAbility():GetSpecialValueFor("unholy_lifesteal") + if bonus > 0 then + reducePhysicalVampirism(nil, hero, bonus) + end +end +modifier_item_satanic_custom_unholy = __TS__Decorate( + modifier_item_satanic_custom_unholy, + modifier_item_satanic_custom_unholy, + {registerModifier(nil)}, + {kind = "class", name = "modifier_item_satanic_custom_unholy"} +) +____exports.modifier_item_satanic_custom_unholy = modifier_item_satanic_custom_unholy +return ____exports diff --git a/scripts/vscripts/items/default_items/item_skadi_custom.lua b/scripts/vscripts/items/default_items/item_skadi_custom.lua new file mode 100644 index 0000000..4c4544f --- /dev/null +++ b/scripts/vscripts/items/default_items/item_skadi_custom.lua @@ -0,0 +1,158 @@ +local ____lualib = require("lualib_bundle") +local __TS__Class = ____lualib.__TS__Class +local __TS__ClassExtends = ____lualib.__TS__ClassExtends +local __TS__Decorate = ____lualib.__TS__Decorate +local ____exports = {} +local ____dota_ts_adapter = require("lib.dota_ts_adapter") +local BaseItem = ____dota_ts_adapter.BaseItem +local BaseModifier = ____dota_ts_adapter.BaseModifier +local registerAbility = ____dota_ts_adapter.registerAbility +local registerModifier = ____dota_ts_adapter.registerModifier +local ____modifier_stats_multiplier = require("modifiers.modifier_stats_multiplier") +local removeStatsMultiplierSource = ____modifier_stats_multiplier.removeStatsMultiplierSource +local setStatsMultiplierSource = ____modifier_stats_multiplier.setStatsMultiplierSource +local SKADI_CUSTOM_SOURCE_PREFIX = "item_skadi_custom" +____exports.item_skadi_custom = __TS__Class() +local item_skadi_custom = ____exports.item_skadi_custom +item_skadi_custom.name = "item_skadi_custom" +item_skadi_custom.____file_path = "scripts/vscripts/items/default_items/item_skadi_custom.lua" +__TS__ClassExtends(item_skadi_custom, BaseItem) +function item_skadi_custom.prototype.GetIntrinsicModifierName(self) + return "modifier_item_skadi_custom" +end +item_skadi_custom = __TS__Decorate( + item_skadi_custom, + item_skadi_custom, + {registerAbility(nil)}, + {kind = "class", name = "item_skadi_custom"} +) +____exports.item_skadi_custom = item_skadi_custom +____exports.modifier_item_skadi_custom = __TS__Class() +local modifier_item_skadi_custom = ____exports.modifier_item_skadi_custom +modifier_item_skadi_custom.name = "modifier_item_skadi_custom" +modifier_item_skadi_custom.____file_path = "scripts/vscripts/items/default_items/item_skadi_custom.lua" +__TS__ClassExtends(modifier_item_skadi_custom, BaseModifier) +function modifier_item_skadi_custom.prototype.____constructor(self, ...) + BaseModifier.prototype.____constructor(self, ...) + self.statsSourceId = "" +end +function modifier_item_skadi_custom.prototype.IsHidden(self) + return true +end +function modifier_item_skadi_custom.prototype.IsPurgable(self) + return false +end +function modifier_item_skadi_custom.prototype.RemoveOnDeath(self) + return false +end +function modifier_item_skadi_custom.prototype.GetAttributes(self) + return MODIFIER_ATTRIBUTE_MULTIPLE +end +function modifier_item_skadi_custom.prototype.OnCreated(self) + local hero = self:GetParent() + if not (hero and hero:IsRealHero()) then + return + end + self.statsSourceId = (SKADI_CUSTOM_SOURCE_PREFIX .. "_") .. tostring(hero:entindex()) + local ability = self:GetAbility() + setStatsMultiplierSource( + nil, + hero, + self.statsSourceId, + function() + if not hero or not IsValidEntity(hero) or not hero:IsRealHero() then + return 0 + end + return ability and ability:GetSpecialValueFor("all_stats_pct") or 0 + end, + "all_stats_pct" + ) +end +function modifier_item_skadi_custom.prototype.OnDestroy(self) + if self.statsSourceId == "" then + return + end + local hero = self:GetParent() + if hero and IsValidEntity(hero) then + removeStatsMultiplierSource(nil, hero, self.statsSourceId) + end + self.statsSourceId = "" +end +function modifier_item_skadi_custom.prototype.DeclareFunctions(self) + return {MODIFIER_PROPERTY_STATS_STRENGTH_BONUS, MODIFIER_PROPERTY_STATS_AGILITY_BONUS, MODIFIER_PROPERTY_STATS_INTELLECT_BONUS} +end +function modifier_item_skadi_custom.prototype.GetModifierBonusStats_Strength(self) + return self:GetAbility():GetSpecialValueFor("bonus_all_stats") +end +function modifier_item_skadi_custom.prototype.GetModifierBonusStats_Agility(self) + return self:GetAbility():GetSpecialValueFor("bonus_all_stats") +end +function modifier_item_skadi_custom.prototype.GetModifierBonusStats_Intellect(self) + return self:GetAbility():GetSpecialValueFor("bonus_all_stats") +end +function modifier_item_skadi_custom.prototype.OnAttackLanded(self, event) + if event.attacker ~= self:GetParent() then + return + end + if not event.target or event.target:IsBuilding() or event.target:IsOther() then + return + end + if event.target ~= nil then + local ____self_8 = event.target + local ____self_8_AddNewModifier_9 = ____self_8.AddNewModifier + local ____temp_6 = self:GetParent() + local ____temp_7 = self:GetAbility() + local ____opt_4 = self:GetAbility() + ____self_8_AddNewModifier_9( + ____self_8, + ____temp_6, + ____temp_7, + "modifier_item_skadi_custom_debuff", + {duration = ____opt_4 and ____opt_4:GetSpecialValueFor("debuff_duration")} + ) + end +end +modifier_item_skadi_custom = __TS__Decorate( + modifier_item_skadi_custom, + modifier_item_skadi_custom, + {registerModifier(nil)}, + {kind = "class", name = "modifier_item_skadi_custom"} +) +____exports.modifier_item_skadi_custom = modifier_item_skadi_custom +____exports.modifier_item_skadi_custom_debuff = __TS__Class() +local modifier_item_skadi_custom_debuff = ____exports.modifier_item_skadi_custom_debuff +modifier_item_skadi_custom_debuff.name = "modifier_item_skadi_custom_debuff" +modifier_item_skadi_custom_debuff.____file_path = "scripts/vscripts/items/default_items/item_skadi_custom.lua" +__TS__ClassExtends(modifier_item_skadi_custom_debuff, BaseModifier) +function modifier_item_skadi_custom_debuff.prototype.IsDebuff(self) + return true +end +function modifier_item_skadi_custom_debuff.prototype.IsPurgable(self) + return true +end +function modifier_item_skadi_custom_debuff.prototype.IsHidden(self) + return true +end +function modifier_item_skadi_custom_debuff.prototype.GetModifierProjectileName(self) + return "particles/items2_fx/skadi_projectile.vpcf" +end +function modifier_item_skadi_custom_debuff.prototype.DeclareFunctions(self) + return {MODIFIER_PROPERTY_PHYSICAL_ARMOR_BONUS, MODIFIER_PROPERTY_HEALTH_REGEN_PERCENTAGE, MODIFIER_PROPERTY_MOVESPEED_BONUS_PERCENTAGE} +end +function modifier_item_skadi_custom_debuff.prototype.GetModifierMoveSpeedBonus_Percentage(self) + return self:GetAbility():GetSpecialValueFor("movespeed_reduction") +end +function modifier_item_skadi_custom_debuff.prototype.GetModifierHealthRegenPercentage(self) + return 1 + (self:GetAbility():GetSpecialValueFor("health_reduction") - 100) * 0.01 +end +function modifier_item_skadi_custom_debuff.prototype.GetModifierPhysicalArmorBonus(self, event) + return self:GetAbility():GetSpecialValueFor("armor_reduction") +end +modifier_item_skadi_custom_debuff = __TS__Decorate( + modifier_item_skadi_custom_debuff, + modifier_item_skadi_custom_debuff, + {registerModifier(nil)}, + {kind = "class", name = "modifier_item_skadi_custom_debuff"} +) +____exports.modifier_item_skadi_custom_debuff = modifier_item_skadi_custom_debuff +return ____exports diff --git a/scripts/vscripts/items/default_items/item_soul_devourer_staff.lua b/scripts/vscripts/items/default_items/item_soul_devourer_staff.lua new file mode 100644 index 0000000..cf84423 --- /dev/null +++ b/scripts/vscripts/items/default_items/item_soul_devourer_staff.lua @@ -0,0 +1,196 @@ +local ____lualib = require("lualib_bundle") +local __TS__Class = ____lualib.__TS__Class +local __TS__ClassExtends = ____lualib.__TS__ClassExtends +local __TS__Decorate = ____lualib.__TS__Decorate +local ____exports = {} +local ____dota_ts_adapter = require("lib.dota_ts_adapter") +local BaseItem = ____dota_ts_adapter.BaseItem +local BaseModifier = ____dota_ts_adapter.BaseModifier +local registerAbility = ____dota_ts_adapter.registerAbility +local registerModifier = ____dota_ts_adapter.registerModifier +____exports.item_soul_devourer_staff = __TS__Class() +local item_soul_devourer_staff = ____exports.item_soul_devourer_staff +item_soul_devourer_staff.name = "item_soul_devourer_staff" +item_soul_devourer_staff.____file_path = "scripts/vscripts/items/default_items/item_soul_devourer_staff.lua" +__TS__ClassExtends(item_soul_devourer_staff, BaseItem) +function item_soul_devourer_staff.prototype.GetIntrinsicModifierName(self) + return "modifier_soul_devourer_staff" +end +item_soul_devourer_staff = __TS__Decorate( + item_soul_devourer_staff, + item_soul_devourer_staff, + {registerAbility(nil)}, + {kind = "class", name = "item_soul_devourer_staff"} +) +____exports.item_soul_devourer_staff = item_soul_devourer_staff +____exports.modifier_soul_devourer_staff = __TS__Class() +local modifier_soul_devourer_staff = ____exports.modifier_soul_devourer_staff +modifier_soul_devourer_staff.name = "modifier_soul_devourer_staff" +modifier_soul_devourer_staff.____file_path = "scripts/vscripts/items/default_items/item_soul_devourer_staff.lua" +__TS__ClassExtends(modifier_soul_devourer_staff, BaseModifier) +function modifier_soul_devourer_staff.prototype.IsHidden(self) + return true +end +function modifier_soul_devourer_staff.prototype.IsDebuff(self) + return false +end +function modifier_soul_devourer_staff.prototype.IsPurgable(self) + return false +end +function modifier_soul_devourer_staff.prototype.RemoveOnDeath(self) + return false +end +function modifier_soul_devourer_staff.prototype.DeclareFunctions(self) + return { + MODIFIER_PROPERTY_STATS_INTELLECT_BONUS, + MODIFIER_PROPERTY_CAST_RANGE_BONUS, + MODIFIER_PROPERTY_MANA_REGEN_CONSTANT, + MODIFIER_PROPERTY_MANACOST_PERCENTAGE_STACKING, + MODIFIER_EVENT_ON_DEATH, + MODIFIER_PROPERTY_MANA_BONUS + } +end +function modifier_soul_devourer_staff.prototype.OnDeath(self, event) + if event.attacker ~= self:GetCaster() then + return + end + local mana = self:GetCaster():GetMaxMana() * self:GetAbility():GetSpecialValueFor("kill_mana") + self:GetCaster():GiveMana(mana) + SendOverheadEventMessage( + nil, + OVERHEAD_ALERT_MANA_ADD, + self:GetCaster(), + mana, + self:GetCaster():GetPlayerOwner() + ) + local caster = self:GetCaster() + local ability = self:GetAbility() + if not ability then + return + end + local blessing = caster:FindModifierByName("modifier_soul_devourer_staff_buff") + if not blessing then + blessing = caster:AddNewModifier( + self:GetParent(), + ability, + "modifier_soul_devourer_staff_buff", + {} + ) + end + if blessing ~= nil then + blessing:IncrementStackCount() + end +end +function modifier_soul_devourer_staff.prototype.GetModifierPercentageManacostStacking(self) + return self:GetAbility():GetSpecialValueFor("bonus_manacost_reduction") +end +function modifier_soul_devourer_staff.prototype.GetModifierBonusStats_Intellect(self) + return self:GetAbility():GetSpecialValueFor("bonus_intellect") +end +function modifier_soul_devourer_staff.prototype.GetModifierCastRangeBonus(self, event) + return self:GetAbility():GetSpecialValueFor("bonus_cast_range") +end +function modifier_soul_devourer_staff.prototype.GetModifierConstantManaRegen(self) + return self:GetAbility():GetSpecialValueFor("bonus_mana_regen") +end +function modifier_soul_devourer_staff.prototype.GetModifierManaBonus(self) + return self:GetAbility():GetSpecialValueFor("bonus_mana_special") +end +modifier_soul_devourer_staff = __TS__Decorate( + modifier_soul_devourer_staff, + modifier_soul_devourer_staff, + {registerModifier(nil)}, + {kind = "class", name = "modifier_soul_devourer_staff"} +) +____exports.modifier_soul_devourer_staff = modifier_soul_devourer_staff +____exports.modifier_soul_devourer_staff_buff = __TS__Class() +local modifier_soul_devourer_staff_buff = ____exports.modifier_soul_devourer_staff_buff +modifier_soul_devourer_staff_buff.name = "modifier_soul_devourer_staff_buff" +modifier_soul_devourer_staff_buff.____file_path = "scripts/vscripts/items/default_items/item_soul_devourer_staff.lua" +__TS__ClassExtends(modifier_soul_devourer_staff_buff, BaseModifier) +function modifier_soul_devourer_staff_buff.prototype.IsHidden(self) + if self:GetCaster():HasModifier("modifier_soul_devourer_staff") then + return false + end + return true +end +function modifier_soul_devourer_staff_buff.prototype.IsDebuff(self) + return false +end +function modifier_soul_devourer_staff_buff.prototype.IsPurgable(self) + return false +end +function modifier_soul_devourer_staff_buff.prototype.RemoveOnDeath(self) + return false +end +function modifier_soul_devourer_staff_buff.prototype.GetTexture(self) + return "default_items/soul_devourer_staff" +end +function modifier_soul_devourer_staff_buff.prototype.refreshStackGrowthHudDuration(self, ability) + local interval = ability:GetSpecialValueFor("stack_growth_interval") > 0 and ability:GetSpecialValueFor("stack_growth_interval") or 60 + local baseDur = ability:GetSpecialValueFor("stack_growth_duration") + if baseDur <= 0 then + baseDur = interval + 0.5 + elseif baseDur <= interval then + baseDur = interval + 0.5 + end + self:SetDuration(baseDur + 0.2, true) +end +function modifier_soul_devourer_staff_buff.prototype.OnCreated(self) + if not IsServer() then + return + end + local ability = self:GetAbility() + if not ability then + return + end + local interval = ability:GetSpecialValueFor("stack_growth_interval") > 0 and ability:GetSpecialValueFor("stack_growth_interval") or 60 + self:StartIntervalThink(interval) + self:refreshStackGrowthHudDuration(ability) +end +function modifier_soul_devourer_staff_buff.prototype.OnIntervalThink(self) + if not IsServer() then + return + end + local parent = self:GetParent() + if not parent or not parent:IsAlive() then + return + end + local caster = self:GetCaster() + local ability = self:GetAbility() + if not caster or not ability then + return + end + if not caster:HasModifier("modifier_soul_devourer_staff") then + return + end + local stacks = self:GetStackCount() + if stacks <= 0 then + return + end + local pct = ability:GetSpecialValueFor("stack_growth_percent") + local growthPct = pct > 0 and pct or 20 + local mult = 1 + growthPct / 100 + local newStacks = math.ceil(stacks * mult) + if newStacks > stacks then + self:SetStackCount(newStacks) + end + self:refreshStackGrowthHudDuration(ability) +end +function modifier_soul_devourer_staff_buff.prototype.DeclareFunctions(self) + return {MODIFIER_PROPERTY_MANA_BONUS} +end +function modifier_soul_devourer_staff_buff.prototype.GetModifierManaBonus(self) + if self:GetCaster():HasModifier("modifier_soul_devourer_staff") then + return self:GetAbility():GetSpecialValueFor("bonus_mana") * self:GetStackCount() + end + return 0 +end +modifier_soul_devourer_staff_buff = __TS__Decorate( + modifier_soul_devourer_staff_buff, + modifier_soul_devourer_staff_buff, + {registerModifier(nil)}, + {kind = "class", name = "modifier_soul_devourer_staff_buff"} +) +____exports.modifier_soul_devourer_staff_buff = modifier_soul_devourer_staff_buff +return ____exports diff --git a/scripts/vscripts/items/default_items/item_storm.lua b/scripts/vscripts/items/default_items/item_storm.lua new file mode 100644 index 0000000..6502ae6 --- /dev/null +++ b/scripts/vscripts/items/default_items/item_storm.lua @@ -0,0 +1,166 @@ +local ____lualib = require("lualib_bundle") +local __TS__Class = ____lualib.__TS__Class +local __TS__ClassExtends = ____lualib.__TS__ClassExtends +local __TS__Decorate = ____lualib.__TS__Decorate +local ____exports = {} +local ____dota_ts_adapter = require("lib.dota_ts_adapter") +local BaseItem = ____dota_ts_adapter.BaseItem +local BaseModifier = ____dota_ts_adapter.BaseModifier +local registerAbility = ____dota_ts_adapter.registerAbility +local registerModifier = ____dota_ts_adapter.registerModifier +____exports.item_storm = __TS__Class() +local item_storm = ____exports.item_storm +item_storm.name = "item_storm" +item_storm.____file_path = "scripts/vscripts/items/default_items/item_storm.lua" +__TS__ClassExtends(item_storm, BaseItem) +function item_storm.prototype.GetIntrinsicModifierName(self) + return "modifier_storm" +end +item_storm = __TS__Decorate( + item_storm, + item_storm, + {registerAbility(nil)}, + {kind = "class", name = "item_storm"} +) +____exports.item_storm = item_storm +____exports.modifier_storm = __TS__Class() +local modifier_storm = ____exports.modifier_storm +modifier_storm.name = "modifier_storm" +modifier_storm.____file_path = "scripts/vscripts/items/default_items/item_storm.lua" +__TS__ClassExtends(modifier_storm, BaseModifier) +function modifier_storm.prototype.IsHidden(self) + return true +end +function modifier_storm.prototype.IsDebuff(self) + return false +end +function modifier_storm.prototype.IsPurgable(self) + return false +end +function modifier_storm.prototype.DeclareFunctions(self) + return {MODIFIER_PROPERTY_PREATTACK_BONUS_DAMAGE, MODIFIER_EVENT_ON_ATTACK_LANDED} +end +function modifier_storm.prototype.GetModifierPreAttack_BonusDamage(self) + return self:GetAbility():GetSpecialValueFor("bonus_damage") +end +function modifier_storm.prototype.OnAttackLanded(self, event) + if event.attacker ~= self:GetParent() then + return + end + if RandomInt(1, 100) > self:GetAbility():GetSpecialValueFor("chance") then + return + end + local ____opt_0 = self:GetAbility() + if (____opt_0 and ____opt_0:IsCooldownReady()) == false then + return + end + self:GetAbility():GetSpecialValueFor("bolt_damage") + event.target:AddNewModifier( + self:GetParent(), + self:GetAbility(), + "modifier_storm_dps", + { + duration = 0.26, + attacker_entindex = self:GetCaster():entindex() + } + ) + self:GetAbility():StartCooldown(self:GetAbility():GetSpecialValueFor("ability_cooldown")) +end +modifier_storm = __TS__Decorate( + modifier_storm, + modifier_storm, + {registerModifier(nil)}, + {kind = "class", name = "modifier_storm"} +) +____exports.modifier_storm = modifier_storm +____exports.modifier_storm_dps = __TS__Class() +local modifier_storm_dps = ____exports.modifier_storm_dps +modifier_storm_dps.name = "modifier_storm_dps" +modifier_storm_dps.____file_path = "scripts/vscripts/items/default_items/item_storm.lua" +__TS__ClassExtends(modifier_storm_dps, BaseModifier) +function modifier_storm_dps.prototype.IsHidden(self) + return false +end +function modifier_storm_dps.prototype.IsDebuff(self) + return true +end +function modifier_storm_dps.prototype.IsPurgable(self) + return true +end +function modifier_storm_dps.prototype.OnCreated(self, params) + self:SetStackCount(3) + self:StartIntervalThink(0.25) +end +function modifier_storm_dps.prototype.OnIntervalThink(self) + if self:GetStackCount() == 0 then + return + end + self:SetDuration(0.26, true) + self:SetStackCount(self:GetStackCount() - 1) + local victim = self:GetParent() + local attacker = self:GetCaster() + local particle = ParticleManager:CreateParticle("particles/items_fx/chain_lightning.vpcf", PATTACH_ABSORIGIN_FOLLOW, victim) + if attacker then + ParticleManager:SetParticleControlEnt( + particle, + 0, + attacker, + PATTACH_POINT_FOLLOW, + "attach_hitloc", + attacker:GetAbsOrigin(), + true + ) + else + ParticleManager:SetParticleControl( + particle, + 0, + victim:GetAbsOrigin() + ) + end + ParticleManager:SetParticleControlEnt( + particle, + 1, + victim, + PATTACH_POINT_FOLLOW, + "attach_hitloc", + victim:GetAbsOrigin(), + true + ) + ParticleManager:SetParticleControl( + particle, + 2, + Vector(1, 1, 1) + ) + ParticleManager:ReleaseParticleIndex(particle) + local ability = self:GetAbility() + if IsClient() then + return + end + if ability ~= nil and victim ~= nil and attacker ~= nil then + ApplyDamage({ + victim = victim, + attacker = attacker, + damage = ability:GetSpecialValueFor("bolt_damage"), + damage_type = DAMAGE_TYPE_MAGICAL, + damage_flags = DOTA_DAMAGE_FLAG_NONE, + ability = ability + }) + end + self:GetParent():AddNewModifier( + self:GetParent(), + self:GetAbility(), + "modifier_stunned", + {duration = self:GetAbility():GetSpecialValueFor("stun")} + ) +end +function modifier_storm_dps.prototype.GetTexture(self) + return "default_items/storm" +end +modifier_storm_dps = __TS__Decorate( + modifier_storm_dps, + modifier_storm_dps, + {registerModifier(nil)}, + {kind = "class", name = "modifier_storm_dps"} +) +____exports.modifier_storm_dps = modifier_storm_dps +return ____exports diff --git a/scripts/vscripts/items/default_items/item_test.lua b/scripts/vscripts/items/default_items/item_test.lua new file mode 100644 index 0000000..b0f81f1 --- /dev/null +++ b/scripts/vscripts/items/default_items/item_test.lua @@ -0,0 +1,211 @@ +local ____lualib = require("lualib_bundle") +local __TS__Class = ____lualib.__TS__Class +local __TS__ClassExtends = ____lualib.__TS__ClassExtends +local __TS__ArrayMap = ____lualib.__TS__ArrayMap +local __TS__Decorate = ____lualib.__TS__Decorate +local ____exports = {} +local ____dota_ts_adapter = require("lib.dota_ts_adapter") +local BaseItem = ____dota_ts_adapter.BaseItem +local BaseModifier = ____dota_ts_adapter.BaseModifier +local registerModifier = ____dota_ts_adapter.registerModifier +local ____WaveManager = require("WaveManager") +local WaveManager = ____WaveManager.WaveManager +local ____dota_ts_adapter = require("lib.dota_ts_adapter") +local registerAbility = ____dota_ts_adapter.registerAbility +local ____crystal_currency = require("crystal_currency") +local CrystalCurrency = ____crystal_currency.CrystalCurrency +local ____dps_tracker = require("abilities.modifiers.dps_tracker") +local dps_tracker = ____dps_tracker.dps_tracker +____exports.item_test = __TS__Class() +local item_test = ____exports.item_test +item_test.name = "item_test" +item_test.____file_path = "scripts/vscripts/items/default_items/item_test.lua" +__TS__ClassExtends(item_test, BaseItem) +function item_test.prototype.OnSpellStart(self) + if not IsServer() then + return + end + local caster = self:GetCaster() + local winnerTeam = caster:GetTeamNumber() + local readyCheckStarted = WaveManager:getInstance():StartCutsceneReadyCheck("camera_start_ending") + if not readyCheckStarted then + local mgr = GameRules.CutsceneManager + if mgr and not mgr:isCutsceneActive() then + mgr:startScene("camera_start_ending", {force = true}) + GameRules:SendCustomMessage("[item_test] Фолбэк: катсцена запущена напрямую", 0, 0) + else + GameRules:SendCustomMessage("[item_test] Не удалось запустить катсцену", 0, 0) + end + else + GameRules:SendCustomMessage("[item_test] Открыт ready-check катсцены", 0, 0) + end + self:GetCaster():AddAbility(dps_tracker.name) + self:GetCaster():AddNewModifier( + self:GetCaster(), + self, + "modifier_effects_effect", + {} + ) + local cursorTarget = self:GetCursorTarget() + print("[item_test] Cursor target: " .. (cursorTarget and cursorTarget:GetName() or "none")) + local owner = self:GetCaster() + local modifiers = owner:FindAllModifiers() + local modifierNames = __TS__ArrayMap( + modifiers, + function(____, mod) return mod:GetName() end + ) + print("[item_test] Modifiers on owner: " .. table.concat(modifierNames, ", ")) + CrystalCurrency:getInstance():addCrystals( + self:GetCaster():GetPlayerID(), + 100 + ) + addLuck( + nil, + self:GetCaster(), + 10 + ) + addCritMult( + nil, + self:GetCaster(), + 30 + ) + if self:GetCaster():HasModifier("modifier_test_inf") then + self:GetCaster():RemoveModifierByName("modifier_test_inf") + else + self:GetCaster():AddNewModifier( + self:GetCaster(), + self, + "modifier_test_inf", + {} + ) + end +end +item_test = __TS__Decorate( + item_test, + item_test, + {registerAbility(nil)}, + {kind = "class", name = "item_test"} +) +____exports.item_test = item_test +____exports.modifier_test = __TS__Class() +local modifier_test = ____exports.modifier_test +modifier_test.name = "modifier_test" +modifier_test.____file_path = "scripts/vscripts/items/default_items/item_test.lua" +__TS__ClassExtends(modifier_test, BaseModifier) +function modifier_test.prototype.IsHidden(self) + return true +end +function modifier_test.prototype.IsDebuff(self) + return false +end +function modifier_test.prototype.IsPurgable(self) + return false +end +function modifier_test.prototype.GetAttributes(self) + return MODIFIER_ATTRIBUTE_MULTIPLE +end +function modifier_test.prototype.OnCreated(self) + if not IsServer() then + return + end + local stackingCritMod = self:GetParent():FindModifierByName("modifier_stacking_crit") + if stackingCritMod then + local ability = self:GetAbility() + stackingCritMod:AddCustomCrit(0, 500, "item_test", ability) + end +end +function modifier_test.prototype.OnDestroy(self) + if IsClient() then + return + end + local parent = self:GetParent() + if not parent or not IsValidEntity(parent) then + return + end + local stackingCritModifier = parent:FindModifierByName("modifier_stacking_crit") + if not stackingCritModifier then + return + end + local ability = self:GetAbility() + stackingCritModifier:RemoveCrit("item_test", ability) +end +function modifier_test.prototype.GetStatusEffectName(self) + return "particles/status_fx/status_effect_abaddon_borrowed_time.vpcf" +end +function modifier_test.prototype.GetEffectName(self) + return "particles/units/heroes/hero_abaddon/abaddon_borrowed_time.vpcf" +end +function modifier_test.prototype.StatusEffectPriority(self) + return 10 +end +modifier_test = __TS__Decorate( + modifier_test, + modifier_test, + {registerModifier(nil)}, + {kind = "class", name = "modifier_test"} +) +____exports.modifier_test = modifier_test +____exports.modifier_test_inf = __TS__Class() +local modifier_test_inf = ____exports.modifier_test_inf +modifier_test_inf.name = "modifier_test_inf" +modifier_test_inf.____file_path = "scripts/vscripts/items/default_items/item_test.lua" +__TS__ClassExtends(modifier_test_inf, BaseModifier) +function modifier_test_inf.prototype.____constructor(self, ...) + BaseModifier.prototype.____constructor(self, ...) + self.bonusStrength = 1000000 + self.bonusAgility = 1000000 + self.bonusIntelligence = 1000000 +end +function modifier_test_inf.prototype.DeclareFunctions(self) + return {MODIFIER_PROPERTY_STATS_STRENGTH_BONUS, MODIFIER_PROPERTY_STATS_AGILITY_BONUS, MODIFIER_PROPERTY_STATS_INTELLECT_BONUS} +end +function modifier_test_inf.prototype.GetModifierBonusStats_Strength(self) + return self.bonusStrength +end +function modifier_test_inf.prototype.GetModifierBonusStats_Agility(self) + return self.bonusAgility +end +function modifier_test_inf.prototype.GetModifierBonusStats_Intellect(self) + return self.bonusIntelligence +end +modifier_test_inf = __TS__Decorate( + modifier_test_inf, + modifier_test_inf, + {registerModifier(nil)}, + {kind = "class", name = "modifier_test_inf"} +) +____exports.modifier_test_inf = modifier_test_inf +____exports.modifier_test_agi = __TS__Class() +local modifier_test_agi = ____exports.modifier_test_agi +modifier_test_agi.name = "modifier_test_agi" +modifier_test_agi.____file_path = "scripts/vscripts/items/default_items/item_test.lua" +__TS__ClassExtends(modifier_test_agi, BaseModifier) +function modifier_test_agi.prototype.____constructor(self, ...) + BaseModifier.prototype.____constructor(self, ...) + self.AgiPct = 1000 + self.Lock = false +end +function modifier_test_agi.prototype.DeclareFunctions(self) + return {MODIFIER_PROPERTY_STATS_AGILITY_BONUS} +end +function modifier_test_agi.prototype.GetModifierBonusStats_Agility(self) + if not IsServer() then + return 0 + end + if self.Lock then + return 0 + end + self.Lock = true + local agi = self:GetParent():GetAgility() + self.Lock = false + local bonus = self.AgiPct * agi / 100 + return bonus +end +modifier_test_agi = __TS__Decorate( + modifier_test_agi, + modifier_test_agi, + {registerModifier(nil)}, + {kind = "class", name = "modifier_test_agi"} +) +____exports.modifier_test_agi = modifier_test_agi +return ____exports diff --git a/scripts/vscripts/items/default_items/item_ultimate_crown.lua b/scripts/vscripts/items/default_items/item_ultimate_crown.lua new file mode 100644 index 0000000..8f594ba --- /dev/null +++ b/scripts/vscripts/items/default_items/item_ultimate_crown.lua @@ -0,0 +1,106 @@ +local ____lualib = require("lualib_bundle") +local __TS__Class = ____lualib.__TS__Class +local __TS__ClassExtends = ____lualib.__TS__ClassExtends +local __TS__Decorate = ____lualib.__TS__Decorate +local ____exports = {} +local ____dota_ts_adapter = require("lib.dota_ts_adapter") +local BaseItem = ____dota_ts_adapter.BaseItem +local BaseModifier = ____dota_ts_adapter.BaseModifier +local registerAbility = ____dota_ts_adapter.registerAbility +local registerModifier = ____dota_ts_adapter.registerModifier +local ____modifier_stats_multiplier = require("modifiers.modifier_stats_multiplier") +local removeStatsMultiplierSource = ____modifier_stats_multiplier.removeStatsMultiplierSource +local setStatsMultiplierSource = ____modifier_stats_multiplier.setStatsMultiplierSource +local ULTIMATE_CROWN_SOURCE_PREFIX = "item_ultimate_crown" +____exports.item_ultimate_crown = __TS__Class() +local item_ultimate_crown = ____exports.item_ultimate_crown +item_ultimate_crown.name = "item_ultimate_crown" +item_ultimate_crown.____file_path = "scripts/vscripts/items/default_items/item_ultimate_crown.lua" +__TS__ClassExtends(item_ultimate_crown, BaseItem) +function item_ultimate_crown.prototype.GetIntrinsicModifierName(self) + return ____exports.modifier_item_ultimate_crown.name +end +item_ultimate_crown = __TS__Decorate( + item_ultimate_crown, + item_ultimate_crown, + {registerAbility(nil)}, + {kind = "class", name = "item_ultimate_crown"} +) +____exports.item_ultimate_crown = item_ultimate_crown +____exports.modifier_item_ultimate_crown = __TS__Class() +local modifier_item_ultimate_crown = ____exports.modifier_item_ultimate_crown +modifier_item_ultimate_crown.name = "modifier_item_ultimate_crown" +modifier_item_ultimate_crown.____file_path = "scripts/vscripts/items/default_items/item_ultimate_crown.lua" +__TS__ClassExtends(modifier_item_ultimate_crown, BaseModifier) +function modifier_item_ultimate_crown.prototype.____constructor(self, ...) + BaseModifier.prototype.____constructor(self, ...) + self.statsSourceId = "" +end +function modifier_item_ultimate_crown.prototype.IsHidden(self) + return true +end +function modifier_item_ultimate_crown.prototype.IsPurgable(self) + return false +end +function modifier_item_ultimate_crown.prototype.RemoveOnDeath(self) + return false +end +function modifier_item_ultimate_crown.prototype.GetAttributes(self) + return MODIFIER_ATTRIBUTE_MULTIPLE +end +function modifier_item_ultimate_crown.prototype.OnCreated(self) + if not IsServer() then + return + end + local hero = self:GetParent() + if not (hero and hero:IsRealHero()) then + return + end + self.statsSourceId = (ULTIMATE_CROWN_SOURCE_PREFIX .. "_") .. tostring(hero:entindex()) + local ability = self:GetAbility() + setStatsMultiplierSource( + nil, + hero, + self.statsSourceId, + function() + if not hero or not IsValidEntity(hero) or not hero:IsRealHero() then + return 0 + end + return ability and ability:GetSpecialValueFor("all_stats_pct") or 0 + end, + "all_stats_pct" + ) +end +function modifier_item_ultimate_crown.prototype.OnDestroy(self) + if not IsServer() then + return + end + if self.statsSourceId == "" then + return + end + local hero = self:GetParent() + if hero and IsValidEntity(hero) then + removeStatsMultiplierSource(nil, hero, self.statsSourceId) + end + self.statsSourceId = "" +end +function modifier_item_ultimate_crown.prototype.DeclareFunctions(self) + return {MODIFIER_PROPERTY_STATS_STRENGTH_BONUS, MODIFIER_PROPERTY_STATS_AGILITY_BONUS, MODIFIER_PROPERTY_STATS_INTELLECT_BONUS} +end +function modifier_item_ultimate_crown.prototype.GetModifierBonusStats_Strength(self) + return self:GetAbility():GetSpecialValueFor("bonus_all_stats") +end +function modifier_item_ultimate_crown.prototype.GetModifierBonusStats_Agility(self) + return self:GetAbility():GetSpecialValueFor("bonus_all_stats") +end +function modifier_item_ultimate_crown.prototype.GetModifierBonusStats_Intellect(self) + return self:GetAbility():GetSpecialValueFor("bonus_all_stats") +end +modifier_item_ultimate_crown = __TS__Decorate( + modifier_item_ultimate_crown, + modifier_item_ultimate_crown, + {registerModifier(nil)}, + {kind = "class", name = "modifier_item_ultimate_crown"} +) +____exports.modifier_item_ultimate_crown = modifier_item_ultimate_crown +return ____exports diff --git a/scripts/vscripts/items/default_items/item_vampire_claw.lua b/scripts/vscripts/items/default_items/item_vampire_claw.lua new file mode 100644 index 0000000..77d0331 --- /dev/null +++ b/scripts/vscripts/items/default_items/item_vampire_claw.lua @@ -0,0 +1,160 @@ +local ____lualib = require("lualib_bundle") +local __TS__Class = ____lualib.__TS__Class +local __TS__ClassExtends = ____lualib.__TS__ClassExtends +local __TS__Decorate = ____lualib.__TS__Decorate +local ____exports = {} +local ____dota_ts_adapter = require("lib.dota_ts_adapter") +local BaseItem = ____dota_ts_adapter.BaseItem +local BaseModifier = ____dota_ts_adapter.BaseModifier +local registerAbility = ____dota_ts_adapter.registerAbility +local registerModifier = ____dota_ts_adapter.registerModifier +local ____vampirism = require("utils.vampirism") +local addPhysicalVampirism = ____vampirism.addPhysicalVampirism +local reducePhysicalVampirism = ____vampirism.reducePhysicalVampirism +____exports.item_vampire_claw = __TS__Class() +local item_vampire_claw = ____exports.item_vampire_claw +item_vampire_claw.name = "item_vampire_claw" +item_vampire_claw.____file_path = "scripts/vscripts/items/default_items/item_vampire_claw.lua" +__TS__ClassExtends(item_vampire_claw, BaseItem) +function item_vampire_claw.prototype.GetIntrinsicModifierName(self) + return "modifier_item_vampire_claw" +end +function item_vampire_claw.prototype.Precache(self, context) + PrecacheResource("particle", "particles/econ/items/bloodseeker/bloodseeker_eztzhok_weapon/bloodseeker_bloodbath_eztzhok.vpcf", context) +end +function item_vampire_claw.prototype.GetAbilityTextureName(self) + local charges = self:GetCurrentCharges() + if charges < 7 then + return "default_items/vampire_claw/vampire_claw_1" + elseif charges < 14 then + return "default_items/vampire_claw/vampire_claw_2" + elseif charges < 21 then + return "default_items/vampire_claw/vampire_claw_3" + else + return "default_items/vampire_claw/vampire_claw_4" + end +end +function item_vampire_claw.prototype.OnSpellStart(self) + local charges = self:GetCurrentCharges() + if charges <= 0 then + return + end + local caster = self:GetCaster() + local healing = charges * self:GetSpecialValueFor("heal_per_charge") + caster:Heal(healing, self) + self:SetCurrentCharges(0) + SendOverheadEventMessage( + nil, + OVERHEAD_ALERT_HEAL, + caster, + healing, + caster:GetPlayerOwner() + ) + local particle = ParticleManager:CreateParticle("particles/econ/items/bloodseeker/bloodseeker_eztzhok_weapon/bloodseeker_bloodbath_eztzhok.vpcf", PATTACH_ABSORIGIN_FOLLOW, caster) + ParticleManager:SetParticleControlEnt( + particle, + 1, + caster, + PATTACH_ABSORIGIN_FOLLOW, + "", + caster:GetAbsOrigin(), + true + ) + EmitSoundOn("hero_bloodseeker.bloodRite.silence", caster) +end +item_vampire_claw = __TS__Decorate( + item_vampire_claw, + item_vampire_claw, + {registerAbility(nil)}, + {kind = "class", name = "item_vampire_claw"} +) +____exports.item_vampire_claw = item_vampire_claw +____exports.modifier_item_vampire_claw = __TS__Class() +local modifier_item_vampire_claw = ____exports.modifier_item_vampire_claw +modifier_item_vampire_claw.name = "modifier_item_vampire_claw" +modifier_item_vampire_claw.____file_path = "scripts/vscripts/items/default_items/item_vampire_claw.lua" +__TS__ClassExtends(modifier_item_vampire_claw, BaseModifier) +function modifier_item_vampire_claw.prototype.____constructor(self, ...) + BaseModifier.prototype.____constructor(self, ...) + self.bonusStr = 0 + self.bonusDamage = 0 + self.chargesPerBossAttack = 0 + self.chargesPerCreepAttack = 0 + self.maxCharges = 0 +end +function modifier_item_vampire_claw.prototype.IsHidden(self) + return true +end +function modifier_item_vampire_claw.prototype.IsPurgable(self) + return false +end +function modifier_item_vampire_claw.prototype.IsPurgeException(self) + return false +end +function modifier_item_vampire_claw.prototype.RemoveOnDeath(self) + return false +end +function modifier_item_vampire_claw.prototype.IsDebuff(self) + return false +end +function modifier_item_vampire_claw.prototype.GetAttributes(self) + return MODIFIER_ATTRIBUTE_MULTIPLE +end +function modifier_item_vampire_claw.prototype.DeclareFunctions(self) + return {MODIFIER_PROPERTY_PREATTACK_BONUS_DAMAGE, MODIFIER_PROPERTY_STATS_STRENGTH_BONUS, MODIFIER_EVENT_ON_TAKEDAMAGE} +end +function modifier_item_vampire_claw.prototype.OnCreated(self, params) + self:OnRefresh() + local hero = self:GetParent() + local vampirism = self:GetAbility():GetSpecialValueFor("vampirism") + if vampirism > 0 then + addPhysicalVampirism(nil, hero, vampirism) + end +end +function modifier_item_vampire_claw.prototype.OnRefresh(self, params) + local ability = self:GetAbility() + if not ability or ability:IsNull() then + return + end + self.bonusStr = ability:GetSpecialValueFor("bonus_str") + self.bonusDamage = ability:GetSpecialValueFor("bonus_damage") + self.chargesPerBossAttack = ability:GetSpecialValueFor("charges_for_attack_boss") + self.chargesPerCreepAttack = ability:GetSpecialValueFor("charges_for_attack_creep") + self.maxCharges = ability:GetSpecialValueFor("max_charges") +end +function modifier_item_vampire_claw.prototype.OnDestroy(self) + if not IsServer() then + return + end + local hero = self:GetParent() + local vampirism = self:GetAbility():GetSpecialValueFor("vampirism") + if vampirism > 0 then + reducePhysicalVampirism(nil, hero, vampirism) + end +end +function modifier_item_vampire_claw.prototype.GetModifierBonusStats_Strength(self) + return self.bonusStr +end +function modifier_item_vampire_claw.prototype.GetModifierPreAttack_BonusDamage(self) + return self.bonusDamage +end +function modifier_item_vampire_claw.prototype.OnTakeDamage(self, event) + if event.attacker ~= self:GetParent() then + return + end + local ability = self:GetAbility() + if not ability then + return + end + local currentCharges = ability:GetCurrentCharges() + local bonusChargesPerAttack = self.chargesPerCreepAttack + ability:SetCurrentCharges(math.min(currentCharges + bonusChargesPerAttack, self.maxCharges)) +end +modifier_item_vampire_claw = __TS__Decorate( + modifier_item_vampire_claw, + modifier_item_vampire_claw, + {registerModifier(nil)}, + {kind = "class", name = "modifier_item_vampire_claw"} +) +____exports.modifier_item_vampire_claw = modifier_item_vampire_claw +return ____exports diff --git a/scripts/vscripts/items/default_items/item_voodoo_mask_custom.lua b/scripts/vscripts/items/default_items/item_voodoo_mask_custom.lua new file mode 100644 index 0000000..462117c --- /dev/null +++ b/scripts/vscripts/items/default_items/item_voodoo_mask_custom.lua @@ -0,0 +1,70 @@ +local ____lualib = require("lualib_bundle") +local __TS__Class = ____lualib.__TS__Class +local __TS__ClassExtends = ____lualib.__TS__ClassExtends +local __TS__Decorate = ____lualib.__TS__Decorate +local ____exports = {} +local ____dota_ts_adapter = require("lib.dota_ts_adapter") +local BaseAbility = ____dota_ts_adapter.BaseAbility +local BaseModifier = ____dota_ts_adapter.BaseModifier +local registerAbility = ____dota_ts_adapter.registerAbility +local registerModifier = ____dota_ts_adapter.registerModifier +local ____vampirism = require("utils.vampirism") +local addMagicalVampirism = ____vampirism.addMagicalVampirism +local reduceMagicalVampirism = ____vampirism.reduceMagicalVampirism +____exports.item_voodoo_mask_custom = __TS__Class() +local item_voodoo_mask_custom = ____exports.item_voodoo_mask_custom +item_voodoo_mask_custom.name = "item_voodoo_mask_custom" +item_voodoo_mask_custom.____file_path = "scripts/vscripts/items/default_items/item_voodoo_mask_custom.lua" +__TS__ClassExtends(item_voodoo_mask_custom, BaseAbility) +function item_voodoo_mask_custom.prototype.GetIntrinsicModifierName(self) + return "modifier_item_voodoo_mask_custom" +end +item_voodoo_mask_custom = __TS__Decorate( + item_voodoo_mask_custom, + item_voodoo_mask_custom, + {registerAbility(nil)}, + {kind = "class", name = "item_voodoo_mask_custom"} +) +____exports.item_voodoo_mask_custom = item_voodoo_mask_custom +____exports.modifier_item_voodoo_mask_custom = __TS__Class() +local modifier_item_voodoo_mask_custom = ____exports.modifier_item_voodoo_mask_custom +modifier_item_voodoo_mask_custom.name = "modifier_item_voodoo_mask_custom" +modifier_item_voodoo_mask_custom.____file_path = "scripts/vscripts/items/default_items/item_voodoo_mask_custom.lua" +__TS__ClassExtends(modifier_item_voodoo_mask_custom, BaseModifier) +function modifier_item_voodoo_mask_custom.prototype.IsHidden(self) + return true +end +function modifier_item_voodoo_mask_custom.prototype.IsPurgable(self) + return false +end +function modifier_item_voodoo_mask_custom.prototype.GetAttributes(self) + return MODIFIER_ATTRIBUTE_MULTIPLE +end +function modifier_item_voodoo_mask_custom.prototype.OnCreated(self) + if not IsServer() then + return + end + local hero = self:GetParent() + local lifesteal = self:GetAbility():GetSpecialValueFor("lifesteal") + if lifesteal > 0 then + addMagicalVampirism(nil, hero, lifesteal) + end +end +function modifier_item_voodoo_mask_custom.prototype.OnDestroy(self) + if not IsServer() then + return + end + local hero = self:GetParent() + local lifesteal = self:GetAbility():GetSpecialValueFor("lifesteal") + if lifesteal > 0 then + reduceMagicalVampirism(nil, hero, lifesteal) + end +end +modifier_item_voodoo_mask_custom = __TS__Decorate( + modifier_item_voodoo_mask_custom, + modifier_item_voodoo_mask_custom, + {registerModifier(nil)}, + {kind = "class", name = "modifier_item_voodoo_mask_custom"} +) +____exports.modifier_item_voodoo_mask_custom = modifier_item_voodoo_mask_custom +return ____exports diff --git a/scripts/vscripts/items/default_items/item_warrior_shield.lua b/scripts/vscripts/items/default_items/item_warrior_shield.lua new file mode 100644 index 0000000..ee0c023 --- /dev/null +++ b/scripts/vscripts/items/default_items/item_warrior_shield.lua @@ -0,0 +1,83 @@ +local ____lualib = require("lualib_bundle") +local __TS__Class = ____lualib.__TS__Class +local __TS__ClassExtends = ____lualib.__TS__ClassExtends +local __TS__Decorate = ____lualib.__TS__Decorate +local ____exports = {} +local ____dota_ts_adapter = require("lib.dota_ts_adapter") +local BaseItem = ____dota_ts_adapter.BaseItem +local BaseModifier = ____dota_ts_adapter.BaseModifier +local registerAbility = ____dota_ts_adapter.registerAbility +local registerModifier = ____dota_ts_adapter.registerModifier +local ____luck = require("utils.luck") +local rollLuckChance = ____luck.rollLuckChance +local MELEE_STRENGTH_BLOCK_PCT = 50 +--- Щит воина: запас здоровья, реген, шанс заблокировать физический урон от атак; шанс для героев с Luck. +____exports.item_warrior_shield = __TS__Class() +local item_warrior_shield = ____exports.item_warrior_shield +item_warrior_shield.name = "item_warrior_shield" +item_warrior_shield.____file_path = "scripts/vscripts/items/default_items/item_warrior_shield.lua" +__TS__ClassExtends(item_warrior_shield, BaseItem) +function item_warrior_shield.prototype.GetIntrinsicModifierName(self) + return ____exports.modifier_item_warrior_shield.name +end +item_warrior_shield = __TS__Decorate( + item_warrior_shield, + item_warrior_shield, + {registerAbility(nil)}, + {kind = "class", name = "item_warrior_shield"} +) +____exports.item_warrior_shield = item_warrior_shield +____exports.modifier_item_warrior_shield = __TS__Class() +local modifier_item_warrior_shield = ____exports.modifier_item_warrior_shield +modifier_item_warrior_shield.name = "modifier_item_warrior_shield" +modifier_item_warrior_shield.____file_path = "scripts/vscripts/items/default_items/item_warrior_shield.lua" +__TS__ClassExtends(modifier_item_warrior_shield, BaseModifier) +function modifier_item_warrior_shield.prototype.IsHidden(self) + return true +end +function modifier_item_warrior_shield.prototype.IsPurgable(self) + return false +end +function modifier_item_warrior_shield.prototype.DeclareFunctions(self) + return {MODIFIER_PROPERTY_PHYSICAL_CONSTANT_BLOCK, MODIFIER_PROPERTY_HEALTH_REGEN_CONSTANT, MODIFIER_PROPERTY_HEALTH_BONUS} +end +function modifier_item_warrior_shield.prototype.GetModifierHealthBonus(self) + return self:GetAbility():GetSpecialValueFor("bonus_health") +end +function modifier_item_warrior_shield.prototype.GetModifierConstantHealthRegen(self) + return self:GetAbility():GetSpecialValueFor("bonus_health_regen") +end +function modifier_item_warrior_shield.prototype.GetModifierPhysical_ConstantBlock(self) + if not IsServer() then + return 0 + end + local ability = self:GetAbility() + if not ability then + return 0 + end + local chancePct = ability:GetSpecialValueFor("damage_block_chance") + local parent = self:GetParent() + local ____parent_IsRealHero_result_0 + if parent:IsRealHero() then + ____parent_IsRealHero_result_0 = rollLuckChance(nil, parent, chancePct / 100) + else + ____parent_IsRealHero_result_0 = RollPercentage(chancePct) + end + local blocked = ____parent_IsRealHero_result_0 + if blocked then + local baseBlock = parent:IsRangedAttacker() and ability:GetSpecialValueFor("damage_block") * 0.5 or ability:GetSpecialValueFor("damage_block") + if not parent:IsRealHero() or parent:IsRangedAttacker() then + return baseBlock + end + return baseBlock + parent:GetStrength() * (MELEE_STRENGTH_BLOCK_PCT / 100) + end + return 0 +end +modifier_item_warrior_shield = __TS__Decorate( + modifier_item_warrior_shield, + modifier_item_warrior_shield, + {registerModifier(nil)}, + {kind = "class", name = "modifier_item_warrior_shield"} +) +____exports.modifier_item_warrior_shield = modifier_item_warrior_shield +return ____exports diff --git a/scripts/vscripts/items/default_items/item_zombie_slayer copy.lua b/scripts/vscripts/items/default_items/item_zombie_slayer copy.lua new file mode 100644 index 0000000..bd24d9c --- /dev/null +++ b/scripts/vscripts/items/default_items/item_zombie_slayer copy.lua @@ -0,0 +1,202 @@ +local ____lualib = require("lualib_bundle") +local __TS__Class = ____lualib.__TS__Class +local __TS__ClassExtends = ____lualib.__TS__ClassExtends +local __TS__Decorate = ____lualib.__TS__Decorate +local __TS__StringStartsWith = ____lualib.__TS__StringStartsWith +local __TS__StringIncludes = ____lualib.__TS__StringIncludes +local ____exports = {} +local ____dota_ts_adapter = require("lib.dota_ts_adapter") +local BaseItem = ____dota_ts_adapter.BaseItem +local BaseModifier = ____dota_ts_adapter.BaseModifier +local registerAbility = ____dota_ts_adapter.registerAbility +local registerModifier = ____dota_ts_adapter.registerModifier +--- Холодный дебафф Corrosion (как Cold Attack у Skadi). +local ZOMBIE_SLAYER_SKADI_DEBUFF_PARTICLE = "particles/generic_gameplay/generic_slowed_cold.vpcf" +____exports.item_zombie_slayer = __TS__Class() +local item_zombie_slayer = ____exports.item_zombie_slayer +item_zombie_slayer.name = "item_zombie_slayer" +item_zombie_slayer.____file_path = "scripts/vscripts/items/default_items/item_zombie_slayer copy.lua" +__TS__ClassExtends(item_zombie_slayer, BaseItem) +function item_zombie_slayer.prototype.Precache(self, context) + PrecacheResource("particle", "particles/units/heroes/hero_skeletonking/wraith_king_curse_debuff_slash.vpcf", context) + PrecacheResource("particle", "particles/items2_fx/mage_slayer_debuff.vpcf", context) + PrecacheResource("particle", ZOMBIE_SLAYER_SKADI_DEBUFF_PARTICLE, context) +end +function item_zombie_slayer.prototype.GetIntrinsicModifierName(self) + return "modifier_item_zombie_slayer" +end +item_zombie_slayer = __TS__Decorate( + item_zombie_slayer, + item_zombie_slayer, + {registerAbility(nil)}, + {kind = "class", name = "item_zombie_slayer"} +) +____exports.item_zombie_slayer = item_zombie_slayer +____exports.modifier_item_zombie_slayer = __TS__Class() +local modifier_item_zombie_slayer = ____exports.modifier_item_zombie_slayer +modifier_item_zombie_slayer.name = "modifier_item_zombie_slayer" +modifier_item_zombie_slayer.____file_path = "scripts/vscripts/items/default_items/item_zombie_slayer copy.lua" +__TS__ClassExtends(modifier_item_zombie_slayer, BaseModifier) +function modifier_item_zombie_slayer.prototype.IsHidden(self) + return true +end +function modifier_item_zombie_slayer.prototype.IsPurgable(self) + return false +end +function modifier_item_zombie_slayer.prototype.DeclareFunctions(self) + return { + MODIFIER_PROPERTY_PREATTACK_BONUS_DAMAGE, + MODIFIER_PROPERTY_STATS_AGILITY_BONUS, + MODIFIER_PROPERTY_MANA_REGEN_CONSTANT, + MODIFIER_PROPERTY_HEALTH_BONUS, + MODIFIER_EVENT_ON_ATTACK_LANDED + } +end +function modifier_item_zombie_slayer.prototype.GetModifierHealthBonus(self) + return self:GetAbility():GetSpecialValueFor("bonus_health") +end +function modifier_item_zombie_slayer.prototype.GetModifierConstantManaRegen(self) + return self:GetAbility():GetSpecialValueFor("bonus_mana_regen") +end +function modifier_item_zombie_slayer.prototype.GetModifierPreAttack_BonusDamage(self) + return self:GetAbility():GetSpecialValueFor("bonus_damage") +end +function modifier_item_zombie_slayer.prototype.GetModifierBonusStats_Agility(self) + return self:GetAbility():GetSpecialValueFor("bonus_agility") +end +function modifier_item_zombie_slayer.prototype.OnAttackLanded(self, event) + if not IsServer() then + return + end + if event.attacker ~= self:GetParent() then + return + end + if not event.target or event.target:IsBuilding() or event.target:IsOther() then + return + end + if event.target ~= nil then + local ____self_4 = event.target + local ____self_4_AddNewModifier_5 = ____self_4.AddNewModifier + local ____temp_2 = self:GetParent() + local ____temp_3 = self:GetAbility() + local ____opt_0 = self:GetAbility() + ____self_4_AddNewModifier_5( + ____self_4, + ____temp_2, + ____temp_3, + "modifier_item_zombie_slayer_debuff_2", + {duration = ____opt_0 and ____opt_0:GetSpecialValueFor("debuff_duration")} + ) + local ____this_7 + ____this_7 = event.target + local ____opt_6 = ____this_7.GetUnitName + local targetName = ____opt_6 and ____opt_6(____this_7) or "" + if __TS__StringStartsWith(targetName, "npc_wave") and not __TS__StringIncludes( + string.lower(targetName), + "boss" + ) then + ParticleManager:CreateParticle("particles/units/heroes/hero_skeletonking/wraith_king_curse_debuff_slash.vpcf", PATTACH_ABSORIGIN_FOLLOW, event.target) + local ____self_12 = event.target + local ____self_12_AddNewModifier_13 = ____self_12.AddNewModifier + local ____temp_10 = self:GetParent() + local ____temp_11 = self:GetAbility() + local ____opt_8 = self:GetAbility() + ____self_12_AddNewModifier_13( + ____self_12, + ____temp_10, + ____temp_11, + "modifier_item_zombie_slayer_debuff", + {duration = ____opt_8 and ____opt_8:GetSpecialValueFor("debuff_duration")} + ) + return + end + end +end +modifier_item_zombie_slayer = __TS__Decorate( + modifier_item_zombie_slayer, + modifier_item_zombie_slayer, + {registerModifier(nil)}, + {kind = "class", name = "modifier_item_zombie_slayer"} +) +____exports.modifier_item_zombie_slayer = modifier_item_zombie_slayer +____exports.modifier_item_zombie_slayer_debuff = __TS__Class() +local modifier_item_zombie_slayer_debuff = ____exports.modifier_item_zombie_slayer_debuff +modifier_item_zombie_slayer_debuff.name = "modifier_item_zombie_slayer_debuff" +modifier_item_zombie_slayer_debuff.____file_path = "scripts/vscripts/items/default_items/item_zombie_slayer copy.lua" +__TS__ClassExtends(modifier_item_zombie_slayer_debuff, BaseModifier) +function modifier_item_zombie_slayer_debuff.prototype.IsDebuff(self) + return true +end +function modifier_item_zombie_slayer_debuff.prototype.IsPurgable(self) + return true +end +function modifier_item_zombie_slayer_debuff.prototype.IsHidden(self) + return true +end +function modifier_item_zombie_slayer_debuff.prototype.GetEffectName(self) + return "particles/items2_fx/mage_slayer_debuff.vpcf" +end +function modifier_item_zombie_slayer_debuff.prototype.GetEffectAttachType(self) + return PATTACH_ABSORIGIN_FOLLOW +end +function modifier_item_zombie_slayer_debuff.prototype.DeclareFunctions(self) + return {MODIFIER_PROPERTY_INCOMING_DAMAGE_PERCENTAGE} +end +function modifier_item_zombie_slayer_debuff.prototype.GetModifierIncomingDamage_Percentage(self, event) + return self:GetAbility():GetSpecialValueFor("incress_damage") +end +modifier_item_zombie_slayer_debuff = __TS__Decorate( + modifier_item_zombie_slayer_debuff, + modifier_item_zombie_slayer_debuff, + {registerModifier(nil)}, + {kind = "class", name = "modifier_item_zombie_slayer_debuff"} +) +____exports.modifier_item_zombie_slayer_debuff = modifier_item_zombie_slayer_debuff +--- Corrosion: броня / реген / скорость / атакспид — визуал и логика как у Cold Attack (Skadi). +____exports.modifier_item_zombie_slayer_debuff_2 = __TS__Class() +local modifier_item_zombie_slayer_debuff_2 = ____exports.modifier_item_zombie_slayer_debuff_2 +modifier_item_zombie_slayer_debuff_2.name = "modifier_item_zombie_slayer_debuff_2" +modifier_item_zombie_slayer_debuff_2.____file_path = "scripts/vscripts/items/default_items/item_zombie_slayer copy.lua" +__TS__ClassExtends(modifier_item_zombie_slayer_debuff_2, BaseModifier) +function modifier_item_zombie_slayer_debuff_2.prototype.IsDebuff(self) + return true +end +function modifier_item_zombie_slayer_debuff_2.prototype.IsPurgable(self) + return true +end +function modifier_item_zombie_slayer_debuff_2.prototype.IsHidden(self) + return false +end +function modifier_item_zombie_slayer_debuff_2.prototype.DeclareFunctions(self) + return {MODIFIER_PROPERTY_PHYSICAL_ARMOR_BONUS, MODIFIER_PROPERTY_HEALTH_REGEN_PERCENTAGE, MODIFIER_PROPERTY_MOVESPEED_BONUS_PERCENTAGE, MODIFIER_PROPERTY_ATTACKSPEED_BONUS_CONSTANT} +end +function modifier_item_zombie_slayer_debuff_2.prototype.GetModifierMoveSpeedBonus_Percentage(self) + return self:GetAbility():GetSpecialValueFor("movespeed_reduction") +end +function modifier_item_zombie_slayer_debuff_2.prototype.GetModifierAttackSpeedBonus_Constant(self) + return self:GetAbility():GetSpecialValueFor("attackspeed_reduction") +end +function modifier_item_zombie_slayer_debuff_2.prototype.GetModifierHealthRegenPercentage(self) + local healReductionPct = self:GetAbility():GetSpecialValueFor("health_reduction") + return 1 + healReductionPct * 0.01 +end +function modifier_item_zombie_slayer_debuff_2.prototype.GetModifierPhysicalArmorBonus(self) + return self:GetAbility():GetSpecialValueFor("armor_reduction") +end +function modifier_item_zombie_slayer_debuff_2.prototype.GetEffectName(self) + return ZOMBIE_SLAYER_SKADI_DEBUFF_PARTICLE +end +function modifier_item_zombie_slayer_debuff_2.prototype.GetEffectAttachType(self) + return PATTACH_ABSORIGIN_FOLLOW +end +function modifier_item_zombie_slayer_debuff_2.prototype.GetTexture(self) + return "item_skadi" +end +modifier_item_zombie_slayer_debuff_2 = __TS__Decorate( + modifier_item_zombie_slayer_debuff_2, + modifier_item_zombie_slayer_debuff_2, + {registerModifier(nil)}, + {kind = "class", name = "modifier_item_zombie_slayer_debuff_2"} +) +____exports.modifier_item_zombie_slayer_debuff_2 = modifier_item_zombie_slayer_debuff_2 +return ____exports diff --git a/scripts/vscripts/items/default_items/item_zombie_slayer.lua b/scripts/vscripts/items/default_items/item_zombie_slayer.lua new file mode 100644 index 0000000..79f533b --- /dev/null +++ b/scripts/vscripts/items/default_items/item_zombie_slayer.lua @@ -0,0 +1,192 @@ +local ____lualib = require("lualib_bundle") +local __TS__Class = ____lualib.__TS__Class +local __TS__ClassExtends = ____lualib.__TS__ClassExtends +local __TS__Decorate = ____lualib.__TS__Decorate +local __TS__StringStartsWith = ____lualib.__TS__StringStartsWith +local __TS__StringIncludes = ____lualib.__TS__StringIncludes +local ____exports = {} +local ____dota_ts_adapter = require("lib.dota_ts_adapter") +local BaseItem = ____dota_ts_adapter.BaseItem +local BaseModifier = ____dota_ts_adapter.BaseModifier +local registerAbility = ____dota_ts_adapter.registerAbility +local registerModifier = ____dota_ts_adapter.registerModifier +____exports.item_zombie_slayer = __TS__Class() +local item_zombie_slayer = ____exports.item_zombie_slayer +item_zombie_slayer.name = "item_zombie_slayer" +item_zombie_slayer.____file_path = "scripts/vscripts/items/default_items/item_zombie_slayer.lua" +__TS__ClassExtends(item_zombie_slayer, BaseItem) +function item_zombie_slayer.prototype.Precache(self, context) + PrecacheResource("particle", "particles/units/heroes/hero_skeletonking/wraith_king_curse_debuff_slash.vpcf", context) + PrecacheResource("particle", "particles/items2_fx/mage_slayer_debuff.vpcf", context) + PrecacheResource("particle", "particles/units/heroes/hero_dark_willow/dark_willow_wisp_spell_debuff.vpcf", context) +end +function item_zombie_slayer.prototype.GetIntrinsicModifierName(self) + return "modifier_item_zombie_slayer" +end +item_zombie_slayer = __TS__Decorate( + item_zombie_slayer, + item_zombie_slayer, + {registerAbility(nil)}, + {kind = "class", name = "item_zombie_slayer"} +) +____exports.item_zombie_slayer = item_zombie_slayer +____exports.modifier_item_zombie_slayer = __TS__Class() +local modifier_item_zombie_slayer = ____exports.modifier_item_zombie_slayer +modifier_item_zombie_slayer.name = "modifier_item_zombie_slayer" +modifier_item_zombie_slayer.____file_path = "scripts/vscripts/items/default_items/item_zombie_slayer.lua" +__TS__ClassExtends(modifier_item_zombie_slayer, BaseModifier) +function modifier_item_zombie_slayer.prototype.IsHidden(self) + return true +end +function modifier_item_zombie_slayer.prototype.IsPurgable(self) + return false +end +function modifier_item_zombie_slayer.prototype.DeclareFunctions(self) + return { + MODIFIER_PROPERTY_PREATTACK_BONUS_DAMAGE, + MODIFIER_PROPERTY_STATS_AGILITY_BONUS, + MODIFIER_PROPERTY_MANA_REGEN_CONSTANT, + MODIFIER_PROPERTY_HEALTH_BONUS, + MODIFIER_EVENT_ON_ATTACK_LANDED + } +end +function modifier_item_zombie_slayer.prototype.GetModifierHealthBonus(self) + return self:GetAbility():GetSpecialValueFor("bonus_health") +end +function modifier_item_zombie_slayer.prototype.GetModifierConstantManaRegen(self) + return self:GetAbility():GetSpecialValueFor("bonus_mana_regen") +end +function modifier_item_zombie_slayer.prototype.GetModifierPreAttack_BonusDamage(self) + return self:GetAbility():GetSpecialValueFor("bonus_damage") +end +function modifier_item_zombie_slayer.prototype.GetModifierBonusStats_Agility(self) + return self:GetAbility():GetSpecialValueFor("bonus_agility") +end +function modifier_item_zombie_slayer.prototype.OnAttackLanded(self, event) + if not IsServer() then + return + end + if event.attacker ~= self:GetParent() then + return + end + if not event.target or event.target:IsBuilding() or event.target:IsOther() then + return + end + if event.target ~= nil then + local ____self_4 = event.target + local ____self_4_AddNewModifier_5 = ____self_4.AddNewModifier + local ____temp_2 = self:GetParent() + local ____temp_3 = self:GetAbility() + local ____opt_0 = self:GetAbility() + ____self_4_AddNewModifier_5( + ____self_4, + ____temp_2, + ____temp_3, + "modifier_item_zombie_slayer_debuff_2", + {duration = ____opt_0 and ____opt_0:GetSpecialValueFor("debuff_duration")} + ) + local ____this_7 + ____this_7 = event.target + local ____opt_6 = ____this_7.GetUnitName + local targetName = ____opt_6 and ____opt_6(____this_7) or "" + if __TS__StringStartsWith(targetName, "npc_wave") and not __TS__StringIncludes( + string.lower(targetName), + "boss" + ) then + ParticleManager:CreateParticle("particles/units/heroes/hero_skeletonking/wraith_king_curse_debuff_slash.vpcf", PATTACH_ABSORIGIN_FOLLOW, event.target) + local ____self_12 = event.target + local ____self_12_AddNewModifier_13 = ____self_12.AddNewModifier + local ____temp_10 = self:GetParent() + local ____temp_11 = self:GetAbility() + local ____opt_8 = self:GetAbility() + ____self_12_AddNewModifier_13( + ____self_12, + ____temp_10, + ____temp_11, + "modifier_item_zombie_slayer_debuff", + {duration = ____opt_8 and ____opt_8:GetSpecialValueFor("debuff_duration")} + ) + return + end + end +end +modifier_item_zombie_slayer = __TS__Decorate( + modifier_item_zombie_slayer, + modifier_item_zombie_slayer, + {registerModifier(nil)}, + {kind = "class", name = "modifier_item_zombie_slayer"} +) +____exports.modifier_item_zombie_slayer = modifier_item_zombie_slayer +____exports.modifier_item_zombie_slayer_debuff = __TS__Class() +local modifier_item_zombie_slayer_debuff = ____exports.modifier_item_zombie_slayer_debuff +modifier_item_zombie_slayer_debuff.name = "modifier_item_zombie_slayer_debuff" +modifier_item_zombie_slayer_debuff.____file_path = "scripts/vscripts/items/default_items/item_zombie_slayer.lua" +__TS__ClassExtends(modifier_item_zombie_slayer_debuff, BaseModifier) +function modifier_item_zombie_slayer_debuff.prototype.IsDebuff(self) + return true +end +function modifier_item_zombie_slayer_debuff.prototype.IsPurgable(self) + return true +end +function modifier_item_zombie_slayer_debuff.prototype.IsHidden(self) + return true +end +function modifier_item_zombie_slayer_debuff.prototype.GetEffectName(self) + return "particles/items2_fx/mage_slayer_debuff.vpcf" +end +function modifier_item_zombie_slayer_debuff.prototype.GetEffectAttachType(self) + return PATTACH_ABSORIGIN_FOLLOW +end +function modifier_item_zombie_slayer_debuff.prototype.DeclareFunctions(self) + return {MODIFIER_PROPERTY_INCOMING_DAMAGE_PERCENTAGE} +end +function modifier_item_zombie_slayer_debuff.prototype.GetModifierIncomingDamage_Percentage(self, event) + return self:GetAbility():GetSpecialValueFor("incress_damage") +end +modifier_item_zombie_slayer_debuff = __TS__Decorate( + modifier_item_zombie_slayer_debuff, + modifier_item_zombie_slayer_debuff, + {registerModifier(nil)}, + {kind = "class", name = "modifier_item_zombie_slayer_debuff"} +) +____exports.modifier_item_zombie_slayer_debuff = modifier_item_zombie_slayer_debuff +____exports.modifier_item_zombie_slayer_debuff_2 = __TS__Class() +local modifier_item_zombie_slayer_debuff_2 = ____exports.modifier_item_zombie_slayer_debuff_2 +modifier_item_zombie_slayer_debuff_2.name = "modifier_item_zombie_slayer_debuff_2" +modifier_item_zombie_slayer_debuff_2.____file_path = "scripts/vscripts/items/default_items/item_zombie_slayer.lua" +__TS__ClassExtends(modifier_item_zombie_slayer_debuff_2, BaseModifier) +function modifier_item_zombie_slayer_debuff_2.prototype.IsDebuff(self) + return true +end +function modifier_item_zombie_slayer_debuff_2.prototype.IsPurgable(self) + return true +end +function modifier_item_zombie_slayer_debuff_2.prototype.IsHidden(self) + return true +end +function modifier_item_zombie_slayer_debuff_2.prototype.GetEffectName(self) + return "particles/units/heroes/hero_dark_willow/dark_willow_wisp_spell_debuff.vpcf" +end +function modifier_item_zombie_slayer_debuff_2.prototype.GetEffectAttachType(self) + return PATTACH_OVERHEAD_FOLLOW +end +function modifier_item_zombie_slayer_debuff_2.prototype.DeclareFunctions(self) + return {MODIFIER_PROPERTY_PHYSICAL_ARMOR_BONUS, MODIFIER_PROPERTY_HEALTH_REGEN_PERCENTAGE, MODIFIER_PROPERTY_MOVESPEED_BONUS_PERCENTAGE} +end +function modifier_item_zombie_slayer_debuff_2.prototype.GetModifierMoveSpeedBonus_Percentage(self) + return self:GetAbility():GetSpecialValueFor("movespeed_reduction") +end +function modifier_item_zombie_slayer_debuff_2.prototype.GetModifierHealthRegenPercentage(self) + return 1 + (self:GetAbility():GetSpecialValueFor("health_reduction") - 100) * 0.01 +end +function modifier_item_zombie_slayer_debuff_2.prototype.GetModifierPhysicalArmorBonus(self, event) + return self:GetAbility():GetSpecialValueFor("armor_reduction") +end +modifier_item_zombie_slayer_debuff_2 = __TS__Decorate( + modifier_item_zombie_slayer_debuff_2, + modifier_item_zombie_slayer_debuff_2, + {registerModifier(nil)}, + {kind = "class", name = "modifier_item_zombie_slayer_debuff_2"} +) +____exports.modifier_item_zombie_slayer_debuff_2 = modifier_item_zombie_slayer_debuff_2 +return ____exports diff --git a/scripts/vscripts/items/quest_items/item_pet.lua b/scripts/vscripts/items/quest_items/item_pet.lua new file mode 100644 index 0000000..9520d6a --- /dev/null +++ b/scripts/vscripts/items/quest_items/item_pet.lua @@ -0,0 +1,131 @@ +local ____lualib = require("lualib_bundle") +local __TS__Class = ____lualib.__TS__Class +local __TS__ClassExtends = ____lualib.__TS__ClassExtends +local __TS__Decorate = ____lualib.__TS__Decorate +local ____exports = {} +local ____dota_ts_adapter = require("lib.dota_ts_adapter") +local BaseItem = ____dota_ts_adapter.BaseItem +local registerAbility = ____dota_ts_adapter.registerAbility +local BaseModifier = ____dota_ts_adapter.BaseModifier +local registerModifier = ____dota_ts_adapter.registerModifier +local ____QuestSystem = require("quests.QuestSystem") +local QuestSystem = ____QuestSystem.QuestSystem +____exports.item_pet = __TS__Class() +local item_pet = ____exports.item_pet +item_pet.name = "item_pet" +item_pet.____file_path = "scripts/vscripts/items/quest_items/item_pet.lua" +__TS__ClassExtends(item_pet, BaseItem) +function item_pet.prototype.GetAOERadius(self) + return 450 +end +function item_pet.prototype.OnAbilityPhaseStart(self) + self:GetCaster():StartGestureWithPlaybackRate(ACT_DOTA_ATTACK, 1) + return true +end +function item_pet.prototype.OnAbilityPhaseInterrupted(self) + self:GetCaster():FadeGesture(ACT_DOTA_ATTACK) +end +function item_pet.prototype.OnSpellStart(self) + local target = self:GetCursorTarget() + if not target then + return + end + if target:IsInstance(CDOTA_MapTree) then + target:CutDown(self:GetCaster():GetTeamNumber()) + local unit = CreateUnitByName( + "npc_squirrel_event", + target:GetCenter(), + true, + self:GetCaster(), + self:GetCaster(), + DOTA_TEAM_GOODGUYS + ) + unit:SetControllableByPlayer( + self:GetCaster():GetPlayerID(), + true + ) + unit:SetOwner(self:GetCaster()) + self:RemoveSelf() + else + local unitName = target:GetUnitName() + if unitName == "npc_pig" then + target:Kill( + self, + self:GetCaster() + ) + local unit = CreateUnitByName( + "npc_pig_event", + target:GetCenter(), + true, + nil, + nil, + DOTA_TEAM_GOODGUYS + ) + unit:SetControllableByPlayer( + self:GetCaster():GetPlayerID(), + true + ) + unit:SetOwner(self:GetCaster()) + self:RemoveSelf() + elseif unitName == "npc_wolf" then + target:Kill( + self, + self:GetCaster() + ) + local unit = CreateUnitByName( + "npc_wolf_event", + target:GetCenter(), + true, + nil, + nil, + DOTA_TEAM_GOODGUYS + ) + unit:SetControllableByPlayer( + self:GetCaster():GetPlayerID(), + true + ) + unit:SetOwner(self:GetCaster()) + self:RemoveSelf() + else + local questSystem = QuestSystem:getInstance() + questSystem:failQuest("denny_quest_find_pet") + GameRules:SendCustomMessage("Бля, у вас не получилось приручить питомца(((", 0, 0) + self:RemoveSelf() + end + end + target:AddNewModifier( + self:GetCaster(), + self, + "modifier_item_pet", + {} + ) +end +item_pet = __TS__Decorate( + item_pet, + item_pet, + {registerAbility(nil)}, + {kind = "class", name = "item_pet"} +) +____exports.item_pet = item_pet +____exports.modifier_item_pet = __TS__Class() +local modifier_item_pet = ____exports.modifier_item_pet +modifier_item_pet.name = "modifier_item_pet" +modifier_item_pet.____file_path = "scripts/vscripts/items/quest_items/item_pet.lua" +__TS__ClassExtends(modifier_item_pet, BaseModifier) +function modifier_item_pet.prototype.IsHidden(self) + return false +end +function modifier_item_pet.prototype.RemoveOnDeath(self) + return false +end +function modifier_item_pet.prototype.CheckState(self) + return {[MODIFIER_STATE_INVULNERABLE] = true, [MODIFIER_STATE_NO_UNIT_COLLISION] = true} +end +modifier_item_pet = __TS__Decorate( + modifier_item_pet, + modifier_item_pet, + {registerModifier(nil)}, + {kind = "class", name = "modifier_item_pet"} +) +____exports.modifier_item_pet = modifier_item_pet +return ____exports diff --git a/scripts/vscripts/items/quest_items/item_pollen_keeper.lua b/scripts/vscripts/items/quest_items/item_pollen_keeper.lua new file mode 100644 index 0000000..7598624 --- /dev/null +++ b/scripts/vscripts/items/quest_items/item_pollen_keeper.lua @@ -0,0 +1,88 @@ +local ____lualib = require("lualib_bundle") +local __TS__Class = ____lualib.__TS__Class +local __TS__ClassExtends = ____lualib.__TS__ClassExtends +local __TS__Decorate = ____lualib.__TS__Decorate +local ____exports = {} +local ____dota_ts_adapter = require("lib.dota_ts_adapter") +local BaseItem = ____dota_ts_adapter.BaseItem +local BaseModifier = ____dota_ts_adapter.BaseModifier +local registerModifier = ____dota_ts_adapter.registerModifier +local ____dota_ts_adapter = require("lib.dota_ts_adapter") +local registerAbility = ____dota_ts_adapter.registerAbility +____exports.item_pollen_keeper = __TS__Class() +local item_pollen_keeper = ____exports.item_pollen_keeper +item_pollen_keeper.name = "item_pollen_keeper" +item_pollen_keeper.____file_path = "scripts/vscripts/items/quest_items/item_pollen_keeper.lua" +__TS__ClassExtends(item_pollen_keeper, BaseItem) +function item_pollen_keeper.prototype.GetIntrinsicModifierName(self) + return ____exports.modifier_item_pollen_keeper.name +end +function item_pollen_keeper.prototype.GetAbilityTextureName(self) + local charges = self:GetCurrentCharges() + if charges <= 5 then + return "../items/quest_items/pollen_keeper_1" + elseif charges <= 15 then + return "../items/quest_items/pollen_keeper_2" + elseif charges <= 19 then + return "../items/quest_items/pollen_keeper_3" + elseif charges == 20 then + return "../items/quest_items/pollen_keeper_4" + end + return "../items/quest_items/pollen_keeper_1" +end +item_pollen_keeper = __TS__Decorate( + item_pollen_keeper, + item_pollen_keeper, + {registerAbility(nil)}, + {kind = "class", name = "item_pollen_keeper"} +) +____exports.item_pollen_keeper = item_pollen_keeper +____exports.modifier_item_pollen_keeper = __TS__Class() +local modifier_item_pollen_keeper = ____exports.modifier_item_pollen_keeper +modifier_item_pollen_keeper.name = "modifier_item_pollen_keeper" +modifier_item_pollen_keeper.____file_path = "scripts/vscripts/items/quest_items/item_pollen_keeper.lua" +__TS__ClassExtends(modifier_item_pollen_keeper, BaseModifier) +function modifier_item_pollen_keeper.prototype.IsHidden(self) + return true +end +function modifier_item_pollen_keeper.prototype.IsPurgable(self) + return false +end +function modifier_item_pollen_keeper.prototype.IsDebuff(self) + return false +end +function modifier_item_pollen_keeper.prototype.IsBuff(self) + return true +end +function modifier_item_pollen_keeper.prototype.DeclareFunctions(self) + return {MODIFIER_EVENT_ON_ATTACK_LANDED} +end +function modifier_item_pollen_keeper.prototype.OnAttackLanded(self, event) + if not IsServer() then + return + end + if event.attacker ~= self:GetParent() then + return + end + if not event.target or event.target:GetUnitName() ~= "npc_wisps" then + return + end + local ability = self:GetAbility() + if not ability or ability:IsNull() then + return + end + if RandomInt(1, 100) > ability:GetSpecialValueFor("chance") then + return + end + if ability:GetCurrentCharges() < 20 then + ability:SetCurrentCharges(ability:GetCurrentCharges() + 1) + end +end +modifier_item_pollen_keeper = __TS__Decorate( + modifier_item_pollen_keeper, + modifier_item_pollen_keeper, + {registerModifier(nil)}, + {kind = "class", name = "modifier_item_pollen_keeper"} +) +____exports.modifier_item_pollen_keeper = modifier_item_pollen_keeper +return ____exports diff --git a/scripts/vscripts/items/quest_items/item_shameful_pipe.lua b/scripts/vscripts/items/quest_items/item_shameful_pipe.lua new file mode 100644 index 0000000..7db1e77 --- /dev/null +++ b/scripts/vscripts/items/quest_items/item_shameful_pipe.lua @@ -0,0 +1,69 @@ +local ____lualib = require("lualib_bundle") +local __TS__Class = ____lualib.__TS__Class +local __TS__ClassExtends = ____lualib.__TS__ClassExtends +local __TS__Decorate = ____lualib.__TS__Decorate +local ____exports = {} +local ____dota_ts_adapter = require("lib.dota_ts_adapter") +local BaseItem = ____dota_ts_adapter.BaseItem +local BaseModifier = ____dota_ts_adapter.BaseModifier +local registerAbility = ____dota_ts_adapter.registerAbility +local registerModifier = ____dota_ts_adapter.registerModifier +--- Позорная труба — дальность атаки дальникам; иммунитет к костяной броне крипов. +____exports.item_shameful_pipe = __TS__Class() +local item_shameful_pipe = ____exports.item_shameful_pipe +item_shameful_pipe.name = "item_shameful_pipe" +item_shameful_pipe.____file_path = "scripts/vscripts/items/quest_items/item_shameful_pipe.lua" +__TS__ClassExtends(item_shameful_pipe, BaseItem) +function item_shameful_pipe.prototype.GetIntrinsicModifierName(self) + return ____exports.modifier_item_shameful_pipe.name +end +item_shameful_pipe = __TS__Decorate( + item_shameful_pipe, + item_shameful_pipe, + {registerAbility(nil)}, + {kind = "class", name = "item_shameful_pipe"} +) +____exports.item_shameful_pipe = item_shameful_pipe +--- Имя модификатора — bone_armor не действует на героя с этим предметом. +____exports.modifier_item_shameful_pipe = __TS__Class() +local modifier_item_shameful_pipe = ____exports.modifier_item_shameful_pipe +modifier_item_shameful_pipe.name = "modifier_item_shameful_pipe" +modifier_item_shameful_pipe.____file_path = "scripts/vscripts/items/quest_items/item_shameful_pipe.lua" +__TS__ClassExtends(modifier_item_shameful_pipe, BaseModifier) +function modifier_item_shameful_pipe.prototype.IsHidden(self) + return true +end +function modifier_item_shameful_pipe.prototype.IsDebuff(self) + return false +end +function modifier_item_shameful_pipe.prototype.IsPurgable(self) + return false +end +function modifier_item_shameful_pipe.prototype.RemoveOnDeath(self) + return false +end +function modifier_item_shameful_pipe.prototype.GetTexture(self) + return "../items/blackshop/astral_anchor" +end +function modifier_item_shameful_pipe.prototype.DeclareFunctions(self) + return {MODIFIER_PROPERTY_ATTACK_RANGE_BONUS} +end +function modifier_item_shameful_pipe.prototype.GetModifierAttackRangeBonus(self) + local parent = self:GetParent() + if not parent or parent:IsNull() or not parent:IsRangedAttacker() then + return 0 + end + local ability = self:GetAbility() + if not ability or ability:IsNull() then + return 0 + end + return ability:GetSpecialValueFor("bonus_attack_range") +end +modifier_item_shameful_pipe = __TS__Decorate( + modifier_item_shameful_pipe, + modifier_item_shameful_pipe, + {registerModifier(nil)}, + {kind = "class", name = "modifier_item_shameful_pipe"} +) +____exports.modifier_item_shameful_pipe = modifier_item_shameful_pipe +return ____exports diff --git a/scripts/vscripts/items/quest_items/item_shovel.lua b/scripts/vscripts/items/quest_items/item_shovel.lua new file mode 100644 index 0000000..91f6e4c --- /dev/null +++ b/scripts/vscripts/items/quest_items/item_shovel.lua @@ -0,0 +1,211 @@ +local ____lualib = require("lualib_bundle") +local __TS__Class = ____lualib.__TS__Class +local __TS__ClassExtends = ____lualib.__TS__ClassExtends +local __TS__Decorate = ____lualib.__TS__Decorate +local ____exports = {} +local ____dota_ts_adapter = require("lib.dota_ts_adapter") +local BaseItem = ____dota_ts_adapter.BaseItem +local BaseModifier = ____dota_ts_adapter.BaseModifier +local registerAbility = ____dota_ts_adapter.registerAbility +local registerModifier = ____dota_ts_adapter.registerModifier +local ____kunkka_shovel_anchor_markers = require("quests.kunkka_shovel_anchor_markers") +local handleShovelDigAt = ____kunkka_shovel_anchor_markers.handleShovelDigAt +local isNearUndugShovelPoint = ____kunkka_shovel_anchor_markers.isNearUndugShovelPoint +local SHOVEL_DIG_RADIUS = ____kunkka_shovel_anchor_markers.SHOVEL_DIG_RADIUS +local ____QuestSystem = require("quests.QuestSystem") +local QuestSystem = ____QuestSystem.QuestSystem +local SHOVEL_DIG_PARTICLE = "particles/econ/events/ti9/shovel_dig.vpcf" +local SHOVEL_REVEAL_PARTICLE = "particles/econ/events/ti9/shovel_revealed_baby_roshan.vpcf" +local KUNKA_FIND_ANCHOR_QUEST_ID = "kunkka_quest_find_anchor" +____exports.item_shovel = __TS__Class() +local item_shovel = ____exports.item_shovel +item_shovel.name = "item_shovel" +item_shovel.____file_path = "scripts/vscripts/items/quest_items/item_shovel.lua" +__TS__ClassExtends(item_shovel, BaseItem) +function item_shovel.prototype.Precache(self, context) + PrecacheResource("particle", SHOVEL_DIG_PARTICLE, context) + PrecacheResource("particle", SHOVEL_REVEAL_PARTICLE, context) + PrecacheResource("soundfile", "soundevents/game_sounds_items.vsndevts", context) +end +function item_shovel.prototype.GetIntrinsicModifierName(self) + return ____exports.modifier_shovel_effect.name +end +function item_shovel.prototype.isOnShovelPoint(self, caster) + return isNearUndugShovelPoint( + nil, + caster:GetAbsOrigin(), + SHOVEL_DIG_RADIUS + ) +end +function item_shovel.prototype.getMaxDigs(self) + return math.max( + 1, + self:GetSpecialValueFor("max_digs") + ) +end +function item_shovel.prototype.getDigCharges(self) + return self:GetCurrentCharges() +end +function item_shovel.prototype.CastFilterResult(self) + if not IsServer() then + return UF_SUCCESS + end + local caster = self:GetCaster() + if not caster or caster:IsNull() or not self:isOnShovelPoint(caster) then + return UF_FAIL_CUSTOM + end + if self:getDigCharges() >= self:getMaxDigs() then + return UF_FAIL_CUSTOM + end + return UF_SUCCESS +end +function item_shovel.prototype.GetCustomCastError(self) + if IsServer() and self:getDigCharges() >= self:getMaxDigs() then + return "#dota_hud_error_shovel_broken" + end + return "#dota_hud_error_shovel_not_on_cross" +end +function item_shovel.prototype.OnSpellStart(self) + if not IsServer() then + return + end + local caster = self:GetCaster() + if not caster or caster:IsNull() then + return + end + EmitSoundOn("DOTA_Item.MaskOfMadness.Activate", caster) + self.digParticle = ParticleManager:CreateParticle(SHOVEL_DIG_PARTICLE, PATTACH_ABSORIGIN, caster) + ParticleManager:SetParticleControl( + self.digParticle, + 0, + caster:GetAbsOrigin() + ) +end +function item_shovel.prototype.OnChannelFinish(self, interrupted) + if not IsServer() then + return + end + local caster = self:GetCaster() + if not caster or caster:IsNull() then + return + end + if self.digParticle ~= nil then + ParticleManager:DestroyParticle(self.digParticle, false) + ParticleManager:ReleaseParticleIndex(self.digParticle) + self.digParticle = nil + end + StopSoundOn("DOTA_Item.MaskOfMadness.Activate", caster) + if interrupted then + return + end + if not self:isOnShovelPoint(caster) then + return + end + local digOrigin = caster:GetAbsOrigin() + local digMarker = handleShovelDigAt(nil, digOrigin, SHOVEL_DIG_RADIUS) + if not digMarker.removed then + return + end + local revealFx = ParticleManager:CreateParticle(SHOVEL_REVEAL_PARTICLE, PATTACH_ABSORIGIN, caster) + ParticleManager:SetParticleControl(revealFx, 0, digOrigin) + ParticleManager:ReleaseParticleIndex(revealFx) + if digMarker.grantsShamefulPipe then + self:grantShamefulPipe(caster) + end + local goldMin = self:GetSpecialValueFor("gold_min") + local goldMax = self:GetSpecialValueFor("gold_max") + local gold = RandomInt( + math.max(0, goldMin), + math.max(goldMin, goldMax) + ) + local playerId = caster:GetPlayerOwnerID() + if PlayerResource:IsValidPlayerID(playerId) and gold > 0 then + PlayerResource:ModifyGold(playerId, gold, true, DOTA_ModifyGold_Unspecified) + local player = PlayerResource:GetPlayer(playerId) + if player then + EmitSoundOnClient("General.Coins", player) + SendOverheadEventMessage( + player, + OVERHEAD_ALERT_GOLD, + caster, + gold, + nil + ) + end + end + self:tryCompleteKunkkaFindAnchorQuest(caster) + self:consumeDigCharge() +end +function item_shovel.prototype.consumeDigCharge(self) + local item = self + if not item or item:IsNull() then + return + end + local maxDigs = self:getMaxDigs() + local nextCharges = item:GetCurrentCharges() + 1 + if nextCharges >= maxDigs then + UTIL_Remove(item) + return + end + item:SetCurrentCharges(nextCharges) +end +function item_shovel.prototype.grantShamefulPipe(self, hero) + if hero:FindItemInInventory("item_shameful_pipe") then + return + end + local item = hero:AddItemByName("item_shameful_pipe") + if item and not item:IsNull() then + EmitSoundOn("Item.PickUpGemShop", hero) + end +end +function item_shovel.prototype.tryCompleteKunkkaFindAnchorQuest(self, _caster) + QuestSystem:getInstance():setQuestProgress(KUNKA_FIND_ANCHOR_QUEST_ID, "find_anchor", 1) +end +item_shovel = __TS__Decorate( + item_shovel, + item_shovel, + {registerAbility(nil)}, + {kind = "class", name = "item_shovel"} +) +____exports.item_shovel = item_shovel +____exports.modifier_shovel_effect = __TS__Class() +local modifier_shovel_effect = ____exports.modifier_shovel_effect +modifier_shovel_effect.name = "modifier_shovel_effect" +modifier_shovel_effect.____file_path = "scripts/vscripts/items/quest_items/item_shovel.lua" +__TS__ClassExtends(modifier_shovel_effect, BaseModifier) +function modifier_shovel_effect.prototype.____constructor(self, ...) + BaseModifier.prototype.____constructor(self, ...) + self.bonusHealth = 0 +end +function modifier_shovel_effect.prototype.IsHidden(self) + return true +end +function modifier_shovel_effect.prototype.IsPurgable(self) + return false +end +function modifier_shovel_effect.prototype.RemoveOnDeath(self) + return false +end +function modifier_shovel_effect.prototype.GetAttributes(self) + return MODIFIER_ATTRIBUTE_MULTIPLE +end +function modifier_shovel_effect.prototype.OnCreated(self) + local ability = self:GetAbility() + if ability and not ability:IsNull() then + self.bonusHealth = ability:GetSpecialValueFor("bonus_health") + end +end +function modifier_shovel_effect.prototype.DeclareFunctions(self) + return {MODIFIER_PROPERTY_HEALTH_BONUS} +end +function modifier_shovel_effect.prototype.GetModifierHealthBonus(self) + return self.bonusHealth +end +modifier_shovel_effect = __TS__Decorate( + modifier_shovel_effect, + modifier_shovel_effect, + {registerModifier(nil)}, + {kind = "class", name = "modifier_shovel_effect"} +) +____exports.modifier_shovel_effect = modifier_shovel_effect +return ____exports diff --git a/scripts/vscripts/items/quest_items/item_wolf_hat.lua b/scripts/vscripts/items/quest_items/item_wolf_hat.lua new file mode 100644 index 0000000..370db14 --- /dev/null +++ b/scripts/vscripts/items/quest_items/item_wolf_hat.lua @@ -0,0 +1,152 @@ +local ____lualib = require("lualib_bundle") +local __TS__Class = ____lualib.__TS__Class +local __TS__ClassExtends = ____lualib.__TS__ClassExtends +local __TS__Decorate = ____lualib.__TS__Decorate +local ____exports = {} +local ____dota_ts_adapter = require("lib.dota_ts_adapter") +local BaseItem = ____dota_ts_adapter.BaseItem +local BaseModifier = ____dota_ts_adapter.BaseModifier +local registerAbility = ____dota_ts_adapter.registerAbility +local registerModifier = ____dota_ts_adapter.registerModifier +local ____vampirism = require("utils.vampirism") +local addPhysicalVampirism = ____vampirism.addPhysicalVampirism +local reducePhysicalVampirism = ____vampirism.reducePhysicalVampirism +local HOWL_PARTICLE = "particles/units/heroes/hero_lycan/lycan_shapeshift_buff.vpcf" +____exports.item_wolf_hat = __TS__Class() +local item_wolf_hat = ____exports.item_wolf_hat +item_wolf_hat.name = "item_wolf_hat" +item_wolf_hat.____file_path = "scripts/vscripts/items/quest_items/item_wolf_hat.lua" +__TS__ClassExtends(item_wolf_hat, BaseItem) +function item_wolf_hat.prototype.Precache(self, context) + PrecacheResource("particle", HOWL_PARTICLE, context) + PrecacheResource("soundfile", "soundevents/game_sounds_heroes/game_sounds_lycan.vsndevts", context) +end +function item_wolf_hat.prototype.GetIntrinsicModifierName(self) + return ____exports.modifier_item_wolf_hat_passive.name +end +function item_wolf_hat.prototype.OnSpellStart(self) + if not IsServer() then + return + end + local caster = self:GetCaster() + local duration = self:GetSpecialValueFor("howl_duration") + caster:EmitSound("Hero_Lycan.Howl") + caster:AddNewModifier(caster, self, ____exports.modifier_item_wolf_hat_howl.name, {duration = duration}) +end +item_wolf_hat = __TS__Decorate( + item_wolf_hat, + item_wolf_hat, + {registerAbility(nil)}, + {kind = "class", name = "item_wolf_hat"} +) +____exports.item_wolf_hat = item_wolf_hat +--- Шапка вожака: сила, скорость, вампиризм, добивание раненых. +____exports.modifier_item_wolf_hat_passive = __TS__Class() +local modifier_item_wolf_hat_passive = ____exports.modifier_item_wolf_hat_passive +modifier_item_wolf_hat_passive.name = "modifier_item_wolf_hat_passive" +modifier_item_wolf_hat_passive.____file_path = "scripts/vscripts/items/quest_items/item_wolf_hat.lua" +__TS__ClassExtends(modifier_item_wolf_hat_passive, BaseModifier) +function modifier_item_wolf_hat_passive.prototype.IsHidden(self) + return true +end +function modifier_item_wolf_hat_passive.prototype.IsPurgable(self) + return false +end +function modifier_item_wolf_hat_passive.prototype.OnCreated(self) + if not IsServer() then + return + end + local hero = self:GetParent() + local lifesteal = self:GetAbility():GetSpecialValueFor("lifesteal_pct") + if lifesteal > 0 then + addPhysicalVampirism(nil, hero, lifesteal) + end +end +function modifier_item_wolf_hat_passive.prototype.OnDestroy(self) + if not IsServer() then + return + end + local hero = self:GetParent() + local lifesteal = self:GetAbility():GetSpecialValueFor("lifesteal_pct") + if lifesteal > 0 then + reducePhysicalVampirism(nil, hero, lifesteal) + end +end +function modifier_item_wolf_hat_passive.prototype.DeclareFunctions(self) + return {MODIFIER_PROPERTY_STATS_STRENGTH_BONUS, MODIFIER_PROPERTY_MOVESPEED_BONUS_CONSTANT, MODIFIER_PROPERTY_DAMAGEOUTGOING_PERCENTAGE} +end +function modifier_item_wolf_hat_passive.prototype.GetModifierBonusStats_Strength(self) + return self:GetAbility():GetSpecialValueFor("bonus_strength") +end +function modifier_item_wolf_hat_passive.prototype.GetModifierMoveSpeedBonus_Constant(self) + return self:GetAbility():GetSpecialValueFor("bonus_move_speed") +end +function modifier_item_wolf_hat_passive.prototype.GetModifierDamageOutgoing_Percentage(self, event) + local target = event and event.target + if not target or target:IsNull() or not target:IsAlive() then + return 0 + end + if target:GetHealthPercent() > self:GetAbility():GetSpecialValueFor("low_hp_threshold") then + return 0 + end + return self:GetAbility():GetSpecialValueFor("low_hp_damage_bonus") +end +modifier_item_wolf_hat_passive = __TS__Decorate( + modifier_item_wolf_hat_passive, + modifier_item_wolf_hat_passive, + {registerModifier(nil)}, + {kind = "class", name = "modifier_item_wolf_hat_passive"} +) +____exports.modifier_item_wolf_hat_passive = modifier_item_wolf_hat_passive +--- Активное: вой стаи — скорость атаки и бег. +____exports.modifier_item_wolf_hat_howl = __TS__Class() +local modifier_item_wolf_hat_howl = ____exports.modifier_item_wolf_hat_howl +modifier_item_wolf_hat_howl.name = "modifier_item_wolf_hat_howl" +modifier_item_wolf_hat_howl.____file_path = "scripts/vscripts/items/quest_items/item_wolf_hat.lua" +__TS__ClassExtends(modifier_item_wolf_hat_howl, BaseModifier) +function modifier_item_wolf_hat_howl.prototype.IsHidden(self) + return false +end +function modifier_item_wolf_hat_howl.prototype.IsPurgable(self) + return true +end +function modifier_item_wolf_hat_howl.prototype.OnCreated(self) + if not IsServer() then + return + end + self.particleId = ParticleManager:CreateParticle( + HOWL_PARTICLE, + PATTACH_ABSORIGIN_FOLLOW, + self:GetParent() + ) +end +function modifier_item_wolf_hat_howl.prototype.OnDestroy(self) + if not IsServer() then + return + end + if self.particleId ~= nil then + ParticleManager:DestroyParticle(self.particleId, false) + ParticleManager:ReleaseParticleIndex(self.particleId) + self.particleId = nil + end +end +function modifier_item_wolf_hat_howl.prototype.DeclareFunctions(self) + return {MODIFIER_PROPERTY_ATTACKSPEED_BONUS_CONSTANT, MODIFIER_PROPERTY_MOVESPEED_BONUS_PERCENTAGE} +end +function modifier_item_wolf_hat_howl.prototype.GetModifierAttackSpeedBonus_Constant(self) + return self:GetAbility():GetSpecialValueFor("howl_attack_speed") +end +function modifier_item_wolf_hat_howl.prototype.GetModifierMoveSpeedBonus_Percentage(self) + return self:GetAbility():GetSpecialValueFor("howl_move_speed_pct") +end +function modifier_item_wolf_hat_howl.prototype.GetTexture(self) + return "quest_items/wolf_claw" +end +modifier_item_wolf_hat_howl = __TS__Decorate( + modifier_item_wolf_hat_howl, + modifier_item_wolf_hat_howl, + {registerModifier(nil)}, + {kind = "class", name = "modifier_item_wolf_hat_howl"} +) +____exports.modifier_item_wolf_hat_howl = modifier_item_wolf_hat_howl +return ____exports diff --git a/scripts/vscripts/items/quest_items/kunkka_sword.lua b/scripts/vscripts/items/quest_items/kunkka_sword.lua new file mode 100644 index 0000000..323f52f --- /dev/null +++ b/scripts/vscripts/items/quest_items/kunkka_sword.lua @@ -0,0 +1,175 @@ +local ____lualib = require("lualib_bundle") +local __TS__Class = ____lualib.__TS__Class +local __TS__ClassExtends = ____lualib.__TS__ClassExtends +local __TS__Decorate = ____lualib.__TS__Decorate +local ____exports = {} +local ____dota_ts_adapter = require("lib.dota_ts_adapter") +local BaseItem = ____dota_ts_adapter.BaseItem +local BaseModifier = ____dota_ts_adapter.BaseModifier +local registerAbility = ____dota_ts_adapter.registerAbility +local registerModifier = ____dota_ts_adapter.registerModifier +____exports.item_kunkka_sword = __TS__Class() +local item_kunkka_sword = ____exports.item_kunkka_sword +item_kunkka_sword.name = "item_kunkka_sword" +item_kunkka_sword.____file_path = "scripts/vscripts/items/quest_items/kunkka_sword.lua" +__TS__ClassExtends(item_kunkka_sword, BaseItem) +function item_kunkka_sword.prototype.GetIntrinsicModifierName(self) + return "modifier_item_kunkka_sword_passive" +end +function item_kunkka_sword.prototype.OnSpellStart(self) + local caster = self:GetCaster() + caster:AddNewModifier( + caster, + self, + "modifier_item_kunkka_sword_active", + {duration = self:GetSpecialValueFor("duration")} + ) +end +item_kunkka_sword = __TS__Decorate( + item_kunkka_sword, + item_kunkka_sword, + {registerAbility(nil)}, + {kind = "class", name = "item_kunkka_sword"} +) +____exports.item_kunkka_sword = item_kunkka_sword +____exports.modifier_item_kunkka_sword_passive = __TS__Class() +local modifier_item_kunkka_sword_passive = ____exports.modifier_item_kunkka_sword_passive +modifier_item_kunkka_sword_passive.name = "modifier_item_kunkka_sword_passive" +modifier_item_kunkka_sword_passive.____file_path = "scripts/vscripts/items/quest_items/kunkka_sword.lua" +__TS__ClassExtends(modifier_item_kunkka_sword_passive, BaseModifier) +function modifier_item_kunkka_sword_passive.prototype.IsHidden(self) + return true +end +function modifier_item_kunkka_sword_passive.prototype.IsDebuff(self) + return false +end +function modifier_item_kunkka_sword_passive.prototype.IsPurgable(self) + return false +end +function modifier_item_kunkka_sword_passive.prototype.DeclareFunctions(self) + return {MODIFIER_PROPERTY_PREATTACK_BONUS_DAMAGE, MODIFIER_PROPERTY_MOVESPEED_BONUS_PERCENTAGE} +end +function modifier_item_kunkka_sword_passive.prototype.GetModifierMoveSpeedBonus_Percentage(self) + return -self:GetAbility():GetSpecialValueFor("move_speed_bonus") +end +function modifier_item_kunkka_sword_passive.prototype.GetModifierPreAttack_BonusDamage(self) + return self:GetAbility():GetSpecialValueFor("bonus_damage") +end +modifier_item_kunkka_sword_passive = __TS__Decorate( + modifier_item_kunkka_sword_passive, + modifier_item_kunkka_sword_passive, + {registerModifier(nil)}, + {kind = "class", name = "modifier_item_kunkka_sword_passive"} +) +____exports.modifier_item_kunkka_sword_passive = modifier_item_kunkka_sword_passive +____exports.modifier_item_kunkka_sword_active = __TS__Class() +local modifier_item_kunkka_sword_active = ____exports.modifier_item_kunkka_sword_active +modifier_item_kunkka_sword_active.name = "modifier_item_kunkka_sword_active" +modifier_item_kunkka_sword_active.____file_path = "scripts/vscripts/items/quest_items/kunkka_sword.lua" +__TS__ClassExtends(modifier_item_kunkka_sword_active, BaseModifier) +function modifier_item_kunkka_sword_active.prototype.IsHidden(self) + return false +end +function modifier_item_kunkka_sword_active.prototype.IsDebuff(self) + return false +end +function modifier_item_kunkka_sword_active.prototype.IsPurgable(self) + return true +end +function modifier_item_kunkka_sword_active.prototype.GetAttributes(self) + return MODIFIER_ATTRIBUTE_MULTIPLE +end +function modifier_item_kunkka_sword_active.prototype.DeclareFunctions(self) + return {MODIFIER_EVENT_ON_ATTACK_LANDED} +end +function modifier_item_kunkka_sword_active.prototype.OnAttackLanded(self, event) + if not IsServer() then + return + end + if self:GetParent() ~= event.attacker then + return + end + self:DecrementStackCount() + local target = event.target + if target ~= nil then + local particle = ParticleManager:CreateParticle("particles/units/heroes/hero_morphling/morphling_adaptive_strike.vpcf", PATTACH_ABSORIGIN, target) + ParticleManager:SetParticleControl( + particle, + 1, + target:GetAbsOrigin() + ) + ParticleManager:SetParticleControl( + particle, + 3, + target:GetAbsOrigin() + ) + ParticleManager:SetParticleControl( + particle, + 5, + target:GetAbsOrigin() + ) + ParticleManager:ReleaseParticleIndex(particle) + end + if self:GetStackCount() == 0 then + self:Destroy() + end +end +function modifier_item_kunkka_sword_active.prototype.GetEffectName(self) + return "particles/units/heroes/hero_brewmaster/brewmaster_drunken_haze_debuff.vpcf" +end +function modifier_item_kunkka_sword_active.prototype.GetEffectAttachType(self) + return PATTACH_ABSORIGIN_FOLLOW +end +function modifier_item_kunkka_sword_active.prototype.OnCreated(self) + if not IsServer() then + return + end + local ____self_SetStackCount_2 = self.SetStackCount + local ____opt_0 = self:GetAbility() + ____self_SetStackCount_2( + self, + ____opt_0 and ____opt_0:GetSpecialValueFor("attack_count") or 0 + ) + local stackingCritMod = self:GetParent():FindModifierByName("modifier_stacking_crit") + if stackingCritMod then + local ability = self:GetAbility() + local ____self_8 = stackingCritMod + local ____self_8_AddCustomCrit_9 = ____self_8.AddCustomCrit + local ____opt_3 = self:GetAbility() + local ____temp_7 = ____opt_3 and ____opt_3:GetSpecialValueFor("crit_chance") or 0 + local ____opt_5 = self:GetAbility() + ____self_8_AddCustomCrit_9( + ____self_8, + ____temp_7, + ____opt_5 and ____opt_5:GetSpecialValueFor("crit_mult") or 0, + "item_kunkka_sword", + ability + ) + end +end +function modifier_item_kunkka_sword_active.prototype.OnDestroy(self) + if IsClient() then + return + end + local parent = self:GetParent() + if not parent or not IsValidEntity(parent) then + return + end + local stackingCritModifier = parent:FindModifierByName("modifier_stacking_crit") + if not stackingCritModifier then + return + end + local ability = self:GetAbility() + stackingCritModifier:RemoveCrit("item_kunkka_sword", ability) +end +function modifier_item_kunkka_sword_active.prototype.GetTexture(self) + return "default_items/kunkka_sword" +end +modifier_item_kunkka_sword_active = __TS__Decorate( + modifier_item_kunkka_sword_active, + modifier_item_kunkka_sword_active, + {registerModifier(nil)}, + {kind = "class", name = "modifier_item_kunkka_sword_active"} +) +____exports.modifier_item_kunkka_sword_active = modifier_item_kunkka_sword_active +return ____exports diff --git a/scripts/vscripts/items/quest_items/oldmen_amulet.lua b/scripts/vscripts/items/quest_items/oldmen_amulet.lua new file mode 100644 index 0000000..4377ba2 --- /dev/null +++ b/scripts/vscripts/items/quest_items/oldmen_amulet.lua @@ -0,0 +1,75 @@ +local ____lualib = require("lualib_bundle") +local __TS__Class = ____lualib.__TS__Class +local __TS__ClassExtends = ____lualib.__TS__ClassExtends +local __TS__Decorate = ____lualib.__TS__Decorate +local ____exports = {} +local ____dota_ts_adapter = require("lib.dota_ts_adapter") +local registerAbility = ____dota_ts_adapter.registerAbility +local registerModifier = ____dota_ts_adapter.registerModifier +local ____dota_ts_adapter = require("lib.dota_ts_adapter") +local BaseItem = ____dota_ts_adapter.BaseItem +local BaseModifier = ____dota_ts_adapter.BaseModifier +____exports.item_oldmen_amulet = __TS__Class() +local item_oldmen_amulet = ____exports.item_oldmen_amulet +item_oldmen_amulet.name = "item_oldmen_amulet" +item_oldmen_amulet.____file_path = "scripts/vscripts/items/quest_items/oldmen_amulet.lua" +__TS__ClassExtends(item_oldmen_amulet, BaseItem) +function item_oldmen_amulet.prototype.GetIntrinsicModifierName(self) + return "modifier_item_oldmen_amulet_passive" +end +function item_oldmen_amulet.prototype.OnSpellStart(self) + local target = self:GetCursorTarget() + if not target then + return + end + local manaRestore = self:GetSpecialValueFor("mana_restore") + target:GiveMana(manaRestore) + local particleId = ParticleManager:CreateParticle("particles/items3_fx/mango_active.vpcf", PATTACH_ABSORIGIN_FOLLOW, target) + ParticleManager:ReleaseParticleIndex(particleId) + SendOverheadEventMessage( + nil, + OVERHEAD_ALERT_MANA_ADD, + target, + manaRestore, + target:GetPlayerOwner() + ) + EmitSoundOn("DOTA_Item.Mango.Activate", target) +end +item_oldmen_amulet = __TS__Decorate( + item_oldmen_amulet, + item_oldmen_amulet, + {registerAbility(nil)}, + {kind = "class", name = "item_oldmen_amulet"} +) +____exports.item_oldmen_amulet = item_oldmen_amulet +____exports.modifier_item_oldmen_amulet_passive = __TS__Class() +local modifier_item_oldmen_amulet_passive = ____exports.modifier_item_oldmen_amulet_passive +modifier_item_oldmen_amulet_passive.name = "modifier_item_oldmen_amulet_passive" +modifier_item_oldmen_amulet_passive.____file_path = "scripts/vscripts/items/quest_items/oldmen_amulet.lua" +__TS__ClassExtends(modifier_item_oldmen_amulet_passive, BaseModifier) +function modifier_item_oldmen_amulet_passive.prototype.IsHidden(self) + return true +end +function modifier_item_oldmen_amulet_passive.prototype.IsDebuff(self) + return false +end +function modifier_item_oldmen_amulet_passive.prototype.IsPurgable(self) + return false +end +function modifier_item_oldmen_amulet_passive.prototype.DeclareFunctions(self) + return {MODIFIER_PROPERTY_SPELL_AMPLIFY_PERCENTAGE, MODIFIER_PROPERTY_MANACOST_PERCENTAGE_STACKING} +end +function modifier_item_oldmen_amulet_passive.prototype.GetModifierSpellAmplify_Percentage(self) + return self:GetAbility():GetSpecialValueFor("spell_amplify_percentage") +end +function modifier_item_oldmen_amulet_passive.prototype.GetModifierPercentageManacostStacking(self) + return -self:GetAbility():GetSpecialValueFor("manacost_debuff") +end +modifier_item_oldmen_amulet_passive = __TS__Decorate( + modifier_item_oldmen_amulet_passive, + modifier_item_oldmen_amulet_passive, + {registerModifier(nil)}, + {kind = "class", name = "modifier_item_oldmen_amulet_passive"} +) +____exports.modifier_item_oldmen_amulet_passive = modifier_item_oldmen_amulet_passive +return ____exports diff --git a/scripts/vscripts/items/quest_items/wooden_katana.lua b/scripts/vscripts/items/quest_items/wooden_katana.lua new file mode 100644 index 0000000..2b26f4c --- /dev/null +++ b/scripts/vscripts/items/quest_items/wooden_katana.lua @@ -0,0 +1,54 @@ +local ____lualib = require("lualib_bundle") +local __TS__Class = ____lualib.__TS__Class +local __TS__ClassExtends = ____lualib.__TS__ClassExtends +local __TS__Decorate = ____lualib.__TS__Decorate +local ____exports = {} +local ____dota_ts_adapter = require("lib.dota_ts_adapter") +local registerAbility = ____dota_ts_adapter.registerAbility +local registerModifier = ____dota_ts_adapter.registerModifier +local ____dota_ts_adapter = require("lib.dota_ts_adapter") +local BaseItem = ____dota_ts_adapter.BaseItem +local BaseModifier = ____dota_ts_adapter.BaseModifier +____exports.item_wooden_katana = __TS__Class() +local item_wooden_katana = ____exports.item_wooden_katana +item_wooden_katana.name = "item_wooden_katana" +item_wooden_katana.____file_path = "scripts/vscripts/items/quest_items/wooden_katana.lua" +__TS__ClassExtends(item_wooden_katana, BaseItem) +function item_wooden_katana.prototype.GetIntrinsicModifierName(self) + return "modifier_item_wooden_katana_passive" +end +item_wooden_katana = __TS__Decorate( + item_wooden_katana, + item_wooden_katana, + {registerAbility(nil)}, + {kind = "class", name = "item_wooden_katana"} +) +____exports.item_wooden_katana = item_wooden_katana +____exports.modifier_item_wooden_katana_passive = __TS__Class() +local modifier_item_wooden_katana_passive = ____exports.modifier_item_wooden_katana_passive +modifier_item_wooden_katana_passive.name = "modifier_item_wooden_katana_passive" +modifier_item_wooden_katana_passive.____file_path = "scripts/vscripts/items/quest_items/wooden_katana.lua" +__TS__ClassExtends(modifier_item_wooden_katana_passive, BaseModifier) +function modifier_item_wooden_katana_passive.prototype.IsHidden(self) + return true +end +function modifier_item_wooden_katana_passive.prototype.IsDebuff(self) + return false +end +function modifier_item_wooden_katana_passive.prototype.IsPurgable(self) + return false +end +function modifier_item_wooden_katana_passive.prototype.DeclareFunctions(self) + return {MODIFIER_PROPERTY_DAMAGEOUTGOING_PERCENTAGE} +end +function modifier_item_wooden_katana_passive.prototype.GetModifierDamageOutgoing_Percentage(self, event) + return self:GetAbility():GetSpecialValueFor("damage_outgoing_percentage") +end +modifier_item_wooden_katana_passive = __TS__Decorate( + modifier_item_wooden_katana_passive, + modifier_item_wooden_katana_passive, + {registerModifier(nil)}, + {kind = "class", name = "modifier_item_wooden_katana_passive"} +) +____exports.modifier_item_wooden_katana_passive = modifier_item_wooden_katana_passive +return ____exports diff --git a/scripts/vscripts/items/util_items/item_banana.lua b/scripts/vscripts/items/util_items/item_banana.lua new file mode 100644 index 0000000..a97d78d --- /dev/null +++ b/scripts/vscripts/items/util_items/item_banana.lua @@ -0,0 +1,88 @@ +local ____lualib = require("lualib_bundle") +local __TS__Class = ____lualib.__TS__Class +local __TS__ClassExtends = ____lualib.__TS__ClassExtends +local __TS__Decorate = ____lualib.__TS__Decorate +local ____exports = {} +local ____modifier_general_hunger = require("abilities.modifiers.modifier_general_hunger") +local modifier_general_hunger = ____modifier_general_hunger.modifier_general_hunger +local ____dota_ts_adapter = require("lib.dota_ts_adapter") +local BaseItem = ____dota_ts_adapter.BaseItem +local registerAbility = ____dota_ts_adapter.registerAbility +____exports.item_banana = __TS__Class() +local item_banana = ____exports.item_banana +item_banana.name = "item_banana" +item_banana.____file_path = "scripts/vscripts/items/util_items/item_banana.lua" +__TS__ClassExtends(item_banana, BaseItem) +function item_banana.prototype.CastFilterResultTarget(self, target) + if IsServer() then + if not target:IsRealHero() then + return UF_FAIL_CUSTOM + end + if self:GetCurrentCharges() < self:GetInitialCharges() then + return UF_FAIL_CUSTOM + end + return UF_SUCCESS + end + return UF_SUCCESS +end +function item_banana.prototype.GetCustomCastErrorTarget(self, target) + if IsServer() then + if not target:IsRealHero() then + return "#dota_hud_error_cheese_bad_target" + end + if self:GetCurrentCharges() < self:GetInitialCharges() then + return "#dota_hud_error_havent_charges" + end + end + return "" +end +function item_banana.prototype.OnSpellStart(self) + local target = self:GetCursorTarget() + local item = self + if not target then + return + end + local manaAmount = self:GetSpecialValueFor("mana") + target:EmitSound("DOTA_Item.Cheese.Activate") + if manaAmount > 0 then + target:GiveMana(manaAmount) + SendOverheadEventMessage( + nil, + OVERHEAD_ALERT_MANA_ADD, + target, + manaAmount, + target:GetPlayerOwner() + ) + end + if target:IsRealHero() then + local modifier = target:AddNewModifier( + self:GetCaster(), + self, + modifier_general_hunger.name, + {} + ) + if modifier then + local hunger_bonus = self:GetSpecialValueFor("hunger_bonus") + do + local i = 0 + while i < hunger_bonus do + modifier:IncrementStackCount() + i = i + 1 + end + end + end + if item:GetCurrentCharges() <= item:GetInitialCharges() then + UTIL_Remove(item) + return + end + item:SetCurrentCharges(item:GetCurrentCharges() - item:GetInitialCharges()) + end +end +item_banana = __TS__Decorate( + item_banana, + item_banana, + {registerAbility(nil)}, + {kind = "class", name = "item_banana"} +) +____exports.item_banana = item_banana +return ____exports diff --git a/scripts/vscripts/items/util_items/item_bread.lua b/scripts/vscripts/items/util_items/item_bread.lua new file mode 100644 index 0000000..27420cc --- /dev/null +++ b/scripts/vscripts/items/util_items/item_bread.lua @@ -0,0 +1,85 @@ +local ____lualib = require("lualib_bundle") +local __TS__Class = ____lualib.__TS__Class +local __TS__ClassExtends = ____lualib.__TS__ClassExtends +local __TS__Decorate = ____lualib.__TS__Decorate +local ____exports = {} +local ____modifier_general_hunger = require("abilities.modifiers.modifier_general_hunger") +local modifier_general_hunger = ____modifier_general_hunger.modifier_general_hunger +local ____dota_ts_adapter = require("lib.dota_ts_adapter") +local BaseItem = ____dota_ts_adapter.BaseItem +local registerAbility = ____dota_ts_adapter.registerAbility +____exports.item_bread = __TS__Class() +local item_bread = ____exports.item_bread +item_bread.name = "item_bread" +item_bread.____file_path = "scripts/vscripts/items/util_items/item_bread.lua" +__TS__ClassExtends(item_bread, BaseItem) +function item_bread.prototype.CastFilterResultTarget(self, target) + if IsServer() then + if not target:IsRealHero() then + return UF_FAIL_CUSTOM + end + if self:GetCurrentCharges() < self:GetInitialCharges() then + return UF_FAIL_CUSTOM + end + return UF_SUCCESS + end + return UF_SUCCESS +end +function item_bread.prototype.GetCustomCastErrorTarget(self, target) + if IsServer() then + if not target:IsRealHero() then + return "#dota_hud_error_cheese_bad_target" + end + if self:GetCurrentCharges() < self:GetInitialCharges() then + return "#dota_hud_error_havent_charges" + end + end + return "" +end +function item_bread.prototype.OnSpellStart(self) + local target = self:GetCursorTarget() + local item = self + local healAmount = self:GetSpecialValueFor("heal") + if target ~= nil then + target:EmitSound("DOTA_Item.Cheese.Activate") + target:Heal(healAmount, self) + SendOverheadEventMessage( + nil, + OVERHEAD_ALERT_HEAL, + target, + healAmount, + target:GetPlayerOwner() + ) + end + if target and target:IsRealHero() then + local modifier = target:AddNewModifier( + self:GetCaster(), + self, + modifier_general_hunger.name, + {} + ) + if modifier then + local hunger_bonus = self:GetSpecialValueFor("hunger_bonus") + do + local i = 0 + while i < hunger_bonus do + modifier:IncrementStackCount() + i = i + 1 + end + end + if item:GetCurrentCharges() <= item:GetInitialCharges() then + UTIL_Remove(item) + return + end + item:SetCurrentCharges(item:GetCurrentCharges() - item:GetInitialCharges()) + end + end +end +item_bread = __TS__Decorate( + item_bread, + item_bread, + {registerAbility(nil)}, + {kind = "class", name = "item_bread"} +) +____exports.item_bread = item_bread +return ____exports diff --git a/scripts/vscripts/items/util_items/item_candys.lua b/scripts/vscripts/items/util_items/item_candys.lua new file mode 100644 index 0000000..8a8673b --- /dev/null +++ b/scripts/vscripts/items/util_items/item_candys.lua @@ -0,0 +1,321 @@ +local ____lualib = require("lualib_bundle") +local __TS__Class = ____lualib.__TS__Class +local __TS__ClassExtends = ____lualib.__TS__ClassExtends +local __TS__Decorate = ____lualib.__TS__Decorate +local ____exports = {} +local ____modifier_general_hunger = require("abilities.modifiers.modifier_general_hunger") +local modifier_general_hunger = ____modifier_general_hunger.modifier_general_hunger +local ____dota_ts_adapter = require("lib.dota_ts_adapter") +local BaseItem = ____dota_ts_adapter.BaseItem +local registerAbility = ____dota_ts_adapter.registerAbility +local registerModifier = ____dota_ts_adapter.registerModifier +local BaseModifier = ____dota_ts_adapter.BaseModifier +____exports.item_candy = __TS__Class() +local item_candy = ____exports.item_candy +item_candy.name = "item_candy" +item_candy.____file_path = "scripts/vscripts/items/util_items/item_candys.lua" +__TS__ClassExtends(item_candy, BaseItem) +function item_candy.prototype.CastFilterResultTarget(self, target) + if IsServer() then + if not target:IsRealHero() then + return UF_FAIL_CUSTOM + end + if self:GetCurrentCharges() < self:GetInitialCharges() then + return UF_FAIL_CUSTOM + end + return UF_SUCCESS + end + return UF_SUCCESS +end +function item_candy.prototype.GetCustomCastErrorTarget(self, target) + if IsServer() then + if not target:IsRealHero() then + return "#dota_hud_error_cheese_bad_target" + end + if self:GetCurrentCharges() < self:GetInitialCharges() then + return "#dota_hud_error_havent_charges" + end + end + return "" +end +function item_candy.prototype.OnSpellStart(self) + local target = self:GetCursorTarget() + local item = self + if target == nil then + return + end + target:EmitSound("DOTA_Item.Cheese.Activate") + if target and target:IsRealHero() then + local modifier = target:AddNewModifier( + self:GetCaster(), + self, + modifier_general_hunger.name, + {} + ) + local candyModifiers = {____exports.modifier_chocolate_candy.name, ____exports.modifier_caramel_candy.name, ____exports.modifier_mint_candy.name, ____exports.modifier_magic_candy.name} + local randomIndex = math.floor(math.random() * #candyModifiers) + local selectedModifier = candyModifiers[randomIndex + 1] + target:AddNewModifier( + self:GetCaster(), + self, + selectedModifier, + {duration = 30} + ) + if modifier then + local hunger_bonus = self:GetSpecialValueFor("hunger_bonus") + do + local i = 0 + while i < hunger_bonus do + modifier:IncrementStackCount() + i = i + 1 + end + end + if item:GetCurrentCharges() <= item:GetInitialCharges() then + UTIL_Remove(item) + return + end + item:SetCurrentCharges(item:GetCurrentCharges() - item:GetInitialCharges()) + end + end +end +item_candy = __TS__Decorate( + item_candy, + item_candy, + {registerAbility(nil)}, + {kind = "class", name = "item_candy"} +) +____exports.item_candy = item_candy +____exports.modifier_chocolate_candy = __TS__Class() +local modifier_chocolate_candy = ____exports.modifier_chocolate_candy +modifier_chocolate_candy.name = "modifier_chocolate_candy" +modifier_chocolate_candy.____file_path = "scripts/vscripts/items/util_items/item_candys.lua" +__TS__ClassExtends(modifier_chocolate_candy, BaseModifier) +function modifier_chocolate_candy.prototype.IsHidden(self) + return false +end +function modifier_chocolate_candy.prototype.GetAttributes(self) + return MODIFIER_ATTRIBUTE_MULTIPLE +end +function modifier_chocolate_candy.prototype.DeclareFunctions(self) + return {MODIFIER_PROPERTY_MOVESPEED_BONUS_PERCENTAGE} +end +function modifier_chocolate_candy.prototype.GetModifierMoveSpeedBonus_Percentage(self) + return 15 +end +function modifier_chocolate_candy.prototype.OnCreated(self) + if not IsServer() then + return + end + self:StartIntervalThink(0.33) +end +function modifier_chocolate_candy.prototype.OnIntervalThink(self) + if not IsServer() then + return + end + local parent = self:GetParent() + if not parent or not parent:IsAlive() then + return + end + if parent:IsMoving() then + else + ApplyDamage({ + victim = parent, + attacker = parent, + damage = self:GetParent():GetHealth() * 0.01, + damage_type = DAMAGE_TYPE_PURE + }) + end +end +function modifier_chocolate_candy.prototype.GetTexture(self) + return "util_items/candy" +end +modifier_chocolate_candy = __TS__Decorate( + modifier_chocolate_candy, + modifier_chocolate_candy, + {registerModifier(nil)}, + {kind = "class", name = "modifier_chocolate_candy"} +) +____exports.modifier_chocolate_candy = modifier_chocolate_candy +____exports.modifier_caramel_candy = __TS__Class() +local modifier_caramel_candy = ____exports.modifier_caramel_candy +modifier_caramel_candy.name = "modifier_caramel_candy" +modifier_caramel_candy.____file_path = "scripts/vscripts/items/util_items/item_candys.lua" +__TS__ClassExtends(modifier_caramel_candy, BaseModifier) +function modifier_caramel_candy.prototype.IsHidden(self) + return false +end +function modifier_caramel_candy.prototype.DeclareFunctions(self) + return {MODIFIER_PROPERTY_INCOMING_DAMAGE_PERCENTAGE, MODIFIER_EVENT_ON_TAKEDAMAGE} +end +function modifier_caramel_candy.prototype.GetModifierIncomingDamage_Percentage(self, event) + if RandomInt(0, 99) < 20 then + return 100 + end + return 0 +end +function modifier_caramel_candy.prototype.OnTakeDamage(self, event) + if event.unit ~= self:GetParent() then + return + end + if event.attacker == self:GetParent() then + return + end + if RandomInt(0, 99) < 20 then + ApplyDamage({ + victim = event.attacker, + attacker = event.unit, + damage = self:GetParent():GetAverageTrueAttackDamage(event.attacker) + event.damage * 0.5, + damage_type = DAMAGE_TYPE_PHYSICAL + }) + end +end +function modifier_caramel_candy.prototype.GetAttributes(self) + return MODIFIER_ATTRIBUTE_MULTIPLE +end +function modifier_caramel_candy.prototype.GetTexture(self) + return "util_items/candy" +end +modifier_caramel_candy = __TS__Decorate( + modifier_caramel_candy, + modifier_caramel_candy, + {registerModifier(nil)}, + {kind = "class", name = "modifier_caramel_candy"} +) +____exports.modifier_caramel_candy = modifier_caramel_candy +____exports.modifier_mint_candy = __TS__Class() +local modifier_mint_candy = ____exports.modifier_mint_candy +modifier_mint_candy.name = "modifier_mint_candy" +modifier_mint_candy.____file_path = "scripts/vscripts/items/util_items/item_candys.lua" +__TS__ClassExtends(modifier_mint_candy, BaseModifier) +function modifier_mint_candy.prototype.____constructor(self, ...) + BaseModifier.prototype.____constructor(self, ...) + self.Lock = false +end +function modifier_mint_candy.prototype.IsHidden(self) + return false +end +function modifier_mint_candy.prototype.OnCreated(self, params) + self:StartIntervalThink(0.1) +end +function modifier_mint_candy.prototype.GetAttributes(self) + return MODIFIER_ATTRIBUTE_MULTIPLE +end +function modifier_mint_candy.prototype.OnIntervalThink(self) + if not IsServer() then + return + end + if RandomInt(1, 100) < 50 then + self:GetParent():AddNewModifier( + self:GetParent(), + self:GetAbility(), + "modifier_stunned", + {duration = 0.15} + ) + end +end +function modifier_mint_candy.prototype.DeclareFunctions(self) + return { + MODIFIER_PROPERTY_MOVESPEED_BONUS_PERCENTAGE, + MODIFIER_PROPERTY_BONUS_DAY_VISION, + MODIFIER_PROPERTY_BONUS_NIGHT_VISION, + MODIFIER_PROPERTY_STATS_AGILITY_BONUS, + MODIFIER_PROPERTY_STATS_INTELLECT_BONUS, + MODIFIER_PROPERTY_STATS_STRENGTH_BONUS, + MODIFIER_PROPERTY_MIN_HEALTH + } +end +function modifier_mint_candy.prototype.GetModifierMinHealth(self) + return 1 +end +function modifier_mint_candy.prototype.GetModifierMoveSpeedBonus_Percentage(self) + return -50 +end +function modifier_mint_candy.prototype.GetBonusDayVision(self) + return -250 +end +function modifier_mint_candy.prototype.GetBonusNightVision(self) + return -250 +end +function modifier_mint_candy.prototype.GetModifierBonusStats_Agility(self) + if not IsServer() then + return 0 + end + if self.Lock then + return 0 + end + self.Lock = true + local bonus = self:GetParent():GetAgility() + local PctBonus = 0.5 * bonus + self.Lock = false + return -PctBonus +end +function modifier_mint_candy.prototype.GetModifierBonusStats_Intellect(self) + if not IsServer() then + return 0 + end + if self.Lock then + return 0 + end + self.Lock = true + local bonus = self:GetParent():GetIntellect(true) + local PctBonus = 0.5 * bonus + self.Lock = false + return -PctBonus +end +function modifier_mint_candy.prototype.GetModifierBonusStats_Strength(self) + if not IsServer() then + return 0 + end + if self.Lock then + return 0 + end + self.Lock = true + local bonus = self:GetParent():GetStrength() + local PctBonus = 0.5 * bonus + self.Lock = false + return -PctBonus +end +function modifier_mint_candy.prototype.GetTexture(self) + return "util_items/candy" +end +modifier_mint_candy = __TS__Decorate( + modifier_mint_candy, + modifier_mint_candy, + {registerModifier(nil)}, + {kind = "class", name = "modifier_mint_candy"} +) +____exports.modifier_mint_candy = modifier_mint_candy +____exports.modifier_magic_candy = __TS__Class() +local modifier_magic_candy = ____exports.modifier_magic_candy +modifier_magic_candy.name = "modifier_magic_candy" +modifier_magic_candy.____file_path = "scripts/vscripts/items/util_items/item_candys.lua" +__TS__ClassExtends(modifier_magic_candy, BaseModifier) +function modifier_magic_candy.prototype.IsHidden(self) + return false +end +function modifier_magic_candy.prototype.GetAttributes(self) + return MODIFIER_ATTRIBUTE_MULTIPLE +end +function modifier_magic_candy.prototype.DeclareFunctions(self) + return {MODIFIER_PROPERTY_SPELL_AMPLIFY_PERCENTAGE, MODIFIER_PROPERTY_COOLDOWN_PERCENTAGE, MODIFIER_PROPERTY_DAMAGEOUTGOING_PERCENTAGE} +end +function modifier_magic_candy.prototype.GetModifierSpellAmplify_Percentage(self) + return 15 +end +function modifier_magic_candy.prototype.GetModifierCooldown_Percentage(self) + return 15 +end +function modifier_magic_candy.prototype.GetModifierDamageOutgoing_Percentage(self) + return -15 +end +function modifier_magic_candy.prototype.GetTexture(self) + return "util_items/candy" +end +modifier_magic_candy = __TS__Decorate( + modifier_magic_candy, + modifier_magic_candy, + {registerModifier(nil)}, + {kind = "class", name = "modifier_magic_candy"} +) +____exports.modifier_magic_candy = modifier_magic_candy +return ____exports diff --git a/scripts/vscripts/items/util_items/item_cocktail.lua b/scripts/vscripts/items/util_items/item_cocktail.lua new file mode 100644 index 0000000..c5a9ec5 --- /dev/null +++ b/scripts/vscripts/items/util_items/item_cocktail.lua @@ -0,0 +1,189 @@ +local ____lualib = require("lualib_bundle") +local __TS__Class = ____lualib.__TS__Class +local __TS__ClassExtends = ____lualib.__TS__ClassExtends +local __TS__Decorate = ____lualib.__TS__Decorate +local ____exports = {} +local ____modifier_general_hunger = require("abilities.modifiers.modifier_general_hunger") +local modifier_buff_food_slot = ____modifier_general_hunger.modifier_buff_food_slot +local modifier_general_hunger = ____modifier_general_hunger.modifier_general_hunger +local BUFF_FOOD_MAX_SLOTS = ____modifier_general_hunger.BUFF_FOOD_MAX_SLOTS +local getBuffFoodSlotCount = ____modifier_general_hunger.getBuffFoodSlotCount +local ____dota_ts_adapter = require("lib.dota_ts_adapter") +local BaseItem = ____dota_ts_adapter.BaseItem +local BaseModifier = ____dota_ts_adapter.BaseModifier +local registerAbility = ____dota_ts_adapter.registerAbility +local registerModifier = ____dota_ts_adapter.registerModifier +local ____vampirism = require("utils.vampirism") +local addMagicalVampirism = ____vampirism.addMagicalVampirism +local reduceMagicalVampirism = ____vampirism.reduceMagicalVampirism +--- Коктейль — бафф-еда: маг вампиризм, маг. усиление, +20% к манакосту +____exports.item_cocktail = __TS__Class() +local item_cocktail = ____exports.item_cocktail +item_cocktail.name = "item_cocktail" +item_cocktail.____file_path = "scripts/vscripts/items/util_items/item_cocktail.lua" +__TS__ClassExtends(item_cocktail, BaseItem) +function item_cocktail.prototype.CastFilterResultTarget(self, target) + if IsServer() then + if not target:IsRealHero() then + return UF_FAIL_CUSTOM + end + if getBuffFoodSlotCount(nil, target) >= BUFF_FOOD_MAX_SLOTS then + return UF_FAIL_CUSTOM + end + if self:GetCurrentCharges() < self:GetInitialCharges() then + return UF_FAIL_CUSTOM + end + return UF_SUCCESS + end + return UF_SUCCESS +end +function item_cocktail.prototype.GetCustomCastErrorTarget(self, target) + if IsServer() then + if not target:IsRealHero() then + return "#dota_hud_error_cheese_bad_target" + end + if getBuffFoodSlotCount(nil, target) >= BUFF_FOOD_MAX_SLOTS then + return "#dota_hud_error_ability_not_ready" + end + if self:GetCurrentCharges() < self:GetInitialCharges() then + return "#dota_hud_error_havent_charges" + end + end + return "" +end +function item_cocktail.prototype.OnSpellStart(self) + local target = self:GetCursorTarget() + local item = self + if not target then + return + end + target:EmitSound("DOTA_Item.Cheese.Activate") + if target:IsRealHero() then + local buffDuration = self:GetSpecialValueFor("buff_duration") + local spellLifesteal = self:GetSpecialValueFor("spell_lifesteal") + local spellAmp = self:GetSpecialValueFor("spell_amp") + local manacostIncrease = self:GetSpecialValueFor("manacost_increase") + local hungerModifier = target:AddNewModifier( + self:GetCaster(), + self, + modifier_general_hunger.name, + {} + ) + target:AddNewModifier( + self:GetCaster(), + self, + modifier_buff_food_slot.name, + {duration = buffDuration} + ) + target:AddNewModifier( + self:GetCaster(), + self, + ____exports.modifier_item_cocktail_buff.name, + {duration = buffDuration, spell_lifesteal = spellLifesteal, spell_amp = spellAmp, manacost_increase = manacostIncrease} + ) + if hungerModifier then + local hunger_bonus = self:GetSpecialValueFor("hunger_bonus") + do + local i = 0 + while i < hunger_bonus do + hungerModifier:IncrementStackCount() + i = i + 1 + end + end + end + if item:GetCurrentCharges() <= item:GetInitialCharges() then + UTIL_Remove(item) + return + end + item:SetCurrentCharges(item:GetCurrentCharges() - item:GetInitialCharges()) + end +end +item_cocktail = __TS__Decorate( + item_cocktail, + item_cocktail, + {registerAbility(nil)}, + {kind = "class", name = "item_cocktail"} +) +____exports.item_cocktail = item_cocktail +--- Бафф от коктейля: маг вампиризм, спелл амп, +% к манакосту +____exports.modifier_item_cocktail_buff = __TS__Class() +local modifier_item_cocktail_buff = ____exports.modifier_item_cocktail_buff +modifier_item_cocktail_buff.name = "modifier_item_cocktail_buff" +modifier_item_cocktail_buff.____file_path = "scripts/vscripts/items/util_items/item_cocktail.lua" +__TS__ClassExtends(modifier_item_cocktail_buff, BaseModifier) +function modifier_item_cocktail_buff.prototype.____constructor(self, ...) + BaseModifier.prototype.____constructor(self, ...) + self.spellLifesteal = 0 + self.spellAmp = 0 + self.manacostIncrease = 0 +end +function modifier_item_cocktail_buff.prototype.IsHidden(self) + return false +end +function modifier_item_cocktail_buff.prototype.IsPurgable(self) + return true +end +function modifier_item_cocktail_buff.prototype.IsDebuff(self) + return false +end +function modifier_item_cocktail_buff.prototype.GetAttributes(self) + return MODIFIER_ATTRIBUTE_MULTIPLE +end +function modifier_item_cocktail_buff.prototype.GetTexture(self) + return "../items/utils/cocktail" +end +function modifier_item_cocktail_buff.prototype.DeclareFunctions(self) + return {MODIFIER_PROPERTY_SPELL_AMPLIFY_PERCENTAGE, MODIFIER_PROPERTY_MANACOST_PERCENTAGE_STACKING} +end +function modifier_item_cocktail_buff.prototype.OnCreated(self, params) + if (params and params.spell_lifesteal) ~= nil then + self.spellLifesteal = params.spell_lifesteal + end + if (params and params.spell_amp) ~= nil then + self.spellAmp = params.spell_amp + end + if (params and params.manacost_increase) ~= nil then + self.manacostIncrease = params.manacost_increase + end + local ability = self:GetAbility() + if ability then + if self.spellLifesteal == 0 then + self.spellLifesteal = ability:GetSpecialValueFor("spell_lifesteal") + end + if self.spellAmp == 0 then + self.spellAmp = ability:GetSpecialValueFor("spell_amp") + end + if self.manacostIncrease == 0 then + self.manacostIncrease = ability:GetSpecialValueFor("manacost_increase") + end + end + if IsServer() then + local caster = self:GetCaster() + if caster and self.spellLifesteal > 0 then + addMagicalVampirism(nil, caster, self.spellLifesteal) + end + end +end +function modifier_item_cocktail_buff.prototype.OnDestroy(self) + if not IsServer() then + return + end + local caster = self:GetCaster() + if caster and self.spellLifesteal > 0 then + reduceMagicalVampirism(nil, caster, self.spellLifesteal) + end +end +function modifier_item_cocktail_buff.prototype.GetModifierSpellAmplify_Percentage(self) + return self.spellAmp +end +function modifier_item_cocktail_buff.prototype.GetModifierPercentageManacostStacking(self) + return -self.manacostIncrease +end +modifier_item_cocktail_buff = __TS__Decorate( + modifier_item_cocktail_buff, + modifier_item_cocktail_buff, + {registerModifier(nil)}, + {kind = "class", name = "modifier_item_cocktail_buff"} +) +____exports.modifier_item_cocktail_buff = modifier_item_cocktail_buff +return ____exports diff --git a/scripts/vscripts/items/util_items/item_coffe_bean.lua b/scripts/vscripts/items/util_items/item_coffe_bean.lua new file mode 100644 index 0000000..a19ae4b --- /dev/null +++ b/scripts/vscripts/items/util_items/item_coffe_bean.lua @@ -0,0 +1,76 @@ +local ____lualib = require("lualib_bundle") +local __TS__Class = ____lualib.__TS__Class +local __TS__ClassExtends = ____lualib.__TS__ClassExtends +local __TS__Decorate = ____lualib.__TS__Decorate +local ____exports = {} +local ____dota_ts_adapter = require("lib.dota_ts_adapter") +local BaseItem = ____dota_ts_adapter.BaseItem +local registerAbility = ____dota_ts_adapter.registerAbility +--- Кофейные зёрна — восстанавливают ману и не занимают слоты бафф-еды +____exports.item_coffe_bean = __TS__Class() +local item_coffe_bean = ____exports.item_coffe_bean +item_coffe_bean.name = "item_coffe_bean" +item_coffe_bean.____file_path = "scripts/vscripts/items/util_items/item_coffe_bean.lua" +__TS__ClassExtends(item_coffe_bean, BaseItem) +function item_coffe_bean.prototype.CastFilterResultTarget(self, target) + if IsServer() then + if not target:IsRealHero() then + return UF_FAIL_CUSTOM + end + if self:GetCurrentCharges() < self:GetInitialCharges() then + return UF_FAIL_CUSTOM + end + return UF_SUCCESS + end + return UF_SUCCESS +end +function item_coffe_bean.prototype.GetCustomCastErrorTarget(self, target) + if IsServer() then + if not target:IsRealHero() then + return "#dota_hud_error_cheese_bad_target" + end + if self:GetCurrentCharges() < self:GetInitialCharges() then + return "#dota_hud_error_havent_charges" + end + end + return "" +end +function item_coffe_bean.prototype.OnSpellStart(self) + local target = self:GetCursorTarget() + local item = self + if not target then + return + end + local maxMana = target:GetMaxMana() + local manaPercentRestore = self:GetSpecialValueFor("mana_pct") + local flatManaRestore = self:GetSpecialValueFor("mana") + local manaAmount = maxMana * manaPercentRestore / 100 + flatManaRestore + local currentMana = target:GetMana() + local manaToGive = math.min(manaAmount, maxMana - currentMana) + target:EmitSound("DOTA_Item.Cheese.Activate") + if manaToGive > 0 then + target:GiveMana(manaToGive) + SendOverheadEventMessage( + nil, + OVERHEAD_ALERT_MANA_ADD, + target, + manaToGive, + target:GetPlayerOwner() + ) + end + if target:IsRealHero() then + if item:GetCurrentCharges() <= item:GetInitialCharges() then + UTIL_Remove(item) + return + end + item:SetCurrentCharges(item:GetCurrentCharges() - item:GetInitialCharges()) + end +end +item_coffe_bean = __TS__Decorate( + item_coffe_bean, + item_coffe_bean, + {registerAbility(nil)}, + {kind = "class", name = "item_coffe_bean"} +) +____exports.item_coffe_bean = item_coffe_bean +return ____exports diff --git a/scripts/vscripts/items/util_items/item_coffee.lua b/scripts/vscripts/items/util_items/item_coffee.lua new file mode 100644 index 0000000..d3601b3 --- /dev/null +++ b/scripts/vscripts/items/util_items/item_coffee.lua @@ -0,0 +1,163 @@ +local ____lualib = require("lualib_bundle") +local __TS__Class = ____lualib.__TS__Class +local __TS__ClassExtends = ____lualib.__TS__ClassExtends +local __TS__Decorate = ____lualib.__TS__Decorate +local ____exports = {} +local ____modifier_general_hunger = require("abilities.modifiers.modifier_general_hunger") +local modifier_buff_food_slot = ____modifier_general_hunger.modifier_buff_food_slot +local modifier_general_hunger = ____modifier_general_hunger.modifier_general_hunger +local BUFF_FOOD_MAX_SLOTS = ____modifier_general_hunger.BUFF_FOOD_MAX_SLOTS +local getBuffFoodSlotCount = ____modifier_general_hunger.getBuffFoodSlotCount +local ____dota_ts_adapter = require("lib.dota_ts_adapter") +local BaseItem = ____dota_ts_adapter.BaseItem +local BaseModifier = ____dota_ts_adapter.BaseModifier +local registerAbility = ____dota_ts_adapter.registerAbility +local registerModifier = ____dota_ts_adapter.registerModifier +--- Кофе — бафф-еда: уменьшает кд способностей и ускоряет скорость каста +____exports.item_coffee = __TS__Class() +local item_coffee = ____exports.item_coffee +item_coffee.name = "item_coffee" +item_coffee.____file_path = "scripts/vscripts/items/util_items/item_coffee.lua" +__TS__ClassExtends(item_coffee, BaseItem) +function item_coffee.prototype.CastFilterResultTarget(self, target) + if IsServer() then + if not target:IsRealHero() then + return UF_FAIL_CUSTOM + end + if getBuffFoodSlotCount(nil, target) >= BUFF_FOOD_MAX_SLOTS then + return UF_FAIL_CUSTOM + end + if self:GetCurrentCharges() < self:GetInitialCharges() then + return UF_FAIL_CUSTOM + end + return UF_SUCCESS + end + return UF_SUCCESS +end +function item_coffee.prototype.GetCustomCastErrorTarget(self, target) + if IsServer() then + if not target:IsRealHero() then + return "#dota_hud_error_cheese_bad_target" + end + if getBuffFoodSlotCount(nil, target) >= BUFF_FOOD_MAX_SLOTS then + return "#dota_hud_error_ability_not_ready" + end + if self:GetCurrentCharges() < self:GetInitialCharges() then + return "#dota_hud_error_havent_charges" + end + end + return "" +end +function item_coffee.prototype.OnSpellStart(self) + local target = self:GetCursorTarget() + local item = self + if not target then + return + end + target:EmitSound("DOTA_Item.Cheese.Activate") + if target:IsRealHero() then + local buffDuration = self:GetSpecialValueFor("buff_duration") + local cooldownReduction = self:GetSpecialValueFor("cooldown_reduction") + local casttimeReduction = self:GetSpecialValueFor("casttime_reduction") + local hungerModifier = target:AddNewModifier( + self:GetCaster(), + self, + modifier_general_hunger.name, + {} + ) + target:AddNewModifier( + self:GetCaster(), + self, + modifier_buff_food_slot.name, + {duration = buffDuration} + ) + target:AddNewModifier( + self:GetCaster(), + self, + ____exports.modifier_item_coffee_buff.name, + {duration = buffDuration, cooldown_reduction = cooldownReduction, casttime_reduction = casttimeReduction} + ) + if hungerModifier then + local hunger_bonus = self:GetSpecialValueFor("hunger_bonus") + do + local i = 0 + while i < hunger_bonus do + hungerModifier:IncrementStackCount() + i = i + 1 + end + end + end + if item:GetCurrentCharges() <= item:GetInitialCharges() then + UTIL_Remove(item) + return + end + item:SetCurrentCharges(item:GetCurrentCharges() - item:GetInitialCharges()) + end +end +item_coffee = __TS__Decorate( + item_coffee, + item_coffee, + {registerAbility(nil)}, + {kind = "class", name = "item_coffee"} +) +____exports.item_coffee = item_coffee +--- Бафф от кофе: снижение кд и ускорение каста +____exports.modifier_item_coffee_buff = __TS__Class() +local modifier_item_coffee_buff = ____exports.modifier_item_coffee_buff +modifier_item_coffee_buff.name = "modifier_item_coffee_buff" +modifier_item_coffee_buff.____file_path = "scripts/vscripts/items/util_items/item_coffee.lua" +__TS__ClassExtends(modifier_item_coffee_buff, BaseModifier) +function modifier_item_coffee_buff.prototype.____constructor(self, ...) + BaseModifier.prototype.____constructor(self, ...) + self.cooldownReduction = 0 + self.casttimeReduction = 0 +end +function modifier_item_coffee_buff.prototype.IsHidden(self) + return false +end +function modifier_item_coffee_buff.prototype.IsPurgable(self) + return true +end +function modifier_item_coffee_buff.prototype.IsDebuff(self) + return false +end +function modifier_item_coffee_buff.prototype.GetAttributes(self) + return MODIFIER_ATTRIBUTE_MULTIPLE +end +function modifier_item_coffee_buff.prototype.GetTexture(self) + return "utils/coffee" +end +function modifier_item_coffee_buff.prototype.DeclareFunctions(self) + return {MODIFIER_PROPERTY_COOLDOWN_PERCENTAGE, MODIFIER_PROPERTY_CASTTIME_PERCENTAGE} +end +function modifier_item_coffee_buff.prototype.OnCreated(self, params) + if (params and params.cooldown_reduction) ~= nil then + self.cooldownReduction = params.cooldown_reduction + end + if (params and params.casttime_reduction) ~= nil then + self.casttimeReduction = params.casttime_reduction + end + local ability = self:GetAbility() + if ability then + if self.cooldownReduction == 0 then + self.cooldownReduction = ability:GetSpecialValueFor("cooldown_reduction") + end + if self.casttimeReduction == 0 then + self.casttimeReduction = ability:GetSpecialValueFor("casttime_reduction") + end + end +end +function modifier_item_coffee_buff.prototype.GetModifierPercentageCooldown(self) + return self.cooldownReduction +end +function modifier_item_coffee_buff.prototype.GetModifierPercentageCasttime(self) + return self.casttimeReduction +end +modifier_item_coffee_buff = __TS__Decorate( + modifier_item_coffee_buff, + modifier_item_coffee_buff, + {registerModifier(nil)}, + {kind = "class", name = "modifier_item_coffee_buff"} +) +____exports.modifier_item_coffee_buff = modifier_item_coffee_buff +return ____exports diff --git a/scripts/vscripts/items/util_items/item_easter_egg.lua b/scripts/vscripts/items/util_items/item_easter_egg.lua new file mode 100644 index 0000000..370f2a5 --- /dev/null +++ b/scripts/vscripts/items/util_items/item_easter_egg.lua @@ -0,0 +1,216 @@ +local ____lualib = require("lualib_bundle") +local __TS__Class = ____lualib.__TS__Class +local __TS__ClassExtends = ____lualib.__TS__ClassExtends +local __TS__Decorate = ____lualib.__TS__Decorate +local ____exports = {} +local ____dota_ts_adapter = require("lib.dota_ts_adapter") +local BaseItem = ____dota_ts_adapter.BaseItem +local BaseModifier = ____dota_ts_adapter.BaseModifier +local registerAbility = ____dota_ts_adapter.registerAbility +local registerModifier = ____dota_ts_adapter.registerModifier +____exports.item_easter_egg = __TS__Class() +local item_easter_egg = ____exports.item_easter_egg +item_easter_egg.name = "item_easter_egg" +item_easter_egg.____file_path = "scripts/vscripts/items/util_items/item_easter_egg.lua" +__TS__ClassExtends(item_easter_egg, BaseItem) +function item_easter_egg.prototype.OnSpellStart(self) + if not IsServer() then + return + end + local caster = self:GetCaster() + caster:AddNewModifier(caster, self, "modifier_orbiting_wisp", {duration = 60}) + self:RemoveSelf() +end +item_easter_egg = __TS__Decorate( + item_easter_egg, + item_easter_egg, + {registerAbility(nil)}, + {kind = "class", name = "item_easter_egg"} +) +____exports.item_easter_egg = item_easter_egg +____exports.modifier_orbiting_wisp = __TS__Class() +local modifier_orbiting_wisp = ____exports.modifier_orbiting_wisp +modifier_orbiting_wisp.name = "modifier_orbiting_wisp" +modifier_orbiting_wisp.____file_path = "scripts/vscripts/items/util_items/item_easter_egg.lua" +__TS__ClassExtends(modifier_orbiting_wisp, BaseModifier) +function modifier_orbiting_wisp.prototype.____constructor(self, ...) + BaseModifier.prototype.____constructor(self, ...) + self.interval = 0.03 + self.rotation = 0 + self.rotateDelta = 0 + self.rotateRadius = 200 + self.revolution = 2 + self.baseFacing = Vector(0, 1, 0) + self.relativePos = Vector(0, 0, 0) + self.position = Vector(0, 0, 0) + self.facing = Vector(0, 0, 0) + self.zero = Vector(0, 0, 0) +end +function modifier_orbiting_wisp.prototype.GetTexture(self) + return "util_items/eggs" +end +function modifier_orbiting_wisp.prototype.OnCreated(self) + if not IsServer() then + return + end + local parent = self:GetParent() + self.rotateDelta = 360 / self.revolution * self.interval + self.relativePos = Vector(-self.rotateRadius, 0, 100) + local origin = parent:GetAbsOrigin() + self.position = Vector(origin.x + self.relativePos.x, origin.y + self.relativePos.y, origin.z + self.relativePos.z) + self.rotation = 0 + self.facing = self.baseFacing + self.wisp = CreateUnitByName( + "npc_dota_golden_fish", + self.position, + true, + parent, + parent:GetOwner(), + parent:GetTeamNumber() + ) + if self.wisp ~= nil then + self.wisp:SetForwardVector(self.facing) + self.wisp:AddNewModifier( + self:GetCaster(), + self:GetAbility(), + "modifier_wisp_orbiting", + {} + ) + self.wisp:AddNewModifier( + self:GetCaster(), + self:GetAbility(), + "modifier_wisp_attack", + {duration = 60} + ) + end + self:StartIntervalThink(self.interval) +end +function modifier_orbiting_wisp.prototype.OnIntervalThink(self) + if not IsServer() or not self.wisp then + return + end + local parent = self:GetParent() + self.rotation = self.rotation + self.rotateDelta + local origin = parent:GetAbsOrigin() + local targetPos = Vector(origin.x + self.relativePos.x, origin.y + self.relativePos.y, origin.z + self.relativePos.z) + local rotatedPos = rotatePositionYaw(nil, origin, -self.rotation, targetPos) + self.position = rotatedPos + self.facing = rotatePositionYaw(nil, self.zero, -self.rotation, self.baseFacing) + self.wisp:SetAbsOrigin(self.position) + self.wisp:SetForwardVector(self.facing) +end +function modifier_orbiting_wisp.prototype.OnDestroy(self) + if IsClient() then + return + end + if self.wisp then + UTIL_Remove(self.wisp) + end +end +modifier_orbiting_wisp = __TS__Decorate( + modifier_orbiting_wisp, + modifier_orbiting_wisp, + {registerModifier(nil)}, + {kind = "class", name = "modifier_orbiting_wisp"} +) +____exports.modifier_orbiting_wisp = modifier_orbiting_wisp +____exports.modifier_wisp_orbiting = __TS__Class() +local modifier_wisp_orbiting = ____exports.modifier_wisp_orbiting +modifier_wisp_orbiting.name = "modifier_wisp_orbiting" +modifier_wisp_orbiting.____file_path = "scripts/vscripts/items/util_items/item_easter_egg.lua" +__TS__ClassExtends(modifier_wisp_orbiting, BaseModifier) +function modifier_wisp_orbiting.prototype.IsHidden(self) + return true +end +function modifier_wisp_orbiting.prototype.GetEffectName(self) + return "particles/econ/items/lifestealer/lifestealer_immortal_backbone_gold/lifestealer_immortal_backbone_gold_rage.vpcf" +end +function modifier_wisp_orbiting.prototype.GetEffectAttachType(self) + return PATTACH_OVERHEAD_FOLLOW +end +function modifier_wisp_orbiting.prototype.CheckState(self) + return { + [MODIFIER_STATE_INVULNERABLE] = true, + [MODIFIER_STATE_UNSELECTABLE] = true, + [MODIFIER_STATE_UNTARGETABLE] = true, + [MODIFIER_STATE_OUT_OF_GAME] = true, + [MODIFIER_STATE_NO_HEALTH_BAR] = true + } +end +modifier_wisp_orbiting = __TS__Decorate( + modifier_wisp_orbiting, + modifier_wisp_orbiting, + {registerModifier(nil)}, + {kind = "class", name = "modifier_wisp_orbiting"} +) +____exports.modifier_wisp_orbiting = modifier_wisp_orbiting +____exports.modifier_wisp_attack = __TS__Class() +local modifier_wisp_attack = ____exports.modifier_wisp_attack +modifier_wisp_attack.name = "modifier_wisp_attack" +modifier_wisp_attack.____file_path = "scripts/vscripts/items/util_items/item_easter_egg.lua" +__TS__ClassExtends(modifier_wisp_attack, BaseModifier) +function modifier_wisp_attack.prototype.____constructor(self, ...) + BaseModifier.prototype.____constructor(self, ...) + self.interval = 0.33 +end +function modifier_wisp_attack.prototype.IsHidden(self) + return false +end +function modifier_wisp_attack.prototype.IsPurgable(self) + return false +end +function modifier_wisp_attack.prototype.OnCreated(self) + if not IsServer() then + return + end + self:StartIntervalThink(self.interval) +end +function modifier_wisp_attack.prototype.OnIntervalThink(self) + if not IsServer() then + return + end + local parent = self:GetParent() + local caster = self:GetCaster() + if not caster then + return + end + if RandomFloat(0, 100) <= 50 then + self:CreateGoldBag(parent:GetAbsOrigin()) + end +end +function modifier_wisp_attack.prototype.CreateGoldBag(self, position) + local goldBag = CreateItem("item_bag_of_gold", nil, nil) + if goldBag then + local physicalItem = CreateItemOnPositionForLaunch(position, goldBag) + if physicalItem ~= nil then + local randomAngle = RandomFloat(0, 2 * math.pi) + local randomDistance = RandomFloat(50, 200) + local randomX = position.x + randomDistance * math.cos(randomAngle) + local randomY = position.y + randomDistance * math.sin(randomAngle) + local randomPos = Vector(randomX, randomY, position.z) + goldBag:LaunchLootInitialHeight( + false, + 0, + 100, + 0.5, + randomPos + ) + Timers:CreateTimer( + 15, + function() + if IsValidEntity(physicalItem) then + UTIL_Remove(physicalItem) + end + end + ) + end + end +end +modifier_wisp_attack = __TS__Decorate( + modifier_wisp_attack, + modifier_wisp_attack, + {registerModifier(nil)}, + {kind = "class", name = "modifier_wisp_attack"} +) +____exports.modifier_wisp_attack = modifier_wisp_attack +return ____exports diff --git a/scripts/vscripts/items/util_items/item_energy_drink.lua b/scripts/vscripts/items/util_items/item_energy_drink.lua new file mode 100644 index 0000000..09ce436 --- /dev/null +++ b/scripts/vscripts/items/util_items/item_energy_drink.lua @@ -0,0 +1,194 @@ +local ____lualib = require("lualib_bundle") +local __TS__Class = ____lualib.__TS__Class +local __TS__ClassExtends = ____lualib.__TS__ClassExtends +local __TS__Decorate = ____lualib.__TS__Decorate +local ____exports = {} +local ____modifier_general_hunger = require("abilities.modifiers.modifier_general_hunger") +local modifier_buff_food_slot = ____modifier_general_hunger.modifier_buff_food_slot +local modifier_general_hunger = ____modifier_general_hunger.modifier_general_hunger +local BUFF_FOOD_MAX_SLOTS = ____modifier_general_hunger.BUFF_FOOD_MAX_SLOTS +local getBuffFoodSlotCount = ____modifier_general_hunger.getBuffFoodSlotCount +local ____dota_ts_adapter = require("lib.dota_ts_adapter") +local BaseItem = ____dota_ts_adapter.BaseItem +local BaseModifier = ____dota_ts_adapter.BaseModifier +local registerAbility = ____dota_ts_adapter.registerAbility +local registerModifier = ____dota_ts_adapter.registerModifier +local ____heal_tracker = require("utils.heal_tracker") +local HealWithBattlePass = ____heal_tracker.HealWithBattlePass +--- Энергетический напиток — еда: лечение, мана, голод + бафф скорости и регенерации на время +____exports.item_energy_drink = __TS__Class() +local item_energy_drink = ____exports.item_energy_drink +item_energy_drink.name = "item_energy_drink" +item_energy_drink.____file_path = "scripts/vscripts/items/util_items/item_energy_drink.lua" +__TS__ClassExtends(item_energy_drink, BaseItem) +function item_energy_drink.prototype.CastFilterResultTarget(self, target) + if IsServer() then + if not target:IsRealHero() then + return UF_FAIL_CUSTOM + end + if getBuffFoodSlotCount(nil, target) >= BUFF_FOOD_MAX_SLOTS then + return UF_FAIL_CUSTOM + end + if self:GetCurrentCharges() < self:GetInitialCharges() then + return UF_FAIL_CUSTOM + end + return UF_SUCCESS + end + return UF_SUCCESS +end +function item_energy_drink.prototype.GetCustomCastErrorTarget(self, target) + if IsServer() then + if not target:IsRealHero() then + return "#dota_hud_error_cheese_bad_target" + end + if getBuffFoodSlotCount(nil, target) >= BUFF_FOOD_MAX_SLOTS then + return "#dota_hud_error_ability_not_ready" + end + if self:GetCurrentCharges() < self:GetInitialCharges() then + return "#dota_hud_error_havent_charges" + end + end + return "" +end +function item_energy_drink.prototype.OnSpellStart(self) + local target = self:GetCursorTarget() + local item = self + if not target then + return + end + local healAmount = self:GetSpecialValueFor("heal") + local manaAmount = self:GetSpecialValueFor("mana") + target:EmitSound("DOTA_Item.Cheese.Activate") + HealWithBattlePass( + nil, + target, + healAmount, + self, + self:GetCaster(), + true + ) + if healAmount > 0 then + SendOverheadEventMessage( + nil, + OVERHEAD_ALERT_HEAL, + target, + healAmount, + target:GetPlayerOwner() + ) + end + if manaAmount > 0 then + target:GiveMana(manaAmount) + SendOverheadEventMessage( + nil, + OVERHEAD_ALERT_MANA_ADD, + target, + manaAmount, + target:GetPlayerOwner() + ) + end + if target:IsRealHero() then + local buffDuration = self:GetSpecialValueFor("buff_duration") + local buffMoveSpeedPct = self:GetSpecialValueFor("buff_move_speed_pct") + local buffAttackSpeed = self:GetSpecialValueFor("buff_attack_speed") + local modifier = target:AddNewModifier( + self:GetCaster(), + self, + modifier_general_hunger.name, + {} + ) + target:AddNewModifier( + self:GetCaster(), + self, + modifier_buff_food_slot.name, + {duration = buffDuration} + ) + target:AddNewModifier( + self:GetCaster(), + self, + ____exports.modifier_energy_drink_buff.name, + {duration = buffDuration, buff_move_speed_pct = buffMoveSpeedPct, buff_attack_speed = buffAttackSpeed} + ) + if modifier then + local hunger_bonus = self:GetSpecialValueFor("hunger_bonus") + do + local i = 0 + while i < hunger_bonus do + modifier:IncrementStackCount() + i = i + 1 + end + end + end + if item:GetCurrentCharges() <= item:GetInitialCharges() then + UTIL_Remove(item) + return + end + item:SetCurrentCharges(item:GetCurrentCharges() - item:GetInitialCharges()) + end +end +item_energy_drink = __TS__Decorate( + item_energy_drink, + item_energy_drink, + {registerAbility(nil)}, + {kind = "class", name = "item_energy_drink"} +) +____exports.item_energy_drink = item_energy_drink +--- Бафф от энергетического напитка: скорость передвижения и атаки +____exports.modifier_energy_drink_buff = __TS__Class() +local modifier_energy_drink_buff = ____exports.modifier_energy_drink_buff +modifier_energy_drink_buff.name = "modifier_energy_drink_buff" +modifier_energy_drink_buff.____file_path = "scripts/vscripts/items/util_items/item_energy_drink.lua" +__TS__ClassExtends(modifier_energy_drink_buff, BaseModifier) +function modifier_energy_drink_buff.prototype.____constructor(self, ...) + BaseModifier.prototype.____constructor(self, ...) + self.buffMoveSpeedPct = 0 + self.buffAttackSpeed = 0 +end +function modifier_energy_drink_buff.prototype.IsHidden(self) + return false +end +function modifier_energy_drink_buff.prototype.IsPurgable(self) + return true +end +function modifier_energy_drink_buff.prototype.IsDebuff(self) + return false +end +function modifier_energy_drink_buff.prototype.GetAttributes(self) + return MODIFIER_ATTRIBUTE_MULTIPLE +end +function modifier_energy_drink_buff.prototype.OnCreated(self, params) + if params and params.buff_move_speed_pct ~= nil then + self.buffMoveSpeedPct = params.buff_move_speed_pct + end + if params and params.buff_attack_speed ~= nil then + self.buffAttackSpeed = params.buff_attack_speed + end + local ability = self:GetAbility() + if ability then + if self.buffMoveSpeedPct == 0 then + self.buffMoveSpeedPct = ability:GetSpecialValueFor("buff_move_speed_pct") + end + if self.buffAttackSpeed == 0 then + self.buffAttackSpeed = ability:GetSpecialValueFor("buff_attack_speed") + end + end +end +function modifier_energy_drink_buff.prototype.DeclareFunctions(self) + return {MODIFIER_PROPERTY_MOVESPEED_BONUS_PERCENTAGE, MODIFIER_PROPERTY_ATTACKSPEED_PERCENTAGE} +end +function modifier_energy_drink_buff.prototype.GetModifierMoveSpeedBonus_Percentage(self) + return self.buffMoveSpeedPct +end +function modifier_energy_drink_buff.prototype.GetModifierAttackSpeedPercentage(self) + return self.buffAttackSpeed +end +function modifier_energy_drink_buff.prototype.GetTexture(self) + return "../items/utils/energetic" +end +modifier_energy_drink_buff = __TS__Decorate( + modifier_energy_drink_buff, + modifier_energy_drink_buff, + {registerModifier(nil)}, + {kind = "class", name = "modifier_energy_drink_buff"} +) +____exports.modifier_energy_drink_buff = modifier_energy_drink_buff +return ____exports diff --git a/scripts/vscripts/items/util_items/item_firecore.lua b/scripts/vscripts/items/util_items/item_firecore.lua new file mode 100644 index 0000000..6e46a87 --- /dev/null +++ b/scripts/vscripts/items/util_items/item_firecore.lua @@ -0,0 +1,61 @@ +local ____lualib = require("lualib_bundle") +local __TS__Class = ____lualib.__TS__Class +local __TS__ClassExtends = ____lualib.__TS__ClassExtends +local __TS__Decorate = ____lualib.__TS__Decorate +local ____exports = {} +local ____dota_ts_adapter = require("lib.dota_ts_adapter") +local BaseItem = ____dota_ts_adapter.BaseItem +local ____dota_ts_adapter = require("lib.dota_ts_adapter") +local registerAbility = ____dota_ts_adapter.registerAbility +____exports.item_firecore = __TS__Class() +local item_firecore = ____exports.item_firecore +item_firecore.name = "item_firecore" +item_firecore.____file_path = "scripts/vscripts/items/util_items/item_firecore.lua" +__TS__ClassExtends(item_firecore, BaseItem) +function item_firecore.prototype.CastFilterResultTarget(self, target) + if IsServer() then + if target:GetUnitName() ~= "npc_mound" then + return UF_FAIL_CUSTOM + end + return UF_SUCCESS + end + return UF_SUCCESS +end +function item_firecore.prototype.GetCustomCastErrorTarget(self, target) + if IsServer() then + if target:GetUnitName() ~= "npc_mound" then + return "#dota_hud_error_cheese_bad_target" + end + end + return "" +end +function item_firecore.prototype.OnSpellStart(self) + local target = self:GetCursorTarget() + local item = self + GameRules:SendCustomMessage("#wait_some_time_before_tree_growth", 0, 0) + UTIL_Remove(item) + Timers:CreateTimer( + 60, + function() + local mound = Entities:FindByName(nil, "npc_mound") + local moundpoint = mound:GetAbsOrigin() + local tree = CreateUnitByName( + "npc_sakura_tree", + moundpoint, + true, + nil, + nil, + DOTA_TEAM_NEUTRALS + ) + UTIL_Remove(mound) + end + ) +end +item_firecore = __TS__Decorate( + item_firecore, + item_firecore, + {registerAbility(nil)}, + {kind = "class", name = "item_firecore"} +) +____exports.item_firecore = item_firecore +return ____exports diff --git a/scripts/vscripts/items/util_items/item_fish.lua b/scripts/vscripts/items/util_items/item_fish.lua new file mode 100644 index 0000000..b2ea670 --- /dev/null +++ b/scripts/vscripts/items/util_items/item_fish.lua @@ -0,0 +1,86 @@ +local ____lualib = require("lualib_bundle") +local __TS__Class = ____lualib.__TS__Class +local __TS__ClassExtends = ____lualib.__TS__ClassExtends +local __TS__Decorate = ____lualib.__TS__Decorate +local ____exports = {} +local ____modifier_general_hunger = require("abilities.modifiers.modifier_general_hunger") +local modifier_general_hunger = ____modifier_general_hunger.modifier_general_hunger +local ____dota_ts_adapter = require("lib.dota_ts_adapter") +local BaseItem = ____dota_ts_adapter.BaseItem +local ____dota_ts_adapter = require("lib.dota_ts_adapter") +local registerAbility = ____dota_ts_adapter.registerAbility +____exports.item_fish = __TS__Class() +local item_fish = ____exports.item_fish +item_fish.name = "item_fish" +item_fish.____file_path = "scripts/vscripts/items/util_items/item_fish.lua" +__TS__ClassExtends(item_fish, BaseItem) +function item_fish.prototype.CastFilterResultTarget(self, target) + if IsServer() then + if not target:IsRealHero() then + return UF_FAIL_CUSTOM + end + if self:GetCurrentCharges() < self:GetInitialCharges() then + return UF_FAIL_CUSTOM + end + return UF_SUCCESS + end + return UF_SUCCESS +end +function item_fish.prototype.GetCustomCastErrorTarget(self, target) + if IsServer() then + if not target:IsRealHero() then + return "#dota_hud_error_cheese_bad_target" + end + if self:GetCurrentCharges() < self:GetInitialCharges() then + return "#dota_hud_error_havent_charges" + end + end + return "" +end +function item_fish.prototype.OnSpellStart(self) + local target = self:GetCursorTarget() + local item = self + local healAmount = self:GetSpecialValueFor("heal") + if target ~= nil then + target:EmitSound("DOTA_Item.Cheese.Activate") + target:Heal(healAmount, self) + SendOverheadEventMessage( + nil, + OVERHEAD_ALERT_HEAL, + target, + healAmount, + target:GetPlayerOwner() + ) + end + if target and target:IsRealHero() then + local modifier = target:AddNewModifier( + self:GetCaster(), + self, + modifier_general_hunger.name, + {} + ) + if modifier then + local hunger_bonus = self:GetSpecialValueFor("hunger_bonus") + do + local i = 0 + while i < hunger_bonus do + modifier:IncrementStackCount() + i = i + 1 + end + end + if item:GetCurrentCharges() <= item:GetInitialCharges() then + UTIL_Remove(item) + return + end + item:SetCurrentCharges(item:GetCurrentCharges() - item:GetInitialCharges()) + end + end +end +item_fish = __TS__Decorate( + item_fish, + item_fish, + {registerAbility(nil)}, + {kind = "class", name = "item_fish"} +) +____exports.item_fish = item_fish +return ____exports diff --git a/scripts/vscripts/items/util_items/item_grilled_meat.lua b/scripts/vscripts/items/util_items/item_grilled_meat.lua new file mode 100644 index 0000000..9ec1445 --- /dev/null +++ b/scripts/vscripts/items/util_items/item_grilled_meat.lua @@ -0,0 +1,86 @@ +local ____lualib = require("lualib_bundle") +local __TS__Class = ____lualib.__TS__Class +local __TS__ClassExtends = ____lualib.__TS__ClassExtends +local __TS__Decorate = ____lualib.__TS__Decorate +local ____exports = {} +local ____modifier_general_hunger = require("abilities.modifiers.modifier_general_hunger") +local modifier_general_hunger = ____modifier_general_hunger.modifier_general_hunger +local ____dota_ts_adapter = require("lib.dota_ts_adapter") +local BaseItem = ____dota_ts_adapter.BaseItem +local ____dota_ts_adapter = require("lib.dota_ts_adapter") +local registerAbility = ____dota_ts_adapter.registerAbility +____exports.item_grilled_meat = __TS__Class() +local item_grilled_meat = ____exports.item_grilled_meat +item_grilled_meat.name = "item_grilled_meat" +item_grilled_meat.____file_path = "scripts/vscripts/items/util_items/item_grilled_meat.lua" +__TS__ClassExtends(item_grilled_meat, BaseItem) +function item_grilled_meat.prototype.CastFilterResultTarget(self, target) + if IsServer() then + if not target:IsRealHero() then + return UF_FAIL_CUSTOM + end + if self:GetCurrentCharges() < self:GetInitialCharges() then + return UF_FAIL_CUSTOM + end + return UF_SUCCESS + end + return UF_SUCCESS +end +function item_grilled_meat.prototype.GetCustomCastErrorTarget(self, target) + if IsServer() then + if not target:IsRealHero() then + return "#dota_hud_error_cheese_bad_target" + end + if self:GetCurrentCharges() < self:GetInitialCharges() then + return "#dota_hud_error_havent_charges" + end + end + return "" +end +function item_grilled_meat.prototype.OnSpellStart(self) + local target = self:GetCursorTarget() + local item = self + local healAmount = self:GetSpecialValueFor("heal") + if target ~= nil then + target:EmitSound("DOTA_Item.Cheese.Activate") + target:Heal(healAmount, self) + SendOverheadEventMessage( + nil, + OVERHEAD_ALERT_HEAL, + target, + healAmount, + target:GetPlayerOwner() + ) + end + if target and target:IsRealHero() then + local modifier = target:AddNewModifier( + self:GetCaster(), + self, + modifier_general_hunger.name, + {} + ) + if modifier then + local hunger_bonus = self:GetSpecialValueFor("hunger_bonus") + do + local i = 0 + while i < hunger_bonus do + modifier:IncrementStackCount() + i = i + 1 + end + end + if item:GetCurrentCharges() <= item:GetInitialCharges() then + UTIL_Remove(item) + return + end + item:SetCurrentCharges(item:GetCurrentCharges() - item:GetInitialCharges()) + end + end +end +item_grilled_meat = __TS__Decorate( + item_grilled_meat, + item_grilled_meat, + {registerAbility(nil)}, + {kind = "class", name = "item_grilled_meat"} +) +____exports.item_grilled_meat = item_grilled_meat +return ____exports diff --git a/scripts/vscripts/items/util_items/item_meat.lua b/scripts/vscripts/items/util_items/item_meat.lua new file mode 100644 index 0000000..7a2b712 --- /dev/null +++ b/scripts/vscripts/items/util_items/item_meat.lua @@ -0,0 +1,86 @@ +local ____lualib = require("lualib_bundle") +local __TS__Class = ____lualib.__TS__Class +local __TS__ClassExtends = ____lualib.__TS__ClassExtends +local __TS__Decorate = ____lualib.__TS__Decorate +local ____exports = {} +local ____modifier_general_hunger = require("abilities.modifiers.modifier_general_hunger") +local modifier_general_hunger = ____modifier_general_hunger.modifier_general_hunger +local ____dota_ts_adapter = require("lib.dota_ts_adapter") +local BaseItem = ____dota_ts_adapter.BaseItem +local ____dota_ts_adapter = require("lib.dota_ts_adapter") +local registerAbility = ____dota_ts_adapter.registerAbility +____exports.item_meat = __TS__Class() +local item_meat = ____exports.item_meat +item_meat.name = "item_meat" +item_meat.____file_path = "scripts/vscripts/items/util_items/item_meat.lua" +__TS__ClassExtends(item_meat, BaseItem) +function item_meat.prototype.CastFilterResultTarget(self, target) + if IsServer() then + if not target:IsRealHero() then + return UF_FAIL_CUSTOM + end + if self:GetCurrentCharges() < self:GetInitialCharges() then + return UF_FAIL_CUSTOM + end + return UF_SUCCESS + end + return UF_SUCCESS +end +function item_meat.prototype.GetCustomCastErrorTarget(self, target) + if IsServer() then + if not target:IsRealHero() then + return "#dota_hud_error_cheese_bad_target" + end + if self:GetCurrentCharges() < self:GetInitialCharges() then + return "#dota_hud_error_havent_charges" + end + end + return "" +end +function item_meat.prototype.OnSpellStart(self) + local target = self:GetCursorTarget() + local item = self + local healAmount = self:GetSpecialValueFor("heal") + if target ~= nil then + target:EmitSound("DOTA_Item.Cheese.Activate") + target:Heal(healAmount, self) + SendOverheadEventMessage( + nil, + OVERHEAD_ALERT_HEAL, + target, + healAmount, + target:GetPlayerOwner() + ) + end + if target and target:IsRealHero() then + local modifier = target:AddNewModifier( + self:GetCaster(), + self, + modifier_general_hunger.name, + {} + ) + if modifier then + local hunger_bonus = self:GetSpecialValueFor("hunger_bonus") + do + local i = 0 + while i < hunger_bonus do + modifier:IncrementStackCount() + i = i + 1 + end + end + if item:GetCurrentCharges() <= item:GetInitialCharges() then + UTIL_Remove(item) + return + end + item:SetCurrentCharges(item:GetCurrentCharges() - item:GetInitialCharges()) + end + end +end +item_meat = __TS__Decorate( + item_meat, + item_meat, + {registerAbility(nil)}, + {kind = "class", name = "item_meat"} +) +____exports.item_meat = item_meat +return ____exports diff --git a/scripts/vscripts/items/util_items/item_milk.lua b/scripts/vscripts/items/util_items/item_milk.lua new file mode 100644 index 0000000..ef469d4 --- /dev/null +++ b/scripts/vscripts/items/util_items/item_milk.lua @@ -0,0 +1,87 @@ +local ____lualib = require("lualib_bundle") +local __TS__Class = ____lualib.__TS__Class +local __TS__ClassExtends = ____lualib.__TS__ClassExtends +local __TS__Decorate = ____lualib.__TS__Decorate +local ____exports = {} +local ____modifier_general_hunger = require("abilities.modifiers.modifier_general_hunger") +local modifier_general_hunger = ____modifier_general_hunger.modifier_general_hunger +local ____dota_ts_adapter = require("lib.dota_ts_adapter") +local BaseItem = ____dota_ts_adapter.BaseItem +local ____dota_ts_adapter = require("lib.dota_ts_adapter") +local registerAbility = ____dota_ts_adapter.registerAbility +____exports.item_milk = __TS__Class() +local item_milk = ____exports.item_milk +item_milk.name = "item_milk" +item_milk.____file_path = "scripts/vscripts/items/util_items/item_milk.lua" +__TS__ClassExtends(item_milk, BaseItem) +function item_milk.prototype.CastFilterResultTarget(self, target) + if IsServer() then + if not target:IsRealHero() then + return UF_FAIL_CUSTOM + end + if self:GetCurrentCharges() < self:GetInitialCharges() then + return UF_FAIL_CUSTOM + end + return UF_SUCCESS + end + return UF_SUCCESS +end +function item_milk.prototype.GetCustomCastErrorTarget(self, target) + if IsServer() then + if not target:IsRealHero() then + return "#dota_hud_error_cheese_bad_target" + end + if self:GetCurrentCharges() < self:GetInitialCharges() then + return "#dota_hud_error_havent_charges" + end + end + return "" +end +function item_milk.prototype.OnSpellStart(self) + local target = self:GetCursorTarget() + local item = self + local healAmount = self:GetSpecialValueFor("mana") + if target == nil then + return + end + target:EmitSound("DOTA_Item.Cheese.Activate") + target:GiveMana(healAmount) + SendOverheadEventMessage( + nil, + OVERHEAD_ALERT_MANA_ADD, + target, + healAmount, + target:GetPlayerOwner() + ) + if target and target:IsRealHero() then + local modifier = target:AddNewModifier( + self:GetCaster(), + self, + modifier_general_hunger.name, + {} + ) + if modifier then + local hunger_bonus = self:GetSpecialValueFor("hunger_bonus") + do + local i = 0 + while i < hunger_bonus do + modifier:IncrementStackCount() + i = i + 1 + end + end + if item:GetCurrentCharges() <= item:GetInitialCharges() then + UTIL_Remove(item) + return + end + item:SetCurrentCharges(item:GetCurrentCharges() - item:GetInitialCharges()) + end + end +end +item_milk = __TS__Decorate( + item_milk, + item_milk, + {registerAbility(nil)}, + {kind = "class", name = "item_milk"} +) +____exports.item_milk = item_milk +return ____exports diff --git a/scripts/vscripts/items/util_items/item_pizza.lua b/scripts/vscripts/items/util_items/item_pizza.lua new file mode 100644 index 0000000..2404675 --- /dev/null +++ b/scripts/vscripts/items/util_items/item_pizza.lua @@ -0,0 +1,201 @@ +local ____lualib = require("lualib_bundle") +local __TS__Class = ____lualib.__TS__Class +local __TS__ClassExtends = ____lualib.__TS__ClassExtends +local __TS__ArrayForEach = ____lualib.__TS__ArrayForEach +local __TS__Decorate = ____lualib.__TS__Decorate +local ____exports = {} +local ____modifier_general_hunger = require("abilities.modifiers.modifier_general_hunger") +local modifier_general_hunger = ____modifier_general_hunger.modifier_general_hunger +local ____dota_ts_adapter = require("lib.dota_ts_adapter") +local BaseItem = ____dota_ts_adapter.BaseItem +local BaseModifier = ____dota_ts_adapter.BaseModifier +local registerModifier = ____dota_ts_adapter.registerModifier +local ____dota_ts_adapter = require("lib.dota_ts_adapter") +local registerAbility = ____dota_ts_adapter.registerAbility +____exports.item_pizza = __TS__Class() +local item_pizza = ____exports.item_pizza +item_pizza.name = "item_pizza" +item_pizza.____file_path = "scripts/vscripts/items/util_items/item_pizza.lua" +__TS__ClassExtends(item_pizza, BaseItem) +function item_pizza.prototype.CastFilterResultTarget(self, target) + if IsServer() then + if self:GetCurrentCharges() < self:GetInitialCharges() then + return UF_FAIL_CUSTOM + end + return UF_SUCCESS + end + return UF_SUCCESS +end +function item_pizza.prototype.GetCustomCastErrorTarget(self, target) + if IsServer() then + if self:GetCurrentCharges() < self:GetInitialCharges() then + return "#dota_hud_error_havent_charges" + end + end + return "" +end +function item_pizza.prototype.OnSpellStart(self) + local target = self:GetCursorTarget() + local item = self + local castedOnGurd = (target and target:GetName()) == "npc_quest_giver_friend" + if target and target:IsRealHero() and target:GetName() ~= "npc_quest_giver_friend" then + local modifier = target:AddNewModifier( + self:GetCaster(), + self, + modifier_general_hunger.name, + {} + ) + local modifier_pizza = target:AddNewModifier( + self:GetCaster(), + self, + ____exports.modifier_item_pizza.name, + {} + ) + if not ____exports.item_pizza.wasGivenToGurd then + Timers:CreateTimer( + 50, + function() + EmitGlobalSound("Hero_Bloodseeker.Thirst.Cast") + local soundCount = 0 + local maxRepeats = 20 + local repeatSound + repeatSound = function() + if soundCount < maxRepeats then + EmitGlobalSound("Hero_Pudge.Dismember") + soundCount = soundCount + 1 + Timers:CreateTimer(0.5, repeatSound) + end + end + repeatSound(nil) + local questGiver = Entities:FindByName(nil, "npc_quest_giver_friend") + local questGiverVictim1 = Entities:FindByName(nil, "npc_quest_giver_maiden") + local questGiverVictim2 = Entities:FindByName(nil, "npc_quest_giver_lina") + if not questGiver then + return + end + __TS__ArrayForEach( + FindUnitsInRadius( + 2, + questGiver:GetAbsOrigin(), + nil, + 2000, + 1, + 1, + 8192, + 1, + false + ), + function(____, unit) + unit:ForceKill(false) + end + ) + UTIL_Remove(questGiver) + UTIL_Remove(questGiverVictim1) + UTIL_Remove(questGiverVictim2) + end + ) + end + if modifier_pizza then + modifier_pizza:SetStackCount(modifier_pizza:GetStackCount() + self:GetSpecialValueFor("stack_count")) + end + if modifier then + local hunger_bonus = self:GetSpecialValueFor("hunger_bonus") + do + local i = 0 + while i < hunger_bonus do + modifier:IncrementStackCount() + i = i + 1 + end + end + if item:GetCurrentCharges() <= item:GetInitialCharges() then + UTIL_Remove(item) + return + end + item:SetCurrentCharges(item:GetCurrentCharges() - item:GetInitialCharges()) + end + else + if castedOnGurd then + ____exports.item_pizza.wasGivenToGurd = true + end + local heroes = HeroList:GetAllHeroes() + GameRules:SendCustomMessage("#friend_quest_pizza_prep_message_complete_2", 0, 0) + for ____, hero in ipairs(heroes) do + if hero:IsRealHero() and hero:GetName() ~= "npc_quest_giver_friend" then + local modifier = hero:AddNewModifier( + self:GetCaster(), + self, + modifier_general_hunger.name, + {} + ) + local modifier_pizza = hero:AddNewModifier( + self:GetCaster(), + self, + ____exports.modifier_item_pizza.name, + {} + ) + if modifier_pizza then + modifier_pizza:SetStackCount(modifier_pizza:GetStackCount() + self:GetSpecialValueFor("stack_count")) + end + if modifier then + local hunger_bonus = self:GetSpecialValueFor("hunger_bonus") + do + local i = 0 + while i < hunger_bonus do + modifier:IncrementStackCount() + i = i + 1 + end + end + end + end + end + UTIL_Remove(item) + end +end +item_pizza.wasGivenToGurd = false +item_pizza = __TS__Decorate( + item_pizza, + item_pizza, + {registerAbility(nil)}, + {kind = "class", name = "item_pizza"} +) +____exports.item_pizza = item_pizza +____exports.modifier_item_pizza = __TS__Class() +local modifier_item_pizza = ____exports.modifier_item_pizza +modifier_item_pizza.name = "modifier_item_pizza" +modifier_item_pizza.____file_path = "scripts/vscripts/items/util_items/item_pizza.lua" +__TS__ClassExtends(modifier_item_pizza, BaseModifier) +function modifier_item_pizza.prototype.IsHidden(self) + return false +end +function modifier_item_pizza.prototype.IsPurgable(self) + return false +end +function modifier_item_pizza.prototype.IsDebuff(self) + return false +end +function modifier_item_pizza.prototype.RemoveOnDeath(self) + return false +end +function modifier_item_pizza.prototype.DeclareFunctions(self) + return {MODIFIER_PROPERTY_STATS_AGILITY_BONUS, MODIFIER_PROPERTY_STATS_INTELLECT_BONUS, MODIFIER_PROPERTY_STATS_STRENGTH_BONUS} +end +function modifier_item_pizza.prototype.GetModifierBonusStats_Agility(self) + return self:GetStackCount() +end +function modifier_item_pizza.prototype.GetModifierBonusStats_Intellect(self) + return self:GetStackCount() +end +function modifier_item_pizza.prototype.GetModifierBonusStats_Strength(self) + return self:GetStackCount() +end +function modifier_item_pizza.prototype.GetTexture(self) + return "../items/utils/pizza" +end +modifier_item_pizza = __TS__Decorate( + modifier_item_pizza, + modifier_item_pizza, + {registerModifier(nil)}, + {kind = "class", name = "modifier_item_pizza"} +) +____exports.modifier_item_pizza = modifier_item_pizza +return ____exports diff --git a/scripts/vscripts/items/util_items/item_rofl_for_kaban_pumba.lua b/scripts/vscripts/items/util_items/item_rofl_for_kaban_pumba.lua new file mode 100644 index 0000000..9b84c9a --- /dev/null +++ b/scripts/vscripts/items/util_items/item_rofl_for_kaban_pumba.lua @@ -0,0 +1,37 @@ +local ____lualib = require("lualib_bundle") +local __TS__Class = ____lualib.__TS__Class +local __TS__ClassExtends = ____lualib.__TS__ClassExtends +local __TS__Decorate = ____lualib.__TS__Decorate +local ____exports = {} +local ____dota_ts_adapter = require("lib.dota_ts_adapter") +local BaseItem = ____dota_ts_adapter.BaseItem +local ____dota_ts_adapter = require("lib.dota_ts_adapter") +local registerAbility = ____dota_ts_adapter.registerAbility +____exports.item_rofl_for_kaban_pumba = __TS__Class() +local item_rofl_for_kaban_pumba = ____exports.item_rofl_for_kaban_pumba +item_rofl_for_kaban_pumba.name = "item_rofl_for_kaban_pumba" +item_rofl_for_kaban_pumba.____file_path = "scripts/vscripts/items/util_items/item_rofl_for_kaban_pumba.lua" +__TS__ClassExtends(item_rofl_for_kaban_pumba, BaseItem) +function item_rofl_for_kaban_pumba.prototype.OnSpellStart(self) + if not IsServer() then + return + end + local caster = self:GetCaster() + local point = self:GetCursorPosition() + local unit = CreateUnitByName( + "npc_dota_rofl_kaban_pumba", + point, + true, + caster, + caster, + DOTA_TEAM_BADGUYS + ) +end +item_rofl_for_kaban_pumba = __TS__Decorate( + item_rofl_for_kaban_pumba, + item_rofl_for_kaban_pumba, + {registerAbility(nil)}, + {kind = "class", name = "item_rofl_for_kaban_pumba"} +) +____exports.item_rofl_for_kaban_pumba = item_rofl_for_kaban_pumba +return ____exports diff --git a/scripts/vscripts/items/util_items/item_sandwich.lua b/scripts/vscripts/items/util_items/item_sandwich.lua new file mode 100644 index 0000000..a0a323f --- /dev/null +++ b/scripts/vscripts/items/util_items/item_sandwich.lua @@ -0,0 +1,190 @@ +local ____lualib = require("lualib_bundle") +local __TS__Class = ____lualib.__TS__Class +local __TS__ClassExtends = ____lualib.__TS__ClassExtends +local __TS__Decorate = ____lualib.__TS__Decorate +local ____exports = {} +local ____modifier_general_hunger = require("abilities.modifiers.modifier_general_hunger") +local modifier_buff_food_slot = ____modifier_general_hunger.modifier_buff_food_slot +local modifier_general_hunger = ____modifier_general_hunger.modifier_general_hunger +local BUFF_FOOD_MAX_SLOTS = ____modifier_general_hunger.BUFF_FOOD_MAX_SLOTS +local getBuffFoodSlotCount = ____modifier_general_hunger.getBuffFoodSlotCount +local ____dota_ts_adapter = require("lib.dota_ts_adapter") +local BaseItem = ____dota_ts_adapter.BaseItem +local BaseModifier = ____dota_ts_adapter.BaseModifier +local registerAbility = ____dota_ts_adapter.registerAbility +local registerModifier = ____dota_ts_adapter.registerModifier +--- Сэндвич — бафф-еда: увеличивает размер, броню, урон, но замедляет +____exports.item_sandwich = __TS__Class() +local item_sandwich = ____exports.item_sandwich +item_sandwich.name = "item_sandwich" +item_sandwich.____file_path = "scripts/vscripts/items/util_items/item_sandwich.lua" +__TS__ClassExtends(item_sandwich, BaseItem) +function item_sandwich.prototype.CastFilterResultTarget(self, target) + if IsServer() then + if not target:IsRealHero() then + return UF_FAIL_CUSTOM + end + if getBuffFoodSlotCount(nil, target) >= BUFF_FOOD_MAX_SLOTS then + return UF_FAIL_CUSTOM + end + if self:GetCurrentCharges() < self:GetInitialCharges() then + return UF_FAIL_CUSTOM + end + return UF_SUCCESS + end + return UF_SUCCESS +end +function item_sandwich.prototype.GetCustomCastErrorTarget(self, target) + if IsServer() then + if not target:IsRealHero() then + return "#dota_hud_error_cheese_bad_target" + end + if getBuffFoodSlotCount(nil, target) >= BUFF_FOOD_MAX_SLOTS then + return "#dota_hud_error_ability_not_ready" + end + if self:GetCurrentCharges() < self:GetInitialCharges() then + return "#dota_hud_error_havent_charges" + end + end + return "" +end +function item_sandwich.prototype.OnSpellStart(self) + local target = self:GetCursorTarget() + local item = self + if not target then + return + end + target:EmitSound("DOTA_Item.Cheese.Activate") + if target:IsRealHero() then + local buffDuration = self:GetSpecialValueFor("buff_duration") + local bonusDamage = self:GetSpecialValueFor("bonus_damage") + local bonusArmor = self:GetSpecialValueFor("bonus_armor") + local moveSpeedSlowPct = self:GetSpecialValueFor("move_speed_slow_pct") + local modelScale = self:GetSpecialValueFor("model_scale") + local hungerModifier = target:AddNewModifier( + self:GetCaster(), + self, + modifier_general_hunger.name, + {} + ) + target:AddNewModifier( + self:GetCaster(), + self, + modifier_buff_food_slot.name, + {duration = buffDuration} + ) + target:AddNewModifier( + self:GetCaster(), + self, + ____exports.modifier_item_sandwich_buff.name, + { + duration = buffDuration, + bonus_damage = bonusDamage, + bonus_armor = bonusArmor, + move_speed_slow_pct = moveSpeedSlowPct, + model_scale = modelScale + } + ) + if hungerModifier then + local hunger_bonus = self:GetSpecialValueFor("hunger_bonus") + do + local i = 0 + while i < hunger_bonus do + hungerModifier:IncrementStackCount() + i = i + 1 + end + end + end + if item:GetCurrentCharges() <= item:GetInitialCharges() then + UTIL_Remove(item) + return + end + item:SetCurrentCharges(item:GetCurrentCharges() - item:GetInitialCharges()) + end +end +item_sandwich = __TS__Decorate( + item_sandwich, + item_sandwich, + {registerAbility(nil)}, + {kind = "class", name = "item_sandwich"} +) +____exports.item_sandwich = item_sandwich +____exports.modifier_item_sandwich_buff = __TS__Class() +local modifier_item_sandwich_buff = ____exports.modifier_item_sandwich_buff +modifier_item_sandwich_buff.name = "modifier_item_sandwich_buff" +modifier_item_sandwich_buff.____file_path = "scripts/vscripts/items/util_items/item_sandwich.lua" +__TS__ClassExtends(modifier_item_sandwich_buff, BaseModifier) +function modifier_item_sandwich_buff.prototype.____constructor(self, ...) + BaseModifier.prototype.____constructor(self, ...) + self.bonusDamage = 0 + self.bonusArmor = 0 + self.moveSpeedSlowPct = 0 + self.modelScale = 0 +end +function modifier_item_sandwich_buff.prototype.IsHidden(self) + return false +end +function modifier_item_sandwich_buff.prototype.IsPurgable(self) + return true +end +function modifier_item_sandwich_buff.prototype.IsDebuff(self) + return false +end +function modifier_item_sandwich_buff.prototype.GetAttributes(self) + return MODIFIER_ATTRIBUTE_MULTIPLE +end +function modifier_item_sandwich_buff.prototype.GetTexture(self) + return "../items/utils/sandwich" +end +function modifier_item_sandwich_buff.prototype.DeclareFunctions(self) + return {MODIFIER_PROPERTY_PREATTACK_BONUS_DAMAGE, MODIFIER_PROPERTY_PHYSICAL_ARMOR_BONUS, MODIFIER_PROPERTY_MOVESPEED_BONUS_PERCENTAGE, MODIFIER_PROPERTY_MODEL_SCALE} +end +function modifier_item_sandwich_buff.prototype.OnCreated(self, params) + if (params and params.bonus_damage) ~= nil then + self.bonusDamage = params.bonus_damage + end + if (params and params.bonus_armor) ~= nil then + self.bonusArmor = params.bonus_armor + end + if (params and params.move_speed_slow_pct) ~= nil then + self.moveSpeedSlowPct = params.move_speed_slow_pct + end + if (params and params.model_scale) ~= nil then + self.modelScale = params.model_scale + end + local ability = self:GetAbility() + if ability then + if self.bonusDamage == 0 then + self.bonusDamage = ability:GetSpecialValueFor("bonus_damage") + end + if self.bonusArmor == 0 then + self.bonusArmor = ability:GetSpecialValueFor("bonus_armor") + end + if self.moveSpeedSlowPct == 0 then + self.moveSpeedSlowPct = ability:GetSpecialValueFor("move_speed_slow_pct") + end + if self.modelScale == 0 then + self.modelScale = ability:GetSpecialValueFor("model_scale") + end + end +end +function modifier_item_sandwich_buff.prototype.GetModifierPreAttack_BonusDamage(self) + return self.bonusDamage +end +function modifier_item_sandwich_buff.prototype.GetModifierPhysicalArmorBonus(self) + return self.bonusArmor +end +function modifier_item_sandwich_buff.prototype.GetModifierMoveSpeedBonus_Percentage(self) + return -self.moveSpeedSlowPct +end +function modifier_item_sandwich_buff.prototype.GetModifierModelScale(self) + return self.modelScale +end +modifier_item_sandwich_buff = __TS__Decorate( + modifier_item_sandwich_buff, + modifier_item_sandwich_buff, + {registerModifier(nil)}, + {kind = "class", name = "modifier_item_sandwich_buff"} +) +____exports.modifier_item_sandwich_buff = modifier_item_sandwich_buff +return ____exports diff --git a/scripts/vscripts/leaderboard_server.lua b/scripts/vscripts/leaderboard_server.lua new file mode 100644 index 0000000..eb577e2 --- /dev/null +++ b/scripts/vscripts/leaderboard_server.lua @@ -0,0 +1,119 @@ +local ____lualib = require("lualib_bundle") +local __TS__Class = ____lualib.__TS__Class +local __TS__New = ____lualib.__TS__New +local __TS__ArrayIsArray = ____lualib.__TS__ArrayIsArray +local __TS__ObjectKeys = ____lualib.__TS__ObjectKeys +local ____exports = {} +local ____server_config = require("server_config") +local SERVER_CONFIG = ____server_config.SERVER_CONFIG +local ____api_helper = require("api_helper") +local setApiHeaders = ____api_helper.setApiHeaders +____exports.LeaderboardServer = __TS__Class() +local LeaderboardServer = ____exports.LeaderboardServer +LeaderboardServer.name = "LeaderboardServer" +LeaderboardServer.____file_path = "scripts/vscripts/leaderboard_server.lua" +function LeaderboardServer.prototype.____constructor(self) + self.serverUrl = SERVER_CONFIG.API_URL + self:setupEventListeners() +end +function LeaderboardServer.getInstance(self) + if not ____exports.LeaderboardServer.instance then + ____exports.LeaderboardServer.instance = __TS__New(____exports.LeaderboardServer) + end + return ____exports.LeaderboardServer.instance +end +function LeaderboardServer.prototype.setupEventListeners(self) + CustomGameEventManager:RegisterListener( + "request_leaderboard", + function(source, event) + local playerId = event.PlayerID + local limit = event.limit or 20 + local offset = event.offset or 0 + local rawBoard = event.board ~= nil and event.board ~= nil and tostring(event.board) or "rating" + local board = string.lower(rawBoard) + if board ~= "rating" and board ~= "wealth" and board ~= "impossible" then + board = "rating" + end + self:loadLeaderboardFromServer(playerId, limit, offset, board) + end + ) +end +function LeaderboardServer.prototype.loadLeaderboardFromServer(self, playerId, limit, offset, board) + local request = CreateHTTPRequest( + "GET", + (((((self.serverUrl .. "/leaderboard?limit=") .. tostring(limit)) .. "&offset=") .. tostring(offset)) .. "&board=") .. board + ) + setApiHeaders(nil, request) + request:Send(function(result) + do + local function ____catch(____error) + local player = PlayerResource:GetPlayer(playerId) + if player then + CustomGameEventManager:Send_ServerToPlayer(player, "leaderboard_data", {error = "Ошибка обработки данных"}) + end + end + local ____try, ____hasReturned, ____returnValue = pcall(function() + local player = PlayerResource:GetPlayer(playerId) + if not player then + return true + end + if result.StatusCode == 0 then + CustomGameEventManager:Send_ServerToPlayer(player, "leaderboard_data", {error = "Сервер недоступен"}) + return true + end + if result.StatusCode >= 200 and result.StatusCode < 300 then + do + local function ____catch(decodeError) + CustomGameEventManager:Send_ServerToPlayer(player, "leaderboard_data", {error = "Ошибка парсинга JSON"}) + end + local ____try, ____hasReturned = pcall(function() + local responseData = {json.decode(result.Body)} + local data = nil + if __TS__ArrayIsArray(responseData) and #responseData > 0 then + data = responseData[1] + elseif responseData and type(responseData) == "table" then + data = responseData + end + if data then + if data.leaderboard ~= nil and data.leaderboard ~= nil then + local leaderboard = data.leaderboard + local leaderboardLength = 0 + if __TS__ArrayIsArray(leaderboard) then + leaderboardLength = #leaderboard or 0 + elseif type(leaderboard) == "table" then + local keys = __TS__ObjectKeys(leaderboard) + leaderboardLength = keys ~= nil and keys ~= nil and #keys or 0 + end + local total = data.total or leaderboardLength + CustomGameEventManager:Send_ServerToPlayer(player, "leaderboard_data", {success = true, leaderboard = leaderboard, total = total, board = data.board or board}) + elseif __TS__ArrayIsArray(data) then + CustomGameEventManager:Send_ServerToPlayer(player, "leaderboard_data", {success = true, leaderboard = data, total = #data, board = board}) + else + CustomGameEventManager:Send_ServerToPlayer(player, "leaderboard_data", {error = "Ошибка декодирования данных: leaderboard не найден"}) + end + else + CustomGameEventManager:Send_ServerToPlayer(player, "leaderboard_data", {error = "Ошибка декодирования данных: данные пусты"}) + end + end) + if not ____try then + ____catch(____hasReturned) + end + end + else + CustomGameEventManager:Send_ServerToPlayer( + player, + "leaderboard_data", + {error = "Ошибка сервера: " .. tostring(result.StatusCode)} + ) + end + end) + if not ____try then + ____hasReturned, ____returnValue = ____catch(____hasReturned) + end + if ____hasReturned then + return ____returnValue + end + end + end) +end +return ____exports diff --git a/scripts/vscripts/lib/dota_ts_adapter.lua b/scripts/vscripts/lib/dota_ts_adapter.lua new file mode 100644 index 0000000..0531472 --- /dev/null +++ b/scripts/vscripts/lib/dota_ts_adapter.lua @@ -0,0 +1,155 @@ +local ____lualib = require("lualib_bundle") +local __TS__Class = ____lualib.__TS__Class +local __TS__ClassExtends = ____lualib.__TS__ClassExtends +local ____exports = {} +local getFileScope, toDotaClassInstance +function getFileScope(self) + return { + getfenv(3), + "unknown" + } +end +function toDotaClassInstance(self, instance, ____table) + local ____table_0 = ____table + local prototype = ____table_0.prototype + while prototype do + for key in pairs(prototype) do + if not (rawget(instance, key) ~= nil) then + instance[key] = prototype[key] + end + end + prototype = getmetatable(prototype) + end +end +____exports.BaseAbility = __TS__Class() +local BaseAbility = ____exports.BaseAbility +BaseAbility.name = "BaseAbility" +BaseAbility.____file_path = "scripts/vscripts/lib/dota_ts_adapter.lua" +function BaseAbility.prototype.____constructor(self) +end +function BaseAbility.prototype.IsAltCastAbility(self) + local caster = self:GetCaster() + if not caster then + print("[IsAltCastAbility] Caster is null") + return false + end + local playerId = caster:GetPlayerOwnerID() + if playerId == -1 or playerId == nil then + playerId = caster:GetPlayerID() + end + if playerId == -1 or playerId == nil then + print((("[IsAltCastAbility] Invalid playerId: " .. tostring(playerId)) .. ", caster=") .. caster:GetUnitName()) + return false + end + local abilityName = self:GetAbilityName() + print((((("[IsAltCastAbility] Checking: playerId=" .. tostring(playerId)) .. ", abilityName=") .. abilityName) .. ", caster=") .. caster:GetUnitName()) + if type(__abilityAltCastManager) == "nil" then + local AbilityAltCastManager = require("ability_alt_cast_manager").AbilityAltCastManager + __abilityAltCastManager = AbilityAltCastManager:getInstance() + end + return __abilityAltCastManager:getAltCastState(playerId, abilityName) +end +____exports.BaseItem = __TS__Class() +local BaseItem = ____exports.BaseItem +BaseItem.name = "BaseItem" +BaseItem.____file_path = "scripts/vscripts/lib/dota_ts_adapter.lua" +function BaseItem.prototype.____constructor(self) +end +function BaseItem.prototype.OnChargeCountChanged(self, _chargeCount, _previousChargeCount) +end +____exports.BaseModifier = __TS__Class() +local BaseModifier = ____exports.BaseModifier +BaseModifier.name = "BaseModifier" +BaseModifier.____file_path = "scripts/vscripts/lib/dota_ts_adapter.lua" +function BaseModifier.prototype.____constructor(self) +end +function BaseModifier.apply(self, target, caster, ability, modifierTable) + return target:AddNewModifier(caster, ability, self.name, modifierTable) +end +____exports.BaseModifierMotionHorizontal = __TS__Class() +local BaseModifierMotionHorizontal = ____exports.BaseModifierMotionHorizontal +BaseModifierMotionHorizontal.name = "BaseModifierMotionHorizontal" +BaseModifierMotionHorizontal.____file_path = "scripts/vscripts/lib/dota_ts_adapter.lua" +__TS__ClassExtends(BaseModifierMotionHorizontal, ____exports.BaseModifier) +____exports.BaseModifierMotionVertical = __TS__Class() +local BaseModifierMotionVertical = ____exports.BaseModifierMotionVertical +BaseModifierMotionVertical.name = "BaseModifierMotionVertical" +BaseModifierMotionVertical.____file_path = "scripts/vscripts/lib/dota_ts_adapter.lua" +__TS__ClassExtends(BaseModifierMotionVertical, ____exports.BaseModifier) +____exports.BaseModifierMotionBoth = __TS__Class() +local BaseModifierMotionBoth = ____exports.BaseModifierMotionBoth +BaseModifierMotionBoth.name = "BaseModifierMotionBoth" +BaseModifierMotionBoth.____file_path = "scripts/vscripts/lib/dota_ts_adapter.lua" +__TS__ClassExtends(BaseModifierMotionBoth, ____exports.BaseModifier) +setmetatable(____exports.BaseAbility.prototype, {__index = CDOTA_Ability_Lua or C_DOTA_Ability_Lua}) +setmetatable(____exports.BaseItem.prototype, {__index = CDOTA_Item_Lua or C_DOTA_Item_Lua}) +setmetatable(____exports.BaseModifier.prototype, {__index = CDOTA_Modifier_Lua or CDOTA_Modifier_Lua}) +____exports.registerAbility = function(____, name) return function(____, ability, context) + if name ~= nil then + ability.name = name + end + if context.name then + name = context.name + else + error("Unable to determine name of this ability class!", 0) + end + local env = unpack(getFileScope(nil)) + env[name] = {} + toDotaClassInstance(nil, env[name], ability) + local originalSpawn = env[name].Spawn + env[name].Spawn = function(self) + self:____constructor() + if originalSpawn then + originalSpawn(self) + end + end +end end +____exports.registerModifier = function(____, name) return function(____, modifier, context) + if name ~= nil then + modifier.name = name + end + if context.name then + name = context.name + else + error("Unable to determine name of this modifier class!", 0) + end + local fileName = modifier.____file_path + if not fileName then + error("Unable to determine file path of this modifier class!", 0) + end + local env = unpack(getFileScope(nil)) + env[name] = {} + toDotaClassInstance(nil, env[name], modifier) + local originalOnCreated = env[name].OnCreated + env[name].OnCreated = function(self, parameters) + self:____constructor() + if originalOnCreated ~= nil then + originalOnCreated(self, parameters) + end + end + local ____type = LUA_MODIFIER_MOTION_NONE + local base = modifier.____super + while base do + if base == ____exports.BaseModifierMotionBoth then + ____type = LUA_MODIFIER_MOTION_BOTH + break + elseif base == ____exports.BaseModifierMotionHorizontal then + ____type = LUA_MODIFIER_MOTION_HORIZONTAL + break + elseif base == ____exports.BaseModifierMotionVertical then + ____type = LUA_MODIFIER_MOTION_VERTICAL + break + end + base = base.____super + end + LinkLuaModifier(name, fileName, ____type) +end end +--- Use to expose top-level functions in entity scripts. +-- Usage: registerEntityFunction("OnStartTouch", (trigger: TriggerStartTouchEvent) => { }); +function ____exports.registerEntityFunction(self, name, f) + local env = unpack(getFileScope(nil)) + env[name] = function(...) + f(nil, ...) + end +end +return ____exports diff --git a/scripts/vscripts/lib/pool.lua b/scripts/vscripts/lib/pool.lua new file mode 100644 index 0000000..e40251f --- /dev/null +++ b/scripts/vscripts/lib/pool.lua @@ -0,0 +1,177 @@ +local ____lualib = require("lualib_bundle") +local __TS__Class = ____lualib.__TS__Class +local __TS__Delete = ____lualib.__TS__Delete +local __TS__ArrayIndexOf = ____lualib.__TS__ArrayIndexOf +local __TS__ArraySplice = ____lualib.__TS__ArraySplice +local __TS__New = ____lualib.__TS__New +local ____exports = {} +____exports.WeightedPool = __TS__Class() +local WeightedPool = ____exports.WeightedPool +WeightedPool.name = "WeightedPool" +WeightedPool.____file_path = "scripts/vscripts/lib/pool.lua" +function WeightedPool.prototype.____constructor(self, name, upperLimit, gain) + if name == nil then + name = "Безымянный пул" + end + self.data = {} + self.mappingTable = {} + self.len = 0 + self.totalProportion = 0 + self.virtualTotalProportion = 0 + self.gain = 10000 + self.name = name + self.upperLimit = upperLimit + self.gain = gain or 10000 +end +function WeightedPool.prototype.add(self, object, proportion) + if proportion == nil then + proportion = 1 + end + if self.mappingTable[object] then + local currentWeight = self:getWeightPrize(object) + self:remove(object) + return self:add(object, currentWeight + proportion) + end + if self.upperLimit and self.len >= self.upperLimit then + return self + end + self.totalProportion = self.totalProportion + proportion + self.virtualTotalProportion = math.floor(self.totalProportion * self.gain) + local weightPrize = {object = object, proportion = proportion, virtualProportion = proportion * self.gain} + self.mappingTable[object] = weightPrize + local ____self_data_0 = self.data + ____self_data_0[#____self_data_0 + 1] = weightPrize + self.len = self.len + 1 + return self +end +function WeightedPool.prototype.sub(self, object, proportion) + if self.mappingTable[object] then + local currentWeight = self:getWeightPrize(object) + self:remove(object) + self:add(object, currentWeight - proportion) + else + end +end +function WeightedPool.prototype.remove(self, object) + if not self.mappingTable[object] then + return + end + local prize = self.mappingTable[object] + __TS__Delete(self.mappingTable, object) + local index = __TS__ArrayIndexOf(self.data, prize) + if index > -1 then + __TS__ArraySplice(self.data, index, 1) + end + self.totalProportion = self.totalProportion - prize.proportion + self.virtualTotalProportion = math.floor(self.totalProportion * self.gain) + self.len = self.len - 1 +end +function WeightedPool.prototype.getProbability(self, object) + local weightPrize = self.mappingTable[object] + if not weightPrize then + return {0, 0} + end + return {weightPrize.proportion / self.totalProportion, weightPrize.proportion} +end +function WeightedPool.prototype.getWeightPrize(self, object) + local weightPrize = self.mappingTable[object] + return weightPrize ~= nil and weightPrize.proportion or 0 +end +function WeightedPool.prototype.setWeightPrize(self, object, proportion) + local weightPrize = self.mappingTable[object] + if not weightPrize then + return {0, 0} + end + local used = weightPrize.proportion + self.totalProportion = self.totalProportion - used + weightPrize.proportion = proportion + weightPrize.virtualProportion = proportion * self.gain + self.totalProportion = self.totalProportion + proportion + self.virtualTotalProportion = math.floor(self.totalProportion * self.gain) + return {used, proportion} +end +function WeightedPool.prototype.random(self) + if self.len == 0 then + return nil + end + local winningProbability = RandomInt(1, self.virtualTotalProportion) + for ____, prize in ipairs(self.data) do + winningProbability = winningProbability - prize.virtualProportion + if winningProbability <= 0 then + return prize.object + end + end + return nil +end +function WeightedPool.prototype.randomAndRemove(self) + if self.len == 0 then + return nil + end + local winningProbability = RandomInt(1, self.virtualTotalProportion) + for ____, prize in ipairs(self.data) do + winningProbability = winningProbability - prize.virtualProportion + if winningProbability <= 0 then + self:remove(prize.object) + return prize.object + end + end + return nil +end +function WeightedPool.prototype.clear(self) + self.len = 0 + self.data = {} + self.mappingTable = {} + self.totalProportion = 0 + self.virtualTotalProportion = 0 +end +function WeightedPool.prototype.randomSole(self, n) + local result = {} + local temp = {} + do + local i = 0 + while i < n do + local obj = self:random() + if obj ~= nil then + result[#result + 1] = obj + local weight = self:getWeightPrize(obj) + temp[#temp + 1] = {obj, weight} + self:setWeightPrize(obj, 0) + end + i = i + 1 + end + end + for ____, ____value in ipairs(temp) do + local obj = ____value[1] + local weight = ____value[2] + self:setWeightPrize(obj, weight) + end + return result +end +function WeightedPool.prototype.randomCard(self, n) + local result = {} + local temp = {} + do + local i = 0 + while i < n do + local obj = self:random() + if obj ~= nil then + result[#result + 1] = obj + local weight = self:getWeightPrize(obj) + temp[#temp + 1] = {obj, weight} + local newWeight = weight - 1 + self:setWeightPrize(obj, newWeight >= 0 and newWeight or 0) + end + i = i + 1 + end + end + for ____, ____value in ipairs(temp) do + local obj = ____value[1] + local currentWeight = self:getWeightPrize(obj) + self:setWeightPrize(obj, currentWeight + 1) + end + return result +end +_G.pool = function(____, name, upperLimit, gain) + return __TS__New(____exports.WeightedPool, name, upperLimit, gain) +end +return ____exports diff --git a/scripts/vscripts/lib/timers.lua b/scripts/vscripts/lib/timers.lua new file mode 100644 index 0000000..5d05060 --- /dev/null +++ b/scripts/vscripts/lib/timers.lua @@ -0,0 +1,333 @@ +TIMERS_VERSION = "1.07" + +--[[ + 1.07 modified by Perry (fixed stack overflow if lots of timers finish at the same time and removed HandleErrors throwing error outside tools mode) + 1.06 modified by Celireor (now uses binary heap priority queue instead of iteration to determine timer of shortest duration) + + DO NOT MODIFY A REALTIME TIMER TO USE GAMETIME OR VICE VERSA MIDWAY WITHOUT FIRST REMOVING AND RE-ADDING THE TIMER + + -- A timer running every second that starts immediately on the next frame, respects pauses + Timers:CreateTimer(function() + print ("Hello. I'm running immediately and then every second thereafter.") + return 1.0 + end + ) + + -- The same timer as above with a shorthand call + Timers(function() + print ("Hello. I'm running immediately and then every second thereafter.") + return 1.0 + end) + + + -- A timer which calls a function with a table context + Timers:CreateTimer(GameMode.someFunction, GameMode) + + -- A timer running every second that starts 5 seconds in the future, respects pauses + Timers:CreateTimer(5, function() + print ("Hello. I'm running 5 seconds after you called me and then every second thereafter.") + return 1.0 + end + ) + + -- 10 second delayed, run once using gametime (respect pauses) + Timers:CreateTimer({ + endTime = 10, -- when this timer should first execute, you can omit this if you want it to run first on the next frame + callback = function() + print ("Hello. I'm running 10 seconds after when I was started.") + end + }) + + -- 10 second delayed, run once regardless of pauses + Timers:CreateTimer({ + useGameTime = false, + endTime = 10, -- when this timer should first execute, you can omit this if you want it to run first on the next frame + callback = function() + print ("Hello. I'm running 10 seconds after I was started even if someone paused the game.") + end + }) + + + -- A timer running every second that starts after 2 minutes regardless of pauses + Timers:CreateTimer("uniqueTimerString3", { + useGameTime = false, + endTime = 120, + callback = function() + print ("Hello. I'm running after 2 minutes and then every second thereafter.") + return 1 + end + }) + +]] + +-- Binary Heap implementation copy-pasted from https://gist.github.com/starwing/1757443a1bd295653c39 +-- BinaryHeap[1] always points to the element with the lowest "key" variable +-- API +-- BinaryHeap(key) - Creates a new BinaryHeap with key. The key is the name of the integer variable used to sort objects. +-- BinaryHeap:Insert - Inserts an object into BinaryHeap +-- BinaryHeap:Remove - Removes an object from BinaryHeap + +BinaryHeap = BinaryHeap or {} +BinaryHeap.__index = BinaryHeap + +function BinaryHeap:Insert(item) + local index = #self + 1 + local key = self.key + item.index = index + self[index] = item + while index > 1 do + local parent = math.floor(index/2) + if self[parent][key] <= item[key] then + break + end + self[index], self[parent] = self[parent], self[index] + self[index].index = index + self[parent].index = parent + index = parent + end + return item +end + +function BinaryHeap:Remove(item) + local index = item.index + if self[index] ~= item then return end + local key = self.key + local heap_size = #self + if index == heap_size then + self[heap_size] = nil + return + end + self[index] = self[heap_size] + self[index].index = index + self[heap_size] = nil + while true do + local left = index*2 + local right = left + 1 + if not self[left] then break end + local newindex = right + if self[index][key] >= self[left][key] then + if not self[right] or self[left][key] < self[right][key] then + newindex = left + end + elseif not self[right] or self[index][key] <= self[right][key] then + break + end + self[index], self[newindex] = self[newindex], self[index] + self[index].index = index + self[newindex].index = newindex + index = newindex + end +end + +setmetatable(BinaryHeap, {__call = function(self, key) return setmetatable({key=key}, self) end}) + +function table.merge(input1, input2) + for i,v in pairs(input2) do + input1[i] = v + end + return input1 +end + + +TIMERS_THINK = 0.01 + +if Timers == nil then + print ( '[Timers] creating Timers ['..TIMERS_VERSION..']' ) + Timers = {} + setmetatable(Timers, { + __call = function(t, ...) + return t:CreateTimer(...) + end + }) + --Timers.__index = Timers +end + +function Timers:start() + self.started = true + Timers = self + self:InitializeTimers() + self.nextTickCallbacks = {} + + local ent = SpawnEntityFromTableSynchronous("info_target", {targetname="timers_lua_thinker"}) + ent:SetThink("Think", self, "timers", TIMERS_THINK) +end + +function Timers:Think() + local nextTickCallbacks = table.merge({}, Timers.nextTickCallbacks) + Timers.nextTickCallbacks = {} + for _, cb in ipairs(nextTickCallbacks) do + local status, result = xpcall(cb, function(err) + return tostring(err) + end) + if not status then + Timers:HandleEventError(result) + end + end + if GameRules:State_Get() >= DOTA_GAMERULES_STATE_POST_GAME then + return + end + + -- Track game time, since the dt passed in to think is actually wall-clock time not simulation time. + local now = GameRules:GetGameTime() + + -- Process timers + self:ExecuteTimers(self.realTimeHeap, Time()) + self:ExecuteTimers(self.gameTimeHeap, GameRules:GetGameTime()) + + return TIMERS_THINK +end + +function Timers:ExecuteTimers(timerList, now) + --Timers are alr. sorted by end time upon insertion + local currentTimer = timerList[1] + + --Check if timer has finished + while currentTimer and (now >= currentTimer.endTime) do + -- Remove from timers list + timerList:Remove(currentTimer) + Timers.runningTimer = currentTimer + Timers.removeSelf = false + + -- Run the callback + local status, timerResult + if currentTimer.context then + status, timerResult = xpcall(function() + return currentTimer.callback(currentTimer.context, currentTimer) + end, function(err) + return tostring(err) + end) + else + status, timerResult = xpcall(function() + return currentTimer.callback(currentTimer) + end, function(err) + return tostring(err) + end) + end + + Timers.runningTimer = nil + + -- Make sure it worked + if status then + -- Check if it needs to loop + if timerResult and not Timers.removeSelf then + -- Change its end time + + currentTimer.endTime = currentTimer.endTime + timerResult + + timerList:Insert(currentTimer) + end + + -- Update timer data + --self:UpdateTimerData() + else + -- Nope, handle the error + Timers:HandleEventError(timerResult) + end + --Check next timer in heap + currentTimer = timerList[1] + end +end + +function Timers:HandleEventError(err) + -- Защита от спама "nil" без контекста + if err == nil or err == "" then + print("[Timers] Timer error: ") + return + end + + print("[Timers] Timer error:", err) + --if not IsInToolsMode() then + -- If you want to send errors from inside timers on live servers to your own webserver, do it here + --end +end + +function Timers:CreateTimer(arg1, arg2, context) + local timer + if type(arg1) == "function" then + if arg2 ~= nil then + context = arg2 + end + timer = {callback = arg1} + elseif type(arg1) == "table" then + timer = arg1 + elseif type(arg1) == "number" then + timer = {endTime = arg1, callback = arg2} + elseif type(arg1) == "string" then + -- First argument is timer name, second is table with timer config + timer = arg2 + if timer then + timer.name = arg1 + end + end + if not timer or not timer.callback then + print("Invalid timer created: timer is nil or callback is missing") + return + end + + local now = GameRules:GetGameTime() + local timerHeap = self.gameTimeHeap + if timer.useGameTime ~= nil and timer.useGameTime == false then + now = Time() + timerHeap = self.realTimeHeap + end + + if timer.endTime == nil then + timer.endTime = now + else + timer.endTime = now + timer.endTime + end + + timer.context = context + + timerHeap:Insert(timer) + + return timer +end + +function Timers:NextTick(callback) + table.insert(Timers.nextTickCallbacks, callback) +end + +function Timers:RemoveTimer(nameOrTimer) + -- Разрешаем передавать либо сам таймер, либо строковое имя + if not nameOrTimer then return end + + -- Если передали строку – ищем таймер с таким name в кучах + if type(nameOrTimer) == "string" then + local name = nameOrTimer + local function findByName(heap) + for _, t in ipairs(heap) do + if t.name == name then + return t + end + end + return nil + end + + local timer = findByName(self.gameTimeHeap) or findByName(self.realTimeHeap) + if not timer then + return + end + nameOrTimer = timer + end + + local timer = nameOrTimer + local timerHeap = self.gameTimeHeap + if timer.useGameTime ~= nil and timer.useGameTime == false then + timerHeap = self.realTimeHeap + end + + timerHeap:Remove(timer) + if Timers.runningTimer == timer then + Timers.removeSelf = true + end +end + +function Timers:InitializeTimers() + self.realTimeHeap = BinaryHeap("endTime") + self.gameTimeHeap = BinaryHeap("endTime") +end + +if not Timers.started then Timers:start() end + +GameRules.Timers = Timers \ No newline at end of file diff --git a/scripts/vscripts/lib/tstl-utils.lua b/scripts/vscripts/lib/tstl-utils.lua new file mode 100644 index 0000000..8e2600f --- /dev/null +++ b/scripts/vscripts/lib/tstl-utils.lua @@ -0,0 +1,19 @@ +local ____lualib = require("lualib_bundle") +local __TS__ObjectAssign = ____lualib.__TS__ObjectAssign +local ____exports = {} +local global = _G +if global.reloadCache == nil then + global.reloadCache = {} +end +function ____exports.reloadable(self, constructor, context) + local className = context.name + if className == nil then + error("Cannot reload classes without names!", 0) + end + if global.reloadCache[className] == nil then + global.reloadCache[className] = constructor + end + __TS__ObjectAssign(global.reloadCache[className].prototype, constructor.prototype) + return global.reloadCache[className] +end +return ____exports diff --git a/scripts/vscripts/lualib_bundle.lua b/scripts/vscripts/lualib_bundle.lua new file mode 100644 index 0000000..09a3962 --- /dev/null +++ b/scripts/vscripts/lualib_bundle.lua @@ -0,0 +1,2722 @@ +local function __TS__ArrayAt(self, relativeIndex) + local absoluteIndex = relativeIndex < 0 and #self + relativeIndex or relativeIndex + if absoluteIndex >= 0 and absoluteIndex < #self then + return self[absoluteIndex + 1] + end + return nil +end + +local function __TS__ArrayIsArray(value) + return type(value) == "table" and (value[1] ~= nil or next(value) == nil) +end + +local function __TS__ArrayConcat(self, ...) + local items = {...} + local result = {} + local len = 0 + for i = 1, #self do + len = len + 1 + result[len] = self[i] + end + for i = 1, #items do + local item = items[i] + if __TS__ArrayIsArray(item) then + for j = 1, #item do + len = len + 1 + result[len] = item[j] + end + else + len = len + 1 + result[len] = item + end + end + return result +end + +local __TS__Symbol, Symbol +do + local symbolMetatable = {__tostring = function(self) + return ("Symbol(" .. (self.description or "")) .. ")" + end} + function __TS__Symbol(description) + return setmetatable({description = description}, symbolMetatable) + end + Symbol = { + asyncDispose = __TS__Symbol("Symbol.asyncDispose"), + dispose = __TS__Symbol("Symbol.dispose"), + iterator = __TS__Symbol("Symbol.iterator"), + hasInstance = __TS__Symbol("Symbol.hasInstance"), + species = __TS__Symbol("Symbol.species"), + toStringTag = __TS__Symbol("Symbol.toStringTag") + } +end + +local function __TS__ArrayEntries(array) + local key = 0 + return { + [Symbol.iterator] = function(self) + return self + end, + next = function(self) + local result = {done = array[key + 1] == nil, value = {key, array[key + 1]}} + key = key + 1 + return result + end + } +end + +local function __TS__ArrayEvery(self, callbackfn, thisArg) + for i = 1, #self do + if not callbackfn(thisArg, self[i], i - 1, self) then + return false + end + end + return true +end + +local function __TS__ArrayFill(self, value, start, ____end) + local relativeStart = start or 0 + local relativeEnd = ____end or #self + if relativeStart < 0 then + relativeStart = relativeStart + #self + end + if relativeEnd < 0 then + relativeEnd = relativeEnd + #self + end + do + local i = relativeStart + while i < relativeEnd do + self[i + 1] = value + i = i + 1 + end + end + return self +end + +local function __TS__ArrayFilter(self, callbackfn, thisArg) + local result = {} + local len = 0 + for i = 1, #self do + if callbackfn(thisArg, self[i], i - 1, self) then + len = len + 1 + result[len] = self[i] + end + end + return result +end + +local function __TS__ArrayForEach(self, callbackFn, thisArg) + for i = 1, #self do + callbackFn(thisArg, self[i], i - 1, self) + end +end + +local function __TS__ArrayFind(self, predicate, thisArg) + for i = 1, #self do + local elem = self[i] + if predicate(thisArg, elem, i - 1, self) then + return elem + end + end + return nil +end + +local function __TS__ArrayFindIndex(self, callbackFn, thisArg) + for i = 1, #self do + if callbackFn(thisArg, self[i], i - 1, self) then + return i - 1 + end + end + return -1 +end + +local __TS__Iterator +do + local function iteratorGeneratorStep(self) + local co = self.____coroutine + local status, value = coroutine.resume(co) + if not status then + error(value, 0) + end + if coroutine.status(co) == "dead" then + return + end + return true, value + end + local function iteratorIteratorStep(self) + local result = self:next() + if result.done then + return + end + return true, result.value + end + local function iteratorStringStep(self, index) + index = index + 1 + if index > #self then + return + end + return index, string.sub(self, index, index) + end + function __TS__Iterator(iterable) + if type(iterable) == "string" then + return iteratorStringStep, iterable, 0 + elseif iterable.____coroutine ~= nil then + return iteratorGeneratorStep, iterable + elseif iterable[Symbol.iterator] then + local iterator = iterable[Symbol.iterator](iterable) + return iteratorIteratorStep, iterator + else + return ipairs(iterable) + end + end +end + +local __TS__ArrayFrom +do + local function arrayLikeStep(self, index) + index = index + 1 + if index > self.length then + return + end + return index, self[index] + end + local function arrayLikeIterator(arr) + if type(arr.length) == "number" then + return arrayLikeStep, arr, 0 + end + return __TS__Iterator(arr) + end + function __TS__ArrayFrom(arrayLike, mapFn, thisArg) + local result = {} + if mapFn == nil then + for ____, v in arrayLikeIterator(arrayLike) do + result[#result + 1] = v + end + else + for i, v in arrayLikeIterator(arrayLike) do + result[#result + 1] = mapFn(thisArg, v, i - 1) + end + end + return result + end +end + +local function __TS__ArrayIncludes(self, searchElement, fromIndex) + if fromIndex == nil then + fromIndex = 0 + end + local len = #self + local k = fromIndex + if fromIndex < 0 then + k = len + fromIndex + end + if k < 0 then + k = 0 + end + for i = k + 1, len do + if self[i] == searchElement then + return true + end + end + return false +end + +local function __TS__ArrayIndexOf(self, searchElement, fromIndex) + if fromIndex == nil then + fromIndex = 0 + end + local len = #self + if len == 0 then + return -1 + end + if fromIndex >= len then + return -1 + end + if fromIndex < 0 then + fromIndex = len + fromIndex + if fromIndex < 0 then + fromIndex = 0 + end + end + for i = fromIndex + 1, len do + if self[i] == searchElement then + return i - 1 + end + end + return -1 +end + +local function __TS__ArrayJoin(self, separator) + if separator == nil then + separator = "," + end + local parts = {} + for i = 1, #self do + parts[i] = tostring(self[i]) + end + return table.concat(parts, separator) +end + +local function __TS__ArrayMap(self, callbackfn, thisArg) + local result = {} + for i = 1, #self do + result[i] = callbackfn(thisArg, self[i], i - 1, self) + end + return result +end + +local function __TS__ArrayPush(self, ...) + local items = {...} + local len = #self + for i = 1, #items do + len = len + 1 + self[len] = items[i] + end + return len +end + +local function __TS__ArrayPushArray(self, items) + local len = #self + for i = 1, #items do + len = len + 1 + self[len] = items[i] + end + return len +end + +local function __TS__CountVarargs(...) + return select("#", ...) +end + +local function __TS__ArrayReduce(self, callbackFn, ...) + local len = #self + local k = 0 + local accumulator = nil + if __TS__CountVarargs(...) ~= 0 then + accumulator = ... + elseif len > 0 then + accumulator = self[1] + k = 1 + else + error("Reduce of empty array with no initial value", 0) + end + for i = k + 1, len do + accumulator = callbackFn( + nil, + accumulator, + self[i], + i - 1, + self + ) + end + return accumulator +end + +local function __TS__ArrayReduceRight(self, callbackFn, ...) + local len = #self + local k = len - 1 + local accumulator = nil + if __TS__CountVarargs(...) ~= 0 then + accumulator = ... + elseif len > 0 then + accumulator = self[k + 1] + k = k - 1 + else + error("Reduce of empty array with no initial value", 0) + end + for i = k + 1, 1, -1 do + accumulator = callbackFn( + nil, + accumulator, + self[i], + i - 1, + self + ) + end + return accumulator +end + +local function __TS__ArrayReverse(self) + local i = 1 + local j = #self + while i < j do + local temp = self[j] + self[j] = self[i] + self[i] = temp + i = i + 1 + j = j - 1 + end + return self +end + +local function __TS__ArrayUnshift(self, ...) + local items = {...} + local numItemsToInsert = #items + if numItemsToInsert == 0 then + return #self + end + for i = #self, 1, -1 do + self[i + numItemsToInsert] = self[i] + end + for i = 1, numItemsToInsert do + self[i] = items[i] + end + return #self +end + +local function __TS__ArraySort(self, compareFn) + if compareFn ~= nil then + table.sort( + self, + function(a, b) return compareFn(nil, a, b) < 0 end + ) + else + table.sort(self) + end + return self +end + +local function __TS__ArraySlice(self, first, last) + local len = #self + first = first or 0 + if first < 0 then + first = len + first + if first < 0 then + first = 0 + end + else + if first > len then + first = len + end + end + last = last or len + if last < 0 then + last = len + last + if last < 0 then + last = 0 + end + else + if last > len then + last = len + end + end + local out = {} + first = first + 1 + last = last + 1 + local n = 1 + while first < last do + out[n] = self[first] + first = first + 1 + n = n + 1 + end + return out +end + +local function __TS__ArraySome(self, callbackfn, thisArg) + for i = 1, #self do + if callbackfn(thisArg, self[i], i - 1, self) then + return true + end + end + return false +end + +local function __TS__ArraySplice(self, ...) + local args = {...} + local len = #self + local actualArgumentCount = __TS__CountVarargs(...) + local start = args[1] + local deleteCount = args[2] + if start < 0 then + start = len + start + if start < 0 then + start = 0 + end + elseif start > len then + start = len + end + local itemCount = actualArgumentCount - 2 + if itemCount < 0 then + itemCount = 0 + end + local actualDeleteCount + if actualArgumentCount == 0 then + actualDeleteCount = 0 + elseif actualArgumentCount == 1 then + actualDeleteCount = len - start + else + actualDeleteCount = deleteCount or 0 + if actualDeleteCount < 0 then + actualDeleteCount = 0 + end + if actualDeleteCount > len - start then + actualDeleteCount = len - start + end + end + local out = {} + for k = 1, actualDeleteCount do + local from = start + k + if self[from] ~= nil then + out[k] = self[from] + end + end + if itemCount < actualDeleteCount then + for k = start + 1, len - actualDeleteCount do + local from = k + actualDeleteCount + local to = k + itemCount + if self[from] then + self[to] = self[from] + else + self[to] = nil + end + end + for k = len - actualDeleteCount + itemCount + 1, len do + self[k] = nil + end + elseif itemCount > actualDeleteCount then + for k = len - actualDeleteCount, start + 1, -1 do + local from = k + actualDeleteCount + local to = k + itemCount + if self[from] then + self[to] = self[from] + else + self[to] = nil + end + end + end + local j = start + 1 + for i = 3, actualArgumentCount do + self[j] = args[i] + j = j + 1 + end + for k = #self, len - actualDeleteCount + itemCount + 1, -1 do + self[k] = nil + end + return out +end + +local function __TS__ArrayToObject(self) + local object = {} + for i = 1, #self do + object[i - 1] = self[i] + end + return object +end + +local function __TS__ArrayFlat(self, depth) + if depth == nil then + depth = 1 + end + local result = {} + local len = 0 + for i = 1, #self do + local value = self[i] + if depth > 0 and __TS__ArrayIsArray(value) then + local toAdd + if depth == 1 then + toAdd = value + else + toAdd = __TS__ArrayFlat(value, depth - 1) + end + for j = 1, #toAdd do + local val = toAdd[j] + len = len + 1 + result[len] = val + end + else + len = len + 1 + result[len] = value + end + end + return result +end + +local function __TS__ArrayFlatMap(self, callback, thisArg) + local result = {} + local len = 0 + for i = 1, #self do + local value = callback(thisArg, self[i], i - 1, self) + if __TS__ArrayIsArray(value) then + for j = 1, #value do + len = len + 1 + result[len] = value[j] + end + else + len = len + 1 + result[len] = value + end + end + return result +end + +local function __TS__ArraySetLength(self, length) + if length < 0 or length ~= length or length == math.huge or math.floor(length) ~= length then + error( + "invalid array length: " .. tostring(length), + 0 + ) + end + for i = length + 1, #self do + self[i] = nil + end + return length +end + +local __TS__Unpack = table.unpack or unpack + +local function __TS__ArrayToReversed(self) + local copy = {__TS__Unpack(self)} + __TS__ArrayReverse(copy) + return copy +end + +local function __TS__ArrayToSorted(self, compareFn) + local copy = {__TS__Unpack(self)} + __TS__ArraySort(copy, compareFn) + return copy +end + +local function __TS__ArrayToSpliced(self, start, deleteCount, ...) + local copy = {__TS__Unpack(self)} + __TS__ArraySplice(copy, start, deleteCount, ...) + return copy +end + +local function __TS__ArrayWith(self, index, value) + local copy = {__TS__Unpack(self)} + copy[index + 1] = value + return copy +end + +local function __TS__New(target, ...) + local instance = setmetatable({}, target.prototype) + instance:____constructor(...) + return instance +end + +local function __TS__InstanceOf(obj, classTbl) + if type(classTbl) ~= "table" then + error("Right-hand side of 'instanceof' is not an object", 0) + end + if classTbl[Symbol.hasInstance] ~= nil then + return not not classTbl[Symbol.hasInstance](classTbl, obj) + end + if type(obj) == "table" then + local luaClass = obj.constructor + while luaClass ~= nil do + if luaClass == classTbl then + return true + end + luaClass = luaClass.____super + end + end + return false +end + +local function __TS__Class(self) + local c = {prototype = {}} + c.prototype.__index = c.prototype + c.prototype.constructor = c + return c +end + +local __TS__Promise +do + local function makeDeferredPromiseFactory() + local resolve + local reject + local function executor(____, res, rej) + resolve = res + reject = rej + end + return function() + local promise = __TS__New(__TS__Promise, executor) + return promise, resolve, reject + end + end + local makeDeferredPromise = makeDeferredPromiseFactory() + local function isPromiseLike(value) + return __TS__InstanceOf(value, __TS__Promise) + end + local function doNothing(self) + end + local ____pcall = _G.pcall + __TS__Promise = __TS__Class() + __TS__Promise.name = "__TS__Promise" + function __TS__Promise.prototype.____constructor(self, executor) + self.state = 0 + self.fulfilledCallbacks = {} + self.rejectedCallbacks = {} + self.finallyCallbacks = {} + local success, ____error = ____pcall( + executor, + nil, + function(____, v) return self:resolve(v) end, + function(____, err) return self:reject(err) end + ) + if not success then + self:reject(____error) + end + end + function __TS__Promise.resolve(value) + if __TS__InstanceOf(value, __TS__Promise) then + return value + end + local promise = __TS__New(__TS__Promise, doNothing) + promise.state = 1 + promise.value = value + return promise + end + function __TS__Promise.reject(reason) + local promise = __TS__New(__TS__Promise, doNothing) + promise.state = 2 + promise.rejectionReason = reason + return promise + end + __TS__Promise.prototype["then"] = function(self, onFulfilled, onRejected) + local promise, resolve, reject = makeDeferredPromise() + self:addCallbacks( + onFulfilled and self:createPromiseResolvingCallback(onFulfilled, resolve, reject) or resolve, + onRejected and self:createPromiseResolvingCallback(onRejected, resolve, reject) or reject + ) + return promise + end + function __TS__Promise.prototype.addCallbacks(self, fulfilledCallback, rejectedCallback) + if self.state == 1 then + return fulfilledCallback(nil, self.value) + end + if self.state == 2 then + return rejectedCallback(nil, self.rejectionReason) + end + local ____self_fulfilledCallbacks_0 = self.fulfilledCallbacks + ____self_fulfilledCallbacks_0[#____self_fulfilledCallbacks_0 + 1] = fulfilledCallback + local ____self_rejectedCallbacks_1 = self.rejectedCallbacks + ____self_rejectedCallbacks_1[#____self_rejectedCallbacks_1 + 1] = rejectedCallback + end + function __TS__Promise.prototype.catch(self, onRejected) + return self["then"](self, nil, onRejected) + end + function __TS__Promise.prototype.finally(self, onFinally) + if onFinally then + local ____self_finallyCallbacks_2 = self.finallyCallbacks + ____self_finallyCallbacks_2[#____self_finallyCallbacks_2 + 1] = onFinally + if self.state ~= 0 then + onFinally(nil) + end + end + return self + end + function __TS__Promise.prototype.resolve(self, value) + if isPromiseLike(value) then + return value:addCallbacks( + function(____, v) return self:resolve(v) end, + function(____, err) return self:reject(err) end + ) + end + if self.state == 0 then + self.state = 1 + self.value = value + return self:invokeCallbacks(self.fulfilledCallbacks, value) + end + end + function __TS__Promise.prototype.reject(self, reason) + if self.state == 0 then + self.state = 2 + self.rejectionReason = reason + return self:invokeCallbacks(self.rejectedCallbacks, reason) + end + end + function __TS__Promise.prototype.invokeCallbacks(self, callbacks, value) + local callbacksLength = #callbacks + local finallyCallbacks = self.finallyCallbacks + local finallyCallbacksLength = #finallyCallbacks + if callbacksLength ~= 0 then + for i = 1, callbacksLength - 1 do + callbacks[i](callbacks, value) + end + if finallyCallbacksLength == 0 then + return callbacks[callbacksLength](callbacks, value) + end + callbacks[callbacksLength](callbacks, value) + end + if finallyCallbacksLength ~= 0 then + for i = 1, finallyCallbacksLength - 1 do + finallyCallbacks[i](finallyCallbacks) + end + return finallyCallbacks[finallyCallbacksLength](finallyCallbacks) + end + end + function __TS__Promise.prototype.createPromiseResolvingCallback(self, f, resolve, reject) + return function(____, value) + local success, resultOrError = ____pcall(f, nil, value) + if not success then + return reject(nil, resultOrError) + end + return self:handleCallbackValue(resultOrError, resolve, reject) + end + end + function __TS__Promise.prototype.handleCallbackValue(self, value, resolve, reject) + if isPromiseLike(value) then + local nextpromise = value + if nextpromise.state == 1 then + return resolve(nil, nextpromise.value) + elseif nextpromise.state == 2 then + return reject(nil, nextpromise.rejectionReason) + else + return nextpromise:addCallbacks(resolve, reject) + end + else + return resolve(nil, value) + end + end +end + +local __TS__AsyncAwaiter, __TS__Await +do + local ____coroutine = _G.coroutine or ({}) + local cocreate = ____coroutine.create + local coresume = ____coroutine.resume + local costatus = ____coroutine.status + local coyield = ____coroutine.yield + function __TS__AsyncAwaiter(generator) + return __TS__New( + __TS__Promise, + function(____, resolve, reject) + local fulfilled, step, resolved, asyncCoroutine + function fulfilled(self, value) + local success, resultOrError = coresume(asyncCoroutine, value) + if success then + return step(resultOrError) + end + return reject(nil, resultOrError) + end + function step(result) + if resolved then + return + end + if costatus(asyncCoroutine) == "dead" then + return resolve(nil, result) + end + return __TS__Promise.resolve(result):addCallbacks(fulfilled, reject) + end + resolved = false + asyncCoroutine = cocreate(generator) + local success, resultOrError = coresume( + asyncCoroutine, + function(____, v) + resolved = true + return __TS__Promise.resolve(v):addCallbacks(resolve, reject) + end + ) + if success then + return step(resultOrError) + else + return reject(nil, resultOrError) + end + end + ) + end + function __TS__Await(thing) + return coyield(thing) + end +end + +local function __TS__ClassExtends(target, base) + target.____super = base + local staticMetatable = setmetatable({__index = base}, base) + setmetatable(target, staticMetatable) + local baseMetatable = getmetatable(base) + if baseMetatable then + if type(baseMetatable.__index) == "function" then + staticMetatable.__index = baseMetatable.__index + end + if type(baseMetatable.__newindex) == "function" then + staticMetatable.__newindex = baseMetatable.__newindex + end + end + setmetatable(target.prototype, base.prototype) + if type(base.prototype.__index) == "function" then + target.prototype.__index = base.prototype.__index + end + if type(base.prototype.__newindex) == "function" then + target.prototype.__newindex = base.prototype.__newindex + end + if type(base.prototype.__tostring) == "function" then + target.prototype.__tostring = base.prototype.__tostring + end +end + +local function __TS__CloneDescriptor(____bindingPattern0) + local value + local writable + local set + local get + local configurable + local enumerable + enumerable = ____bindingPattern0.enumerable + configurable = ____bindingPattern0.configurable + get = ____bindingPattern0.get + set = ____bindingPattern0.set + writable = ____bindingPattern0.writable + value = ____bindingPattern0.value + local descriptor = {enumerable = enumerable == true, configurable = configurable == true} + local hasGetterOrSetter = get ~= nil or set ~= nil + local hasValueOrWritableAttribute = writable ~= nil or value ~= nil + if hasGetterOrSetter and hasValueOrWritableAttribute then + error("Invalid property descriptor. Cannot both specify accessors and a value or writable attribute.", 0) + end + if get or set then + descriptor.get = get + descriptor.set = set + else + descriptor.value = value + descriptor.writable = writable == true + end + return descriptor +end + +local function __TS__Decorate(self, originalValue, decorators, context) + local result = originalValue + do + local i = #decorators + while i >= 0 do + local decorator = decorators[i + 1] + if decorator ~= nil then + local ____decorator_result_0 = decorator(self, result, context) + if ____decorator_result_0 == nil then + ____decorator_result_0 = result + end + result = ____decorator_result_0 + end + i = i - 1 + end + end + return result +end + +local function __TS__ObjectAssign(target, ...) + local sources = {...} + for i = 1, #sources do + local source = sources[i] + for key in pairs(source) do + target[key] = source[key] + end + end + return target +end + +local function __TS__ObjectGetOwnPropertyDescriptor(object, key) + local metatable = getmetatable(object) + if not metatable then + return + end + if not rawget(metatable, "_descriptors") then + return + end + return rawget(metatable, "_descriptors")[key] +end + +local __TS__DescriptorGet +do + local getmetatable = _G.getmetatable + local ____rawget = _G.rawget + function __TS__DescriptorGet(self, metatable, key) + while metatable do + local rawResult = ____rawget(metatable, key) + if rawResult ~= nil then + return rawResult + end + local descriptors = ____rawget(metatable, "_descriptors") + if descriptors then + local descriptor = descriptors[key] + if descriptor ~= nil then + if descriptor.get then + return descriptor.get(self) + end + return descriptor.value + end + end + metatable = getmetatable(metatable) + end + end +end + +local __TS__DescriptorSet +do + local getmetatable = _G.getmetatable + local ____rawget = _G.rawget + local rawset = _G.rawset + function __TS__DescriptorSet(self, metatable, key, value) + while metatable do + local descriptors = ____rawget(metatable, "_descriptors") + if descriptors then + local descriptor = descriptors[key] + if descriptor ~= nil then + if descriptor.set then + descriptor.set(self, value) + else + if descriptor.writable == false then + error( + ((("Cannot assign to read only property '" .. key) .. "' of object '") .. tostring(self)) .. "'", + 0 + ) + end + descriptor.value = value + end + return + end + end + metatable = getmetatable(metatable) + end + rawset(self, key, value) + end +end + +local __TS__SetDescriptor +do + local getmetatable = _G.getmetatable + local function descriptorIndex(self, key) + return __TS__DescriptorGet( + self, + getmetatable(self), + key + ) + end + local function descriptorNewIndex(self, key, value) + return __TS__DescriptorSet( + self, + getmetatable(self), + key, + value + ) + end + function __TS__SetDescriptor(target, key, desc, isPrototype) + if isPrototype == nil then + isPrototype = false + end + local ____isPrototype_0 + if isPrototype then + ____isPrototype_0 = target + else + ____isPrototype_0 = getmetatable(target) + end + local metatable = ____isPrototype_0 + if not metatable then + metatable = {} + setmetatable(target, metatable) + end + local value = rawget(target, key) + if value ~= nil then + rawset(target, key, nil) + end + if not rawget(metatable, "_descriptors") then + metatable._descriptors = {} + end + metatable._descriptors[key] = __TS__CloneDescriptor(desc) + metatable.__index = descriptorIndex + metatable.__newindex = descriptorNewIndex + end +end + +local function __TS__DecorateLegacy(decorators, target, key, desc) + local result = target + do + local i = #decorators + while i >= 0 do + local decorator = decorators[i + 1] + if decorator ~= nil then + local oldResult = result + if key == nil then + result = decorator(nil, result) + elseif desc == true then + local value = rawget(target, key) + local descriptor = __TS__ObjectGetOwnPropertyDescriptor(target, key) or ({configurable = true, writable = true, value = value}) + local desc = decorator(nil, target, key, descriptor) or descriptor + local isSimpleValue = desc.configurable == true and desc.writable == true and not desc.get and not desc.set + if isSimpleValue then + rawset(target, key, desc.value) + else + __TS__SetDescriptor( + target, + key, + __TS__ObjectAssign({}, descriptor, desc) + ) + end + elseif desc == false then + result = decorator(nil, target, key, desc) + else + result = decorator(nil, target, key) + end + result = result or oldResult + end + i = i - 1 + end + end + return result +end + +local function __TS__DecorateParam(paramIndex, decorator) + return function(____, target, key) return decorator(nil, target, key, paramIndex) end +end + +local function __TS__StringIncludes(self, searchString, position) + if not position then + position = 1 + else + position = position + 1 + end + local index = string.find(self, searchString, position, true) + return index ~= nil +end + +local Error, RangeError, ReferenceError, SyntaxError, TypeError, URIError +do + local function getErrorStack(self, constructor) + if debug == nil then + return nil + end + local level = 1 + while true do + local info = debug.getinfo(level, "f") + level = level + 1 + if not info then + level = 1 + break + elseif info.func == constructor then + break + end + end + if __TS__StringIncludes(_VERSION, "Lua 5.0") then + return debug.traceback(("[Level " .. tostring(level)) .. "]") + else + return debug.traceback(nil, level) + end + end + local function wrapErrorToString(self, getDescription) + return function(self) + local description = getDescription(self) + local caller = debug.getinfo(3, "f") + local isClassicLua = __TS__StringIncludes(_VERSION, "Lua 5.0") or _VERSION == "Lua 5.1" + if isClassicLua or caller and caller.func ~= error then + return description + else + return (description .. "\n") .. tostring(self.stack) + end + end + end + local function initErrorClass(self, Type, name) + Type.name = name + return setmetatable( + Type, + {__call = function(____, _self, message) return __TS__New(Type, message) end} + ) + end + local ____initErrorClass_1 = initErrorClass + local ____class_0 = __TS__Class() + ____class_0.name = "" + function ____class_0.prototype.____constructor(self, message) + if message == nil then + message = "" + end + self.message = message + self.name = "Error" + self.stack = getErrorStack(nil, self.constructor.new) + local metatable = getmetatable(self) + if metatable and not metatable.__errorToStringPatched then + metatable.__errorToStringPatched = true + metatable.__tostring = wrapErrorToString(nil, metatable.__tostring) + end + end + function ____class_0.prototype.__tostring(self) + return self.message ~= "" and (self.name .. ": ") .. self.message or self.name + end + Error = ____initErrorClass_1(nil, ____class_0, "Error") + local function createErrorClass(self, name) + local ____initErrorClass_3 = initErrorClass + local ____class_2 = __TS__Class() + ____class_2.name = ____class_2.name + __TS__ClassExtends(____class_2, Error) + function ____class_2.prototype.____constructor(self, ...) + ____class_2.____super.prototype.____constructor(self, ...) + self.name = name + end + return ____initErrorClass_3(nil, ____class_2, name) + end + RangeError = createErrorClass(nil, "RangeError") + ReferenceError = createErrorClass(nil, "ReferenceError") + SyntaxError = createErrorClass(nil, "SyntaxError") + TypeError = createErrorClass(nil, "TypeError") + URIError = createErrorClass(nil, "URIError") +end + +local function __TS__ObjectGetOwnPropertyDescriptors(object) + local metatable = getmetatable(object) + if not metatable then + return {} + end + return rawget(metatable, "_descriptors") or ({}) +end + +local function __TS__Delete(target, key) + local descriptors = __TS__ObjectGetOwnPropertyDescriptors(target) + local descriptor = descriptors[key] + if descriptor then + if not descriptor.configurable then + error( + __TS__New( + TypeError, + ((("Cannot delete property " .. tostring(key)) .. " of ") .. tostring(target)) .. "." + ), + 0 + ) + end + descriptors[key] = nil + return true + end + target[key] = nil + return true +end + +local function __TS__StringAccess(self, index) + if index >= 0 and index < #self then + return string.sub(self, index + 1, index + 1) + end +end + +local function __TS__DelegatedYield(iterable) + if type(iterable) == "string" then + for index = 0, #iterable - 1 do + coroutine.yield(__TS__StringAccess(iterable, index)) + end + elseif iterable.____coroutine ~= nil then + local co = iterable.____coroutine + while true do + local status, value = coroutine.resume(co) + if not status then + error(value, 0) + end + if coroutine.status(co) == "dead" then + return value + else + coroutine.yield(value) + end + end + elseif iterable[Symbol.iterator] then + local iterator = iterable[Symbol.iterator](iterable) + while true do + local result = iterator:next() + if result.done then + return result.value + else + coroutine.yield(result.value) + end + end + else + for ____, value in ipairs(iterable) do + coroutine.yield(value) + end + end +end + +local function __TS__FunctionBind(fn, ...) + local boundArgs = {...} + return function(____, ...) + local args = {...} + __TS__ArrayUnshift( + args, + __TS__Unpack(boundArgs) + ) + return fn(__TS__Unpack(args)) + end +end + +local __TS__Generator +do + local function generatorIterator(self) + return self + end + local function generatorNext(self, ...) + local co = self.____coroutine + if coroutine.status(co) == "dead" then + return {done = true} + end + local status, value = coroutine.resume(co, ...) + if not status then + error(value, 0) + end + return { + value = value, + done = coroutine.status(co) == "dead" + } + end + function __TS__Generator(fn) + return function(...) + local args = {...} + local argsLength = __TS__CountVarargs(...) + return { + ____coroutine = coroutine.create(function() return fn(__TS__Unpack(args, 1, argsLength)) end), + [Symbol.iterator] = generatorIterator, + next = generatorNext + } + end + end +end + +local function __TS__InstanceOfObject(value) + local valueType = type(value) + return valueType == "table" or valueType == "function" +end + +local function __TS__LuaIteratorSpread(self, state, firstKey) + local results = {} + local key, value = self(state, firstKey) + while key do + results[#results + 1] = {key, value} + key, value = self(state, key) + end + return __TS__Unpack(results) +end + +local Map +do + Map = __TS__Class() + Map.name = "Map" + function Map.prototype.____constructor(self, entries) + self[Symbol.toStringTag] = "Map" + self.items = {} + self.size = 0 + self.nextKey = {} + self.previousKey = {} + if entries == nil then + return + end + local iterable = entries + if iterable[Symbol.iterator] then + local iterator = iterable[Symbol.iterator](iterable) + while true do + local result = iterator:next() + if result.done then + break + end + local value = result.value + self:set(value[1], value[2]) + end + else + local array = entries + for ____, kvp in ipairs(array) do + self:set(kvp[1], kvp[2]) + end + end + end + function Map.prototype.clear(self) + self.items = {} + self.nextKey = {} + self.previousKey = {} + self.firstKey = nil + self.lastKey = nil + self.size = 0 + end + function Map.prototype.delete(self, key) + local contains = self:has(key) + if contains then + self.size = self.size - 1 + local next = self.nextKey[key] + local previous = self.previousKey[key] + if next ~= nil and previous ~= nil then + self.nextKey[previous] = next + self.previousKey[next] = previous + elseif next ~= nil then + self.firstKey = next + self.previousKey[next] = nil + elseif previous ~= nil then + self.lastKey = previous + self.nextKey[previous] = nil + else + self.firstKey = nil + self.lastKey = nil + end + self.nextKey[key] = nil + self.previousKey[key] = nil + end + self.items[key] = nil + return contains + end + function Map.prototype.forEach(self, callback) + for ____, key in __TS__Iterator(self:keys()) do + callback(nil, self.items[key], key, self) + end + end + function Map.prototype.get(self, key) + return self.items[key] + end + function Map.prototype.has(self, key) + return self.nextKey[key] ~= nil or self.lastKey == key + end + function Map.prototype.set(self, key, value) + local isNewValue = not self:has(key) + if isNewValue then + self.size = self.size + 1 + end + self.items[key] = value + if self.firstKey == nil then + self.firstKey = key + self.lastKey = key + elseif isNewValue then + self.nextKey[self.lastKey] = key + self.previousKey[key] = self.lastKey + self.lastKey = key + end + return self + end + Map.prototype[Symbol.iterator] = function(self) + return self:entries() + end + function Map.prototype.entries(self) + local items = self.items + local nextKey = self.nextKey + local key = self.firstKey + return { + [Symbol.iterator] = function(self) + return self + end, + next = function(self) + local result = {done = not key, value = {key, items[key]}} + key = nextKey[key] + return result + end + } + end + function Map.prototype.keys(self) + local nextKey = self.nextKey + local key = self.firstKey + return { + [Symbol.iterator] = function(self) + return self + end, + next = function(self) + local result = {done = not key, value = key} + key = nextKey[key] + return result + end + } + end + function Map.prototype.values(self) + local items = self.items + local nextKey = self.nextKey + local key = self.firstKey + return { + [Symbol.iterator] = function(self) + return self + end, + next = function(self) + local result = {done = not key, value = items[key]} + key = nextKey[key] + return result + end + } + end + Map[Symbol.species] = Map +end + +local function __TS__MapGroupBy(items, keySelector) + local result = __TS__New(Map) + local i = 0 + for ____, item in __TS__Iterator(items) do + local key = keySelector(nil, item, i) + if result:has(key) then + local ____temp_0 = result:get(key) + ____temp_0[#____temp_0 + 1] = item + else + result:set(key, {item}) + end + i = i + 1 + end + return result +end + +local __TS__Match = string.match + +local __TS__MathAtan2 = math.atan2 or math.atan + +local __TS__MathModf = math.modf + +local function __TS__MathSign(val) + if val > 0 then + return 1 + elseif val < 0 then + return -1 + end + return 0 +end + +local function __TS__Number(value) + local valueType = type(value) + if valueType == "number" then + return value + elseif valueType == "string" then + local numberValue = tonumber(value) + if numberValue then + return numberValue + end + if value == "Infinity" then + return math.huge + end + if value == "-Infinity" then + return -math.huge + end + local stringWithoutSpaces = string.gsub(value, "%s", "") + if stringWithoutSpaces == "" then + return 0 + end + return 0 / 0 + elseif valueType == "boolean" then + return value and 1 or 0 + else + return 0 / 0 + end +end + +local function __TS__NumberIsFinite(value) + return type(value) == "number" and value == value and value ~= math.huge and value ~= -math.huge +end + +local function __TS__NumberIsInteger(value) + return __TS__NumberIsFinite(value) and math.floor(value) == value +end + +local function __TS__NumberIsNaN(value) + return value ~= value +end + +local function __TS__StringSubstring(self, start, ____end) + if ____end ~= ____end then + ____end = 0 + end + if ____end ~= nil and start > ____end then + start, ____end = ____end, start + end + if start >= 0 then + start = start + 1 + else + start = 1 + end + if ____end ~= nil and ____end < 0 then + ____end = 0 + end + return string.sub(self, start, ____end) +end + +local __TS__ParseInt +do + local parseIntBasePattern = "0123456789aAbBcCdDeEfFgGhHiIjJkKlLmMnNoOpPqQrRsStTvVwWxXyYzZ" + function __TS__ParseInt(numberString, base) + if base == nil then + base = 10 + local hexMatch = __TS__Match(numberString, "^%s*-?0[xX]") + if hexMatch ~= nil then + base = 16 + numberString = (__TS__Match(hexMatch, "-")) and "-" .. __TS__StringSubstring(numberString, #hexMatch) or __TS__StringSubstring(numberString, #hexMatch) + end + end + if base < 2 or base > 36 then + return 0 / 0 + end + local allowedDigits = base <= 10 and __TS__StringSubstring(parseIntBasePattern, 0, base) or __TS__StringSubstring(parseIntBasePattern, 0, 10 + 2 * (base - 10)) + local pattern = ("^%s*(-?[" .. allowedDigits) .. "]*)" + local number = tonumber((__TS__Match(numberString, pattern)), base) + if number == nil then + return 0 / 0 + end + if number >= 0 then + return math.floor(number) + else + return math.ceil(number) + end + end +end + +local function __TS__ParseFloat(numberString) + local infinityMatch = __TS__Match(numberString, "^%s*(-?Infinity)") + if infinityMatch ~= nil then + return __TS__StringAccess(infinityMatch, 0) == "-" and -math.huge or math.huge + end + local number = tonumber((__TS__Match(numberString, "^%s*(-?%d+%.?%d*)"))) + return number or 0 / 0 +end + +local __TS__NumberToString +do + local radixChars = "0123456789abcdefghijklmnopqrstuvwxyz" + function __TS__NumberToString(self, radix) + if radix == nil or radix == 10 or self == math.huge or self == -math.huge or self ~= self then + return tostring(self) + end + radix = math.floor(radix) + if radix < 2 or radix > 36 then + error("toString() radix argument must be between 2 and 36", 0) + end + local integer, fraction = __TS__MathModf(math.abs(self)) + local result = "" + if radix == 8 then + result = string.format("%o", integer) + elseif radix == 16 then + result = string.format("%x", integer) + else + repeat + do + result = __TS__StringAccess(radixChars, integer % radix) .. result + integer = math.floor(integer / radix) + end + until not (integer ~= 0) + end + if fraction ~= 0 then + result = result .. "." + local delta = 1e-16 + repeat + do + fraction = fraction * radix + delta = delta * radix + local digit = math.floor(fraction) + result = result .. __TS__StringAccess(radixChars, digit) + fraction = fraction - digit + end + until not (fraction >= delta) + end + if self < 0 then + result = "-" .. result + end + return result + end +end + +local function __TS__NumberToFixed(self, fractionDigits) + if math.abs(self) >= 1e+21 or self ~= self then + return tostring(self) + end + local f = math.floor(fractionDigits or 0) + if f < 0 or f > 99 then + error("toFixed() digits argument must be between 0 and 99", 0) + end + return string.format( + ("%." .. tostring(f)) .. "f", + self + ) +end + +local function __TS__ObjectDefineProperty(target, key, desc) + local luaKey = type(key) == "number" and key + 1 or key + local value = rawget(target, luaKey) + local hasGetterOrSetter = desc.get ~= nil or desc.set ~= nil + local descriptor + if hasGetterOrSetter then + if value ~= nil then + error( + "Cannot redefine property: " .. tostring(key), + 0 + ) + end + descriptor = desc + else + local valueExists = value ~= nil + local ____desc_set_4 = desc.set + local ____desc_get_5 = desc.get + local ____temp_0 + if desc.configurable ~= nil then + ____temp_0 = desc.configurable + else + ____temp_0 = valueExists + end + local ____temp_1 + if desc.enumerable ~= nil then + ____temp_1 = desc.enumerable + else + ____temp_1 = valueExists + end + local ____temp_2 + if desc.writable ~= nil then + ____temp_2 = desc.writable + else + ____temp_2 = valueExists + end + local ____temp_3 + if desc.value ~= nil then + ____temp_3 = desc.value + else + ____temp_3 = value + end + descriptor = { + set = ____desc_set_4, + get = ____desc_get_5, + configurable = ____temp_0, + enumerable = ____temp_1, + writable = ____temp_2, + value = ____temp_3 + } + end + __TS__SetDescriptor(target, luaKey, descriptor) + return target +end + +local function __TS__ObjectEntries(obj) + local result = {} + local len = 0 + for key in pairs(obj) do + len = len + 1 + result[len] = {key, obj[key]} + end + return result +end + +local function __TS__ObjectFromEntries(entries) + local obj = {} + local iterable = entries + if iterable[Symbol.iterator] then + local iterator = iterable[Symbol.iterator](iterable) + while true do + local result = iterator:next() + if result.done then + break + end + local value = result.value + obj[value[1]] = value[2] + end + else + for ____, entry in ipairs(entries) do + obj[entry[1]] = entry[2] + end + end + return obj +end + +local function __TS__ObjectGroupBy(items, keySelector) + local result = {} + local i = 0 + for ____, item in __TS__Iterator(items) do + local key = keySelector(nil, item, i) + if result[key] ~= nil then + local ____result_key_0 = result[key] + ____result_key_0[#____result_key_0 + 1] = item + else + result[key] = {item} + end + i = i + 1 + end + return result +end + +local function __TS__ObjectKeys(obj) + local result = {} + local len = 0 + for key in pairs(obj) do + len = len + 1 + result[len] = key + end + return result +end + +local function __TS__ObjectRest(target, usedProperties) + local result = {} + for property in pairs(target) do + if not usedProperties[property] then + result[property] = target[property] + end + end + return result +end + +local function __TS__ObjectValues(obj) + local result = {} + local len = 0 + for key in pairs(obj) do + len = len + 1 + result[len] = obj[key] + end + return result +end + +local function __TS__PromiseAll(iterable) + local results = {} + local toResolve = {} + local numToResolve = 0 + local i = 0 + for ____, item in __TS__Iterator(iterable) do + if __TS__InstanceOf(item, __TS__Promise) then + if item.state == 1 then + results[i + 1] = item.value + elseif item.state == 2 then + return __TS__Promise.reject(item.rejectionReason) + else + numToResolve = numToResolve + 1 + toResolve[i] = item + end + else + results[i + 1] = item + end + i = i + 1 + end + if numToResolve == 0 then + return __TS__Promise.resolve(results) + end + return __TS__New( + __TS__Promise, + function(____, resolve, reject) + for index, promise in pairs(toResolve) do + promise["then"]( + promise, + function(____, data) + results[index + 1] = data + numToResolve = numToResolve - 1 + if numToResolve == 0 then + resolve(nil, results) + end + end, + function(____, reason) + reject(nil, reason) + end + ) + end + end + ) +end + +local function __TS__PromiseAllSettled(iterable) + local results = {} + local toResolve = {} + local numToResolve = 0 + local i = 0 + for ____, item in __TS__Iterator(iterable) do + if __TS__InstanceOf(item, __TS__Promise) then + if item.state == 1 then + results[i + 1] = {status = "fulfilled", value = item.value} + elseif item.state == 2 then + results[i + 1] = {status = "rejected", reason = item.rejectionReason} + else + numToResolve = numToResolve + 1 + toResolve[i] = item + end + else + results[i + 1] = {status = "fulfilled", value = item} + end + i = i + 1 + end + if numToResolve == 0 then + return __TS__Promise.resolve(results) + end + return __TS__New( + __TS__Promise, + function(____, resolve) + for index, promise in pairs(toResolve) do + promise["then"]( + promise, + function(____, data) + results[index + 1] = {status = "fulfilled", value = data} + numToResolve = numToResolve - 1 + if numToResolve == 0 then + resolve(nil, results) + end + end, + function(____, reason) + results[index + 1] = {status = "rejected", reason = reason} + numToResolve = numToResolve - 1 + if numToResolve == 0 then + resolve(nil, results) + end + end + ) + end + end + ) +end + +local function __TS__PromiseAny(iterable) + local rejections = {} + local pending = {} + for ____, item in __TS__Iterator(iterable) do + if __TS__InstanceOf(item, __TS__Promise) then + if item.state == 1 then + return __TS__Promise.resolve(item.value) + elseif item.state == 2 then + rejections[#rejections + 1] = item.rejectionReason + else + pending[#pending + 1] = item + end + else + return __TS__Promise.resolve(item) + end + end + if #pending == 0 then + return __TS__Promise.reject("No promises to resolve with .any()") + end + local numResolved = 0 + return __TS__New( + __TS__Promise, + function(____, resolve, reject) + for ____, promise in ipairs(pending) do + promise["then"]( + promise, + function(____, data) + resolve(nil, data) + end, + function(____, reason) + rejections[#rejections + 1] = reason + numResolved = numResolved + 1 + if numResolved == #pending then + reject(nil, {name = "AggregateError", message = "All Promises rejected", errors = rejections}) + end + end + ) + end + end + ) +end + +local function __TS__PromiseRace(iterable) + local pending = {} + for ____, item in __TS__Iterator(iterable) do + if __TS__InstanceOf(item, __TS__Promise) then + if item.state == 1 then + return __TS__Promise.resolve(item.value) + elseif item.state == 2 then + return __TS__Promise.reject(item.rejectionReason) + else + pending[#pending + 1] = item + end + else + return __TS__Promise.resolve(item) + end + end + return __TS__New( + __TS__Promise, + function(____, resolve, reject) + for ____, promise in ipairs(pending) do + promise["then"]( + promise, + function(____, value) return resolve(nil, value) end, + function(____, reason) return reject(nil, reason) end + ) + end + end + ) +end + +local Set +do + Set = __TS__Class() + Set.name = "Set" + function Set.prototype.____constructor(self, values) + self[Symbol.toStringTag] = "Set" + self.size = 0 + self.nextKey = {} + self.previousKey = {} + if values == nil then + return + end + local iterable = values + if iterable[Symbol.iterator] then + local iterator = iterable[Symbol.iterator](iterable) + while true do + local result = iterator:next() + if result.done then + break + end + self:add(result.value) + end + else + local array = values + for ____, value in ipairs(array) do + self:add(value) + end + end + end + function Set.prototype.add(self, value) + local isNewValue = not self:has(value) + if isNewValue then + self.size = self.size + 1 + end + if self.firstKey == nil then + self.firstKey = value + self.lastKey = value + elseif isNewValue then + self.nextKey[self.lastKey] = value + self.previousKey[value] = self.lastKey + self.lastKey = value + end + return self + end + function Set.prototype.clear(self) + self.nextKey = {} + self.previousKey = {} + self.firstKey = nil + self.lastKey = nil + self.size = 0 + end + function Set.prototype.delete(self, value) + local contains = self:has(value) + if contains then + self.size = self.size - 1 + local next = self.nextKey[value] + local previous = self.previousKey[value] + if next ~= nil and previous ~= nil then + self.nextKey[previous] = next + self.previousKey[next] = previous + elseif next ~= nil then + self.firstKey = next + self.previousKey[next] = nil + elseif previous ~= nil then + self.lastKey = previous + self.nextKey[previous] = nil + else + self.firstKey = nil + self.lastKey = nil + end + self.nextKey[value] = nil + self.previousKey[value] = nil + end + return contains + end + function Set.prototype.forEach(self, callback) + for ____, key in __TS__Iterator(self:keys()) do + callback(nil, key, key, self) + end + end + function Set.prototype.has(self, value) + return self.nextKey[value] ~= nil or self.lastKey == value + end + Set.prototype[Symbol.iterator] = function(self) + return self:values() + end + function Set.prototype.entries(self) + local nextKey = self.nextKey + local key = self.firstKey + return { + [Symbol.iterator] = function(self) + return self + end, + next = function(self) + local result = {done = not key, value = {key, key}} + key = nextKey[key] + return result + end + } + end + function Set.prototype.keys(self) + local nextKey = self.nextKey + local key = self.firstKey + return { + [Symbol.iterator] = function(self) + return self + end, + next = function(self) + local result = {done = not key, value = key} + key = nextKey[key] + return result + end + } + end + function Set.prototype.values(self) + local nextKey = self.nextKey + local key = self.firstKey + return { + [Symbol.iterator] = function(self) + return self + end, + next = function(self) + local result = {done = not key, value = key} + key = nextKey[key] + return result + end + } + end + function Set.prototype.union(self, other) + local result = __TS__New(Set, self) + for ____, item in __TS__Iterator(other) do + result:add(item) + end + return result + end + function Set.prototype.intersection(self, other) + local result = __TS__New(Set) + for ____, item in __TS__Iterator(self) do + if other:has(item) then + result:add(item) + end + end + return result + end + function Set.prototype.difference(self, other) + local result = __TS__New(Set, self) + for ____, item in __TS__Iterator(other) do + result:delete(item) + end + return result + end + function Set.prototype.symmetricDifference(self, other) + local result = __TS__New(Set, self) + for ____, item in __TS__Iterator(other) do + if self:has(item) then + result:delete(item) + else + result:add(item) + end + end + return result + end + function Set.prototype.isSubsetOf(self, other) + for ____, item in __TS__Iterator(self) do + if not other:has(item) then + return false + end + end + return true + end + function Set.prototype.isSupersetOf(self, other) + for ____, item in __TS__Iterator(other) do + if not self:has(item) then + return false + end + end + return true + end + function Set.prototype.isDisjointFrom(self, other) + for ____, item in __TS__Iterator(self) do + if other:has(item) then + return false + end + end + return true + end + Set[Symbol.species] = Set +end + +local function __TS__SparseArrayNew(...) + local sparseArray = {...} + sparseArray.sparseLength = __TS__CountVarargs(...) + return sparseArray +end + +local function __TS__SparseArrayPush(sparseArray, ...) + local args = {...} + local argsLen = __TS__CountVarargs(...) + local listLen = sparseArray.sparseLength + for i = 1, argsLen do + sparseArray[listLen + i] = args[i] + end + sparseArray.sparseLength = listLen + argsLen +end + +local function __TS__SparseArraySpread(sparseArray) + local _unpack = unpack or table.unpack + return _unpack(sparseArray, 1, sparseArray.sparseLength) +end + +local WeakMap +do + WeakMap = __TS__Class() + WeakMap.name = "WeakMap" + function WeakMap.prototype.____constructor(self, entries) + self[Symbol.toStringTag] = "WeakMap" + self.items = {} + setmetatable(self.items, {__mode = "k"}) + if entries == nil then + return + end + local iterable = entries + if iterable[Symbol.iterator] then + local iterator = iterable[Symbol.iterator](iterable) + while true do + local result = iterator:next() + if result.done then + break + end + local value = result.value + self.items[value[1]] = value[2] + end + else + for ____, kvp in ipairs(entries) do + self.items[kvp[1]] = kvp[2] + end + end + end + function WeakMap.prototype.delete(self, key) + local contains = self:has(key) + self.items[key] = nil + return contains + end + function WeakMap.prototype.get(self, key) + return self.items[key] + end + function WeakMap.prototype.has(self, key) + return self.items[key] ~= nil + end + function WeakMap.prototype.set(self, key, value) + self.items[key] = value + return self + end + WeakMap[Symbol.species] = WeakMap +end + +local WeakSet +do + WeakSet = __TS__Class() + WeakSet.name = "WeakSet" + function WeakSet.prototype.____constructor(self, values) + self[Symbol.toStringTag] = "WeakSet" + self.items = {} + setmetatable(self.items, {__mode = "k"}) + if values == nil then + return + end + local iterable = values + if iterable[Symbol.iterator] then + local iterator = iterable[Symbol.iterator](iterable) + while true do + local result = iterator:next() + if result.done then + break + end + self.items[result.value] = true + end + else + for ____, value in ipairs(values) do + self.items[value] = true + end + end + end + function WeakSet.prototype.add(self, value) + self.items[value] = true + return self + end + function WeakSet.prototype.delete(self, value) + local contains = self:has(value) + self.items[value] = nil + return contains + end + function WeakSet.prototype.has(self, value) + return self.items[value] == true + end + WeakSet[Symbol.species] = WeakSet +end + +local function __TS__SourceMapTraceBack(fileName, sourceMap) + _G.__TS__sourcemap = _G.__TS__sourcemap or ({}) + _G.__TS__sourcemap[fileName] = sourceMap + if _G.__TS__originalTraceback == nil then + local originalTraceback = debug.traceback + _G.__TS__originalTraceback = originalTraceback + debug.traceback = function(thread, message, level) + local trace + if thread == nil and message == nil and level == nil then + trace = originalTraceback() + elseif __TS__StringIncludes(_VERSION, "Lua 5.0") then + trace = originalTraceback((("[Level " .. tostring(level)) .. "] ") .. tostring(message)) + else + trace = originalTraceback(thread, message, level) + end + if type(trace) ~= "string" then + return trace + end + local function replacer(____, file, srcFile, line) + local fileSourceMap = _G.__TS__sourcemap[file] + if fileSourceMap ~= nil and fileSourceMap[line] ~= nil then + local data = fileSourceMap[line] + if type(data) == "number" then + return (srcFile .. ":") .. tostring(data) + end + return (data.file .. ":") .. tostring(data.line) + end + return (file .. ":") .. line + end + local result = string.gsub( + trace, + "(%S+)%.lua:(%d+)", + function(file, line) return replacer(nil, file .. ".lua", file .. ".ts", line) end + ) + local function stringReplacer(____, file, line) + local fileSourceMap = _G.__TS__sourcemap[file] + if fileSourceMap ~= nil and fileSourceMap[line] ~= nil then + local chunkName = (__TS__Match(file, "%[string \"([^\"]+)\"%]")) + local sourceName = string.gsub(chunkName, ".lua$", ".ts") + local data = fileSourceMap[line] + if type(data) == "number" then + return (sourceName .. ":") .. tostring(data) + end + return (data.file .. ":") .. tostring(data.line) + end + return (file .. ":") .. line + end + result = string.gsub( + result, + "(%[string \"[^\"]+\"%]):(%d+)", + function(file, line) return stringReplacer(nil, file, line) end + ) + return result + end + end +end + +local function __TS__Spread(iterable) + local arr = {} + if type(iterable) == "string" then + for i = 0, #iterable - 1 do + arr[i + 1] = __TS__StringAccess(iterable, i) + end + else + local len = 0 + for ____, item in __TS__Iterator(iterable) do + len = len + 1 + arr[len] = item + end + end + return __TS__Unpack(arr) +end + +local function __TS__StringCharAt(self, pos) + if pos ~= pos then + pos = 0 + end + if pos < 0 then + return "" + end + return string.sub(self, pos + 1, pos + 1) +end + +local function __TS__StringCharCodeAt(self, index) + if index ~= index then + index = 0 + end + if index < 0 then + return 0 / 0 + end + return string.byte(self, index + 1) or 0 / 0 +end + +local function __TS__StringEndsWith(self, searchString, endPosition) + if endPosition == nil or endPosition > #self then + endPosition = #self + end + return string.sub(self, endPosition - #searchString + 1, endPosition) == searchString +end + +local function __TS__StringPadEnd(self, maxLength, fillString) + if fillString == nil then + fillString = " " + end + if maxLength ~= maxLength then + maxLength = 0 + end + if maxLength == -math.huge or maxLength == math.huge then + error("Invalid string length", 0) + end + if #self >= maxLength or #fillString == 0 then + return self + end + maxLength = maxLength - #self + if maxLength > #fillString then + fillString = fillString .. string.rep( + fillString, + math.floor(maxLength / #fillString) + ) + end + return self .. string.sub( + fillString, + 1, + math.floor(maxLength) + ) +end + +local function __TS__StringPadStart(self, maxLength, fillString) + if fillString == nil then + fillString = " " + end + if maxLength ~= maxLength then + maxLength = 0 + end + if maxLength == -math.huge or maxLength == math.huge then + error("Invalid string length", 0) + end + if #self >= maxLength or #fillString == 0 then + return self + end + maxLength = maxLength - #self + if maxLength > #fillString then + fillString = fillString .. string.rep( + fillString, + math.floor(maxLength / #fillString) + ) + end + return string.sub( + fillString, + 1, + math.floor(maxLength) + ) .. self +end + +local __TS__StringReplace +do + local sub = string.sub + function __TS__StringReplace(source, searchValue, replaceValue) + local startPos, endPos = string.find(source, searchValue, nil, true) + if not startPos then + return source + end + local before = sub(source, 1, startPos - 1) + local replacement = type(replaceValue) == "string" and replaceValue or replaceValue(nil, searchValue, startPos - 1, source) + local after = sub(source, endPos + 1) + return (before .. replacement) .. after + end +end + +local __TS__StringSplit +do + local sub = string.sub + local find = string.find + function __TS__StringSplit(source, separator, limit) + if limit == nil then + limit = 4294967295 + end + if limit == 0 then + return {} + end + local result = {} + local resultIndex = 1 + if separator == nil or separator == "" then + for i = 1, #source do + result[resultIndex] = sub(source, i, i) + resultIndex = resultIndex + 1 + end + else + local currentPos = 1 + while resultIndex <= limit do + local startPos, endPos = find(source, separator, currentPos, true) + if not startPos then + break + end + result[resultIndex] = sub(source, currentPos, startPos - 1) + resultIndex = resultIndex + 1 + currentPos = endPos + 1 + end + if resultIndex <= limit then + result[resultIndex] = sub(source, currentPos) + end + end + return result + end +end + +local __TS__StringReplaceAll +do + local sub = string.sub + local find = string.find + function __TS__StringReplaceAll(source, searchValue, replaceValue) + if type(replaceValue) == "string" then + local concat = table.concat( + __TS__StringSplit(source, searchValue), + replaceValue + ) + if #searchValue == 0 then + return (replaceValue .. concat) .. replaceValue + end + return concat + end + local parts = {} + local partsIndex = 1 + if #searchValue == 0 then + parts[1] = replaceValue(nil, "", 0, source) + partsIndex = 2 + for i = 1, #source do + parts[partsIndex] = sub(source, i, i) + parts[partsIndex + 1] = replaceValue(nil, "", i, source) + partsIndex = partsIndex + 2 + end + else + local currentPos = 1 + while true do + local startPos, endPos = find(source, searchValue, currentPos, true) + if not startPos then + break + end + parts[partsIndex] = sub(source, currentPos, startPos - 1) + parts[partsIndex + 1] = replaceValue(nil, searchValue, startPos - 1, source) + partsIndex = partsIndex + 2 + currentPos = endPos + 1 + end + parts[partsIndex] = sub(source, currentPos) + end + return table.concat(parts) + end +end + +local function __TS__StringSlice(self, start, ____end) + if start == nil or start ~= start then + start = 0 + end + if ____end ~= ____end then + ____end = 0 + end + if start >= 0 then + start = start + 1 + end + if ____end ~= nil and ____end < 0 then + ____end = ____end - 1 + end + return string.sub(self, start, ____end) +end + +local function __TS__StringStartsWith(self, searchString, position) + if position == nil or position < 0 then + position = 0 + end + return string.sub(self, position + 1, #searchString + position) == searchString +end + +local function __TS__StringSubstr(self, from, length) + if from ~= from then + from = 0 + end + if length ~= nil then + if length ~= length or length <= 0 then + return "" + end + length = length + from + end + if from >= 0 then + from = from + 1 + end + return string.sub(self, from, length) +end + +local function __TS__StringTrim(self) + local result = string.gsub(self, "^[%s ]*(.-)[%s ]*$", "%1") + return result +end + +local function __TS__StringTrimEnd(self) + local result = string.gsub(self, "[%s ]*$", "") + return result +end + +local function __TS__StringTrimStart(self) + local result = string.gsub(self, "^[%s ]*", "") + return result +end + +local __TS__SymbolRegistryFor, __TS__SymbolRegistryKeyFor +do + local symbolRegistry = {} + function __TS__SymbolRegistryFor(key) + if not symbolRegistry[key] then + symbolRegistry[key] = __TS__Symbol(key) + end + return symbolRegistry[key] + end + function __TS__SymbolRegistryKeyFor(sym) + for key in pairs(symbolRegistry) do + if symbolRegistry[key] == sym then + return key + end + end + return nil + end +end + +local function __TS__TypeOf(value) + local luaType = type(value) + if luaType == "table" then + return "object" + elseif luaType == "nil" then + return "undefined" + else + return luaType + end +end + +local function __TS__Using(self, cb, ...) + local args = {...} + local thrownError + local ok, result = xpcall( + function() return cb( + nil, + __TS__Unpack(args) + ) end, + function(err) + thrownError = err + return thrownError + end + ) + local argArray = {__TS__Unpack(args)} + do + local i = #argArray - 1 + while i >= 0 do + local ____self_0 = argArray[i + 1] + ____self_0[Symbol.dispose](____self_0) + i = i - 1 + end + end + if not ok then + error(thrownError, 0) + end + return result +end + +local function __TS__UsingAsync(self, cb, ...) + local args = {...} + return __TS__AsyncAwaiter(function(____awaiter_resolve) + local thrownError + local ok, result = xpcall( + function() return cb( + nil, + __TS__Unpack(args) + ) end, + function(err) + thrownError = err + return thrownError + end + ) + local argArray = {__TS__Unpack(args)} + do + local i = #argArray - 1 + while i >= 0 do + if argArray[i + 1][Symbol.dispose] ~= nil then + local ____self_0 = argArray[i + 1] + ____self_0[Symbol.dispose](____self_0) + end + if argArray[i + 1][Symbol.asyncDispose] ~= nil then + local ____self_1 = argArray[i + 1] + __TS__Await(____self_1[Symbol.asyncDispose](____self_1)) + end + i = i - 1 + end + end + if not ok then + error(thrownError, 0) + end + return ____awaiter_resolve(nil, result) + end) +end + +return { + __TS__ArrayAt = __TS__ArrayAt, + __TS__ArrayConcat = __TS__ArrayConcat, + __TS__ArrayEntries = __TS__ArrayEntries, + __TS__ArrayEvery = __TS__ArrayEvery, + __TS__ArrayFill = __TS__ArrayFill, + __TS__ArrayFilter = __TS__ArrayFilter, + __TS__ArrayForEach = __TS__ArrayForEach, + __TS__ArrayFind = __TS__ArrayFind, + __TS__ArrayFindIndex = __TS__ArrayFindIndex, + __TS__ArrayFrom = __TS__ArrayFrom, + __TS__ArrayIncludes = __TS__ArrayIncludes, + __TS__ArrayIndexOf = __TS__ArrayIndexOf, + __TS__ArrayIsArray = __TS__ArrayIsArray, + __TS__ArrayJoin = __TS__ArrayJoin, + __TS__ArrayMap = __TS__ArrayMap, + __TS__ArrayPush = __TS__ArrayPush, + __TS__ArrayPushArray = __TS__ArrayPushArray, + __TS__ArrayReduce = __TS__ArrayReduce, + __TS__ArrayReduceRight = __TS__ArrayReduceRight, + __TS__ArrayReverse = __TS__ArrayReverse, + __TS__ArrayUnshift = __TS__ArrayUnshift, + __TS__ArraySort = __TS__ArraySort, + __TS__ArraySlice = __TS__ArraySlice, + __TS__ArraySome = __TS__ArraySome, + __TS__ArraySplice = __TS__ArraySplice, + __TS__ArrayToObject = __TS__ArrayToObject, + __TS__ArrayFlat = __TS__ArrayFlat, + __TS__ArrayFlatMap = __TS__ArrayFlatMap, + __TS__ArraySetLength = __TS__ArraySetLength, + __TS__ArrayToReversed = __TS__ArrayToReversed, + __TS__ArrayToSorted = __TS__ArrayToSorted, + __TS__ArrayToSpliced = __TS__ArrayToSpliced, + __TS__ArrayWith = __TS__ArrayWith, + __TS__AsyncAwaiter = __TS__AsyncAwaiter, + __TS__Await = __TS__Await, + __TS__Class = __TS__Class, + __TS__ClassExtends = __TS__ClassExtends, + __TS__CloneDescriptor = __TS__CloneDescriptor, + __TS__CountVarargs = __TS__CountVarargs, + __TS__Decorate = __TS__Decorate, + __TS__DecorateLegacy = __TS__DecorateLegacy, + __TS__DecorateParam = __TS__DecorateParam, + __TS__Delete = __TS__Delete, + __TS__DelegatedYield = __TS__DelegatedYield, + __TS__DescriptorGet = __TS__DescriptorGet, + __TS__DescriptorSet = __TS__DescriptorSet, + Error = Error, + RangeError = RangeError, + ReferenceError = ReferenceError, + SyntaxError = SyntaxError, + TypeError = TypeError, + URIError = URIError, + __TS__FunctionBind = __TS__FunctionBind, + __TS__Generator = __TS__Generator, + __TS__InstanceOf = __TS__InstanceOf, + __TS__InstanceOfObject = __TS__InstanceOfObject, + __TS__Iterator = __TS__Iterator, + __TS__LuaIteratorSpread = __TS__LuaIteratorSpread, + Map = Map, + __TS__MapGroupBy = __TS__MapGroupBy, + __TS__Match = __TS__Match, + __TS__MathAtan2 = __TS__MathAtan2, + __TS__MathModf = __TS__MathModf, + __TS__MathSign = __TS__MathSign, + __TS__New = __TS__New, + __TS__Number = __TS__Number, + __TS__NumberIsFinite = __TS__NumberIsFinite, + __TS__NumberIsInteger = __TS__NumberIsInteger, + __TS__NumberIsNaN = __TS__NumberIsNaN, + __TS__ParseInt = __TS__ParseInt, + __TS__ParseFloat = __TS__ParseFloat, + __TS__NumberToString = __TS__NumberToString, + __TS__NumberToFixed = __TS__NumberToFixed, + __TS__ObjectAssign = __TS__ObjectAssign, + __TS__ObjectDefineProperty = __TS__ObjectDefineProperty, + __TS__ObjectEntries = __TS__ObjectEntries, + __TS__ObjectFromEntries = __TS__ObjectFromEntries, + __TS__ObjectGetOwnPropertyDescriptor = __TS__ObjectGetOwnPropertyDescriptor, + __TS__ObjectGetOwnPropertyDescriptors = __TS__ObjectGetOwnPropertyDescriptors, + __TS__ObjectGroupBy = __TS__ObjectGroupBy, + __TS__ObjectKeys = __TS__ObjectKeys, + __TS__ObjectRest = __TS__ObjectRest, + __TS__ObjectValues = __TS__ObjectValues, + __TS__ParseFloat = __TS__ParseFloat, + __TS__ParseInt = __TS__ParseInt, + __TS__Promise = __TS__Promise, + __TS__PromiseAll = __TS__PromiseAll, + __TS__PromiseAllSettled = __TS__PromiseAllSettled, + __TS__PromiseAny = __TS__PromiseAny, + __TS__PromiseRace = __TS__PromiseRace, + Set = Set, + __TS__SetDescriptor = __TS__SetDescriptor, + __TS__SparseArrayNew = __TS__SparseArrayNew, + __TS__SparseArrayPush = __TS__SparseArrayPush, + __TS__SparseArraySpread = __TS__SparseArraySpread, + WeakMap = WeakMap, + WeakSet = WeakSet, + __TS__SourceMapTraceBack = __TS__SourceMapTraceBack, + __TS__Spread = __TS__Spread, + __TS__StringAccess = __TS__StringAccess, + __TS__StringCharAt = __TS__StringCharAt, + __TS__StringCharCodeAt = __TS__StringCharCodeAt, + __TS__StringEndsWith = __TS__StringEndsWith, + __TS__StringIncludes = __TS__StringIncludes, + __TS__StringPadEnd = __TS__StringPadEnd, + __TS__StringPadStart = __TS__StringPadStart, + __TS__StringReplace = __TS__StringReplace, + __TS__StringReplaceAll = __TS__StringReplaceAll, + __TS__StringSlice = __TS__StringSlice, + __TS__StringSplit = __TS__StringSplit, + __TS__StringStartsWith = __TS__StringStartsWith, + __TS__StringSubstr = __TS__StringSubstr, + __TS__StringSubstring = __TS__StringSubstring, + __TS__StringTrim = __TS__StringTrim, + __TS__StringTrimEnd = __TS__StringTrimEnd, + __TS__StringTrimStart = __TS__StringTrimStart, + __TS__Symbol = __TS__Symbol, + Symbol = Symbol, + __TS__SymbolRegistryFor = __TS__SymbolRegistryFor, + __TS__SymbolRegistryKeyFor = __TS__SymbolRegistryKeyFor, + __TS__TypeOf = __TS__TypeOf, + __TS__Unpack = __TS__Unpack, + __TS__Using = __TS__Using, + __TS__UsingAsync = __TS__UsingAsync +} diff --git a/scripts/vscripts/match_end_combat_stats.lua b/scripts/vscripts/match_end_combat_stats.lua new file mode 100644 index 0000000..34d18c1 --- /dev/null +++ b/scripts/vscripts/match_end_combat_stats.lua @@ -0,0 +1,116 @@ +local ____lualib = require("lualib_bundle") +local __TS__Class = ____lualib.__TS__Class +local Map = ____lualib.Map +local __TS__New = ____lualib.__TS__New +local __TS__Number = ____lualib.__TS__Number +local ____exports = {} +local ____get_true_hero_from_entity = require("utils.get_true_hero_from_entity") +local getTrueHeroFromEntity = ____get_true_hero_from_entity.getTrueHeroFromEntity +--- Единый учёт за матч: исх./вх. урон (SetDamageFilter, как defension) и лечение союзников (HealWithBattlePass). +-- Используется: экран итогов, квесты BP, отправка статистики в API (история матчей). +____exports.MatchEndCombatStats = __TS__Class() +local MatchEndCombatStats = ____exports.MatchEndCombatStats +MatchEndCombatStats.name = "MatchEndCombatStats" +MatchEndCombatStats.____file_path = "scripts/vscripts/match_end_combat_stats.lua" +function MatchEndCombatStats.prototype.____constructor(self) + self.incomingSumByPlayer = __TS__New(Map) + self.outgoingSumByPlayer = __TS__New(Map) + self.healToAlliesByPlayer = __TS__New(Map) + ListenToGameEvent( + "game_rules_state_change", + function() + if GameRules:State_Get() == DOTA_GAMERULES_STATE_GAME_IN_PROGRESS then + self:resetForNewMatch() + end + end, + nil + ) +end +function MatchEndCombatStats.getInstance(self) + if not ____exports.MatchEndCombatStats.instance then + ____exports.MatchEndCombatStats.instance = __TS__New(____exports.MatchEndCombatStats) + end + return ____exports.MatchEndCombatStats.instance +end +function MatchEndCombatStats.registerDamageFilter(self) + if not IsServer() or ____exports.MatchEndCombatStats.filterRegistered then + return + end + local inst = ____exports.MatchEndCombatStats:getInstance() + local mode = GameRules:GetGameModeEntity() + mode:SetDamageFilter( + function(____, event) return inst:onDamageFilter(event) end, + inst + ) + ____exports.MatchEndCombatStats.filterRegistered = true +end +function MatchEndCombatStats.prototype.onDamageFilter(self, event) + local dmg = __TS__Number(event.damage) or 0 + if dmg <= 0 then + return true + end + local ____event_entindex_attacker_const_0 = event.entindex_attacker_const + if ____event_entindex_attacker_const_0 == nil then + ____event_entindex_attacker_const_0 = event.entindex_attacker + end + local aIdx = ____event_entindex_attacker_const_0 + local ____event_entindex_victim_const_1 = event.entindex_victim_const + if ____event_entindex_victim_const_1 == nil then + ____event_entindex_victim_const_1 = event.entindex_victim + end + local vIdx = ____event_entindex_victim_const_1 + if aIdx ~= nil and aIdx ~= nil then + local attacker = EntIndexToHScript(aIdx) + local attackerHero = getTrueHeroFromEntity(nil, attacker) + if attackerHero then + local pid = attackerHero:GetPlayerOwnerID() + if pid ~= nil and pid >= 0 then + local prev = self.outgoingSumByPlayer:get(pid) or 0 + self.outgoingSumByPlayer:set(pid, prev + dmg) + end + end + end + if vIdx ~= nil and vIdx ~= nil then + local victim = EntIndexToHScript(vIdx) + if victim and IsValidEntity(victim) and victim:IsBaseNPC() then + local v = victim + if v:IsHero() then + local vpid = v:GetPlayerOwnerID() + if vpid ~= nil and vpid >= 0 then + local prev = self.incomingSumByPlayer:get(vpid) or 0 + self.incomingSumByPlayer:set(vpid, prev + dmg) + end + end + end + end + return true +end +function MatchEndCombatStats.prototype.resetForNewMatch(self) + self.incomingSumByPlayer:clear() + self.outgoingSumByPlayer:clear() + self.healToAlliesByPlayer:clear() +end +function MatchEndCombatStats.prototype.addHealToAllies(self, playerId, amount) + if amount <= 0 or playerId < 0 then + return + end + local prev = self.healToAlliesByPlayer:get(playerId) or 0 + self.healToAlliesByPlayer:set(playerId, prev + amount) +end +function MatchEndCombatStats.prototype.getHealToAlliesSum(self, playerId) + return math.floor(self.healToAlliesByPlayer:get(playerId) or 0) +end +function MatchEndCombatStats.prototype.getIncomingDamageSum(self, playerId) + return math.floor(self.incomingSumByPlayer:get(playerId) or 0) +end +function MatchEndCombatStats.prototype.getOutgoingDamageSum(self, playerId) + return math.floor(self.outgoingSumByPlayer:get(playerId) or 0) +end +function MatchEndCombatStats.prototype.getIncomingHurtEventSum(self, playerId) + return self:getIncomingDamageSum(playerId) +end +MatchEndCombatStats.filterRegistered = false +if IsServer() then + ____exports.MatchEndCombatStats:getInstance() +end +return ____exports diff --git a/scripts/vscripts/match_end_rewards.lua b/scripts/vscripts/match_end_rewards.lua new file mode 100644 index 0000000..ac247fc --- /dev/null +++ b/scripts/vscripts/match_end_rewards.lua @@ -0,0 +1,511 @@ +local ____lualib = require("lualib_bundle") +local __TS__ArrayMap = ____lualib.__TS__ArrayMap +local __TS__Class = ____lualib.__TS__Class +local ____exports = {} +local ____battle_pass_server = require("battle_pass_server") +local BattlePassServer = ____battle_pass_server.BattlePassServer +local ____difficulty_manager = require("difficulty_manager") +local Difficulty = ____difficulty_manager.Difficulty +local ____game_stats_tracker = require("game_stats_tracker") +local GameStatsTracker = ____game_stats_tracker.GameStatsTracker +local ____match_end_combat_stats = require("match_end_combat_stats") +local MatchEndCombatStats = ____match_end_combat_stats.MatchEndCombatStats +local ____store_manager = require("store_manager") +local StoreManager = ____store_manager.StoreManager +local ____ArsenalCatalog = require("arsenal.ArsenalCatalog") +local ARSENAL_ITEMS = ____ArsenalCatalog.ARSENAL_ITEMS +local ____ArsenalStats = require("arsenal.ArsenalStats") +local contractRewardMultToDropStatBias01 = ____ArsenalStats.contractRewardMultToDropStatBias01 +local ____ArsenalManager = require("arsenal.ArsenalManager") +local grantArsenalItemDetailed = ____ArsenalManager.grantArsenalItemDetailed +local flushArsenalInventoryToClientAndApi = ____ArsenalManager.flushArsenalInventoryToClientAndApi +local ____luck = require("utils.luck") +local rollLuckChance = ____luck.rollLuckChance +local getLuck = ____luck.getLuck +--- За каждые N единиц удачи героя — доп. предмет арсенала: на Death Sentence любое качество, иначе только epic и ниже. +local ARSENAL_BONUS_DROP_LUCK_PER_ITEM = 100 +local ARSENAL_FULL_RANDOM_QUALITIES = { + "common", + "rare", + "epic", + "legendary", + "mythic" +} +--- Бонус за удачу вне DS: только common / rare / epic. +local ARSENAL_LUCK_BONUS_CAP_EPIC_QUALITIES = {"common", "rare", "epic"} +--- Качество бонусного дропа за удачу (полный спектр только на смертельном приговоре). +local function rollArsenalLuckBonusDropQuality(self) + if Difficulty.leader == "death_sentence" then + return ARSENAL_FULL_RANDOM_QUALITIES[RandomInt(0, #ARSENAL_FULL_RANDOM_QUALITIES - 1) + 1] + end + return ARSENAL_LUCK_BONUS_CAP_EPIC_QUALITIES[RandomInt(0, #ARSENAL_LUCK_BONUS_CAP_EPIC_QUALITIES - 1) + 1] +end +--- Фиксированное число строк таблицы (слоты Radiant 0–3). +____exports.MATCH_END_PLAYER_SLOTS = 4 +--- Множитель к итоговой награде за победу, если у игрока куплен Battle Pass Premium (+50%). +local MATCH_END_PREMIUM_REWARD_MULT = 1.5 +--- Случайный шаблон из каталога (любой слот); редкость экземпляра задаётся отдельно при выдаче. +local function pickRandomArsenalTemplateItemName(self) + local n = #ARSENAL_ITEMS + if n <= 0 then + return nil + end + local idx = RandomInt(0, n - 1) + local ____opt_0 = ARSENAL_ITEMS[idx + 1] + return ____opt_0 and ____opt_0.itemName or nil +end +local function rollArsenalDropQuality(self, playerId) + local hero = PlayerResource:GetSelectedHeroEntity(playerId) + if Difficulty.leader == "easy" then + if hero and rollLuckChance(nil, hero, 0.12) then + return "rare" + end + return "common" + end + if Difficulty.leader == "normal" then + if hero and rollLuckChance(nil, hero, 0.3) then + return "rare" + end + return "common" + end + if Difficulty.leader == "hard" then + if hero and rollLuckChance(nil, hero, 0.18) then + return "epic" + end + return "rare" + end + if Difficulty.leader == "impossible" then + if hero and rollLuckChance(nil, hero, 0.3) then + return "legendary" + end + return "epic" + end + local snap = Difficulty:getWinningContractSnapshot() + local rarity = snap and snap.rarity or "epic" + local rewardMult = snap and snap.rewardMultiplier or Difficulty:getDeathSentenceContractRewardMultiplier() + local multBias = contractRewardMultToDropStatBias01(nil, rewardMult) + local mythicChance = 8 + if rarity == "common" then + mythicChance = 2 + end + if rarity == "rare" then + mythicChance = 4 + end + if rarity == "epic" then + mythicChance = 10 + end + if rarity == "legendary" then + mythicChance = 18 + end + if rarity == "mythic" then + mythicChance = 26 + end + mythicChance = math.min( + 32, + mythicChance + math.floor(multBias * 6) + ) + if hero and rollLuckChance(nil, hero, mythicChance / 100) then + return "mythic" + end + local legendaryRoll = math.min(0.72, 0.55 + multBias * 0.22) + if hero and rollLuckChance(nil, hero, legendaryRoll) then + return "legendary" + end + return "epic" +end +local function getArsenalWinDropCount(self) + local diffMult = Difficulty:getNpcStatScale() + local scaled = math.ceil(diffMult * 0.75) + local base = math.max(1, scaled) + local bonusTiers = math.floor(diffMult / 5) + local bonus = bonusTiers > 0 and bonusTiers or 0 + return math.max(1, base + bonus) +end +local function grantArsenalWinDrops(self, playerId) + local granted = {} + local deferredPersistence = false + local diffMult = Difficulty:getNpcStatScale() + local dropCount = getArsenalWinDropCount(nil) + local arsenalRollContext = Difficulty.leader == "death_sentence" and ({contractDropStatBias = contractRewardMultToDropStatBias01( + nil, + Difficulty:getDeathSentenceContractRewardMultiplier() + )}) or nil + local baseScaled = math.ceil(diffMult * 0.75) + local baseOnly = math.max(1, baseScaled) + local bonusTiers = math.floor(diffMult / 5) + print((((((((((("[MatchEndRewards] player=" .. tostring(playerId)) .. " roll arsenal drops: count=") .. tostring(dropCount)) .. " (base=") .. tostring(baseOnly)) .. ", bonus_tiers_5=") .. tostring(bonusTiers)) .. "), difficulty=") .. Difficulty.leader) .. ", mult=") .. tostring(diffMult)) + do + local i = 0 + while i < dropCount do + local quality = rollArsenalDropQuality(nil, playerId) + local itemName = pickRandomArsenalTemplateItemName(nil) + if itemName then + deferredPersistence = true + local detailed = grantArsenalItemDetailed( + nil, + playerId, + itemName, + 1, + quality, + arsenalRollContext, + {deferPersistence = true} + ) + local created = detailed[1] + if created ~= nil then + granted[#granted + 1] = {item_name = created.itemName, instance_id = created.instanceId, quality = quality} + end + print((((((((("[MatchEndRewards] player=" .. tostring(playerId)) .. " drop ") .. tostring(i + 1)) .. "/") .. tostring(dropCount)) .. ": quality=") .. quality) .. ", item=") .. itemName) + else + print((((((("[MatchEndRewards] player=" .. tostring(playerId)) .. " drop ") .. tostring(i + 1)) .. "/") .. tostring(dropCount)) .. ": empty pool for quality=") .. quality) + end + i = i + 1 + end + end + local hero = PlayerResource:GetSelectedHeroEntity(playerId) + local bonusFromLuck = hero and math.floor(getLuck(nil, hero) / ARSENAL_BONUS_DROP_LUCK_PER_ITEM) or 0 + if bonusFromLuck > 0 then + print(((((("[MatchEndRewards] player=" .. tostring(playerId)) .. " luck bonus arsenal drops: ") .. tostring(bonusFromLuck)) .. " (luck=") .. tostring(hero and getLuck(nil, hero) or 0)) .. ")") + end + do + local b = 0 + while b < bonusFromLuck do + local quality = rollArsenalLuckBonusDropQuality(nil) + local itemName = pickRandomArsenalTemplateItemName(nil) + if itemName then + deferredPersistence = true + local detailed = grantArsenalItemDetailed( + nil, + playerId, + itemName, + 1, + quality, + arsenalRollContext, + {deferPersistence = true} + ) + local created = detailed[1] + if created ~= nil then + granted[#granted + 1] = {item_name = created.itemName, instance_id = created.instanceId, quality = quality} + end + print((((((((("[MatchEndRewards] player=" .. tostring(playerId)) .. " luck-bonus drop ") .. tostring(b + 1)) .. "/") .. tostring(bonusFromLuck)) .. ": quality=") .. quality) .. ", item=") .. itemName) + end + b = b + 1 + end + end + if deferredPersistence then + flushArsenalInventoryToClientAndApi(nil, playerId) + end + print((("[MatchEndRewards] player=" .. tostring(playerId)) .. " arsenal drops result: ") .. (#granted > 0 and table.concat( + __TS__ArrayMap( + granted, + function(____, x) return ((x.item_name .. "(") .. x.instance_id) .. ")" end + ), + ", " + ) or "none")) + return granted +end +--- Итог награды за матч: одно округление в конце (синхронно с клиентом по payload). +function ____exports.computeMatchRewardAmount(self, difficultyScale, creepKills, deaths) + local preScale = 50 + creepKills * 0.1 + local scaled = preScale * difficultyScale + local deathMultiplier = math.max(0, 1 - deaths * 0.1) + local raw = scaled * deathMultiplier + return math.floor(math.max(0, raw)) +end +--- Только имя из движка; пустую строку отдаём в payload — отображение добирает клиент (GetPlayerInfo). +local function getPlayerDisplayName(self, playerId) + local raw = PlayerResource:GetPlayerName(playerId) + if raw ~= nil and raw ~= nil and tostring(raw) ~= "" then + return tostring(raw) + end + return "" +end +local function buildEmptyRow(self, slot, _outcome, _grantsBlocked) + local rewardAmount = 0 + return { + player_id = slot, + player_name = "", + hero_name = "", + creep_kills = 0, + deaths = 0, + damage_dealt = 0, + damage_taken = 0, + heal_to_allies = 0, + free_currency = 0, + bp_level = 1, + bp_experience = 0, + quests_completed_count = 0, + reward_amount = rewardAmount, + slot_empty = true, + breakdown = { + base_linear = 0, + creep_term = 0, + quest_term = 0, + death_penalty = 0, + total = rewardAmount + } + } +end +____exports.MatchEndRewards = __TS__Class() +local MatchEndRewards = ____exports.MatchEndRewards +MatchEndRewards.name = "MatchEndRewards" +MatchEndRewards.____file_path = "scripts/vscripts/match_end_rewards.lua" +function MatchEndRewards.prototype.____constructor(self) +end +function MatchEndRewards.buildPayload(self, outcome) + --- В DS итоговый множик награды = контракт (полосы 3–7+ по редкости); иначе — шкала сложности. + local difficultyScale = Difficulty.leader == "death_sentence" and Difficulty:getDeathSentenceContractRewardMultiplier() or Difficulty:getNpcStatScale() + local difficultyKey = Difficulty.leader or "normal" + local stats = GameStatsTracker:getInstance() + local grantsBlocked = stats:shouldBlockMatchEndRewards() + local rows = {} + local store = StoreManager:getInstance() + local bp = BattlePassServer:getInstance() + local combat = MatchEndCombatStats:getInstance() + do + local slot = 0 + while slot < ____exports.MATCH_END_PLAYER_SLOTS do + do + local playerId = slot + local key = tostring(slot) + if not PlayerResource:IsValidPlayerID(playerId) or not PlayerResource:IsValidPlayer(playerId) or PlayerResource:IsFakeClient(playerId) then + rows[key] = buildEmptyRow(nil, slot, outcome, grantsBlocked) + goto __continue40 + end + local hero = PlayerResource:GetSelectedHeroEntity(playerId) + if not hero then + rows[key] = buildEmptyRow(nil, slot, outcome, grantsBlocked) + goto __continue40 + end + local creepKills = PlayerResource:GetNearbyCreepDeaths(playerId) + local deaths = PlayerResource:GetDeaths(playerId) + --- Как defension: реальный урон через DamageFilter (match_end_combat_stats). + local damageDealt = combat:getOutgoingDamageSum(playerId) + local damageTaken = combat:getIncomingDamageSum(playerId) + local healToAllies = combat:getHealToAlliesSum(playerId) + local questsCompleted = bp:getNpcQuestsCompletedForPlayer(playerId) + local snap = bp:getBattlePassSnapshotForMatchEnd(playerId) + local baseLinear = 50 + local creepTerm = creepKills * 0.1 + local questTerm = 0 + local deathMultiplier = math.max(0, 1 - deaths * 0.1) + local totalBeforeDeathPenalty = (baseLinear + creepTerm + questTerm) * difficultyScale + local deathPenalty = math.max(0, totalBeforeDeathPenalty - totalBeforeDeathPenalty * deathMultiplier) + local computedTotal = ____exports.computeMatchRewardAmount(nil, difficultyScale, creepKills, deaths) + local rewardAmount = computedTotal + if outcome == "loss" or grantsBlocked then + rewardAmount = 0 + end + local hasPremium = snap.has_premium == true + local totalBeforePremium = nil + if outcome == "win" and not grantsBlocked and rewardAmount > 0 and hasPremium then + totalBeforePremium = rewardAmount + rewardAmount = math.floor(rewardAmount * MATCH_END_PREMIUM_REWARD_MULT) + end + rows[key] = { + player_id = playerId, + player_name = getPlayerDisplayName(nil, playerId), + hero_name = hero:GetUnitName(), + creep_kills = creepKills, + deaths = deaths, + damage_dealt = damageDealt, + damage_taken = damageTaken, + heal_to_allies = healToAllies, + free_currency = store:getFreeCurrency(playerId), + bp_level = snap.level, + bp_experience = snap.experience, + quests_completed_count = questsCompleted, + reward_amount = rewardAmount, + has_battle_pass_premium = hasPremium, + slot_empty = false, + breakdown = { + base_linear = baseLinear, + creep_term = creepTerm, + quest_term = questTerm, + death_penalty = deathPenalty, + total_before_premium = totalBeforePremium, + total = rewardAmount + } + } + end + ::__continue40:: + slot = slot + 1 + end + end + return {outcome = outcome, difficulty_scale = difficultyScale, difficulty_key = difficultyKey, players = rows} +end +function MatchEndRewards.previewForDevPlayer(self, playerId, outcome) + if outcome == nil then + outcome = "win" + end + if not IsServer() then + return + end + do + local function ____catch() + return true + end + local ____try, ____hasReturned, ____returnValue = pcall(function() + local tools = _G.IsInToolsMode + if not tools or not tools(nil) then + return true + end + end) + if not ____try then + ____hasReturned, ____returnValue = ____catch() + end + if ____hasReturned then + return ____returnValue + end + end + local player = PlayerResource:GetPlayer(playerId) + if not player then + return + end + local payload = ____exports.MatchEndRewards:buildPayload(outcome) + CustomGameEventManager:Send_ServerToPlayer(player, "match_end_screen", {outcome = outcome, payload = payload}) +end +function MatchEndRewards.registerStateListenerForMatchEnd(self) + ListenToGameEvent( + "game_rules_state_change", + function() + if GameRules:State_Get() == DOTA_GAMERULES_STATE_GAME_IN_PROGRESS then + ____exports.MatchEndRewards.dispatched = false + end + end, + nil + ) +end +function MatchEndRewards.dispatch(self, outcome, onFullyDispatched) + if not IsServer() then + return + end + if ____exports.MatchEndRewards.dispatched then + return + end + ____exports.MatchEndRewards.dispatched = true + local stats = GameStatsTracker:getInstance() + local grantsBlocked = stats:shouldBlockMatchEndRewards() + local payload = ____exports.MatchEndRewards:buildPayload(outcome) + local screenSent = false + local function sendScreen() + if screenSent then + return + end + screenSent = true + CustomGameEventManager:Send_ServerToAllClients("match_end_screen", {outcome = outcome, payload = payload}) + end + local function finish() + sendScreen(nil) + if onFullyDispatched then + onFullyDispatched(nil) + end + end + do + local function ____catch(e) + print("[MatchEndRewards] reward dispatch error: " .. tostring(e)) + end + local ____try, ____hasReturned, ____returnValue = pcall(function() + if outcome == "loss" and not grantsBlocked and Difficulty.leader == "death_sentence" then + Difficulty:applyDeathSentenceContractDurabilityOnLoss(function() + finish(nil) + end) + return true + end + if outcome == "win" and not grantsBlocked then + local store = StoreManager:getInstance() + local bp = BattlePassServer:getInstance() + local hasAnyRealWinner = false + local winnersBySlot = {} + do + local slot = 0 + while slot < ____exports.MATCH_END_PLAYER_SLOTS do + do + local row = payload.players[tostring(slot)] + if not row or row.slot_empty then + goto __continue65 + end + hasAnyRealWinner = true + winnersBySlot[#winnersBySlot + 1] = slot + local pid = row.player_id + local amt = row.reward_amount + if amt > 0 then + bp:addExperience(pid, amt) + store:grantFreeCurrencyMatchEndReward(pid, amt) + end + row.arsenal_drops = grantArsenalWinDrops(nil, pid) + print((((((("[MatchEndRewards] payload row slot=" .. tostring(slot)) .. " pid=") .. tostring(pid)) .. ": reward=") .. tostring(amt)) .. ", arsenal_drops=") .. (row.arsenal_drops and #row.arsenal_drops > 0 and table.concat( + __TS__ArrayMap( + row.arsenal_drops, + function(____, x) return ((x.item_name .. "(") .. x.instance_id) .. ")" end + ), + ", " + ) or "none")) + end + ::__continue65:: + slot = slot + 1 + end + end + if hasAnyRealWinner then + local pendingContractSaves = 0 + for ____, slot in ipairs(winnersBySlot) do + do + local row = payload.players[tostring(slot)] + if not row or row.slot_empty then + goto __continue70 + end + local pid = row.player_id + local contract = Difficulty:grantMatchEndContractIfEligible(pid) + if not contract then + goto __continue70 + end + local info = { + instance_id = contract.instanceId, + serial = contract.serial, + rarity = contract.rarity, + title_index = contract.titleIndex, + reward_multiplier = contract.rewardMultiplier, + trait_id = contract.traitId, + complication_ids = {unpack(contract.complicationIds)}, + durability = contract.durability, + durability_max = contract.durabilityMax + } + pendingContractSaves = pendingContractSaves + 1 + Difficulty:saveContractRosterForPlayer( + pid, + function(____, ok) + if ok then + row.contract_drop = info + print((((((("[MatchEndRewards] payload row slot=" .. tostring(slot)) .. ": contract_drop saved id=") .. info.instance_id) .. ", rarity=") .. info.rarity) .. ", title=") .. tostring(info.title_index)) + else + print((((("[MatchEndRewards] contract_drop save failed slot=" .. tostring(slot)) .. " pid=") .. tostring(row.player_id)) .. " id=") .. info.instance_id) + end + pendingContractSaves = pendingContractSaves - 1 + if pendingContractSaves <= 0 then + finish(nil) + end + end + ) + end + ::__continue70:: + end + if pendingContractSaves > 0 then + return true + end + print("[MatchEndRewards] no contract drop: difficulty=" .. Difficulty.leader) + end + end + end) + if not ____try then + ____hasReturned, ____returnValue = ____catch(____hasReturned) + end + if ____hasReturned then + return ____returnValue + end + end + finish(nil) +end +MatchEndRewards.dispatched = false +if IsServer() then + ____exports.MatchEndRewards:registerStateListenerForMatchEnd() +end +return ____exports diff --git a/scripts/vscripts/mini_profile_server.lua b/scripts/vscripts/mini_profile_server.lua new file mode 100644 index 0000000..58fcd55 --- /dev/null +++ b/scripts/vscripts/mini_profile_server.lua @@ -0,0 +1,1786 @@ +local ____lualib = require("lualib_bundle") +local __TS__Class = ____lualib.__TS__Class +local Map = ____lualib.Map +local __TS__New = ____lualib.__TS__New +local __TS__ArrayIsArray = ____lualib.__TS__ArrayIsArray +local __TS__TypeOf = ____lualib.__TS__TypeOf +local __TS__ObjectKeys = ____lualib.__TS__ObjectKeys +local __TS__ArrayJoin = ____lualib.__TS__ArrayJoin +local __TS__ArrayForEach = ____lualib.__TS__ArrayForEach +local __TS__ArrayFilter = ____lualib.__TS__ArrayFilter +local __TS__ArraySort = ____lualib.__TS__ArraySort +local __TS__ObjectValues = ____lualib.__TS__ObjectValues +local __TS__ArraySome = ____lualib.__TS__ArraySome +local __TS__ArrayPushArray = ____lualib.__TS__ArrayPushArray +local __TS__ArrayMap = ____lualib.__TS__ArrayMap +local Set = ____lualib.Set +local __TS__ArrayIndexOf = ____lualib.__TS__ArrayIndexOf +local ____exports = {} +local ____server_config = require("server_config") +local SERVER_CONFIG = ____server_config.SERVER_CONFIG +local ____api_helper = require("api_helper") +local setApiHeaders = ____api_helper.setApiHeaders +local setApiHeadersLong = ____api_helper.setApiHeadersLong +local ____store_manager = require("store_manager") +local StoreManager = ____store_manager.StoreManager +local ____hero_list_table = require("tables.hero_list_table") +local HERO_LIST_TABLE = ____hero_list_table.HERO_LIST_TABLE +local function rawPrintFn(____, ...) + _G:print(...) +end +local ENABLE_VERBOSE_MINI_PROFILE_LOGS = false +local ____print = ENABLE_VERBOSE_MINI_PROFILE_LOGS and rawPrintFn or (function(____, ...) return nil end) +____exports.MiniProfileServer = __TS__Class() +local MiniProfileServer = ____exports.MiniProfileServer +MiniProfileServer.name = "MiniProfileServer" +MiniProfileServer.____file_path = "scripts/vscripts/mini_profile_server.lua" +function MiniProfileServer.prototype.____constructor(self) + self.serverUrl = SERVER_CONFIG.API_URL + self.grantedProfileRewardsInSession = __TS__New(Map) + self.grantedHeroRankRewardsInSession = __TS__New(Map) + self.profileLevelRewards = { + {level = 5, freeCurrency = 100}, + {level = 10, freeCurrency = 200}, + {level = 15, freeCurrency = 350}, + {level = 20, freeCurrency = 500}, + {level = 25, freeCurrency = 700}, + {level = 30, freeCurrency = 900}, + {level = 40, freeCurrency = 1400}, + {level = 50, freeCurrency = 2000}, + {level = 75, freeCurrency = 3500}, + {level = 100, freeCurrency = 5000}, + {level = 150, freeCurrency = 5500}, + {level = 200, freeCurrency = 6000}, + {level = 300, freeCurrency = 7000}, + {level = 400, freeCurrency = 8000}, + {level = 500, freeCurrency = 9000}, + {level = 600, freeCurrency = 10000}, + {level = 700, freeCurrency = 11000}, + {level = 800, freeCurrency = 12500}, + {level = 900, freeCurrency = 14000}, + {level = 1000, freeCurrency = 16000} + } + self:setupEventListeners() +end +function MiniProfileServer.getInstance(self) + if not ____exports.MiniProfileServer.instance then + ____exports.MiniProfileServer.instance = __TS__New(____exports.MiniProfileServer) + end + return ____exports.MiniProfileServer.instance +end +function MiniProfileServer.prototype.setupEventListeners(self) + CustomGameEventManager:RegisterListener( + "request_player_profile", + function(source, event) + local playerId = event.PlayerID + local playerName = event.player_name + local requestedSteamId = event.steam_id + local requestedSteamId64 = event.steam_id_64 + local steamId + local steamId64 + if requestedSteamId and requestedSteamId ~= "" then + steamId = tostring(requestedSteamId) + if requestedSteamId64 and requestedSteamId64 ~= "" and requestedSteamId64 ~= "null" then + steamId64 = tostring(requestedSteamId64) + local steamIdStr = tostring(steamId) + local steamIdPrefix = string.sub(steamIdStr, 1, 7) + if string.len(steamIdStr) > 10 and steamIdPrefix == "7656119" then + local base64Str = "76561197960265728" + local steamId64Str = steamIdStr + local accountId = self:subtractBigInts(steamId64Str, base64Str) + steamId = accountId + ____print(nil, (("[MiniProfileServer] Using provided Steam ID64: " .. steamId64) .. ", converted to Account ID: ") .. steamId) + else + ____print(nil, (("[MiniProfileServer] Using provided Steam ID32: " .. steamId) .. ", Steam ID64: ") .. steamId64) + end + else + local steamIdStr = tostring(steamId) + local steamIdPrefix = string.sub(steamIdStr, 1, 7) + if string.len(steamIdStr) > 10 and steamIdPrefix == "7656119" then + steamId64 = steamIdStr + local base64Str = "76561197960265728" + local accountId = self:subtractBigInts(steamIdStr, base64Str) + steamId = accountId + ____print(nil, (("[MiniProfileServer] Steam ID64 detected: " .. steamId64) .. ", converted to Account ID: ") .. steamId) + else + local accountIdNum = tonumber(steamId) + if accountIdNum then + local base64Str = "76561197960265728" + local accountIdStr = tostring(accountIdNum) + local steamId64Str = self:addBigInts(accountIdStr, base64Str) + steamId64 = steamId64Str + ____print(nil, (("[MiniProfileServer] Account ID detected: " .. steamId) .. ", converted to Steam ID64: ") .. steamId64) + else + if playerId ~= -1 and PlayerResource:GetPlayer(playerId) then + steamId64 = tostring(PlayerResource:GetSteamID(playerId)) + else + steamId64 = steamId + end + end + end + end + else + steamId = tostring(PlayerResource:GetSteamAccountID(playerId)) + steamId64 = tostring(PlayerResource:GetSteamID(playerId)) + end + local requestingPlayer = EntIndexToHScript(source) + local requestingPlayerId = requestingPlayer ~= nil and requestingPlayer ~= nil and requestingPlayer:GetPlayerID() or playerId + local requesterSteamId = tostring(PlayerResource:GetSteamAccountID(requestingPlayerId)) + local isOwnProfileRequest = requesterSteamId == steamId + self:loadPlayerProfileFromServer( + requestingPlayerId, + steamId, + playerName, + steamId64, + isOwnProfileRequest + ) + end + ) + CustomGameEventManager:RegisterListener( + "request_match_players", + function(source, event) + local requestingPlayer = EntIndexToHScript(source) + if not requestingPlayer then + return + end + local matchIdRaw = event.match_id or event.game_id or event.id + local matchId = tonumber(tostring(matchIdRaw)) or 0 + local rowId = tonumber(tostring(event.row_id or 0)) or 0 + local steamId = tostring(event.steam_id or "") + if matchId <= 0 then + CustomGameEventManager:Send_ServerToPlayer(requestingPlayer, "match_players_data", {error = "Некорректный ID матча", match_id = 0, players = {}}) + return + end + self:loadMatchPlayersFromServer(requestingPlayer, matchId, rowId, steamId) + end + ) + CustomGameEventManager:RegisterListener( + "request_store_currency", + function(source, event) + local playerId = event.PlayerID + if playerId == nil or playerId == nil or not PlayerResource:IsValidPlayerID(playerId) then + return + end + self:syncHeroRankRewardsForPlayer(playerId) + end + ) +end +function MiniProfileServer.prototype.syncHeroRankRewardsForPlayer(self, playerId) + local steamId = tostring(PlayerResource:GetSteamAccountID(playerId)) + if not steamId or steamId == "0" then + return + end + self:loadHeroAchievementsForProfile( + steamId, + playerId, + true, + function(____, _heroAchievementsObject, grantedNow) + local grantedCount = #self:normalizeArrayLike(grantedNow) + if grantedCount > 0 then + ____print( + nil, + (("[MiniProfileServer] Store-trigger reward sync: player=" .. tostring(playerId)) .. ", granted=") .. tostring(grantedCount) + ) + end + end + ) +end +function MiniProfileServer.prototype.createPlayerProfile(self, playerId, steamId, playerName) + local request = CreateHTTPRequest("POST", self.serverUrl .. "/player") + setApiHeadersLong(nil, request) + request:SetHTTPRequestRawPostBody( + "application/json", + json.encode({steam_id = steamId, player_name = playerName}) + ) + request:Send(function(result) + local player = PlayerResource:GetPlayer(playerId) + if not player then + return + end + if result.StatusCode >= 200 and result.StatusCode < 300 then + do + pcall(function() + local decoded = {json.decode(result.Body)} + local responseData = nil + if __TS__ArrayIsArray(decoded) and #decoded > 0 then + responseData = decoded[1] + elseif decoded and type(decoded) == "table" then + responseData = decoded + end + if responseData then + StoreManager:getInstance():handleLoginPlayerApiResponse(playerId, responseData) + end + end) + end + self:loadPlayerProfileFromServer(playerId, steamId, playerName) + else + CustomGameEventManager:Send_ServerToPlayer(player, "player_profile_data", {error = "Не удалось создать профиль", steam_id = steamId, player_name = playerName}) + end + end) +end +function MiniProfileServer.prototype.loadPlayerProfileFromServer(self, playerId, steamId, playerName, steamId64, shouldGrantLevelRewards) + if shouldGrantLevelRewards == nil then + shouldGrantLevelRewards = false + end + local steamIdStr = tostring(steamId) + local steamIdNum = tonumber(steamIdStr) + if steamIdNum and steamIdNum > 1000000000000 then + local base64 = 76561197960265730 + local accountId = math.floor(steamIdNum - base64) + steamIdStr = tostring(accountId) + ____print(nil, (("[MiniProfileServer] Converted 64-bit Steam ID " .. steamId) .. " to 32-bit Account ID ") .. steamIdStr) + end + ____print(nil, (("[MiniProfileServer] Загрузка профиля: steam_id=" .. steamIdStr) .. ", player=") .. playerName) + local request = CreateHTTPRequest("GET", (self.serverUrl .. "/player/") .. steamIdStr) + setApiHeaders(nil, request) + request:Send(function(result) + do + local function ____catch(____error) + local player = PlayerResource:GetPlayer(playerId) + if player then + CustomGameEventManager:Send_ServerToPlayer(player, "player_profile_data", {error = "Ошибка обработки данных", steam_id = steamId, player_name = playerName}) + end + end + local ____try, ____hasReturned, ____returnValue = pcall(function() + local player = PlayerResource:GetPlayer(playerId) + if not player then + return true + end + if result.StatusCode == 0 then + CustomGameEventManager:Send_ServerToPlayer(player, "player_profile_data", {error = "Сервер недоступен", steam_id = steamId, player_name = playerName}) + return true + end + if result.StatusCode >= 200 and result.StatusCode < 300 then + do + local function ____catch(e) + ____print( + nil, + "[MiniProfileServer] Ошибка обработки данных профиля: " .. tostring(e) + ) + CustomGameEventManager:Send_ServerToPlayer(player, "player_profile_data", {error = "Ошибка обработки данных профиля", steam_id = steamId, player_name = playerName}) + end + local ____try, ____hasReturned, ____returnValue = pcall(function() + local responseBodyStr = tostring(result.Body or "") + local responseBodyPreview = string.len(responseBodyStr) > 1000 and string.sub(responseBodyStr, 1, 1000) .. "... [truncated]" or responseBodyStr + ____print(nil, "[MiniProfileServer] Response Body (preview): " .. responseBodyPreview) + local responseData = self:decodeJsonSafe(result.Body) + if responseData == nil then + ____print( + nil, + "[MiniProfileServer] Ошибка JSON профиля: body_length=" .. tostring(string.len(responseBodyStr)) + ) + CustomGameEventManager:Send_ServerToPlayer(player, "player_profile_data", {error = "Ошибка парсинга JSON", steam_id = steamId, player_name = playerName}) + return true + end + local responseDataAny = responseData + ____print( + nil, + (("[MiniProfileServer] Decoded response type: " .. __TS__TypeOf(responseData)) .. ", isArray: ") .. tostring(__TS__ArrayIsArray(responseData)) + ) + ____print( + nil, + "[MiniProfileServer] Decoded response keys: " .. (type(responseData) == "table" and __TS__ArrayJoin( + __TS__ObjectKeys(responseData), + ", " + ) or "N/A") + ) + local data = nil + local responseItems = {} + local topLevelObjects = {} + if responseDataAny and type(responseDataAny) == "table" then + for k in pairs(responseDataAny) do + local v = responseDataAny[k] + if v and type(v) == "table" then + topLevelObjects[#topLevelObjects + 1] = v + end + end + end + local looksLikeProfilePayload = responseDataAny and type(responseDataAny) == "table" and (responseDataAny.profile ~= nil or responseDataAny.stats ~= nil or responseDataAny.recentGames ~= nil or responseDataAny.recent_games ~= nil or responseDataAny.games ~= nil or responseDataAny.history ~= nil) + if looksLikeProfilePayload then + data = responseDataAny + ____print(nil, "[MiniProfileServer] Using profile payload object directly") + elseif __TS__ArrayIsArray(responseDataAny) then + local reconstructed = {} + local reconstructedCount = 0 + __TS__ArrayForEach( + topLevelObjects, + function(____, obj) + local objAny = obj + local keyNamed = tostring(objAny.key or "") + local valueNamed = objAny.value + local keyIndexed = tostring(objAny[1] or objAny["1"] or "") + local ____temp_0 + if objAny[2] ~= nil then + ____temp_0 = objAny[2] + else + ____temp_0 = objAny["2"] + end + local valueIndexed = ____temp_0 + local finalKey = keyNamed ~= "" and keyNamed or keyIndexed + local ____temp_1 + if keyNamed ~= "" then + ____temp_1 = valueNamed + else + ____temp_1 = valueIndexed + end + local finalValue = ____temp_1 + if finalKey ~= "" and finalValue ~= nil then + reconstructed[finalKey] = finalValue + reconstructedCount = reconstructedCount + 1 + end + end + ) + if reconstructedCount > 0 then + data = reconstructed + ____print( + nil, + "[MiniProfileServer] Reconstructed object from key/value pairs, keys=" .. table.concat( + __TS__ObjectKeys(reconstructed), + ", " + ) + ) + elseif #topLevelObjects > 0 then + local sample = topLevelObjects[1] + local sampleKeys = {} + for k in pairs(sample) do + sampleKeys[#sampleKeys + 1] = tostring(k) + end + local ____temp_10 = ("[MiniProfileServer] array-like sample keys=" .. table.concat(sampleKeys, ", ")) .. " " + local ____tostring_5 = tostring + local ____opt_result_4 + if sample ~= nil then + ____opt_result_4 = sample.key + end + local ____tostring_5_result_9 = ____tostring_5(____opt_result_4) + local ____opt_result_8 + if sample ~= nil then + ____opt_result_8 = sample.value + end + local ____temp_27 = ____temp_10 .. ((("key=" .. ____tostring_5_result_9) .. " value_type=") .. __TS__TypeOf(____opt_result_8)) .. " " + local ____tostring_18 = tostring + local ____opt_result_13 + if sample ~= nil then + ____opt_result_13 = sample[1] + end + local ____opt_result_13_17 = ____opt_result_13 + if not ____opt_result_13_17 then + local ____opt_result_16 + if sample ~= nil then + ____opt_result_16 = sample["1"] + end + ____opt_result_13_17 = ____opt_result_16 + end + local ____tostring_18_result_26 = ____tostring_18(____opt_result_13_17) + local ____opt_result_21 + if sample ~= nil then + ____opt_result_21 = sample[2] + end + local ____opt_result_21_25 = ____opt_result_21 + if ____opt_result_21_25 == nil then + local ____opt_result_24 + if sample ~= nil then + ____opt_result_24 = sample["2"] + end + ____opt_result_21_25 = ____opt_result_24 + end + ____print( + nil, + ____temp_27 .. (("idx1=" .. ____tostring_18_result_26) .. " idx2_type=") .. __TS__TypeOf(____opt_result_21_25) + ) + end + if not data then + for ____, obj in ipairs(topLevelObjects) do + if obj and type(obj) == "table" and (obj.profile ~= nil or obj.stats ~= nil or obj.recentGames ~= nil or obj.recent_games ~= nil or obj.games ~= nil or obj.history ~= nil) then + data = obj + ____print(nil, "[MiniProfileServer] Found payload object inside array-like response") + break + end + end + end + if not data then + responseItems = self:normalizeArrayLike(responseDataAny) + if #responseItems > 0 then + data = responseItems[1] + ____print( + nil, + "[MiniProfileServer] Using array-like response, items=" .. tostring(#responseItems) + ) + end + end + elseif responseDataAny and type(responseDataAny) == "table" and responseDataAny.value ~= nil then + data = responseDataAny.value + ____print(nil, "[MiniProfileServer] Using responseData.value as data") + elseif responseDataAny and type(responseDataAny) == "table" then + data = responseDataAny + ____print(nil, "[MiniProfileServer] Using object directly as data") + else + ____print(nil, "[MiniProfileServer] Failed to decode response") + CustomGameEventManager:Send_ServerToPlayer(player, "player_profile_data", {error = "Ошибка декодирования данных", steam_id = steamId, player_name = playerName}) + return true + end + if data then + local function collectObjectKeys(____, obj) + local keys = {} + if not obj or type(obj) ~= "table" then + return keys + end + for key in pairs(obj) do + keys[#keys + 1] = tostring(key) + end + return keys + end + if not data or type(data) ~= "table" then + for ____, item in ipairs(responseItems) do + if item and type(item) == "table" then + data = item + break + end + end + end + if not data or type(data) ~= "table" then + ____print(nil, "[MiniProfileServer] ❌ Некорректный формат данных профиля (data is not object)") + CustomGameEventManager:Send_ServerToPlayer(player, "player_profile_data", {error = "Некорректный формат данных профиля", steam_id = steamId, player_name = playerName}) + return true + end + ____print( + nil, + "[MiniProfileServer] Data keys: " .. table.concat( + collectObjectKeys(nil, data), + ", " + ) + ) + ____print( + nil, + "[MiniProfileServer] data.recentGames exists: " .. tostring(data.recentGames ~= nil) + ) + ____print( + nil, + (("[MiniProfileServer] data.recentGames type: " .. __TS__TypeOf(data.recentGames)) .. ", isArray: ") .. tostring(__TS__ArrayIsArray(data.recentGames)) + ) + if data.recentGames then + local ____Array_isArray_result_28 + if __TS__ArrayIsArray(data.recentGames) then + ____Array_isArray_result_28 = data.recentGames.length + else + ____Array_isArray_result_28 = "N/A" + end + ____print( + nil, + "[MiniProfileServer] data.recentGames length: " .. tostring(____Array_isArray_result_28) + ) + end + local profileData = data.profile or data + if #responseItems > 0 then + for ____, item in ipairs(responseItems) do + if item and type(item) == "table" and item.profile and type(item.profile) == "table" then + profileData = item.profile + break + end + end + end + local nameFromDB = profileData.name or playerName + local ____tostring_32 = tostring + local ____opt_result_31 + if profileData ~= nil then + ____opt_result_31 = profileData.name + end + local ____tostring_32_result_37 = ____tostring_32(____opt_result_31) + local ____tostring_36 = tostring + local ____opt_result_35 + if profileData ~= nil then + ____opt_result_35 = profileData.level + end + local ____temp_47 = ((("[MiniProfileServer] profileData name=" .. ____tostring_32_result_37) .. " level=") .. ____tostring_36(____opt_result_35)) .. " " + local ____tostring_41 = tostring + local ____opt_result_40 + if profileData ~= nil then + ____opt_result_40 = profileData.free_currency + end + local ____tostring_41_result_46 = ____tostring_41(____opt_result_40) + local ____tostring_45 = tostring + local ____opt_result_44 + if profileData ~= nil then + ____opt_result_44 = profileData.donate_currency + end + ____print( + nil, + ____temp_47 .. (("free_currency=" .. ____tostring_41_result_46) .. " donate_currency=") .. ____tostring_45(____opt_result_44) + ) + local grantedLevelRewardsNow = {} + if shouldGrantLevelRewards then + do + local function ____catch(grantError) + ____print( + nil, + "[MiniProfileServer] Ошибка выдачи наград профиля: " .. tostring(grantError) + ) + grantedLevelRewardsNow = {} + end + local ____try, ____hasReturned = pcall(function() + local profileLevel = tonumber(profileData.level) or 1 + grantedLevelRewardsNow = self:grantPendingProfileLevelRewards(playerId, profileLevel) + end) + if not ____try then + ____catch(____hasReturned) + end + end + end + local grantedLevelRewardsObject = {} + __TS__ArrayForEach( + grantedLevelRewardsNow, + function(____, reward, index) + grantedLevelRewardsObject[tostring(index)] = reward + end + ) + local recentGames = data.recentGames or data.recent_games or data.games or data.history or data.match_history or data.stats and (data.stats.recentGames or data.stats.recent_games or data.stats.games or data.stats.history) or data.profile and (data.profile.recentGames or data.profile.recent_games or data.profile.games or data.profile.history or data.profile.match_history) + local function collectObjectValues(____, obj) + local out = {} + if not obj or type(obj) ~= "table" then + return out + end + for key in pairs(obj) do + out[#out + 1] = obj[key] + end + return out + end + local isRecentGamesEmptyObject = recentGames and type(recentGames) == "table" and not __TS__ArrayIsArray(recentGames) and #collectObjectKeys(nil, recentGames) == 0 + if (not recentGames or isRecentGamesEmptyObject) and #responseItems > 0 then + for ____, item in ipairs(responseItems) do + do + if not item or type(item) ~= "table" then + goto __continue93 + end + local candidate = item.recentGames or item.recent_games or item.games or item.history or item.match_history or item.stats and (item.stats.recentGames or item.stats.recent_games or item.stats.games or item.stats.history) or item.profile and (item.profile.recentGames or item.profile.recent_games or item.profile.games or item.profile.history or item.profile.match_history) + if not candidate then + goto __continue93 + end + if __TS__ArrayIsArray(candidate) then + if #collectObjectValues(nil, candidate) > 0 then + recentGames = candidate + break + end + elseif type(candidate) == "table" and #collectObjectKeys(nil, candidate) > 0 then + recentGames = candidate + break + end + end + ::__continue93:: + end + end + local extractGamesArray + extractGamesArray = function(____, source) + if not source then + return {} + end + if type(source) == "string" then + do + local function ____catch(e) + return true, {} + end + local ____try, ____hasReturned, ____returnValue = pcall(function() + local decoded = {json.decode(source)} + return true, extractGamesArray(nil, decoded) + end) + if not ____try then + ____hasReturned, ____returnValue = ____catch(____hasReturned) + end + if ____hasReturned then + return ____returnValue + end + end + end + if __TS__ArrayIsArray(source) then + local values = collectObjectValues(nil, source) + if #values > 0 then + return __TS__ArrayFilter( + values, + function(____, item) return item and type(item) == "table" end + ) + end + return {} + end + if type(source) == "table" then + local directValues = __TS__ArrayFilter( + collectObjectValues(nil, source), + function(____, item) return item and type(item) == "table" end + ) + if #directValues > 0 then + return directValues + end + local nested = source.items or source.data or source.rows or source.matches or source.history or source.recentGames or source.recent_games + if nested then + return extractGamesArray(nil, nested) + end + end + return {} + end + local gamesExtracted = extractGamesArray(nil, recentGames) + do + local function ____catch(sortError) + ____print( + nil, + "[MiniProfileServer] Не удалось отсортировать recentGames: " .. tostring(sortError) + ) + end + local ____try, ____hasReturned = pcall(function() + if type(Date) == "function" then + __TS__ArraySort( + gamesExtracted, + function(____, a, b) + local dateA = __TS__New(Date, a.game_start or a.gameStart or a.created_at or 0):getTime() + local dateB = __TS__New(Date, b.game_start or b.gameStart or b.created_at or 0):getTime() + return dateB - dateA + end + ) + end + end) + if not ____try then + ____catch(____hasReturned) + end + end + recentGames = gamesExtracted + ____print( + nil, + "[MiniProfileServer] Extracted games: " .. tostring(#gamesExtracted) + ) + ____print( + nil, + (("[MiniProfileServer] Profile loaded for " .. tostring(nameFromDB)) .. ", recentGames count: ") .. tostring(__TS__ArrayIsArray(recentGames) and #recentGames or "not an array") + ) + ____print( + nil, + (("[MiniProfileServer] recentGames type: " .. __TS__TypeOf(recentGames)) .. ", isArray: ") .. tostring(__TS__ArrayIsArray(recentGames)) + ) + if not recentGames or type(recentGames) == "table" and #collectObjectKeys(nil, recentGames) == 0 then + ____print( + nil, + (("[MiniProfileServer] recentGames пустой. Data keys=" .. table.concat( + collectObjectKeys(nil, data), + ", " + )) .. ", profile keys=") .. (profileData and type(profileData) == "table" and table.concat( + collectObjectKeys(nil, profileData), + ", " + ) or "N/A") + ) + end + local gamesToSend = {} + if __TS__ArrayIsArray(recentGames) then + gamesToSend = self:normalizeArrayLike(recentGames) + elseif recentGames and type(recentGames) == "table" then + gamesToSend = __TS__ArrayFilter( + collectObjectValues(nil, recentGames), + function(____, item) return item and type(item) == "table" end + ) + end + if #gamesToSend == 0 then + ____print(nil, "[MiniProfileServer] recentGames пустой в /player. Пробуем fallback /player/:id/history") + self:loadRecentGamesFallback( + player, + steamIdStr, + profileData, + data.stats or data, + nameFromDB, + steamId64 or steamId, + grantedLevelRewardsObject + ) + return true + end + self:loadHeroAchievementsForProfile( + steamIdStr, + playerId, + shouldGrantLevelRewards, + function(____, heroAchievementsObject, grantedHeroRankRewardsObject) + self:sendProfileToClient( + player, + profileData, + data.stats or data, + gamesToSend, + steamId, + nameFromDB, + steamId64 or steamId, + grantedLevelRewardsObject, + heroAchievementsObject, + grantedHeroRankRewardsObject + ) + end + ) + end + end) + if not ____try then + ____hasReturned, ____returnValue = ____catch(____hasReturned) + end + if ____hasReturned then + return true, ____returnValue + end + end + elseif result.StatusCode == 404 then + self:createPlayerProfile(playerId, steamId, playerName) + else + CustomGameEventManager:Send_ServerToPlayer( + player, + "player_profile_data", + { + error = "Ошибка сервера: " .. tostring(result.StatusCode), + steam_id = steamId, + player_name = playerName + } + ) + end + end) + if not ____try then + ____hasReturned, ____returnValue = ____catch(____hasReturned) + end + if ____hasReturned then + return ____returnValue + end + end + end) +end +function MiniProfileServer.prototype.normalizeArrayLike(self, input) + if not input then + return {} + end + local function hasGameFields(____, obj) + if not obj or type(obj) ~= "table" then + return false + end + return not not (obj.hero or obj.hero_name or obj.heroName or obj.result or obj.match_result or obj.difficulty or obj.difficulty_name or obj.mode) + end + local unwrapKnownContainers + unwrapKnownContainers = function(____, value) + if not value or type(value) ~= "table" then + return value + end + local containerKeys = { + "history", + "recentGames", + "games", + "items", + "rows", + "data", + "result", + "payload" + } + for ____, key in ipairs(containerKeys) do + local nested = value[key] + if nested and type(nested) == "table" then + if key == "data" and type(nested) == "table" then + local deep = unwrapKnownContainers(nil, nested) + if deep ~= nested or __TS__ArrayIsArray(deep) then + return deep + end + end + if __TS__ArrayIsArray(nested) then + return nested + end + if hasGameFields(nil, nested) then + return {nested} + end + local nestedValues = __TS__ArrayFilter( + __TS__ObjectValues(nested), + function(____, v) return v and type(v) == "table" end + ) + if #nestedValues > 0 then + return nested + end + end + end + return value + end + local unwrapped = unwrapKnownContainers(nil, input) + if unwrapped ~= input then + return self:normalizeArrayLike(unwrapped) + end + if __TS__ArrayIsArray(input) then + local arr = input + local len = #arr + if type(len) == "number" and len > 0 then + if __TS__ArraySome( + arr, + function(____, item) return hasGameFields(nil, item) end + ) then + return __TS__ArrayFilter( + arr, + function(____, item) return item and type(item) == "table" end + ) + end + local expanded = {} + __TS__ArrayForEach( + arr, + function(____, item) + local normalized = self:normalizeArrayLike(item) + if #normalized > 0 then + __TS__ArrayPushArray(expanded, normalized) + end + end + ) + if #expanded > 0 then + return expanded + end + return __TS__ArrayFilter( + arr, + function(____, item) return item and type(item) == "table" end + ) + end + end + local numericEntries = {} + local otherValues = {} + if type(input) == "table" then + for key in pairs(input) do + if not input.hasOwnProperty or rawget(input, key) ~= nil then + local value = input[key] + local numericKey = tonumber(tostring(key)) + if numericKey ~= nil and numericKey ~= nil then + numericEntries[#numericEntries + 1] = {idx = numericKey, value = value} + else + otherValues[#otherValues + 1] = value + end + end + end + end + if #numericEntries > 0 then + __TS__ArraySort( + numericEntries, + function(____, a, b) return a.idx - b.idx end + ) + return __TS__ArrayFilter( + __TS__ArrayMap( + numericEntries, + function(____, entry) return entry.value end + ), + function(____, item) return item and type(item) == "table" end + ) + end + if hasGameFields(nil, input) then + return {input} + end + local flattened = {} + __TS__ArrayForEach( + __TS__ArrayFilter( + otherValues, + function(____, item) return item and type(item) == "table" end + ), + function(____, item) + local normalized = self:normalizeArrayLike(item) + if #normalized > 0 then + __TS__ArrayPushArray(flattened, normalized) + else + flattened[#flattened + 1] = item + end + end + ) + return flattened +end +function MiniProfileServer.prototype.loadRecentGamesFallback(self, player, steamId, profileData, statsData, playerName, steamId64, grantedLevelRewardsObject) + if grantedLevelRewardsObject == nil then + grantedLevelRewardsObject = {} + end + local fallbackRequest = CreateHTTPRequest("GET", ((self.serverUrl .. "/player/") .. steamId) .. "/history?limit=10&offset=0") + setApiHeaders(nil, fallbackRequest) + fallbackRequest:Send(function(result) + if result.StatusCode >= 200 and result.StatusCode < 300 then + do + local function ____catch(e) + ____print(nil, "[MiniProfileServer] Fallback history parse error") + end + local ____try, ____hasReturned, ____returnValue = pcall(function() + local decoded = {json.decode(result.Body)} + local games = self:normalizeArrayLike(decoded) + ____print( + nil, + ("[MiniProfileServer] Fallback history loaded: " .. tostring(#games)) .. " games" + ) + self:loadHeroAchievementsForProfile( + steamId, + player:GetPlayerID(), + false, + function(____, heroAchievementsObject, grantedHeroRankRewardsObject) + self:sendProfileToClient( + player, + profileData, + statsData, + games, + steamId, + playerName, + steamId64, + grantedLevelRewardsObject, + heroAchievementsObject, + grantedHeroRankRewardsObject + ) + end + ) + return true + end) + if not ____try then + ____hasReturned, ____returnValue = ____catch(____hasReturned) + end + if ____hasReturned then + return ____returnValue + end + end + else + ____print( + nil, + "[MiniProfileServer] Fallback history HTTP " .. tostring(result.StatusCode) + ) + end + self:loadHeroAchievementsForProfile( + steamId, + player:GetPlayerID(), + false, + function(____, heroAchievementsObject, grantedHeroRankRewardsObject) + self:sendProfileToClient( + player, + profileData, + statsData, + {}, + steamId, + playerName, + steamId64, + grantedLevelRewardsObject, + heroAchievementsObject, + grantedHeroRankRewardsObject + ) + end + ) + end) +end +function MiniProfileServer.prototype.sendProfileToClient(self, player, profileData, statsData, gamesToSend, steamId, playerName, steamId64, grantedLevelRewardsObject, heroAchievementsObject, grantedHeroRankRewardsObject) + if grantedLevelRewardsObject == nil then + grantedLevelRewardsObject = {} + end + if heroAchievementsObject == nil then + heroAchievementsObject = {} + end + if grantedHeroRankRewardsObject == nil then + grantedHeroRankRewardsObject = {} + end + ____print( + nil, + (("[MiniProfileServer] Профиль отправлен: games=" .. tostring(#gamesToSend)) .. ", steam64=") .. steamId64 + ) + local gamesObject = {} + __TS__ArrayForEach( + gamesToSend, + function(____, game, index) + gamesObject[tostring(index)] = game + end + ) + CustomGameEventManager:Send_ServerToPlayer(player, "player_profile_data", { + profile = profileData, + stats = statsData, + recentGames = gamesObject, + steam_id = steamId, + player_name = playerName, + player_steamid = steamId64, + profile_level_rewards_granted = grantedLevelRewardsObject, + hero_achievements = heroAchievementsObject, + hero_rank_rewards_granted = grantedHeroRankRewardsObject + }) +end +function MiniProfileServer.prototype.loadHeroAchievementsForProfile(self, steamId, playerId, shouldGrantRankRewards, callback) + local allHeroes = self:getAllAvailableHeroes(playerId) + local request = CreateHTTPRequest("GET", ((self.serverUrl .. "/player/") .. steamId) .. "/history?limit=5000&offset=0") + setApiHeaders(nil, request) + request:Send(function(result) + ____print( + nil, + (("[MiniProfileServer] /history status=" .. tostring(result.StatusCode)) .. " steam_id=") .. steamId + ) + if result.StatusCode < 200 or result.StatusCode >= 300 then + callback( + nil, + self:buildHeroAchievementsObject(allHeroes, {}), + {} + ) + return + end + do + local function ____catch(e) + callback( + nil, + self:buildHeroAchievementsObject(allHeroes, {}), + {} + ) + end + local ____try, ____hasReturned = pcall(function() + local decoded = self:decodeJsonSafe(result.Body) + local games = self:normalizeArrayLike(decoded) + ____print( + nil, + "[MiniProfileServer] history entries=" .. tostring(#games) + ) + do + local i = 0 + while i < math.min(5, #games) do + local g = games[i + 1] + local ____i_60 = i + local ____tostring_59 = tostring + local ____opt_result_50 + if g ~= nil then + ____opt_result_50 = g.hero + end + local ____opt_result_50_54 = ____opt_result_50 + if not ____opt_result_50_54 then + local ____opt_result_53 + if g ~= nil then + ____opt_result_53 = g.hero_name + end + ____opt_result_50_54 = ____opt_result_53 + end + local ____opt_result_50_54_58 = ____opt_result_50_54 + if not ____opt_result_50_54_58 then + local ____opt_result_57 + if g ~= nil then + ____opt_result_57 = g.heroName + end + ____opt_result_50_54_58 = ____opt_result_57 + end + local ____temp_69 = (("[MiniProfileServer] raw[" .. tostring(____i_60)) .. "] hero=") .. ____tostring_59(____opt_result_50_54_58 or "") + local ____tostring_68 = tostring + local ____opt_result_63 + if g ~= nil then + ____opt_result_63 = g.result + end + local ____opt_result_63_67 = ____opt_result_63 + if not ____opt_result_63_67 then + local ____opt_result_66 + if g ~= nil then + ____opt_result_66 = g.match_result + end + ____opt_result_63_67 = ____opt_result_66 + end + local ____temp_74 = ____temp_69 .. " result=" .. ____tostring_68(____opt_result_63_67 or "") + local ____tostring_73 = tostring + local ____opt_result_72 + if g ~= nil then + ____opt_result_72 = g.is_win + end + local ____temp_91 = ____temp_74 .. " is_win=" .. ____tostring_73(____opt_result_72) + local ____tostring_90 = tostring + local ____opt_result_77 + if g ~= nil then + ____opt_result_77 = g.difficulty + end + local ____opt_result_77_81 = ____opt_result_77 + if not ____opt_result_77_81 then + local ____opt_result_80 + if g ~= nil then + ____opt_result_80 = g.difficulty_name + end + ____opt_result_77_81 = ____opt_result_80 + end + local ____opt_result_77_81_85 = ____opt_result_77_81 + if not ____opt_result_77_81_85 then + local ____opt_result_84 + if g ~= nil then + ____opt_result_84 = g.mode + end + ____opt_result_77_81_85 = ____opt_result_84 + end + local ____opt_result_77_81_85_89 = ____opt_result_77_81_85 + if not ____opt_result_77_81_85_89 then + local ____opt_result_88 + if g ~= nil then + ____opt_result_88 = g.game_mode + end + ____opt_result_77_81_85_89 = ____opt_result_88 + end + ____print( + nil, + ____temp_91 .. " difficulty=" .. ____tostring_90(____opt_result_77_81_85_89 or "") + ) + i = i + 1 + end + end + local aggregated = self:aggregateHeroAchievements(games) + local heroAchievementsObject = self:buildHeroAchievementsObject(allHeroes, aggregated) + local grantedHeroRankRewardsObject = {} + if shouldGrantRankRewards then + grantedHeroRankRewardsObject = self:grantPendingHeroRankRewards(playerId, heroAchievementsObject) + end + callback(nil, heroAchievementsObject, grantedHeroRankRewardsObject) + end) + if not ____try then + ____catch(____hasReturned) + end + end + end) +end +function MiniProfileServer.prototype.getAllAvailableHeroes(self, playerId) + local heroes = {} + local storeManager = StoreManager:getInstance() + local enabledHeroes = self:getEnabledHeroesFromHeroList() + for heroName in pairs(enabledHeroes) do + do + local heroInfo = HERO_LIST_TABLE[heroName] + local isDonateHero = (heroInfo and heroInfo.isDonate) == true + if isDonateHero and not storeManager:hasUnlockedHero( + playerId, + tostring(heroName), + heroInfo.storeItemId + ) then + goto __continue185 + end + heroes[#heroes + 1] = tostring(heroName) + end + ::__continue185:: + end + __TS__ArraySort( + heroes, + function(____, a, b) return a < b and -1 or (a > b and 1 or 0) end + ) + return heroes +end +function MiniProfileServer.prototype.getEnabledHeroesFromHeroList(self) + local enabledHeroes = {} + local rawKv = LoadKeyValues("scripts/npc/herolist.txt") + local ____temp_96 = rawKv and rawKv.CustomHeroList + if ____temp_96 == nil then + ____temp_96 = rawKv + end + local heroList = ____temp_96 + if heroList then + for heroName in pairs(heroList) do + local count = tonumber(tostring(heroList[heroName])) or 0 + if count > 0 or count == -1 then + enabledHeroes[tostring(heroName)] = true + end + end + end + if #__TS__ObjectKeys(enabledHeroes) == 0 then + for heroName in pairs(HERO_LIST_TABLE) do + enabledHeroes[tostring(heroName)] = true + end + end + return enabledHeroes +end +function MiniProfileServer.prototype.aggregateHeroAchievements(self, games) + local stats = {} + local enabledHeroes = self:getEnabledHeroesFromHeroList() + local function normalizeDifficultyKey(____, rawDifficulty) + local key = string.lower(tostring(rawDifficulty or "normal")) + if key == "easy" or key == "difficulty_easy" or key == "лёгкая" or key == "легкая" then + return "easy" + end + if key == "normal" or key == "difficulty_normal" or key == "обычная" or key == "normal_mode" then + return "normal" + end + if key == "hard" or key == "difficulty_hard" or key == "сложная" then + return "hard" + end + if key == "impossible" or key == "difficulty_impossible" or key == "невозможная" or key == "imposible" then + return "impossible" + end + if key == "death_sentence" or key == "difficulty_death_sentence" or key == "смертельный приговор" or key == "deathsentence" then + return "death_sentence" + end + return "normal" + end + local function getRankByDifficulty(____, difficulty) + local key = normalizeDifficultyKey(nil, difficulty) + if key == "easy" then + return 1 + end + if key == "normal" then + return 3 + end + if key == "hard" then + return 6 + end + if key == "impossible" then + return 8 + end + if key == "death_sentence" then + return 10 + end + return 0 + end + local function isWinResult(____, game) + local ____opt_result_99 + if game ~= nil then + ____opt_result_99 = game.is_win + end + local directWin = ____opt_result_99 + if directWin == true or directWin == 1 or tostring(directWin) == "1" then + return true + end + local ____tonumber_103 = tonumber + local ____opt_result_102 + if game ~= nil then + ____opt_result_102 = game.result + end + local numericResult = ____tonumber_103(____opt_result_102) + if numericResult == 1 then + return true + end + local ____tostring_111 = tostring + local ____opt_result_106 + if game ~= nil then + ____opt_result_106 = game.result + end + local ____opt_result_106_110 = ____opt_result_106 + if not ____opt_result_106_110 then + local ____opt_result_109 + if game ~= nil then + ____opt_result_109 = game.match_result + end + ____opt_result_106_110 = ____opt_result_109 + end + local key = string.lower(____tostring_111(____opt_result_106_110 or "")) + return key == "win" or key == "victory" or key == "won" or key == "radiant_win" or key == "dire_win" or key == "success" + end + local function normalizeHeroName(____, rawHero) + local base = tostring(rawHero or "") + if base == "" then + return "" + end + if string.sub(base, 1, 14) == "npc_dota_hero_" then + return base + end + local normalized = "npc_dota_hero_" .. base + if enabledHeroes[normalized] ~= nil then + return normalized + end + return base + end + __TS__ArrayForEach( + games, + function(____, game, index) + if not game or type(game) ~= "table" then + return + end + local hero = normalizeHeroName(nil, game.hero or game.hero_name or game.heroName) + if hero == "" or string.sub(hero, 1, 14) ~= "npc_dota_hero_" then + return + end + if not (enabledHeroes[hero] ~= nil) then + return + end + if not (stats[hero] ~= nil) then + stats[hero] = {games = 0, wins = 0, impossibleWins = 0, bestRank = 0} + end + local heroStats = stats[hero] + heroStats.games = heroStats.games + 1 + local difficulty = normalizeDifficultyKey(nil, game.difficulty or game.difficulty_name or game.mode or game.game_mode) + local win = isWinResult(nil, game) + if index < 8 then + local ____temp_125 = (((((("[MiniProfileServer] normalized[" .. tostring(index)) .. "] hero=") .. hero) .. " difficulty=") .. difficulty) .. " win=") .. tostring(win) + local ____tostring_119 = tostring + local ____opt_result_114 + if game ~= nil then + ____opt_result_114 = game.result + end + local ____opt_result_114_118 = ____opt_result_114 + if not ____opt_result_114_118 then + local ____opt_result_117 + if game ~= nil then + ____opt_result_117 = game.match_result + end + ____opt_result_114_118 = ____opt_result_117 + end + local ____tostring_119_result_124 = ____tostring_119(____opt_result_114_118 or "") + local ____tostring_123 = tostring + local ____opt_result_122 + if game ~= nil then + ____opt_result_122 = game.is_win + end + ____print( + nil, + ____temp_125 .. ((" raw_result=" .. ____tostring_119_result_124) .. " raw_is_win=") .. ____tostring_123(____opt_result_122) + ) + end + if win then + heroStats.wins = heroStats.wins + 1 + heroStats.bestRank = math.max( + heroStats.bestRank, + getRankByDifficulty(nil, difficulty) + ) + if difficulty == "impossible" then + heroStats.impossibleWins = heroStats.impossibleWins + 1 + end + end + end + ) + local keys = __TS__ObjectKeys(stats) + ____print( + nil, + "[MiniProfileServer] aggregated heroes=" .. tostring(#keys) + ) + __TS__ArrayForEach( + keys, + function(____, heroName, idx) + if idx >= 8 then + return + end + local s = stats[heroName] + ____print( + nil, + ((((("[MiniProfileServer] stats[" .. heroName) .. "] games=") .. tostring(s.games)) .. " wins=") .. tostring(s.wins)) .. ((" best_rank=" .. tostring(s.bestRank)) .. " impossible_wins=") .. tostring(s.impossibleWins) + ) + end + ) + return stats +end +function MiniProfileServer.prototype.buildHeroAchievementsObject(self, allHeroes, aggregated) + local out = {} + __TS__ArrayForEach( + allHeroes, + function(____, heroName, index) + local heroStats = aggregated[heroName] or ({games = 0, wins = 0, impossibleWins = 0, bestRank = 0}) + local games = heroStats.games + local wins = heroStats.wins + local impossibleWins = heroStats.impossibleWins + local winRate = games > 0 and math.floor(wins / games * 1000) / 10 or 0 + out[tostring(index)] = { + hero = heroName, + games = games, + wins = wins, + win_rate = winRate, + best_rank = heroStats.bestRank, + impossible_wins = impossibleWins + } + end + ) + return out +end +function MiniProfileServer.prototype.getHeroRankRewardAmount(self, tier) + if tier == "rank1" then + return 100 + end + if tier == "rank3" then + return 200 + end + if tier == "rank6" then + return 400 + end + if tier == "rank8a" then + return 800 + end + if tier == "rank8b" then + return 1200 + end + if tier == "rank8c" then + return 2000 + end + return 0 +end +function MiniProfileServer.prototype.getHeroAchievementTier(self, bestRank, impossibleWins) + if bestRank >= 8 then + if impossibleWins >= 100 then + return "rank8c" + end + if impossibleWins >= 10 then + return "rank8b" + end + return "rank8a" + end + if bestRank >= 6 then + return "rank6" + end + if bestRank >= 3 then + return "rank3" + end + if bestRank >= 1 then + return "rank1" + end + return "rank0" +end +function MiniProfileServer.prototype.grantPendingHeroRankRewards(self, playerId, heroAchievementsObject) + local storeManager = StoreManager:getInstance() + if not self.grantedHeroRankRewardsInSession:has(playerId) then + self.grantedHeroRankRewardsInSession:set( + playerId, + __TS__New(Set) + ) + end + local sessionGranted = self.grantedHeroRankRewardsInSession:get(playerId) + local grantedNow = {} + local rewardsToCheck = { + "rank1", + "rank3", + "rank6", + "rank8a", + "rank8b", + "rank8c" + } + local achievementsArray = self:normalizeArrayLike(heroAchievementsObject) + local grantedIndex = 0 + __TS__ArrayForEach( + achievementsArray, + function(____, entry) + local hero = tostring(entry.hero or "") + if hero == "" then + return + end + local bestRank = tonumber(entry.best_rank) or 0 + local impossibleWins = tonumber(entry.impossible_wins) or 0 + local currentTier = self:getHeroAchievementTier(bestRank, impossibleWins) + local currentTierIndex = __TS__ArrayIndexOf(rewardsToCheck, currentTier) + if currentTierIndex < 0 then + return + end + do + local i = 0 + while i <= currentTierIndex do + do + local tier = rewardsToCheck[i + 1] + local amount = self:getHeroRankRewardAmount(tier) + if amount <= 0 then + goto __continue248 + end + local rewardItemId = (("hero_rank_reward_" .. hero) .. "_") .. tier + if sessionGranted:has(rewardItemId) then + ____print(nil, "[MiniProfileServer] reward skip (session) " .. rewardItemId) + goto __continue248 + end + if storeManager:hasPurchasedItem(playerId, rewardItemId) then + ____print(nil, "[MiniProfileServer] reward skip (already purchased) " .. rewardItemId) + sessionGranted:add(rewardItemId) + goto __continue248 + end + local added = storeManager:addFreeCurrency(playerId, amount) + if not added then + ____print( + nil, + (("[MiniProfileServer] reward addFreeCurrency failed " .. rewardItemId) .. " amount=") .. tostring(amount) + ) + goto __continue248 + end + storeManager:savePurchaseToServer(playerId, rewardItemId, "hero_rank_reward") + ____print( + nil, + (("[MiniProfileServer] reward granted " .. rewardItemId) .. " amount=") .. tostring(amount) + ) + sessionGranted:add(rewardItemId) + grantedNow[tostring(grantedIndex)] = {hero = hero, tier = tier, freeCurrency = amount} + grantedIndex = grantedIndex + 1 + end + ::__continue248:: + i = i + 1 + end + end + end + ) + return grantedNow +end +function MiniProfileServer.prototype.loadMatchPlayersFromServer(self, player, matchId, rowId, steamId) + if rowId == nil then + rowId = 0 + end + if steamId == nil then + steamId = "" + end + local idsToTry = {} + local function addId(____, value) + if value > 0 and __TS__ArrayIndexOf(idsToTry, value) == -1 then + idsToTry[#idsToTry + 1] = value + end + end + addId(nil, matchId) + addId(nil, rowId) + local endpoints = {} + __TS__ArrayForEach( + idsToTry, + function(____, id) + endpoints[#endpoints + 1] = ((self.serverUrl .. "/game/") .. tostring(id)) .. "/players" + end + ) + if steamId ~= "" then + __TS__ArrayForEach( + idsToTry, + function(____, id) + endpoints[#endpoints + 1] = ((((self.serverUrl .. "/player/") .. steamId) .. "/history?match_id=") .. tostring(id)) .. "&limit=10&offset=0" + endpoints[#endpoints + 1] = ((((self.serverUrl .. "/player/") .. steamId) .. "/history?game_id=") .. tostring(id)) .. "&limit=10&offset=0" + endpoints[#endpoints + 1] = ((((self.serverUrl .. "/player/") .. steamId) .. "/history?id=") .. tostring(id)) .. "&limit=10&offset=0" + end + ) + end + local function collectObjectValues(____, obj) + local out = {} + if not obj or type(obj) ~= "table" then + return out + end + for key in pairs(obj) do + out[#out + 1] = obj[key] + end + return out + end + local extractPlayers + extractPlayers = function(____, source) + if not source then + return {} + end + if __TS__ArrayIsArray(source) then + local normalizedArray = __TS__ArrayFilter( + self:normalizeArrayLike(source), + function(____, item) return item and type(item) == "table" end + ) + local playersFromParty = {} + __TS__ArrayForEach( + normalizedArray, + function(____, item) + local nestedParty = item.party_players or item.partyPlayers or item.players or item.participants or item.match_players + if nestedParty then + local nested = extractPlayers(nil, nestedParty) + __TS__ArrayForEach( + nested, + function(____, p) + local ____temp_126 = #playersFromParty + 1 + playersFromParty[____temp_126] = p + return ____temp_126 + end + ) + end + end + ) + if #playersFromParty > 0 then + return playersFromParty + end + return normalizedArray + end + if type(source) == "table" then + local directPlayers = source.players or source.participants or source.match_players or source.party_players or source.partyPlayers or source.items or source.rows or source.data + if directPlayers then + return extractPlayers(nil, directPlayers) + end + return __TS__ArrayFilter( + collectObjectValues(nil, source), + function(____, item) return item and type(item) == "table" end + ) + end + return {} + end + local tryRequest + tryRequest = function(____, index) + if index >= #endpoints then + CustomGameEventManager:Send_ServerToPlayer(player, "match_players_data", {error = "Не удалось загрузить участников матча", match_id = matchId, players = {}}) + return + end + local request = CreateHTTPRequest("GET", endpoints[index + 1]) + setApiHeaders(nil, request) + request:Send(function(result) + if result.StatusCode < 200 or result.StatusCode >= 300 then + ____print( + nil, + (("[MiniProfileServer] match players endpoint failed: " .. endpoints[index + 1]) .. " status=") .. tostring(result.StatusCode) + ) + tryRequest(nil, index + 1) + return + end + local decoded = self:decodeJsonSafe(result.Body) + if decoded == nil then + ____print(nil, "[MiniProfileServer] match players decode failed: " .. endpoints[index + 1]) + tryRequest(nil, index + 1) + return + end + local players = extractPlayers(nil, decoded) + if #players == 0 and index < #endpoints - 1 then + ____print(nil, ("[MiniProfileServer] Endpoint " .. endpoints[index + 1]) .. " returned 0 players, trying next") + tryRequest(nil, index + 1) + return + end + local playersObject = {} + __TS__ArrayForEach( + players, + function(____, item, idx) + playersObject[tostring(idx)] = item + end + ) + CustomGameEventManager:Send_ServerToPlayer(player, "match_players_data", {match_id = matchId, players = playersObject}) + end) + end + tryRequest(nil, 0) +end +function MiniProfileServer.prototype.addBigInts(self, a, b) + local maxLen = math.max( + string.len(a), + string.len(b) + ) + a = string.rep( + "0", + maxLen - string.len(a) + ) .. a + b = string.rep( + "0", + maxLen - string.len(b) + ) .. b + local result = "" + local carry = 0 + do + local i = maxLen + while i >= 1 do + local digitA = tonumber(string.sub(a, i, i)) or 0 + local digitB = tonumber(string.sub(b, i, i)) or 0 + local sum = digitA + digitB + carry + result = tostring(sum % 10) .. result + carry = math.floor(sum / 10) + i = i - 1 + end + end + if carry > 0 then + result = tostring(carry) .. result + end + return result +end +function MiniProfileServer.prototype.subtractBigInts(self, a, b) + local maxLen = math.max( + string.len(a), + string.len(b) + ) + a = string.rep( + "0", + maxLen - string.len(a) + ) .. a + b = string.rep( + "0", + maxLen - string.len(b) + ) .. b + local result = "" + local borrow = 0 + do + local i = maxLen + while i >= 1 do + local digitA = tonumber(string.sub(a, i, i)) or 0 + local digitB = tonumber(string.sub(b, i, i)) or 0 + local diff = digitA - digitB - borrow + if diff < 0 then + diff = diff + 10 + borrow = 1 + else + borrow = 0 + end + result = tostring(diff) .. result + i = i - 1 + end + end + result = (string.gsub(result, "^0+", "")) + if result == "" then + result = "0" + end + return result +end +function MiniProfileServer.prototype.decodeJsonSafe(self, rawBody) + do + local function ____catch(e) + local body = tostring(rawBody) + local firstObj = {string.find(body, "{", 1, true)} + local firstArr = {string.find(body, "[", 1, true)} + local first = nil + if firstObj ~= nil and firstArr ~= nil then + first = math.min(firstObj[1], firstArr[1]) + elseif firstObj ~= nil then + first = firstObj[1] + elseif firstArr ~= nil then + first = firstArr[1] + end + if first == nil then + return true, nil + end + local lastObj = {string.find(body, "}", -1, true)} + local lastArr = {string.find(body, "]", -1, true)} + local last = nil + if lastObj ~= nil and lastArr ~= nil then + last = math.max(lastObj[1], lastArr[1]) + elseif lastObj ~= nil then + last = lastObj[1] + elseif lastArr ~= nil then + last = lastArr[1] + end + if last == nil or last < first then + return true, nil + end + local sliced = string.sub(body, first, last) + do + local function ____catch(e2) + return true, nil + end + local ____try, ____hasReturned, ____returnValue = pcall(function() + return true, {json.decode(sliced)} + end) + if not ____try then + ____hasReturned, ____returnValue = ____catch(____hasReturned) + end + if ____hasReturned then + return true, ____returnValue + end + end + end + local ____try, ____hasReturned, ____returnValue = pcall(function() + return true, {json.decode(rawBody)} + end) + if not ____try then + ____hasReturned, ____returnValue = ____catch(____hasReturned) + end + if ____hasReturned then + return ____returnValue + end + end +end +function MiniProfileServer.prototype.grantPendingProfileLevelRewards(self, playerId, profileLevel) + if profileLevel <= 0 then + return {} + end + local storeManager = StoreManager:getInstance() + if not self.grantedProfileRewardsInSession:has(playerId) then + self.grantedProfileRewardsInSession:set( + playerId, + __TS__New(Set) + ) + end + local sessionGrantedSet = self.grantedProfileRewardsInSession:get(playerId) + local grantedNow = {} + __TS__ArrayForEach( + self.profileLevelRewards, + function(____, reward) + if reward.level > profileLevel then + return + end + local rewardItemId = "profile_level_reward_" .. tostring(reward.level) + if sessionGrantedSet:has(rewardItemId) then + return + end + if storeManager:hasPurchasedItem(playerId, rewardItemId) then + sessionGrantedSet:add(rewardItemId) + return + end + local added = storeManager:addFreeCurrency(playerId, reward.freeCurrency) + if not added then + return + end + storeManager:savePurchaseToServer(playerId, rewardItemId, "profile_level_reward") + sessionGrantedSet:add(rewardItemId) + grantedNow[#grantedNow + 1] = {level = reward.level, freeCurrency = reward.freeCurrency} + ____print( + nil, + (((("[MiniProfileServer] Выдана награда за уровень " .. tostring(reward.level)) .. ": +") .. tostring(reward.freeCurrency)) .. " осколков игроку ") .. tostring(playerId) + ) + end + ) + return grantedNow +end +if IsServer() then + (function() + Timers:CreateTimer( + 1, + function() + do + local function ____catch(____error) + return true, nil + end + local ____try, ____hasReturned, ____returnValue = pcall(function() + local miniProfileServer = ____exports.MiniProfileServer:getInstance() + return true, nil + end) + if not ____try then + ____hasReturned, ____returnValue = ____catch(____hasReturned) + end + if ____hasReturned then + return ____returnValue + end + end + end + ) + end)(nil) +end +return ____exports diff --git a/scripts/vscripts/modifiers/load_core_modifiers.lua b/scripts/vscripts/modifiers/load_core_modifiers.lua new file mode 100644 index 0000000..94c74d3 --- /dev/null +++ b/scripts/vscripts/modifiers/load_core_modifiers.lua @@ -0,0 +1,5 @@ +--[[ Generated with https://github.com/TypeScriptToLua/TypeScriptToLua ]] +local ____exports = {} +require("modifiers.modifier_stats_multiplier") +require("abilities.modifiers.modifier_swamp_slow") +return ____exports diff --git a/scripts/vscripts/modifiers/modifier_cutscene_lock.lua b/scripts/vscripts/modifiers/modifier_cutscene_lock.lua new file mode 100644 index 0000000..21c5a3f --- /dev/null +++ b/scripts/vscripts/modifiers/modifier_cutscene_lock.lua @@ -0,0 +1,30 @@ +local ____lualib = require("lualib_bundle") +local __TS__Class = ____lualib.__TS__Class +local __TS__ClassExtends = ____lualib.__TS__ClassExtends +local __TS__Decorate = ____lualib.__TS__Decorate +local ____exports = {} +local ____dota_ts_adapter = require("lib.dota_ts_adapter") +local BaseModifier = ____dota_ts_adapter.BaseModifier +local registerModifier = ____dota_ts_adapter.registerModifier +____exports.modifier_cutscene_lock = __TS__Class() +local modifier_cutscene_lock = ____exports.modifier_cutscene_lock +modifier_cutscene_lock.name = "modifier_cutscene_lock" +modifier_cutscene_lock.____file_path = "scripts/vscripts/modifiers/modifier_cutscene_lock.lua" +__TS__ClassExtends(modifier_cutscene_lock, BaseModifier) +function modifier_cutscene_lock.prototype.IsHidden(self) + return true +end +function modifier_cutscene_lock.prototype.IsPurgable(self) + return false +end +function modifier_cutscene_lock.prototype.CheckState(self) + return {[MODIFIER_STATE_SILENCED] = true, [MODIFIER_STATE_MUTED] = true, [MODIFIER_STATE_COMMAND_RESTRICTED] = true, [MODIFIER_STATE_NO_HEALTH_BAR] = true} +end +modifier_cutscene_lock = __TS__Decorate( + modifier_cutscene_lock, + modifier_cutscene_lock, + {registerModifier(nil)}, + {kind = "class", name = "modifier_cutscene_lock"} +) +____exports.modifier_cutscene_lock = modifier_cutscene_lock +return ____exports diff --git a/scripts/vscripts/modifiers/modifier_cutscene_npc_lock.lua b/scripts/vscripts/modifiers/modifier_cutscene_npc_lock.lua new file mode 100644 index 0000000..e8a30da --- /dev/null +++ b/scripts/vscripts/modifiers/modifier_cutscene_npc_lock.lua @@ -0,0 +1,30 @@ +local ____lualib = require("lualib_bundle") +local __TS__Class = ____lualib.__TS__Class +local __TS__ClassExtends = ____lualib.__TS__ClassExtends +local __TS__Decorate = ____lualib.__TS__Decorate +local ____exports = {} +local ____dota_ts_adapter = require("lib.dota_ts_adapter") +local BaseModifier = ____dota_ts_adapter.BaseModifier +local registerModifier = ____dota_ts_adapter.registerModifier +____exports.modifier_cutscene_npc_lock = __TS__Class() +local modifier_cutscene_npc_lock = ____exports.modifier_cutscene_npc_lock +modifier_cutscene_npc_lock.name = "modifier_cutscene_npc_lock" +modifier_cutscene_npc_lock.____file_path = "scripts/vscripts/modifiers/modifier_cutscene_npc_lock.lua" +__TS__ClassExtends(modifier_cutscene_npc_lock, BaseModifier) +function modifier_cutscene_npc_lock.prototype.IsHidden(self) + return true +end +function modifier_cutscene_npc_lock.prototype.IsPurgable(self) + return false +end +function modifier_cutscene_npc_lock.prototype.CheckState(self) + return {[MODIFIER_STATE_STUNNED] = true, [MODIFIER_STATE_NO_HEALTH_BAR] = true, [MODIFIER_STATE_INVULNERABLE] = true} +end +modifier_cutscene_npc_lock = __TS__Decorate( + modifier_cutscene_npc_lock, + modifier_cutscene_npc_lock, + {registerModifier(nil)}, + {kind = "class", name = "modifier_cutscene_npc_lock"} +) +____exports.modifier_cutscene_npc_lock = modifier_cutscene_npc_lock +return ____exports diff --git a/scripts/vscripts/modifiers/modifier_stats_multiplier.lua b/scripts/vscripts/modifiers/modifier_stats_multiplier.lua new file mode 100644 index 0000000..66b85f8 --- /dev/null +++ b/scripts/vscripts/modifiers/modifier_stats_multiplier.lua @@ -0,0 +1,292 @@ +local ____lualib = require("lualib_bundle") +local __TS__Class = ____lualib.__TS__Class +local __TS__ClassExtends = ____lualib.__TS__ClassExtends +local __TS__Decorate = ____lualib.__TS__Decorate +local __TS__ObjectKeys = ____lualib.__TS__ObjectKeys +local __TS__ObjectValues = ____lualib.__TS__ObjectValues +local __TS__Delete = ____lualib.__TS__Delete +local ____exports = {} +local ____dota_ts_adapter = require("lib.dota_ts_adapter") +local BaseAbility = ____dota_ts_adapter.BaseAbility +local BaseModifier = ____dota_ts_adapter.BaseModifier +local registerAbility = ____dota_ts_adapter.registerAbility +local registerModifier = ____dota_ts_adapter.registerModifier +local ____incoming_damage_reduction_combine = require("utils.incoming_damage_reduction_combine") +local removeStatsMultiplierIncomingDamageReductionSource = ____incoming_damage_reduction_combine.removeStatsMultiplierIncomingDamageReductionSource +local setStatsMultiplierIncomingDamageReductionSource = ____incoming_damage_reduction_combine.setStatsMultiplierIncomingDamageReductionSource +____exports.MODIFIER_STATS_MULTIPLIER = "modifier_stats_multiplier" +--- Стабильные id источников для снятия в `modifier_arsenal_dynamic_stats.OnDestroy`. +____exports.ARSENAL_STATS_MULTIPLIER_SOURCE_ID = { + all_stats_pct = "arsenal_dyn_pct_all_stats", + strength_pct = "arsenal_dyn_pct_str", + agility_pct = "arsenal_dyn_pct_agi", + intellect_pct = "arsenal_dyn_pct_int", + outgoing_damage_pct = "arsenal_dyn_pct_outgoing", + incoming_damage_reduction_pct = "arsenal_dyn_pct_incoming", + attack_speed_pct = "arsenal_dyn_pct_as", + move_speed_pct = "arsenal_dyn_pct_ms", + base_damage_pct = "arsenal_dyn_pct_basedmg" +} +____exports.ability_stats_multiplier = __TS__Class() +local ability_stats_multiplier = ____exports.ability_stats_multiplier +ability_stats_multiplier.name = "ability_stats_multiplier" +ability_stats_multiplier.____file_path = "scripts/vscripts/modifiers/modifier_stats_multiplier.lua" +__TS__ClassExtends(ability_stats_multiplier, BaseAbility) +function ability_stats_multiplier.prototype.GetIntrinsicModifierName(self) + return "modifier_stats_multiplier" +end +function ability_stats_multiplier.prototype.IsHidden(self) + return true +end +ability_stats_multiplier = __TS__Decorate( + ability_stats_multiplier, + ability_stats_multiplier, + {registerAbility(nil)}, + {kind = "class", name = "ability_stats_multiplier"} +) +____exports.ability_stats_multiplier = ability_stats_multiplier +local globalModifierFunctionSourceSeq = 1 +function ____exports.createModifierFunctionSourceId(self, prefix) + local safePrefix = prefix and #prefix > 0 and prefix or "source" + local id = (safePrefix .. "_") .. tostring(globalModifierFunctionSourceSeq) + globalModifierFunctionSourceSeq = globalModifierFunctionSourceSeq + 1 + return id +end +local function sourceKind(self, s) + return s.kind or "attributes" +end +local function getStorage(self, hero) + local holder = hero + if not holder.__statsMultiplierStorage then + holder.__statsMultiplierStorage = {} + end + return holder.__statsMultiplierStorage +end +local function hasSources(self, hero) + return #__TS__ObjectKeys(getStorage(nil, hero)) > 0 +end +--- Только карточные % к атрибутам — для видимости баффа и тултипа «%». +local function hasAttributeSources(self, hero) + local storage = getStorage(nil, hero) + for ____, source in ipairs(__TS__ObjectValues(storage)) do + if sourceKind(nil, source) == "attributes" then + return true + end + end + return false +end +--- Сбросить кэш сумм (арсенал/колоды меняются без смены id источника). +function ____exports.invalidateStatsMultiplierSumCache(self, hero) + if not hero or not IsValidEntity(hero) then + return + end + local c = hero.__statsMultiplierSumCache + if c then + c.dirty = true + end +end +local function rebuildSumCache(self, hero, cache) + local storage = getStorage(nil, hero) + local byKind = {} + for ____, source in ipairs(__TS__ObjectValues(storage)) do + local k = sourceKind(nil, source) + byKind[k] = (byKind[k] or 0) + source:resolver() + end + cache.byKind = byKind + cache.dirty = false +end +local function sumForKind(self, hero, kind) + local holder = hero + local cache = holder.__statsMultiplierSumCache + if not cache then + cache = {dirty = true, byKind = {}} + holder.__statsMultiplierSumCache = cache + end + if cache.dirty then + rebuildSumCache(nil, hero, cache) + end + return cache.byKind[kind] or 0 +end +local function ensureModifier(self, hero) + if not hero:FindModifierByName(____exports.MODIFIER_STATS_MULTIPLIER) then + local sourceAbility = hero:FindAbilityByName("ability_stats_multiplier") or hero:FindAbilityByName("ability_stacking_crit") + hero:AddNewModifier(hero, sourceAbility, ____exports.MODIFIER_STATS_MULTIPLIER, {}) + end +end +local function removeModifierIfUnused(self, hero) + if hasSources(nil, hero) then + return + end + local mod = hero:FindModifierByName(____exports.MODIFIER_STATS_MULTIPLIER) + if mod then + mod:Destroy() + end + ____exports.invalidateStatsMultiplierSumCache(nil, hero) +end +--- Регистрирует вклад в `modifier_stats_multiplier`. +-- - `kind === "attributes"` (по умолчанию): сумма % умножается на **базовые** Str/Agi/Int и даёт бонус к соответствующему атрибуту (карты). +-- - `all_stats_pct`: тот же % к **каждой** базовой характеристике Str/Agi/Int (арсенал). +-- - `strength_pct` / `agility_pct` / `intellect_pct`: % только к **базовому** Str/Agi/Int (арсенал). +-- - Остальные `kind`: сумма идёт в соответствующий `ModifierFunction` (исходящий/входящий урон, AS%/MS%, урон от базы атаки) — арсенал; +-- для `incoming_damage_reduction_pct` сумма **не** добавляется линейно в движок, а сжимается формулой с убыванием (см. `incoming_damage_reduction_combine`). +function ____exports.setStatsMultiplierSource(self, hero, sourceId, resolver, kind) + if kind == nil then + kind = "attributes" + end + if not hero or not IsValidEntity(hero) then + return + end + getStorage(nil, hero)[sourceId] = {resolver = resolver, kind = kind} + if kind == "incoming_damage_reduction_pct" then + setStatsMultiplierIncomingDamageReductionSource(nil, hero, sourceId, resolver) + end + ____exports.invalidateStatsMultiplierSumCache(nil, hero) + ensureModifier(nil, hero) +end +function ____exports.removeStatsMultiplierSource(self, hero, sourceId) + if not hero or not IsValidEntity(hero) then + return + end + local storage = getStorage(nil, hero) + local removed = storage[sourceId] + if removed and sourceKind(nil, removed) == "incoming_damage_reduction_pct" then + removeStatsMultiplierIncomingDamageReductionSource(nil, hero, sourceId) + end + __TS__Delete(storage, sourceId) + ____exports.invalidateStatsMultiplierSumCache(nil, hero) + removeModifierIfUnused(nil, hero) +end +____exports.modifier_stats_multiplier = __TS__Class() +local modifier_stats_multiplier = ____exports.modifier_stats_multiplier +modifier_stats_multiplier.name = "modifier_stats_multiplier" +modifier_stats_multiplier.____file_path = "scripts/vscripts/modifiers/modifier_stats_multiplier.lua" +__TS__ClassExtends(modifier_stats_multiplier, BaseModifier) +function modifier_stats_multiplier.prototype.____constructor(self, ...) + BaseModifier.prototype.____constructor(self, ...) + self.lock = false +end +function modifier_stats_multiplier.prototype.IsHidden(self) + local parent = self:GetParent() + if not parent or not IsValidEntity(parent) then + return true + end + return not hasAttributeSources(nil, parent) +end +function modifier_stats_multiplier.prototype.RemoveOnDeath(self) + return false +end +function modifier_stats_multiplier.prototype.IsPurgable(self) + return false +end +function modifier_stats_multiplier.prototype.GetTexture(self) + return "nevermore_dark_lord" +end +function modifier_stats_multiplier.prototype.DeclareFunctions(self) + return { + MODIFIER_PROPERTY_STATS_STRENGTH_BONUS, + MODIFIER_PROPERTY_STATS_AGILITY_BONUS, + MODIFIER_PROPERTY_STATS_INTELLECT_BONUS, + MODIFIER_PROPERTY_TOOLTIP, + MODIFIER_PROPERTY_DAMAGEOUTGOING_PERCENTAGE, + MODIFIER_PROPERTY_ATTACKSPEED_PERCENTAGE, + MODIFIER_PROPERTY_MOVESPEED_BONUS_PERCENTAGE, + MODIFIER_PROPERTY_BASEDAMAGEOUTGOING_PERCENTAGE + } +end +function modifier_stats_multiplier.prototype.sumResolverPercentAttributes(self) + local hero = self:GetParent() + if not hero or not IsValidEntity(hero) then + return 0 + end + return sumForKind(nil, hero, "attributes") +end +function modifier_stats_multiplier.prototype.OnTooltip(self) + if not IsServer() then + return 0 + end + return self:sumResolverPercentAttributes() +end +function modifier_stats_multiplier.prototype.GetModifierBonusStats_Strength(self) + if self.lock then + return 0 + end + local hero = self:GetParent() + if not hero or not IsValidEntity(hero) then + return 0 + end + self.lock = true + local base = hero:GetStrength() or 0 + local shared = sumForKind(nil, hero, "all_stats_pct") + local pct = self:sumResolverPercentAttributes() + shared + sumForKind(nil, hero, "strength_pct") + local v = base * (pct / 100) + self.lock = false + return v +end +function modifier_stats_multiplier.prototype.GetModifierBonusStats_Agility(self) + if self.lock then + return 0 + end + local hero = self:GetParent() + if not hero or not IsValidEntity(hero) then + return 0 + end + self.lock = true + local base = hero:GetAgility() or 0 + local shared = sumForKind(nil, hero, "all_stats_pct") + local pct = self:sumResolverPercentAttributes() + shared + sumForKind(nil, hero, "agility_pct") + local v = base * (pct / 100) + self.lock = false + return v +end +function modifier_stats_multiplier.prototype.GetModifierBonusStats_Intellect(self) + if self.lock then + return 0 + end + local hero = self:GetParent() + if not hero or not IsValidEntity(hero) then + return 0 + end + self.lock = true + local base = hero:GetIntellect(true) or 0 + local shared = sumForKind(nil, hero, "all_stats_pct") + local pct = self:sumResolverPercentAttributes() + shared + sumForKind(nil, hero, "intellect_pct") + local v = base * (pct / 100) + self.lock = false + return v +end +function modifier_stats_multiplier.prototype.GetModifierDamageOutgoing_Percentage(self) + local hero = self:GetParent() + if not hero or not IsValidEntity(hero) then + return 0 + end + return sumForKind(nil, hero, "outgoing_damage_pct") +end +function modifier_stats_multiplier.prototype.GetModifierAttackSpeedPercentage(self) + local hero = self:GetParent() + if not hero or not IsValidEntity(hero) then + return 0 + end + return sumForKind(nil, hero, "attack_speed_pct") +end +function modifier_stats_multiplier.prototype.GetModifierMoveSpeedBonus_Percentage(self) + local hero = self:GetParent() + if not hero or not IsValidEntity(hero) then + return 0 + end + return sumForKind(nil, hero, "move_speed_pct") +end +function modifier_stats_multiplier.prototype.GetModifierBaseDamageOutgoing_Percentage(self) + local hero = self:GetParent() + if not hero or not IsValidEntity(hero) then + return 0 + end + return sumForKind(nil, hero, "base_damage_pct") +end +modifier_stats_multiplier = __TS__Decorate( + modifier_stats_multiplier, + modifier_stats_multiplier, + {registerModifier(nil)}, + {kind = "class", name = "modifier_stats_multiplier"} +) +____exports.modifier_stats_multiplier = modifier_stats_multiplier +return ____exports diff --git a/scripts/vscripts/music_player.lua b/scripts/vscripts/music_player.lua new file mode 100644 index 0000000..8554dce --- /dev/null +++ b/scripts/vscripts/music_player.lua @@ -0,0 +1,247 @@ +local ____lualib = require("lualib_bundle") +local __TS__Class = ____lualib.__TS__Class +local Map = ____lualib.Map +local __TS__New = ____lualib.__TS__New +local __TS__ArrayFilter = ____lualib.__TS__ArrayFilter +local __TS__ArrayReduce = ____lualib.__TS__ArrayReduce +local __TS__ArrayIndexOf = ____lualib.__TS__ArrayIndexOf +local ____exports = {} +local ____DayNightCycleManager = require("DayNightCycleManager") +local DayNightCycleManager = ____DayNightCycleManager.DayNightCycleManager +____exports.MusicPlayer = __TS__Class() +local MusicPlayer = ____exports.MusicPlayer +MusicPlayer.name = "MusicPlayer" +MusicPlayer.____file_path = "scripts/vscripts/music_player.lua" +function MusicPlayer.prototype.____constructor(self) + self.musicController = { + playlists = {day = {tracks = { + {name = "zvezdi_v_luzhax_30_02", duration = 186}, + {name = "DSPRITE_Iyul", duration = 138}, + {name = "Parnishka_ELLA_-_My_umrem_gde_to_posredi_nochi", duration = 194}, + {name = "AHa-TakeOnMe", duration = 226}, + {name = "AJR_WorldsSmallestViolin", duration = 71}, + {name = "BangersOnlyfawlinPrestonPabloChillOnly-Circles", duration = 96}, + {name = "jump", duration = 38}, + {name = "DaftPunk-GetLucky", duration = 98}, + {name = "EarthWindAndFire-LetsGroove", duration = 92}, + {name = "EarthWindAndFire-September", duration = 81}, + {name = "GroverWashingtonJrBillWithers-JustTheTwoOfUs", duration = 61}, + {name = "HOLLYFLAME-Sorvannye_plany", duration = 151}, + {name = "GarikPogorelov-Rozovye_tapochki", duration = 163}, + {name = "Redbone-ComeandGetYourLove", duration = 66}, + {name = "SergejjTrofimov-GorodSochi", duration = 181} + }, currentTrackIndex = 0}, night = {tracks = {{name = "zvezdi_v_luzhax_30_02", duration = 186}}, currentTrackIndex = 0}}, + currentSound = nil, + soundState = __TS__New(Map), + timer = nil + } + self:Initialize() +end +function MusicPlayer.getInstance(self) + if not ____exports.MusicPlayer.instance then + ____exports.MusicPlayer.instance = __TS__New(____exports.MusicPlayer) + end + return ____exports.MusicPlayer.instance +end +function MusicPlayer.prototype.Initialize(self) + local dayNightManager = DayNightCycleManager:getInstance() + self.musicController.playlists.day.currentTrackIndex = RandomInt(0, #self.musicController.playlists.day.tracks - 1) + self.musicController.playlists.night.currentTrackIndex = RandomInt(0, #self.musicController.playlists.night.tracks - 1) + self:registerEventHandlers() + self:startInitialTrack(dayNightManager) +end +function MusicPlayer.prototype.registerEventHandlers(self) + CustomGameEventManager:RegisterListener( + "toggle_music", + function(userId, event) + local playerId = event.PlayerID + local ____temp_0 = self.musicController.soundState:get(playerId) + if ____temp_0 == nil then + ____temp_0 = true + end + local currentState = ____temp_0 + self.musicController.soundState:set(playerId, not currentState) + if self.musicController.currentSound then + if not currentState then + self:EmitSoundToClient(playerId, self.musicController.currentSound, true) + else + self:EmitSoundToClient(playerId, self.musicController.currentSound, false) + end + end + CustomGameEventManager:Send_ServerToAllClients("update_music_state", {isEnabled = not currentState, playerId = playerId}) + end + ) + CustomGameEventManager:RegisterListener( + "day_night_timer_update", + function(_, data) + local dayNightManager = DayNightCycleManager:getInstance() + local expectedDuration = data.isDay == 1 and dayNightManager:GetDayDuration() or dayNightManager:GetNightDuration() + if data.timeLeft == 1 then + local currentIsNight = data.isDay == 0 + self:StopCurrentTrack() + Timers:CreateTimer( + 0.5, + function() + self:PlayNextTrack(currentIsNight) + return nil + end + ) + end + end + ) +end +function MusicPlayer.prototype.getRandomTrackWithDuration(self, playlist, maxDuration, currentTrackName) + local availableTracks = __TS__ArrayFilter( + playlist.tracks, + function(____, track) return track.duration <= maxDuration and (not currentTrackName or track.name ~= currentTrackName) end + ) + if #availableTracks == 0 then + local shortestTrack = __TS__ArrayReduce( + playlist.tracks, + function(____, shortest, current) return current.duration < shortest.duration and (not currentTrackName or current.name ~= currentTrackName) and current or shortest end + ) + local index = __TS__ArrayIndexOf(playlist.tracks, shortestTrack) + return {track = shortestTrack, index = index} + end + local randomIndex = RandomInt(0, #availableTracks - 1) + local track = availableTracks[randomIndex + 1] + local originalIndex = __TS__ArrayIndexOf(playlist.tracks, track) + return {track = track, index = originalIndex} +end +function MusicPlayer.prototype.getTimeLeft(self, dayNightManager, isNight) + return dayNightManager:GetTimeLeft() +end +function MusicPlayer.prototype.updatePlaylistAndTimer(self, playlist, track, isNight) + if self.musicController.timer then + Timers:RemoveTimer(self.musicController.timer) + end + self.musicController.timer = Timers:CreateTimer( + track.duration, + function() + local dayNightManager = DayNightCycleManager:getInstance() + local currentIsNight = not dayNightManager:IsDaytime() + if currentIsNight ~= isNight then + local newPlaylist = currentIsNight and self.musicController.playlists.night or self.musicController.playlists.day + local timeLeft = self:getTimeLeft(dayNightManager, currentIsNight) + local ____temp_1 = self:getRandomTrackWithDuration(newPlaylist, timeLeft) + local newTrack = ____temp_1.track + local randomIndex = ____temp_1.index + newPlaylist.currentTrackIndex = randomIndex + self:PlayNextTrackWithTrack(currentIsNight, newTrack, randomIndex) + return nil + end + local timeLeft = self:getTimeLeft(dayNightManager, currentIsNight) + local ____temp_2 = self:getRandomTrackWithDuration(playlist, timeLeft, track.name) + local nextTrack = ____temp_2.track + local nextIndex = ____temp_2.index + playlist.currentTrackIndex = nextIndex + self:PlayNextTrackWithTrack(currentIsNight, nextTrack, nextIndex) + return nil + end + ) +end +function MusicPlayer.prototype.startInitialTrack(self, dayNightManager) + local isNight = not dayNightManager:IsDaytime() + local playlist = isNight and self.musicController.playlists.night or self.musicController.playlists.day + local timeLeft = self:getTimeLeft(dayNightManager, isNight) + local ____temp_3 = self:getRandomTrackWithDuration(playlist, timeLeft) + local track = ____temp_3.track + local randomIndex = ____temp_3.index + playlist.currentTrackIndex = randomIndex + self:playTrack(track) + self:updatePlaylistAndTimer(playlist, track, isNight) + local ____CustomGameEventManager_Send_ServerToAllClients_5 = CustomGameEventManager.Send_ServerToAllClients + local ____temp_4 = self.musicController.soundState:get(0) + if ____temp_4 == nil then + ____temp_4 = true + end + ____CustomGameEventManager_Send_ServerToAllClients_5(CustomGameEventManager, "update_music_state", {isEnabled = ____temp_4}) +end +function MusicPlayer.prototype.StopCurrentTrack(self) + if self.musicController.currentSound then + self:broadcastSoundToValidPlayers(self.musicController.currentSound, false) + end +end +function MusicPlayer.prototype.ResumeCurrentTrack(self) + if self.musicController.currentSound then + self:broadcastSoundToValidPlayers(self.musicController.currentSound, true) + end +end +function MusicPlayer.prototype.PlayNextTrackWithTrack(self, isNight, track, trackIndex) + self:StopCurrentTrack() + Timers:CreateTimer( + 0.5, + function() + self:playTrack(track) + local playlist = isNight and self.musicController.playlists.night or self.musicController.playlists.day + self:updatePlaylistAndTimer(playlist, track, isNight) + return nil + end + ) +end +function MusicPlayer.prototype.PlayNextTrack(self, isNight) + local playlist = isNight and self.musicController.playlists.night or self.musicController.playlists.day + local track = playlist.tracks[playlist.currentTrackIndex + 1] + self:PlayNextTrackWithTrack(isNight, track, playlist.currentTrackIndex) +end +function MusicPlayer.prototype.playTrack(self, track) + self.musicController.currentSound = track.name + GameRules:SendCustomMessage(("Now playing: " .. track.name) .. "", 0, 0) + self:broadcastSoundToValidPlayers(track.name, true) +end +function MusicPlayer.prototype.broadcastSoundToValidPlayers(self, soundName, play) + do + local playerID = 0 + while playerID < DOTA_MAX_TEAM_PLAYERS do + if PlayerResource:IsValidPlayer(playerID) then + local ____temp_6 = self.musicController.soundState:get(playerID) + if ____temp_6 == nil then + ____temp_6 = true + end + local playerState = ____temp_6 + if playerState then + self:EmitSoundToClient(playerID, soundName, play) + end + end + playerID = playerID + 1 + end + end +end +function MusicPlayer.prototype.EmitSoundToClient(self, playerId, soundName, play) + local player = PlayerResource:GetPlayer(playerId) + if player then + if play then + EmitSoundOnClient(soundName, player) + else + StopSoundOn(soundName, player) + end + end +end +function MusicPlayer.prototype.ToggleSound(self) + self.musicController.soundState:forEach(function(____, state, playerId) + self.musicController.soundState:set(playerId, not state) + if self.musicController.currentSound then + if not state then + self:EmitSoundToClient(playerId, self.musicController.currentSound, true) + else + self:EmitSoundToClient(playerId, self.musicController.currentSound, false) + end + end + CustomGameEventManager:Send_ServerToAllClients("update_music_state", {isEnabled = not state, playerId = playerId}) + end) + local ____CustomGameEventManager_Send_ServerToAllClients_8 = CustomGameEventManager.Send_ServerToAllClients + local ____temp_7 = self.musicController.soundState:get(0) + if ____temp_7 == nil then + ____temp_7 = true + end + ____CustomGameEventManager_Send_ServerToAllClients_8(CustomGameEventManager, "update_music_state", {isEnabled = ____temp_7}) +end +function MusicPlayer.prototype.IsSoundEnabled(self) + local ____temp_9 = self.musicController.soundState:get(0) + if ____temp_9 == nil then + ____temp_9 = true + end + return ____temp_9 +end +____exports.InitializeMusicPlayer = function() return ____exports.MusicPlayer:getInstance() end +return ____exports diff --git a/scripts/vscripts/player_info.lua b/scripts/vscripts/player_info.lua new file mode 100644 index 0000000..fbe5505 --- /dev/null +++ b/scripts/vscripts/player_info.lua @@ -0,0 +1,51 @@ +local ____lualib = require("lualib_bundle") +local __TS__Class = ____lualib.__TS__Class +local __TS__ObjectEntries = ____lualib.__TS__ObjectEntries +local __TS__New = ____lualib.__TS__New +local ____exports = {} +____exports.PlayerInfoClass = __TS__Class() +local PlayerInfoClass = ____exports.PlayerInfoClass +PlayerInfoClass.name = "PlayerInfoClass" +PlayerInfoClass.____file_path = "scripts/vscripts/player_info.lua" +function PlayerInfoClass.prototype.____constructor(self) + self.playersIdBySteamId = {} +end +function PlayerInfoClass.prototype.InitPlayer(self, playerId, info) + local steamId = tostring(PlayerResource:GetSteamAccountID(playerId)) + self.playersIdBySteamId[steamId] = playerId + self:UpdatePlayerInfo(playerId, info) +end +function PlayerInfoClass.prototype.UpdatePlayerInfo(self, playerId, info) + for ____, ____value in ipairs(__TS__ObjectEntries(info)) do + local key = ____value[1] + local value = ____value[2] + if type(value) == "string" then + do + pcall(function() + local parsedValue = JSON:parse(value) + info[key] = parsedValue + end) + end + end + end + CustomNetTables:SetTableValue( + "player_info", + tostring(playerId), + info + ) + if GameRules.donateManager and type(GameRules.donateManager.CheckForChangeDonate) == "function" then + GameRules.donateManager:CheckForChangeDonate(playerId) + end +end +function PlayerInfoClass.prototype.GetPlayerInfo(self, playerId) + local info = CustomNetTables:GetTableValue( + "player_info", + tostring(playerId) + ) + return info or nil +end +function PlayerInfoClass.prototype.GetPlayerIdBySteamId(self, steamId) + return self.playersIdBySteamId[steamId] +end +____exports.PlayerInfo = __TS__New(____exports.PlayerInfoClass) +return ____exports diff --git a/scripts/vscripts/player_profile_manager.lua b/scripts/vscripts/player_profile_manager.lua new file mode 100644 index 0000000..21489b8 --- /dev/null +++ b/scripts/vscripts/player_profile_manager.lua @@ -0,0 +1,66 @@ +local ____lualib = require("lualib_bundle") +local __TS__Class = ____lualib.__TS__Class +local __TS__New = ____lualib.__TS__New +local __TS__ArrayIsArray = ____lualib.__TS__ArrayIsArray +local ____exports = {} +local ____server_config = require("server_config") +local SERVER_CONFIG = ____server_config.SERVER_CONFIG +local ____api_helper = require("api_helper") +local encodeApiBody = ____api_helper.encodeApiBody +local setApiHeaders = ____api_helper.setApiHeaders +local ____store_manager = require("store_manager") +local StoreManager = ____store_manager.StoreManager +____exports.PlayerProfileManager = __TS__Class() +local PlayerProfileManager = ____exports.PlayerProfileManager +PlayerProfileManager.name = "PlayerProfileManager" +PlayerProfileManager.____file_path = "scripts/vscripts/player_profile_manager.lua" +function PlayerProfileManager.prototype.____constructor(self) + self:setupListeners() +end +function PlayerProfileManager.getInstance(self) + if not ____exports.PlayerProfileManager.instance then + ____exports.PlayerProfileManager.instance = __TS__New(____exports.PlayerProfileManager) + end + return ____exports.PlayerProfileManager.instance +end +function PlayerProfileManager.prototype.setupListeners(self) + CustomGameEventManager:RegisterListener( + "create_player_profile", + function(source, event) + local playerId = event.player_id + local playerName = event.player_name + local steamId = tostring(PlayerResource:GetSteamAccountID(playerId)) + self:createPlayerProfile(playerId, steamId, playerName) + end + ) +end +function PlayerProfileManager.prototype.createPlayerProfile(self, playerId, steamId, playerName) + local request = CreateHTTPRequest("POST", SERVER_CONFIG.API_URL .. "/player") + setApiHeaders(nil, request) + local dataToSend = {steam_id = steamId, player_name = playerName} + request:SetHTTPRequestRawPostBody( + "application/json", + encodeApiBody(nil, dataToSend) + ) + request:Send(function(result) + if result.StatusCode >= 200 and result.StatusCode < 300 then + do + pcall(function() + local decoded = {json.decode(result.Body)} + local responseData = nil + if __TS__ArrayIsArray(decoded) and #decoded > 0 then + responseData = decoded[1] + elseif decoded and type(decoded) == "table" then + responseData = decoded + end + if responseData then + StoreManager:getInstance():handleLoginPlayerApiResponse(playerId, responseData) + end + end) + end + elseif result.StatusCode == 409 then + else + end + end) +end +return ____exports diff --git a/scripts/vscripts/quests/clean_harbor_flag_markers.lua b/scripts/vscripts/quests/clean_harbor_flag_markers.lua new file mode 100644 index 0000000..0e24974 --- /dev/null +++ b/scripts/vscripts/quests/clean_harbor_flag_markers.lua @@ -0,0 +1,189 @@ +local ____lualib = require("lualib_bundle") +local __TS__Class = ____lualib.__TS__Class +local Set = ____lualib.Set +local __TS__New = ____lualib.__TS__New +local __TS__ArrayMap = ____lualib.__TS__ArrayMap +local ____exports = {} +local ____SpawnManager = require("SpawnManager") +local SpawnManager = ____SpawnManager.SpawnManager +local POINT_PATTERN = "point_clean_harbor_flag_*" +local POINT_FALLBACK = "point_clean_harbor_flag" +local MARK_PARTICLE = "particles/bloodbath_circle.vpcf" +--- Радиус круга и допустимой зоны установки флага (совпадает с item_clean_harbor). +____exports.CLEAN_HARBOR_FLAG_PLACEMENT_RADIUS = 350 +local CleanHarborFlagMarkers = __TS__Class() +CleanHarborFlagMarkers.name = "CleanHarborFlagMarkers" +CleanHarborFlagMarkers.____file_path = "scripts/vscripts/quests/clean_harbor_flag_markers.lua" +function CleanHarborFlagMarkers.prototype.____constructor(self) + self.spots = {} + self.positionsResolved = false + self.visible = false +end +function CleanHarborFlagMarkers.prototype.show(self) + if not IsServer() then + return + end + self:resolvePositions() + if #self.spots == 0 then + Timers:CreateTimer( + 1, + function() + self:show() + return nil + end + ) + return + end + if self.visible then + return + end + for ____, spot in ipairs(self.spots) do + do + if spot.particleId ~= nil then + goto __continue8 + end + local particleId = ParticleManager:CreateParticle(MARK_PARTICLE, PATTACH_WORLDORIGIN, nil) + ParticleManager:SetParticleControl(particleId, 0, spot.position) + ParticleManager:SetParticleControl( + particleId, + 1, + Vector(____exports.CLEAN_HARBOR_FLAG_PLACEMENT_RADIUS, 0, 0) + ) + spot.particleId = particleId + end + ::__continue8:: + end + self.visible = true + print(("[clean_harbor_flag] показан круг установки флага (" .. tostring(#self.spots)) .. ")") +end +function CleanHarborFlagMarkers.prototype.hide(self) + if not IsServer() then + return + end + for ____, spot in ipairs(self.spots) do + do + if spot.particleId == nil then + goto __continue13 + end + ParticleManager:DestroyParticle(spot.particleId, false) + ParticleManager:ReleaseParticleIndex(spot.particleId) + spot.particleId = nil + end + ::__continue13:: + end + self.visible = false +end +function CleanHarborFlagMarkers.prototype.isNearPlacement(self, position, radius) + if radius == nil then + radius = ____exports.CLEAN_HARBOR_FLAG_PLACEMENT_RADIUS + end + if not IsServer() then + return false + end + self:resolvePositions() + for ____, spot in ipairs(self.spots) do + local dist = (position - spot.position):Length2D() + if dist <= radius then + return true + end + end + return false +end +function CleanHarborFlagMarkers.prototype.resolvePositions(self) + if self.positionsResolved and #self.spots > 0 then + return + end + local mapPoints = self:collectMapPoints() + if #mapPoints > 0 then + self:setSpotsFromEntities(mapPoints) + return + end + local computed = self:computePlacementFromSkeletonZones() + if computed then + self:setSpots({{pointName = "computed_clean_harbor_flag", position = computed}}) + end +end +function CleanHarborFlagMarkers.prototype.collectMapPoints(self) + local seen = __TS__New(Set) + local result = {} + local function addUnique(____, list) + if not list then + return + end + for ____, ent in ipairs(list) do + do + if not ent or ent:IsNull() then + goto __continue28 + end + local index = ent:entindex() + if seen:has(index) then + goto __continue28 + end + seen:add(index) + result[#result + 1] = ent + end + ::__continue28:: + end + end + addUnique( + nil, + Entities:FindAllByName(POINT_PATTERN) + ) + addUnique( + nil, + Entities:FindAllByName(POINT_FALLBACK) + ) + return result +end +function CleanHarborFlagMarkers.prototype.computePlacementFromSkeletonZones(self) + local spawnManager = SpawnManager:getInstance() + local zone1 = spawnManager:GetZoneCentersByPrefixes({"zone_skeletons_"}) + local zone2 = spawnManager:GetZoneCentersByPrefixes({"zone_skeletons2_"}) + if #zone1 == 0 or #zone2 == 0 then + return nil + end + local bestMidpoint + local bestDistance = 2 ^ 1024 + for ____, pos1 in ipairs(zone1) do + for ____, pos2 in ipairs(zone2) do + local dist = (pos1 - pos2):Length2D() + if dist < bestDistance then + bestDistance = dist + bestMidpoint = (pos1 + pos2) / 2 + end + end + end + return bestMidpoint +end +function CleanHarborFlagMarkers.prototype.setSpotsFromEntities(self, entities) + local entries = {} + for ____, ent in ipairs(entities) do + entries[#entries + 1] = { + pointName = ent:GetName(), + position = ent:GetAbsOrigin() + } + end + self:setSpots(entries) +end +function CleanHarborFlagMarkers.prototype.setSpots(self, entries) + self:hide() + self.spots = __TS__ArrayMap( + entries, + function(____, entry) return {pointName = entry.pointName, position = entry.position} end + ) + self.positionsResolved = #self.spots > 0 +end +local cleanHarborFlagMarkers = __TS__New(CleanHarborFlagMarkers) +function ____exports.showCleanHarborFlagPlacementMarkers(self) + cleanHarborFlagMarkers:show() +end +function ____exports.hideCleanHarborFlagPlacementMarkers(self) + cleanHarborFlagMarkers:hide() +end +function ____exports.isNearCleanHarborFlagPlacement(self, position, radius) + if radius == nil then + radius = ____exports.CLEAN_HARBOR_FLAG_PLACEMENT_RADIUS + end + return cleanHarborFlagMarkers:isNearPlacement(position, radius) +end +return ____exports diff --git a/scripts/vscripts/quests/kunkka_shovel_anchor_markers.lua b/scripts/vscripts/quests/kunkka_shovel_anchor_markers.lua new file mode 100644 index 0000000..26cbd1d --- /dev/null +++ b/scripts/vscripts/quests/kunkka_shovel_anchor_markers.lua @@ -0,0 +1,195 @@ +local ____lualib = require("lualib_bundle") +local __TS__Class = ____lualib.__TS__Class +local Set = ____lualib.Set +local __TS__New = ____lualib.__TS__New +local ____exports = {} +--- Клады лопаты: партикл на info_target `point_shovel` / `point_shovel_*` в Hammer. +-- Один случайный крест за матч даёт item_shameful_pipe при копке. +local SHOVEL_POINT_PATTERN = "point_shovel_*" +local SHOVEL_POINT_FALLBACK = "point_shovel" +local SHOVEL_MARK_PARTICLE = "particles/econ/items/kunkka/divine_anchor/hero_kunkka_dafx_skills/kunkka_spell_x_spot_mark_red_fxset.vpcf" +--- Радиус от героя до point_shovel, в котором можно копать. +____exports.SHOVEL_DIG_RADIUS = 100 +local ShovelTreasureMarkers = __TS__Class() +ShovelTreasureMarkers.name = "ShovelTreasureMarkers" +ShovelTreasureMarkers.____file_path = "scripts/vscripts/quests/kunkka_shovel_anchor_markers.lua" +function ShovelTreasureMarkers.prototype.____constructor(self) + self.markers = {} + self.initialized = false +end +function ShovelTreasureMarkers.prototype.initialize(self) + if not IsServer() then + return + end + local trySpawn + trySpawn = function() + self:spawnAllFromMap() + if #self.markers == 0 then + Timers:CreateTimer(1, trySpawn) + end + end + Timers:CreateTimer(0.15, trySpawn) +end +function ShovelTreasureMarkers.prototype.isNearUndugPoint(self, position, radius) + if radius == nil then + radius = ____exports.SHOVEL_DIG_RADIUS + end + if not IsServer() then + return false + end + self:ensureMarkersSpawned() + return self:findNearestUndug(position, radius) ~= nil +end +function ShovelTreasureMarkers.prototype.handleDigAt(self, digPosition, radius) + if radius == nil then + radius = ____exports.SHOVEL_DIG_RADIUS + end + if not IsServer() then + return {removed = false, grantsShamefulPipe = false} + end + self:ensureMarkersSpawned() + local best = self:findNearestUndug(digPosition, radius) + if not best then + print("[shovel_treasure] копка без точки в радиусе " .. tostring(radius)) + return {removed = false, grantsShamefulPipe = false} + end + local grantsShamefulPipe = best.hasShamefulPipe + self:markDugAtPosition(best.position, radius) + print(("[shovel_treasure] вскопано " .. best.pointName) .. (grantsShamefulPipe and " (позорная труба)" or "")) + return {removed = true, grantsShamefulPipe = grantsShamefulPipe, pointName = best.pointName} +end +function ShovelTreasureMarkers.prototype.markDugAtPosition(self, position, radius) + for ____, entry in ipairs(self.markers) do + do + if entry.dug then + goto __continue13 + end + local dist = (position - entry.position):Length2D() + if dist <= radius then + self:destroyMarkerParticle(entry) + entry.dug = true + end + end + ::__continue13:: + end +end +function ShovelTreasureMarkers.prototype.collectMapPoints(self) + local seen = __TS__New(Set) + local result = {} + local function addUnique(____, list) + if not list then + return + end + for ____, ent in ipairs(list) do + do + if not ent or ent:IsNull() then + goto __continue20 + end + local idx = ent:entindex() + if seen:has(idx) then + goto __continue20 + end + seen:add(idx) + result[#result + 1] = ent + end + ::__continue20:: + end + end + addUnique( + nil, + Entities:FindAllByName(SHOVEL_POINT_PATTERN) + ) + addUnique( + nil, + Entities:FindAllByName(SHOVEL_POINT_FALLBACK) + ) + return result +end +function ShovelTreasureMarkers.prototype.ensureMarkersSpawned(self) + if #self.markers > 0 then + return + end + self:spawnAllFromMap() +end +function ShovelTreasureMarkers.prototype.spawnAllFromMap(self) + local points = self:collectMapPoints() + if #points == 0 then + print((("[shovel_treasure] на карте нет точек " .. SHOVEL_POINT_PATTERN) .. " или ") .. SHOVEL_POINT_FALLBACK) + return + end + self:clearAllMarkerParticles() + self.markers = {} + self.initialized = true + for ____, ent in ipairs(points) do + local position = ent:GetAbsOrigin() + local pointName = ent:GetName() + local particleId = ParticleManager:CreateParticle(SHOVEL_MARK_PARTICLE, PATTACH_WORLDORIGIN, nil) + ParticleManager:SetParticleControl(particleId, 0, position) + local ____self_markers_0 = self.markers + ____self_markers_0[#____self_markers_0 + 1] = { + pointName = pointName, + position = position, + particleId = particleId, + dug = false, + hasShamefulPipe = false + } + end + local treasureIndex = RandomInt(0, #self.markers - 1) + self.markers[treasureIndex + 1].hasShamefulPipe = true + self.treasurePointName = self.markers[treasureIndex + 1].pointName + print((("[shovel_treasure] маркеров: " .. tostring(#self.markers)) .. ", труба на: ") .. (self.treasurePointName or "—")) +end +function ShovelTreasureMarkers.prototype.findNearestUndug(self, position, radius) + local best + local bestDist = radius + 1 + for ____, entry in ipairs(self.markers) do + do + if entry.dug then + goto __continue31 + end + local dist = (position - entry.position):Length2D() + if dist <= radius and dist < bestDist then + best = entry + bestDist = dist + end + end + ::__continue31:: + end + return best +end +function ShovelTreasureMarkers.prototype.clearAllMarkerParticles(self) + for ____, entry in ipairs(self.markers) do + self:destroyMarkerParticle(entry) + end +end +function ShovelTreasureMarkers.prototype.destroyMarkerParticle(self, entry) + if entry.particleId == nil then + return + end + local pfx = entry.particleId + entry.particleId = nil + ParticleManager:DestroyParticle(pfx, true) + ParticleManager:ReleaseParticleIndex(pfx) +end +local shovelTreasureMarkers = __TS__New(ShovelTreasureMarkers) +function ____exports.initializeKunkkaShovelAnchorMarkers(self) + shovelTreasureMarkers:initialize() +end +function ____exports.isNearUndugShovelPoint(self, position, radius) + if radius == nil then + radius = ____exports.SHOVEL_DIG_RADIUS + end + return shovelTreasureMarkers:isNearUndugPoint(position, radius) +end +function ____exports.handleShovelDigAt(self, digPosition, radius) + if radius == nil then + radius = ____exports.SHOVEL_DIG_RADIUS + end + return shovelTreasureMarkers:handleDigAt(digPosition, radius) +end +--- +-- @deprecated используй handleShovelDigAt +function ____exports.removeKunkkaAnchorMarkerAtDig(self, digPosition) + return ____exports.handleShovelDigAt(nil, digPosition).removed +end +return ____exports diff --git a/scripts/vscripts/quests/quest_reward_scaling.lua b/scripts/vscripts/quests/quest_reward_scaling.lua new file mode 100644 index 0000000..f1f0649 --- /dev/null +++ b/scripts/vscripts/quests/quest_reward_scaling.lua @@ -0,0 +1,42 @@ +--[[ Generated with https://github.com/TypeScriptToLua/TypeScriptToLua ]] +local ____exports = {} +--- Максимум игроков, между которыми делится командный пул наград квеста. +____exports.QUEST_REWARD_MAX_PLAYERS = 4 +--- Золото на одного игрока при полной команде → суммарный пул команды. +function ____exports.questTeamGold(self, perPlayerGold) + if perPlayerGold <= 0 then + return 0 + end + return math.floor(perPlayerGold * ____exports.QUEST_REWARD_MAX_PLAYERS) +end +--- Сколько частей при делении награды (не больше игроков в матче и не больше лимита). +function ____exports.getQuestRewardSplitCount(self, activePlayerCount) + if activePlayerCount <= 0 then + return 1 + end + return math.min(activePlayerCount, ____exports.QUEST_REWARD_MAX_PLAYERS) +end +--- Делит сумму поровну; +1 к первым `remainder` игрокам. +function ____exports.splitQuestTeamReward(self, total, splitCount) + if total <= 0 or splitCount <= 0 then + return {} + end + local share = math.floor(total / splitCount) + local remainder = total % splitCount + local parts = {} + do + local i = 0 + while i < splitCount do + parts[#parts + 1] = share + (i < remainder and 1 or 0) + i = i + 1 + end + end + return parts +end +--- Доля золота на игрока при текущем размере команды (для UI). +function ____exports.getQuestGoldPerPlayerShare(self, teamGold, activePlayerCount) + local splitCount = ____exports.getQuestRewardSplitCount(nil, activePlayerCount) + local parts = ____exports.splitQuestTeamReward(nil, teamGold, splitCount) + return parts[1] or 0 +end +return ____exports diff --git a/scripts/vscripts/quests/questeventhandlers.lua b/scripts/vscripts/quests/questeventhandlers.lua new file mode 100644 index 0000000..bd98fe2 --- /dev/null +++ b/scripts/vscripts/quests/questeventhandlers.lua @@ -0,0 +1,1511 @@ +--[[ Generated with https://github.com/TypeScriptToLua/TypeScriptToLua ]] +local ____exports = {} +local ____modifier_pet_buff_npc_squirrel_event = require("abilities.modifiers.pets.modifier_pet_buff_npc_squirrel_event") +local modifier_pet_buff_npc_squirrel_event = ____modifier_pet_buff_npc_squirrel_event.modifier_pet_buff_npc_squirrel_event +local ____modifier_pet_buff_npc_pig_event = require("abilities.modifiers.pets.modifier_pet_buff_npc_pig_event") +local modifier_pet_buff_npc_pig_event = ____modifier_pet_buff_npc_pig_event.modifier_pet_buff_npc_pig_event +local ____modifier_pet_buff_npc_wolf_event = require("abilities.modifiers.pets.modifier_pet_buff_npc_wolf_event") +local modifier_pet_buff_npc_wolf_event = ____modifier_pet_buff_npc_wolf_event.modifier_pet_buff_npc_wolf_event +local ____clean_harbor_flag_markers = require("quests.clean_harbor_flag_markers") +local hideCleanHarborFlagPlacementMarkers = ____clean_harbor_flag_markers.hideCleanHarborFlagPlacementMarkers +local showCleanHarborFlagPlacementMarkers = ____clean_harbor_flag_markers.showCleanHarborFlagPlacementMarkers +local ____QuestSystem = require("quests.QuestSystem") +local QuestSystem = ____QuestSystem.QuestSystem +local ____QuestSystem = require("quests.QuestSystem") +local QuestState = ____QuestSystem.QuestState +local ____recipes = require("cooking.recipes") +local openRecipe = ____recipes.openRecipe +local ____entity_radius = require("utils.entity_radius") +local findNearestByClassname = ____entity_radius.findNearestByClassname +--- Общая утилита для квестов «принеси X предметов» +local function handleQuestItemTurnIn(self, hero, options) + local questSystem = QuestSystem:getInstance() + local shouldRemoveFromHero = options.removeFromHero ~= false + do + local slot = 0 + while slot < 9 do + do + local item = hero:GetItemInSlot(slot) + if not item or item:IsNull() or item:GetAbilityName() ~= options.itemName then + goto __continue3 + end + if options.useCharges then + local itemcount = item:GetCurrentCharges() + if itemcount < options.requiredCount then + questSystem:setQuestProgress(options.questId, options.objectiveKey, itemcount) + else + questSystem:setQuestProgress(options.questId, options.objectiveKey, options.requiredCount) + if shouldRemoveFromHero then + if itemcount > options.requiredCount then + item:SetCurrentCharges(itemcount - options.requiredCount) + else + item:RemoveSelf() + end + end + if options.timerHandle then + Timers:RemoveTimer(options.timerHandle) + end + end + else + questSystem:setQuestProgress(options.questId, options.objectiveKey, options.requiredCount) + if shouldRemoveFromHero then + item:RemoveSelf() + end + if options.timerHandle then + Timers:RemoveTimer(options.timerHandle) + end + end + break + end + ::__continue3:: + slot = slot + 1 + end + end +end +local OLDMEN_MILK_BASE_REQUIRED = 5 +local oldmenMilkTurnInCount = 0 +--- Общая утилита: квестодатель бросает наградной предмет ближайшему герою +local function launchQuestRewardToNearestHero(self, questGiverName, itemName, searchRadius, launchHeight, launchDistance, launchDuration, targetRandomRadius) + if searchRadius == nil then + searchRadius = 700 + end + if launchHeight == nil then + launchHeight = 150 + end + if launchDistance == nil then + launchDistance = 200 + end + if launchDuration == nil then + launchDuration = 0.7 + end + if targetRandomRadius == nil then + targetRandomRadius = 0 + end + local questGiver = Entities:FindByName(nil, questGiverName) + if not questGiver then + return + end + local nearbyHeroes = FindUnitsInRadius( + DOTA_TEAM_GOODGUYS, + questGiver:GetAbsOrigin(), + nil, + searchRadius, + DOTA_UNIT_TARGET_TEAM_FRIENDLY, + DOTA_UNIT_TARGET_HERO, + DOTA_UNIT_TARGET_FLAG_NOT_ILLUSIONS, + FIND_CLOSEST, + false + ) + if #nearbyHeroes == 0 then + return + end + local hero = nearbyHeroes[1] + local rewardItem = CreateItem(itemName, nil, nil) + if not rewardItem then + return + end + local drop = CreateItemOnPositionForLaunch( + questGiver:GetAbsOrigin(), + rewardItem + ) + if not drop then + return + end + local targetPosition = targetRandomRadius > 0 and hero:GetAbsOrigin() + RandomVector(targetRandomRadius) or hero:GetAbsOrigin() + rewardItem:LaunchLootInitialHeight( + false, + launchHeight, + launchDistance, + launchDuration, + targetPosition + ) + questGiver:FaceTowards(hero:GetAbsOrigin()) +end +local function dropQuestItemToHero(self, questGiver, hero, itemName) + local rewardItem = CreateItem(itemName, nil, nil) + if not rewardItem or rewardItem:IsNull() then + return + end + rewardItem:SetCurrentCharges(0) + local drop = CreateItemOnPositionForLaunch( + questGiver:GetAbsOrigin(), + rewardItem + ) + if not drop then + UTIL_Remove(rewardItem) + return + end + rewardItem:LaunchLootInitialHeight( + false, + 150, + 200, + 0.7, + hero:GetAbsOrigin() + ) + questGiver:FaceTowards(hero:GetAbsOrigin()) +end +--- Кладёт предмет квеста под ноги NPC, если некому кинуть герою. +local function dropQuestItemAtNpcFeet(self, questGiver, itemName) + local rewardItem = CreateItem(itemName, nil, nil) + if not rewardItem or rewardItem:IsNull() then + return + end + rewardItem:SetCurrentCharges(0) + local origin = questGiver:GetAbsOrigin() + local drop = CreateItemOnPositionForLaunch(origin, rewardItem) + if not drop then + UTIL_Remove(rewardItem) + return + end + rewardItem:LaunchLootInitialHeight( + false, + 0, + 80, + 0.4, + origin + ) +end +local function giveShovelToNearestHeroNearKunkka(self) + local questGiver = Entities:FindByName(nil, "npc_quest_giver_kunkka") + if not questGiver or questGiver:IsNull() then + print("[KunkkaQuest] npc_quest_giver_kunkka не найден — лопату не выдали") + return + end + local heroes = FindUnitsInRadius( + DOTA_TEAM_GOODGUYS, + questGiver:GetAbsOrigin(), + nil, + 1400, + DOTA_UNIT_TARGET_TEAM_FRIENDLY, + DOTA_UNIT_TARGET_HERO, + DOTA_UNIT_TARGET_FLAG_NOT_ILLUSIONS, + FIND_CLOSEST, + false + ) + for ____, unit in ipairs(heroes) do + do + local hero = unit + if not hero or hero:IsNull() or hero:FindItemInInventory("item_shovel") then + goto __continue28 + end + dropQuestItemToHero(nil, questGiver, hero, "item_shovel") + return + end + ::__continue28:: + end + dropQuestItemAtNpcFeet(nil, questGiver, "item_shovel") +end +____exports.QuestEventHandlers = { + kunkka_quest_give_claw = { + onAccept = function() + GameRules:SendCustomMessage("#kunkka_quest_give_claw_message_1", 0, 0) + local timer + timer = Timers:CreateTimer(function() + local questGiver = Entities:FindByName(nil, "npc_quest_giver_kunkka") + if questGiver ~= nil then + local nearbyHeroes = FindUnitsInRadius( + DOTA_TEAM_GOODGUYS, + questGiver:GetAbsOrigin(), + nil, + 700, + DOTA_UNIT_TARGET_TEAM_FRIENDLY, + DOTA_UNIT_TARGET_HERO, + DOTA_UNIT_TARGET_FLAG_NOT_ILLUSIONS, + FIND_ANY_ORDER, + false + ) + if #nearbyHeroes > 0 then + handleQuestItemTurnIn(nil, nearbyHeroes[1], { + itemName = "item_wolf_claw", + questId = "kunkka_quest_give_claw", + objectiveKey = "custom_objective", + requiredCount = 10, + timerHandle = timer, + useCharges = true + }) + end + end + return 3 + end) + end, + onComplete = function() + local timertext0 + timertext0 = Timers:CreateTimer( + 0.1, + function() + GameRules:SendCustomMessage("#kunkka_quest_give_claw_message_complete_1", 0, 0) + Timers:RemoveTimer(timertext0) + end + ) + local timertext1 + timertext1 = Timers:CreateTimer( + 4, + function() + GameRules:SendCustomMessage("#kunkka_quest_give_claw_message_complete_2", 0, 0) + Timers:RemoveTimer(timertext1) + end + ) + local timertext2 + timertext2 = Timers:CreateTimer( + 7, + function() + GameRules:SendCustomMessage("#kunkka_quest_give_claw_message_complete_3", 0, 0) + launchQuestRewardToNearestHero( + nil, + "npc_quest_giver_kunkka", + "item_wolf_hat", + 1400, + 0, + 150, + 0.5, + RandomFloat(50, 100) + ) + Timers:RemoveTimer(timertext2) + end + ) + local timertext3 + timertext3 = Timers:CreateTimer( + 10, + function() + GameRules:SendCustomMessage("#kunkka_quest_give_claw_message_complete_4", 0, 0) + Timers:RemoveTimer(timertext3) + end + ) + end, + onFail = function() + end + }, + kunkka_quest_clean_harbor = { + onAccept = function() + GameRules:SendCustomMessage("#kunkka_quest_clean_harbor_message_1", 0, 0) + showCleanHarborFlagPlacementMarkers(nil) + local questGiver = Entities:FindByName(nil, "npc_quest_giver_kunkka") + if questGiver ~= nil then + local item = CreateItem("item_clean_harbor", nil, nil) + if item then + local drop = CreateItemOnPositionForLaunch( + questGiver:GetAbsOrigin(), + item + ) + local dropRadius = RandomFloat(50, 100) + local heroes = FindUnitsInRadius( + DOTA_TEAM_GOODGUYS, + questGiver:GetAbsOrigin(), + nil, + 1400, + DOTA_UNIT_TARGET_TEAM_FRIENDLY, + DOTA_UNIT_TARGET_HERO, + DOTA_UNIT_TARGET_FLAG_NOT_ILLUSIONS, + FIND_CLOSEST, + false + ) + if #heroes > 0 then + item:LaunchLootInitialHeight( + false, + 0, + 150, + 0.5, + heroes[1]:GetAbsOrigin() + RandomVector(dropRadius) + ) + end + end + end + end, + onComplete = function() + hideCleanHarborFlagPlacementMarkers(nil) + GameRules:SendCustomMessage("#kunkka_quest_clean_harbor_message_complete_1", 0, 0) + end, + onFail = function() + hideCleanHarborFlagPlacementMarkers(nil) + GameRules:SendCustomMessage("#kunkka_quest_clean_harbor_message_fail_1", 0, 0) + end + }, + kunkka_quest_kill_lycan = { + onAccept = function() + GameRules:SendCustomMessage("#kunkka_quest_kill_lycan_message_1", 0, 0) + local questSystem = QuestSystem:getInstance() + local lycanAlive = false + local entity = Entities:First() + while entity ~= nil do + if IsValidEntity(entity) then + local unit = entity + if unit.GetUnitName and unit:GetUnitName() == "npc_boss_lycan" then + if not unit:IsNull() and unit.IsAlive and unit:IsAlive() then + lycanAlive = true + break + end + end + end + entity = Entities:Next(entity) + end + if not lycanAlive then + questSystem:setQuestProgress("kunkka_quest_kill_lycan", "kill_lycan", 1) + end + end, + onComplete = function() + local timertext0 + timertext0 = Timers:CreateTimer( + 0.1, + function() + GameRules:SendCustomMessage("#kunkka_quest_kill_lycan_message_complete_1", 0, 0) + Timers:RemoveTimer(timertext0) + end + ) + end + }, + kunkka_quest_kill_satyr_demon = { + onAccept = function() + GameRules:SendCustomMessage("#kunkka_quest_kill_satyr_demon_message_1", 0, 0) + local questSystem = QuestSystem:getInstance() + local satyrAlive = false + local entity = Entities:First() + while entity ~= nil do + if IsValidEntity(entity) then + local unit = entity + if unit.GetUnitName and unit:GetUnitName() == "npc_demon_dragon_satyr" then + if not unit:IsNull() and unit.IsAlive and unit:IsAlive() then + satyrAlive = true + break + end + end + end + entity = Entities:Next(entity) + end + if not satyrAlive then + questSystem:setQuestProgress("kunkka_quest_kill_satyr_demon", "kill_satyr_demon", 1) + end + end, + onComplete = function() + GameRules:SendCustomMessage("#kunkka_quest_kill_satyr_demon_message_complete_1", 0, 0) + end, + onFail = function() + end + }, + kunkka_quest_find_rom = { + onAccept = function() + GameRules:SendCustomMessage("#kunkka_quest_find_rom_message_1", 0, 0) + local spawnPoints = Entities:FindAllByName("kunkka_rom_point") + if #spawnPoints > 0 then + local item = CreateItem("item_rom", nil, nil) + local physicalItem = CreateItemOnPositionForLaunch( + spawnPoints[1]:GetAbsOrigin(), + item + ) + local dropRadius = RandomFloat(50, 100) + if item ~= nil then + item:LaunchLootInitialHeight( + false, + 0, + 150, + 0.5, + spawnPoints[RandomInt(0, #spawnPoints - 1) + 1]:GetAbsOrigin() + RandomVector(dropRadius) + ) + end + end + local timer + timer = Timers:CreateTimer(function() + local questGiver = Entities:FindByName(nil, "npc_quest_giver_kunkka") + if questGiver ~= nil then + local nearbyHeroes = FindUnitsInRadius( + DOTA_TEAM_GOODGUYS, + questGiver:GetAbsOrigin(), + nil, + 700, + DOTA_UNIT_TARGET_TEAM_FRIENDLY, + DOTA_UNIT_TARGET_HERO, + DOTA_UNIT_TARGET_FLAG_NOT_ILLUSIONS, + FIND_ANY_ORDER, + false + ) + if #nearbyHeroes > 0 then + handleQuestItemTurnIn(nil, nearbyHeroes[1], { + itemName = "item_rom", + questId = "kunkka_quest_find_rom", + objectiveKey = "custom_objective", + requiredCount = 1, + timerHandle = timer, + useCharges = false + }) + end + end + return 3 + end) + end, + onComplete = function() + local timertext0 + timertext0 = Timers:CreateTimer( + 0.1, + function() + GameRules:SendCustomMessage("#kunkka_quest_find_rom_message_complete_1", 0, 0) + Timers:RemoveTimer(timertext0) + end + ) + local timertext1 + timertext1 = Timers:CreateTimer( + 4, + function() + GameRules:SendCustomMessage("#kunkka_quest_find_rom_message_complete_2", 0, 0) + Timers:RemoveTimer(timertext1) + end + ) + local timertext2 + timertext2 = Timers:CreateTimer( + 7, + function() + GameRules:SendCustomMessage("#kunkka_quest_find_rom_message_complete_3", 0, 0) + Timers:RemoveTimer(timertext2) + end + ) + local timertext3 + timertext3 = Timers:CreateTimer( + 10, + function() + GameRules:SendCustomMessage("#kunkka_quest_find_rom_message_complete_4", 0, 0) + Timers:RemoveTimer(timertext3) + end + ) + launchQuestRewardToNearestHero( + nil, + "npc_quest_giver_kunkka", + "item_kunkka_sword", + 1400, + 0, + 150, + 0.5, + RandomFloat(50, 100) + ) + end, + onFail = function() + end + }, + kunkka_quest_2 = { + onAccept = function() + GameRules:SendCustomMessage("#kunkka_quest_2_message_1", 0, 0) + end, + onComplete = function() + local timertext0 + timertext0 = Timers:CreateTimer( + 0.1, + function() + GameRules:SendCustomMessage("#kunkka_quest_2_message_complete_1", 0, 0) + Timers:RemoveTimer(timertext0) + end + ) + local timertext1 + timertext1 = Timers:CreateTimer( + 4, + function() + GameRules:SendCustomMessage("#kunkka_quest_2_message_complete_2", 0, 0) + Timers:RemoveTimer(timertext1) + end + ) + end + }, + kunkka_quest_find_anchor = { + onAccept = function() + GameRules:SendCustomMessage("#kunkka_quest_find_anchor_message_1", 0, 0) + giveShovelToNearestHeroNearKunkka(nil) + end, + onComplete = function() + GameRules:SendCustomMessage("#kunkka_quest_find_anchor_message_complete_1", 0, 0) + end, + onFail = function() + GameRules:SendCustomMessage("#kunkka_quest_find_anchor_message_fail_1", 0, 0) + end + }, + denny_quest_kill_sheeps = { + onAccept = function() + GameRules:SendCustomMessage("#denny_quest_kill_sheeps_message_1", 0, 0) + end, + onComplete = function() + local timertext0 + timertext0 = Timers:CreateTimer( + 0.1, + function() + GameRules:SendCustomMessage("#denny_quest_kill_sheeps_message_complete_1", 0, 0) + Timers:RemoveTimer(timertext0) + end + ) + local timertext1 + timertext1 = Timers:CreateTimer( + 4, + function() + GameRules:SendCustomMessage("#denny_quest_kill_sheeps_message_complete_2", 0, 0) + Timers:RemoveTimer(timertext1) + end + ) + end + }, + denny_quest_kill_thieves = { + onAccept = function() + GameRules:SendCustomMessage("#denny_quest_kill_thieves_message_1", 0, 0) + end, + onComplete = function() + local timertext0 + timertext0 = Timers:CreateTimer( + 0.1, + function() + GameRules:SendCustomMessage("#denny_quest_kill_thieves_message_complete_1", 0, 0) + Timers:RemoveTimer(timertext0) + end + ) + local timertext1 + timertext1 = Timers:CreateTimer( + 4, + function() + GameRules:SendCustomMessage("#denny_quest_kill_thieves_message_complete_2", 0, 0) + Timers:RemoveTimer(timertext1) + end + ) + end, + onFail = function() + end + }, + denny_quest_find_a_pet = { + onAccept = function() + GameRules:SendCustomMessage("#denny_quest_find_pet_message_1", 0, 0) + local questGiver = Entities:FindByName(nil, "npc_quest_giver_denny") + local item = CreateItem("item_pet", nil, nil) + if not item then + return + end + local nearbyHeroes = FindUnitsInRadius( + DOTA_TEAM_GOODGUYS, + questGiver:GetAbsOrigin(), + nil, + 14000, + DOTA_UNIT_TARGET_TEAM_FRIENDLY, + DOTA_UNIT_TARGET_HERO, + DOTA_UNIT_TARGET_FLAG_NOT_ILLUSIONS, + FIND_CLOSEST, + false + ) + local physicalItem = CreateItemOnPositionForLaunch( + questGiver and questGiver:GetAbsOrigin(), + item + ) + if #nearbyHeroes > 0 then + item:LaunchLootInitialHeight( + false, + 100, + 150, + 1, + nearbyHeroes[1]:GetAbsOrigin() + ) + end + local point = Entities:FindByName(nil, "pet_point"):GetAbsOrigin() + local particle1 = ParticleManager:CreateParticle("particles/units/heroes/hero_bounty_hunter/bounty_hunter_track_trail_circle.vpcf", PATTACH_WORLDORIGIN, nil) + ParticleManager:SetParticleControl(particle1, 0, point) + local particle2 = ParticleManager:CreateParticle("particles/units/heroes/hero_bounty_hunter/bounty_hunter_track_shield_mark.vpcf", PATTACH_WORLDORIGIN, nil) + ParticleManager:SetParticleControl(particle2, 0, point) + local timer + timer = Timers:CreateTimer(function() + local nearbypet = FindUnitsInRadius( + DOTA_TEAM_GOODGUYS, + point, + nil, + 150, + DOTA_UNIT_TARGET_TEAM_FRIENDLY, + DOTA_UNIT_TARGET_BASIC, + DOTA_UNIT_TARGET_FLAG_OUT_OF_WORLD, + FIND_CLOSEST, + false + ) + if #nearbypet > 0 then + do + local i = 0 + while i < #nearbypet do + local unit = nearbypet[i + 1] + local unitName = unit:GetUnitName() + if unitName == "npc_squirrel_event" or unitName == "npc_pig_event" or unitName == "npc_wolf_event" then + UTIL_Remove(unit) + local modifier + if unitName == "npc_squirrel_event" then + modifier = modifier_pet_buff_npc_squirrel_event.name + elseif unitName == "npc_pig_event" then + modifier = modifier_pet_buff_npc_pig_event.name + else + modifier = modifier_pet_buff_npc_wolf_event.name + end + local heroes = HeroList:GetAllHeroes() + do + local j = 0 + while j < #heroes do + heroes[j + 1]:AddNewModifier( + heroes[j + 1], + getModifierSourceAbility(nil, heroes[j + 1]), + modifier, + {} + ) + j = j + 1 + end + end + local face = point - 1 + local spawnunit = CreateUnitByName( + unitName, + point, + true, + nil, + nil, + DOTA_TEAM_GOODGUYS + ) + spawnunit:FaceTowards(face) + local questSystem = QuestSystem:getInstance() + questSystem:setQuestProgress("denny_quest_find_pet", "find_a_pet", 1) + ParticleManager:DestroyParticle(particle1, false) + ParticleManager:DestroyParticle(particle2, false) + Timers:RemoveTimer(timer) + end + i = i + 1 + end + end + else + local questSystem = QuestSystem:getInstance() + local quest = questSystem.quests:get("denny_quest_find_pet") + local questState = quest and quest.state + if questState == QuestState.FAILED then + Timers:RemoveTimer(timer) + ParticleManager:DestroyParticle(particle1, false) + ParticleManager:DestroyParticle(particle2, false) + return nil + end + end + return 0.66 + end) + end, + onComplete = function() + local timertext0 + timertext0 = Timers:CreateTimer( + 0.1, + function() + GameRules:SendCustomMessage("#denny_quest_find_pet_message_complete_1", 0, 0) + Timers:RemoveTimer(timertext0) + end + ) + local timertext1 + timertext1 = Timers:CreateTimer( + 3, + function() + GameRules:SendCustomMessage("#denny_quest_find_pet_message_complete_2", 0, 0) + Timers:RemoveTimer(timertext1) + end + ) + local timertext2 + timertext2 = Timers:CreateTimer( + 5, + function() + GameRules:SendCustomMessage("#denny_quest_find_pet_message_complete_3", 0, 0) + Timers:RemoveTimer(timertext2) + end + ) + end + }, + denny_quest_1 = { + onAccept = function() + local timertext0 + timertext0 = Timers:CreateTimer( + 0.1, + function() + GameRules:SendCustomMessage("#Denny_quest_1_message_1", 0, 0) + Timers:RemoveTimer(timertext0) + end + ) + local timertext1 + timertext1 = Timers:CreateTimer( + 2, + function() + GameRules:SendCustomMessage("#Denny_quest_1_message_2", 0, 0) + Timers:RemoveTimer(timertext1) + end + ) + local timertext2 + timertext2 = Timers:CreateTimer( + 4, + function() + GameRules:SendCustomMessage("#Denny_quest_1_message_3", 0, 0) + local questGiver = Entities:FindByName(nil, "npc_quest_giver_denny") + local point = Entities:FindByName(nil, "point_denny_fight"):GetAbsOrigin() + if not (questGiver and questGiver:IsInstance(CDOTA_BaseNPC)) then + return + end + questGiver:SetAttackCapability(DOTA_UNIT_CAP_MELEE_ATTACK) + questGiver:MoveToPositionAggressive(toVectorWS(nil, point)) + Timers:RemoveTimer(timertext2) + end + ) + local rofltimer + rofltimer = Timers:CreateTimer( + 9, + function() + local questSystem = QuestSystem:getInstance() + local quest = questSystem.quests:get("denny_quest_kill_sheeps") + local questState = quest and quest.state + if questState == QuestState.FAILED or questState == QuestState.COMPLETED then + Timers:RemoveTimer(rofltimer) + return nil + end + local Randomtext = RandomInt(1, 5) + if Randomtext == 1 then + GameRules:SendCustomMessage("#Denny_quest_1_rofl_message_1", 0, 0) + elseif Randomtext == 2 then + GameRules:SendCustomMessage("#Denny_quest_1_rofl_message_2", 0, 0) + elseif Randomtext == 3 then + GameRules:SendCustomMessage("#Denny_quest_1_rofl_message_3", 0, 0) + elseif Randomtext == 4 then + GameRules:SendCustomMessage("#Denny_quest_1_rofl_message_4", 0, 0) + elseif Randomtext == 5 then + GameRules:SendCustomMessage("#Denny_quest_1_rofl_message_5", 0, 0) + end + return 5 + end + ) + local hasSword = false + local timer + timer = Timers:CreateTimer( + 4, + function() + local questGiver = Entities:FindByName(nil, "npc_quest_giver_denny") + if not questGiver then + return + end + if not hasSword then + local itemEntity = findNearestByClassname( + "dota_item_drop", + questGiver:GetAbsOrigin(), + 450 + ) + if itemEntity ~= nil then + local containedItem = itemEntity:GetContainedItem() + if containedItem and not containedItem:IsNull() and containedItem:GetAbilityName() == "item_kunkka_sword" then + local sword = "item_kunkka_sword" + itemEntity:RemoveSelf() + questGiver:AddItemByName(sword) + questGiver:PickupDroppedItem(containedItem) + end + end + end + do + local i = 0 + while i < 9 do + local item = questGiver:GetItemInSlot(i) + if item and item:GetAbilityName() == "item_kunkka_sword" then + hasSword = true + break + end + i = i + 1 + end + end + local questSystem = QuestSystem:getInstance() + local progress = questSystem:getQuestProgress("denny_quest_kill_sheeps", "training_farm") + if not hasSword and progress >= 9 then + questSystem:failQuest("denny_quest_kill_sheeps") + local questGiver = Entities:FindByName(nil, "npc_quest_giver_denny") + local point = Entities:FindByName(nil, "new_point_denny"):GetAbsOrigin() + if not (questGiver and questGiver:IsInstance(CDOTA_BaseNPC)) then + return + end + questGiver:MoveToPosition(toVectorWS(nil, point)) + Timers:RemoveTimer(timer) + Timers:RemoveTimer(rofltimer) + questGiver:SetAttackCapability(DOTA_UNIT_CAP_NO_ATTACK) + return nil + end + return 0.25 + end + ) + end, + onFail = function() + local timertext0 + timertext0 = Timers:CreateTimer( + 6, + function() + GameRules:SendCustomMessage("#Denny_quest_1_message_fail_1", 0, 0) + Timers:RemoveTimer(timertext0) + end + ) + local timertext1 + timertext1 = Timers:CreateTimer( + 9, + function() + GameRules:SendCustomMessage("#Denny_quest_1_message_fail_2", 0, 0) + Timers:RemoveTimer(timertext1) + end + ) + local timertext2 + timertext2 = Timers:CreateTimer( + 11, + function() + GameRules:SendCustomMessage("#Denny_quest_1_message_fail_3", 0, 0) + Timers:RemoveTimer(timertext2) + end + ) + end, + onComplete = function() + local questGiver = Entities:FindByName(nil, "npc_quest_giver_denny") + local point = Entities:FindByName(nil, "new_point_denny"):GetAbsOrigin() + if not (questGiver and questGiver:IsInstance(CDOTA_BaseNPC)) then + return + end + questGiver:SetAttackCapability(DOTA_UNIT_CAP_NO_ATTACK) + questGiver:MoveToPosition(toVectorWS(nil, point)) + local timertext0 + timertext0 = Timers:CreateTimer( + 0.1, + function() + GameRules:SendCustomMessage("#Denny_quest_1_message_complete_1", 0, 0) + Timers:RemoveTimer(timertext0) + end + ) + local timer + timer = Timers:CreateTimer( + 6, + function() + GameRules:SendCustomMessage("#Denny_quest_1_message_complete_2", 0, 0) + local timertext1 + timertext1 = Timers:CreateTimer( + 2.5, + function() + GameRules:SendCustomMessage("#Denny_quest_1_message_complete_3", 0, 0) + Timers:RemoveTimer(timertext1) + end + ) + do + local i = 0 + while i < 9 do + local item = questGiver:GetItemInSlot(i) + if item and item:GetAbilityName() == "item_kunkka_sword" then + item:RemoveSelf() + break + end + i = i + 1 + end + end + launchQuestRewardToNearestHero( + nil, + "npc_quest_giver_denny", + "item_kunkka_sword", + 1400, + 0, + 150, + 0.5, + RandomFloat(50, 100) + ) + Timers:RemoveTimer(timer) + end + ) + return nil + end + }, + firestar_quest_1 = { + onAccept = function() + GameRules:SendCustomMessage("#firestar_quest_1_message_1", 0, 0) + local timer + timer = Timers:CreateTimer(function() + local questGiver = Entities:FindByName(nil, "npc_quest_giver_firestar") + if questGiver ~= nil then + local nearbyHeroes = FindUnitsInRadius( + DOTA_TEAM_GOODGUYS, + questGiver:GetAbsOrigin(), + nil, + 700, + DOTA_UNIT_TARGET_TEAM_FRIENDLY, + DOTA_UNIT_TARGET_HERO, + DOTA_UNIT_TARGET_FLAG_NOT_ILLUSIONS, + FIND_ANY_ORDER, + false + ) + if #nearbyHeroes > 0 then + handleQuestItemTurnIn(nil, nearbyHeroes[1], { + itemName = "item_meat", + questId = "firestar_quest_1", + objectiveKey = "custom_objective", + requiredCount = 10, + timerHandle = timer, + useCharges = true + }) + end + end + return 3 + end) + end, + onComplete = function() + local timertext0 + timertext0 = Timers:CreateTimer( + 0.1, + function() + GameRules:SendCustomMessage("#firestar_quest_1_message_complete_1", 0, 0) + Timers:RemoveTimer(timertext0) + end + ) + end + }, + firestar_quest_gourmet = { + onAccept = function() + local timertext0 + timertext0 = Timers:CreateTimer( + 0.1, + function() + GameRules:SendCustomMessage("#firestar_quest_gourmet_message_1", 0, 0) + Timers:RemoveTimer(timertext0) + end + ) + end, + onComplete = function() + local timertext0 + timertext0 = Timers:CreateTimer( + 0.1, + function() + GameRules:SendCustomMessage("#firestar_quest_gourmet_message_complete_1", 0, 0) + Timers:RemoveTimer(timertext0) + end + ) + end + }, + firestar_quest_leader_horn = { + onAccept = function() + local timer + timer = Timers:CreateTimer(function() + local questGiver = Entities:FindByName(nil, "npc_quest_giver_firestar") + if questGiver ~= nil then + local nearbyHeroes = FindUnitsInRadius( + DOTA_TEAM_GOODGUYS, + questGiver:GetAbsOrigin(), + nil, + 700, + DOTA_UNIT_TARGET_TEAM_FRIENDLY, + DOTA_UNIT_TARGET_HERO, + DOTA_UNIT_TARGET_FLAG_NOT_ILLUSIONS, + FIND_ANY_ORDER, + false + ) + if #nearbyHeroes > 0 then + handleQuestItemTurnIn(nil, nearbyHeroes[1], { + itemName = "item_lycan_horn", + questId = "firestar_quest_leader_horn", + objectiveKey = "bring_leader_horn", + requiredCount = 1, + timerHandle = timer, + useCharges = false + }) + end + end + return 3 + end) + end, + onComplete = function() + GameRules:SendCustomMessage("#firestar_quest_leader_horn_message_complete_1", 0, 0) + end + }, + firestar_quest_fishing = { + onAccept = function() + GameRules:SendCustomMessage("#firestar_quest_fishing_message_1", 0, 0) + launchQuestRewardToNearestHero( + nil, + "npc_quest_giver_firestar", + "item_fishing_rod", + 1400, + 0, + 150, + 0.5, + RandomFloat(50, 100) + ) + local timer + timer = Timers:CreateTimer(function() + local questGiver = Entities:FindByName(nil, "npc_quest_giver_firestar") + if questGiver ~= nil then + local nearbyHeroes = FindUnitsInRadius( + DOTA_TEAM_GOODGUYS, + questGiver:GetAbsOrigin(), + nil, + 700, + DOTA_UNIT_TARGET_TEAM_FRIENDLY, + DOTA_UNIT_TARGET_HERO, + DOTA_UNIT_TARGET_FLAG_NOT_ILLUSIONS, + FIND_ANY_ORDER, + false + ) + if #nearbyHeroes > 0 then + handleQuestItemTurnIn(nil, nearbyHeroes[1], { + itemName = "item_fish", + questId = "firestar_quest_fishing", + objectiveKey = "bring_fish", + requiredCount = 10, + timerHandle = timer, + useCharges = true + }) + end + end + return 3 + end) + end, + onComplete = function() + GameRules:SendCustomMessage("#firestar_quest_fishing_message_complete_1", 0, 0) + end + }, + oldmen_quest_1 = { + onAccept = function() + GameRules:SendCustomMessage("#oldmen_quest_1_message_1", 0, 0) + local requiredMilkCount = OLDMEN_MILK_BASE_REQUIRED * (oldmenMilkTurnInCount + 1) + QuestSystem:getInstance():setObjectiveRequired("oldmen_quest_1", "custom_objective", requiredMilkCount) + local timer + timer = Timers:CreateTimer(function() + local questGiver = Entities:FindByName(nil, "npc_quest_giver_oldmen") + if questGiver ~= nil then + local nearbyHeroes = FindUnitsInRadius( + DOTA_TEAM_GOODGUYS, + questGiver:GetAbsOrigin(), + nil, + 700, + DOTA_UNIT_TARGET_TEAM_FRIENDLY, + DOTA_UNIT_TARGET_HERO, + DOTA_UNIT_TARGET_FLAG_NOT_ILLUSIONS, + FIND_ANY_ORDER, + false + ) + if #nearbyHeroes > 0 then + handleQuestItemTurnIn(nil, nearbyHeroes[1], { + itemName = "item_milk", + questId = "oldmen_quest_1", + objectiveKey = "custom_objective", + requiredCount = requiredMilkCount, + timerHandle = timer, + useCharges = true + }) + end + end + return 3 + end) + end, + onComplete = function() + oldmenMilkTurnInCount = oldmenMilkTurnInCount + 1 + local nextRequiredMilkCount = OLDMEN_MILK_BASE_REQUIRED * (oldmenMilkTurnInCount + 1) + QuestSystem:getInstance():setObjectiveRequired("oldmen_quest_1", "custom_objective", nextRequiredMilkCount, false) + local timertext0 + timertext0 = Timers:CreateTimer( + 0.1, + function() + GameRules:SendCustomMessage("#oldmen_quest_1_message_complete_1", 0, 0) + Timers:RemoveTimer(timertext0) + end + ) + local timertext1 + timertext1 = Timers:CreateTimer( + 3, + function() + GameRules:SendCustomMessage("#oldmen_quest_1_message_complete_2", 0, 0) + Timers:RemoveTimer(timertext1) + end + ) + local timertext2 + timertext2 = Timers:CreateTimer( + 5, + function() + GameRules:SendCustomMessage("#oldmen_quest_1_message_complete_3", 0, 0) + Timers:RemoveTimer(timertext2) + end + ) + launchQuestRewardToNearestHero( + nil, + "npc_quest_giver_oldmen", + "item_testo", + 1400, + 0, + 150, + 0.5, + RandomFloat(50, 100) + ) + local questSystem = QuestSystem:getInstance() + questSystem:resetQuest("oldmen_quest_1") + end + }, + oldmen_quest_cheesemaker = { + onAccept = function() + GameRules:SendCustomMessage("#oldmen_quest_cheesemaker_message_1", 0, 0) + local timer + timer = Timers:CreateTimer(function() + local questGiver = Entities:FindByName(nil, "npc_quest_giver_oldmen") + if questGiver ~= nil then + local nearbyHeroes = FindUnitsInRadius( + DOTA_TEAM_GOODGUYS, + questGiver:GetAbsOrigin(), + nil, + 700, + DOTA_UNIT_TARGET_TEAM_FRIENDLY, + DOTA_UNIT_TARGET_HERO, + DOTA_UNIT_TARGET_FLAG_NOT_ILLUSIONS, + FIND_ANY_ORDER, + false + ) + if #nearbyHeroes > 0 then + handleQuestItemTurnIn(nil, nearbyHeroes[1], { + itemName = "item_cheese", + questId = "oldmen_quest_cheesemaker", + objectiveKey = "bring_cheese", + requiredCount = 3, + timerHandle = timer, + useCharges = true + }) + end + end + return 3 + end) + end, + onComplete = function() + local timertext0 + timertext0 = Timers:CreateTimer( + 0.1, + function() + GameRules:SendCustomMessage("#oldmen_quest_cheesemaker_message_complete_1", 0, 0) + Timers:RemoveTimer(timertext0) + end + ) + end + }, + lina_quest_ent_heart = { + onAccept = function() + local timer + timer = Timers:CreateTimer(function() + local questGiver = Entities:FindByName(nil, "npc_quest_giver_lina") + if questGiver ~= nil then + local nearbyHeroes = FindUnitsInRadius( + DOTA_TEAM_GOODGUYS, + questGiver:GetAbsOrigin(), + nil, + 700, + DOTA_UNIT_TARGET_TEAM_FRIENDLY, + DOTA_UNIT_TARGET_HERO, + DOTA_UNIT_TARGET_FLAG_NOT_ILLUSIONS, + FIND_ANY_ORDER, + false + ) + if #nearbyHeroes > 0 then + handleQuestItemTurnIn(nil, nearbyHeroes[1], { + itemName = "item_ent_heart", + questId = "lina_quest_ent_heart", + objectiveKey = "custom_objective", + requiredCount = 20, + timerHandle = timer, + useCharges = true + }) + end + end + return 3 + end) + end, + onComplete = function() + local timertext0 + timertext0 = Timers:CreateTimer( + 0.1, + function() + GameRules:SendCustomMessage("#lina_quest_ent_heart_message_complete_1", 0, 0) + Timers:RemoveTimer(timertext0) + end + ) + launchQuestRewardToNearestHero(nil, "npc_quest_giver_lina", "item_firecore") + end, + onFail = function() + end + }, + lina_quest_bottle = { + onAccept = function() + launchQuestRewardToNearestHero(nil, "npc_quest_giver_lina", "item_pollen_keeper") + GameRules:SendCustomMessage("#lina_quest_bottle_message_1", 0, 0) + local timer + timer = Timers:CreateTimer(function() + local questGiver = Entities:FindByName(nil, "npc_quest_giver_lina") + if questGiver ~= nil then + local nearbyHeroes = FindUnitsInRadius( + DOTA_TEAM_GOODGUYS, + questGiver:GetAbsOrigin(), + nil, + 700, + DOTA_UNIT_TARGET_TEAM_FRIENDLY, + DOTA_UNIT_TARGET_HERO, + DOTA_UNIT_TARGET_FLAG_NOT_ILLUSIONS, + FIND_ANY_ORDER, + false + ) + if #nearbyHeroes > 0 then + handleQuestItemTurnIn(nil, nearbyHeroes[1], { + itemName = "item_pollen_keeper", + questId = "lina_quest_bottle", + objectiveKey = "custom_objective", + requiredCount = 20, + timerHandle = timer, + useCharges = true, + removeFromHero = true + }) + end + end + return 3 + end) + end, + onComplete = function() + local timertext0 + timertext0 = Timers:CreateTimer( + 0.1, + function() + GameRules:SendCustomMessage("#lina_quest_bottle_message_complete_1", 0, 0) + Timers:RemoveTimer(timertext0) + end + ) + end, + onFail = function() + end + }, + friend_quest_pizza_prep = { + onAccept = function() + GameRules:SendCustomMessage("#friend_quest_pizza_prep_message_1", 0, 0) + local timer + timer = Timers:CreateTimer(function() + local questGiver = Entities:FindByName(nil, "npc_quest_giver_friend") + if questGiver ~= nil then + local nearbyHeroes = FindUnitsInRadius( + DOTA_TEAM_GOODGUYS, + questGiver:GetAbsOrigin(), + nil, + 700, + DOTA_UNIT_TARGET_TEAM_FRIENDLY, + DOTA_UNIT_TARGET_HERO, + DOTA_UNIT_TARGET_FLAG_NOT_ILLUSIONS, + FIND_ANY_ORDER, + false + ) + if #nearbyHeroes > 0 then + openRecipe(nil, "item_testo_pizza") + handleQuestItemTurnIn(nil, nearbyHeroes[1], { + itemName = "item_testo_pizza", + questId = "friend_quest_pizza_prep", + objectiveKey = "custom_objective", + requiredCount = 1, + timerHandle = timer, + useCharges = false, + removeFromHero = false + }) + end + end + return 3 + end) + end, + onComplete = function() + openRecipe(nil, "item_pizza") + openRecipe(nil, "item_mayonnaise") + GameRules:SendCustomMessage("#friend_quest_pizza_prep_message_complete_1", 0, 0) + launchQuestRewardToNearestHero( + nil, + "npc_quest_giver_friend", + "item_mayonnaise", + 1400, + 0, + 150, + 0.5 + ) + end, + onFail = function() + end + }, + largo_quest_kill_frogs = { + onAccept = function() + GameRules:SendCustomMessage("#largo_quest_kill_frogs_message_1", 0, 0) + end, + onComplete = function() + local timertext0 + timertext0 = Timers:CreateTimer( + 0.1, + function() + GameRules:SendCustomMessage("#largo_quest_kill_frogs_message_complete_1", 0, 0) + Timers:RemoveTimer(timertext0) + end + ) + end, + onFail = function() + end + }, + largo_quest_spider_legs = { + onAccept = function() + GameRules:SendCustomMessage("#largo_quest_spider_legs_message_1", 0, 0) + local timer + timer = Timers:CreateTimer(function() + local questGiver = Entities:FindByName(nil, "npc_quest_giver_largo") + if questGiver ~= nil then + local nearbyHeroes = FindUnitsInRadius( + DOTA_TEAM_GOODGUYS, + questGiver:GetAbsOrigin(), + nil, + 700, + DOTA_UNIT_TARGET_TEAM_FRIENDLY, + DOTA_UNIT_TARGET_HERO, + DOTA_UNIT_TARGET_FLAG_NOT_ILLUSIONS, + FIND_ANY_ORDER, + false + ) + if #nearbyHeroes > 0 then + handleQuestItemTurnIn(nil, nearbyHeroes[1], { + itemName = "item_spider_legs_custom", + questId = "largo_quest_spider_legs", + objectiveKey = "custom_objective", + requiredCount = 20, + timerHandle = timer, + useCharges = true + }) + end + end + return 3 + end) + end, + onComplete = function() + local timertext0 + timertext0 = Timers:CreateTimer( + 0.1, + function() + GameRules:SendCustomMessage("#largo_quest_spider_legs_message_complete_1", 0, 0) + Timers:RemoveTimer(timertext0) + end + ) + end, + onFail = function() + end + }, + maiden_quest_eggs = { + onAccept = function() + GameRules:SendCustomMessage("#maiden_quest_eggs_message_1", 0, 0) + local timer + timer = Timers:CreateTimer(function() + local questGiver = Entities:FindByName(nil, "npc_quest_giver_maiden") + if questGiver ~= nil then + local nearbyHeroes = FindUnitsInRadius( + DOTA_TEAM_GOODGUYS, + questGiver:GetAbsOrigin(), + nil, + 700, + DOTA_UNIT_TARGET_TEAM_FRIENDLY, + DOTA_UNIT_TARGET_HERO, + DOTA_UNIT_TARGET_FLAG_NOT_ILLUSIONS, + FIND_ANY_ORDER, + false + ) + if #nearbyHeroes > 0 then + handleQuestItemTurnIn(nil, nearbyHeroes[1], { + itemName = "item_egg", + questId = "maiden_quest_eggs", + objectiveKey = "custom_objective", + requiredCount = 20, + timerHandle = timer, + useCharges = true + }) + end + end + return 3 + end) + end, + onComplete = function() + local timertext0 + timertext0 = Timers:CreateTimer( + 0.1, + function() + GameRules:SendCustomMessage("#maiden_quest_eggs_message_complete_1", 0, 0) + Timers:RemoveTimer(timertext0) + end + ) + end, + onFail = function() + end + }, + doctor_quest_frog_paws = { + onAccept = function() + GameRules:SendCustomMessage("#doctor_quest_frog_paws_message_1", 0, 0) + local timer + timer = Timers:CreateTimer(function() + local questGiver = Entities:FindByName(nil, "npc_quest_giver_doctor") + if questGiver ~= nil then + local nearbyHeroes = FindUnitsInRadius( + DOTA_TEAM_GOODGUYS, + questGiver:GetAbsOrigin(), + nil, + 700, + DOTA_UNIT_TARGET_TEAM_FRIENDLY, + DOTA_UNIT_TARGET_HERO, + DOTA_UNIT_TARGET_FLAG_NOT_ILLUSIONS, + FIND_ANY_ORDER, + false + ) + if #nearbyHeroes > 0 then + handleQuestItemTurnIn(nil, nearbyHeroes[1], { + itemName = "item_frog_paw", + questId = "doctor_quest_frog_paws", + objectiveKey = "custom_objective", + requiredCount = 20, + timerHandle = timer, + useCharges = true + }) + end + end + return 3 + end) + end, + onComplete = function() + local timertext0 + timertext0 = Timers:CreateTimer( + 0.1, + function() + GameRules:SendCustomMessage("#doctor_quest_frog_paws_message_complete_1", 0, 0) + Timers:RemoveTimer(timertext0) + end + ) + end, + onFail = function() + end + }, + doctor_quest_poison = { + onAccept = function() + GameRules:SendCustomMessage("#doctor_quest_poison_message_1", 0, 0) + local timer + timer = Timers:CreateTimer(function() + local questGiver = Entities:FindByName(nil, "npc_quest_giver_doctor") + if questGiver ~= nil then + local nearbyHeroes = FindUnitsInRadius( + DOTA_TEAM_GOODGUYS, + questGiver:GetAbsOrigin(), + nil, + 700, + DOTA_UNIT_TARGET_TEAM_FRIENDLY, + DOTA_UNIT_TARGET_HERO, + DOTA_UNIT_TARGET_FLAG_NOT_ILLUSIONS, + FIND_ANY_ORDER, + false + ) + if #nearbyHeroes > 0 then + handleQuestItemTurnIn(nil, nearbyHeroes[1], { + itemName = "item_poison", + questId = "doctor_quest_poison", + objectiveKey = "custom_objective", + requiredCount = 20, + timerHandle = timer, + useCharges = true + }) + end + end + return 3 + end) + end, + onComplete = function() + local timertext0 + timertext0 = Timers:CreateTimer( + 0.1, + function() + GameRules:SendCustomMessage("#doctor_quest_poison_message_complete_1", 0, 0) + Timers:RemoveTimer(timertext0) + end + ) + end, + onFail = function() + end + } +} +--- Трэк BH для квестовых всплывающих эффектов +function ____exports.precacheQuestBountyHunterParticles(self, context) + PrecacheResource("particle", "particles/units/heroes/hero_bounty_hunter/bounty_hunter_track_trail_circle.vpcf", context) + PrecacheResource("particle", "particles/units/heroes/hero_bounty_hunter/bounty_hunter_track_shield_mark.vpcf", context) + PrecacheResource("particle", "particles/econ/items/kunkka/divine_anchor/hero_kunkka_dafx_skills/kunkka_spell_x_spot_mark_red_fxset.vpcf", context) +end +return ____exports diff --git a/scripts/vscripts/quests/questinitializer.lua b/scripts/vscripts/quests/questinitializer.lua new file mode 100644 index 0000000..1a30906 --- /dev/null +++ b/scripts/vscripts/quests/questinitializer.lua @@ -0,0 +1,418 @@ +local ____lualib = require("lualib_bundle") +local __TS__ArrayForEach = ____lualib.__TS__ArrayForEach +local __TS__ArrayMap = ____lualib.__TS__ArrayMap +local ____exports = {} +local ____QuestSystem = require("quests.QuestSystem") +local QuestType = ____QuestSystem.QuestType +local QuestState = ____QuestSystem.QuestState +local ____QuestSystem = require("quests.QuestSystem") +local QuestSystem = ____QuestSystem.QuestSystem +local ____QuestEventHandlers = require("quests.QuestEventHandlers") +local QuestEventHandlers = ____QuestEventHandlers.QuestEventHandlers +local ____quest_reward_scaling = require("quests.quest_reward_scaling") +local questTeamGold = ____quest_reward_scaling.questTeamGold +function ____exports.InitializeQuests(self) + local questSystem = QuestSystem:getInstance() + local npcQuestChains = { + { + npcName = "npc_quest_giver_kunkka", + quests = { + { + id = "kunkka_quest_give_claw", + title = "#quest_give_claw_title", + description = "#quest_give_claw_description", + objectives = {custom_objective = {type = QuestType.CUSTOM, target = "item_wolf_claw", required = 10, current = 0}}, + rewards = { + gold = questTeamGold(nil, 600), + experience = 150, + crystals = 5 + }, + state = QuestState.AVAILABLE, + unlocksQuestIds = {"kunkka_quest_kill_lycan", "kunkka_quest_find_anchor"}, + events = QuestEventHandlers.kunkka_quest_give_claw + }, + { + id = "kunkka_quest_kill_lycan", + title = "#quest_kunkka_kill_lycan_title", + description = "#quest_kunkka_kill_lycan_description", + objectives = {kill_lycan = {type = QuestType.KILL_UNIT, target = "npc_boss_lycan", required = 1, current = 0}}, + rewards = { + gold = questTeamGold(nil, 1200), + experience = 400, + crystals = 10 + }, + state = QuestState.LOCKED, + previousQuestId = "kunkka_quest_give_claw", + events = QuestEventHandlers.kunkka_quest_kill_lycan + }, + { + id = "kunkka_quest_kill_satyr_demon", + title = "#quest_kunkka_kill_satyr_demon_title", + description = "#quest_kunkka_kill_satyr_demon_description", + objectives = {kill_satyr_demon = {type = QuestType.KILL_UNIT, target = "npc_demon_dragon_satyr", required = 1, current = 0}}, + rewards = { + gold = questTeamGold(nil, 1400), + experience = 450, + crystals = 11 + }, + state = QuestState.LOCKED, + previousQuestId = "kunkka_quest_kill_lycan", + events = QuestEventHandlers.kunkka_quest_kill_satyr_demon + }, + { + id = "kunkka_quest_clean_harbor", + title = "#quest_kunkka_clean_harbor_title", + description = "#quest_kunkka_clean_harbor_description", + objectives = {clean_harbor = {type = QuestType.CUSTOM, target = "#zone_skeletons_clean", required = 1, current = 0}}, + rewards = { + gold = questTeamGold(nil, 800), + experience = 300, + crystals = 6 + }, + state = QuestState.LOCKED, + previousQuestId = "kunkka_quest_kill_satyr_demon", + events = QuestEventHandlers.kunkka_quest_clean_harbor + }, + { + id = "kunkka_quest_find_rom", + title = "#quest_found_rom_title", + description = "#quest_found_rom_description", + objectives = {custom_objective = {type = QuestType.CUSTOM, target = "item_rom", required = 1, current = 0}}, + rewards = { + gold = questTeamGold(nil, 400), + experience = 150, + crystals = 8 + }, + state = QuestState.AVAILABLE, + events = QuestEventHandlers.kunkka_quest_find_rom + }, + { + id = "kunkka_quest_2", + title = "#quest_kunkka_skeletons_title", + description = "#quest_kunkka_skeletons_description", + objectives = {kill_skeletons0 = {type = QuestType.KILL_UNIT, target = "npc_skeleton_zombie_undead", required = 10, current = 0}, kill_skeletons1 = {type = QuestType.KILL_UNIT, target = "npc_dead_skeleton_archer_undead", required = 6, current = 0}, kill_skeletons2 = {type = QuestType.KILL_UNIT, target = "npc_skeleton_zombie_half_undead", required = 8, current = 0}, kill_skeletons3 = {type = QuestType.KILL_UNIT, target = "npc_dead_skeleton_undead", required = 5, current = 0}}, + rewards = { + gold = questTeamGold(nil, 150), + experience = 100, + crystals = 3 + }, + state = QuestState.LOCKED, + previousQuestId = "kunkka_quest_find_rom", + events = QuestEventHandlers.kunkka_quest_2 + }, + { + id = "kunkka_quest_find_anchor", + title = "#quest_kunkka_find_anchor_title", + description = "#quest_kunkka_find_anchor_description", + objectives = {find_anchor = {type = QuestType.CUSTOM, target = "point_shovel", required = 1, current = 0}}, + rewards = { + gold = questTeamGold(nil, 500), + experience = 200, + crystals = 6 + }, + state = QuestState.LOCKED, + previousQuestId = "kunkka_quest_give_claw", + events = QuestEventHandlers.kunkka_quest_find_anchor + } + } + }, + { + npcName = "npc_quest_giver_denny", + quests = { + { + id = "denny_quest_1", + title = "#quest_denny_quest_kill_sheeps_title", + description = "#quest_denny_quest_kill_sheeps_description", + objectives = {kill_ranged = {type = QuestType.KILL_UNIT, target = "npc_sheep", required = 20, current = 0}}, + rewards = { + gold = questTeamGold(nil, 400), + experience = 200, + crystals = 4 + }, + state = QuestState.AVAILABLE, + unlocksQuestIds = {"denny_quest_find_pet"}, + events = QuestEventHandlers.denny_quest_kill_sheeps + }, + { + id = "denny_quest_find_pet", + title = "#quest_denny_need_a_pet_title", + description = "#quest_denny_need_a_pet_description", + objectives = {find_a_pet = {type = QuestType.CUSTOM, target = "#denny_pet", required = 1, current = 0}}, + rewards = { + gold = questTeamGold(nil, 500), + experience = 400, + crystals = 6 + }, + state = QuestState.LOCKED, + previousQuestId = "denny_quest_1", + unlocksQuestIds = {"denny_quest_kill_sheeps"}, + events = QuestEventHandlers.denny_quest_find_a_pet + }, + { + id = "denny_quest_kill_sheeps", + title = "#quest_denny_training_title", + description = "#quest_denny_training_description", + objectives = {training_farm = {type = QuestType.KILL_UNIT, target = "npc_sheep", required = 20, current = 0}}, + rewards = { + gold = questTeamGold(nil, 600), + experience = 100, + crystals = 5 + }, + state = QuestState.LOCKED, + previousQuestId = "denny_quest_find_pet", + unlocksQuestIds = {"denny_quest_kill_thieves"}, + events = QuestEventHandlers.denny_quest_1 + }, + { + id = "denny_quest_kill_thieves", + title = "#quest_denny_quest_kill_thieves_title", + description = "#quest_denny_quest_kill_thieves_description", + objectives = {kill_leader = {type = QuestType.KILL_UNIT, target = "npc_thief_leader", required = 1, current = 0}, kill_archer = {type = QuestType.KILL_UNIT, target = "npc_thief_archer", required = 2, current = 0}, kill_backer = {type = QuestType.KILL_UNIT, target = "npc_thief_backer", required = 2, current = 0}}, + rewards = { + gold = questTeamGold(nil, 450), + experience = 250, + crystals = 5 + }, + state = QuestState.LOCKED, + previousQuestId = "denny_quest_kill_sheeps", + events = QuestEventHandlers.denny_quest_kill_thieves + } + } + }, + { + npcName = "npc_quest_giver_firestar", + quests = { + { + id = "firestar_quest_1", + title = "#quest_kill_pigs_title", + description = "#quest_kill_pigs_description", + objectives = {custom_objective = {type = QuestType.CUSTOM, target = "item_meat", required = 10, current = 0}}, + rewards = {gold = 0, experience = 350, crystals = 7}, + state = QuestState.AVAILABLE, + events = QuestEventHandlers.firestar_quest_1 + }, + { + id = "firestar_quest_gourmet", + title = "#quest_firestar_gourmet_title", + description = "#quest_firestar_gourmet_description", + objectives = {cook_meat = {type = QuestType.CUSTOM, target = "item_grilled_meat", required = 5, current = 0}}, + rewards = { + gold = questTeamGold(nil, 500), + experience = 350, + crystals = 6 + }, + state = QuestState.LOCKED, + previousQuestId = "firestar_quest_1", + events = QuestEventHandlers.firestar_quest_gourmet + }, + { + id = "firestar_quest_leader_horn", + title = "#quest_firestar_leader_horn_title", + description = "#quest_firestar_leader_horn_description", + objectives = {bring_leader_horn = {type = QuestType.CUSTOM, target = "item_lycan_horn", required = 1, current = 0}}, + rewards = { + gold = questTeamGold(nil, 900), + experience = 450, + crystals = 9 + }, + state = QuestState.LOCKED, + previousQuestId = "firestar_quest_gourmet", + events = QuestEventHandlers.firestar_quest_leader_horn + }, + { + id = "firestar_quest_fishing", + title = "#quest_firestar_fishing_title", + description = "#quest_firestar_fishing_description", + objectives = {bring_fish = {type = QuestType.CUSTOM, target = "item_fish", required = 10, current = 0}}, + rewards = { + gold = questTeamGold(nil, 1100), + experience = 500, + crystals = 10 + }, + state = QuestState.LOCKED, + previousQuestId = "firestar_quest_leader_horn", + events = QuestEventHandlers.firestar_quest_fishing + } + } + }, + { + npcName = "npc_quest_giver_oldmen", + quests = { + { + id = "oldmen_quest_1", + title = "#quest_kill_sheep_title", + description = "#quest_kill_sheep_description", + objectives = {custom_objective = {type = QuestType.CUSTOM, target = "item_milk", required = 5, current = 0}}, + rewards = { + gold = questTeamGold(nil, 25), + experience = 0, + crystals = 0 + }, + state = QuestState.AVAILABLE, + unlocksQuestIds = {"oldmen_quest_cheesemaker"}, + events = QuestEventHandlers.oldmen_quest_1 + }, + { + id = "oldmen_quest_cheesemaker", + title = "#quest_oldmen_cheesemaker_title", + description = "#quest_oldmen_cheesemaker_description", + objectives = {bring_cheese = {type = QuestType.CUSTOM, target = "item_cheese", required = 3, current = 0}}, + rewards = { + gold = questTeamGold(nil, 300), + experience = 150, + crystals = 3 + }, + state = QuestState.LOCKED, + previousQuestId = "oldmen_quest_1", + events = QuestEventHandlers.oldmen_quest_cheesemaker + } + } + }, + { + npcName = "npc_quest_giver_lina", + quests = { + { + id = "lina_quest_ent_heart", + title = "#lina_quest_ent_heart_title", + description = "#lina_quest_ent_heart_description", + objectives = {custom_objective = {type = QuestType.CUSTOM, target = "item_ent_heart", required = 20, current = 0}}, + rewards = { + gold = questTeamGold(nil, 250), + experience = 220, + crystals = 4 + }, + state = QuestState.AVAILABLE, + events = QuestEventHandlers.lina_quest_ent_heart + }, + { + id = "lina_quest_bottle", + title = "#lina_quest_bottle_title", + description = "#lina_quest_bottle_description", + objectives = {custom_objective = {type = QuestType.CUSTOM, target = "item_pollen_keeper", required = 20, current = 0}}, + rewards = { + gold = questTeamGold(nil, 300), + experience = 260, + crystals = 4 + }, + state = QuestState.LOCKED, + previousQuestId = "lina_quest_ent_heart", + events = QuestEventHandlers.lina_quest_bottle + } + } + }, + { + npcName = "npc_quest_giver_largo", + quests = { + { + id = "largo_quest_kill_frogs", + title = "#largo_quest_kill_frogs_title", + description = "#largo_quest_kill_frogs_description", + objectives = {kill_mini_frog = {type = QuestType.KILL_UNIT, target = "npc_mini_frog", required = 8, current = 0}, kill_froglet = {type = QuestType.KILL_UNIT, target = "npc_small_frog_froglet", required = 6, current = 0}, kill_frog_magi = {type = QuestType.KILL_UNIT, target = "npc_frogman_magi", required = 6, current = 0}}, + rewards = { + gold = questTeamGold(nil, 350), + experience = 260, + crystals = 4 + }, + state = QuestState.AVAILABLE, + events = QuestEventHandlers.largo_quest_kill_frogs + }, + { + id = "largo_quest_spider_legs", + title = "#largo_quest_spider_legs_title", + description = "#largo_quest_spider_legs_description", + objectives = {custom_objective = {type = QuestType.CUSTOM, target = "item_spider_legs_custom", required = 20, current = 0}}, + rewards = { + gold = questTeamGold(nil, 360), + experience = 260, + crystals = 5 + }, + state = QuestState.LOCKED, + previousQuestId = "largo_quest_kill_frogs", + events = QuestEventHandlers.largo_quest_spider_legs + } + } + }, + { + npcName = "npc_quest_giver_maiden", + quests = {{ + id = "maiden_quest_eggs", + title = "#maiden_quest_eggs_title", + description = "#maiden_quest_eggs_description", + objectives = {custom_objective = {type = QuestType.CUSTOM, target = "item_egg", required = 20, current = 0}}, + rewards = { + gold = questTeamGold(nil, 300), + experience = 220, + crystals = 4 + }, + state = QuestState.AVAILABLE, + events = QuestEventHandlers.maiden_quest_eggs + }} + }, + { + npcName = "npc_quest_giver_doctor", + quests = { + { + id = "doctor_quest_frog_paws", + title = "#doctor_quest_frog_paws_title", + description = "#doctor_quest_frog_paws_description", + objectives = {custom_objective = {type = QuestType.CUSTOM, target = "item_frog_paw", required = 20, current = 0}}, + rewards = { + gold = questTeamGold(nil, 320), + experience = 240, + crystals = 4 + }, + state = QuestState.AVAILABLE, + events = QuestEventHandlers.doctor_quest_frog_paws + }, + { + id = "doctor_quest_poison", + title = "#doctor_quest_poison_title", + description = "#doctor_quest_poison_description", + objectives = {custom_objective = {type = QuestType.CUSTOM, target = "item_poison", required = 20, current = 0}}, + rewards = { + gold = questTeamGold(nil, 380), + experience = 280, + crystals = 5 + }, + state = QuestState.LOCKED, + previousQuestId = "doctor_quest_frog_paws", + events = QuestEventHandlers.doctor_quest_poison + } + } + }, + { + npcName = "npc_quest_giver_friend", + quests = {{ + id = "friend_quest_pizza_prep", + title = "#friend_quest_pizza_prep_title", + description = "#friend_quest_pizza_prep_description", + objectives = {custom_objective = {type = QuestType.CUSTOM, target = "item_testo_pizza", required = 1, current = 0}}, + rewards = { + gold = questTeamGold(nil, 180), + experience = 180, + crystals = 3 + }, + state = QuestState.AVAILABLE, + events = QuestEventHandlers.friend_quest_pizza_prep + }} + } + } + __TS__ArrayForEach( + npcQuestChains, + function(____, chain) + __TS__ArrayForEach( + chain.quests, + function(____, quest) + questSystem:registerQuest(quest) + end + ) + local questIds = __TS__ArrayMap( + chain.quests, + function(____, quest) return quest.id end + ) + questSystem:registerNpcQuests(chain.npcName, questIds) + end + ) + questSystem:linkQuestChains() +end +return ____exports diff --git a/scripts/vscripts/quests/questsystem.lua b/scripts/vscripts/quests/questsystem.lua new file mode 100644 index 0000000..9935913 --- /dev/null +++ b/scripts/vscripts/quests/questsystem.lua @@ -0,0 +1,753 @@ +local ____lualib = require("lualib_bundle") +local __TS__Class = ____lualib.__TS__Class +local __TS__ArrayForEach = ____lualib.__TS__ArrayForEach +local Map = ____lualib.Map +local __TS__New = ____lualib.__TS__New +local Set = ____lualib.Set +local __TS__ArrayIncludes = ____lualib.__TS__ArrayIncludes +local __TS__ArrayIsArray = ____lualib.__TS__ArrayIsArray +local __TS__ObjectEntries = ____lualib.__TS__ObjectEntries +local __TS__ObjectValues = ____lualib.__TS__ObjectValues +local __TS__ArrayEvery = ____lualib.__TS__ArrayEvery +local __TS__ArrayPushArray = ____lualib.__TS__ArrayPushArray +local __TS__Iterator = ____lualib.__TS__Iterator +local __TS__ArrayFrom = ____lualib.__TS__ArrayFrom +local __TS__ArrayFilter = ____lualib.__TS__ArrayFilter +local __TS__ArrayMap = ____lualib.__TS__ArrayMap +local ____exports = {} +local ____crystal_currency = require("crystal_currency") +local CrystalCurrency = ____crystal_currency.CrystalCurrency +local ____entity_radius = require("utils.entity_radius") +local findNearestByClassname = ____entity_radius.findNearestByClassname +local ____real_lobby_player = require("utils.real_lobby_player") +local collectStatsEligiblePlayerIds = ____real_lobby_player.collectStatsEligiblePlayerIds +local ____quest_reward_scaling = require("quests.quest_reward_scaling") +local getQuestRewardSplitCount = ____quest_reward_scaling.getQuestRewardSplitCount +local splitQuestTeamReward = ____quest_reward_scaling.splitQuestTeamReward +____exports.QuestType = QuestType or ({}) +____exports.QuestType.KILL_UNIT = "KILL_UNIT" +____exports.QuestType.COLLECT_ITEM = "COLLECT_ITEM" +____exports.QuestType.REACH_LOCATION = "REACH_LOCATION" +____exports.QuestType.CUSTOM = "CUSTOM" +____exports.QuestState = QuestState or ({}) +____exports.QuestState.AVAILABLE = 0 +____exports.QuestState[____exports.QuestState.AVAILABLE] = "AVAILABLE" +____exports.QuestState.IN_PROGRESS = 1 +____exports.QuestState[____exports.QuestState.IN_PROGRESS] = "IN_PROGRESS" +____exports.QuestState.COMPLETED = 2 +____exports.QuestState[____exports.QuestState.COMPLETED] = "COMPLETED" +____exports.QuestState.LOCKED = 3 +____exports.QuestState[____exports.QuestState.LOCKED] = "LOCKED" +____exports.QuestState.FAILED = 4 +____exports.QuestState[____exports.QuestState.FAILED] = "FAILED" +____exports.QuestEvents = __TS__Class() +local QuestEvents = ____exports.QuestEvents +QuestEvents.name = "QuestEvents" +QuestEvents.____file_path = "scripts/vscripts/quests/QuestSystem.lua" +function QuestEvents.prototype.____constructor(self) +end +function QuestEvents.OnQuestAccepted(self, callback) + local ____self_onQuestAcceptedCallbacks_0 = self.onQuestAcceptedCallbacks + ____self_onQuestAcceptedCallbacks_0[#____self_onQuestAcceptedCallbacks_0 + 1] = callback +end +function QuestEvents.OnQuestCompleted(self, callback) + local ____self_onQuestCompletedCallbacks_1 = self.onQuestCompletedCallbacks + ____self_onQuestCompletedCallbacks_1[#____self_onQuestCompletedCallbacks_1 + 1] = callback +end +function QuestEvents.FireQuestAccepted(self, data) + __TS__ArrayForEach( + self.onQuestAcceptedCallbacks, + function(____, callback) return callback(nil, data) end + ) +end +function QuestEvents.FireQuestCompleted(self, data) + __TS__ArrayForEach( + self.onQuestCompletedCallbacks, + function(____, callback) return callback(nil, data) end + ) +end +QuestEvents.onQuestAcceptedCallbacks = {} +QuestEvents.onQuestCompletedCallbacks = {} +local DOTA_ModifyGold_Unspecified = 0 +local DOTA_ModifyXP_Unspecified = 0 +____exports.QuestSystem = __TS__Class() +local QuestSystem = ____exports.QuestSystem +QuestSystem.name = "QuestSystem" +QuestSystem.____file_path = "scripts/vscripts/quests/QuestSystem.lua" +function QuestSystem.prototype.____constructor(self) + self.quests = __TS__New(Map) + self.npcQuests = __TS__New(Map) + self.processedItems = __TS__New(Set) + self.questItems = __TS__New(Map) + self.initialized = false + self.questMenuOpenNpcByPlayer = __TS__New(Map) + self:initializeEventListeners() +end +function QuestSystem.getInstance(self) + if not ____exports.QuestSystem.instance then + ____exports.QuestSystem.instance = __TS__New(____exports.QuestSystem) + end + return ____exports.QuestSystem.instance +end +function QuestSystem.prototype.registerQuest(self, quest) + self.quests:set(quest.id, quest) + self.initialized = true +end +function QuestSystem.prototype.linkQuestChains(self) + self.quests:forEach(function(____, quest) + if not quest.previousQuestId then + return + end + local previousQuest = self.quests:get(quest.previousQuestId) + if not previousQuest then + return + end + local questNpc = self:getNpcForQuest(quest.id) + local previousNpc = self:getNpcForQuest(previousQuest.id) + if questNpc and questNpc == previousNpc then + previousQuest.nextQuestId = quest.id + end + end) +end +function QuestSystem.prototype.initializeEventListeners(self) + CustomGameEventManager:RegisterListener( + "request_quests_data", + function(source, event) + local npcName = event.npcName + local player = PlayerResource:GetPlayer(event.PlayerID) + if not player then + return + end + if npcName then + local questsData = self:getQuestsForUI(npcName) + CustomGameEventManager:Send_ServerToPlayer(player, "quests_update", {quests = questsData, npcName = npcName, playerId = event.PlayerID}) + return + end + local acceptedQuestsData = self:getAcceptedQuestsForUI() + CustomGameEventManager:Send_ServerToPlayer(player, "quests_update", {quests = acceptedQuestsData, playerId = event.PlayerID}) + end + ) + CustomGameEventManager:RegisterListener( + "quest_action", + function(source, event) + if event and event.questId and event.action == "accept" then + local playerId = event.PlayerID or source + if not PlayerResource:IsValidPlayerID(playerId) then + return + end + self:startQuest(playerId, event.questId) + local npcName = self:getNpcForQuest(event.questId) + if npcName then + local player = PlayerResource:GetPlayer(playerId) + if player then + local questsData = self:getQuestsForUI(npcName) + CustomGameEventManager:Send_ServerToPlayer(player, "quests_update", {quests = questsData, npcName = npcName, playerId = playerId}) + end + end + end + end + ) + CustomGameEventManager:RegisterListener( + "quests_client_ui_state", + function(_source, event) + local playerId = event.PlayerID + if playerId == nil or playerId < 0 then + return + end + local open = event.open == 1 or event.open == true + local rawNpc = event.npcName or "" + if open then + self.questMenuOpenNpcByPlayer:set(playerId, rawNpc ~= "" and rawNpc or "*") + else + self.questMenuOpenNpcByPlayer:delete(playerId) + end + end + ) + ListenToGameEvent( + "entity_killed", + function(event) + local unit = EntIndexToHScript(event.entindex_killed) + if unit and unit:IsBaseNPC() then + self:OnNpcKilled(unit) + end + end, + nil + ) + ListenToGameEvent( + "dota_item_picked_up", + function(event) + self:OnItemPickedUp(event) + end, + nil + ) +end +function QuestSystem.prototype.startQuest(self, playerId, questId) + local quest = self.quests:get(questId) + if not quest then + return + end + if not self:isQuestAvailable(questId) then + return + end + quest.state = ____exports.QuestState.IN_PROGRESS + ____exports.QuestEvents:FireQuestAccepted({questId = questId, playerId = playerId}) + local ____opt_4 = quest.events + local ____opt_2 = ____opt_4 and ____opt_4.onAccept + if ____opt_2 ~= nil then + ____opt_2(____opt_4) + end + local npcName = self:getNpcForQuest(questId) + if npcName then + self:updateUI(npcName, playerId) + end +end +function QuestSystem.prototype.isPreviousQuestRequirementMet(self, quest) + if not quest.previousQuestId then + return true + end + local previousQuest = self.quests:get(quest.previousQuestId) + if not previousQuest then + return false + end + if previousQuest.state == ____exports.QuestState.COMPLETED then + return true + end + local ____temp_8 = previousQuest.state == ____exports.QuestState.AVAILABLE + if ____temp_8 then + local ____opt_6 = previousQuest.unlocksQuestIds + ____temp_8 = ____opt_6 and __TS__ArrayIncludes(previousQuest.unlocksQuestIds, quest.id) + end + if ____temp_8 then + return true + end + return false +end +function QuestSystem.prototype.isQuestAvailable(self, questId) + local quest = self.quests:get(questId) + if not quest or quest.state ~= ____exports.QuestState.AVAILABLE then + return false + end + return self:isPreviousQuestRequirementMet(quest) +end +function QuestSystem.prototype.updateQuestProgress(self, ____type, target, playerID, data) + self.quests:forEach(function(____, quest, questId) + if quest.state ~= ____exports.QuestState.IN_PROGRESS then + return + end + local questDirty = false + __TS__ArrayForEach( + __TS__ObjectEntries(quest.objectives), + function(____, ____bindingPattern0) + local objective + local objectiveId = ____bindingPattern0[1] + objective = ____bindingPattern0[2] + local objectiveChanged = false + if objective.type == ____exports.QuestType.CUSTOM and objective.customHandler then + objective.customHandler:onProgress(data) + objectiveChanged = true + elseif objective.type == ____type then + local ____Array_isArray_result_9 + if __TS__ArrayIsArray(objective.target) then + ____Array_isArray_result_9 = __TS__ArrayIncludes(objective.target, target) + else + ____Array_isArray_result_9 = objective.target == target + end + local isTargetMatch = ____Array_isArray_result_9 + if isTargetMatch and objective.current < objective.required then + objective.current = objective.current + 1 + objectiveChanged = true + end + end + if objectiveChanged then + questDirty = true + self:checkQuestCompletion(quest) + end + end + ) + if questDirty then + local npcName = self:getNpcForQuest(quest.id) or "" + if npcName ~= nil then + self:updateUI(npcName, playerID) + end + end + end) +end +function QuestSystem.prototype.checkQuestCompletion(self, quest) + local allCompleted = __TS__ArrayEvery( + __TS__ObjectValues(quest.objectives), + function(____, objective) return objective.current >= objective.required end + ) + if allCompleted and quest.state == ____exports.QuestState.IN_PROGRESS then + quest.state = ____exports.QuestState.COMPLETED + print("[QuestSystem] FireQuestCompleted: questId=" .. quest.id) + ____exports.QuestEvents:FireQuestCompleted({questId = quest.id, playerId = 0}) + local ____opt_12 = quest.events + local ____opt_10 = ____opt_12 and ____opt_12.onComplete + if ____opt_10 ~= nil then + ____opt_10(____opt_12) + end + self:giveRewards(quest) + self:clearCollectedItems(quest) + self:unlockLinkedQuests(quest) + end +end +function QuestSystem.prototype.unlockLinkedQuests(self, quest) + local npcsToUpdate = __TS__New(Set) + local questIdsToUnlock = {} + if quest.nextQuestId then + questIdsToUnlock[#questIdsToUnlock + 1] = quest.nextQuestId + end + if quest.unlocksQuestIds then + __TS__ArrayPushArray(questIdsToUnlock, quest.unlocksQuestIds) + end + for ____, questId in ipairs(questIdsToUnlock) do + local linkedQuest = self.quests:get(questId) + if linkedQuest and linkedQuest.state == ____exports.QuestState.LOCKED then + linkedQuest.state = ____exports.QuestState.AVAILABLE + local npcName = self:getNpcForQuest(questId) + if npcName then + npcsToUpdate:add(npcName) + end + end + end + for ____, npcName in __TS__Iterator(npcsToUpdate) do + self:updateUI(npcName, 0) + end +end +function QuestSystem.prototype.repairStuckQuestUnlocks(self) + self.quests:forEach(function(____, quest) + if quest.state ~= ____exports.QuestState.LOCKED or not quest.previousQuestId then + return + end + local previousQuest = self.quests:get(quest.previousQuestId) + if (previousQuest and previousQuest.state) == ____exports.QuestState.COMPLETED then + quest.state = ____exports.QuestState.AVAILABLE + end + end) +end +function QuestSystem.prototype.shouldShowQuestInNpcUI(self, quest) + repeat + local ____switch72 = quest.state + local previousQuest + local ____cond72 = ____switch72 == ____exports.QuestState.AVAILABLE + if ____cond72 then + return self:isPreviousQuestRequirementMet(quest) + end + ____cond72 = ____cond72 or (____switch72 == ____exports.QuestState.IN_PROGRESS or ____switch72 == ____exports.QuestState.COMPLETED or ____switch72 == ____exports.QuestState.FAILED) + if ____cond72 then + return true + end + ____cond72 = ____cond72 or ____switch72 == ____exports.QuestState.LOCKED + if ____cond72 then + if not quest.previousQuestId then + return false + end + previousQuest = self.quests:get(quest.previousQuestId) + return (previousQuest and previousQuest.state) == ____exports.QuestState.COMPLETED + end + do + return false + end + until true +end +function QuestSystem.prototype.resetQuest(self, questId) + local quest = self.quests:get(questId) + if not quest then + return + end + __TS__ArrayForEach( + __TS__ObjectValues(quest.objectives), + function(____, objective) + objective.current = 0 + end + ) + quest.state = ____exports.QuestState.AVAILABLE + local npcName = self:getNpcForQuest(questId) or "" + self:updateUI(npcName, 0) +end +function QuestSystem.prototype.getRewardPlayerIds(self) + local ids = collectStatsEligiblePlayerIds(nil) + if #ids > 0 then + return ids + end + do + local playerID = 0 + while playerID < DOTA_MAX_TEAM_PLAYERS do + do + local pid = playerID + if not PlayerResource:IsValidPlayerID(pid) then + goto __continue79 + end + if PlayerResource:GetTeam(pid) ~= DOTA_TEAM_GOODGUYS then + goto __continue79 + end + return {pid} + end + ::__continue79:: + playerID = playerID + 1 + end + end + return {} +end +function QuestSystem.prototype.giveRewards(self, quest) + local playerIds = self:getRewardPlayerIds() + local playerCount = math.max(1, #playerIds) + local goldSplitCount = getQuestRewardSplitCount(nil, playerCount) + local goldParts = splitQuestTeamReward(nil, quest.rewards.gold or 0, goldSplitCount) + local xpParts = splitQuestTeamReward(nil, quest.rewards.experience or 0, playerCount) + local crystalParts = splitQuestTeamReward(nil, quest.rewards.crystals or 0, playerCount) + local crystalCurrency = CrystalCurrency:getInstance() + do + local i = 0 + while i < #playerIds do + local playerID = playerIds[i + 1] + local hero = PlayerResource:GetSelectedHeroEntity(playerID) + local gold = goldParts[i + 1] or 0 + if gold > 0 and hero then + hero:ModifyGold(gold, true, DOTA_ModifyGold_Unspecified) + end + local xp = xpParts[i + 1] or 0 + if xp > 0 and hero then + hero:AddExperience(xp, DOTA_ModifyXP_Unspecified, false, true) + end + local crystals = crystalParts[i + 1] or 0 + if crystals > 0 then + crystalCurrency:addCrystals(playerID, crystals) + end + if hero then + local madstone = CreateItem("item_madstone_bundle", nil, hero) + if madstone then + hero:AddItem(madstone) + end + end + i = i + 1 + end + end +end +function QuestSystem.prototype.getVisibleQuests(self) + local visibleQuests = {} + local startingQuests = __TS__ArrayFilter( + __TS__ArrayFrom(self.quests:values()), + function(____, quest) return not quest.previousQuestId end + ) + for ____, startQuest in ipairs(startingQuests) do + local currentQuest = startQuest + while currentQuest do + visibleQuests[#visibleQuests + 1] = currentQuest + if currentQuest.state ~= ____exports.QuestState.COMPLETED then + break + end + if currentQuest.nextQuestId then + currentQuest = self.quests:get(currentQuest.nextQuestId) + else + break + end + end + end + return visibleQuests +end +function QuestSystem.prototype.registerNpcQuests(self, npcName, questIds) + self.npcQuests:set(npcName, questIds) +end +function QuestSystem.prototype.getQuestsForNpc(self, npcName) + local questIds = self.npcQuests:get(npcName) or ({}) + local quests = __TS__ArrayFilter( + __TS__ArrayMap( + questIds, + function(____, id) + local quest = self.quests:get(id) + return quest + end + ), + function(____, quest) + return quest ~= nil and (quest.state == ____exports.QuestState.AVAILABLE or quest.state == ____exports.QuestState.LOCKED or quest.state == ____exports.QuestState.IN_PROGRESS or quest.state == ____exports.QuestState.COMPLETED) + end + ) + return quests +end +function QuestSystem.prototype.getQuestsForUI(self, npcName) + local questsData = {} + if not self.initialized or not npcName then + return questsData + end + self:repairStuckQuestUnlocks() + local npcQuestIds = self.npcQuests:get(npcName) or ({}) + for ____, questId in ipairs(npcQuestIds) do + local quest = self.quests:get(questId) + if quest and self:shouldShowQuestInNpcUI(quest) then + questsData[questId] = quest + end + end + return questsData +end +function QuestSystem.prototype.getAcceptedQuestsForUI(self) + local questsData = {} + if not self.initialized then + return questsData + end + self.quests:forEach(function(____, quest, id) + if quest.state == ____exports.QuestState.IN_PROGRESS or quest.state == ____exports.QuestState.COMPLETED then + questsData[id] = quest + end + end) + return questsData +end +function QuestSystem.prototype.getNpcForQuest(self, questId) + for ____, ____value in __TS__Iterator(self.npcQuests:entries()) do + local npcName = ____value[1] + local questIds = ____value[2] + if __TS__ArrayIncludes(questIds, questId) then + return npcName + end + end + return nil +end +function QuestSystem.prototype.updateUI(self, npcName, playerId) + if not npcName then + return + end + local questsData = self:getQuestsForUI(npcName) + do + local i = 0 + while i < DOTA_MAX_TEAM_PLAYERS do + do + if not PlayerResource:IsValidPlayerID(i) or PlayerResource:IsFakeClient(i) then + goto __continue116 + end + local openFor = self.questMenuOpenNpcByPlayer:get(i) + if openFor == nil then + goto __continue116 + end + if openFor ~= "*" and openFor ~= npcName then + goto __continue116 + end + local player = PlayerResource:GetPlayer(i) + if player then + CustomGameEventManager:Send_ServerToPlayer(player, "quests_update", {quests = questsData, npcName = npcName, playerId = i}) + end + end + ::__continue116:: + i = i + 1 + end + end +end +function QuestSystem.prototype.OnNpcKilled(self, unit) + local unitName = unit:GetUnitName() + self:updateQuestProgress(____exports.QuestType.KILL_UNIT, unitName, 0) +end +function QuestSystem.prototype.OnItemPickedUp(self, event) + local itemEntity = EntIndexToHScript(event.ItemEntityIndex) + if not itemEntity then + return + end + local itemName = itemEntity:GetAbilityName() + local itemHandle = itemEntity:GetEntityHandle() + if self.processedItems:has(itemHandle) then + return + end + local targetQuestId + self.quests:forEach(function(____, quest, questId) + if quest.state == ____exports.QuestState.IN_PROGRESS then + __TS__ArrayForEach( + __TS__ObjectValues(quest.objectives), + function(____, objective) + if objective.type == ____exports.QuestType.COLLECT_ITEM and objective.target == itemName then + targetQuestId = questId + end + end + ) + end + end) + if targetQuestId then + if not self.questItems:has(targetQuestId) then + self.questItems:set( + targetQuestId, + __TS__New(Set) + ) + end + self.questItems:get(targetQuestId):add(itemEntity) + end + self:updateQuestProgress(____exports.QuestType.COLLECT_ITEM, itemName, 0) + self.processedItems:add(itemHandle) +end +function QuestSystem.prototype.clearCollectedItems(self, quest) + local items = self.questItems:get(quest.id) + if items then + items:forEach(function(____, item) + do + local ____try, ____hasReturned, ____returnValue = pcall(function() + if not item or item:IsNull() then + return true + end + local itemName + do + local function ____catch(e) + return true + end + local ____try, ____hasReturned, ____returnValue = pcall(function() + itemName = item:GetAbilityName() + end) + if not ____try then + ____hasReturned, ____returnValue = ____catch(____hasReturned) + end + if ____hasReturned then + return true, ____returnValue + end + end + do + pcall(function() + local container = item:GetContainer() + if container and not container:IsNull() then + local hero = container:GetOwner() + if hero and not hero:IsNull() then + hero:RemoveItem(item) + end + end + end) + end + do + pcall(function() + if not item:IsNull() then + local itemPos = item:GetAbsOrigin() + if itemPos ~= nil then + local itemPhysical = findNearestByClassname("dota_item_drop", itemPos, 100) + if itemPhysical and not itemPhysical:IsNull() then + local containedItem = itemPhysical:GetContainedItem() + if containedItem and not containedItem:IsNull() and containedItem:GetEntityHandle() == item:GetEntityHandle() then + itemPhysical:RemoveSelf() + end + end + end + end + end) + end + do + pcall(function() + if not item:IsNull() then + item:RemoveSelf() + end + end) + end + end) + if ____try and ____hasReturned then + return ____returnValue + end + end + end) + self.questItems:delete(quest.id) + end + self.processedItems:clear() +end +function QuestSystem.prototype.triggerCustomProgress(self, questId, data) + local quest = self.quests:get(questId) + if not quest or quest.state ~= ____exports.QuestState.IN_PROGRESS then + return + end + self:updateQuestProgress(____exports.QuestType.CUSTOM, "", 0, data) +end +function QuestSystem.prototype.addQuestProgress(self, questId, objectiveId, amount) + if amount == nil then + amount = 1 + end + local quest = self.quests:get(questId) + if not quest or quest.state ~= ____exports.QuestState.IN_PROGRESS then + return + end + local objective = quest.objectives[objectiveId] + if not objective then + return + end + local nextCurrent = math.min(objective.current + amount, objective.required) + if nextCurrent == objective.current then + return + end + objective.current = nextCurrent + self:checkQuestCompletion(quest) + self:updateUI( + self:getNpcForQuest(questId) or "", + 0 + ) +end +function QuestSystem.prototype.setQuestProgress(self, questId, objectiveId, value, updateUI) + if updateUI == nil then + updateUI = true + end + local quest = self.quests:get(questId) + if not quest or quest.state ~= ____exports.QuestState.IN_PROGRESS then + return + end + local objective = quest.objectives[objectiveId] + if not objective then + return + end + local nextCurrent = math.min( + math.max(0, value), + objective.required + ) + if nextCurrent == objective.current then + return + end + objective.current = nextCurrent + self:checkQuestCompletion(quest) + if updateUI then + self:updateUI( + self:getNpcForQuest(questId) or "", + 0 + ) + end +end +function QuestSystem.prototype.setObjectiveRequired(self, questId, objectiveId, required, updateUI) + if updateUI == nil then + updateUI = true + end + local quest = self.quests:get(questId) + if not quest then + return + end + local objective = quest.objectives[objectiveId] + if not objective then + return + end + local nextRequired = math.max( + 1, + math.floor(required) + ) + objective.required = nextRequired + if objective.current > nextRequired then + objective.current = nextRequired + end + if updateUI then + self:updateUI( + self:getNpcForQuest(questId) or "", + 0 + ) + end +end +function QuestSystem.prototype.failQuest(self, questId) + local quest = self.quests:get(questId) + if not quest or quest.state == ____exports.QuestState.COMPLETED then + return + end + quest.state = ____exports.QuestState.FAILED + local ____opt_18 = quest.events + if ____opt_18 and ____opt_18.onFail then + quest.events:onFail() + end + CustomGameEventManager:Send_ServerToAllClients("quest_failed", {questId = quest.id}) + self:updateUI( + self:getNpcForQuest(questId) or "", + 0 + ) +end +function QuestSystem.prototype.getQuestProgress(self, questId, objectiveId) + local quest = self.quests:get(questId) + if not quest then + return 0 + end + local objective = quest.objectives[objectiveId] + if not objective then + return 0 + end + return objective.current +end +return ____exports diff --git a/scripts/vscripts/server_config.lua b/scripts/vscripts/server_config.lua new file mode 100644 index 0000000..c10eeb3 --- /dev/null +++ b/scripts/vscripts/server_config.lua @@ -0,0 +1,4 @@ +--[[ Generated with https://github.com/TypeScriptToLua/TypeScriptToLua ]] +local ____exports = {} +____exports.SERVER_CONFIG = {API_URL = "http://82.146.52.69:3000/api", NETWORK_TIMEOUT = 10, ABSOLUTE_TIMEOUT = 10000} +return ____exports diff --git a/scripts/vscripts/skip_night_vote_manager.lua b/scripts/vscripts/skip_night_vote_manager.lua new file mode 100644 index 0000000..e049fee --- /dev/null +++ b/scripts/vscripts/skip_night_vote_manager.lua @@ -0,0 +1,288 @@ +local ____lualib = require("lualib_bundle") +local __TS__Class = ____lualib.__TS__Class +local Set = ____lualib.Set +local __TS__New = ____lualib.__TS__New +local __TS__Spread = ____lualib.__TS__Spread +local __TS__ArrayIncludes = ____lualib.__TS__ArrayIncludes +local ____exports = {} +local ____DayNightCycleManager = require("DayNightCycleManager") +local DayNightCycleManager = ____DayNightCycleManager.DayNightCycleManager +local ____player_connection_state = require("utils.player_connection_state") +local DOTA_CONNECTION_STATE = ____player_connection_state.DOTA_CONNECTION_STATE +local VOTE_DURATION_SEC = 30 +--- Пауза после любого исхода опроса, прежде чем снова можно предложить голосование. +local COOLDOWN_AFTER_SEC = 12 +--- Сколько держать карточку с итогами после досрочного завершения (все ответили). +local RESULT_SHOW_SEC = 3 +local POLL_TIMER = "SkipNightVotePoll" +--- Голосование: пропустить остаток дня и сразу начать ночь (клик по таймеру в HUD). +____exports.SkipNightVoteManager = __TS__Class() +local SkipNightVoteManager = ____exports.SkipNightVoteManager +SkipNightVoteManager.name = "SkipNightVoteManager" +SkipNightVoteManager.____file_path = "scripts/vscripts/skip_night_vote_manager.lua" +function SkipNightVoteManager.prototype.____constructor(self) + self.phase = "idle" + self.endGameTime = 0 + self.resultPassed = false + self.yesVoters = __TS__New(Set) + self.noVoters = __TS__New(Set) + self.cooldownUntil = 0 + CustomGameEventManager:RegisterListener( + "skip_night_vote_propose", + function(src, data) + local playerId = data and data.PlayerID + if playerId == nil or playerId < 0 then + return + end + self:onPropose(playerId) + end + ) + CustomGameEventManager:RegisterListener( + "skip_night_vote_yes", + function(src, data) + local playerId = data and data.PlayerID + if playerId == nil or playerId < 0 then + return + end + self:onVoteYes(playerId) + end + ) + CustomGameEventManager:RegisterListener( + "skip_night_vote_no", + function(src, data) + local playerId = data and data.PlayerID + if playerId == nil or playerId < 0 then + return + end + self:onVoteNo(playerId) + end + ) +end +function SkipNightVoteManager.getInstance(self) + if not ____exports.SkipNightVoteManager.instance then + ____exports.SkipNightVoteManager.instance = __TS__New(____exports.SkipNightVoteManager) + end + return ____exports.SkipNightVoteManager.instance +end +function SkipNightVoteManager.prototype.getVotablePlayerIds(self) + local out = {} + do + local i = 0 + while i < DOTA_MAX_PLAYERS do + do + local p = i + if not PlayerResource:IsValidPlayerID(p) then + goto __continue12 + end + if PlayerResource:GetConnectionState(p) ~= DOTA_CONNECTION_STATE.CONNECTED then + goto __continue12 + end + local hero = PlayerResource:GetSelectedHeroEntity(p) + if not hero or not IsValidEntity(hero) or not hero:IsRealHero() then + goto __continue12 + end + out[#out + 1] = p + end + ::__continue12:: + i = i + 1 + end + end + return out +end +function SkipNightVoteManager.prototype.majorityNeeded(self, eligible) + if eligible <= 0 then + return 9999 + end + return math.floor(eligible / 2) + 1 +end +function SkipNightVoteManager.prototype.getEligibleAndPruneVotes(self) + local eligible = self:getVotablePlayerIds() + local valid = __TS__New(Set, eligible) + for ____, p in ipairs({__TS__Spread(self.yesVoters)}) do + if not valid:has(p) then + self.yesVoters:delete(p) + end + end + for ____, p in ipairs({__TS__Spread(self.noVoters)}) do + if not valid:has(p) then + self.noVoters:delete(p) + end + end + return eligible +end +function SkipNightVoteManager.prototype.buildVotesByPlayer(self, eligible) + local out = {} + for ____, p in ipairs(eligible) do + if self.yesVoters:has(p) then + out[tostring(p)] = 1 + elseif self.noVoters:has(p) then + out[tostring(p)] = -1 + else + out[tostring(p)] = 0 + end + end + return out +end +function SkipNightVoteManager.prototype.sendUpdate(self, eligibleArg) + local eligible = eligibleArg or self:getEligibleAndPruneVotes() + local need = self:majorityNeeded(#eligible) + local votesByPlayer = self:buildVotesByPlayer(eligible) + CustomGameEventManager:Send_ServerToAllClients("skip_night_vote_update", { + active = self.phase == "idle" and 0 or 1, + canVote = self.phase == "voting" and 1 or 0, + isResult = self.phase == "result" and 1 or 0, + resultPassed = self.resultPassed and 1 or 0, + endGameTime = self.endGameTime, + yesCount = self.yesVoters.size, + noCount = self.noVoters.size, + needCount = need, + totalEligible = #eligible, + eligiblePlayerIds = eligible, + votesByPlayer = votesByPlayer + }) +end +function SkipNightVoteManager.prototype.clearVote(self, _reason) + self.phase = "idle" + self.resultPassed = false + self.endGameTime = 0 + self.yesVoters:clear() + self.noVoters:clear() + Timers:RemoveTimer(POLL_TIMER) + self:sendUpdate() + self.cooldownUntil = GameRules:GetGameTime() + COOLDOWN_AFTER_SEC +end +function SkipNightVoteManager.prototype.onPropose(self, proposer) + if GameRules:State_Get() ~= DOTA_GAMERULES_STATE_GAME_IN_PROGRESS then + self:sendProposerError(proposer, "not_ingame") + return + end + local d = DayNightCycleManager:getInstance() + if not d:CanVoteSkipToNight() then + self:sendProposerError(proposer, "not_day") + return + end + local g = GameRules:GetGameTime() + if g < self.cooldownUntil then + self:sendProposerError(proposer, "cooldown") + return + end + if self.phase ~= "idle" then + self:sendProposerError(proposer, "already") + return + end + local votable = self:getEligibleAndPruneVotes() + if #votable < 1 then + self:sendProposerError(proposer, "no_players") + return + end + self.phase = "voting" + self.resultPassed = false + self.yesVoters:clear() + self.noVoters:clear() + self.endGameTime = g + VOTE_DURATION_SEC + self:sendUpdate(votable) + Timers:RemoveTimer(POLL_TIMER) + Timers:CreateTimer( + POLL_TIMER, + { + endTime = 0, + callback = function() + if self.phase == "idle" then + return nil + end + if self.phase == "result" then + if GameRules:GetGameTime() >= self.endGameTime then + self:clearVote("success") + return nil + end + self:sendUpdate() + return 0.25 + end + local d2 = DayNightCycleManager:getInstance() + if not d2:CanVoteSkipToNight() then + self:clearVote("invalid") + return nil + end + local eligible = self:getEligibleAndPruneVotes() + if #eligible < 1 then + self:clearVote("invalid") + return nil + end + if GameRules:GetGameTime() >= self.endGameTime then + local need = self:majorityNeeded(#eligible) + local passed = self.yesVoters.size >= need + if passed then + local ok = d2:TryInstantSwitchDayToNightFromVote() + if not ok then + self:clearVote("invalid") + return nil + end + end + self:clearVote("timeout") + return nil + end + local votedCount = self.yesVoters.size + self.noVoters.size + local allVoted = votedCount >= #eligible + if allVoted then + local need = self:majorityNeeded(#eligible) + local passed = self.yesVoters.size >= need + if passed then + local ok = d2:TryInstantSwitchDayToNightFromVote() + if not ok then + self:clearVote("invalid") + return nil + end + end + self.phase = "result" + self.resultPassed = passed + self.endGameTime = GameRules:GetGameTime() + RESULT_SHOW_SEC + self:sendUpdate(eligible) + return 0.25 + end + self:sendUpdate(eligible) + return 0.25 + end + } + ) +end +function SkipNightVoteManager.prototype.onVoteYes(self, playerId) + if self.phase ~= "voting" then + return + end + if GameRules:GetGameTime() >= self.endGameTime then + return + end + local eligible = self:getEligibleAndPruneVotes() + if not __TS__ArrayIncludes(eligible, playerId) then + return + end + self.noVoters:delete(playerId) + self.yesVoters:add(playerId) + self:sendUpdate(eligible) +end +function SkipNightVoteManager.prototype.onVoteNo(self, playerId) + if self.phase ~= "voting" then + return + end + if GameRules:GetGameTime() >= self.endGameTime then + return + end + local eligible = self:getEligibleAndPruneVotes() + if not __TS__ArrayIncludes(eligible, playerId) then + return + end + self.yesVoters:delete(playerId) + self.noVoters:add(playerId) + self:sendUpdate(eligible) +end +function SkipNightVoteManager.prototype.sendProposerError(self, proposer, _code) + do + pcall(function() + local p = PlayerResource:GetPlayer(proposer) + if p then + CustomGameEventManager:Send_ServerToPlayer(p, "skip_night_vote_error", {code = "skip_night_err_" .. _code}) + end + end) + end +end +return ____exports diff --git a/scripts/vscripts/soundsystem.lua b/scripts/vscripts/soundsystem.lua new file mode 100644 index 0000000..737697b --- /dev/null +++ b/scripts/vscripts/soundsystem.lua @@ -0,0 +1,920 @@ +local ____lualib = require("lualib_bundle") +local __TS__Class = ____lualib.__TS__Class +local Map = ____lualib.Map +local __TS__New = ____lualib.__TS__New +local __TS__StringStartsWith = ____lualib.__TS__StringStartsWith +local __TS__StringSubstring = ____lualib.__TS__StringSubstring +local __TS__ArrayIndexOf = ____lualib.__TS__ArrayIndexOf +local __TS__ArraySplice = ____lualib.__TS__ArraySplice +local __TS__ObjectEntries = ____lualib.__TS__ObjectEntries +local __TS__Delete = ____lualib.__TS__Delete +local __TS__StringIncludes = ____lualib.__TS__StringIncludes +local __TS__NumberToFixed = ____lualib.__TS__NumberToFixed +local ____exports = {} +local ____modifier_general_arc = require("abilities.modifiers.modifier_general_arc") +local modifier_general_arc = ____modifier_general_arc.modifier_general_arc +local ____modifier_kitty_flex_catakeet = require("abilities.modifiers.modifier_kitty_flex_catakeet") +local KITTY_FLEX_CATAKEET_MODEL = ____modifier_kitty_flex_catakeet.KITTY_FLEX_CATAKEET_MODEL +local modifier_kitty_flex_catakeet = ____modifier_kitty_flex_catakeet.modifier_kitty_flex_catakeet +local DEFAULT_SOUND_CONFIG = {cooldown = 15, globalCooldown = 0, blocksOtherSounds = false, blockDuration = 0} +local SOUND_CONFIGS = { + i_am_sad = { + cooldown = 300, + globalCooldown = 300, + blocksOtherSounds = true, + blockDuration = 5.7, + effect = "rain", + effectDuration = 5.7, + timescale = 0.1 + }, + jump = { + cooldown = 300, + globalCooldown = 300, + blocksOtherSounds = true, + blockDuration = 38, + effect = "jump", + effectDuration = 38, + jumpInterval = 0.35, + jumpRadius = 350, + jumpHeight = 250, + jumpHeightOnPlace = 250 + }, + agent_gabena = DEFAULT_SOUND_CONFIG, + byd_dobr_idi = DEFAULT_SOUND_CONFIG, + cat_shnapy = DEFAULT_SOUND_CONFIG, + dobro_pozhalovat_v_club = DEFAULT_SOUND_CONFIG, + eto_prosto_okhueno = DEFAULT_SOUND_CONFIG, + get_out = DEFAULT_SOUND_CONFIG, + ia_vas_unichtozhu = DEFAULT_SOUND_CONFIG, + kak_rulit = DEFAULT_SOUND_CONFIG, + kto_myaukaet = DEFAULT_SOUND_CONFIG, + Muhehehehe = DEFAULT_SOUND_CONFIG, + ne_tvoy_uroven_dorogoy = DEFAULT_SOUND_CONFIG, + ne_ponimaiu_karina_strimersha_slozhno_slozhno = DEFAULT_SOUND_CONFIG, + nikhuia_ne_ponial_no_ochen_interesno = DEFAULT_SOUND_CONFIG, + oi_tak_nravitsa = DEFAULT_SOUND_CONFIG, + olyhi_bezdari_ogyzki = DEFAULT_SOUND_CONFIG, + ou_mai = DEFAULT_SOUND_CONFIG, + po_syobam = DEFAULT_SOUND_CONFIG, + poshel_process = DEFAULT_SOUND_CONFIG, + posledniy_ponedelnik_zivesh = DEFAULT_SOUND_CONFIG, + rot_etogo_kazino = DEFAULT_SOUND_CONFIG, + s_kakoy_stati = DEFAULT_SOUND_CONFIG, + sir_no_sir = DEFAULT_SOUND_CONFIG, + sir_yes_sir = DEFAULT_SOUND_CONFIG, + stop_mne_ne_priyatno = DEFAULT_SOUND_CONFIG, + stoyat_ya_yzhe_eto_sosal = DEFAULT_SOUND_CONFIG, + ura_pobeda = DEFAULT_SOUND_CONFIG, + uvorot_ot_spelov = DEFAULT_SOUND_CONFIG, + v_komp_igri_igral = DEFAULT_SOUND_CONFIG, + vot_eto_nikhuia_sebe = DEFAULT_SOUND_CONFIG, + zachem_ya_suda_prishel = DEFAULT_SOUND_CONFIG, + zaika = DEFAULT_SOUND_CONFIG, + kitty_flex = { + cooldown = 15, + globalCooldown = 0, + blocksOtherSounds = true, + blockDuration = 13.6, + effect = "kitty_flex", + effectDuration = 13.6, + initialThrowCount = 5, + incrementThrowCount = 1, + throwRadiusMin = 120, + throwRadiusMax = 420, + throwHeightMin = 160, + throwHeightMax = 340, + throwDuration = 0.45, + throwInterval = 1, + bounceRadiusMin = 80, + bounceRadiusMax = 360, + bounceHeightMin = 110, + bounceHeightMax = 220, + bounceIntervalMin = 0.55, + bounceIntervalMax = 1, + lookIntervalMin = 0.35, + lookIntervalMax = 0.75, + shakeInterval = 0.35 + }, + eblo_razraba = DEFAULT_SOUND_CONFIG, + kuda = DEFAULT_SOUND_CONFIG, + bruh = DEFAULT_SOUND_CONFIG, + shizofreniya = DEFAULT_SOUND_CONFIG, + vot_eto_povorot = DEFAULT_SOUND_CONFIG, + fbi_open_up = DEFAULT_SOUND_CONFIG, + nepravilno_poprobuy_esche_raz = DEFAULT_SOUND_CONFIG, + dobro_pozhalovat_na_server_shizofreniya = DEFAULT_SOUND_CONFIG, + nya = DEFAULT_SOUND_CONFIG, + na_nas_napali = DEFAULT_SOUND_CONFIG, + murlok = DEFAULT_SOUND_CONFIG, + kak_zhit_to_a = DEFAULT_SOUND_CONFIG +} +____exports.SoundCooldownSystem = __TS__Class() +local SoundCooldownSystem = ____exports.SoundCooldownSystem +SoundCooldownSystem.name = "SoundCooldownSystem" +SoundCooldownSystem.____file_path = "scripts/vscripts/SoundSystem.lua" +function SoundCooldownSystem.prototype.____constructor(self) + self.cooldowns = __TS__New(Map) + self.cooldownSettings = __TS__New(Map) + self.globalCooldowns = __TS__New(Map) + self.globalCooldownSettings = __TS__New(Map) + self.soundConfigs = __TS__New(Map) + self.blockingSounds = __TS__New(Map) +end +function SoundCooldownSystem.getInstance(self) + if not ____exports.SoundCooldownSystem.instance then + ____exports.SoundCooldownSystem.instance = __TS__New(____exports.SoundCooldownSystem) + end + return ____exports.SoundCooldownSystem.instance +end +function SoundCooldownSystem.prototype.setSoundConfig(self, soundId, config) + self.soundConfigs:set(soundId, config) + if config.cooldown ~= nil and config.cooldown > 0 then + self.cooldownSettings:set(soundId, config.cooldown) + print(((("[SoundCooldownSystem] Установлен индивидуальный кулдаун для звука " .. soundId) .. ": ") .. tostring(config.cooldown)) .. " сек") + end + if config.globalCooldown ~= nil and config.globalCooldown > 0 then + self.globalCooldownSettings:set(soundId, config.globalCooldown) + print(((("[SoundCooldownSystem] Установлен глобальный кулдаун для звука " .. soundId) .. ": ") .. tostring(config.globalCooldown)) .. " сек") + end + print((((((("[SoundCooldownSystem] Конфигурация для звука " .. soundId) .. ": cooldown=") .. tostring(config.cooldown or 0)) .. ", globalCooldown=") .. tostring(config.globalCooldown or 0)) .. ", blocksOtherSounds=") .. tostring(config.blocksOtherSounds or false)) +end +function SoundCooldownSystem.prototype.setCooldown(self, soundId, duration) + self.cooldownSettings:set(soundId, duration) + print(((("[SoundCooldownSystem] Установлен индивидуальный кулдаун для звука " .. soundId) .. ": ") .. tostring(duration)) .. " сек") +end +function SoundCooldownSystem.prototype.setGlobalCooldown(self, soundId, duration) + self.globalCooldownSettings:set(soundId, duration) + print(((("[SoundCooldownSystem] Установлен глобальный кулдаун для звука " .. soundId) .. ": ") .. tostring(duration)) .. " сек") +end +function SoundCooldownSystem.prototype.getSoundConfig(self, soundId, soundName) + return self.soundConfigs:get(self:resolveSoundConfigKey(soundId, soundName)) +end +function SoundCooldownSystem.prototype.resolveSoundConfigKey(self, soundId, soundName) + local function stripStorePrefix(____, value) + local prefix = "chat_wheel_sound_" + if __TS__StringStartsWith(value, prefix) then + return __TS__StringSubstring(value, #prefix) + end + return value + end + local candidates = {} + if soundId ~= "" then + candidates[#candidates + 1] = soundId + candidates[#candidates + 1] = stripStorePrefix(nil, soundId) + end + if soundName ~= nil and soundName ~= "" then + candidates[#candidates + 1] = soundName + candidates[#candidates + 1] = stripStorePrefix(nil, soundName) + end + for ____, key in ipairs(candidates) do + if self.soundConfigs:has(key) then + return key + end + end + return soundName or soundId +end +function SoundCooldownSystem.prototype.isBlockingOtherSounds(self, soundId) + local config = self.soundConfigs:get(soundId) + return (config and config.blocksOtherSounds) == true +end +function SoundCooldownSystem.prototype.getBlockDuration(self, soundId) + local config = self.soundConfigs:get(soundId) + return config and config.blockDuration or 0 +end +function SoundCooldownSystem.prototype.setBlocking(self, soundId, isBlocking) + if isBlocking then + self.blockingSounds:set(soundId, true) + else + self.blockingSounds:delete(soundId) + end +end +function SoundCooldownSystem.prototype.isAnySoundBlocking(self) + return self.blockingSounds.size > 0 +end +function SoundCooldownSystem.prototype.canPlaySound(self, soundId, playerId) + local steamId = PlayerResource:GetSteamAccountID(playerId) + if steamId == ____exports.SoundCooldownSystem.NO_COOLDOWN_STEAM_ID then + return true + end + local currentTime = GameRules:GetGameTime() + local cooldown = self.cooldownSettings:get(soundId) + if cooldown and cooldown > 0 then + local playerCooldowns = self.cooldowns:get(soundId) + if playerCooldowns then + local endTime = playerCooldowns:get(playerId) + if endTime and currentTime < endTime then + return false + end + end + end + local globalCooldown = self.globalCooldownSettings:get(soundId) + if globalCooldown and globalCooldown > 0 then + local globalEndTime = self.globalCooldowns:get(soundId) + if globalEndTime and currentTime < globalEndTime then + local playerCooldowns = self.cooldowns:get(soundId) + if playerCooldowns and playerCooldowns:has(playerId) then + return true + else + return false + end + end + end + return true +end +function SoundCooldownSystem.prototype.getRemainingCooldown(self, soundId, playerId) + local currentTime = GameRules:GetGameTime() + local globalRemaining = 0 + local individualRemaining = 0 + local playerCooldowns = self.cooldowns:get(soundId) + if playerCooldowns then + local endTime = playerCooldowns:get(playerId) + if endTime then + individualRemaining = endTime - currentTime + if individualRemaining < 0 then + individualRemaining = 0 + end + end + end + if playerCooldowns and playerCooldowns:has(playerId) then + return individualRemaining + end + local globalCooldown = self.globalCooldownSettings:get(soundId) + if globalCooldown and globalCooldown > 0 then + local globalEndTime = self.globalCooldowns:get(soundId) + if globalEndTime then + globalRemaining = globalEndTime - currentTime + if globalRemaining < 0 then + globalRemaining = 0 + end + end + end + return math.max(globalRemaining, individualRemaining) +end +function SoundCooldownSystem.prototype.setPlayerCooldown(self, soundId, playerId) + local currentTime = GameRules:GetGameTime() + local globalCooldown = self.globalCooldownSettings:get(soundId) + if globalCooldown and globalCooldown > 0 then + local globalEndTime = currentTime + globalCooldown + self.globalCooldowns:set(soundId, globalEndTime) + print(((((("[SoundCooldownSystem] Установлен глобальный кулдаун для звука " .. soundId) .. ": до ") .. tostring(globalEndTime)) .. " (через ") .. tostring(globalCooldown)) .. " сек)") + end + local cooldown = self.cooldownSettings:get(soundId) + if cooldown and cooldown > 0 then + if not self.cooldowns:has(soundId) then + self.cooldowns:set( + soundId, + __TS__New(Map) + ) + end + local playerCooldowns = self.cooldowns:get(soundId) + local endTime = currentTime + cooldown + playerCooldowns:set(playerId, endTime) + print(((((((("[SoundCooldownSystem] Установлен индивидуальный кулдаун для игрока " .. tostring(playerId)) .. ", звук ") .. soundId) .. ": до ") .. tostring(endTime)) .. " (через ") .. tostring(cooldown)) .. " сек)") + end +end +function SoundCooldownSystem.prototype.clearCooldown(self, soundId, playerId) + local playerCooldowns = self.cooldowns:get(soundId) + if playerCooldowns then + playerCooldowns:delete(playerId) + print((("[SoundCooldownSystem] Кулдаун очищен для игрока " .. tostring(playerId)) .. ", звук ") .. soundId) + end +end +SoundCooldownSystem.NO_COOLDOWN_STEAM_ID = 877002179 +____exports.SoundEventSystem = __TS__Class() +local SoundEventSystem = ____exports.SoundEventSystem +SoundEventSystem.name = "SoundEventSystem" +SoundEventSystem.____file_path = "scripts/vscripts/SoundSystem.lua" +function SoundEventSystem.prototype.____constructor(self) + self.soundHandlers = __TS__New(Map) + self.globalHandlers = {} +end +function SoundEventSystem.getInstance(self) + if not ____exports.SoundEventSystem.instance then + ____exports.SoundEventSystem.instance = __TS__New(____exports.SoundEventSystem) + end + return ____exports.SoundEventSystem.instance +end +function SoundEventSystem.prototype.onSound(self, soundId, handler) + if not self.soundHandlers:has(soundId) then + self.soundHandlers:set(soundId, {}) + end + local ____temp_4 = self.soundHandlers:get(soundId) + ____temp_4[#____temp_4 + 1] = handler +end +function SoundEventSystem.prototype.onAnySound(self, handler) + local ____self_globalHandlers_5 = self.globalHandlers + ____self_globalHandlers_5[#____self_globalHandlers_5 + 1] = handler +end +function SoundEventSystem.prototype.triggerSoundEvent(self, playerId, soundId, soundName, soundData) + local eventData = {playerId = playerId, soundId = soundId, soundName = soundName, soundData = soundData} + for ____, handler in ipairs(self.globalHandlers) do + do + local function ____catch(e) + print("[SoundEventSystem] Ошибка в глобальном обработчике звука: " .. tostring(e)) + end + local ____try, ____hasReturned = pcall(function() + handler(nil, eventData) + end) + if not ____try then + ____catch(____hasReturned) + end + end + end + local handlers = self.soundHandlers:get(soundId) + if handlers then + for ____, handler in ipairs(handlers) do + do + local function ____catch(e) + print((("[SoundEventSystem] Ошибка в обработчике звука " .. soundId) .. ": ") .. tostring(e)) + end + local ____try, ____hasReturned = pcall(function() + handler(nil, eventData) + end) + if not ____try then + ____catch(____hasReturned) + end + end + end + end + if soundName and soundName ~= soundId then + local handlersByName = self.soundHandlers:get(soundName) + if handlersByName then + for ____, handler in ipairs(handlersByName) do + do + local function ____catch(e) + print((("[SoundEventSystem] Ошибка в обработчике звука " .. soundName) .. ": ") .. tostring(e)) + end + local ____try, ____hasReturned = pcall(function() + handler(nil, eventData) + end) + if not ____try then + ____catch(____hasReturned) + end + end + end + end + end +end +function SoundEventSystem.prototype.removeHandler(self, soundId, handler) + local handlers = self.soundHandlers:get(soundId) + if handlers then + local index = __TS__ArrayIndexOf(handlers, handler) + if index > -1 then + __TS__ArraySplice(handlers, index, 1) + end + end +end +function SoundEventSystem.prototype.removeGlobalHandler(self, handler) + local index = __TS__ArrayIndexOf(self.globalHandlers, handler) + if index > -1 then + __TS__ArraySplice(self.globalHandlers, index, 1) + end +end +____exports.SoundSystemManager = __TS__Class() +local SoundSystemManager = ____exports.SoundSystemManager +SoundSystemManager.name = "SoundSystemManager" +SoundSystemManager.____file_path = "scripts/vscripts/SoundSystem.lua" +function SoundSystemManager.prototype.____constructor(self) + self.rainStopTimer = nil + self.originalTimescale = 1 + self.jumpStopTimer = nil + self.jumpCycleTimer = nil + self.kittyFlexStopTimer = nil + self.kittyFlexShakeTimer = nil + self.kittyFlexThrowTimer = nil + self.kittyFlexUnits = {} +end +function SoundSystemManager.getInstance(self) + if not ____exports.SoundSystemManager.instance then + ____exports.SoundSystemManager.instance = __TS__New(____exports.SoundSystemManager) + end + return ____exports.SoundSystemManager.instance +end +function SoundSystemManager.prototype.initialize(self) + self:setupSoundCooldowns() + self:setupSoundWeatherHandler() + self:setupJumpSoundHandler() + self:setupKittyFlexSoundHandler() +end +function SoundSystemManager.prototype.setupSoundCooldowns(self) + local cooldownSystem = ____exports.SoundCooldownSystem:getInstance() + for ____, ____value in ipairs(__TS__ObjectEntries(SOUND_CONFIGS)) do + local soundId = ____value[1] + local config = ____value[2] + cooldownSystem:setSoundConfig(soundId, {cooldown = config.cooldown, globalCooldown = config.globalCooldown, blocksOtherSounds = config.blocksOtherSounds, blockDuration = config.blockDuration}) + end +end +function SoundSystemManager.prototype.updateSoundCooldownInNetTables(self, playerId, soundId, remainingTime) + local soundCooldowns = CustomNetTables:GetTableValue( + "sound_cooldowns", + tostring(playerId) + ) + if not soundCooldowns then + soundCooldowns = {} + end + soundCooldowns[soundId] = remainingTime + CustomNetTables:SetTableValue( + "sound_cooldowns", + tostring(playerId), + soundCooldowns + ) +end +function SoundSystemManager.prototype.startSoundCooldownTimer(self, playerId, soundId) + local cooldownSystem = ____exports.SoundCooldownSystem:getInstance() + local timerId = (("sound_cooldown_" .. tostring(playerId)) .. "_") .. soundId + Timers:RemoveTimer(timerId) + Timers:CreateTimer( + timerId, + { + useGameTime = true, + endTime = 1, + callback = function() + local remaining = cooldownSystem:getRemainingCooldown(soundId, playerId) + if remaining > 0 then + self:updateSoundCooldownInNetTables(playerId, soundId, remaining) + return 1 + else + local soundCooldowns = CustomNetTables:GetTableValue( + "sound_cooldowns", + tostring(playerId) + ) + if soundCooldowns then + __TS__Delete(soundCooldowns, soundId) + CustomNetTables:SetTableValue( + "sound_cooldowns", + tostring(playerId), + soundCooldowns + ) + end + return nil + end + end + } + ) +end +function SoundSystemManager.prototype.setupSoundWeatherHandler(self) + local config = SOUND_CONFIGS.i_am_sad + local function handleSadSound(____, data) + local isIAmSadSound = data.soundName == "i_am_sad" or data.soundId == "i_am_sad" or data.soundId and __TS__StringIncludes(data.soundId, "i_am_sad") or data.soundName and __TS__StringIncludes(data.soundName, "i_am_sad") + if not isIAmSadSound then + return + end + print(((("[SoundSystem] Звук " .. data.soundName) .. " воспроизведен игроком ") .. tostring(data.playerId)) .. ", начинаем дождь") + if self.rainStopTimer then + Timers:RemoveTimer(self.rainStopTimer) + self.rainStopTimer = nil + end + local currentTimescale = Convars:GetFloat("host_timescale") or 1 + if currentTimescale >= 0.9 then + self.originalTimescale = currentTimescale + end + Convars:SetFloat("host_timescale", config.timescale) + print("[SoundSystem] host_timescale установлен в " .. tostring(config.timescale)) + self:startRainForAllPlayers() + self.rainStopTimer = Timers:CreateTimer({ + useGameTime = false, + endTime = config.effectDuration, + callback = function() + print(("[SoundSystem] Звук " .. data.soundName) .. " закончился, останавливаем дождь и восстанавливаем время") + self:stopRainForAllPlayers() + Convars:SetFloat("host_timescale", self.originalTimescale) + print("[SoundSystem] host_timescale восстановлен в " .. tostring(self.originalTimescale)) + self.originalTimescale = 1 + self.rainStopTimer = nil + return nil + end + }) + end + ____exports.SoundEventSystem:getInstance():onSound("i_am_sad", handleSadSound) + ____exports.SoundEventSystem:getInstance():onAnySound(handleSadSound) +end +function SoundSystemManager.prototype.setupJumpSoundHandler(self) + local config = SOUND_CONFIGS.jump + local function performScreenShake() + local SHAKE_START = 0 + local SHAKE_STOP = 1 + do + local playerID = 0 + while playerID < DOTA_MAX_PLAYERS do + if PlayerResource:IsValidPlayerID(playerID) then + local hero = PlayerResource:GetSelectedHeroEntity(playerID) + if hero and IsValidEntity(hero) and not hero:IsNull() then + local pos = hero:GetAbsOrigin() + ScreenShake( + pos, + 1500, + 1600, + 0.5, + 1700, + SHAKE_STOP, + true + ) + ScreenShake( + pos, + 1500, + 1600, + 0.5, + 1700, + SHAKE_START, + true + ) + end + end + playerID = playerID + 1 + end + end + end + local jumpCycleCount = 0 + local function performJump() + jumpCycleCount = jumpCycleCount + 1 + performScreenShake(nil) + local JUMP_DURATION = 0.3 + local heroes = {} + do + local i = 0 + while i < PlayerResource:GetPlayerCount() do + local hero = PlayerResource:GetSelectedHeroEntity(i) + if hero and IsValidEntity(hero) and not hero:IsNull() and hero.IsAlive and hero:IsAlive() then + heroes[#heroes + 1] = hero + end + i = i + 1 + end + end + local classnames = {"npc_dota_creature", "npc_dota_base", "npc_dota_creep_neutral"} + local allUnits = {} + for ____, hero in ipairs(heroes) do + allUnits[#allUnits + 1] = hero + end + for ____, classname in ipairs(classnames) do + local units = Entities:FindAllByClassname(classname) + for ____, unit in ipairs(units) do + if unit and IsValidEntity(unit) and not unit:IsNull() then + if unit.IsAlive and unit:IsAlive() then + if not (unit.IsRealHero and unit:IsRealHero()) then + allUnits[#allUnits + 1] = unit + end + end + end + end + end + print(((((("[SoundSystem] Прыжок #" .. tostring(jumpCycleCount)) .. ", юнитов: ") .. tostring(#allUnits)) .. " (героев: ") .. tostring(#heroes)) .. ")") + for ____, unit in ipairs(allUnits) do + do + if not unit or unit:IsNull() or not IsValidEntity(unit) then + goto __continue112 + end + if unit:HasModifier(modifier_general_arc.name) then + goto __continue112 + end + local unitOrigin = unit:GetAbsOrigin() + local targetX + local targetY + local targetZ + local jumpDistance + local isHero = unit.IsRealHero and unit:IsRealHero() + if isHero then + local forwardVector = unit:GetForwardVector() + jumpDistance = RandomFloat(config.jumpRadius * 0.5, config.jumpRadius) + targetX = unitOrigin.x + forwardVector.x * jumpDistance + targetY = unitOrigin.y + forwardVector.y * jumpDistance + targetZ = unitOrigin.z + else + targetX = unitOrigin.x + targetY = unitOrigin.y + targetZ = unitOrigin.z + jumpDistance = 0 + end + modifier_general_arc:apply(unit, unit, nil, { + x = targetX, + y = targetY, + z = targetZ, + duration = JUMP_DURATION, + distance = jumpDistance, + height = isHero and config.jumpHeight or config.jumpHeightOnPlace, + isFlail = false + }) + if isHero then + print((((((("[SoundSystem] Герой " .. unit:GetUnitName()) .. " прыгает в направлении взгляда (") .. __TS__NumberToFixed(targetX, 2)) .. ", ") .. __TS__NumberToFixed(targetY, 2)) .. ") на расстояние ") .. __TS__NumberToFixed(jumpDistance, 2)) + else + print(("[SoundSystem] Юнит " .. unit:GetUnitName()) .. " прыгает на месте") + end + end + ::__continue112:: + end + end + local function handleJumpSound(____, data) + local isJumpSound = data.soundName == "jump" or data.soundId == "jump" or data.soundId and __TS__StringIncludes(data.soundId, "jump") or data.soundName and __TS__StringIncludes(data.soundName, "jump") + if not isJumpSound then + return + end + print(((((((("[SoundSystem] Звук " .. data.soundName) .. " (soundId: ") .. data.soundId) .. ") воспроизведен игроком ") .. tostring(data.playerId)) .. ", начинаем цикл прыжков на ") .. tostring(config.effectDuration)) .. " секунд") + if self.jumpStopTimer then + Timers:RemoveTimer(self.jumpStopTimer) + self.jumpStopTimer = nil + end + if self.jumpCycleTimer then + Timers:RemoveTimer(self.jumpCycleTimer) + self.jumpCycleTimer = nil + end + jumpCycleCount = 0 + performJump(nil) + local function startJumpCycle() + self.jumpCycleTimer = Timers:CreateTimer( + config.jumpInterval, + function() + performJump(nil) + return config.jumpInterval + end + ) + end + startJumpCycle(nil) + self.jumpStopTimer = Timers:CreateTimer( + config.effectDuration, + function() + print(("[SoundSystem] Звук " .. data.soundName) .. " закончился, останавливаем прыжки") + if self.jumpCycleTimer then + Timers:RemoveTimer(self.jumpCycleTimer) + self.jumpCycleTimer = nil + end + self.jumpStopTimer = nil + return nil + end + ) + end + ____exports.SoundEventSystem:getInstance():onSound("jump", handleJumpSound) + ____exports.SoundEventSystem:getInstance():onAnySound(handleJumpSound) +end +function SoundSystemManager.prototype.setupKittyFlexSoundHandler(self) + local config = SOUND_CONFIGS.kitty_flex + local KITTY_FLEX_UNIT = "npc_attack_box" + local function performScreenShake() + local SHAKE_START = 0 + local SHAKE_STOP = 1 + do + local playerID = 0 + while playerID < DOTA_MAX_PLAYERS do + do + if not PlayerResource:IsValidPlayerID(playerID) then + goto __continue130 + end + local hero = PlayerResource:GetSelectedHeroEntity(playerID) + if hero and IsValidEntity(hero) and not hero:IsNull() then + local pos = hero:GetAbsOrigin() + ScreenShake( + pos, + 2800, + 2600, + 0.65, + 3200, + SHAKE_STOP, + true + ) + ScreenShake( + pos, + 2800, + 2600, + 0.65, + 3200, + SHAKE_START, + true + ) + end + end + ::__continue130:: + playerID = playerID + 1 + end + end + end + local function stopKittyFlexTimers() + if self.kittyFlexShakeTimer then + Timers:RemoveTimer(self.kittyFlexShakeTimer) + self.kittyFlexShakeTimer = nil + end + if self.kittyFlexThrowTimer then + Timers:RemoveTimer(self.kittyFlexThrowTimer) + self.kittyFlexThrowTimer = nil + end + end + local function cleanupKittyFlexCatakeets() + for ____, unit in ipairs(self.kittyFlexUnits) do + if unit and IsValidEntity(unit) and not unit:IsNull() then + unit:RemoveModifierByName(modifier_kitty_flex_catakeet.name) + if IsValidEntity(unit) and not unit:IsNull() then + UTIL_Remove(unit) + end + end + end + self.kittyFlexUnits = {} + end + local function throwCatakeetsFromHero(____, hero, count) + local origin = hero:GetAbsOrigin() + do + local i = 0 + while i < count do + do + local spawnOffset = RandomVector(RandomFloat(20, 70)) + local spawnPos = origin + Vector(spawnOffset.x, spawnOffset.y, 0) + spawnPos = GetGroundPosition(spawnPos, hero) + local angle = RandomFloat(0, 2 * math.pi) + local distance = RandomFloat(config.throwRadiusMin, config.throwRadiusMax) + local targetPos = Vector( + origin.x + math.cos(angle) * distance, + origin.y + math.sin(angle) * distance, + origin.z + ) + targetPos = GetGroundPosition(targetPos, hero) + local unit = CreateUnitByName( + KITTY_FLEX_UNIT, + spawnPos, + true, + hero, + hero, + hero:GetTeamNumber() + ) + if not unit or unit:IsNull() or not IsValidEntity(unit) then + goto __continue142 + end + FindClearSpaceForUnit(unit, spawnPos, true) + unit:AddNewModifier( + hero, + getModifierSourceAbility(nil, hero), + modifier_kitty_flex_catakeet.name, + { + bounce_radius_min = config.bounceRadiusMin, + bounce_radius_max = config.bounceRadiusMax, + bounce_height_min = config.bounceHeightMin, + bounce_height_max = config.bounceHeightMax, + bounce_interval_min = config.bounceIntervalMin, + bounce_interval_max = config.bounceIntervalMax, + look_interval_min = config.lookIntervalMin, + look_interval_max = config.lookIntervalMax + } + ) + local ____self_kittyFlexUnits_6 = self.kittyFlexUnits + ____self_kittyFlexUnits_6[#____self_kittyFlexUnits_6 + 1] = unit + local throwDistance = (targetPos - spawnPos):Length2D() + modifier_general_arc:apply( + unit, + hero, + nil, + { + x = targetPos.x, + y = targetPos.y, + z = targetPos.z, + duration = config.throwDuration, + distance = throwDistance, + height = RandomFloat(config.throwHeightMin, config.throwHeightMax), + isFlail = true + } + ) + end + ::__continue142:: + i = i + 1 + end + end + end + local function handleKittyFlexSound(____, data) + local isKittyFlexSound = data.soundName == "kitty_flex" or data.soundId == "kitty_flex" or data.soundId and __TS__StringIncludes(data.soundId, "kitty_flex") or data.soundName and __TS__StringIncludes(data.soundName, "kitty_flex") + if not isKittyFlexSound then + return + end + print(((("[SoundSystem] kitty_flex от игрока " .. tostring(data.playerId)) .. ", запускаем catakeet + screen shake на ") .. tostring(config.effectDuration)) .. " сек") + if self.kittyFlexStopTimer then + Timers:RemoveTimer(self.kittyFlexStopTimer) + self.kittyFlexStopTimer = nil + end + stopKittyFlexTimers(nil) + cleanupKittyFlexCatakeets(nil) + local hero = PlayerResource:GetSelectedHeroEntity(data.playerId) + local function throwFromHero(____, count) + if not hero or not IsValidEntity(hero) or hero:IsNull() or not hero:IsAlive() then + return + end + throwCatakeetsFromHero(nil, hero, count) + end + throwFromHero(nil, config.initialThrowCount) + performScreenShake(nil) + self.kittyFlexShakeTimer = Timers:CreateTimer( + config.shakeInterval, + function() + performScreenShake(nil) + return config.shakeInterval + end + ) + self.kittyFlexThrowTimer = Timers:CreateTimer( + config.throwInterval, + function() + throwFromHero(nil, config.incrementThrowCount) + return config.throwInterval + end + ) + self.kittyFlexStopTimer = Timers:CreateTimer( + config.effectDuration, + function() + print("[SoundSystem] kitty_flex закончился, убираем catakeet, screen shake и броски") + stopKittyFlexTimers(nil) + cleanupKittyFlexCatakeets(nil) + self.kittyFlexStopTimer = nil + return nil + end + ) + end + ____exports.SoundEventSystem:getInstance():onSound("kitty_flex", handleKittyFlexSound) + ____exports.SoundEventSystem:getInstance():onAnySound(handleKittyFlexSound) +end +function SoundSystemManager.prototype.startRainForAllPlayers(self) + local players = self:getAllPlayers() + for ____, ____value in ipairs(players) do + local player = ____value.player + if player ~= nil and player ~= nil then + self:setWeatherPlayer(player, "dityRain") + end + end +end +function SoundSystemManager.prototype.stopRainForAllPlayers(self) + local players = self:getAllPlayers() + for ____, ____value in ipairs(players) do + local player = ____value.player + if player ~= nil and player ~= nil then + self:setWeatherPlayer(player, "none") + end + end +end +function SoundSystemManager.prototype.getAllPlayers(self) + local players = {} + do + local playerID = 0 + while playerID < DOTA_MAX_PLAYERS do + do + if not PlayerResource:IsValidPlayerID(playerID) then + goto __continue161 + end + local player = PlayerResource:GetPlayer(playerID) + if player == nil or player == nil then + goto __continue161 + end + local hero = PlayerResource:GetSelectedHeroEntity(playerID) + players[#players + 1] = {player = player, hero = hero or nil} + end + ::__continue161:: + playerID = playerID + 1 + end + end + return players +end +function SoundSystemManager.prototype.setWeatherPlayer(self, player, ____type) + if not player.weatherEffect then + player.weatherEffect = {} + end + local ____player_weatherEffect_7 = player.weatherEffect + local sound = ____player_weatherEffect_7.sound + local effect = ____player_weatherEffect_7.effect + if sound and sound.timer then + Timers:RemoveTimer(sound.timer) + end + if sound then + StopGlobalSound(sound.name) + end + if effect then + ParticleManager:DestroyParticle(effect, false) + end + if ____type == "none" then + player.weatherEffect.effect = nil + player.weatherEffect.sound = nil + return + end + local weatherEffects = {none = "", desertStorm = "particles/rain_fx/econ_weather_sirocco.vpcf", dityRain = "particles/rain_fx/econ_rain.vpcf", snowstorm = "particles/winter_fx/weather_plateau_snow.vpcf"} + local weatherSounds = {none = nil, dityRain = {duration = 175, name = "rain"}, desertStorm = nil, snowstorm = {duration = 140, name = "snowstorm"}} + local weatherSound = weatherSounds[____type] + if weatherSound then + local timer = Timers:CreateTimer({ + endTime = 0.1, + useGameTime = false, + callback = function() + EmitGlobalSound(weatherSound.name) + return weatherSound.duration + end + }) + player.weatherEffect.sound = {name = weatherSound.name, timer = timer} + end + local effectPath = weatherEffects[____type] + if effectPath ~= nil and effectPath ~= nil and effectPath ~= "" then + local hero = PlayerResource:GetSelectedHeroEntity(player:GetPlayerID()) + if hero ~= nil and hero ~= nil then + local particle = ParticleManager:CreateParticleForPlayer(effectPath, PATTACH_EYES_FOLLOW, hero, player) + player.weatherEffect.effect = particle + end + end +end +--- Частицы погоды (чат-колесо) +function ____exports.precacheWeatherParticles(self, context) + PrecacheResource("particle", "particles/rain_fx/econ_weather_sirocco.vpcf", context) + PrecacheResource("particle", "particles/rain_fx/econ_weather_pestilence.vpcf", context) + PrecacheResource("particle", "particles/winter_fx/weather_plateau_snow.vpcf", context) + PrecacheResource("particle", "particles/rain_fx/econ_rain.vpcf", context) +end +--- Модель catakeet для эффекта kitty_flex +function ____exports.precacheKittyFlexResources(self, context) + PrecacheResource("model", KITTY_FLEX_CATAKEET_MODEL, context) +end +return ____exports diff --git a/scripts/vscripts/spawnmanager.lua b/scripts/vscripts/spawnmanager.lua new file mode 100644 index 0000000..e8e3510 --- /dev/null +++ b/scripts/vscripts/spawnmanager.lua @@ -0,0 +1,2901 @@ +local ____lualib = require("lualib_bundle") +local __TS__Class = ____lualib.__TS__Class +local Map = ____lualib.Map +local __TS__New = ____lualib.__TS__New +local __TS__ArraySome = ____lualib.__TS__ArraySome +local __TS__ArrayFilter = ____lualib.__TS__ArrayFilter +local __TS__StringStartsWith = ____lualib.__TS__StringStartsWith +local __TS__ArrayForEach = ____lualib.__TS__ArrayForEach +local __TS__StringIncludes = ____lualib.__TS__StringIncludes +local __TS__Iterator = ____lualib.__TS__Iterator +local __TS__ArrayIndexOf = ____lualib.__TS__ArrayIndexOf +local __TS__ArraySplice = ____lualib.__TS__ArraySplice +local __TS__StringReplace = ____lualib.__TS__StringReplace +local __TS__ParseInt = ____lualib.__TS__ParseInt +local __TS__Number = ____lualib.__TS__Number +local __TS__NumberIsNaN = ____lualib.__TS__NumberIsNaN +local __TS__ArrayReduce = ____lualib.__TS__ArrayReduce +local __TS__ArrayFrom = ____lualib.__TS__ArrayFrom +local ____exports = {} +____exports.SpawnManager = __TS__Class() +local SpawnManager = ____exports.SpawnManager +SpawnManager.name = "SpawnManager" +SpawnManager.____file_path = "scripts/vscripts/SpawnManager.lua" +function SpawnManager.prototype.____constructor(self) + self.spawnConfigs = __TS__New(Map) + self.spawnTimers = __TS__New(Map) + self.spawnedUnits = __TS__New(Map) + self.unitGroups = __TS__New(Map) + self.zoneRespawnScheduled = __TS__New(Map) + self.zoneTimerHandles = __TS__New(Map) + self.debugZonesVisible = false + self.spawnConfigs = __TS__New(Map) + self.spawnedUnits = __TS__New(Map) + self.spawnTimers = __TS__New(Map) + ListenToGameEvent( + "entity_killed", + function(event) + local killedIndex = event.entindex_killed + if killedIndex == nil or killedIndex == nil then + return + end + local killedUnit = EntIndexToHScript(killedIndex) + if not killedUnit or not IsValidEntity(killedUnit) then + return + end + self.spawnConfigs:forEach(function(____, config, zoneId) + local units = self.spawnedUnits:get(zoneId) or ({}) + local wasInZone = __TS__ArraySome( + units, + function(____, unit) return unit == killedUnit end + ) + if wasInZone then + local aliveUnits = __TS__ArrayFilter( + units, + function(____, unit) return unit ~= nil and unit ~= killedUnit and unit:IsAlive() end + ) + self.spawnedUnits:set(zoneId, aliveUnits) + self:removeUnitFromGroup(killedUnit) + self:QueueRespawn(zoneId) + end + end) + end, + nil + ) + ListenToGameEvent( + "entity_hurt", + function(event) + local hurtIndex = event.entindex_victim + local attackerIndex = event.entindex_attacker + if hurtIndex == nil or hurtIndex == nil then + return + end + if attackerIndex == nil or attackerIndex == nil then + return + end + local hurtUnit = EntIndexToHScript(hurtIndex) + local attacker = EntIndexToHScript(attackerIndex) + if not hurtUnit or not attacker or not IsValidEntity(hurtUnit) or not IsValidEntity(attacker) then + return + end + self.spawnConfigs:forEach(function(____, config, zoneId) + local units = self.spawnedUnits:get(zoneId) or ({}) + local wasInZone = __TS__ArraySome( + units, + function(____, unit) return unit == hurtUnit end + ) + if wasInZone and hurtUnit:IsAlive() and attacker:IsAlive() then + if hurtUnit:GetTeamNumber() ~= attacker:GetTeamNumber() then + hurtUnit:SetContextNum( + "last_attacker", + attacker:entindex(), + 5 + ) + if config.moveInGroups then + local group = self:getGroupForUnit(hurtUnit) + if group then + for ____, mate in ipairs(group) do + if mate ~= hurtUnit and mate:IsAlive() and IsValidEntity(mate) and mate:GetTeamNumber() ~= attacker:GetTeamNumber() then + if mate:GetAttackCapability() ~= DOTA_UNIT_CAP_NO_ATTACK then + ExecuteOrderFromTable({ + UnitIndex = mate:entindex(), + OrderType = DOTA_UNIT_ORDER_ATTACK_TARGET, + TargetIndex = attacker:entindex(), + Queue = false + }) + end + end + end + end + end + end + end + end) + end, + nil + ) + self:Think() +end +function SpawnManager.getInstance(self) + if not ____exports.SpawnManager.instance then + ____exports.SpawnManager.instance = __TS__New(____exports.SpawnManager) + end + return ____exports.SpawnManager.instance +end +function SpawnManager.prototype.Initialize(self) + local spawnPoints = {} + local currentEntity = Entities:First() + while currentEntity ~= nil do + local entityName = currentEntity:GetName() + if __TS__StringStartsWith(entityName, "spawn_point_") then + if not spawnPoints[entityName] then + spawnPoints[entityName] = {} + end + local ____spawnPoints_entityName_0 = spawnPoints[entityName] + ____spawnPoints_entityName_0[#____spawnPoints_entityName_0 + 1] = currentEntity:GetAbsOrigin() + end + currentEntity = Entities:Next(currentEntity) + end + if spawnPoints.spawn_point_witch then + __TS__ArrayForEach( + spawnPoints.spawn_point_witch, + function(____, position, index) + local zoneId = "zone_witch_" .. tostring(index) + self:AddSpawnZone(zoneId, { + units = {{unitName = "npc_witch", count = 1}}, + position = position, + radius = 650, + interval = 300, + team = DOTA_TEAM_BADGUYS, + behavior = "aggressive" + }) + local config = self.spawnConfigs:get(zoneId) + self:SpawnInitialUnits(zoneId, config) + end + ) + end + if spawnPoints.spawn_point_pigs then + __TS__ArrayForEach( + spawnPoints.spawn_point_pigs, + function(____, position, index) + local zoneId = "zone_pigs_" .. tostring(index) + self:AddSpawnZone(zoneId, { + units = {{unitName = "npc_pig", count = 5}}, + position = position, + radius = 600, + width = 2400, + height = 1100, + interval = 5, + team = DOTA_TEAM_NEUTRALS, + behavior = "friendly", + shape = "square" + }) + local config = self.spawnConfigs:get(zoneId) + self:SpawnInitialUnits(zoneId, config) + end + ) + end + if spawnPoints.spawn_point_sheep then + __TS__ArrayForEach( + spawnPoints.spawn_point_sheep, + function(____, position, index) + local zoneId = "zone_sheep_" .. tostring(index) + self:AddSpawnZone(zoneId, { + units = {{unitName = "npc_sheep", count = 5, canUseAbilities = true}}, + position = position, + radius = 600, + interval = 5, + team = DOTA_TEAM_NEUTRALS, + behavior = "aggressive", + shape = "square" + }) + local config = self.spawnConfigs:get(zoneId) + self:SpawnInitialUnits(zoneId, config) + end + ) + end + if spawnPoints.spawn_point_wolf then + __TS__ArrayForEach( + spawnPoints.spawn_point_wolf, + function(____, position, index) + local zoneId = "zone_wolf_" .. tostring(index) + self:AddSpawnZone(zoneId, { + units = {{unitName = "npc_wolf", count = 6, groupKey = "wolf"}, {unitName = "npc_ent", count = 2}}, + position = position, + radius = 1500, + interval = 7, + team = DOTA_TEAM_NEUTRALS, + behavior = "aggressive", + moveInGroups = {groupSize = 3, stayRadius = 280} + }) + local config = self.spawnConfigs:get(zoneId) + self:SpawnInitialUnits(zoneId, config) + end + ) + end + if spawnPoints.spawn_point_chicken then + __TS__ArrayForEach( + spawnPoints.spawn_point_chicken, + function(____, position, index) + local zoneId = "zone_chicken_" .. tostring(index) + self:AddSpawnZone(zoneId, { + units = {{unitName = "npc_chicken", count = 8, canUseAbilities = true}}, + position = position, + radius = 1500, + interval = 8, + team = DOTA_TEAM_NEUTRALS, + behavior = "aggressive", + shape = "square", + width = 1000, + height = 2450 + }) + local config = self.spawnConfigs:get(zoneId) + self:SpawnInitialUnits(zoneId, config) + end + ) + end + if spawnPoints.spawn_point_skeletons then + __TS__ArrayForEach( + spawnPoints.spawn_point_skeletons, + function(____, position, index) + local zoneId = "zone_skeletons_" .. tostring(index) + self:AddSpawnZone(zoneId, { + units = {{unitName = "npc_skeleton_zombie_undead", count = 1, canUseAbilities = true}, {unitName = "npc_skeleton_zombie_half_undead", count = 1, canUseAbilities = true}, {unitName = "npc_dead_skeleton_undead", count = 1, canUseAbilities = true}, {unitName = "npc_dead_skeleton_archer_undead", count = 1, canUseAbilities = true}}, + position = position, + radius = 600, + width = 1000, + height = 3300, + interval = 5, + team = DOTA_TEAM_NEUTRALS, + behavior = "aggressive", + shape = "square" + }) + local config = self.spawnConfigs:get(zoneId) + self:SpawnInitialUnits(zoneId, config) + end + ) + end + if spawnPoints.spawn_point_skeletons2 then + __TS__ArrayForEach( + spawnPoints.spawn_point_skeletons2, + function(____, position, index) + local zoneId = "zone_skeletons2_" .. tostring(index) + self:AddSpawnZone(zoneId, { + units = {{unitName = "npc_skeleton_zombie_undead", count = 1, canUseAbilities = true}, {unitName = "npc_skeleton_zombie_half_undead", count = 1, canUseAbilities = true}, {unitName = "npc_dead_skeleton_undead", count = 1, canUseAbilities = true}, {unitName = "npc_dead_skeleton_archer_undead", count = 1, canUseAbilities = true}}, + position = position, + radius = 600, + width = 2000, + height = 2000, + interval = 5, + team = DOTA_TEAM_NEUTRALS, + behavior = "aggressive", + shape = "square" + }) + local config = self.spawnConfigs:get(zoneId) + self:SpawnInitialUnits(zoneId, config) + end + ) + end + if spawnPoints.spawn_point_fish then + __TS__ArrayForEach( + spawnPoints.spawn_point_fish, + function(____, position, index) + local zoneId = "zone_fish_" .. tostring(index) + self:AddSpawnZone(zoneId, { + units = {{unitName = "npc_fish_1", count = 4}, {unitName = "npc_fish_2", count = 4}, {unitName = "npc_bomb", count = 6}}, + position = position, + radius = 1500, + interval = 8, + team = DOTA_TEAM_NEUTRALS, + behavior = "friendly" + }) + local config = self.spawnConfigs:get(zoneId) + self:SpawnInitialUnits(zoneId, config) + end + ) + end + if spawnPoints.spawn_point_wisps then + __TS__ArrayForEach( + spawnPoints.spawn_point_wisps, + function(____, position, index) + local zoneId = "zone_wisps_" .. tostring(index) + self:AddSpawnZone(zoneId, { + units = {{unitName = "npc_wisps", count = 6}}, + position = position, + radius = 1200, + interval = 8, + team = DOTA_TEAM_NEUTRALS, + behavior = "friendly" + }) + local config = self.spawnConfigs:get(zoneId) + self:SpawnInitialUnits(zoneId, config) + end + ) + end + if spawnPoints.spawn_point_thief then + __TS__ArrayForEach( + spawnPoints.spawn_point_thief, + function(____, position, index) + local zoneId = "zone_thief_" .. tostring(index) + self:AddSpawnZone(zoneId, { + units = {{unitName = "npc_thief_leader", count = 1, canUseAbilities = true, groupKey = "thief"}, {unitName = "npc_thief_archer", count = 2, canUseAbilities = true, groupKey = "thief"}, { + unitName = "npc_thief_backer", + count = 2, + canUseAbilities = true, + groupKey = "thief", + ignoreAIWhileHasModifiers = {"modifier_spirit_breaker_charge_of_darkness"} + }}, + position = position, + radius = 600, + width = 1200, + height = 15500, + interval = 180, + team = DOTA_TEAM_NEUTRALS, + behavior = "aggressive", + shape = "square", + moveInGroups = {groupSize = 5, stayRadius = 450} + }) + local config = self.spawnConfigs:get(zoneId) + self:SpawnInitialUnits(zoneId, config) + end + ) + end + if spawnPoints.spawn_point_dragons then + __TS__ArrayForEach( + spawnPoints.spawn_point_dragons, + function(____, position, index) + local zoneId = "zone_dragons_" .. tostring(index) + self:AddSpawnZone(zoneId, { + units = { + {unitName = "npc_black_dragon", count = 2, canUseAbilities = true, kiteRetreatDistance = 250}, + {unitName = "npc_red_dragon", count = 2}, + {unitName = "npc_blue_dragon_small", count = 2, canUseAbilities = true}, + {unitName = "npc_blue_dragon", count = 2, canUseAbilities = true}, + {unitName = "npc_red_dragon_small", count = 2, kiteRetreatDistance = 550} + }, + position = position, + radius = 600, + width = 3400, + height = 3400, + interval = 15, + team = DOTA_TEAM_NEUTRALS, + behavior = "aggressive", + shape = "square" + }) + local config = self.spawnConfigs:get(zoneId) + self:SpawnInitialUnits(zoneId, config) + end + ) + end + if spawnPoints.spawn_point_small_frog then + __TS__ArrayForEach( + spawnPoints.spawn_point_small_frog, + function(____, position, index) + local zoneId = "zone_small_frog_" .. tostring(index) + self:AddSpawnZone(zoneId, { + units = {{unitName = "npc_frogman_magi", count = 3, canUseAbilities = true}, {unitName = "npc_frop_tadpole", count = 3, canUseAbilities = true}, {unitName = "npc_small_frog_froglet", count = 2, canUseAbilities = true}, {unitName = "npc_mini_frog", count = 2, canUseAbilities = true}}, + position = position, + radius = 600, + width = 3400, + height = 3400, + interval = 6, + team = DOTA_TEAM_NEUTRALS, + behavior = "aggressive", + shape = "square" + }) + local config = self.spawnConfigs:get(zoneId) + self:SpawnInitialUnits(zoneId, config) + end + ) + end + if spawnPoints.spawn_point_snakes_spiders then + __TS__ArrayForEach( + spawnPoints.spawn_point_snakes_spiders, + function(____, position, index) + local zoneId = "zone_snakes_spiders_" .. tostring(index) + self:AddSpawnZone(zoneId, { + units = {{unitName = "npc_venomancer_brute", count = 3, canUseAbilities = true, groupKey = "snakes"}, {unitName = "npc_ravenous_woodfang", count = 3, canUseAbilities = true, groupKey = "spiders"}, {unitName = "npc_lycosidae_stalker", count = 3, canUseAbilities = true, groupKey = "snakes"}}, + position = position, + radius = 3500, + interval = 8, + team = DOTA_TEAM_NEUTRALS, + behavior = "aggressive", + moveInGroups = {groupSize = 5, stayRadius = 250} + }) + local config = self.spawnConfigs:get(zoneId) + self:SpawnInitialUnits(zoneId, config) + end + ) + end +end +function SpawnManager.prototype.AddSpawnZone(self, id, config) + self.spawnConfigs:set(id, config) + self.spawnedUnits:set(id, {}) + if self.debugZonesVisible then + self:DrawSpawnZoneDebug(id, config) + end +end +function SpawnManager.prototype.CreateZoneFromAdmin(self, id, config) + self:AddSpawnZone(id, config) + self:SpawnInitialUnits(id, config) +end +function SpawnManager.prototype.ToggleDebugZones(self) + self.debugZonesVisible = not self.debugZonesVisible + if self.debugZonesVisible then + self.spawnConfigs:forEach(function(____, config, id) + self:DrawSpawnZoneDebug(id, config) + end) + else + DebugDrawClear() + end +end +function SpawnManager.prototype.Think(self) + Timers:CreateTimer( + 1, + function() + self.spawnConfigs:forEach(function(____, _, zoneId) + local units = self.spawnedUnits:get(zoneId) or ({}) + local validUnits = __TS__ArrayFilter( + units, + function(____, unit) return unit and IsValidEntity(unit) and unit:IsAlive() end + ) + self.spawnedUnits:set(zoneId, validUnits) + end) + self.unitGroups:forEach(function(____, arr, groupId) + local valid = __TS__ArrayFilter( + arr, + function(____, u) return u and IsValidEntity(u) and u:IsAlive() end + ) + if #valid == 0 then + self.unitGroups:delete(groupId) + else + self.unitGroups:set(groupId, valid) + end + end) + return 1 + end + ) +end +function SpawnManager.prototype.startZoneTimer(self, zoneId, delay, callback) + local handle = Timers:CreateTimer( + delay, + function() + if not self.spawnConfigs:has(zoneId) then + return nil + end + return callback(nil) + end + ) + local list = self.zoneTimerHandles:get(zoneId) + if not list then + list = {} + self.zoneTimerHandles:set(zoneId, list) + end + list[#list + 1] = handle +end +function SpawnManager.prototype.cancelZoneTimers(self, zoneId) + local list = self.zoneTimerHandles:get(zoneId) + if not list then + return + end + for ____, h in ipairs(list) do + if h ~= nil and h ~= nil then + Timers:RemoveTimer(h) + end + end + self.zoneTimerHandles:delete(zoneId) +end +function SpawnManager.prototype.DrawSpawnZoneDebug(self, id, config) + local function getZoneColor(____, zoneId) + if __TS__StringIncludes(zoneId, "witch") then + return Vector(255, 0, 255) + end + if __TS__StringIncludes(zoneId, "pigs") then + return Vector(255, 200, 150) + end + if __TS__StringIncludes(zoneId, "sheep") then + return Vector(255, 255, 255) + end + if __TS__StringIncludes(zoneId, "wolf") then + return Vector(100, 100, 255) + end + if __TS__StringIncludes(zoneId, "skeletons") then + return Vector(200, 200, 200) + end + return Vector(0, 255, 0) + end + local zoneColor = getZoneColor(nil, id) + DebugDrawCircle( + config.position, + Vector(255, 255, 0), + 255, + 50, + true, + -1 + ) + if config.shape == "square" then + local halfWidth = config.width ~= nil and config.width / 2 or config.radius + local halfHeight = config.height ~= nil and config.height / 2 or config.radius + local z = config.position.z + local center = config.position + local topLeft = Vector(center.x - halfWidth, center.y + halfHeight, z) + local topRight = Vector(center.x + halfWidth, center.y + halfHeight, z) + local bottomRight = Vector(center.x + halfWidth, center.y - halfHeight, z) + local bottomLeft = Vector(center.x - halfWidth, center.y - halfHeight, z) + DebugDrawLine( + topLeft, + topRight, + zoneColor.x, + zoneColor.y, + zoneColor.z, + true, + -1 + ) + DebugDrawLine( + topRight, + bottomRight, + zoneColor.x, + zoneColor.y, + zoneColor.z, + true, + -1 + ) + DebugDrawLine( + bottomRight, + bottomLeft, + zoneColor.x, + zoneColor.y, + zoneColor.z, + true, + -1 + ) + DebugDrawLine( + bottomLeft, + topLeft, + zoneColor.x, + zoneColor.y, + zoneColor.z, + true, + -1 + ) + else + DebugDrawCircle( + config.position, + zoneColor, + 100, + config.radius, + true, + -1 + ) + end +end +function SpawnManager.prototype.isWithinZoneBounds(self, config, pos) + local dx = pos.x - config.position.x + local dy = pos.y - config.position.y + if config.shape == "square" then + local halfWidth = config.width ~= nil and config.width / 2 or config.radius + local halfHeight = config.height ~= nil and config.height / 2 or config.radius + return math.abs(dx) <= halfWidth and math.abs(dy) <= halfHeight + end + local distanceSquared = dx * dx + dy * dy + return distanceSquared <= config.radius * config.radius +end +function SpawnManager.prototype.isWithinChaseRange(self, config, pos) + local dx = pos.x - config.position.x + local dy = pos.y - config.position.y + local margin = ____exports.SpawnManager.CHASE_LEASH_MARGIN + if config.shape == "square" then + local halfWidth = (config.width ~= nil and config.width / 2 or config.radius) + margin + local halfHeight = (config.height ~= nil and config.height / 2 or config.radius) + margin + return math.abs(dx) <= halfWidth and math.abs(dy) <= halfHeight + end + local distanceSquared = dx * dx + dy * dy + local maxRadius = config.radius + margin + return distanceSquared <= maxRadius * maxRadius +end +function SpawnManager.prototype.getDeepPositionInZone(self, config) + if config.shape == "square" then + local halfWidth = (config.width ~= nil and config.width / 2 or config.radius) * 0.5 + local halfHeight = (config.height ~= nil and config.height / 2 or config.radius) * 0.5 + local offsetX = RandomFloat(-halfWidth, halfWidth) + local offsetY = RandomFloat(-halfHeight, halfHeight) + return GetGroundPosition( + Vector(config.position.x + offsetX, config.position.y + offsetY, config.position.z), + nil + ) + end + local angle = RandomFloat(0, 2 * math.pi) + local distance = RandomFloat(0, config.radius * 0.5) + return GetGroundPosition( + Vector( + config.position.x + distance * math.cos(angle), + config.position.y + distance * math.sin(angle), + config.position.z + ), + nil + ) +end +function SpawnManager.prototype.clampToZoneBounds(self, config, pos) + local dx = pos.x - config.position.x + local dy = pos.y - config.position.y + if config.shape == "square" then + local halfWidth = config.width ~= nil and config.width / 2 or config.radius + local halfHeight = config.height ~= nil and config.height / 2 or config.radius + local clampedX = math.max( + -halfWidth, + math.min(halfWidth, dx) + ) + local clampedY = math.max( + -halfHeight, + math.min(halfHeight, dy) + ) + return GetGroundPosition( + Vector(config.position.x + clampedX, config.position.y + clampedY, pos.z), + nil + ) + end + local distanceSquared = dx * dx + dy * dy + if distanceSquared <= config.radius * config.radius then + return pos + end + local distance = math.sqrt(distanceSquared) or 1 + local scale = config.radius / distance + return GetGroundPosition( + Vector(config.position.x + dx * scale, config.position.y + dy * scale, pos.z), + nil + ) +end +function SpawnManager.prototype.getGroupForUnit(self, unit) + for ____, ____value in __TS__Iterator(self.unitGroups) do + local arr = ____value[2] + if __TS__ArraySome( + arr, + function(____, u) return u == unit end + ) then + return arr + end + end + return nil +end +function SpawnManager.prototype.removeUnitFromGroup(self, unit) + for ____, ____value in __TS__Iterator(self.unitGroups) do + local groupId = ____value[1] + local arr = ____value[2] + local idx = __TS__ArrayIndexOf(arr, unit) + if idx >= 0 then + __TS__ArraySplice(arr, idx, 1) + if #arr == 0 then + self.unitGroups:delete(groupId) + end + return + end + end +end +function SpawnManager.prototype.findOrCreateGroupForSpawn(self, zoneId, config, groupKey) + local groupSize = config.moveInGroups.groupSize + local prefix = ((zoneId .. "_") .. groupKey) .. "_grp_" + local maxIndex = -1 + for ____, ____value in __TS__Iterator(self.unitGroups) do + local gid = ____value[1] + local arr = ____value[2] + do + if not __TS__StringStartsWith(gid, prefix) then + goto __continue109 + end + local alive = __TS__ArrayFilter( + arr, + function(____, u) return u and IsValidEntity(u) and u:IsAlive() end + ) + if #alive < groupSize then + local center = self:getGroupCenter(alive) + return {groupId = gid, spawnNear = center} + end + local num = __TS__ParseInt( + __TS__StringReplace(gid, prefix, ""), + 10 + ) + if not __TS__NumberIsNaN(__TS__Number(num)) and num > maxIndex then + maxIndex = num + end + end + ::__continue109:: + end + local newId = prefix .. tostring(maxIndex + 1) + self.unitGroups:set(newId, {}) + return {groupId = newId} +end +function SpawnManager.prototype.getGroupCenter(self, units) + local alive = __TS__ArrayFilter( + units, + function(____, u) return u and IsValidEntity(u) and u:IsAlive() end + ) + if #alive == 0 then + return Vector(0, 0, 0) + end + local x = 0 + local y = 0 + local z = 0 + for ____, u in ipairs(alive) do + local p = u:GetAbsOrigin() + x = x + p.x + y = y + p.y + z = z + p.z + end + local n = #alive + return Vector(x / n, y / n, z / n) +end +function SpawnManager.prototype.getGroupLeader(self, units) + local alive = __TS__ArrayFilter( + units, + function(____, u) return u and IsValidEntity(u) and u:IsAlive() end + ) + return alive[1] +end +function SpawnManager.prototype.isSpawnAllowed(self) + return GameRules:State_Get() == DOTA_GAMERULES_STATE_GAME_IN_PROGRESS +end +function SpawnManager.prototype.RemoveZoneAtPosition(self, pos) + for ____, ____value in __TS__Iterator(self.spawnConfigs) do + local id = ____value[1] + local config = ____value[2] + if self:isWithinZoneBounds(config, pos) then + self:RemoveSpawnZone(id) + return id + end + end + return nil +end +function SpawnManager.prototype.SpawnInitialUnits(self, zoneId, config) + if not self:isSpawnAllowed() then + self:startZoneTimer( + zoneId, + 0.5, + function() + self:SpawnInitialUnits(zoneId, config) + return nil + end + ) + return + end + __TS__ArrayForEach( + config.units, + function(____, unitInfo) + do + local i = 0 + while i < unitInfo.count do + self:startZoneTimer( + zoneId, + i * 0.1, + function() + self:SpawnUnit(zoneId, config, unitInfo) + return nil + end + ) + i = i + 1 + end + end + end + ) +end +function SpawnManager.prototype.SpawnUnit(self, id, config, unitInfo) + if not self.spawnConfigs:has(id) then + return + end + local unitName = unitInfo.unitName + local units = self.spawnedUnits:get(id) + local aliveUnits = __TS__ArrayFilter( + units, + function(____, unit) return unit ~= nil and unit:IsAlive() end + ) + local currentTypeCount = #__TS__ArrayFilter( + aliveUnits, + function(____, unit) + do + local function ____catch(____error) + return true, false + end + local ____try, ____hasReturned, ____returnValue = pcall(function() + return true, unit and IsValidEntity(unit) and self:getSpawnUnitConfigName(unit) == unitName + end) + if not ____try then + ____hasReturned, ____returnValue = ____catch(____hasReturned) + end + if ____hasReturned then + return ____returnValue + end + end + end + ) + if currentTypeCount >= unitInfo.count then + return + end + local totalMaxUnits = __TS__ArrayReduce( + config.units, + function(____, sum, unit) return sum + unit.count end, + 0 + ) + if #aliveUnits >= totalMaxUnits then + return + end + local groupInfo + if config.moveInGroups and unitInfo.groupKey then + groupInfo = self:findOrCreateGroupForSpawn(id, config, unitInfo.groupKey) + end + local function isWithinRadius(____, pos) + return self:isWithinZoneBounds(config, pos) + end + local spawnPos + local attempts = 0 + local maxAttempts = 20 + while not spawnPos and attempts < maxAttempts do + attempts = attempts + 1 + local testPos + if config.moveInGroups and (groupInfo and groupInfo.spawnNear) and attempts <= 5 then + local r = config.moveInGroups.stayRadius or 250 + testPos = GetGroundPosition( + Vector( + groupInfo.spawnNear.x + RandomFloat(-r, r), + groupInfo.spawnNear.y + RandomFloat(-r, r), + groupInfo.spawnNear.z + ), + nil + ) + elseif config.shape == "square" then + local halfWidth = config.width ~= nil and config.width / 2 or config.radius + local halfHeight = config.height ~= nil and config.height / 2 or config.radius + local offsetX = RandomFloat(-halfWidth, halfWidth) + local offsetY = RandomFloat(-halfHeight, halfHeight) + testPos = GetGroundPosition( + Vector(config.position.x + offsetX, config.position.y + offsetY, config.position.z), + nil + ) + else + local angle = RandomFloat(0, 2 * math.pi) + local distance = RandomFloat(0, config.radius) + testPos = GetGroundPosition( + Vector( + config.position.x + distance * math.cos(angle), + config.position.y + distance * math.sin(angle), + config.position.z + ), + nil + ) + end + if isWithinRadius(nil, testPos) then + spawnPos = testPos + break + end + end + if not spawnPos then + spawnPos = config.position + end + local unit = CreateUnitByName( + unitName, + spawnPos, + true, + nil, + nil, + config.team + ) + if unit and IsValidEntity(unit) then + unit:SetEntityName((unitInfo.unitName .. "_") .. tostring(unit:entindex())) + unit.__spawnManagerUnitName = unitInfo.unitName + if config.moveInGroups and groupInfo then + local ____temp_3 = self.unitGroups:get(groupInfo.groupId) + ____temp_3[#____temp_3 + 1] = unit + end + if unit:IsAlive() then + repeat + local ____switch153 = config.behavior + local friendlySpawnPosition, checkPosition, aggressiveSpawnPosition, canUseAbilities, ignoreAiModifiers, aggressiveLastAbilityUseTime, aggressiveIsApproachingForAbility, aggressiveIsCastingAbility, aggressiveCastingEndTime, aggressiveLastPos, aggressiveLastPosCheckTime, aggressiveStuckCounter, getKiteRetreatDistance, hasReadyAbilities, tryUseAbilitiesAggressive, checkAggressivePosition + local ____cond153 = ____switch153 == "friendly" + if ____cond153 then + unit:SetAttackCapability(DOTA_UNIT_CAP_NO_ATTACK) + friendlySpawnPosition = unit:GetAbsOrigin() + checkPosition = function() + if not unit or not unit:IsAlive() then + return nil + end + local currentPos = unit:GetAbsOrigin() + if not self:isWithinZoneBounds(config, currentPos) then + local zoneCenter = config.position + ExecuteOrderFromTable({ + UnitIndex = unit:entindex(), + OrderType = DOTA_UNIT_ORDER_MOVE_TO_POSITION, + Position = zoneCenter, + Queue = false + }) + return 1 + end + return 1 + end + self:startZoneTimer(id, 1, checkPosition) + break + end + ____cond153 = ____cond153 or ____switch153 == "aggressive" + if ____cond153 then + aggressiveSpawnPosition = unit:GetAbsOrigin() + local ____unitInfo_canUseAbilities_4 = unitInfo.canUseAbilities + if ____unitInfo_canUseAbilities_4 == nil then + ____unitInfo_canUseAbilities_4 = false + end + canUseAbilities = ____unitInfo_canUseAbilities_4 + ignoreAiModifiers = unitInfo.ignoreAIWhileHasModifiers or ({}) + aggressiveLastAbilityUseTime = 0 + aggressiveIsApproachingForAbility = false + aggressiveIsCastingAbility = false + aggressiveCastingEndTime = 0 + aggressiveLastPos = unit:GetAbsOrigin() + aggressiveLastPosCheckTime = GameRules:GetGameTime() + aggressiveStuckCounter = 0 + getKiteRetreatDistance = function() + return unitInfo.kiteRetreatDistance + end + hasReadyAbilities = function(____, target) + if not canUseAbilities then + return false + end + if not unit or not unit:IsAlive() or unit:IsStunned() or unit:IsHexed() then + return false + end + local now = GameRules:GetGameTime() + if now - aggressiveLastAbilityUseTime < 2 then + return false + end + do + local i = 0 + while i < unit:GetAbilityCount() do + do + local ability = unit:GetAbilityByIndex(i) + if not ability or ability:IsNull() then + goto __continue162 + end + if ability:IsPassive() or ability:IsHidden() then + goto __continue162 + end + if not ability:IsCooldownReady() then + goto __continue162 + end + if ability:GetManaCost(ability:GetLevel()) > unit:GetMana() then + goto __continue162 + end + return true + end + ::__continue162:: + i = i + 1 + end + end + return false + end + tryUseAbilitiesAggressive = function() + if #ignoreAiModifiers > 0 then + for ____, mod in ipairs(ignoreAiModifiers) do + if unit:HasModifier(mod) then + return false + end + end + end + if not canUseAbilities then + return false + end + if not unit or not unit:IsAlive() or unit:IsStunned() or unit:IsHexed() then + return false + end + local now = GameRules:GetGameTime() + if now - aggressiveLastAbilityUseTime < 2 then + return false + end + local enemiesRaw = FindUnitsInRadius( + unit:GetTeamNumber(), + unit:GetAbsOrigin(), + nil, + 1500, + DOTA_UNIT_TARGET_TEAM_ENEMY, + bit.bor(DOTA_UNIT_TARGET_HERO, DOTA_UNIT_TARGET_BASIC), + DOTA_UNIT_TARGET_FLAG_NONE, + FIND_CLOSEST, + false + ) + local enemies = __TS__ArrayFilter( + enemiesRaw, + function(____, e) return self:isWithinChaseRange( + config, + e:GetAbsOrigin() + ) end + ) + if #enemies == 0 then + return false + end + local target = enemies[1] + do + local i = 0 + while i < unit:GetAbilityCount() do + do + local ability = unit:GetAbilityByIndex(i) + if not ability or ability:IsNull() then + goto __continue177 + end + local abilityName = ability:GetAbilityName() + local suppressWarningParticles = abilityName == "spider_hunger" + if ability:IsPassive() or ability:IsHidden() then + goto __continue177 + end + if not ability:IsCooldownReady() then + goto __continue177 + end + local manaCost = ability:GetManaCost(ability:GetLevel()) + local currentMana = unit:GetMana() + if manaCost > currentMana then + goto __continue177 + end + local behavior = 0 + do + local function ____catch(e) + local abilityName = ability:GetAbilityName() + if abilityName == "sheep_coil" then + behavior = 16 + elseif abilityName == "black_dragon_fireball" then + behavior = 16 + elseif abilityName == "frogmen_acid_jump" then + behavior = 16 + else + local behaviorRaw = ability:GetBehavior() + if type(behaviorRaw) == "number" and not __TS__NumberIsNaN(__TS__Number(behaviorRaw)) then + behavior = behaviorRaw + end + end + end + local ____try, ____hasReturned = pcall(function() + local kv = ability:GetAbilityKeyValues() + if kv and kv.AbilityBehavior then + local behaviorStr = kv.AbilityBehavior + if __TS__StringIncludes(behaviorStr, "POINT") then + behavior = bit.bor(behavior, 16) + end + if __TS__StringIncludes(behaviorStr, "UNIT_TARGET") then + behavior = bit.bor(behavior, 32) + end + if __TS__StringIncludes(behaviorStr, "NO_TARGET") then + behavior = bit.bor(behavior, 4) + end + end + end) + if not ____try then + ____catch(____hasReturned) + end + end + local pointFlag = 16 + local unitTargetFlag = 32 + local noTargetFlag = 4 + local unitPos = unit:GetAbsOrigin() + local castRange = ability:GetCastRange(unitPos, target) + local targetPos = target:GetAbsOrigin() + local dx = unitPos.x - targetPos.x + local dy = unitPos.y - targetPos.y + local distance = math.sqrt(dx * dx + dy * dy) + local ____table_GetSpecialValueFor_5 + if ability.GetSpecialValueFor then + ____table_GetSpecialValueFor_5 = ability:GetSpecialValueFor("precast_warning_time") or 1 + else + ____table_GetSpecialValueFor_5 = 1 + end + local warningTime = ____table_GetSpecialValueFor_5 + local isThiefUnit = (function() + if not unit or unit:IsNull() then + return false + end + local name = unit:GetUnitName() + return name == "npc_thief_archer" or name == "npc_thief_leader" + end)(nil) + local function createPreCastParticle(____, particlePosition, radius, warningDuration) + if isThiefUnit then + return + end + local duration = warningDuration ~= nil and warningDuration or 1 + local groundPos = GetGroundPosition(particlePosition, nil) + local aoeRadius = radius ~= nil and radius or 0 + if aoeRadius > 0 then + local particle = ParticleManager:CreateParticle("particles/econ/events/darkmoon_2017/darkmoon_generic_aoe.vpcf", PATTACH_CUSTOMORIGIN, nil) + if particle ~= nil then + ParticleManager:SetParticleControl(particle, 0, groundPos) + ParticleManager:SetParticleControl( + particle, + 1, + Vector(aoeRadius, 0, 0) + ) + ParticleManager:SetParticleControl( + particle, + 2, + Vector(6, 0, 1) + ) + ParticleManager:SetParticleControl( + particle, + 3, + Vector(200, 0, 0) + ) + ParticleManager:SetParticleControl(particle, 4, groundPos) + self:startZoneTimer( + id, + duration, + function() + if particle ~= nil then + ParticleManager:DestroyParticle(particle, false) + ParticleManager:ReleaseParticleIndex(particle) + end + return nil + end + ) + end + else + local casterPos = GetGroundPosition( + unit:GetAbsOrigin(), + nil + ) + local targetPos = groundPos + local particle = ParticleManager:CreateParticle("particles/econ/events/darkmoon_2017/darkmoon_calldown_marker_arrows.vpcf", PATTACH_CUSTOMORIGIN, nil) + if particle ~= nil then + ParticleManager:SetParticleControl(particle, 0, casterPos) + ParticleManager:SetParticleControl(particle, 1, targetPos) + ParticleManager:SetParticleControl( + particle, + 2, + Vector(duration, 0, 0) + ) + self:startZoneTimer( + id, + duration, + function() + if particle ~= nil then + ParticleManager:DestroyParticle(particle, false) + ParticleManager:ReleaseParticleIndex(particle) + end + return nil + end + ) + end + end + end + local function createDirectionArrow(____, caster, targetPosition, warningDuration) + if isThiefUnit then + return + end + local duration = warningDuration ~= nil and warningDuration or 1 + local casterPos = GetGroundPosition( + caster:GetAbsOrigin(), + nil + ) + local groundTarget = GetGroundPosition(targetPosition, nil) + local particle = ParticleManager:CreateParticle("particles/econ/events/darkmoon_2017/darkmoon_calldown_marker_arrows.vpcf", PATTACH_CUSTOMORIGIN, nil) + if particle ~= nil then + ParticleManager:SetParticleControl(particle, 0, casterPos) + ParticleManager:SetParticleControl(particle, 1, groundTarget) + ParticleManager:SetParticleControl( + particle, + 2, + Vector(duration, 0, 0) + ) + self:startZoneTimer( + id, + duration, + function() + if particle ~= nil then + ParticleManager:DestroyParticle(particle, false) + ParticleManager:ReleaseParticleIndex(particle) + end + return nil + end + ) + end + end + if bit.band(behavior, pointFlag) ~= 0 then + if distance <= castRange and target:IsAlive() then + if abilityName ~= "frog_magi_wave" and not suppressWarningParticles then + local ____table_GetAOERadius_6 + if ability.GetAOERadius then + ____table_GetAOERadius_6 = ability:GetAOERadius() + else + ____table_GetAOERadius_6 = 0 + end + local aoeRadius = ____table_GetAOERadius_6 + local ____temp_7 + if aoeRadius > 0 then + ____temp_7 = aoeRadius + else + ____temp_7 = 200 + end + createPreCastParticle(nil, targetPos, ____temp_7, warningTime) + end + local direction = Vector(targetPos.x - unitPos.x, targetPos.y - unitPos.y, 0) + local length = math.sqrt(direction.x * direction.x + direction.y * direction.y) + if length > 0 then + local normalizedDir = Vector(direction.x / length, direction.y / length, 0) + unit:SetForwardVector(normalizedDir) + end + aggressiveIsCastingAbility = true + aggressiveCastingEndTime = now + warningTime + (ability:GetCastPoint() or 0) + self:startZoneTimer( + id, + warningTime, + function() + if not unit or not unit:IsAlive() or not target or not target:IsAlive() then + return nil + end + ExecuteOrderFromTable({ + UnitIndex = unit:entindex(), + OrderType = DOTA_UNIT_ORDER_CAST_POSITION, + Position = targetPos, + AbilityIndex = ability:GetEntityIndex(), + Queue = false + }) + local castPoint = ability:GetCastPoint() or 0 + self:startZoneTimer( + id, + castPoint, + function() + aggressiveIsCastingAbility = false + return nil + end + ) + return nil + end + ) + aggressiveLastAbilityUseTime = now + aggressiveIsApproachingForAbility = false + return true + end + elseif bit.band(behavior, unitTargetFlag) ~= 0 then + if distance <= castRange and target:IsAlive() then + if not suppressWarningParticles then + local overheadParticle = ParticleManager:CreateParticle("particles/econ/items/faceless_void/faceless_void_arcana/faceless_void_arcana_deny_v2_symbol_question.vpcf", PATTACH_OVERHEAD_FOLLOW, target) + if overheadParticle ~= nil then + self:startZoneTimer( + id, + warningTime + 0.5, + function() + if overheadParticle ~= nil then + ParticleManager:DestroyParticle(overheadParticle, false) + ParticleManager:ReleaseParticleIndex(overheadParticle) + end + return nil + end + ) + end + end + local direction = Vector(targetPos.x - unitPos.x, targetPos.y - unitPos.y, 0) + local length = math.sqrt(direction.x * direction.x + direction.y * direction.y) + if length > 0 then + local normalizedDir = Vector(direction.x / length, direction.y / length, 0) + unit:SetForwardVector(normalizedDir) + end + aggressiveIsCastingAbility = true + aggressiveCastingEndTime = now + warningTime + (ability:GetCastPoint() or 0) + self:startZoneTimer( + id, + warningTime, + function() + if not unit or not unit:IsAlive() or not target or not target:IsAlive() then + return nil + end + ExecuteOrderFromTable({ + UnitIndex = unit:entindex(), + OrderType = DOTA_UNIT_ORDER_CAST_TARGET, + TargetIndex = target:entindex(), + AbilityIndex = ability:GetEntityIndex(), + Queue = false + }) + local castPoint = ability:GetCastPoint() or 0 + self:startZoneTimer( + id, + castPoint, + function() + aggressiveIsCastingAbility = false + return nil + end + ) + return nil + end + ) + aggressiveLastAbilityUseTime = now + aggressiveIsApproachingForAbility = false + return true + end + elseif bit.band(behavior, noTargetFlag) ~= 0 then + local lockedTargetPos = target:GetAbsOrigin() + local ____table_GetAOERadius_8 + if ability.GetAOERadius then + ____table_GetAOERadius_8 = ability:GetAOERadius() + else + ____table_GetAOERadius_8 = 0 + end + local aoeRadius = ____table_GetAOERadius_8 + if aoeRadius <= 0 and ability.GetSpecialValueFor then + local specialRadius = ability:GetSpecialValueFor("radius") + if specialRadius and specialRadius > 0 then + aoeRadius = specialRadius + end + end + if not suppressWarningParticles then + local ____temp_9 + if aoeRadius > 0 then + ____temp_9 = aoeRadius + else + ____temp_9 = 200 + end + local followRadius = ____temp_9 + local followParticle = ParticleManager:CreateParticle("particles/econ/events/darkmoon_2017/darkmoon_generic_aoe.vpcf", PATTACH_CUSTOMORIGIN, nil) + if followParticle ~= nil then + ParticleManager:SetParticleControlEnt( + followParticle, + 0, + unit, + PATTACH_ABSORIGIN_FOLLOW, + nil, + unit:GetAbsOrigin(), + true + ) + ParticleManager:SetParticleControl( + followParticle, + 1, + Vector(followRadius, 0, 0) + ) + ParticleManager:SetParticleControl( + followParticle, + 2, + Vector(6, 0, 1) + ) + ParticleManager:SetParticleControl( + followParticle, + 3, + Vector(200, 0, 0) + ) + ParticleManager:SetParticleControl( + followParticle, + 4, + unit:GetAbsOrigin() + ) + self:startZoneTimer( + id, + warningTime, + function() + if followParticle ~= nil then + ParticleManager:DestroyParticle(followParticle, false) + ParticleManager:ReleaseParticleIndex(followParticle) + end + return nil + end + ) + end + end + do + local direction = Vector(lockedTargetPos.x - unitPos.x, lockedTargetPos.y - unitPos.y, 0) + local length = math.sqrt(direction.x * direction.x + direction.y * direction.y) + if length > 0 then + local normalizedDir = Vector(direction.x / length, direction.y / length, 0) + unit:SetForwardVector(normalizedDir) + end + end + if not suppressWarningParticles then + createDirectionArrow(nil, unit, lockedTargetPos, warningTime) + end + self:startZoneTimer( + id, + warningTime, + function() + if not unit or not unit:IsAlive() then + return nil + end + local currentPos = unit:GetAbsOrigin() + local direction = Vector(lockedTargetPos.x - currentPos.x, lockedTargetPos.y - currentPos.y, 0) + local length = math.sqrt(direction.x * direction.x + direction.y * direction.y) + if length > 0 then + local normalizedDir = Vector(direction.x / length, direction.y / length, 0) + unit:SetForwardVector(normalizedDir) + end + aggressiveIsCastingAbility = true + aggressiveCastingEndTime = now + warningTime + (ability:GetCastPoint() or 0) + ExecuteOrderFromTable({ + UnitIndex = unit:entindex(), + OrderType = DOTA_UNIT_ORDER_CAST_NO_TARGET, + AbilityIndex = ability:GetEntityIndex(), + Queue = false + }) + local castPoint = ability:GetCastPoint() or 0 + self:startZoneTimer( + id, + castPoint, + function() + aggressiveIsCastingAbility = false + return nil + end + ) + return nil + end + ) + aggressiveLastAbilityUseTime = now + aggressiveIsApproachingForAbility = false + return true + end + end + ::__continue177:: + i = i + 1 + end + end + return false + end + checkAggressivePosition = function() + if not unit or not unit:IsAlive() then + return nil + end + local currentHealth = unit:GetHealth() + local maxHealth = unit:GetMaxHealth() + local currentPos = unit:GetAbsOrigin() + local now = GameRules:GetGameTime() + if #ignoreAiModifiers > 0 then + for ____, mod in ipairs(ignoreAiModifiers) do + if unit:HasModifier(mod) then + return 0.2 + end + end + end + if aggressiveIsCastingAbility then + if now < aggressiveCastingEndTime then + return 0.2 + else + aggressiveIsCastingAbility = false + end + end + if config.moveInGroups then + local group = self:getGroupForUnit(unit) + if group and #group > 0 then + local leader = self:getGroupLeader(group) + if leader and leader ~= unit and leader:IsAlive() then + local leaderPos = leader:GetAbsOrigin() + local followRadius = config.moveInGroups.stayRadius or 250 + local dxF = currentPos.x - leaderPos.x + local dyF = currentPos.y - leaderPos.y + local distToLeader = math.sqrt(dxF * dxF + dyF * dyF) + if distToLeader > followRadius * 0.7 and not unit:IsAttacking() and not unit:IsChanneling() then + ExecuteOrderFromTable({ + UnitIndex = unit:entindex(), + OrderType = DOTA_UNIT_ORDER_MOVE_TO_POSITION, + Position = leaderPos, + Queue = false + }) + return 0.2 + end + end + end + end + if aggressiveLastPos then + local dt = now - aggressiveLastPosCheckTime + if dt >= 1 then + local dxStuck = currentPos.x - aggressiveLastPos.x + local dyStuck = currentPos.y - aggressiveLastPos.y + local distMoved = math.sqrt(dxStuck * dxStuck + dyStuck * dyStuck) + if distMoved < 10 and not unit:IsChanneling() and not unit:IsAttacking() then + aggressiveStuckCounter = aggressiveStuckCounter + 1 + if aggressiveStuckCounter >= 2 then + local escapePos + if config.shape == "square" then + local halfWidth = config.width ~= nil and config.width / 2 or config.radius + local halfHeight = config.height ~= nil and config.height / 2 or config.radius + local offsetX = RandomFloat(-halfWidth, halfWidth) + local offsetY = RandomFloat(-halfHeight, halfHeight) + escapePos = GetGroundPosition( + Vector(config.position.x + offsetX, config.position.y + offsetY, config.position.z), + nil + ) + else + local angle = RandomFloat(0, 2 * math.pi) + local distance = RandomFloat(0, config.radius) + escapePos = GetGroundPosition( + Vector( + config.position.x + distance * math.cos(angle), + config.position.y + distance * math.sin(angle), + config.position.z + ), + nil + ) + end + ExecuteOrderFromTable({ + UnitIndex = unit:entindex(), + OrderType = DOTA_UNIT_ORDER_MOVE_TO_POSITION, + Position = escapePos, + Queue = false + }) + aggressiveStuckCounter = 0 + end + else + aggressiveStuckCounter = 0 + end + aggressiveLastPos = currentPos + aggressiveLastPosCheckTime = now + end + else + aggressiveLastPos = currentPos + aggressiveLastPosCheckTime = now + end + local isOutsideZone = not self:isWithinZoneBounds(config, currentPos) + local lastAttackerIndex = unit:GetContext("last_attacker") + local lastAttacker + if lastAttackerIndex and lastAttackerIndex > 0 then + lastAttacker = EntIndexToHScript(lastAttackerIndex) + if not lastAttacker or not IsValidEntity(lastAttacker) or not lastAttacker:IsAlive() then + lastAttacker = nil + elseif not self:isWithinChaseRange( + config, + lastAttacker:GetAbsOrigin() + ) then + lastAttacker = nil + end + end + if lastAttacker and lastAttacker:IsAlive() then + local unitPos = unit:GetAbsOrigin() + local attackerPos = lastAttacker:GetAbsOrigin() + local dx = unitPos.x - attackerPos.x + local dy = unitPos.y - attackerPos.y + local distanceToAttacker = math.sqrt(dx * dx + dy * dy) + local hasAbilities = hasReadyAbilities(nil, lastAttacker) + if hasAbilities then + local used = tryUseAbilitiesAggressive(nil) + if used then + return 0.5 + end + end + local retreatDistance = getKiteRetreatDistance(nil) + if not hasAbilities and retreatDistance == nil and distanceToAttacker < 140 then + local attackTarget = unit:GetAttackTarget() + local isAttacking = unit:IsAttacking() + local isInAttackRange = attackTarget == lastAttacker and isAttacking and distanceToAttacker <= unit:GetBaseAttackRange() * 1.2 + if isInAttackRange then + return 0.5 + end + end + if hasAbilities then + local maxCastRange = 0 + local hasReadyAbility = false + do + local i = 0 + while i < unit:GetAbilityCount() do + do + local ability = unit:GetAbilityByIndex(i) + if not ability or ability:IsNull() then + goto __continue271 + end + if ability:IsPassive() or ability:IsHidden() then + goto __continue271 + end + if not ability:IsCooldownReady() then + goto __continue271 + end + local manaCost = ability:GetManaCost(ability:GetLevel()) + if manaCost > unit:GetMana() then + goto __continue271 + end + hasReadyAbility = true + local castRange = ability:GetCastRange(unitPos, lastAttacker) + if castRange > maxCastRange then + maxCastRange = castRange + end + end + ::__continue271:: + i = i + 1 + end + end + if hasReadyAbility and distanceToAttacker > maxCastRange and maxCastRange > 0 then + aggressiveIsApproachingForAbility = true + ExecuteOrderFromTable({ + UnitIndex = unit:entindex(), + OrderType = DOTA_UNIT_ORDER_MOVE_TO_POSITION, + Position = attackerPos, + Queue = false + }) + self:startZoneTimer( + id, + 3, + function() + aggressiveIsApproachingForAbility = false + return nil + end + ) + return 0.5 + elseif hasReadyAbility and distanceToAttacker <= maxCastRange then + aggressiveIsApproachingForAbility = false + if retreatDistance ~= nil and distanceToAttacker < retreatDistance then + local dir = Vector(unitPos.x - attackerPos.x, unitPos.y - attackerPos.y, 0) + local len = math.sqrt(dir.x * dir.x + dir.y * dir.y) or 1 + local normalizedDir = Vector(dir.x / len, dir.y / len, 0) + local retreatPos = Vector(attackerPos.x + normalizedDir.x * retreatDistance, attackerPos.y + normalizedDir.y * retreatDistance, attackerPos.z) + local ____temp_10 + if self:isWithinZoneBounds(config, retreatPos) and GridNav.CanFindPath then + ____temp_10 = GridNav:CanFindPath(unitPos, retreatPos) + else + ____temp_10 = true + end + local canEscape = ____temp_10 + if canEscape then + print(((((((((unit:GetUnitName() .. " kites (") .. tostring(math.floor(attackerPos.x))) .. ",") .. tostring(math.floor(attackerPos.y))) .. ") -> (") .. tostring(math.floor(retreatPos.x))) .. ",") .. tostring(math.floor(retreatPos.y))) .. ") [abilities]") + ExecuteOrderFromTable({ + UnitIndex = unit:entindex(), + OrderType = DOTA_UNIT_ORDER_MOVE_TO_POSITION, + Position = retreatPos, + Queue = false + }) + else + print(unit:GetUnitName() .. " cannot escape while kiting (no path / out of zone), fights back attacker") + ExecuteOrderFromTable({ + UnitIndex = unit:entindex(), + OrderType = DOTA_UNIT_ORDER_ATTACK_TARGET, + TargetIndex = lastAttacker:entindex(), + Queue = false + }) + end + else + ExecuteOrderFromTable({ + UnitIndex = unit:entindex(), + OrderType = DOTA_UNIT_ORDER_ATTACK_TARGET, + TargetIndex = lastAttacker:entindex(), + Queue = false + }) + end + return 0.5 + else + aggressiveIsApproachingForAbility = false + if retreatDistance ~= nil and distanceToAttacker < retreatDistance then + local dir = Vector(unitPos.x - attackerPos.x, unitPos.y - attackerPos.y, 0) + local len = math.sqrt(dir.x * dir.x + dir.y * dir.y) or 1 + local normalizedDir = Vector(dir.x / len, dir.y / len, 0) + local retreatPos = Vector(attackerPos.x + normalizedDir.x * retreatDistance, attackerPos.y + normalizedDir.y * retreatDistance, attackerPos.z) + local ____temp_11 + if self:isWithinZoneBounds(config, retreatPos) and GridNav.CanFindPath then + ____temp_11 = GridNav:CanFindPath(unitPos, retreatPos) + else + ____temp_11 = true + end + local canEscape = ____temp_11 + if canEscape then + print(((((((((unit:GetUnitName() .. " kites (") .. tostring(math.floor(attackerPos.x))) .. ",") .. tostring(math.floor(attackerPos.y))) .. ") -> (") .. tostring(math.floor(retreatPos.x))) .. ",") .. tostring(math.floor(retreatPos.y))) .. ") [no_abilities]") + ExecuteOrderFromTable({ + UnitIndex = unit:entindex(), + OrderType = DOTA_UNIT_ORDER_MOVE_TO_POSITION, + Position = retreatPos, + Queue = false + }) + else + print(unit:GetUnitName() .. " cannot escape while kiting without abilities, fights back attacker") + ExecuteOrderFromTable({ + UnitIndex = unit:entindex(), + OrderType = DOTA_UNIT_ORDER_ATTACK_TARGET, + TargetIndex = lastAttacker:entindex(), + Queue = false + }) + end + else + ExecuteOrderFromTable({ + UnitIndex = unit:entindex(), + OrderType = DOTA_UNIT_ORDER_ATTACK_TARGET, + TargetIndex = lastAttacker:entindex(), + Queue = false + }) + end + return 0.5 + end + end + end + if not aggressiveIsApproachingForAbility then + local detectionRadius = 900 + local nearbyEnemiesRaw = FindUnitsInRadius( + unit:GetTeamNumber(), + unit:GetAbsOrigin(), + nil, + detectionRadius, + DOTA_UNIT_TARGET_TEAM_ENEMY, + bit.bor(DOTA_UNIT_TARGET_HERO, DOTA_UNIT_TARGET_BASIC), + DOTA_UNIT_TARGET_FLAG_NONE, + FIND_CLOSEST, + false + ) + local nearbyEnemies = __TS__ArrayFilter( + nearbyEnemiesRaw, + function(____, e) return self:isWithinChaseRange( + config, + e:GetAbsOrigin() + ) end + ) + if #nearbyEnemies > 0 then + local closestEnemy = nearbyEnemies[1] + local unitPos = unit:GetAbsOrigin() + local enemyPos = closestEnemy:GetAbsOrigin() + local dx = unitPos.x - enemyPos.x + local dy = unitPos.y - enemyPos.y + local distanceToEnemy = math.sqrt(dx * dx + dy * dy) + if distanceToEnemy <= detectionRadius and closestEnemy:IsAlive() then + local retreatDistance = getKiteRetreatDistance(nil) + if retreatDistance ~= nil and distanceToEnemy < retreatDistance then + local dir = Vector(unitPos.x - enemyPos.x, unitPos.y - enemyPos.y, 0) + local len = math.sqrt(dir.x * dir.x + dir.y * dir.y) or 1 + local normalizedDir = Vector(dir.x / len, dir.y / len, 0) + local retreatPos = Vector(enemyPos.x + normalizedDir.x * retreatDistance, enemyPos.y + normalizedDir.y * retreatDistance, enemyPos.z) + local ____temp_12 + if self:isWithinZoneBounds(config, retreatPos) and GridNav.CanFindPath then + ____temp_12 = GridNav:CanFindPath(unitPos, retreatPos) + else + ____temp_12 = true + end + local canEscape = ____temp_12 + if canEscape then + print(((((((((unit:GetUnitName() .. " kites (") .. tostring(math.floor(enemyPos.x))) .. ",") .. tostring(math.floor(enemyPos.y))) .. ") -> (") .. tostring(math.floor(retreatPos.x))) .. ",") .. tostring(math.floor(retreatPos.y))) .. ") [search]") + ExecuteOrderFromTable({ + UnitIndex = unit:entindex(), + OrderType = DOTA_UNIT_ORDER_MOVE_TO_POSITION, + Position = retreatPos, + Queue = false + }) + else + print(unit:GetUnitName() .. " cannot escape while kiting in search phase, attacks closest enemy") + ExecuteOrderFromTable({ + UnitIndex = unit:entindex(), + OrderType = DOTA_UNIT_ORDER_ATTACK_TARGET, + TargetIndex = closestEnemy:entindex(), + Queue = false + }) + end + else + ExecuteOrderFromTable({ + UnitIndex = unit:entindex(), + OrderType = DOTA_UNIT_ORDER_ATTACK_TARGET, + TargetIndex = closestEnemy:entindex(), + Queue = false + }) + end + if distanceToEnemy <= 800 then + local usedAbility = tryUseAbilitiesAggressive(nil) + if usedAbility then + return 0.5 + end + end + end + end + end + local nearbyEnemiesCheckRaw = FindUnitsInRadius( + unit:GetTeamNumber(), + unit:GetAbsOrigin(), + nil, + 1000, + DOTA_UNIT_TARGET_TEAM_ENEMY, + bit.bor(DOTA_UNIT_TARGET_HERO, DOTA_UNIT_TARGET_BASIC), + DOTA_UNIT_TARGET_FLAG_NONE, + FIND_CLOSEST, + false + ) + local nearbyEnemiesCheck = __TS__ArrayFilter( + nearbyEnemiesCheckRaw, + function(____, e) return self:isWithinChaseRange( + config, + e:GetAbsOrigin() + ) end + ) + local hasReadyAbilitiesForAnyEnemy = false + if #nearbyEnemiesCheck > 0 and canUseAbilities then + for ____, enemy in ipairs(nearbyEnemiesCheck) do + if hasReadyAbilities(nil, enemy) then + hasReadyAbilitiesForAnyEnemy = true + break + end + end + end + if currentHealth >= maxHealth * 0.95 and not aggressiveIsApproachingForAbility and #nearbyEnemiesCheck == 0 and not hasReadyAbilitiesForAnyEnemy then + local currentPos2 = unit:GetAbsOrigin() + local returnTarget = config.moveInGroups and self:getGroupForUnit(unit) and self:getGroupCenter(self:getGroupForUnit(unit)) or aggressiveSpawnPosition + local threshold = config.moveInGroups and (config.moveInGroups.stayRadius or 250) or 200 + local dx = currentPos2.x - returnTarget.x + local dy = currentPos2.y - returnTarget.y + local distanceToSpawn = math.sqrt(dx * dx + dy * dy) + if distanceToSpawn > threshold then + ExecuteOrderFromTable({ + UnitIndex = unit:entindex(), + OrderType = DOTA_UNIT_ORDER_MOVE_TO_POSITION, + Position = returnTarget, + Queue = false + }) + end + end + if isOutsideZone and #nearbyEnemiesCheck == 0 and not hasReadyAbilitiesForAnyEnemy and not aggressiveIsApproachingForAbility then + local deepPos = self:getDeepPositionInZone(config) + ExecuteOrderFromTable({ + UnitIndex = unit:entindex(), + OrderType = DOTA_UNIT_ORDER_MOVE_TO_POSITION, + Position = deepPos, + Queue = false + }) + self:startZoneTimer( + id, + 1, + function() + if not unit or not unit:IsAlive() then + return nil + end + local checkPos = unit:GetAbsOrigin() + if not self:isWithinZoneBounds(config, checkPos) then + ExecuteOrderFromTable({ + UnitIndex = unit:entindex(), + OrderType = DOTA_UNIT_ORDER_MOVE_TO_POSITION, + Position = self:getDeepPositionInZone(config), + Queue = false + }) + end + return nil + end + ) + return 0.5 + end + return 0.5 + end + self:startZoneTimer(id, 1, checkAggressivePosition) + break + end + ____cond153 = ____cond153 or ____switch153 == "neutral" + if ____cond153 then + do + unit:SetAttackCapability(DOTA_UNIT_CAP_NO_ATTACK) + local spawnPosition = unit:GetAbsOrigin() + local ____unitInfo_canUseAbilities_13 = unitInfo.canUseAbilities + if ____unitInfo_canUseAbilities_13 == nil then + ____unitInfo_canUseAbilities_13 = false + end + local neutralCanUseAbilities = ____unitInfo_canUseAbilities_13 + local isAggressive = false + local neutralLastAbilityUseTime = 0 + local checkAggression + local function hasReadyAbilitiesNeutral(____, target) + if not neutralCanUseAbilities then + return false + end + if not unit or not unit:IsAlive() or unit:IsStunned() or unit:IsHexed() then + return false + end + local now = GameRules:GetGameTime() + if now - neutralLastAbilityUseTime < 2 then + return false + end + local unitPos = unit:GetAbsOrigin() + do + local i = 0 + while i < unit:GetAbilityCount() do + do + local ability = unit:GetAbilityByIndex(i) + if not ability or ability:IsNull() then + goto __continue315 + end + if ability:IsPassive() or ability:IsHidden() then + goto __continue315 + end + if not ability:IsCooldownReady() then + goto __continue315 + end + if ability:GetManaCost(ability:GetLevel()) > unit:GetMana() then + goto __continue315 + end + local behavior = 0 + do + local function ____catch(e) + local abilityNameForCheck = ability:GetAbilityName() + if abilityNameForCheck == "sheep_coil" then + behavior = 16 + elseif abilityNameForCheck == "black_dragon_fireball" then + behavior = 16 + elseif abilityNameForCheck == "frogmen_acid_jump" then + behavior = 16 + else + local behaviorRaw = ability:GetBehavior() + if type(behaviorRaw) == "number" and not __TS__NumberIsNaN(__TS__Number(behaviorRaw)) then + behavior = behaviorRaw + end + end + end + local ____try, ____hasReturned = pcall(function() + local kv = ability:GetAbilityKeyValues() + if kv and kv.AbilityBehavior then + local behaviorStr = kv.AbilityBehavior + if __TS__StringIncludes(behaviorStr, "POINT") then + behavior = bit.bor(behavior, 16) + end + if __TS__StringIncludes(behaviorStr, "UNIT_TARGET") then + behavior = bit.bor(behavior, 32) + end + if __TS__StringIncludes(behaviorStr, "NO_TARGET") then + behavior = bit.bor(behavior, 4) + end + end + end) + if not ____try then + ____catch(____hasReturned) + end + end + local pointFlag = 16 + local unitTargetFlag = 32 + local noTargetFlag = 4 + local castRange = ability:GetCastRange(unitPos, target) + local targetPos = target:GetAbsOrigin() + local dx = unitPos.x - targetPos.x + local dy = unitPos.y - targetPos.y + local distance = math.sqrt(dx * dx + dy * dy) + if bit.band(behavior, unitTargetFlag) ~= 0 or bit.band(behavior, pointFlag) ~= 0 then + if distance <= castRange and target:IsAlive() then + return true + end + elseif bit.band(behavior, noTargetFlag) ~= 0 then + return true + end + end + ::__continue315:: + i = i + 1 + end + end + return false + end + local function tryUseAbilitiesNeutral() + if not neutralCanUseAbilities then + return false + end + if not unit or not unit:IsAlive() or unit:IsStunned() or unit:IsHexed() then + return false + end + local now = GameRules:GetGameTime() + if now - neutralLastAbilityUseTime < 2 then + return false + end + local enemiesRaw = FindUnitsInRadius( + unit:GetTeamNumber(), + unit:GetAbsOrigin(), + nil, + 600, + DOTA_UNIT_TARGET_TEAM_ENEMY, + bit.bor(DOTA_UNIT_TARGET_HERO, DOTA_UNIT_TARGET_BASIC), + DOTA_UNIT_TARGET_FLAG_NONE, + FIND_CLOSEST, + false + ) + local enemies = __TS__ArrayFilter( + enemiesRaw, + function(____, e) return self:isWithinChaseRange( + config, + e:GetAbsOrigin() + ) end + ) + if #enemies == 0 then + return false + end + local target = enemies[1] + do + local i = 0 + while i < unit:GetAbilityCount() do + do + local ability = unit:GetAbilityByIndex(i) + if not ability or ability:IsNull() then + goto __continue340 + end + local abilityName = ability:GetAbilityName() + local suppressWarningParticles = abilityName == "spider_hunger" + if ability:IsPassive() or ability:IsHidden() then + goto __continue340 + end + if not ability:IsCooldownReady() then + goto __continue340 + end + if ability:GetManaCost(ability:GetLevel()) > unit:GetMana() then + goto __continue340 + end + local pointFlag = 16 + local unitTargetFlag = 32 + local noTargetFlag = 4 + local knownAbilityBehavior = {sheep_coil = pointFlag, black_dragon_fireball = pointFlag, frogmen_acid_jump = pointFlag} + local behavior = 0 + do + local function ____catch(e) + local abilityNameForCheck = ability:GetAbilityName() + local override = knownAbilityBehavior[abilityNameForCheck] + if override ~= nil then + behavior = override + else + local behaviorRaw = ability:GetBehavior() + if type(behaviorRaw) == "number" and not __TS__NumberIsNaN(__TS__Number(behaviorRaw)) then + behavior = behaviorRaw + end + end + end + local ____try, ____hasReturned = pcall(function() + local kv = ability:GetAbilityKeyValues() + if kv and kv.AbilityBehavior then + local behaviorStr = kv.AbilityBehavior + if __TS__StringIncludes(behaviorStr, "POINT") then + behavior = bit.bor(behavior, pointFlag) + end + if __TS__StringIncludes(behaviorStr, "UNIT_TARGET") then + behavior = bit.bor(behavior, unitTargetFlag) + end + if __TS__StringIncludes(behaviorStr, "NO_TARGET") then + behavior = bit.bor(behavior, noTargetFlag) + end + end + end) + if not ____try then + ____catch(____hasReturned) + end + end + local unitPos = unit:GetAbsOrigin() + local castRange = ability:GetCastRange(unitPos, target) + local ____table_GetSpecialValueFor_14 + if ability.GetSpecialValueFor then + ____table_GetSpecialValueFor_14 = ability:GetSpecialValueFor("precast_warning_time") or 1 + else + ____table_GetSpecialValueFor_14 = 1 + end + local warningTimeNeutral = ____table_GetSpecialValueFor_14 + local function createPreCastParticle(____, particlePosition, radius, warningDuration) + local duration = warningDuration ~= nil and warningDuration or warningTimeNeutral + local groundPos = GetGroundPosition(particlePosition, nil) + local aoeRadius = radius ~= nil and radius or 0 + if aoeRadius > 0 then + local particle = ParticleManager:CreateParticle("particles/econ/events/darkmoon_2017/darkmoon_generic_aoe.vpcf", PATTACH_CUSTOMORIGIN, nil) + if particle ~= nil then + ParticleManager:SetParticleControl(particle, 0, groundPos) + ParticleManager:SetParticleControl( + particle, + 1, + Vector(aoeRadius, 0, 0) + ) + ParticleManager:SetParticleControl( + particle, + 2, + Vector(6, 0, 1) + ) + ParticleManager:SetParticleControl( + particle, + 3, + Vector(200, 0, 0) + ) + ParticleManager:SetParticleControl(particle, 4, groundPos) + self:startZoneTimer( + id, + duration, + function() + if particle ~= nil then + ParticleManager:DestroyParticle(particle, false) + ParticleManager:ReleaseParticleIndex(particle) + end + return nil + end + ) + end + else + local casterPos = GetGroundPosition( + unit:GetAbsOrigin(), + nil + ) + local targetPos = groundPos + local particle = ParticleManager:CreateParticle("particles/econ/events/darkmoon_2017/darkmoon_calldown_marker_arrows.vpcf", PATTACH_CUSTOMORIGIN, nil) + if particle ~= nil then + ParticleManager:SetParticleControl(particle, 0, casterPos) + ParticleManager:SetParticleControl(particle, 1, targetPos) + ParticleManager:SetParticleControl( + particle, + 2, + Vector(duration, 0, 0) + ) + self:startZoneTimer( + id, + duration, + function() + if particle ~= nil then + ParticleManager:DestroyParticle(particle, false) + ParticleManager:ReleaseParticleIndex(particle) + end + return nil + end + ) + end + end + end + local function createDirectionArrowNeutral(____, caster, targetPosition, warningDuration) + local duration = warningDuration ~= nil and warningDuration or warningTimeNeutral + local casterPos = GetGroundPosition( + caster:GetAbsOrigin(), + nil + ) + local groundTarget = GetGroundPosition(targetPosition, nil) + local particle = ParticleManager:CreateParticle("particles/econ/events/darkmoon_2017/darkmoon_calldown_marker_arrows.vpcf", PATTACH_CUSTOMORIGIN, nil) + if particle ~= nil then + ParticleManager:SetParticleControl(particle, 0, casterPos) + ParticleManager:SetParticleControl(particle, 1, groundTarget) + ParticleManager:SetParticleControl( + particle, + 2, + Vector(duration, 0, 0) + ) + self:startZoneTimer( + id, + duration, + function() + if particle ~= nil then + ParticleManager:DestroyParticle(particle, false) + ParticleManager:ReleaseParticleIndex(particle) + end + return nil + end + ) + end + end + local pointFlagNeutral = 16 + local unitTargetFlagNeutral = 32 + local noTargetFlagNeutral = 4 + if bit.band(behavior, unitTargetFlagNeutral) ~= 0 then + local unitPos = unit:GetAbsOrigin() + local targetPos = target:GetAbsOrigin() + local dx = unitPos.x - targetPos.x + local dy = unitPos.y - targetPos.y + local distance = math.sqrt(dx * dx + dy * dy) + if distance <= castRange and target:IsAlive() then + if not suppressWarningParticles then + createDirectionArrowNeutral(nil, unit, targetPos, warningTimeNeutral) + end + if not suppressWarningParticles then + local overheadParticle = ParticleManager:CreateParticle("particles/econ/items/faceless_void/faceless_void_arcana/faceless_void_arcana_deny_v2_symbol_question.vpcf", PATTACH_OVERHEAD_FOLLOW, target) + if overheadParticle ~= nil then + self:startZoneTimer( + id, + warningTimeNeutral + 0.5, + function() + if overheadParticle ~= nil then + ParticleManager:DestroyParticle(overheadParticle, false) + ParticleManager:ReleaseParticleIndex(overheadParticle) + end + return nil + end + ) + end + end + local direction = Vector(targetPos.x - unitPos.x, targetPos.y - unitPos.y, 0) + local length = math.sqrt(direction.x * direction.x + direction.y * direction.y) + if length > 0 then + local normalizedDir = Vector(direction.x / length, direction.y / length, 0) + unit:SetForwardVector(normalizedDir) + end + self:startZoneTimer( + id, + warningTimeNeutral, + function() + if not unit or not unit:IsAlive() or not target or not target:IsAlive() then + return nil + end + ExecuteOrderFromTable({ + UnitIndex = unit:entindex(), + OrderType = DOTA_UNIT_ORDER_CAST_TARGET, + TargetIndex = target:entindex(), + AbilityIndex = ability:GetEntityIndex(), + Queue = false + }) + return nil + end + ) + neutralLastAbilityUseTime = now + return true + end + elseif bit.band(behavior, pointFlagNeutral) ~= 0 then + local unitPos = unit:GetAbsOrigin() + local targetPos = target:GetAbsOrigin() + local dx = unitPos.x - targetPos.x + local dy = unitPos.y - targetPos.y + local distance = math.sqrt(dx * dx + dy * dy) + if distance <= castRange and target:IsAlive() then + if abilityName ~= "frog_magi_wave" and not suppressWarningParticles then + local ____table_GetAOERadius_15 + if ability.GetAOERadius then + ____table_GetAOERadius_15 = ability:GetAOERadius() + else + ____table_GetAOERadius_15 = 0 + end + local aoeRadius = ____table_GetAOERadius_15 + local ____temp_16 + if aoeRadius > 0 then + ____temp_16 = aoeRadius + else + ____temp_16 = 200 + end + createPreCastParticle(nil, targetPos, ____temp_16, warningTimeNeutral) + end + local direction = Vector(targetPos.x - unitPos.x, targetPos.y - unitPos.y, 0) + local length = math.sqrt(direction.x * direction.x + direction.y * direction.y) + if length > 0 then + local normalizedDir = Vector(direction.x / length, direction.y / length, 0) + unit:SetForwardVector(normalizedDir) + end + self:startZoneTimer( + id, + warningTimeNeutral, + function() + if not unit or not unit:IsAlive() or not target or not target:IsAlive() then + return nil + end + ExecuteOrderFromTable({ + UnitIndex = unit:entindex(), + OrderType = DOTA_UNIT_ORDER_CAST_POSITION, + Position = targetPos, + AbilityIndex = ability:GetEntityIndex(), + Queue = false + }) + return nil + end + ) + neutralLastAbilityUseTime = now + return true + end + elseif bit.band(behavior, noTargetFlagNeutral) ~= 0 then + local unitPos = unit:GetAbsOrigin() + local targetPos = target:GetAbsOrigin() + local dx = unitPos.x - targetPos.x + local dy = unitPos.y - targetPos.y + local distance = math.sqrt(dx * dx + dy * dy) + local desiredDistance = 100 + if distance > desiredDistance then + ExecuteOrderFromTable({ + UnitIndex = unit:entindex(), + OrderType = DOTA_UNIT_ORDER_ATTACK_MOVE, + TargetIndex = target:entindex(), + Queue = false + }) + self:startZoneTimer( + id, + 0.3, + function() + if not unit or not unit:IsAlive() or not target or not target:IsAlive() then + return nil + end + local newUnitPos = unit:GetAbsOrigin() + local newTargetPos = target:GetAbsOrigin() + local ndx = newUnitPos.x - newTargetPos.x + local ndy = newUnitPos.y - newTargetPos.y + local newDistance = math.sqrt(ndx * ndx + ndy * ndy) + if newDistance <= desiredDistance + 25 then + ExecuteOrderFromTable({ + UnitIndex = unit:entindex(), + OrderType = DOTA_UNIT_ORDER_CAST_NO_TARGET, + AbilityIndex = ability:GetEntityIndex(), + Queue = false + }) + neutralLastAbilityUseTime = now + return nil + end + return 0.2 + end + ) + return true + end + local lockedTargetPos = target:GetAbsOrigin() + local ____table_GetAOERadius_17 + if ability.GetAOERadius then + ____table_GetAOERadius_17 = ability:GetAOERadius() + else + ____table_GetAOERadius_17 = 0 + end + local aoeRadius = ____table_GetAOERadius_17 + if aoeRadius <= 0 and ability.GetSpecialValueFor then + local specialRadius = ability:GetSpecialValueFor("radius") + if specialRadius and specialRadius > 0 then + aoeRadius = specialRadius + end + end + if not suppressWarningParticles then + local ____temp_18 + if aoeRadius > 0 then + ____temp_18 = aoeRadius + else + ____temp_18 = 200 + end + local followRadius = ____temp_18 + local followParticle = ParticleManager:CreateParticle("particles/econ/events/darkmoon_2017/darkmoon_generic_aoe.vpcf", PATTACH_CUSTOMORIGIN, nil) + if followParticle ~= nil then + ParticleManager:SetParticleControlEnt( + followParticle, + 0, + unit, + PATTACH_ABSORIGIN_FOLLOW, + nil, + unit:GetAbsOrigin(), + true + ) + ParticleManager:SetParticleControl( + followParticle, + 1, + Vector(followRadius, 0, 0) + ) + ParticleManager:SetParticleControl( + followParticle, + 2, + Vector(6, 0, 1) + ) + ParticleManager:SetParticleControl( + followParticle, + 3, + Vector(200, 0, 0) + ) + ParticleManager:SetParticleControl( + followParticle, + 4, + unit:GetAbsOrigin() + ) + self:startZoneTimer( + id, + warningTimeNeutral, + function() + if followParticle ~= nil then + ParticleManager:DestroyParticle(followParticle, false) + ParticleManager:ReleaseParticleIndex(followParticle) + end + return nil + end + ) + end + end + do + local direction = Vector(lockedTargetPos.x - unitPos.x, lockedTargetPos.y - unitPos.y, 0) + local length = math.sqrt(direction.x * direction.x + direction.y * direction.y) + if length > 0 then + local normalizedDir = Vector(direction.x / length, direction.y / length, 0) + unit:SetForwardVector(normalizedDir) + end + end + if not suppressWarningParticles then + createDirectionArrowNeutral(nil, unit, lockedTargetPos, warningTimeNeutral) + end + self:startZoneTimer( + id, + warningTimeNeutral, + function() + if not unit or not unit:IsAlive() then + return nil + end + local currentPos = unit:GetAbsOrigin() + local direction = Vector(lockedTargetPos.x - currentPos.x, lockedTargetPos.y - currentPos.y, 0) + local length = math.sqrt(direction.x * direction.x + direction.y * direction.y) + if length > 0 then + local normalizedDir = Vector(direction.x / length, direction.y / length, 0) + unit:SetForwardVector(normalizedDir) + end + ExecuteOrderFromTable({ + UnitIndex = unit:entindex(), + OrderType = DOTA_UNIT_ORDER_CAST_NO_TARGET, + AbilityIndex = ability:GetEntityIndex(), + Queue = false + }) + return nil + end + ) + neutralLastAbilityUseTime = now + return true + end + end + ::__continue340:: + i = i + 1 + end + end + return false + end + checkAggression = function() + if not unit or not unit:IsAlive() then + return + end + local currentHealth = unit:GetHealth() + local maxHealth = unit:GetMaxHealth() + local currentPos = unit:GetAbsOrigin() + local now = GameRules:GetGameTime() + if config.moveInGroups then + local group = self:getGroupForUnit(unit) + if group and #group > 0 then + local leader = self:getGroupLeader(group) + if leader and leader ~= unit and leader:IsAlive() then + local leaderPos = leader:GetAbsOrigin() + local followRadius = config.moveInGroups.stayRadius or 250 + local dxF = currentPos.x - leaderPos.x + local dyF = currentPos.y - leaderPos.y + local distToLeader = math.sqrt(dxF * dxF + dyF * dyF) + if distToLeader > followRadius * 0.7 and not unit:IsAttacking() and not unit:IsChanneling() then + ExecuteOrderFromTable({ + UnitIndex = unit:entindex(), + OrderType = DOTA_UNIT_ORDER_MOVE_TO_POSITION, + Position = leaderPos, + Queue = false + }) + return 0.2 + end + end + end + end + if not self:isWithinZoneBounds(config, currentPos) then + local nearbyEnemiesRaw = FindUnitsInRadius( + unit:GetTeamNumber(), + currentPos, + nil, + 200, + DOTA_UNIT_TARGET_TEAM_ENEMY, + bit.bor(DOTA_UNIT_TARGET_HERO, DOTA_UNIT_TARGET_BASIC), + DOTA_UNIT_TARGET_FLAG_NONE, + FIND_CLOSEST, + false + ) + local nearbyEnemies = __TS__ArrayFilter( + nearbyEnemiesRaw, + function(____, e) return self:isWithinChaseRange( + config, + e:GetAbsOrigin() + ) end + ) + if #nearbyEnemies == 0 then + local deepPos = self:getDeepPositionInZone(config) + ExecuteOrderFromTable({ + UnitIndex = unit:entindex(), + OrderType = DOTA_UNIT_ORDER_MOVE_TO_POSITION, + Position = deepPos, + Queue = false + }) + self:startZoneTimer( + id, + 1, + function() + if not unit or not unit:IsAlive() then + return nil + end + local checkPos = unit:GetAbsOrigin() + if not self:isWithinZoneBounds(config, checkPos) then + ExecuteOrderFromTable({ + UnitIndex = unit:entindex(), + OrderType = DOTA_UNIT_ORDER_MOVE_TO_POSITION, + Position = self:getDeepPositionInZone(config), + Queue = false + }) + end + return nil + end + ) + return 0.1 + end + end + if currentHealth < maxHealth and not isAggressive then + isAggressive = true + unit:SetAttackCapability(DOTA_UNIT_CAP_MELEE_ATTACK) + local nearbyUnitsRaw = FindUnitsInRadius( + unit:GetTeamNumber(), + unit:GetAbsOrigin(), + nil, + 500, + DOTA_UNIT_TARGET_TEAM_ENEMY, + DOTA_UNIT_TARGET_HERO + DOTA_UNIT_TARGET_BASIC, + DOTA_UNIT_TARGET_FLAG_NONE, + FIND_CLOSEST, + false + ) + local nearbyUnits = __TS__ArrayFilter( + nearbyUnitsRaw, + function(____, e) return self:isWithinChaseRange( + config, + e:GetAbsOrigin() + ) end + ) + if #nearbyUnits > 0 then + ExecuteOrderFromTable({ + UnitIndex = unit:entindex(), + OrderType = DOTA_UNIT_ORDER_ATTACK_TARGET, + TargetIndex = nearbyUnits[1]:entindex(), + Queue = false + }) + end + self:startZoneTimer( + id, + 5, + function() + if not unit or not unit:IsAlive() then + return nil + end + isAggressive = false + unit:SetAttackCapability(DOTA_UNIT_CAP_NO_ATTACK) + local returnPos = config.moveInGroups and self:getGroupForUnit(unit) and self:getGroupCenter(self:getGroupForUnit(unit)) or spawnPosition + ExecuteOrderFromTable({ + UnitIndex = unit:entindex(), + OrderType = DOTA_UNIT_ORDER_MOVE_TO_POSITION, + Position = returnPos, + Queue = false + }) + return nil + end + ) + end + if isAggressive then + local detectionRadius = 2000 + local nearbyEnemiesRaw = FindUnitsInRadius( + unit:GetTeamNumber(), + unit:GetAbsOrigin(), + nil, + detectionRadius, + DOTA_UNIT_TARGET_TEAM_ENEMY, + bit.bor(DOTA_UNIT_TARGET_HERO, DOTA_UNIT_TARGET_BASIC), + DOTA_UNIT_TARGET_FLAG_NONE, + FIND_CLOSEST, + false + ) + local nearbyEnemies = __TS__ArrayFilter( + nearbyEnemiesRaw, + function(____, e) return self:isWithinChaseRange( + config, + e:GetAbsOrigin() + ) end + ) + if #nearbyEnemies > 0 then + local closestEnemy = nearbyEnemies[1] + local unitPos = unit:GetAbsOrigin() + local enemyPos = closestEnemy:GetAbsOrigin() + local dx = unitPos.x - enemyPos.x + local dy = unitPos.y - enemyPos.y + local distanceToEnemy = math.sqrt(dx * dx + dy * dy) + if distanceToEnemy <= detectionRadius and closestEnemy:IsAlive() then + if distanceToEnemy > unit:GetBaseAttackRange() * 1.2 then + ExecuteOrderFromTable({ + UnitIndex = unit:entindex(), + OrderType = DOTA_UNIT_ORDER_ATTACK_MOVE, + Position = enemyPos, + Queue = false + }) + else + ExecuteOrderFromTable({ + UnitIndex = unit:entindex(), + OrderType = DOTA_UNIT_ORDER_ATTACK_TARGET, + TargetIndex = closestEnemy:entindex(), + Queue = false + }) + end + if distanceToEnemy <= 800 then + local usedAbility = tryUseAbilitiesNeutral(nil) + if usedAbility then + return 0.1 + end + end + end + end + end + local currentPosCheck = unit:GetAbsOrigin() + if not self:isWithinZoneBounds(config, currentPosCheck) and unit:GetAttackCapability() == DOTA_UNIT_CAP_NO_ATTACK then + local newPos = self:clampToZoneBounds(config, currentPosCheck) + ExecuteOrderFromTable({ + UnitIndex = unit:entindex(), + OrderType = DOTA_UNIT_ORDER_MOVE_TO_POSITION, + Position = newPos, + Queue = false + }) + end + return 0.1 + end + unit:SetContextThink("AggressionCheck", checkAggression, 0) + break + end + end + until true + local function getRandomWalkPosition() + local factor = config.patrolRadiusFactor ~= nil and math.max( + 0.1, + math.min(1, config.patrolRadiusFactor) + ) or 1 + local basePos = config.position + if config.moveInGroups then + local group = self:getGroupForUnit(unit) + if group and #group > 0 then + basePos = self:getGroupCenter(group) + end + end + local validPos + if config.shape == "square" then + local halfWidth = (config.width ~= nil and config.width / 2 or config.radius) * factor + local halfHeight = (config.height ~= nil and config.height / 2 or config.radius) * factor + local offsetX = RandomFloat(-halfWidth, halfWidth) + local offsetY = RandomFloat(-halfHeight, halfHeight) + validPos = GetGroundPosition( + Vector(basePos.x + offsetX, basePos.y + offsetY, basePos.z), + nil + ) + else + local angle = RandomFloat(0, 2 * math.pi) + local distance = RandomFloat(0, config.radius * factor) + validPos = GetGroundPosition( + Vector( + basePos.x + distance * math.cos(angle), + basePos.y + distance * math.sin(angle), + basePos.z + ), + nil + ) + end + if not self:isWithinZoneBounds(config, validPos) then + return self:clampToZoneBounds(config, validPos) + end + return validPos + end + local moveRandomly + moveRandomly = function() + if not self.spawnConfigs:has(id) then + return + end + if not unit or not unit:IsAlive() then + return + end + unit:SetForceAttackTarget(nil) + if unit:GetAttackCapability() == DOTA_UNIT_CAP_MELEE_ATTACK or unit:GetAttackCapability() == DOTA_UNIT_CAP_RANGED_ATTACK then + local newPos = getRandomWalkPosition(nil) + ExecuteOrderFromTable({ + UnitIndex = unit:entindex(), + OrderType = DOTA_UNIT_ORDER_ATTACK_MOVE, + Position = newPos, + Queue = false + }) + end + if unit:GetAttackCapability() == DOTA_UNIT_CAP_NO_ATTACK then + local newPos = getRandomWalkPosition(nil) + ExecuteOrderFromTable({ + UnitIndex = unit:entindex(), + OrderType = DOTA_UNIT_ORDER_MOVE_TO_POSITION, + Position = newPos, + Queue = false + }) + end + Timers:CreateTimer( + RandomFloat(3, 7), + function() + if not self.spawnConfigs:has(id) then + return nil + end + if unit and unit:IsAlive() then + moveRandomly(nil) + end + return nil + end + ) + end + unit:SetMoveCapability(DOTA_UNIT_CAP_MOVE_GROUND) + Timers:CreateTimer( + 1, + function() + if not self.spawnConfigs:has(id) then + return nil + end + moveRandomly(nil) + return nil + end + ) + aliveUnits[#aliveUnits + 1] = unit + self.spawnedUnits:set(id, aliveUnits) + self.spawnTimers:set( + id, + GameRules:GetGameTime() + ) + end + end +end +function SpawnManager.prototype.RemoveSpawnZone(self, id) + self:cancelZoneTimers(id) + local units = self.spawnedUnits:get(id) or ({}) + for ____, unit in ipairs(units) do + if unit and IsValidEntity(unit) then + do + pcall(function() + unit:SetContextThink("AggressionCheck", nil, 0) + unit:RemoveSelf() + end) + end + end + end + local prefix = id .. "_grp_" + for ____, groupId in ipairs(__TS__ArrayFrom(self.unitGroups:keys())) do + if __TS__StringStartsWith(groupId, prefix) then + self.unitGroups:delete(groupId) + end + end + self.spawnConfigs:delete(id) + self.spawnTimers:delete(id) + self.spawnedUnits:delete(id) + self.zoneRespawnScheduled:delete(id) + if self.debugZonesVisible then + DebugDrawClear() + self.spawnConfigs:forEach(function(____, config, zoneId) + self:DrawSpawnZoneDebug(zoneId, config) + end) + end +end +function SpawnManager.prototype.RemoveSpawnZonesByType(self, ____type) + local zoneIds = __TS__ArrayFrom(self.spawnConfigs:keys()) + __TS__ArrayForEach( + zoneIds, + function(____, zoneId) + if __TS__StringStartsWith(zoneId, ("zone_" .. ____type) .. "_") then + self:RemoveSpawnZone(zoneId) + end + end + ) +end +function SpawnManager.prototype.RemoveAllSpawnZones(self) + local zoneIds = __TS__ArrayFrom(self.spawnConfigs:keys()) + __TS__ArrayForEach( + zoneIds, + function(____, zoneId) + self:RemoveSpawnZone(zoneId) + end + ) +end +function SpawnManager.prototype.GetSpawnedUnits(self, id) + return self.spawnedUnits:get(id) or ({}) +end +function SpawnManager.prototype.GetZoneIdsByPrefixes(self, prefixes) + local result = {} + self.spawnConfigs:forEach(function(____, _, zoneId) + for ____, prefix in ipairs(prefixes) do + if __TS__StringStartsWith(zoneId, prefix) then + result[#result + 1] = zoneId + break + end + end + end) + return result +end +function SpawnManager.prototype.GetZoneCentersByPrefixes(self, prefixes) + local centers = {} + self.spawnConfigs:forEach(function(____, config, zoneId) + for ____, prefix in ipairs(prefixes) do + if __TS__StringStartsWith(zoneId, prefix) and config.position then + centers[#centers + 1] = config.position + break + end + end + end) + return centers +end +function SpawnManager.prototype.getSpawnUnitConfigName(self, unit) + do + local function ____catch() + return true, nil + end + local ____try, ____hasReturned, ____returnValue = pcall(function() + local stored = unit.__spawnManagerUnitName + if stored and #stored > 0 then + return true, stored + end + local name = unit:GetUnitName() + return true, name and #name > 0 and name or nil + end) + if not ____try then + ____hasReturned, ____returnValue = ____catch() + end + if ____hasReturned then + return ____returnValue + end + end +end +function SpawnManager.prototype.getValidSpawnedUnits(self, zoneId) + local units = self.spawnedUnits:get(zoneId) or ({}) + local validUnits = __TS__ArrayFilter( + units, + function(____, unit) + if not unit or not IsValidEntity(unit) then + return false + end + do + local function ____catch() + return true, false + end + local ____try, ____hasReturned, ____returnValue = pcall(function() + return true, unit:IsAlive() + end) + if not ____try then + ____hasReturned, ____returnValue = ____catch() + end + if ____hasReturned then + return ____returnValue + end + end + end + ) + self.spawnedUnits:set(zoneId, validUnits) + return validUnits +end +function SpawnManager.prototype.countSpawnedUnitsByConfigName(self, zoneId) + local counts = __TS__New(Map) + for ____, unit in ipairs(self:getValidSpawnedUnits(zoneId)) do + do + local name = self:getSpawnUnitConfigName(unit) + if not name then + goto __continue478 + end + counts:set( + name, + (counts:get(name) or 0) + 1 + ) + end + ::__continue478:: + end + return counts +end +function SpawnManager.prototype.getSpawnDelayForUnit(self, config, unitInfo) + return __TS__StringIncludes(unitInfo.unitName, "zombie") and config.interval * 2 or config.interval +end +function SpawnManager.prototype.replenishZoneMissingUnits(self, zoneId) + local config = self.spawnConfigs:get(zoneId) + if not config then + return + end + if not self:isSpawnAllowed() then + self:startZoneTimer( + zoneId, + 0.5, + function() + self:replenishZoneMissingUnits(zoneId) + return nil + end + ) + return + end + local currentUnitCounts = self:countSpawnedUnitsByConfigName(zoneId) + local toSpawn = {} + for ____, unitInfo in ipairs(config.units) do + local currentCount = currentUnitCounts:get(unitInfo.unitName) or 0 + local deficit = math.max(0, unitInfo.count - currentCount) + do + local i = 0 + while i < deficit do + toSpawn[#toSpawn + 1] = unitInfo + i = i + 1 + end + end + end + if #toSpawn <= 0 then + return + end + do + local i = 0 + while i < #toSpawn do + local unitInfo = toSpawn[i + 1] + local spawnDelay = self:getSpawnDelayForUnit(config, unitInfo) + i * 0.15 + self:startZoneTimer( + zoneId, + spawnDelay, + function() + self:SpawnUnit(zoneId, config, unitInfo) + return nil + end + ) + i = i + 1 + end + end +end +function SpawnManager.prototype.QueueRespawn(self, zoneId) + if not self.spawnConfigs:get(zoneId) then + return + end + if self.zoneRespawnScheduled:get(zoneId) then + return + end + self.zoneRespawnScheduled:set(zoneId, true) + self:startZoneTimer( + zoneId, + 0.5, + function() + self.zoneRespawnScheduled:delete(zoneId) + self:replenishZoneMissingUnits(zoneId) + return nil + end + ) +end +function SpawnManager.prototype.log(self, message) +end +SpawnManager.CHASE_LEASH_MARGIN = 350 +--- Маркеры зон спавна / визуалы событий +function ____exports.precacheSpawnManagerEffectParticles(self, context) + PrecacheResource("particle", "particles/econ/events/darkmoon_2017/darkmoon_generic_aoe.vpcf", context) + PrecacheResource("particle", "particles/econ/events/darkmoon_2017/darkmoon_calldown_marker_arrows.vpcf", context) + PrecacheResource("particle", "particles/econ/items/faceless_void/faceless_void_arcana/faceless_void_arcana_deny_v2_symbol_question.vpcf", context) +end +return ____exports diff --git a/scripts/vscripts/store_arcade_packs.lua b/scripts/vscripts/store_arcade_packs.lua new file mode 100644 index 0000000..ff1eefc --- /dev/null +++ b/scripts/vscripts/store_arcade_packs.lua @@ -0,0 +1,820 @@ +local ____lualib = require("lualib_bundle") +local Map = ____lualib.Map +local __TS__New = ____lualib.__TS__New +local Set = ____lualib.Set +local __TS__Number = ____lualib.__TS__Number +local __TS__ArrayIsArray = ____lualib.__TS__ArrayIsArray +local __TS__ObjectAssign = ____lualib.__TS__ObjectAssign +local __TS__NumberIsFinite = ____lualib.__TS__NumberIsFinite +local __TS__ObjectEntries = ____lualib.__TS__ObjectEntries +local __TS__ArrayFilter = ____lualib.__TS__ArrayFilter +local __TS__ArrayIncludes = ____lualib.__TS__ArrayIncludes +local __TS__ArrayEvery = ____lualib.__TS__ArrayEvery +local ____exports = {} +local getPurchasableCardPool, getPurchasablePoolByQuality, pickCardExactQuality, purchasableCardPoolCache, purchasablePoolByQualityCache, ARCADE_PACK_QUALITY_PICK_ATTEMPTS +local ____card_catalog = require("card_catalog") +local ALL_CARD_CATALOG_DEFS = ____card_catalog.ALL_CARD_CATALOG_DEFS +local ____CardSystem = require("cards.CardSystem") +local CardQuality = ____CardSystem.CardQuality +local CardSystem = ____CardSystem.CardSystem +local ____custom_game_events = require("custom_game_events") +local ensurePlayerCardSystem = ____custom_game_events.ensurePlayerCardSystem +local ____api_helper = require("api_helper") +local encodeApiBody = ____api_helper.encodeApiBody +local setApiHeaders = ____api_helper.setApiHeaders +local ____player_info = require("player_info") +local PlayerInfo = ____player_info.PlayerInfo +local ____server_config = require("server_config") +local SERVER_CONFIG = ____server_config.SERVER_CONFIG +local ____store_manager = require("store_manager") +local StoreManager = ____store_manager.StoreManager +function getPurchasableCardPool(self) + if purchasableCardPoolCache and #purchasableCardPoolCache > 0 then + return purchasableCardPoolCache + end + local pool = {} + for ____, def in ipairs(ALL_CARD_CATALOG_DEFS) do + do + local id = math.floor(__TS__Number(def.id)) + if not __TS__NumberIsFinite(id) or id <= 0 or id == 404 then + goto __continue46 + end + if def.defaultCard == true or def.purchasable == false then + goto __continue46 + end + local runtimeData = CardSystem.cardData[id] + if (runtimeData and runtimeData.disabled) == true then + goto __continue46 + end + pool[#pool + 1] = id + end + ::__continue46:: + end + if #pool == 0 then + for ____, ____value in ipairs(__TS__ObjectEntries(CardSystem.cardData)) do + local idKey = ____value[1] + local data = ____value[2] + do + local id = math.floor(__TS__Number(idKey)) + if not __TS__NumberIsFinite(id) or id <= 0 or data.disabled == true or data.default == true or data.purchasable == false then + goto __continue52 + end + pool[#pool + 1] = id + end + ::__continue52:: + end + end + purchasableCardPoolCache = pool + purchasablePoolByQualityCache = nil + return pool +end +function getPurchasablePoolByQuality(self) + if purchasablePoolByQualityCache then + return purchasablePoolByQualityCache + end + local byQuality = {} + for ____, cardId in ipairs(getPurchasableCardPool(nil)) do + local cardData = CardSystem.cardData[cardId] + local quality = math.max( + CardQuality.COMMON, + math.min( + CardQuality.MYTHIC, + math.floor(__TS__Number(cardData and cardData.quality or CardQuality.COMMON)) + ) + ) + if not byQuality[quality] then + byQuality[quality] = {} + end + local ____byQuality_quality_11 = byQuality[quality] + ____byQuality_quality_11[#____byQuality_quality_11 + 1] = cardId + end + purchasablePoolByQualityCache = byQuality + return byQuality +end +function pickCardExactQuality(self, quality, excludeIds) + local pool = getPurchasablePoolByQuality(nil)[quality] + if not pool or #pool == 0 then + return nil + end + local exclude = __TS__New(Set, excludeIds) + local available = __TS__ArrayFilter( + pool, + function(____, id) return not exclude:has(id) end + ) + local pickFrom = #available > 0 and available or pool + return pickFrom[RandomInt(0, #pickFrom - 1) + 1] +end +____exports.ARCADE_PACK_DEFINITIONS = {arcade_pack_standard = {id = "arcade_pack_standard", cardsCount = 3, price_free = 25000, allowed_currencies = {"free_currency"}}, arcade_pack_premium = {id = "arcade_pack_premium", cardsCount = 3, price_donate = 80, allowed_currencies = {"donate_currency"}}} +--- Пороги pity: паков подряд без редкости → гарантия в следующем паке. +local ARCADE_PITY_STANDARD_MYTHIC_PACKS = 100 +local ARCADE_PITY_STANDARD_LEGENDARY_PACKS = 50 +local ARCADE_PITY_PREMIUM_MYTHIC_PACKS = 10 +local ARCADE_SESSION_TTL_SECONDS = 600 +local sessionsByPlayerId = __TS__New(Map) +local arcadePityByPlayerId = __TS__New(Map) +local arcadePityLoadedFromBackend = __TS__New(Set) +local function defaultArcadePityState(self) + return {standardPacksWithoutMythic = 0, standardPacksWithoutLegendary = 0, premiumPacksWithoutMythic = 0} +end +local function normalizeArcadePityState(self, raw) + local state = defaultArcadePityState(nil) + if not raw or type(raw) ~= "table" then + return state + end + local obj = raw + state.standardPacksWithoutMythic = math.max( + 0, + math.floor(__TS__Number(obj.standardPacksWithoutMythic) or 0) + ) + local ____math_max_2 = math.max + local ____math_floor_1 = math.floor + local ____obj_standardPacksWithoutLegendary_0 = obj.standardPacksWithoutLegendary + if ____obj_standardPacksWithoutLegendary_0 == nil then + ____obj_standardPacksWithoutLegendary_0 = obj.standardPacksWithoutEpic + end + state.standardPacksWithoutLegendary = ____math_max_2( + 0, + ____math_floor_1(__TS__Number(____obj_standardPacksWithoutLegendary_0) or 0) + ) + state.premiumPacksWithoutMythic = math.max( + 0, + math.floor(__TS__Number(obj.premiumPacksWithoutMythic) or 0) + ) + return state +end +local function decodeJsonBody(self, body) + do + local function ____catch() + return true, nil + end + local ____try, ____hasReturned, ____returnValue = pcall(function() + return true, {json.decode(body)} + end) + if not ____try then + ____hasReturned, ____returnValue = ____catch() + end + if ____hasReturned then + return ____returnValue + end + end +end +local function unwrapApiJsonObject(self, decoded) + if __TS__ArrayIsArray(decoded) and #decoded > 0 then + return decoded[1] + end + return decoded +end +local function parseArcadePityPayload(self, raw) + if not raw or type(raw) ~= "table" then + return nil + end + local root = raw + local pityRaw = root.arcade_pity + if pityRaw == nil or pityRaw == nil then + return normalizeArcadePityState(nil, nil) + end + return normalizeArcadePityState(nil, pityRaw) +end +local function applyArcadePityState(self, playerId, state) + arcadePityByPlayerId:set(playerId, state) + local info = PlayerInfo:GetPlayerInfo(playerId) or ({}) + PlayerInfo:UpdatePlayerInfo( + playerId, + __TS__ObjectAssign({}, info, {arcade_pity = state}) + ) +end +local function saveArcadePityToBackend(self, playerId, state) + local steamId = PlayerResource:GetSteamAccountID(playerId) + if not steamId then + return + end + local request = CreateHTTPRequest( + "PUT", + ((SERVER_CONFIG.API_URL .. "/player/") .. tostring(steamId)) .. "/arcade_pity" + ) + setApiHeaders(nil, request) + request:SetHTTPRequestRawPostBody( + "application/json", + encodeApiBody(nil, {arcade_pity = state}) + ) + request:Send(function(result) + if result.StatusCode < 200 or result.StatusCode >= 300 then + print((((("[ARCADE_PITY] PUT fail player=" .. tostring(playerId)) .. " steam=") .. tostring(steamId)) .. " code=") .. tostring(result.StatusCode)) + end + end) +end +--- Загрузить pity с бэка при входе в игру / реконнект. +function ____exports.loadArcadePityForPlayer(self, playerId) + local steamId = PlayerResource:GetSteamAccountID(playerId) + if not steamId then + return + end + local request = CreateHTTPRequest( + "GET", + ((SERVER_CONFIG.API_URL .. "/player/") .. tostring(steamId)) .. "/arcade_pity" + ) + setApiHeaders(nil, request) + request:Send(function(result) + if result.StatusCode < 200 or result.StatusCode >= 300 then + print((((("[ARCADE_PITY] GET fail player=" .. tostring(playerId)) .. " steam=") .. tostring(steamId)) .. " code=") .. tostring(result.StatusCode)) + return + end + local bodyStr = result.Body ~= nil and tostring(result.Body) or "" + if #bodyStr == 0 then + return + end + local decoded = decodeJsonBody(nil, bodyStr) + local obj = unwrapApiJsonObject(nil, decoded) + local state = parseArcadePityPayload(nil, obj) + if state == nil then + print((("[ARCADE_PITY] GET parse invalid player=" .. tostring(playerId)) .. " steam=") .. tostring(steamId)) + return + end + arcadePityLoadedFromBackend:add(playerId) + applyArcadePityState(nil, playerId, state) + print((((((("[ARCADE_PITY] GET ok player=" .. tostring(playerId)) .. " stdM=") .. tostring(state.standardPacksWithoutMythic)) .. " stdL=") .. tostring(state.standardPacksWithoutLegendary)) .. " premM=") .. tostring(state.premiumPacksWithoutMythic)) + end) +end +local function getArcadePityState(self, playerId) + local state = arcadePityByPlayerId:get(playerId) + if not state then + local info = PlayerInfo:GetPlayerInfo(playerId) + state = normalizeArcadePityState(nil, info and info.arcade_pity) + arcadePityByPlayerId:set(playerId, state) + end + return state +end +local function saveArcadePityState(self, playerId, state) + applyArcadePityState(nil, playerId, state) + saveArcadePityToBackend(nil, playerId, state) +end +local function getCardQualityForPity(self, cardId) + local cardData = CardSystem.cardData[cardId] + return math.max( + CardQuality.COMMON, + math.min( + CardQuality.MYTHIC, + math.floor(__TS__Number(cardData and cardData.quality or CardQuality.COMMON)) + ) + ) +end +local function packHasMythic(self, cardIds) + for ____, cardId in ipairs(cardIds) do + if getCardQualityForPity(nil, cardId) == CardQuality.MYTHIC then + return true + end + end + return false +end +local function packHasLegendaryOrBetter(self, cardIds) + for ____, cardId in ipairs(cardIds) do + if getCardQualityForPity(nil, cardId) >= CardQuality.LEGENDARY then + return true + end + end + return false +end +local function buildArcadePityPlan(self, packId, state) + local plan = {} + local slot = 0 + if packId == "arcade_pack_premium" then + if state.premiumPacksWithoutMythic >= ARCADE_PITY_PREMIUM_MYTHIC_PACKS then + plan.mythicSlot = slot + slot = slot + 1 + end + return plan + end + if state.standardPacksWithoutMythic >= ARCADE_PITY_STANDARD_MYTHIC_PACKS then + plan.mythicSlot = slot + slot = slot + 1 + end + if state.standardPacksWithoutLegendary >= ARCADE_PITY_STANDARD_LEGENDARY_PACKS then + plan.legendarySlot = slot + slot = slot + 1 + end + return plan +end +local function pickCardForcedQuality(self, quality, excludeIds) + do + local attempt = 0 + while attempt < ARCADE_PACK_QUALITY_PICK_ATTEMPTS do + local cardId = pickCardExactQuality(nil, quality, excludeIds) + if cardId ~= nil then + return cardId + end + attempt = attempt + 1 + end + end + return nil +end +--- Шансы редкости карты в паке аркады (сумма = 100%, шкала 1..10000). +local ARCADE_PACK_QUALITY_ROLL_MAX = 10000 +local ARCADE_PACK_STANDARD_QUALITY_THRESHOLDS = { + {quality = CardQuality.COMMON, cumulative = 5500}, + {quality = CardQuality.RARE, cumulative = 8000}, + {quality = CardQuality.EPIC, cumulative = 9500}, + {quality = CardQuality.LEGENDARY, cumulative = 9980}, + {quality = CardQuality.MYTHIC, cumulative = 10000} +} +--- Премиум-пак: только эпик / легендарка / мифик. +local ARCADE_PACK_PREMIUM_QUALITY_THRESHOLDS = {{quality = CardQuality.EPIC, cumulative = 6000}, {quality = CardQuality.LEGENDARY, cumulative = 9800}, {quality = CardQuality.MYTHIC, cumulative = 10000}} +ARCADE_PACK_QUALITY_PICK_ATTEMPTS = 64 +local function getArcadePackQualityWeights(self, packId) + local thresholds = packId == "arcade_pack_premium" and ARCADE_PACK_PREMIUM_QUALITY_THRESHOLDS or ARCADE_PACK_STANDARD_QUALITY_THRESHOLDS + local weights = {} + local prevCumulative = 0 + for ____, entry in ipairs(thresholds) do + weights[#weights + 1] = {quality = entry.quality, weight = entry.cumulative - prevCumulative} + prevCumulative = entry.cumulative + end + return weights +end +local function rollArcadePackQualityFromWeights(self, weights) + local total = 0 + for ____, w in ipairs(weights) do + total = total + w.weight + end + if total <= 0 then + local ____opt_12 = weights[#weights] + return ____opt_12 and ____opt_12.quality or CardQuality.COMMON + end + local roll = RandomInt(1, total) + for ____, w in ipairs(weights) do + if roll <= w.weight then + return w.quality + end + roll = roll - w.weight + end + return weights[#weights].quality +end +--- Редкости, для которых в каталоге есть хотя бы одна карта. +local function getStockedQualityWeights(self, packId) + local minQuality = packId == "arcade_pack_premium" and CardQuality.EPIC or CardQuality.COMMON + local byQuality = getPurchasablePoolByQuality(nil) + return __TS__ArrayFilter( + getArcadePackQualityWeights(nil, packId), + function(____, entry) + if entry.quality < minQuality then + return false + end + local pool = byQuality[entry.quality] + return pool ~= nil and #pool > 0 + end + ) +end +--- Сначала бросок по таблице шансов → карта строго этой редкости. +-- Если пул пуст — повторный бросок (без понижения/повышения тира). +local function pickRandomCardForPack(self, packId, excludeIds) + local tableWeights = getArcadePackQualityWeights(nil, packId) + do + local attempt = 0 + while attempt < ARCADE_PACK_QUALITY_PICK_ATTEMPTS do + local rolledQuality = rollArcadePackQualityFromWeights(nil, tableWeights) + local cardId = pickCardExactQuality(nil, rolledQuality, excludeIds) + if cardId ~= nil then + return cardId + end + attempt = attempt + 1 + end + end + local stockedWeights = getStockedQualityWeights(nil, packId) + if #stockedWeights == 0 then + return nil + end + do + local attempt = 0 + while attempt < ARCADE_PACK_QUALITY_PICK_ATTEMPTS do + local rolledQuality = rollArcadePackQualityFromWeights(nil, stockedWeights) + local cardId = pickCardExactQuality(nil, rolledQuality, excludeIds) + if cardId ~= nil then + return cardId + end + attempt = attempt + 1 + end + end + return nil +end +local function rollRandomCardIds(self, count, packId, pityPlan) + if #getPurchasableCardPool(nil) == 0 then + return {} + end + local forcedBySlot = __TS__New(Map) + if (pityPlan and pityPlan.mythicSlot) ~= nil and pityPlan.mythicSlot < count then + forcedBySlot:set(pityPlan.mythicSlot, CardQuality.MYTHIC) + end + if (pityPlan and pityPlan.legendarySlot) ~= nil and pityPlan.legendarySlot < count and not forcedBySlot:has(pityPlan.legendarySlot) then + forcedBySlot:set(pityPlan.legendarySlot, CardQuality.LEGENDARY) + end + local result = {} + do + local slot = 0 + while slot < count do + local forcedQuality = forcedBySlot:get(slot) + local cardId + if forcedQuality ~= nil then + cardId = pickCardForcedQuality(nil, forcedQuality, result) + end + if cardId == nil then + cardId = pickRandomCardForPack(nil, packId, result) + end + if cardId ~= nil then + result[#result + 1] = cardId + end + slot = slot + 1 + end + end + return result +end +local function clearExpiredSession(self, playerId) + local session = sessionsByPlayerId:get(playerId) + if not session then + return + end + if GameRules:GetGameTime() - session.createdAt > ARCADE_SESSION_TTL_SECONDS then + sessionsByPlayerId:delete(playerId) + end +end +local function updateArcadePityAfterPack(self, playerId, packId, cardIds) + local state = getArcadePityState(nil, playerId) + local gotMythic = packHasMythic(nil, cardIds) + local gotLegendaryPlus = packHasLegendaryOrBetter(nil, cardIds) + if packId == "arcade_pack_premium" then + state.premiumPacksWithoutMythic = gotMythic and 0 or state.premiumPacksWithoutMythic + 1 + else + state.standardPacksWithoutMythic = gotMythic and 0 or state.standardPacksWithoutMythic + 1 + state.standardPacksWithoutLegendary = gotLegendaryPlus and 0 or state.standardPacksWithoutLegendary + 1 + end + saveArcadePityState(nil, playerId, state) +end +local function sendArcadeEvent(self, playerId, eventName, payload) + local player = PlayerResource:GetPlayer(playerId) + if not player then + return + end + CustomGameEventManager:Send_ServerToPlayer(player, eventName, payload) +end +local function resolvePackPrice(self, pack, currency) + if currency == "dust_currency" and pack.price_dust ~= nil then + return math.floor(pack.price_dust) + end + if currency == "donate_currency" and pack.price_donate ~= nil then + return math.floor(pack.price_donate) + end + if currency == "free_currency" and pack.price_free ~= nil then + return math.floor(pack.price_free) + end + return -1 +end +local function deductCurrency(self, store, playerId, currency, price) + if price <= 0 then + return true + end + if currency == "free_currency" then + return store:removeFreeCurrency(playerId, price) + end + if currency == "donate_currency" then + return store:removeDonateCurrency(playerId, price) + end + if currency == "dust_currency" then + return store:removeDustCurrency(playerId, price) + end + return false +end +local function refundCurrency(self, store, playerId, currency, price) + if price <= 0 then + return + end + if currency == "free_currency" then + store:addFreeCurrency(playerId, price) + elseif currency == "donate_currency" then + store:addDonateCurrency(playerId, price) + elseif currency == "dust_currency" then + store:addDustCurrency(playerId, price) + end +end +local function defaultArcadePackCredits(self) + return {standard = 0, premium = 0} +end +local function normalizeArcadePackCredits(self, raw) + local state = defaultArcadePackCredits(nil) + if not raw or type(raw) ~= "table" then + return state + end + local obj = raw + state.standard = math.max( + 0, + math.floor(__TS__Number(obj.standard) or 0) + ) + state.premium = math.max( + 0, + math.floor(__TS__Number(obj.premium) or 0) + ) + return state +end +local function applyArcadePackCredits(self, playerId, credits) + local info = PlayerInfo:GetPlayerInfo(playerId) or ({}) + PlayerInfo:UpdatePlayerInfo( + playerId, + __TS__ObjectAssign({}, info, {arcade_pack_credits = credits}) + ) +end +local function getLocalArcadePackCredits(self, playerId) + local info = PlayerInfo:GetPlayerInfo(playerId) + return normalizeArcadePackCredits(nil, info and info.arcade_pack_credits) +end +local function getArcadePackCreditKey(self, packId) + if packId == "arcade_pack_standard" then + return "standard" + end + if packId == "arcade_pack_premium" then + return "premium" + end + return nil +end +local function tryConsumeArcadePackCredit(self, playerId, packId, onDone) + local creditKey = getArcadePackCreditKey(nil, packId) + if not creditKey then + onDone(nil, false) + return + end + local ____local = getLocalArcadePackCredits(nil, playerId) + if ____local[creditKey] <= 0 then + onDone(nil, false) + return + end + local steamId = PlayerResource:GetSteamAccountID(playerId) + if not steamId then + onDone(nil, false) + return + end + local request = CreateHTTPRequest( + "POST", + ((SERVER_CONFIG.API_URL .. "/player/") .. tostring(steamId)) .. "/arcade_pack_credits/consume" + ) + setApiHeaders(nil, request) + request:SetHTTPRequestRawPostBody( + "application/json", + encodeApiBody(nil, {pack_id = packId}) + ) + request:Send(function(result) + if result.StatusCode < 200 or result.StatusCode >= 300 then + onDone(nil, false) + return + end + do + local ____try, ____hasReturned, ____returnValue = pcall(function() + local decoded = decodeJsonBody(nil, result.Body) + local data = unwrapApiJsonObject(nil, decoded) + if (data and data.consumed) == true and data.arcade_pack_credits then + local credits = normalizeArcadePackCredits(nil, data.arcade_pack_credits) + applyArcadePackCredits(nil, playerId, credits) + onDone(nil, true) + return true + end + end) + if ____try and ____hasReturned then + return ____returnValue + end + end + onDone(nil, false) + end) +end +local function getCardLevelForPlayer(self, playerId, cardId) + local cardSystem = ensurePlayerCardSystem(nil, playerId) + if not cardSystem then + return 1 + end + local level = cardSystem:GetCardLevel(cardId) + return __TS__NumberIsFinite(level) and math.max( + 1, + math.floor(level) + ) or 1 +end +local function buildFlipPayload(self, playerId, cardId, slotIndex) + local cardData = CardSystem.cardData[cardId] + local quality = math.max( + 1, + math.floor(__TS__Number(cardData and cardData.quality or CardQuality.COMMON)) + ) + local cardLevel = getCardLevelForPlayer(nil, playerId, cardId) + local store = StoreManager:getInstance() + local ownedBefore = store:getOwnedCardCopies(playerId, cardId) + local maxCopies = store:getCardMaxCopies(cardId) + local isDuplicate = ownedBefore >= maxCopies + local dustGranted = 0 + if isDuplicate then + dustGranted = CardSystem:getDuplicateCardDustCompensation(quality, cardLevel) + if dustGranted > 0 then + store:addDustCurrency(playerId, dustGranted) + store:saveCurrencyToServer(playerId) + end + else + store:grantCardPurchaseWithoutPayment(playerId, cardId) + end + local rawIcon = cardData and cardData.icon + local icon = rawIcon ~= nil and #tostring(rawIcon) > 0 and tostring(rawIcon) or ("file://{images}/custom_game/cards/card_" .. tostring(cardId)) .. ".png" + local rawCardName = cardData and cardData.name + local cardName = rawCardName ~= nil and #tostring(rawCardName) > 0 and tostring(rawCardName) or ("card_" .. tostring(cardId)) .. "_name" + return { + slot_index = slotIndex, + card_id = cardId, + duplicate = isDuplicate and 1 or 0, + dust_granted = dustGranted, + card_level = cardLevel, + quality = quality, + icon = icon, + card_name = cardName + } +end +--- Выдать все карты пака сразу (порядок слотов важен для дубликатов). +local function grantArcadePackCards(self, playerId, cardIds) + local payloads = {} + do + local i = 0 + while i < #cardIds do + payloads[#payloads + 1] = buildFlipPayload(nil, playerId, cardIds[i + 1], i) + i = i + 1 + end + end + return payloads +end +local function handleBuyArcadePack(self, playerId, packId, selectedCurrency) + local pack = ____exports.ARCADE_PACK_DEFINITIONS[packId] + local store = StoreManager:getInstance() + if not pack then + store:sendPurchaseResult(playerId, false, "Неизвестный пак") + return + end + if not store:tryAcquireStorePurchaseLock(playerId) then + store:sendPurchaseResult(playerId, false, "Подождите, предыдущая покупка обрабатывается") + return + end + store:syncLatestCurrencyTotalsFromApi( + playerId, + function(____, currencyOk) + do + local function ____catch() + store:releaseStorePurchaseLock(playerId) + end + local ____try, ____hasReturned, ____returnValue = pcall(function() + if not currencyOk then + store:sendPurchaseResult(playerId, false, "Не удалось проверить баланс") + store:releaseStorePurchaseLock(playerId) + return true + end + if not arcadePityLoadedFromBackend:has(playerId) then + ____exports.loadArcadePityForPlayer(nil, playerId) + end + clearExpiredSession(nil, playerId) + if sessionsByPlayerId:has(playerId) then + store:sendPurchaseResult(playerId, false, "Сначала откройте карты из текущего пака") + store:releaseStorePurchaseLock(playerId) + return true + end + tryConsumeArcadePackCredit( + nil, + playerId, + packId, + function(____, usedCredit) + do + local ____try, ____hasReturned, ____returnValue = pcall(function() + local currency = selectedCurrency or pack.allowed_currencies[1] or "dust_currency" + if not usedCredit and not __TS__ArrayIncludes(pack.allowed_currencies, currency) then + store:sendPurchaseResult(playerId, false, "Эта валюта недоступна для пака") + return true + end + local price = usedCredit and 0 or resolvePackPrice(nil, pack, currency) + if price < 0 then + store:sendPurchaseResult(playerId, false, "Некорректная цена пака") + return true + end + local pityState = getArcadePityState(nil, playerId) + local pityPlan = buildArcadePityPlan(nil, pack.id, pityState) + local cardIds = rollRandomCardIds(nil, pack.cardsCount, pack.id, pityPlan) + if #cardIds < pack.cardsCount then + store:sendPurchaseResult(playerId, false, "Нет доступных карт для пака") + return true + end + if not usedCredit and not deductCurrency( + nil, + store, + playerId, + currency, + price + ) then + store:sendPurchaseResult(playerId, false, "Недостаточно валюты") + return true + end + if not usedCredit then + store:advanceCurrencyLoadSeq(playerId) + store:saveCurrencyToServer(playerId) + else + store:notifyArcadePackCredits(playerId) + end + store:updateCurrencyDisplay(playerId) + local flipPayloads = grantArcadePackCards(nil, playerId, cardIds) + updateArcadePityAfterPack(nil, playerId, pack.id, cardIds) + store:updateCurrencyDisplay(playerId) + local session = { + packId = pack.id, + flipPayloads = flipPayloads, + revealed = {}, + createdAt = GameRules:GetGameTime() + } + do + local i = 0 + while i < pack.cardsCount do + local ____session_revealed_28 = session.revealed + ____session_revealed_28[#____session_revealed_28 + 1] = false + i = i + 1 + end + end + sessionsByPlayerId:set(playerId, session) + local successMessage = usedCredit and "Открыт бесплатный пак из бандла" or "Пак куплен" + store:sendPurchaseResult(playerId, true, successMessage, pack.id) + sendArcadeEvent(nil, playerId, "store_arcade_pack_opened", {pack_id = pack.id, slot_count = pack.cardsCount, cards = flipPayloads, used_bundle_credit = usedCredit and 1 or 0}) + end) + do + store:releaseStorePurchaseLock(playerId) + end + if ____try and ____hasReturned then + return ____returnValue + end + end + end + ) + end) + if not ____try then + ____hasReturned, ____returnValue = ____catch() + end + if ____hasReturned then + return ____returnValue + end + end + end + ) +end +local function handleFlipArcadeCard(self, playerId, slotIndexRaw) + clearExpiredSession(nil, playerId) + local session = sessionsByPlayerId:get(playerId) + if not session then + sendArcadeEvent(nil, playerId, "store_arcade_flip_result", {success = 0, message = "Нет активного пака"}) + return + end + local slotIndex = math.floor(__TS__Number(slotIndexRaw)) + if not __TS__NumberIsFinite(slotIndex) or slotIndex < 0 or slotIndex >= #session.flipPayloads then + sendArcadeEvent(nil, playerId, "store_arcade_flip_result", {success = 0, message = "Некорректная карта"}) + return + end + if session.revealed[slotIndex + 1] then + sendArcadeEvent(nil, playerId, "store_arcade_flip_result", {success = 0, message = "Карта уже открыта"}) + return + end + session.revealed[slotIndex + 1] = true + local flipPayload = session.flipPayloads[slotIndex + 1] + sendArcadeEvent( + nil, + playerId, + "store_arcade_flip_result", + __TS__ObjectAssign({success = 1}, flipPayload) + ) + local allRevealed = __TS__ArrayEvery( + session.revealed, + function(____, v) return v == true end + ) + if allRevealed then + sessionsByPlayerId:delete(playerId) + sendArcadeEvent(nil, playerId, "store_arcade_pack_complete", {pack_id = session.packId}) + end +end +local function setupStoreArcadePackListeners(self) + CustomGameEventManager:RegisterListener( + "store_buy_arcade_pack", + function(_source, event) + local playerId = event.PlayerID + local packId = tostring(event.pack_id or event.item_id or "") + local selectedCurrency = event.selected_currency + handleBuyArcadePack(nil, playerId, packId, selectedCurrency) + end + ) + CustomGameEventManager:RegisterListener( + "store_arcade_flip_card", + function(_source, event) + local playerId = event.PlayerID + local ____event_slot_index_29 = event.slot_index + if ____event_slot_index_29 == nil then + ____event_slot_index_29 = event.slotIndex + end + local ____event_slot_index_29_30 = ____event_slot_index_29 + if ____event_slot_index_29_30 == nil then + ____event_slot_index_29_30 = -1 + end + local slotIndex = __TS__Number(____event_slot_index_29_30) + handleFlipArcadeCard(nil, playerId, slotIndex) + end + ) +end +if IsServer() then + setupStoreArcadePackListeners(nil) +end +return ____exports diff --git a/scripts/vscripts/store_item_access.lua b/scripts/vscripts/store_item_access.lua new file mode 100644 index 0000000..9d91d41 --- /dev/null +++ b/scripts/vscripts/store_item_access.lua @@ -0,0 +1,17 @@ +--[[ Generated with https://github.com/TypeScriptToLua/TypeScriptToLua ]] +local ____exports = {} +____exports.STORE_ITEM_ACCESS_BY_ID = { + skin_effect_fire = {purchaseDisabled = true, onlyVisibleIfOwned = true}, + skin_effect_ice = {purchaseDisabled = true, onlyVisibleIfOwned = true}, + skin_effect_bp_red = {purchaseDisabled = true, onlyVisibleIfOwned = true}, + skin_effect_bp_garden = {purchaseDisabled = true, onlyVisibleIfOwned = true}, + skin_effect_bp_golden = {purchaseDisabled = true, onlyVisibleIfOwned = true}, + skin_effect_bp_diretide_emblem = {purchaseDisabled = true, onlyVisibleIfOwned = true}, + skin_effect_bp_diretide_emblem_v1 = {purchaseDisabled = true, onlyVisibleIfOwned = true}, + skin_effect_bp_diretide_emblem_v3 = {purchaseDisabled = true, onlyVisibleIfOwned = true} +} +function ____exports.isStorePurchaseBlockedById(self, itemId) + local ____opt_0 = ____exports.STORE_ITEM_ACCESS_BY_ID[itemId] + return (____opt_0 and ____opt_0.purchaseDisabled) == true +end +return ____exports diff --git a/scripts/vscripts/store_manager.lua b/scripts/vscripts/store_manager.lua new file mode 100644 index 0000000..2260668 --- /dev/null +++ b/scripts/vscripts/store_manager.lua @@ -0,0 +1,2951 @@ +local ____lualib = require("lualib_bundle") +local __TS__Number = ____lualib.__TS__Number +local __TS__NumberIsFinite = ____lualib.__TS__NumberIsFinite +local __TS__Class = ____lualib.__TS__Class +local Map = ____lualib.Map +local __TS__New = ____lualib.__TS__New +local __TS__ArrayIsArray = ____lualib.__TS__ArrayIsArray +local __TS__StringIncludes = ____lualib.__TS__StringIncludes +local __TS__StringSubstring = ____lualib.__TS__StringSubstring +local Set = ____lualib.Set +local __TS__ObjectAssign = ____lualib.__TS__ObjectAssign +local __TS__StringReplace = ____lualib.__TS__StringReplace +local __TS__StringTrim = ____lualib.__TS__StringTrim +local __TS__ArrayIncludes = ____lualib.__TS__ArrayIncludes +local __TS__ObjectValues = ____lualib.__TS__ObjectValues +local __TS__ArrayPush = ____lualib.__TS__ArrayPush +local __TS__StringStartsWith = ____lualib.__TS__StringStartsWith +local __TS__Iterator = ____lualib.__TS__Iterator +local __TS__ObjectEntries = ____lualib.__TS__ObjectEntries +local __TS__StringCharAt = ____lualib.__TS__StringCharAt +local __TS__ArrayFrom = ____lualib.__TS__ArrayFrom +local ____exports = {} +local ____CardSystem = require("cards.CardSystem") +local AddCardToPlayerPool = ____CardSystem.AddCardToPlayerPool +local ____server_config = require("server_config") +local SERVER_CONFIG = ____server_config.SERVER_CONFIG +local ____api_helper = require("api_helper") +local encodeApiBody = ____api_helper.encodeApiBody +local setApiHeaders = ____api_helper.setApiHeaders +local setApiHeadersLong = ____api_helper.setApiHeadersLong +local ____player_info = require("player_info") +local PlayerInfo = ____player_info.PlayerInfo +local ____store_item_access = require("store_item_access") +local isStorePurchaseBlockedById = ____store_item_access.isStorePurchaseBlockedById +local ____card_catalog = require("card_catalog") +local ALL_CARD_CATALOG_DEFS = ____card_catalog.ALL_CARD_CATALOG_DEFS +local ____chat_wheel_grant = require("chat_wheel_grant") +local grantChatWheelSoundFromBattlePass = ____chat_wheel_grant.grantChatWheelSoundFromBattlePass +local ENABLE_VERBOSE_STORE_MANAGER_LOGS = true +local STORE_EXCHANGE_RATE_DONATE_TO_FREE = 50 +local STORE_EXCHANGE_MIN_INTERVAL_SECONDS = 0.35 +--- Вербозные логи магазина: только одна строка в глобальный `print`. +-- Иначе TSTL оборачивает вариадик в `fn(____, ...)` и подставляет первым аргументом служебную таблицу — +-- в консоли получается `table: 0x…\\t[STORE] …` при каждом вызове. +local function storeVerboseLog(self, message) + if not ENABLE_VERBOSE_STORE_MANAGER_LOGS then + return + end + _G:print(message) +end +local CARD_MAX_COPIES_BY_ID = {} +local CARD_PURCHASABLE_BY_ID = {} +for ____, cardDef in ipairs(ALL_CARD_CATALOG_DEFS) do + local configuredCopies = __TS__Number(cardDef.max_copies) + local normalizedCopies = __TS__NumberIsFinite(configuredCopies) and configuredCopies > 0 and math.floor(configuredCopies) or 1 + CARD_MAX_COPIES_BY_ID[math.floor(cardDef.id)] = normalizedCopies + CARD_PURCHASABLE_BY_ID[math.floor(cardDef.id)] = cardDef.purchasable ~= false +end +____exports.StoreManager = __TS__Class() +local StoreManager = ____exports.StoreManager +StoreManager.name = "StoreManager" +StoreManager.____file_path = "scripts/vscripts/store_manager.lua" +function StoreManager.prototype.____constructor(self) + self.playerFreeCurrency = __TS__New(Map) + self.playerDonateCurrency = __TS__New(Map) + self.playerDustCurrency = __TS__New(Map) + self.playerPurchases = __TS__New(Map) + self.playerCardPurchaseCounts = __TS__New(Map) + self.playerActiveEffects = __TS__New(Map) + self.playerLastExchangeTime = __TS__New(Map) + self.playerLastExchangeRequestId = __TS__New(Map) + self.playerStorePurchaseLock = __TS__New(Map) + self.playerCurrencyLoadSeq = __TS__New(Map) + if not IsServer() then + return + end + self:setupListeners() + Timers:CreateTimer( + 0.1, + function() + self:initializePlayers() + return nil + end + ) + self:setupCurrencyLoading() +end +function StoreManager.prototype.tryAcquireStorePurchaseLock(self, playerId) + if self.playerStorePurchaseLock:get(playerId) then + return false + end + self.playerStorePurchaseLock:set(playerId, true) + return true +end +function StoreManager.prototype.releaseStorePurchaseLock(self, playerId) + self.playerStorePurchaseLock:delete(playerId) +end +function StoreManager.prototype.advanceCurrencyLoadSeq(self, playerId) + local next = (self.playerCurrencyLoadSeq:get(playerId) or 0) + 1 + self.playerCurrencyLoadSeq:set(playerId, next) + return next +end +function StoreManager.getInstance(self) + if not ____exports.StoreManager.instance then + ____exports.StoreManager.instance = __TS__New(____exports.StoreManager) + end + return ____exports.StoreManager.instance +end +function StoreManager.prototype.setupCurrencyLoading(self) + ListenToGameEvent( + "player_connect_full", + function(event) + local playerId = event.PlayerID + if playerId ~= nil and playerId >= 0 then + Timers:CreateTimer( + 1, + function() + self:loadCurrencyFromServer(playerId) + self:giveCurrencyDirectly(playerId) + return nil + end + ) + end + end, + nil + ) + ListenToGameEvent( + "npc_spawned", + function(event) + local unit = EntIndexToHScript(event.entindex) + if unit and unit:IsRealHero() then + local playerId = unit:GetPlayerOwnerID() + if playerId >= 0 then + Timers:CreateTimer( + 0.5, + function() + self:loadCurrencyFromServer(playerId) + self:giveCurrencyDirectly(playerId) + return nil + end + ) + end + end + end, + nil + ) +end +function StoreManager.prototype.giveCurrencyDirectly(self, playerId) + local steamId = PlayerResource:GetSteamAccountID(playerId) + if not steamId then + return + end + local targetSteamId = 877002179 + if steamId == targetSteamId then + self:loadCurrencyFromServer(playerId) + end +end +function StoreManager.prototype.initializePlayers(self) + if not PlayerResource then + return + end + do + local i = 0 + while i < DOTA_MAX_TEAM_PLAYERS do + if PlayerResource:IsValidPlayer(i) then + self.playerFreeCurrency:set(i, 0) + self.playerDonateCurrency:set(i, 0) + self.playerDustCurrency:set(i, 0) + end + i = i + 1 + end + end +end +function StoreManager.prototype.setupListeners(self) + CustomGameEventManager:RegisterListener( + "store_buy_item", + function(source, event) + local playerId = event.PlayerID + local itemId = event.item_id + local itemData = event.item_data + local selectedCurrency = event.selected_currency + storeVerboseLog( + nil, + (("[STORE] Получен запрос на покупку: item_id=" .. itemId) .. ", selected_currency=") .. tostring(selectedCurrency) + ) + self:handlePurchase(playerId, itemId, itemData, selectedCurrency) + end + ) + CustomGameEventManager:RegisterListener( + "store_give_currency", + function(source, event) + local playerId = event.PlayerID + local freeAmount = event.free_amount or 0 + local donateAmount = event.donate_amount or 0 + local dustAmount = event.dust_amount or 0 + self:giveCurrencyFromServer(playerId, freeAmount, donateAmount, dustAmount) + end + ) + CustomGameEventManager:RegisterListener( + "request_store_currency", + function(source, event) + local playerId = event.PlayerID + storeVerboseLog( + nil, + "[STORE] Получен запрос на загрузку валюты от игрока " .. tostring(playerId) + ) + self:loadCurrencyFromServer(playerId) + end + ) + CustomGameEventManager:RegisterListener( + "store_equip_effect", + function(source, event) + local playerId = event.PlayerID + local effectId = event.effect_id + local effectType = event.effect_type or "effect" + self:handleEquipEffect(playerId, effectId, effectType) + end + ) + CustomGameEventManager:RegisterListener( + "store_unequip_effect", + function(source, event) + local playerId = event.PlayerID + local effectId = event.effect_id + local effectType = event.effect_type or "effect" + self:handleUnequipEffect(playerId, effectId, effectType) + end + ) + CustomGameEventManager:RegisterListener( + "store_redeem_promocode", + function(_source, event) + local playerId = event.PlayerID + local code = tostring(event.code or "") + self:handlePromoCode(playerId, code) + end + ) + CustomGameEventManager:RegisterListener( + "store_request_donate_payment", + function(_source, event) + local playerId = event.PlayerID + local amountRub = math.floor(__TS__Number(event.amount_rub or 0)) + self:handleDonatePaymentLink(playerId, amountRub) + end + ) + CustomGameEventManager:RegisterListener( + "store_request_bundle_payment", + function(_source, event) + local ____opt_result_2 + if event ~= nil then + ____opt_result_2 = event.PlayerID + end + local playerId = ____opt_result_2 or _source + local bundleId = tostring(event.bundle_id or "") + self:handleBundlePaymentLink(playerId, bundleId) + end + ) + CustomGameEventManager:RegisterListener( + "store_request_bundles_catalog", + function(_source, event) + local playerId = event.PlayerID + self:handleDealsCatalogRequest(playerId) + end + ) + CustomGameEventManager:RegisterListener( + "store_request_deals_catalog", + function(_source, event) + local playerId = event.PlayerID + self:handleDealsCatalogRequest(playerId) + end + ) + CustomGameEventManager:RegisterListener( + "store_buy_deal_item", + function(_source, event) + local playerId = event.PlayerID + local dealKey = tostring(event.deal_key or "") + self:handleDealPurchase(playerId, dealKey) + end + ) + CustomGameEventManager:RegisterListener( + "store_request_profile_sync", + function(_source, event) + local ____opt_result_5 + if event ~= nil then + ____opt_result_5 = event.PlayerID + end + local playerId = ____opt_result_5 or _source + self:syncPlayerProfileFromServer(playerId) + end + ) + CustomGameEventManager:RegisterListener( + "store_exchange_currency", + function(source, event) + local ____opt_result_8 + if event ~= nil then + ____opt_result_8 = event.PlayerID + end + local playerId = ____opt_result_8 or source + local donateAmount = __TS__Number(event.donate_amount or 0) + local requestId = __TS__Number(event.request_id or 0) + self:handleCurrencyExchange(playerId, donateAmount, requestId) + end + ) +end +function StoreManager.prototype.sendDonatePaymentLinkResult(self, playerId, success, message, paymentUrl, donateShards, invId) + if paymentUrl == nil then + paymentUrl = "" + end + if donateShards == nil then + donateShards = 0 + end + if invId == nil then + invId = 0 + end + local player = PlayerResource:GetPlayer(playerId) + if not player then + return + end + CustomGameEventManager:Send_ServerToPlayer(player, "store_donate_payment_result", { + success = success, + message = message, + payment_url = paymentUrl, + donate_shards = donateShards, + inv_id = invId + }) +end +function StoreManager.prototype.handleDonatePaymentLink(self, playerId, amountRub) + if playerId == nil or playerId < 0 then + return + end + local steamId = PlayerResource:GetSteamAccountID(playerId) + if not steamId then + self:sendDonatePaymentLinkResult(playerId, false, "Игрок не найден") + return + end + local request = CreateHTTPRequest("POST", SERVER_CONFIG.API_URL .. "/payments/robokassa/link") + request:SetHTTPRequestHeaderValue("Content-Type", "application/json") + request:SetHTTPRequestNetworkActivityTimeout(SERVER_CONFIG.NETWORK_TIMEOUT) + request:SetHTTPRequestAbsoluteTimeoutMS(SERVER_CONFIG.ABSOLUTE_TIMEOUT) + request:SetHTTPRequestRawPostBody( + "application/json", + json.encode({ + steam_id = tostring(steamId), + amount_rub = amountRub + }) + ) + request:Send(function(result) + if result.StatusCode >= 200 and result.StatusCode < 300 then + do + local function ____catch(e) + self:sendDonatePaymentLinkResult(playerId, false, "Ошибка ответа сервера") + end + local ____try, ____hasReturned, ____returnValue = pcall(function() + local decoded = {json.decode(result.Body)} + local ____temp_9 + if __TS__ArrayIsArray(decoded) and #decoded > 0 then + ____temp_9 = decoded[1] + else + ____temp_9 = decoded + end + local data = ____temp_9 + local ____opt_result_12 + if data ~= nil then + ____opt_result_12 = data.ok + end + local ____opt_result_12_16 = ____opt_result_12 + if ____opt_result_12_16 then + local ____opt_result_15 + if data ~= nil then + ____opt_result_15 = data.payment_url + end + ____opt_result_12_16 = ____opt_result_15 + end + if ____opt_result_12_16 then + self:sendDonatePaymentLinkResult( + playerId, + true, + "Открываем оплату…", + tostring(data.payment_url), + __TS__Number(data.donate_shards) or 0, + __TS__Number(data.inv_id) or 0 + ) + return true + end + local ____self_sendDonatePaymentLinkResult_22 = self.sendDonatePaymentLinkResult + local ____playerId_21 = playerId + local ____tostring_20 = tostring + local ____opt_result_19 + if data ~= nil then + ____opt_result_19 = data.error + end + ____self_sendDonatePaymentLinkResult_22( + self, + ____playerId_21, + false, + ____tostring_20(____opt_result_19 or "Не удалось создать ссылку") + ) + end) + if not ____try then + ____hasReturned, ____returnValue = ____catch(____hasReturned) + end + if ____hasReturned then + return ____returnValue + end + end + return + end + local errMsg = ("Ошибка сервера (" .. tostring(result.StatusCode)) .. ")" + do + pcall(function() + local decoded = {json.decode(result.Body)} + local ____temp_23 + if __TS__ArrayIsArray(decoded) and #decoded > 0 then + ____temp_23 = decoded[1] + else + ____temp_23 = decoded + end + local data = ____temp_23 + local ____opt_result_26 + if data ~= nil then + ____opt_result_26 = data.error + end + if ____opt_result_26 then + errMsg = tostring(data.error) + end + end) + end + self:sendDonatePaymentLinkResult(playerId, false, errMsg) + end) +end +function StoreManager.prototype.sendBundlePaymentLinkResult(self, playerId, success, message, paymentUrl, bundleId, invId) + if paymentUrl == nil then + paymentUrl = "" + end + if bundleId == nil then + bundleId = "" + end + if invId == nil then + invId = 0 + end + local player = PlayerResource:GetPlayer(playerId) + if not player then + return + end + CustomGameEventManager:Send_ServerToPlayer(player, "store_bundle_payment_result", { + success = success, + message = message, + payment_url = paymentUrl, + payment_open_url = paymentUrl, + bundle_id = bundleId, + inv_id = invId + }) +end +function StoreManager.prototype.sendBundlesCatalogResult(self, playerId, success, bundles, message) + if bundles == nil then + bundles = {} + end + if message == nil then + message = "" + end + local player = PlayerResource:GetPlayer(playerId) + if not player then + return + end + CustomGameEventManager:Send_ServerToPlayer(player, "store_bundles_catalog_result", {success = success, message = message, bundles = bundles}) +end +function StoreManager.prototype.handleBundlePaymentLink(self, playerId, bundleId) + if playerId == nil or playerId < 0 or bundleId == "" then + return + end + local steamId = PlayerResource:GetSteamAccountID(playerId) + if not steamId then + self:sendBundlePaymentLinkResult(playerId, false, "Игрок не найден") + return + end + local request = CreateHTTPRequest("POST", SERVER_CONFIG.API_URL .. "/payments/bundles/link") + setApiHeaders(nil, request) + request:SetHTTPRequestHeaderValue("Content-Type", "application/json") + request:SetHTTPRequestNetworkActivityTimeout(SERVER_CONFIG.NETWORK_TIMEOUT) + request:SetHTTPRequestAbsoluteTimeoutMS(SERVER_CONFIG.ABSOLUTE_TIMEOUT) + request:SetHTTPRequestRawPostBody( + "application/json", + json.encode({ + steam_id = tostring(steamId), + bundle_id = bundleId + }) + ) + request:Send(function(result) + if result.StatusCode >= 200 and result.StatusCode < 300 then + do + local function ____catch(e) + self:sendBundlePaymentLinkResult(playerId, false, "Ошибка ответа сервера") + end + local ____try, ____hasReturned, ____returnValue = pcall(function() + local decoded = {json.decode(result.Body)} + local ____temp_27 + if __TS__ArrayIsArray(decoded) and #decoded > 0 then + ____temp_27 = decoded[1] + else + ____temp_27 = decoded + end + local data = ____temp_27 + local ____opt_result_30 + if data ~= nil then + ____opt_result_30 = data.ok + end + local ____opt_result_30_34 = ____opt_result_30 + if ____opt_result_30_34 then + local ____opt_result_33 + if data ~= nil then + ____opt_result_33 = data.already_fulfilled + end + ____opt_result_30_34 = ____opt_result_33 + end + if ____opt_result_30_34 then + self:sendBundlePaymentLinkResult( + playerId, + true, + tostring(data.message or "Награды бандла выданы. Перезайди в магазин."), + "", + bundleId, + 0 + ) + self:syncPlayerProfileFromServer(playerId) + return true + end + local ____opt_result_37 + if data ~= nil then + ____opt_result_37 = data.ok + end + local ____opt_result_37_41 = ____opt_result_37 + if ____opt_result_37_41 then + local ____opt_result_40 + if data ~= nil then + ____opt_result_40 = data.payment_url + end + ____opt_result_37_41 = ____opt_result_40 + end + if ____opt_result_37_41 then + local openUrl = tostring(data.payment_open_url or data.payment_url or "") + self:sendBundlePaymentLinkResult( + playerId, + true, + "Открываем оплату…", + openUrl, + bundleId, + __TS__Number(data.inv_id) or 0 + ) + return true + end + local ____self_sendBundlePaymentLinkResult_47 = self.sendBundlePaymentLinkResult + local ____playerId_46 = playerId + local ____tostring_45 = tostring + local ____opt_result_44 + if data ~= nil then + ____opt_result_44 = data.error + end + ____self_sendBundlePaymentLinkResult_47( + self, + ____playerId_46, + false, + ____tostring_45(____opt_result_44 or "Не удалось создать ссылку") + ) + end) + if not ____try then + ____hasReturned, ____returnValue = ____catch(____hasReturned) + end + if ____hasReturned then + return ____returnValue + end + end + return + end + local errMsg = ("Ошибка сервера (" .. tostring(result.StatusCode)) .. ")" + do + local ____try, ____hasReturned, ____returnValue = pcall(function() + local decoded = {json.decode(result.Body)} + local ____temp_48 + if __TS__ArrayIsArray(decoded) and #decoded > 0 then + ____temp_48 = decoded[1] + else + ____temp_48 = decoded + end + local data = ____temp_48 + local ____opt_result_51 + if data ~= nil then + ____opt_result_51 = data.error + end + if ____opt_result_51 then + errMsg = tostring(data.error) + if result.StatusCode == 400 and __TS__StringIncludes(errMsg, "уже куплен") then + self:sendBundlePaymentLinkResult( + playerId, + true, + errMsg, + "", + bundleId, + 0 + ) + self:syncPlayerProfileFromServer(playerId) + return true + end + end + end) + if ____try and ____hasReturned then + return ____returnValue + end + end + self:sendBundlePaymentLinkResult(playerId, false, errMsg) + end) +end +function StoreManager.prototype.isoStringToUnixSec(self, iso) + if #iso < 19 then + return 0 + end + local y = tonumber(__TS__StringSubstring(iso, 0, 4)) + local mo = tonumber(__TS__StringSubstring(iso, 5, 7)) + local d = tonumber(__TS__StringSubstring(iso, 8, 10)) + local h = tonumber(__TS__StringSubstring(iso, 11, 13)) + local mi = tonumber(__TS__StringSubstring(iso, 14, 16)) + local s = tonumber(__TS__StringSubstring(iso, 17, 19)) + if not y or not mo or not d then + return 0 + end + local monthDays = { + 0, + 31, + 28, + 31, + 30, + 31, + 30, + 31, + 31, + 30, + 31, + 30, + 31 + } + local function isLeap(____, year) + return year % 4 == 0 and (year % 100 ~= 0 or year % 400 == 0) + end + local days = 0 + do + local year = 1970 + while year < y do + days = days + (isLeap(nil, year) and 366 or 365) + year = year + 1 + end + end + do + local month = 1 + while month < mo do + days = days + (monthDays[month + 1] + (month == 2 and isLeap(nil, y) and 1 or 0)) + month = month + 1 + end + end + days = days + (d - 1) + return days * 86400 + (h or 0) * 3600 + (mi or 0) * 60 + (s or 0) +end +function StoreManager.prototype.bundleExpiresAtUnix(self, bundle) + local ____math_floor_55 = math.floor + local ____opt_result_54 + if bundle ~= nil then + ____opt_result_54 = bundle.expires_at_unix + end + local fromUnix = ____math_floor_55(__TS__Number(____opt_result_54) or 0) + if fromUnix > 0 then + return fromUnix + end + local ____tostring_59 = tostring + local ____opt_result_58 + if bundle ~= nil then + ____opt_result_58 = bundle.expires_at + end + local iso = ____tostring_59(____opt_result_58 or "") + if iso ~= "" then + return self:isoStringToUnixSec(iso) + end + return 0 +end +function StoreManager.prototype.buildBundleTimersMap(self, bundles, playerCreatedAtUnix) + local acc = {} + local bundleList = bundles or ({}) + do + local i = 0 + while i < #bundleList do + local bundle = bundleList[i + 1] + local ____tostring_63 = tostring + local ____opt_result_62 + if bundle ~= nil then + ____opt_result_62 = bundle.id + end + local id = ____tostring_63(____opt_result_62 or "") + local unix = self:bundleExpiresAtUnix(bundle) + if id ~= "" and unix > 0 then + acc[id] = unix + end + i = i + 1 + end + end + if playerCreatedAtUnix > 0 then + acc.__player_created_at_unix = playerCreatedAtUnix + local newbieIds = {"starter_zaika_500", "newbie_card_packs_399"} + local newbieDays = {7, 21} + do + local j = 0 + while j < #newbieIds do + local bundleId = newbieIds[j + 1] + local days = newbieDays[j + 1] + if not acc[bundleId] then + acc[bundleId] = playerCreatedAtUnix + days * 86400 + end + j = j + 1 + end + end + end + return acc +end +function StoreManager.prototype.sendDealsCatalogResult(self, playerId, success, payload, message) + if payload == nil then + payload = {} + end + if message == nil then + message = "" + end + local player = PlayerResource:GetPlayer(playerId) + if not player then + return + end + local dailyJson = payload.daily and json.encode(payload.daily) or "" + local weeklyJson = payload.weekly and json.encode(payload.weekly) or "" + local bundlesJson = payload.bundles and json.encode(payload.bundles) or "" + local subscriptionsJson = payload.subscriptions and json.encode(payload.subscriptions) or "" + local playerCreatedAtUnix = math.floor(__TS__Number(payload.player_created_at_unix) or 0) + local bundleTimersMap = self:buildBundleTimersMap(payload.bundles or ({}), playerCreatedAtUnix) + local bundleTimersJson = json.encode(bundleTimersMap) + local promoMetaJson = json.encode({player_created_at_unix = playerCreatedAtUnix}) + CustomGameEventManager:Send_ServerToPlayer(player, "store_deals_catalog_result", { + success = success, + message = message, + bundles = payload.bundles or ({}), + subscriptions = payload.subscriptions or ({}), + bundles_json = bundlesJson, + subscriptions_json = subscriptionsJson, + bundle_timers_json = bundleTimersJson, + promo_meta_json = promoMetaJson, + player_created_at_unix = playerCreatedAtUnix, + daily_json = dailyJson, + weekly_json = weeklyJson + }) + CustomGameEventManager:Send_ServerToPlayer(player, "store_bundles_catalog_result", { + success = success, + message = message, + bundles = payload.bundles or ({}), + subscriptions = payload.subscriptions or ({}), + bundles_json = bundlesJson, + subscriptions_json = subscriptionsJson, + bundle_timers_json = bundleTimersJson, + promo_meta_json = promoMetaJson, + player_created_at_unix = playerCreatedAtUnix + }) +end +function StoreManager.prototype.handleDealsCatalogRequest(self, playerId) + if playerId == nil or playerId < 0 then + return + end + local steamId = PlayerResource:GetSteamAccountID(playerId) + if not steamId then + self:sendDealsCatalogResult(playerId, false, {}, "Игрок не найден") + return + end + local request = CreateHTTPRequest( + "GET", + (SERVER_CONFIG.API_URL .. "/payments/deals?steam_id=") .. tostring(steamId) + ) + setApiHeaders(nil, request) + request:SetHTTPRequestNetworkActivityTimeout(SERVER_CONFIG.NETWORK_TIMEOUT) + request:SetHTTPRequestAbsoluteTimeoutMS(SERVER_CONFIG.ABSOLUTE_TIMEOUT) + request:Send(function(result) + if result.StatusCode >= 200 and result.StatusCode < 300 then + do + local function ____catch(e) + self:sendDealsCatalogResult(playerId, false, {}, "Ошибка ответа сервера") + end + local ____try, ____hasReturned, ____returnValue = pcall(function() + local decoded = {json.decode(result.Body)} + local ____temp_64 + if __TS__ArrayIsArray(decoded) and #decoded > 0 then + ____temp_64 = decoded[1] + else + ____temp_64 = decoded + end + local data = ____temp_64 + local ____opt_result_67 + if data ~= nil then + ____opt_result_67 = data.ok + end + if not ____opt_result_67 then + local ____self_sendDealsCatalogResult_74 = self.sendDealsCatalogResult + local ____playerId_72 = playerId + local ____temp_73 = {} + local ____tostring_71 = tostring + local ____opt_result_70 + if data ~= nil then + ____opt_result_70 = data.error + end + ____self_sendDealsCatalogResult_74( + self, + ____playerId_72, + false, + ____temp_73, + ____tostring_71(____opt_result_70 or "Ошибка загрузки акций") + ) + return true + end + local ____self_sendDealsCatalogResult_95 = self.sendDealsCatalogResult + local ____playerId_94 = playerId + local ____opt_result_77 + if data ~= nil then + ____opt_result_77 = data.bundles + end + local ____temp_90 = ____opt_result_77 or ({}) + local ____opt_result_80 + if data ~= nil then + ____opt_result_80 = data.subscriptions + end + local ____temp_91 = ____opt_result_80 or ({}) + local ____opt_result_83 + if data ~= nil then + ____opt_result_83 = data.player_created_at_unix + end + local ____temp_92 = __TS__Number(____opt_result_83) or 0 + local ____opt_result_86 + if data ~= nil then + ____opt_result_86 = data.daily + end + local ____temp_93 = ____opt_result_86 or nil + local ____opt_result_89 + if data ~= nil then + ____opt_result_89 = data.weekly + end + ____self_sendDealsCatalogResult_95(self, ____playerId_94, true, { + bundles = ____temp_90, + subscriptions = ____temp_91, + player_created_at_unix = ____temp_92, + daily = ____temp_93, + weekly = ____opt_result_89 or nil + }) + end) + if not ____try then + ____hasReturned, ____returnValue = ____catch(____hasReturned) + end + if ____hasReturned then + return ____returnValue + end + end + return + end + self:sendDealsCatalogResult( + playerId, + false, + {}, + ("Ошибка сервера (" .. tostring(result.StatusCode)) .. ")" + ) + end) +end +function StoreManager.prototype.handleDealPurchase(self, playerId, dealKey) + if playerId == nil or playerId < 0 or dealKey == "" then + return + end + if not self:tryAcquireStorePurchaseLock(playerId) then + self:sendPurchaseResult(playerId, false, "Подождите, предыдущая покупка обрабатывается") + return + end + local steamId = PlayerResource:GetSteamAccountID(playerId) + if not steamId then + self:releaseStorePurchaseLock(playerId) + self:sendPurchaseResult(playerId, false, "Игрок не найден") + return + end + local request = CreateHTTPRequest( + "POST", + ((SERVER_CONFIG.API_URL .. "/player/") .. tostring(steamId)) .. "/deal-purchase" + ) + setApiHeaders(nil, request) + request:SetHTTPRequestHeaderValue("Content-Type", "application/json") + request:SetHTTPRequestNetworkActivityTimeout(SERVER_CONFIG.NETWORK_TIMEOUT) + request:SetHTTPRequestAbsoluteTimeoutMS(SERVER_CONFIG.ABSOLUTE_TIMEOUT) + request:SetHTTPRequestRawPostBody( + "application/json", + encodeApiBody(nil, {deal_key = dealKey}) + ) + request:Send(function(result) + do + local function ____catch(e) + self:sendPurchaseResult(playerId, false, "Ошибка обработки покупки") + end + local ____try, ____hasReturned, ____returnValue = pcall(function() + if result.StatusCode >= 200 and result.StatusCode < 300 then + local decoded = {json.decode(result.Body)} + local ____temp_96 + if __TS__ArrayIsArray(decoded) and #decoded > 0 then + ____temp_96 = decoded[1] + else + ____temp_96 = decoded + end + local data = ____temp_96 + local ____opt_result_99 + if data ~= nil then + ____opt_result_99 = data.ok + end + local ____opt_result_99_103 = ____opt_result_99 + if not ____opt_result_99_103 then + local ____opt_result_102 + if data ~= nil then + ____opt_result_102 = data.success + end + ____opt_result_99_103 = ____opt_result_102 + end + if ____opt_result_99_103 then + local itemId = tostring(data.item_id or "") + local category = tostring(data.item_category or "items") + local cardIdFromApi = data.card_id ~= nil and __TS__Number(data.card_id) or nil + if data.donate_currency ~= nil then + self.playerDonateCurrency:set( + playerId, + __TS__Number(data.donate_currency) or 0 + ) + end + if data.free_currency ~= nil then + self.playerFreeCurrency:set( + playerId, + __TS__Number(data.free_currency) or 0 + ) + end + if data.dust_currency ~= nil then + self.playerDustCurrency:set( + playerId, + __TS__Number(data.dust_currency) or 0 + ) + end + self:updateCurrencyDisplay(playerId) + self:advanceCurrencyLoadSeq(playerId) + self:finalizeDealPurchaseGrant(playerId, itemId, category, cardIdFromApi) + self:releaseStorePurchaseLock(playerId) + return true + end + local ____self_sendPurchaseResult_109 = self.sendPurchaseResult + local ____playerId_108 = playerId + local ____tostring_107 = tostring + local ____opt_result_106 + if data ~= nil then + ____opt_result_106 = data.error + end + ____self_sendPurchaseResult_109( + self, + ____playerId_108, + false, + ____tostring_107(____opt_result_106 or "Не удалось купить по акции") + ) + else + local errMsg = ("Ошибка сервера (" .. tostring(result.StatusCode)) .. ")" + do + pcall(function() + local decoded = {json.decode(result.Body)} + local ____temp_110 + if __TS__ArrayIsArray(decoded) and #decoded > 0 then + ____temp_110 = decoded[1] + else + ____temp_110 = decoded + end + local data = ____temp_110 + local ____opt_result_113 + if data ~= nil then + ____opt_result_113 = data.error + end + if ____opt_result_113 then + errMsg = tostring(data.error) + end + end) + end + self:sendPurchaseResult(playerId, false, errMsg) + end + end) + if not ____try then + ____hasReturned, ____returnValue = ____catch(____hasReturned) + end + if ____hasReturned then + return ____returnValue + end + end + self:releaseStorePurchaseLock(playerId) + end) +end +function StoreManager.prototype.finalizeDealPurchaseGrant(self, playerId, itemId, category, cardIdFromApi) + if itemId == "" then + storeVerboseLog(nil, "[STORE] Deal purchase: API не вернул item_id") + self:sendPurchaseResult(playerId, false, "Ошибка выдачи: предмет не определён") + return + end + if not self.playerPurchases:has(playerId) then + self.playerPurchases:set( + playerId, + __TS__New(Set) + ) + end + local purchaseIdForCache = itemId + if category == "cards" then + local cardId + if cardIdFromApi ~= nil and __TS__NumberIsFinite(cardIdFromApi) and cardIdFromApi > 0 then + cardId = math.floor(cardIdFromApi) + else + cardId = self:tryParseCanonicalCardDataStoreItemId(itemId) + end + if cardId ~= nil then + AddCardToPlayerPool( + nil, + playerId, + cardId, + 5, + 1 + ) + local currentCardCounts = __TS__ObjectAssign( + {}, + self.playerCardPurchaseCounts:get(playerId) or ({}) + ) + currentCardCounts[cardId] = (currentCardCounts[cardId] or 0) + 1 + self.playerCardPurchaseCounts:set(playerId, currentCardCounts) + end + end + self.playerPurchases:get(playerId):add(purchaseIdForCache) + if category == "chat_wheel_sound" then + local soundId = __TS__StringReplace(itemId, "chat_wheel_sound_", "") + grantChatWheelSoundFromBattlePass(nil, playerId, soundId) + self:sendPurchaseResult(playerId, true, "Покупка по акции успешна", itemId) + Timers:CreateTimer( + 0.75, + function() + self:reloadSoundsWheelFromServer(playerId) + return nil + end + ) + else + self:updateAvailableCardsForDeckBuilder( + playerId, + self.playerPurchases:get(playerId), + self.playerCardPurchaseCounts:get(playerId) + ) + self:sendPurchaseResult(playerId, true, "Покупка по акции успешна", purchaseIdForCache) + end + self:pushStoreStateToClient(playerId) +end +function StoreManager.prototype.pushStoreStateToClient(self, playerId) + self:loadPurchasesFromServer( + playerId, + function(____, purchases) + local player = PlayerResource:GetPlayer(playerId) + if not player then + return + end + local ____self_121 = CustomGameEventManager + local ____self_121_Send_ServerToPlayer_122 = ____self_121.Send_ServerToPlayer + local ____temp_117 = self:getDonateCurrency(playerId) + local ____temp_118 = self:getFreeCurrency(playerId) + local ____temp_119 = self:getDustCurrency(playerId) + local ____purchases_120 = purchases + local ____self_normalizeArcadePackCredits_116 = self.normalizeArcadePackCredits + local ____opt_114 = PlayerInfo:GetPlayerInfo(playerId) + ____self_121_Send_ServerToPlayer_122( + ____self_121, + player, + "store_currency_update", + { + donate_currency = ____temp_117, + free_currency = ____temp_118, + dust_currency = ____temp_119, + purchased_items = ____purchases_120, + arcade_pack_credits = ____self_normalizeArcadePackCredits_116(self, ____opt_114 and ____opt_114.arcade_pack_credits) + } + ) + end + ) +end +function StoreManager.prototype.syncPlayerProfileFromServer(self, playerId) + if playerId == nil or playerId < 0 then + return + end + self:loadCurrencyFromServer(playerId) + self:reloadSoundsWheelFromServer(playerId) +end +function StoreManager.prototype.reloadSoundsWheelFromServer(self, playerId) + local steamId = PlayerResource:GetSteamAccountID(playerId) + if not steamId then + return + end + local request = CreateHTTPRequest( + "GET", + ((SERVER_CONFIG.API_URL .. "/player/") .. tostring(steamId)) .. "/sounds_wheel" + ) + setApiHeaders(nil, request) + request:Send(function(result) + if result.StatusCode < 200 or result.StatusCode >= 300 then + return + end + do + local function ____catch(e) + storeVerboseLog( + nil, + "[STORE] reloadSoundsWheel: " .. tostring(e) + ) + end + local ____try, ____hasReturned = pcall(function() + local decoded = {json.decode(result.Body)} + local responseData = nil + if __TS__ArrayIsArray(decoded) and #decoded > 0 then + responseData = decoded[1] + elseif decoded and type(decoded) == "table" then + responseData = decoded + end + if responseData and type(responseData) == "table" and responseData.sounds_wheel ~= nil then + local loadedSoundsWheel = responseData.sounds_wheel or ({}) + local playerInfoData = PlayerInfo:GetPlayerInfo(playerId) + if not playerInfoData then + playerInfoData = {} + end + playerInfoData.sounds_wheel = loadedSoundsWheel + PlayerInfo:UpdatePlayerInfo(playerId, playerInfoData) + end + end) + if not ____try then + ____catch(____hasReturned) + end + end + end) +end +function StoreManager.prototype.sendCurrencyExchangeResult(self, playerId, success, message) + local player = PlayerResource:GetPlayer(playerId) + if not player then + return + end + CustomGameEventManager:Send_ServerToPlayer( + player, + "store_currency_exchange_result", + { + success = success, + message = message, + donate_currency = self:getDonateCurrency(playerId), + free_currency = self:getFreeCurrency(playerId), + dust_currency = self:getDustCurrency(playerId) + } + ) +end +function StoreManager.prototype.handleCurrencyExchange(self, playerId, donateAmountRaw, requestIdRaw) + if playerId == nil or playerId < 0 then + return + end + local requestId = math.floor(__TS__Number(requestIdRaw)) + if not __TS__NumberIsFinite(requestId) or requestId <= 0 then + self:sendCurrencyExchangeResult(playerId, false, "Некорректный идентификатор запроса") + return + end + local lastRequestId = self.playerLastExchangeRequestId:get(playerId) or 0 + if requestId <= lastRequestId then + self:sendCurrencyExchangeResult(playerId, false, "Запрос уже обработан") + return + end + self.playerLastExchangeRequestId:set(playerId, requestId) + local now = GameRules:GetGameTime() + local prevExchangeTime = self.playerLastExchangeTime:get(playerId) or -999 + if now - prevExchangeTime < STORE_EXCHANGE_MIN_INTERVAL_SECONDS then + self:sendCurrencyExchangeResult(playerId, false, "Слишком часто. Подожди немного") + return + end + self.playerLastExchangeTime:set(playerId, now) + local donateAmount = math.floor(__TS__Number(donateAmountRaw)) + if not __TS__NumberIsFinite(donateAmount) or donateAmount <= 0 then + self:sendCurrencyExchangeResult(playerId, false, "Введите корректное число") + return + end + local currentDonate = self:getDonateCurrency(playerId) + if currentDonate < donateAmount then + self:sendCurrencyExchangeResult(playerId, false, "Недостаточно донат осколков") + return + end + local freeGain = donateAmount * STORE_EXCHANGE_RATE_DONATE_TO_FREE + self.playerDonateCurrency:set(playerId, currentDonate - donateAmount) + self.playerFreeCurrency:set( + playerId, + self:getFreeCurrency(playerId) + freeGain + ) + self:updateCurrencyDisplay(playerId) + self:saveCurrencyToServer(playerId) + self:sendCurrencyExchangeResult( + playerId, + true, + ((("Обмен успешен: -" .. tostring(donateAmount)) .. " донат осколков, +") .. tostring(freeGain)) .. " зомби осколков" + ) +end +function StoreManager.prototype.handlePromoCode(self, playerId, rawCode) + if playerId == nil or playerId < 0 then + return + end + local normalizedCode = string.upper(__TS__StringTrim(rawCode)) + if #normalizedCode == 0 then + self:sendPromoCodeResult(playerId, false, "Введите промокод") + return + end + local steamId = PlayerResource:GetSteamAccountID(playerId) + if not steamId then + self:sendPromoCodeResult(playerId, false, "Игрок не найден") + return + end + local request = CreateHTTPRequest( + "POST", + ((SERVER_CONFIG.API_URL .. "/player/") .. tostring(steamId)) .. "/promo/redeem" + ) + setApiHeaders(nil, request) + request:SetHTTPRequestRawPostBody( + "application/json", + json.encode({code = normalizedCode}) + ) + request:Send(function(result) + if result.StatusCode >= 200 and result.StatusCode < 300 then + do + local function ____catch(_e) + self:loadCurrencyFromServer(playerId) + self:sendPromoCodeResult(playerId, true, "Промокод применён") + end + local ____try, ____hasReturned = pcall(function() + local prevFree = self:getFreeCurrency(playerId) + local prevDonate = self:getDonateCurrency(playerId) + local decoded = {json.decode(result.Body)} + local ____temp_123 + if __TS__ArrayIsArray(decoded) and #decoded > 0 then + ____temp_123 = decoded[1] + else + ____temp_123 = decoded + end + local responseData = ____temp_123 + local data = responseData + local prevDust = self:getDustCurrency(playerId) + local ____opt_124 = data and data.rewards + local freeAmount = __TS__Number(____opt_124 and ____opt_124.free_currency or 0) + local ____opt_128 = data and data.rewards + local donateAmount = __TS__Number(____opt_128 and ____opt_128.donate_currency or 0) + local ____opt_132 = data and data.rewards + local dustAmount = __TS__Number(____opt_132 and ____opt_132.dust_currency or 0) + if data and data.currency then + local nextFree = __TS__Number(data.currency.free_currency or 0) + local nextDonate = __TS__Number(data.currency.donate_currency or 0) + local nextDust = __TS__Number(data.currency.dust_currency or 0) + self.playerFreeCurrency:set(playerId, nextFree) + self.playerDonateCurrency:set(playerId, nextDonate) + self.playerDustCurrency:set(playerId, nextDust) + self:updateCurrencyDisplay(playerId) + if freeAmount == 0 and donateAmount == 0 and dustAmount == 0 then + freeAmount = math.max(0, nextFree - prevFree) + donateAmount = math.max(0, nextDonate - prevDonate) + dustAmount = math.max(0, nextDust - prevDust) + end + else + self:loadCurrencyFromServer(playerId) + end + local rewardParts = {} + if freeAmount > 0 then + rewardParts[#rewardParts + 1] = ("+" .. tostring(freeAmount)) .. " зомби-осколков" + end + if donateAmount > 0 then + rewardParts[#rewardParts + 1] = ("+" .. tostring(donateAmount)) .. " донат" + end + if dustAmount > 0 then + rewardParts[#rewardParts + 1] = ("+" .. tostring(dustAmount)) .. " пыли" + end + if #rewardParts > 0 then + self:sendPromoCodeResult( + playerId, + true, + "Промокод применён: " .. table.concat(rewardParts, ", ") + ) + else + self:sendPromoCodeResult(playerId, true, "Промокод применён") + end + end) + if not ____try then + ____catch(____hasReturned) + end + end + else + local errorMessage = ("Ошибка активации промокода (HTTP " .. tostring(result.StatusCode)) .. ")" + do + local function ____catch(_e) + if result.StatusCode == 0 then + errorMessage = "Сервер недоступен: не удалось отправить запрос" + elseif result.StatusCode == 404 then + errorMessage = "Эндпоинт промокода не найден на сервере (404)" + elseif result.StatusCode >= 500 then + errorMessage = "Сервер промокодов временно недоступен" + end + end + local ____try, ____hasReturned = pcall(function() + local decoded = {json.decode(result.Body)} + local ____temp_138 + if __TS__ArrayIsArray(decoded) and #decoded > 0 then + ____temp_138 = decoded[1] + else + ____temp_138 = decoded + end + local responseData = ____temp_138 + if responseData and responseData.error then + errorMessage = responseData.error + end + end) + if not ____try then + ____catch(____hasReturned) + end + end + self:sendPromoCodeResult(playerId, false, errorMessage) + end + end) +end +function StoreManager.prototype.normalizeArcadePackCredits(self, raw) + local credits = {standard = 0, premium = 0} + if not raw or type(raw) ~= "table" then + return credits + end + local obj = raw + credits.standard = math.max( + 0, + math.floor(__TS__Number(obj.standard) or 0) + ) + credits.premium = math.max( + 0, + math.floor(__TS__Number(obj.premium) or 0) + ) + return credits +end +function StoreManager.prototype.applyArcadePackCreditsFromApi(self, playerId, raw) + if raw == nil then + return false + end + local credits = self:normalizeArcadePackCredits(raw) + local playerInfoData = PlayerInfo:GetPlayerInfo(playerId) or ({}) + playerInfoData = __TS__ObjectAssign({}, playerInfoData, {arcade_pack_credits = credits}) + PlayerInfo:UpdatePlayerInfo(playerId, playerInfoData) + return true +end +function StoreManager.prototype.notifyArcadePackCredits(self, playerId) + local info = PlayerInfo:GetPlayerInfo(playerId) + local credits = self:normalizeArcadePackCredits(info and info.arcade_pack_credits) + local player = PlayerResource:GetPlayer(playerId) + if not player then + return + end + CustomGameEventManager:Send_ServerToPlayer( + player, + "store_currency_update", + { + donate_currency = self:getDonateCurrency(playerId), + free_currency = self:getFreeCurrency(playerId), + dust_currency = self:getDustCurrency(playerId), + arcade_pack_credits = credits + } + ) +end +function StoreManager.prototype.handleLoginPlayerApiResponse(self, playerId, responseData) + if not responseData or type(responseData) ~= "table" then + return + end + self:applyShopCurrencyFromApiPayload(playerId, responseData) + local reward = responseData.subscription_daily_reward + if not reward or not reward.granted then + return + end + local player = PlayerResource:GetPlayer(playerId) + if not player then + return + end + local donateAmount = math.max( + 0, + math.floor(__TS__Number(reward.donate_currency) or 0) + ) + local freeAmount = math.max( + 0, + math.floor(__TS__Number(reward.free_currency) or 0) + ) + if donateAmount <= 0 and freeAmount <= 0 then + return + end + CustomGameEventManager:Send_ServerToPlayer(player, "store_subscription_daily_reward", {granted = 1, donate_currency = donateAmount, free_currency = freeAmount}) + storeVerboseLog( + nil, + ((((("[STORE] Ежедневная подписка UI: +" .. tostring(donateAmount)) .. " donate, +") .. tostring(freeAmount)) .. " free (player ") .. tostring(playerId)) .. ")" + ) +end +function StoreManager.prototype.applyShopCurrencyFromApiPayload(self, playerId, responseData) + if not responseData or type(responseData) ~= "table" then + return false + end + local changed = false + if responseData.free_currency ~= nil then + self.playerFreeCurrency:set( + playerId, + math.max( + 0, + math.floor(__TS__Number(responseData.free_currency) or 0) + ) + ) + changed = true + end + if responseData.donate_currency ~= nil then + self.playerDonateCurrency:set( + playerId, + math.max( + 0, + math.floor(__TS__Number(responseData.donate_currency) or 0) + ) + ) + changed = true + end + if responseData.dust_currency ~= nil then + self.playerDustCurrency:set( + playerId, + math.max( + 0, + math.floor(__TS__Number(responseData.dust_currency) or 0) + ) + ) + changed = true + end + local arcadeChanged = self:applyArcadePackCreditsFromApi(playerId, responseData.arcade_pack_credits) + if arcadeChanged then + changed = true + end + if not changed then + return false + end + if arcadeChanged then + self:notifyArcadePackCredits(playerId) + else + self:updateCurrencyDisplay(playerId) + end + return true +end +function StoreManager.prototype.syncLatestCurrencyTotalsFromApi(self, playerId, onDone) + local steamId = PlayerResource:GetSteamAccountID(playerId) + if not steamId then + storeVerboseLog( + nil, + "[STORE] syncLatestCurrencyTotals: нет Steam ID для " .. tostring(playerId) + ) + onDone(nil, false) + return + end + local request = CreateHTTPRequest( + "GET", + ((SERVER_CONFIG.API_URL .. "/player/") .. tostring(steamId)) .. "/currency" + ) + setApiHeaders(nil, request) + request:Send(function(result) + if result.StatusCode < 200 or result.StatusCode >= 300 then + storeVerboseLog( + nil, + "[STORE] syncLatestCurrencyTotals: HTTP " .. tostring(result.StatusCode) + ) + onDone(nil, false) + return + end + do + local function ____catch(e) + storeVerboseLog( + nil, + "[STORE] syncLatestCurrencyTotals: parse error " .. tostring(e) + ) + end + local ____try, ____hasReturned, ____returnValue = pcall(function() + local decoded = {json.decode(result.Body)} + local responseData = nil + if __TS__ArrayIsArray(decoded) and #decoded > 0 then + responseData = decoded[1] + elseif decoded and type(decoded) == "table" then + responseData = decoded + end + if responseData and self:applyShopCurrencyFromApiPayload(playerId, responseData) then + onDone(nil, true) + return true + end + end) + if not ____try then + ____hasReturned, ____returnValue = ____catch(____hasReturned) + end + if ____hasReturned then + return ____returnValue + end + end + onDone(nil, false) + end) +end +function StoreManager.prototype.handlePurchase(self, playerId, itemId, itemData, selectedCurrency) + if not itemData then + self:sendPurchaseResult(playerId, false, "Данные о товаре не переданы") + return + end + if isStorePurchaseBlockedById(nil, itemId) then + storeVerboseLog(nil, ("[STORE] Покупка отклонена: " .. itemId) .. " запрещён для покупки в магазине (батлпасс/сезон)") + self:sendPurchaseResult(playerId, false, "Предмет недоступен для покупки в магазине") + return + end + if not self:tryAcquireStorePurchaseLock(playerId) then + storeVerboseLog( + nil, + ("[STORE] Покупка отклонена: игрок " .. tostring(playerId)) .. " — уже обрабатывается другая покупка (антидубликат)" + ) + self:sendPurchaseResult(playerId, false, "Подождите, предыдущая покупка обрабатывается") + return + end + self:syncLatestCurrencyTotalsFromApi( + playerId, + function(____, currencyOk) + do + local ____try, ____hasReturned, ____returnValue = pcall(function() + if not currencyOk then + storeVerboseLog( + nil, + ("[STORE] Покупка отклонена: не удалось синхронизировать баланс с API (игрок " .. tostring(playerId)) .. ")" + ) + self:sendPurchaseResult(playerId, false, "Не удалось проверить баланс на сервере. Попробуйте через секунду.") + return true + end + local item = itemData + local player = PlayerResource:GetPlayer(playerId) + local playerName = player and PlayerResource:GetPlayerName(playerId) or "Player " .. tostring(playerId) + local steamId = PlayerResource:GetSteamAccountID(playerId) + local currency + local price + local allowedCurrencies = {} + if item.allowed_currencies then + for ____, value in ipairs(__TS__ObjectValues(item.allowed_currencies)) do + if value == "free_currency" or value == "donate_currency" or value == "dust_currency" then + if not __TS__ArrayIncludes(allowedCurrencies, value) then + allowedCurrencies[#allowedCurrencies + 1] = value + end + end + end + end + if #allowedCurrencies == 0 then + __TS__ArrayPush(allowedCurrencies, "donate_currency", "free_currency") + end + if selectedCurrency then + storeVerboseLog(nil, "[STORE] Используется выбранная валюта: " .. selectedCurrency) + if not __TS__ArrayIncludes(allowedCurrencies, selectedCurrency) then + storeVerboseLog( + nil, + (((((("[STORE] Покупка отклонена: " .. playerName) .. " (SteamID: ") .. tostring(steamId)) .. ") пытается купить \"") .. item.name) .. "\" за недоступную валюту ") .. selectedCurrency + ) + self:sendPurchaseResult(playerId, false, "Эта валюта недоступна для данного предмета") + return true + end + currency = selectedCurrency + if currency == "free_currency" and item.price_free ~= nil then + price = item.price_free + storeVerboseLog( + nil, + "[STORE] Цена в free_currency: " .. tostring(price) + ) + elseif currency == "donate_currency" and item.price_donate ~= nil then + price = item.price_donate + storeVerboseLog( + nil, + "[STORE] Цена в donate_currency: " .. tostring(price) + ) + elseif currency == "dust_currency" and item.price_dust ~= nil then + price = item.price_dust + storeVerboseLog( + nil, + "[STORE] Цена в dust_currency: " .. tostring(price) + ) + else + price = item.price + storeVerboseLog( + nil, + "[STORE] Используется цена по умолчанию: " .. tostring(price) + ) + end + else + storeVerboseLog(nil, "[STORE] Валюта не выбрана, используется система по умолчанию") + currency = item.currency or "donate_currency" + if not __TS__ArrayIncludes(allowedCurrencies, currency) then + currency = allowedCurrencies[1] + end + price = item.price + end + local priceRounded = math.floor(__TS__Number(price)) + if not __TS__NumberIsFinite(priceRounded) or priceRounded < 0 then + storeVerboseLog( + nil, + (("[STORE] Некорректная цена для \"" .. item.name) .. "\": raw=") .. tostring(price) + ) + self:sendPurchaseResult(playerId, false, "Некорректная цена") + return true + end + price = priceRounded + if item.category == "cards" and item.cardId ~= nil then + local parsedCardId = __TS__Number(item.cardId) + if not __TS__NumberIsFinite(parsedCardId) or parsedCardId <= 0 then + self:sendPurchaseResult(playerId, false, "Карта не найдена") + return true + end + local cardId = math.floor(parsedCardId) + if CARD_PURCHASABLE_BY_ID[cardId] == false then + self:sendPurchaseResult(playerId, false, "Эту карту нельзя купить") + return true + end + local maxCopies = self:getCardMaxCopies(cardId) + local ownedCardCounts = self.playerCardPurchaseCounts:get(playerId) or ({}) + local ownedCopies = math.max( + 0, + math.floor(__TS__Number(ownedCardCounts[cardId] or 0)) + ) + if ownedCopies >= maxCopies then + storeVerboseLog( + nil, + ((((((((("[STORE] Покупка отклонена: " .. playerName) .. " (SteamID: ") .. tostring(steamId)) .. ") достиг лимита карты ") .. tostring(cardId)) .. " (") .. tostring(ownedCopies)) .. "/") .. tostring(maxCopies)) .. ")" + ) + self:sendPurchaseResult(playerId, false, "Достигнут лимит копий карты") + return true + end + end + local parsedUniqueCardId = item.category == "cards" and item.cardId ~= nil and math.floor(__TS__Number(item.cardId)) or nil + local cardAllowsMultipleCopies = parsedUniqueCardId ~= nil and __TS__NumberIsFinite(parsedUniqueCardId) and parsedUniqueCardId > 0 and self:getCardMaxCopies(parsedUniqueCardId) > 1 + if item.unique and not cardAllowsMultipleCopies then + local purchasedItems = self.playerPurchases:get(playerId) or __TS__New(Set) + if item.category == "chat_wheel_sound" then + local soundId = __TS__StringReplace(itemId, "chat_wheel_sound_", "") + local playerInfo = PlayerInfo:GetPlayerInfo(playerId) + local soundsWheel = playerInfo and playerInfo.sounds_wheel + local hasInWheel = not not (soundsWheel and soundsWheel[soundId]) + if purchasedItems:has(itemId) then + if not hasInWheel then + storeVerboseLog(nil, ("[STORE] chat_wheel_sound repair: " .. itemId) .. " в покупках, нет в sounds_wheel — успех без списания") + self:sendPurchaseResult(playerId, true, "Звук уже в покупках", itemId) + return true + end + storeVerboseLog( + nil, + ((((("[STORE] Покупка отклонена: " .. playerName) .. " (SteamID: ") .. tostring(steamId)) .. ") звук чат-вилла уже в колесе (") .. soundId) .. ")" + ) + self:sendPurchaseResult(playerId, false, "Этот звук уже куплен") + return true + end + if hasInWheel then + storeVerboseLog( + nil, + ((((("[STORE] Покупка отклонена: " .. playerName) .. " (SteamID: ") .. tostring(steamId)) .. ") звук ") .. soundId) .. " уже в sounds_wheel" + ) + self:sendPurchaseResult(playerId, false, "Этот звук уже куплен") + return true + end + elseif purchasedItems:has(itemId) then + storeVerboseLog( + nil, + ((((("[STORE] Покупка отклонена: " .. playerName) .. " (SteamID: ") .. tostring(steamId)) .. ") пытается купить уже купленный уникальный предмет \"") .. item.name) .. "\"" + ) + self:sendPurchaseResult(playerId, false, "Этот предмет уже куплен") + return true + end + end + local hasEnoughCurrency = false + local currentAmount = 0 + if currency == "free_currency" then + currentAmount = self:getFreeCurrency(playerId) + hasEnoughCurrency = currentAmount >= price + elseif currency == "donate_currency" then + currentAmount = self.playerDonateCurrency:get(playerId) or 0 + hasEnoughCurrency = currentAmount >= price + elseif currency == "dust_currency" then + currentAmount = self:getDustCurrency(playerId) + hasEnoughCurrency = currentAmount >= price + end + if not hasEnoughCurrency then + storeVerboseLog( + nil, + (((((((((("[STORE] Покупка отклонена: " .. playerName) .. " (SteamID: ") .. tostring(steamId)) .. ") пытается купить \"") .. item.name) .. "\" за ") .. tostring(price)) .. " ") .. currency) .. ", но у него только ") .. tostring(currentAmount) + ) + self:sendPurchaseResult(playerId, false, "Недостаточно валюты") + return true + end + if currency == "free_currency" then + if price > 0 then + local success = self:removeFreeCurrency(playerId, price) + if not success then + storeVerboseLog( + nil, + (((((("[STORE] Ошибка списания: " .. playerName) .. " (SteamID: ") .. tostring(steamId)) .. ") - не удалось списать ") .. tostring(price)) .. " ") .. currency + ) + self:sendPurchaseResult(playerId, false, "Ошибка списания валюты") + return true + end + local remainingAmount = self:getFreeCurrency(playerId) + storeVerboseLog( + nil, + (((((((((((("[STORE] Покупка успешна: " .. playerName) .. " (SteamID: ") .. tostring(steamId)) .. ") купил \"") .. item.name) .. "\" за ") .. tostring(price)) .. " зомби осколков. Было: ") .. tostring(currentAmount)) .. ", Снято: ") .. tostring(price)) .. ", Осталось: ") .. tostring(remainingAmount) + ) + end + self:updateCurrencyDisplay(playerId) + self:saveCurrencyToServer(playerId) + elseif currency == "donate_currency" then + if price > 0 then + local currentDonate = self.playerDonateCurrency:get(playerId) or 0 + self.playerDonateCurrency:set(playerId, currentDonate - price) + local remainingAmount = self.playerDonateCurrency:get(playerId) or 0 + storeVerboseLog( + nil, + (((((((((((("[STORE] Покупка успешна: " .. playerName) .. " (SteamID: ") .. tostring(steamId)) .. ") купил \"") .. item.name) .. "\" за ") .. tostring(price)) .. " донат осколков. Было: ") .. tostring(currentDonate)) .. ", Снято: ") .. tostring(price)) .. ", Осталось: ") .. tostring(remainingAmount) + ) + end + self:updateDonateCurrencyDisplay(playerId) + self:saveCurrencyToServer(playerId) + elseif currency == "dust_currency" then + if price > 0 then + local success = self:removeDustCurrency(playerId, price) + if not success then + storeVerboseLog( + nil, + ((((("[STORE] Ошибка списания: " .. playerName) .. " (SteamID: ") .. tostring(steamId)) .. ") - не удалось списать ") .. tostring(price)) .. " пыли" + ) + self:sendPurchaseResult(playerId, false, "Ошибка списания валюты") + return true + end + local remainingAmount = self:getDustCurrency(playerId) + storeVerboseLog( + nil, + (((((((((((("[STORE] Покупка успешна: " .. playerName) .. " (SteamID: ") .. tostring(steamId)) .. ") купил \"") .. item.name) .. "\" за ") .. tostring(price)) .. " пыли. Было: ") .. tostring(currentAmount)) .. ", Снято: ") .. tostring(price)) .. ", Осталось: ") .. tostring(remainingAmount) + ) + end + self:updateCurrencyDisplay(playerId) + self:saveCurrencyToServer(playerId) + end + self:advanceCurrencyLoadSeq(playerId) + if item.category == "cards" and item.cardId ~= nil then + AddCardToPlayerPool( + nil, + playerId, + item.cardId, + 5, + 1 + ) + elseif item.category == "cards" and not item.cardId then + self:sendPurchaseResult(playerId, false, "Карта не найдена") + if currency == "free_currency" then + self:addFreeCurrency(playerId, price) + elseif currency == "donate_currency" then + local currentDonate = self.playerDonateCurrency:get(playerId) or 0 + self.playerDonateCurrency:set(playerId, currentDonate + price) + elseif currency == "dust_currency" then + self:addDustCurrency(playerId, price) + end + return true + elseif item.category == "chat_wheel_sound" then + storeVerboseLog(nil, ("[STORE] Покупка звука чат-вилла " .. itemId) .. " обрабатывается через chat_wheel_buy_sound") + end + local priceFreeRecorded = currency == "free_currency" and price or 0 + local priceDonateRecorded = currency == "donate_currency" and price or 0 + local priceDustRecorded = currency == "dust_currency" and price or 0 + local persistedItemId = self:buildPersistedPurchaseItemId(itemId, item.category, item.cardId) + self:savePurchaseToServer( + playerId, + persistedItemId, + item.category, + item.cardId, + priceFreeRecorded, + priceDonateRecorded, + priceDustRecorded + ) + if not self.playerPurchases:has(playerId) then + self.playerPurchases:set( + playerId, + __TS__New(Set) + ) + end + local purchaseIdForCache = item.category == "cards" and item.cardId ~= nil and persistedItemId or itemId + self.playerPurchases:get(playerId):add(purchaseIdForCache) + if item.category == "cards" and item.cardId ~= nil then + local parsedCardId = __TS__Number(item.cardId) + if __TS__NumberIsFinite(parsedCardId) and parsedCardId > 0 then + local cardId = math.floor(parsedCardId) + local currentCardCounts = __TS__ObjectAssign( + {}, + self.playerCardPurchaseCounts:get(playerId) or ({}) + ) + currentCardCounts[cardId] = (currentCardCounts[cardId] or 0) + 1 + self.playerCardPurchaseCounts:set(playerId, currentCardCounts) + end + end + self:updateAvailableCardsForDeckBuilder( + playerId, + self.playerPurchases:get(playerId), + self.playerCardPurchaseCounts:get(playerId) + ) + local purchaseResultItemId = item.category == "cards" and item.cardId ~= nil and persistedItemId or itemId + self:sendPurchaseResult(playerId, true, "Покупка успешна", purchaseResultItemId) + end) + do + self:releaseStorePurchaseLock(playerId) + end + if ____try and ____hasReturned then + return ____returnValue + end + end + end + ) +end +function StoreManager.prototype.handleEquipEffect(self, playerId, effectId, effectType) + local player = PlayerResource:GetPlayer(playerId) + if not player then + return + end + local purchases = self.playerPurchases:get(playerId) + if not purchases or not purchases:has(effectId) then + storeVerboseLog( + nil, + (("[STORE] Игрок " .. tostring(playerId)) .. " пытается надеть некупленный эффект ") .. effectId + ) + return + end + local activeEffects = self.playerActiveEffects:get(playerId) + if not activeEffects then + activeEffects = {} + end + activeEffects[effectType] = effectId + self.playerActiveEffects:set(playerId, activeEffects) + self:saveActiveEffectsToServer(playerId, activeEffects) + local playerInfo = CustomNetTables:GetTableValue( + "player_info", + tostring(playerId) + ) or ({}) + playerInfo.active_effects = activeEffects + CustomNetTables:SetTableValue( + "player_info", + tostring(playerId), + playerInfo + ) + self:sendActiveEffectsUpdate(playerId, activeEffects) + storeVerboseLog( + nil, + (((("[STORE] Игрок " .. tostring(playerId)) .. " надел эффект ") .. effectId) .. " типа ") .. effectType + ) +end +function StoreManager.prototype.handleUnequipEffect(self, playerId, effectId, effectType) + local player = PlayerResource:GetPlayer(playerId) + if not player then + return + end + local purchases = self.playerPurchases:get(playerId) + if not purchases or not purchases:has(effectId) then + storeVerboseLog( + nil, + (("[STORE] Игрок " .. tostring(playerId)) .. " пытается снять некупленный эффект ") .. effectId + ) + return + end + local activeEffects = self.playerActiveEffects:get(playerId) + if not activeEffects then + self:loadActiveEffectsFromServer( + playerId, + function(____, loadedEffects) + if loadedEffects[effectType] == effectId then + loadedEffects[effectType] = nil + self.playerActiveEffects:set(playerId, loadedEffects) + self:saveActiveEffectsToServer(playerId, loadedEffects) + CustomNetTables:SetTableValue( + "player_info", + tostring(playerId), + {active_effects = loadedEffects} + ) + self:sendActiveEffectsUpdate(playerId, loadedEffects) + storeVerboseLog( + nil, + (((("[STORE] Игрок " .. tostring(playerId)) .. " снял эффект ") .. effectId) .. " типа ") .. effectType + ) + else + storeVerboseLog( + nil, + ((((((("[STORE] Игрок " .. tostring(playerId)) .. " пытается снять неактивный эффект ") .. effectId) .. " типа ") .. effectType) .. " (активен: ") .. tostring(loadedEffects[effectType])) .. ")" + ) + end + end + ) + return + end + if activeEffects[effectType] ~= effectId then + storeVerboseLog( + nil, + ((((((("[STORE] Игрок " .. tostring(playerId)) .. " пытается снять неактивный эффект ") .. effectId) .. " типа ") .. effectType) .. " (активен: ") .. tostring(activeEffects[effectType])) .. ")" + ) + return + end + activeEffects[effectType] = nil + self:saveActiveEffectsToServer(playerId, activeEffects) + self:sendActiveEffectsUpdate(playerId, activeEffects) + storeVerboseLog( + nil, + (((("[STORE] Игрок " .. tostring(playerId)) .. " снял эффект ") .. effectId) .. " типа ") .. effectType + ) +end +function StoreManager.prototype.saveActiveEffectsToServer(self, playerId, activeEffects) + local steamId = PlayerResource:GetSteamAccountID(playerId) + if not steamId then + return + end + local request = CreateHTTPRequest( + "PUT", + ((SERVER_CONFIG.API_URL .. "/player/") .. tostring(steamId)) .. "/active_effects" + ) + setApiHeaders(nil, request) + local dataToSend = {active_effects = activeEffects} + request:SetHTTPRequestRawPostBody( + "application/json", + encodeApiBody(nil, dataToSend) + ) + request:Send(function(result) + if result.StatusCode >= 200 and result.StatusCode < 300 then + storeVerboseLog( + nil, + (("[STORE] Активные эффекты сохранены на сервере для игрока " .. tostring(playerId)) .. ": ") .. json.encode(activeEffects) + ) + else + storeVerboseLog( + nil, + "[STORE] Ошибка сохранения активных эффектов: StatusCode=" .. tostring(result.StatusCode) + ) + end + end) +end +function StoreManager.prototype.sendActiveEffectsUpdate(self, playerId, activeEffects) + local player = PlayerResource:GetPlayer(playerId) + if player then + CustomGameEventManager:Send_ServerToPlayer(player, "store_active_effects_update", {active_effects = activeEffects}) + end +end +function StoreManager.prototype.getPlayerActiveEffects(self, playerId) + local activeEffects = self.playerActiveEffects:get(playerId) + if activeEffects then + return activeEffects + end + return {} +end +function StoreManager.prototype.getPlayerActiveEffect(self, playerId, effectType) + local activeEffects = self.playerActiveEffects:get(playerId) + if activeEffects and activeEffects[effectType] then + return activeEffects[effectType] + end + return nil +end +function StoreManager.prototype.isEffectEquipped(self, playerId, effectId, effectType) + local activeEffects = self.playerActiveEffects:get(playerId) + if not activeEffects then + return false + end + if effectType then + return activeEffects[effectType] == effectId + else + for ____type in pairs(activeEffects) do + if activeEffects[____type] == effectId then + return true + end + end + end + return false +end +function StoreManager.prototype.loadActiveEffectsFromServer(self, playerId, callback) + local steamId = PlayerResource:GetSteamAccountID(playerId) + if not steamId then + callback(nil, {}) + return + end + local request = CreateHTTPRequest( + "GET", + ((SERVER_CONFIG.API_URL .. "/player/") .. tostring(steamId)) .. "/active_effects" + ) + setApiHeaders(nil, request) + request:Send(function(result) + if result.StatusCode >= 200 and result.StatusCode < 300 then + do + local function ____catch(e) + storeVerboseLog( + nil, + "[STORE] Ошибка парсинга активных эффектов: " .. tostring(e) + ) + callback(nil, {}) + end + local ____try, ____hasReturned = pcall(function() + local decoded = {json.decode(result.Body)} + local responseData = nil + if __TS__ArrayIsArray(decoded) and #decoded > 0 then + responseData = decoded[1] + elseif decoded and type(decoded) == "table" then + responseData = decoded + end + if responseData and type(responseData) == "table" and responseData.active_effects ~= nil then + local loadedEffects = responseData.active_effects or ({}) + self.playerActiveEffects:set(playerId, loadedEffects) + local playerInfo = CustomNetTables:GetTableValue( + "player_info", + tostring(playerId) + ) or ({}) + playerInfo.active_effects = loadedEffects + CustomNetTables:SetTableValue( + "player_info", + tostring(playerId), + playerInfo + ) + storeVerboseLog( + nil, + (("[STORE] Загружены активные эффекты для игрока " .. tostring(playerId)) .. ": ") .. json.encode(loadedEffects) + ) + callback(nil, loadedEffects) + else + callback(nil, {}) + end + end) + if not ____try then + ____catch(____hasReturned) + end + end + else + storeVerboseLog( + nil, + "[STORE] Ошибка загрузки активных эффектов: StatusCode=" .. tostring(result.StatusCode) + ) + callback(nil, {}) + end + end) +end +function StoreManager.prototype.savePurchaseToServer(self, playerId, itemId, category, cardId, priceFree, priceDonate, priceDust) + if priceFree == nil then + priceFree = 0 + end + if priceDonate == nil then + priceDonate = 0 + end + if priceDust == nil then + priceDust = 0 + end + local steamId = PlayerResource:GetSteamAccountID(playerId) + if not steamId then + return + end + local request = CreateHTTPRequest( + "POST", + ((SERVER_CONFIG.API_URL .. "/player/") .. tostring(steamId)) .. "/purchases" + ) + setApiHeaders(nil, request) + local dataToSend = { + item_id = itemId, + item_category = category, + card_id = cardId or nil, + price_free = priceFree, + price_donate = priceDonate, + price_dust = priceDust + } + request:SetHTTPRequestRawPostBody( + "application/json", + encodeApiBody(nil, dataToSend) + ) + request:Send(function(result) + if result.StatusCode >= 200 and result.StatusCode < 300 then + storeVerboseLog(nil, ("[STORE] Покупка " .. itemId) .. " сохранена на сервере") + else + storeVerboseLog( + nil, + (("[STORE] Ошибка сохранения покупки " .. itemId) .. ": StatusCode=") .. tostring(result.StatusCode) + ) + end + end) +end +function StoreManager.prototype.sendPurchaseResult(self, playerId, success, message, itemId) + local player = PlayerResource:GetPlayer(playerId) + if player then + CustomGameEventManager:Send_ServerToPlayer(player, "store_purchase_result", {success = success, message = message, item_id = itemId or ""}) + end +end +function StoreManager.prototype.sendPromoCodeResult(self, playerId, success, message) + local player = PlayerResource:GetPlayer(playerId) + if player then + CustomGameEventManager:Send_ServerToPlayer(player, "store_promocode_result", {success = success, message = message}) + end +end +function StoreManager.prototype.registerChatWheelSoundFromBattlePass(self, playerId, soundId) + local itemId = "chat_wheel_sound_" .. soundId + if not self.playerPurchases:has(playerId) then + self.playerPurchases:set( + playerId, + __TS__New(Set) + ) + end + self.playerPurchases:get(playerId):add(itemId) + self:updateAvailableCardsForDeckBuilder( + playerId, + self.playerPurchases:get(playerId) + ) + self:savePurchaseToServer(playerId, itemId, "chat_wheel_sound", nil) + self:sendPurchaseResult(playerId, true, "Награда Battle Pass: звук чат-колеса", itemId) +end +function StoreManager.prototype.registerCardFromBattlePass(self, playerId, cardIdRaw) + local parsedCardId = __TS__Number(cardIdRaw) + if not __TS__NumberIsFinite(parsedCardId) or parsedCardId <= 0 then + return + end + local cardId = math.floor(parsedCardId) + local itemId = "card_data_" .. tostring(cardId) + if not self.playerPurchases:has(playerId) then + self.playerPurchases:set( + playerId, + __TS__New(Set) + ) + end + self.playerPurchases:get(playerId):add(itemId) + local currentCardCounts = __TS__ObjectAssign( + {}, + self.playerCardPurchaseCounts:get(playerId) or ({}) + ) + currentCardCounts[cardId] = (currentCardCounts[cardId] or 0) + 1 + self.playerCardPurchaseCounts:set(playerId, currentCardCounts) + self:updateAvailableCardsForDeckBuilder( + playerId, + self.playerPurchases:get(playerId), + currentCardCounts + ) + self:savePurchaseToServer( + playerId, + itemId, + "cards", + cardId, + 0, + 0 + ) + self:sendPurchaseResult(playerId, true, "Награда Battle Pass: карта добавлена", itemId) +end +function StoreManager.prototype.buildCardPurchaseCountsFromPurchaseList(self, purchases) + local counts = {} + local cardPrefix = "card_data_" + for ____, purchaseId in __TS__Iterator(purchases) do + do + if not __TS__StringStartsWith(purchaseId, cardPrefix) then + goto __continue308 + end + local rawCardId = __TS__StringSubstring(purchaseId, #cardPrefix) + local suffixDelimiterIndex = (string.find(rawCardId, "_", nil, true) or 0) - 1 + if suffixDelimiterIndex >= 0 then + rawCardId = __TS__StringSubstring(rawCardId, 0, suffixDelimiterIndex) + end + local parsedCardId = __TS__Number(rawCardId) + if not __TS__NumberIsFinite(parsedCardId) or parsedCardId <= 0 then + goto __continue308 + end + local cardId = math.floor(parsedCardId) + counts[cardId] = (counts[cardId] or 0) + 1 + end + ::__continue308:: + end + return counts +end +function StoreManager.prototype.getCardMaxCopies(self, cardId) + local configured = CARD_MAX_COPIES_BY_ID[cardId] + if __TS__NumberIsFinite(configured) and configured > 0 then + return math.floor(configured) + end + return 1 +end +function StoreManager.prototype.buildPersistedPurchaseItemId(self, itemId, category, cardId) + if category ~= "cards" or cardId == nil then + return itemId + end + local parsedCardId = __TS__Number(cardId) + if not __TS__NumberIsFinite(parsedCardId) or parsedCardId <= 0 then + return itemId + end + local normalizedCardId = math.floor(parsedCardId) + local nonce = (tostring(math.floor(GameRules:GetGameTime() * 1000)) .. "_") .. tostring(RandomInt(100000, 999999)) + return (("card_data_" .. tostring(normalizedCardId)) .. "__") .. nonce +end +function StoreManager.prototype.updateAvailableCardsForDeckBuilder(self, playerId, purchases, providedCardCounts) + local purchasedCardIds = {} + local cardCounts = providedCardCounts or self:buildCardPurchaseCountsFromPurchaseList(purchases) + storeVerboseLog( + nil, + "[STORE] Обновление списка купленных карт для deck builder. Всего покупок: " .. tostring(purchases.size) + ) + for ____, ____value in ipairs(__TS__ObjectEntries(cardCounts)) do + local cardIdRaw = ____value[1] + local copiesRaw = ____value[2] + do + local cardId = __TS__Number(cardIdRaw) + local copies = math.max( + 0, + math.floor(__TS__Number(copiesRaw)) + ) + if not __TS__NumberIsFinite(cardId) or cardId <= 0 or copies <= 0 then + goto __continue319 + end + do + local i = 0 + while i < copies do + purchasedCardIds[#purchasedCardIds + 1] = cardId + i = i + 1 + end + end + storeVerboseLog( + nil, + (("[STORE] Добавлена карта " .. tostring(cardId)) .. " в количестве ") .. tostring(copies) + ) + end + ::__continue319:: + end + local player = PlayerResource:GetPlayer(playerId) + if player then + CustomNetTables:SetTableValue( + "cards", + "purchased_cards_" .. tostring(playerId), + purchasedCardIds + ) + storeVerboseLog( + nil, + ((("[STORE] Обновлен список купленных карт для deck builder: " .. tostring(#purchasedCardIds)) .. " карт: [") .. table.concat(purchasedCardIds, ", ")) .. "]" + ) + else + storeVerboseLog( + nil, + ("[STORE] Ошибка: игрок " .. tostring(playerId)) .. " не найден для обновления списка купленных карт" + ) + end +end +function StoreManager.prototype.getFreeCurrency(self, playerId) + return self.playerFreeCurrency:get(playerId) or 0 +end +function StoreManager.prototype.addFreeCurrency(self, playerId, amount) + if amount <= 0 then + return false + end + local current = self:getFreeCurrency(playerId) + self.playerFreeCurrency:set(playerId, current + amount) + self:updateCurrencyDisplay(playerId) + self:saveCurrencyToServer(playerId) + return true +end +function StoreManager.prototype.removeFreeCurrency(self, playerId, amount) + if amount <= 0 then + return false + end + local current = self:getFreeCurrency(playerId) + if current < amount then + return false + end + self.playerFreeCurrency:set(playerId, current - amount) + self:updateCurrencyDisplay(playerId) + return true +end +function StoreManager.prototype.getDonateCurrency(self, playerId) + return self.playerDonateCurrency:get(playerId) or 0 +end +function StoreManager.prototype.addDonateCurrency(self, playerId, amount) + if amount <= 0 then + return false + end + local current = self:getDonateCurrency(playerId) + self.playerDonateCurrency:set(playerId, current + amount) + self:updateDonateCurrencyDisplay(playerId) + self:saveCurrencyToServer(playerId) + return true +end +function StoreManager.prototype.removeDonateCurrency(self, playerId, amount) + if amount <= 0 then + return false + end + local current = self:getDonateCurrency(playerId) + if current < amount then + return false + end + self.playerDonateCurrency:set(playerId, current - amount) + self:updateDonateCurrencyDisplay(playerId) + return true +end +function StoreManager.prototype.getDustCurrency(self, playerId) + return self.playerDustCurrency:get(playerId) or 0 +end +function StoreManager.prototype.addDustCurrency(self, playerId, amount) + if amount <= 0 then + return false + end + local current = self:getDustCurrency(playerId) + self.playerDustCurrency:set(playerId, current + amount) + self:updateCurrencyDisplay(playerId) + self:saveCurrencyToServer(playerId) + return true +end +function StoreManager.prototype.addArcadePackCredits(self, playerId, standard, premium) + local addStandard = math.max( + 0, + math.floor(standard or 0) + ) + local addPremium = math.max( + 0, + math.floor(premium or 0) + ) + if addStandard <= 0 and addPremium <= 0 then + return false + end + local info = PlayerInfo:GetPlayerInfo(playerId) or ({}) + local credits = self:normalizeArcadePackCredits(info.arcade_pack_credits) + credits.standard = credits.standard + addStandard + credits.premium = credits.premium + addPremium + PlayerInfo:UpdatePlayerInfo( + playerId, + __TS__ObjectAssign({}, info, {arcade_pack_credits = credits}) + ) + self:notifyArcadePackCredits(playerId) + return true +end +function StoreManager.prototype.removeDustCurrency(self, playerId, amount) + if amount <= 0 then + return false + end + local current = self:getDustCurrency(playerId) + if current < amount then + return false + end + self.playerDustCurrency:set(playerId, current - amount) + self:updateCurrencyDisplay(playerId) + return true +end +function StoreManager.prototype.tryConsumeDonateCurrency(self, playerId, amount) + if not self:removeDonateCurrency(playerId, amount) then + return false + end + self:saveCurrencyToServer(playerId) + return true +end +function StoreManager.prototype.hasUnlockedHero(self, playerId, heroName, storeItemId) + local purchases = self.playerPurchases:get(playerId) + if not purchases or purchases.size == 0 then + return false + end + local targetItemId = storeItemId or self:transformHeroNameToStoreItemId(heroName) + if not targetItemId then + return false + end + return purchases:has(targetItemId) +end +function StoreManager.prototype.hasPurchasedItem(self, playerId, itemId) + local purchases = self.playerPurchases:get(playerId) + if not purchases or purchases.size == 0 then + return false + end + if purchases:has(itemId) then + return true + end + local canonCardId = self:tryParseCanonicalCardDataStoreItemId(itemId) + if canonCardId ~= nil then + return self:hasPurchasedCardById(playerId, canonCardId) + end + return false +end +function StoreManager.prototype.tryParseCanonicalCardDataStoreItemId(self, itemId) + local prefix = "card_data_" + if not __TS__StringStartsWith(itemId, prefix) then + return nil + end + local suffix = __TS__StringSubstring(itemId, #prefix) + local digits = "" + do + local i = 0 + while i < #suffix do + local ch = __TS__StringCharAt(suffix, i) + if ch >= "0" and ch <= "9" then + digits = digits .. ch + else + break + end + i = i + 1 + end + end + if #digits == 0 or #digits ~= #suffix then + return nil + end + local cid = tonumber(digits) + if cid == nil or cid <= 0 then + return nil + end + return math.floor(cid) +end +function StoreManager.prototype.hasPurchasedCardById(self, playerId, cardIdRaw) + return self:getOwnedCardCopies(playerId, cardIdRaw) > 0 +end +function StoreManager.prototype.getOwnedCardCopies(self, playerId, cardIdRaw) + local parsedCardId = __TS__Number(cardIdRaw) + if not __TS__NumberIsFinite(parsedCardId) or parsedCardId <= 0 then + return 0 + end + local cardId = math.floor(parsedCardId) + local cardCounts = self.playerCardPurchaseCounts:get(playerId) + return math.max( + 0, + math.floor(__TS__Number(cardCounts and cardCounts[cardId] or 0)) + ) +end +function StoreManager.prototype.grantCardPurchaseWithoutPayment(self, playerId, cardIdRaw) + local parsedCardId = __TS__Number(cardIdRaw) + if not __TS__NumberIsFinite(parsedCardId) or parsedCardId <= 0 then + return nil + end + local cardId = math.floor(parsedCardId) + if CARD_PURCHASABLE_BY_ID[cardId] == false then + return nil + end + local maxCopies = self:getCardMaxCopies(cardId) + local ownedCopies = self:getOwnedCardCopies(playerId, cardId) + if ownedCopies >= maxCopies then + return nil + end + AddCardToPlayerPool( + nil, + playerId, + cardId, + 5, + 1 + ) + local persistedItemId = self:buildPersistedPurchaseItemId( + "card_data_" .. tostring(cardId), + "cards", + cardId + ) + self:savePurchaseToServer( + playerId, + persistedItemId, + "cards", + cardId, + 0, + 0, + 0 + ) + if not self.playerPurchases:has(playerId) then + self.playerPurchases:set( + playerId, + __TS__New(Set) + ) + end + self.playerPurchases:get(playerId):add(persistedItemId) + local currentCardCounts = __TS__ObjectAssign( + {}, + self.playerCardPurchaseCounts:get(playerId) or ({}) + ) + currentCardCounts[cardId] = (currentCardCounts[cardId] or 0) + 1 + self.playerCardPurchaseCounts:set(playerId, currentCardCounts) + self:updateAvailableCardsForDeckBuilder( + playerId, + self.playerPurchases:get(playerId), + currentCardCounts + ) + return persistedItemId +end +function StoreManager.prototype.transformHeroNameToStoreItemId(self, heroName) + if __TS__StringStartsWith(heroName, ____exports.StoreManager.HERO_STORE_PREFIX) then + return "hero_" .. __TS__StringSubstring(heroName, #____exports.StoreManager.HERO_STORE_PREFIX) + end + if #heroName > 0 then + return heroName + end + return nil +end +function StoreManager.prototype.updateDonateCurrencyDisplay(self, playerId) + self:updateCurrencyDisplay(playerId) +end +function StoreManager.prototype.updateCurrencyDisplay(self, playerId) + local donateAmount = self:getDonateCurrency(playerId) + local freeAmount = self:getFreeCurrency(playerId) + local dustAmount = self:getDustCurrency(playerId) + local info = PlayerInfo:GetPlayerInfo(playerId) + local arcadePackCredits = self:normalizeArcadePackCredits(info and info.arcade_pack_credits) + local player = PlayerResource:GetPlayer(playerId) + if player then + CustomGameEventManager:Send_ServerToPlayer(player, "store_currency_update", {donate_currency = donateAmount, free_currency = freeAmount, dust_currency = dustAmount, arcade_pack_credits = arcadePackCredits}) + end +end +function StoreManager.prototype.grantShopExtrasViaApi(self, playerId, extras, logSuccess) + self:sendShopExtrasGiveRequest( + playerId, + { + free = math.max( + 0, + math.floor(extras.freeAmount or 0) + ), + donate = math.max( + 0, + math.floor(extras.donateAmount or 0) + ), + dust = math.max( + 0, + math.floor(extras.dustAmount or 0) + ), + arcadeStandard = math.max( + 0, + math.floor(extras.arcadePackStandard or 0) + ), + arcadePremium = math.max( + 0, + math.floor(extras.arcadePackPremium or 0) + ) + }, + logSuccess, + false + ) +end +function StoreManager.prototype.sendShopExtrasGiveRequest(self, playerId, amounts, logSuccess, alreadyRetriedAfterEnsure) + local ____amounts_149 = amounts + local free = ____amounts_149.free + local donate = ____amounts_149.donate + local dust = ____amounts_149.dust + local arcadeStandard = ____amounts_149.arcadeStandard + local arcadePremium = ____amounts_149.arcadePremium + if free <= 0 and donate <= 0 and dust <= 0 and arcadeStandard <= 0 and arcadePremium <= 0 then + return + end + local steamId = PlayerResource:GetSteamAccountID(playerId) + if not steamId then + storeVerboseLog( + nil, + "[STORE] grantShopExtrasViaApi: нет Steam ID для " .. tostring(playerId) + ) + return + end + local body = {free_currency = free, donate_currency = donate, dust_currency = dust} + if arcadeStandard > 0 or arcadePremium > 0 then + body.arcade_pack_credits = {standard = arcadeStandard, premium = arcadePremium} + end + local request = CreateHTTPRequest( + "POST", + ((SERVER_CONFIG.API_URL .. "/player/") .. tostring(steamId)) .. "/currency/give" + ) + setApiHeadersLong(nil, request) + request:SetHTTPRequestRawPostBody( + "application/json", + encodeApiBody(nil, body) + ) + request:Send(function(result) + if result.StatusCode >= 200 and result.StatusCode < 300 then + storeVerboseLog(nil, logSuccess) + do + local function ____catch(e) + storeVerboseLog( + nil, + "[STORE] grantShopExtrasViaApi: ошибка парсинга: " .. tostring(e) + ) + end + local ____try, ____hasReturned = pcall(function() + local decoded = {json.decode(result.Body)} + local responseData = nil + if __TS__ArrayIsArray(decoded) and #decoded > 0 then + responseData = decoded[1] + elseif decoded and type(decoded) == "table" then + responseData = decoded + end + if responseData then + self:applyShopCurrencyFromApiPayload(playerId, responseData) + end + end) + if not ____try then + ____catch(____hasReturned) + end + end + return + end + if result.StatusCode == 404 and not alreadyRetriedAfterEnsure then + storeVerboseLog(nil, "[STORE] grantShopExtrasViaApi 404 — создаём player и повторяем") + self:ensurePlayerRowOnApi( + playerId, + function(____, ok) + if ok then + self:sendShopExtrasGiveRequest(playerId, amounts, logSuccess, true) + else + storeVerboseLog(nil, "[STORE] grantShopExtrasViaApi: не удалось создать игрока на API") + end + end + ) + return + end + local bodyStr = result.Body ~= nil and tostring(result.Body) or "" + storeVerboseLog( + nil, + (("[STORE] grantShopExtrasViaApi: HTTP " .. tostring(result.StatusCode)) .. " body=") .. bodyStr + ) + end) +end +function StoreManager.prototype.saveCurrencyToServer(self, playerId) + local steamId = PlayerResource:GetSteamAccountID(playerId) + if not steamId then + return + end + local freeCurrency = self:getFreeCurrency(playerId) + local donateCurrency = self:getDonateCurrency(playerId) + local dustCurrency = self:getDustCurrency(playerId) + local request = CreateHTTPRequest( + "PUT", + ((SERVER_CONFIG.API_URL .. "/player/") .. tostring(steamId)) .. "/currency" + ) + setApiHeaders(nil, request) + local dataToSend = {free_currency = freeCurrency, donate_currency = donateCurrency, dust_currency = dustCurrency} + request:SetHTTPRequestRawPostBody( + "application/json", + encodeApiBody(nil, dataToSend) + ) + request:Send(function(result) + if result.StatusCode >= 200 and result.StatusCode < 300 then + else + end + end) +end +function StoreManager.prototype.ensurePlayerRowOnApi(self, playerId, done) + local steamId = PlayerResource:GetSteamAccountID(playerId) + if not steamId then + storeVerboseLog( + nil, + "[STORE] ensurePlayerRowOnApi: нет Steam ID для " .. tostring(playerId) + ) + done(nil, false) + return + end + local request = CreateHTTPRequest("POST", SERVER_CONFIG.API_URL .. "/player") + setApiHeadersLong(nil, request) + request:SetHTTPRequestRawPostBody( + "application/json", + encodeApiBody( + nil, + { + steam_id = steamId, + player_name = PlayerResource:GetPlayerName(playerId) or "" + } + ) + ) + request:Send(function(result) + if result.StatusCode >= 200 and result.StatusCode < 300 then + done(nil, true) + return + end + local bodyStr = result.Body ~= nil and tostring(result.Body) or "" + storeVerboseLog( + nil, + (("[STORE] ensurePlayerRowOnApi: HTTP " .. tostring(result.StatusCode)) .. " body=") .. bodyStr + ) + done(nil, false) + end) +end +function StoreManager.prototype.giveCurrencyFromServer(self, playerId, freeAmount, donateAmount, dustAmount) + if dustAmount == nil then + dustAmount = 0 + end + self:sendGiveCurrencyRequest( + playerId, + freeAmount, + donateAmount, + dustAmount, + false + ) +end +function StoreManager.prototype.sendGiveCurrencyRequest(self, playerId, freeAmount, donateAmount, dustAmount, alreadyRetriedAfterEnsure) + local steamId = PlayerResource:GetSteamAccountID(playerId) + if not steamId then + storeVerboseLog( + nil, + "[STORE] giveCurrency: нет Steam ID для " .. tostring(playerId) + ) + return + end + if freeAmount <= 0 and donateAmount <= 0 and dustAmount <= 0 then + return + end + local request = CreateHTTPRequest( + "POST", + ((SERVER_CONFIG.API_URL .. "/player/") .. tostring(steamId)) .. "/currency/give" + ) + setApiHeadersLong(nil, request) + request:SetHTTPRequestRawPostBody( + "application/json", + encodeApiBody(nil, {free_currency = freeAmount, donate_currency = donateAmount, dust_currency = dustAmount}) + ) + request:Send(function(result) + if result.StatusCode >= 200 and result.StatusCode < 300 then + do + local function ____catch(e) + storeVerboseLog( + nil, + "[STORE] currency/give: ошибка парсинга: " .. tostring(e) + ) + end + local ____try, ____hasReturned = pcall(function() + local decoded = {json.decode(result.Body)} + local responseData = nil + if __TS__ArrayIsArray(decoded) and #decoded > 0 then + responseData = decoded[1] + elseif decoded and type(decoded) == "table" then + responseData = decoded + end + if responseData and self:applyShopCurrencyFromApiPayload(playerId, responseData) then + storeVerboseLog( + nil, + (("[STORE] currency/give OK: free=" .. tostring(responseData.free_currency)) .. " donate=") .. tostring(responseData.donate_currency) + ) + else + storeVerboseLog( + nil, + "[STORE] currency/give: неожиданный JSON: " .. tostring(result.Body) + ) + end + end) + if not ____try then + ____catch(____hasReturned) + end + end + elseif result.StatusCode == 404 and not alreadyRetriedAfterEnsure then + storeVerboseLog(nil, "[STORE] currency/give 404 (нет строки player) — POST /player и повтор") + self:ensurePlayerRowOnApi( + playerId, + function(____, ok) + if ok then + self:sendGiveCurrencyRequest( + playerId, + freeAmount, + donateAmount, + dustAmount, + true + ) + else + storeVerboseLog(nil, "[STORE] Не удалось создать игрока на API — осколки не начислены") + end + end + ) + else + local bodyStr = result.Body ~= nil and tostring(result.Body) or "" + storeVerboseLog( + nil, + (("[STORE] Ошибка currency/give: HTTP " .. tostring(result.StatusCode)) .. " body=") .. bodyStr + ) + end + end) +end +function StoreManager.prototype.grantDustCurrencyMatchEndReward(self, playerId, amount) + local n = math.floor(amount) + if n <= 0 then + return + end + self:giveCurrencyFromServer(playerId, 0, 0, n) +end +function StoreManager.prototype.grantFreeCurrencyMatchEndReward(self, playerId, amount) + local n = math.floor(amount) + if n <= 0 then + return + end + self:giveCurrencyFromServer(playerId, n, 0) +end +function StoreManager.prototype.loadCurrencyFromServer(self, playerId) + local steamId = PlayerResource:GetSteamAccountID(playerId) + if not steamId then + storeVerboseLog( + nil, + "[STORE] Ошибка: Steam ID не найден для игрока " .. tostring(playerId) + ) + return + end + storeVerboseLog( + nil, + ((("[STORE] Загрузка валюты для игрока " .. tostring(playerId)) .. " (SteamID: ") .. tostring(steamId)) .. ")" + ) + local loadSeq = self:advanceCurrencyLoadSeq(playerId) + local request = CreateHTTPRequest( + "GET", + ((SERVER_CONFIG.API_URL .. "/player/") .. tostring(steamId)) .. "/currency" + ) + setApiHeaders(nil, request) + request:Send(function(result) + if (self.playerCurrencyLoadSeq:get(playerId) or 0) ~= loadSeq then + storeVerboseLog( + nil, + ((("[STORE] Пропуск устаревшего ответа валюты для игрока " .. tostring(playerId)) .. " (seq ") .. tostring(loadSeq)) .. ")" + ) + return + end + storeVerboseLog( + nil, + (("[STORE] Ответ сервера: StatusCode=" .. tostring(result.StatusCode)) .. ", Body=") .. tostring(result.Body) + ) + if result.StatusCode >= 200 and result.StatusCode < 300 then + do + local function ____catch(e) + storeVerboseLog( + nil, + "[STORE] Ошибка парсинга ответа: " .. tostring(e) + ) + end + local ____try, ____hasReturned = pcall(function() + local decoded = {json.decode(result.Body)} + storeVerboseLog( + nil, + "[STORE] Декодированный ответ: " .. json.encode(decoded) + ) + local responseData = nil + if __TS__ArrayIsArray(decoded) and #decoded > 0 then + responseData = decoded[1] + elseif decoded and type(decoded) == "table" then + responseData = decoded + end + if responseData and (responseData.free_currency ~= nil or responseData.donate_currency ~= nil or responseData.dust_currency ~= nil) then + storeVerboseLog( + nil, + (((("[STORE] Валюта с сервера: free=" .. tostring(responseData.free_currency)) .. ", donate=") .. tostring(responseData.donate_currency)) .. ", dust=") .. tostring(responseData.dust_currency) + ) + if responseData.free_currency ~= nil then + self.playerFreeCurrency:set(playerId, responseData.free_currency) + storeVerboseLog( + nil, + "[STORE] Установлены зомби осколки: " .. tostring(responseData.free_currency) + ) + end + if responseData.donate_currency ~= nil then + self.playerDonateCurrency:set(playerId, responseData.donate_currency) + storeVerboseLog( + nil, + "[STORE] Установлена донат валюта: " .. tostring(responseData.donate_currency) + ) + end + if responseData.dust_currency ~= nil then + self.playerDustCurrency:set(playerId, responseData.dust_currency) + storeVerboseLog( + nil, + "[STORE] Установлена пыль: " .. tostring(responseData.dust_currency) + ) + end + self:loadPurchasesFromServer( + playerId, + function(____, purchases) + self:loadActiveEffectsFromServer( + playerId, + function(____, loadedEffects) + local player = PlayerResource:GetPlayer(playerId) + if player then + local eventData = { + donate_currency = responseData.donate_currency or 0, + free_currency = responseData.free_currency or 0, + dust_currency = responseData.dust_currency or 0, + purchased_items = purchases, + arcade_pack_credits = self:normalizeArcadePackCredits(responseData.arcade_pack_credits) + } + storeVerboseLog( + nil, + "[STORE] Отправка события клиенту: " .. json.encode(eventData) + ) + CustomGameEventManager:Send_ServerToPlayer(player, "store_currency_update", eventData) + self:sendActiveEffectsUpdate(playerId, loadedEffects) + else + storeVerboseLog( + nil, + ("[STORE] Ошибка: Игрок " .. tostring(playerId)) .. " не найден для отправки события" + ) + end + end + ) + end + ) + else + storeVerboseLog( + nil, + "[STORE] Ошибка: Неверный формат ответа от сервера. responseData: " .. json.encode(responseData) + ) + end + end) + if not ____try then + ____catch(____hasReturned) + end + end + else + storeVerboseLog( + nil, + "[STORE] Ошибка HTTP: StatusCode=" .. tostring(result.StatusCode) + ) + end + end) +end +function StoreManager.prototype.loadPurchasesFromServer(self, playerId, callback) + local steamId = PlayerResource:GetSteamAccountID(playerId) + if not steamId then + callback(nil, {}) + return + end + local request = CreateHTTPRequest( + "GET", + ((SERVER_CONFIG.API_URL .. "/player/") .. tostring(steamId)) .. "/purchases" + ) + setApiHeaders(nil, request) + request:Send(function(result) + if result.StatusCode >= 200 and result.StatusCode < 300 then + do + local function ____catch(e) + storeVerboseLog( + nil, + "[STORE] Ошибка парсинга списка покупок: " .. tostring(e) + ) + callback(nil, {}) + end + local ____try, ____hasReturned = pcall(function() + local decoded = {json.decode(result.Body)} + local purchases = {} + local responseData = nil + if __TS__ArrayIsArray(decoded) and #decoded > 0 then + responseData = decoded[1] + elseif decoded and type(decoded) == "table" then + responseData = decoded + end + if responseData and type(responseData) == "table" and responseData.purchases ~= nil then + local purchasesData = responseData.purchases + if __TS__ArrayIsArray(purchasesData) then + purchases = purchasesData + end + end + local previousPurchases = self.playerPurchases:get(playerId) or __TS__New(Set) + local purchasesSet = __TS__New(Set, purchases) + for ____, p in __TS__Iterator(previousPurchases) do + purchasesSet:add(p) + end + local cardCounts = self:buildCardPurchaseCountsFromPurchaseList(purchasesSet) + self.playerPurchases:set(playerId, purchasesSet) + self.playerCardPurchaseCounts:set(playerId, cardCounts) + self:updateAvailableCardsForDeckBuilder(playerId, purchasesSet, cardCounts) + local mergedPurchasesArray = __TS__ArrayFrom(purchasesSet) + storeVerboseLog( + nil, + (((("[STORE] Загружено с API " .. tostring(#purchases)) .. " покупок, после слияния с сессией ") .. tostring(#mergedPurchasesArray)) .. " для игрока ") .. tostring(playerId) + ) + callback(nil, mergedPurchasesArray) + end) + if not ____try then + ____catch(____hasReturned) + end + end + else + storeVerboseLog( + nil, + "[STORE] Ошибка загрузки покупок: StatusCode=" .. tostring(result.StatusCode) + ) + callback(nil, {}) + end + end) +end +StoreManager.HERO_STORE_PREFIX = "npc_dota_hero_" +____exports.StoreManager:getInstance() +return ____exports diff --git a/scripts/vscripts/tables/hero_list_table.lua b/scripts/vscripts/tables/hero_list_table.lua new file mode 100644 index 0000000..f617cdf --- /dev/null +++ b/scripts/vscripts/tables/hero_list_table.lua @@ -0,0 +1,38 @@ +--[[ Generated with https://github.com/TypeScriptToLua/TypeScriptToLua ]] +local ____exports = {} +--- Герои в объекте должны быть в `game/scripts/npc/herolist.txt`; иначе закомментировать. +____exports.HERO_LIST_TABLE = { + npc_dota_hero_axe = {heroId = 2}, + npc_dota_hero_crystal_maiden = {heroId = 5, isDonate = true, storeItemId = "hero_crystal_maiden"}, + npc_dota_hero_drow_ranger = {heroId = 6}, + npc_dota_hero_juggernaut = {heroId = 8, isDonate = true, storeItemId = "hero_juggernaut"}, + npc_dota_hero_mirana = {heroId = 9, isDonate = true, storeItemId = "hero_mirana"}, + npc_dota_hero_nevermore = {heroId = 11, isDonate = true, storeItemId = "hero_nevermore"}, + npc_dota_hero_pudge = {heroId = 14, isDonate = true, storeItemId = "hero_pudge"}, + npc_dota_hero_sand_king = {heroId = 16, isDonate = true, storeItemId = "hero_sand_king"}, + npc_dota_hero_sven = {heroId = 18}, + npc_dota_hero_vengefulspirit = {heroId = 20, isDonate = true, storeItemId = "hero_vengefulspirit"}, + npc_dota_hero_lina = {heroId = 25, isDonate = true, storeItemId = "hero_lina"}, + npc_dota_hero_sniper = {heroId = 35}, + npc_dota_hero_queenofpain = {heroId = 39, isDonate = true, storeItemId = "hero_queenofpain"}, + npc_dota_hero_phantom_assassin = {heroId = 44, isDonate = true, storeItemId = "hero_phantom_assassin"}, + npc_dota_hero_templar_assassin = {heroId = 46, isDonate = true, storeItemId = "hero_templar_assassin"}, + npc_dota_hero_luna = {heroId = 48}, + npc_dota_hero_spectre = {heroId = 67, isDonate = true, storeItemId = "hero_spectre"}, + npc_dota_hero_silencer = {heroId = 75}, + npc_dota_hero_ogre_magi = {heroId = 84}, + npc_dota_hero_rubick = {heroId = 86, isDonate = true, storeItemId = "hero_rubick"}, + npc_dota_hero_keeper_of_the_light = {heroId = 90}, + npc_dota_hero_medusa = {heroId = 94}, + npc_dota_hero_troll_warlord = {heroId = 95}, + npc_dota_hero_bristleback = {heroId = 99}, + npc_dota_hero_skywrath_mage = {heroId = 101}, + npc_dota_hero_legion_commander = {heroId = 104, isDonate = true, storeItemId = "hero_legion_commander"}, + npc_dota_hero_hoodwink = {heroId = 123}, + npc_dota_hero_nagash = {heroId = 180, isDonate = true, storeItemId = "hero_nagash"}, + npc_dota_hero_bloodhunter = {heroId = 181, isDonate = true, storeItemId = "hero_bloodhunter"}, + npc_dota_hero_yuki_onna = {heroId = 182, isDonate = true, storeItemId = "hero_yuki_onna"}, + npc_dota_hero_sargatanas = {heroId = 183, isDonate = true, storeItemId = "hero_sargatanas"}, + npc_dota_hero_elder_dragon_smaug = {heroId = 184, isDonate = true, storeItemId = "hero_elder_dragon_smaug"} +} +return ____exports diff --git a/scripts/vscripts/triggers/activity_button_prop.lua b/scripts/vscripts/triggers/activity_button_prop.lua new file mode 100644 index 0000000..2354c8f --- /dev/null +++ b/scripts/vscripts/triggers/activity_button_prop.lua @@ -0,0 +1,236 @@ +--[[ Generated with https://github.com/TypeScriptToLua/TypeScriptToLua ]] +local ____exports = {} +local ____dota_ts_adapter = require("lib.dota_ts_adapter") +local registerEntityFunction = ____dota_ts_adapter.registerEntityFunction +local ACTIVITY_BUTTON_NAME = "activity_button" +local LOG_PREFIX = "[activity_button_prop]" +local ATTACK1_TO_CAPTURE_DELAY = 0.45 +local ATTACK2_TO_IDLE_DELAY = 0.65 +local stateByPropEntIndex = {} +local function tryAsPropEntity(self, v) + if v == nil or v == nil then + return nil + end + if not IsValidEntity(v) then + return nil + end + return v +end +--- CallPrivateScriptFunction часто не даёт thisEntity — берём из data или FindByName. +local function resolveProp(self, data, reason) + do + local ____try, ____hasReturned, ____returnValue = pcall(function() + if thisEntity ~= nil and thisEntity ~= nil and not thisEntity:IsNull() and IsValidEntity(thisEntity) then + print((((LOG_PREFIX .. " resolveProp(") .. (reason or "?")) .. "): thisEntity=") .. tostring(thisEntity:entindex())) + return true, thisEntity + end + end) + if ____try and ____hasReturned then + return ____returnValue + end + end + if data ~= nil and data ~= nil and type(data) == "table" then + local t = data + --- Порядок: сначала сам объект кнопки; caller/activator — триггер/герой, в конце. + local keys = { + "self", + "entity", + "thisEntity", + "parent", + "owner", + "callee", + "activator", + "caller" + } + for ____, key in ipairs(keys) do + local e = tryAsPropEntity(nil, t[key]) + if e ~= nil then + print((((((LOG_PREFIX .. " resolveProp(") .. (reason or "?")) .. "): data.") .. key) .. "=") .. tostring(e:entindex())) + return e + end + end + local dbg = {} + for ____, key in ipairs(keys) do + dbg[#dbg + 1] = (key .. "=") .. type(t[key]) + end + print((((LOG_PREFIX .. " resolveProp(") .. (reason or "?")) .. "): data поля: ") .. table.concat(dbg, ", ")) + end + local byName = Entities:FindByName(nil, ACTIVITY_BUTTON_NAME) + local named = tryAsPropEntity(nil, byName) + if named ~= nil then + print((((((LOG_PREFIX .. " resolveProp(") .. (reason or "?")) .. "): FindByName(\"") .. ACTIVITY_BUTTON_NAME) .. "\")=") .. tostring(named:entindex())) + return named + end + print(((LOG_PREFIX .. " resolveProp(") .. (reason or "?")) .. "): FAIL — нет prop") + return nil +end +local function fadeGestures(self, prop) + do + pcall(function() + prop:FadeGesture(ACT_DOTA_ATTACK) + prop:FadeGesture(ACT_DOTA_CAPTURE) + prop:FadeGesture(ACT_DOTA_ATTACK2) + prop:FadeGesture(ACT_DOTA_IDLE) + end) + end +end +local function clearPending(self, state) + if state.pendingTimerId ~= nil then + Timers:RemoveTimer(state.pendingTimerId) + state.pendingTimerId = nil + end +end +local function clearAttack2(self, state) + if state.attack2ToIdleTimerId ~= nil then + Timers:RemoveTimer(state.attack2ToIdleTimerId) + state.attack2ToIdleTimerId = nil + end +end +local function playIdle(self, prop) + do + local function ____catch(e) + print((LOG_PREFIX .. " playIdle ошибка: ") .. tostring(e)) + end + local ____try, ____hasReturned = pcall(function() + fadeGestures(nil, prop) + prop:StartGesture(ACT_DOTA_IDLE) + print((LOG_PREFIX .. " DOTA_IDLE prop=") .. tostring(prop:entindex())) + end) + if not ____try then + ____catch(____hasReturned) + end + end +end +local function playRiseThenIdle(self, prop, state) + print((LOG_PREFIX .. " playRiseThenIdle prop=") .. tostring(prop:entindex())) + clearAttack2(nil, state) + do + pcall(function() + fadeGestures(nil, prop) + prop:StartGesture(ACT_DOTA_ATTACK2) + end) + end + state.attack2ToIdleTimerId = Timers:CreateTimer( + ATTACK2_TO_IDLE_DELAY, + function() + state.attack2ToIdleTimerId = nil + print((LOG_PREFIX .. " таймер attack2→idle prop=") .. tostring(prop:entindex())) + playIdle(nil, prop) + return nil + end + ) +end +registerEntityFunction( + nil, + "OnStartTouch", + function(____, data) + if not IsServer() then + return + end + print((LOG_PREFIX .. " OnStartTouch вызван data=") .. tostring(data)) + local prop = resolveProp(nil, data, "OnStartTouch") + if prop == nil then + return + end + local idx = prop:entindex() + local state = stateByPropEntIndex[idx] + if state == nil then + state = {stillInside = true, inCapture = false} + stateByPropEntIndex[idx] = state + else + clearPending(nil, state) + clearAttack2(nil, state) + state.stillInside = true + state.inCapture = false + end + do + local function ____catch(e) + print((LOG_PREFIX .. " OnStartTouch жест DOTA_ATTACK: ") .. tostring(e)) + return true + end + local ____try, ____hasReturned, ____returnValue = pcall(function() + fadeGestures(nil, prop) + prop:StartGesture(ACT_DOTA_ATTACK) + print(((((LOG_PREFIX .. " OnStartTouch → DOTA_ATTACK prop=") .. tostring(idx)) .. ", capture через ") .. tostring(ATTACK1_TO_CAPTURE_DELAY)) .. "s") + end) + if not ____try then + ____hasReturned, ____returnValue = ____catch(____hasReturned) + end + if ____hasReturned then + return ____returnValue + end + end + state.pendingTimerId = Timers:CreateTimer( + ATTACK1_TO_CAPTURE_DELAY, + function() + state.pendingTimerId = nil + if not state.stillInside then + print((LOG_PREFIX .. " таймер capture: уже вышли — отмена prop=") .. tostring(idx)) + return nil + end + local p = EntIndexToHScript(idx) + if p == nil or not IsValidEntity(p) then + print((LOG_PREFIX .. " таймер capture: prop пропал idx=") .. tostring(idx)) + return nil + end + do + local function ____catch(e) + print((LOG_PREFIX .. " таймер capture ошибка: ") .. tostring(e)) + end + local ____try, ____hasReturned = pcall(function() + p:FadeGesture(ACT_DOTA_ATTACK) + p:StartGesture(ACT_DOTA_CAPTURE) + state.inCapture = true + print((LOG_PREFIX .. " таймер capture → DOTA_CAPTURE prop=") .. tostring(idx)) + end) + if not ____try then + ____catch(____hasReturned) + end + end + return nil + end + ) + end +) +registerEntityFunction( + nil, + "OnEndTouch", + function(____, data) + if not IsServer() then + return + end + print((LOG_PREFIX .. " OnEndTouch вызван data=") .. tostring(data)) + local prop = resolveProp(nil, data, "OnEndTouch") + if prop == nil then + return + end + local idx = prop:entindex() + local state = stateByPropEntIndex[idx] + if state == nil then + print((LOG_PREFIX .. " OnEndTouch: нет state prop=") .. tostring(idx)) + return + end + print((((((LOG_PREFIX .. " OnEndTouch prop=") .. tostring(idx)) .. " inCapture=") .. tostring(state.inCapture)) .. " pending=") .. tostring(state.pendingTimerId ~= nil)) + state.stillInside = false + if state.pendingTimerId ~= nil then + clearPending(nil, state) + print(LOG_PREFIX .. " OnEndTouch: до захвата → ATTACK_2 + idle") + playRiseThenIdle(nil, prop, state) + state.inCapture = false + return + end + if state.inCapture then + do + pcall(function() + prop:FadeGesture(ACT_DOTA_CAPTURE) + end) + end + state.inCapture = false + print(LOG_PREFIX .. " OnEndTouch: из захвата → ATTACK_2 + idle") + playRiseThenIdle(nil, prop, state) + else + print(LOG_PREFIX .. " OnEndTouch: без pending/inCapture") + end + end +) +return ____exports diff --git a/scripts/vscripts/triggers/shovel_treasure_cross.lua b/scripts/vscripts/triggers/shovel_treasure_cross.lua new file mode 100644 index 0000000..adc1bf2 --- /dev/null +++ b/scripts/vscripts/triggers/shovel_treasure_cross.lua @@ -0,0 +1,56 @@ +local ____lualib = require("lualib_bundle") +local __TS__Class = ____lualib.__TS__Class +local __TS__ClassExtends = ____lualib.__TS__ClassExtends +local __TS__Decorate = ____lualib.__TS__Decorate +local ____exports = {} +local ____dota_ts_adapter = require("lib.dota_ts_adapter") +local BaseModifier = ____dota_ts_adapter.BaseModifier +local registerEntityFunction = ____dota_ts_adapter.registerEntityFunction +local registerModifier = ____dota_ts_adapter.registerModifier +--- Герой стоит на кладбищенском кресте (триггер Hammer → StartTouch / StartModif). +____exports.modifier_shovel_treasure_cross = __TS__Class() +local modifier_shovel_treasure_cross = ____exports.modifier_shovel_treasure_cross +modifier_shovel_treasure_cross.name = "modifier_shovel_treasure_cross" +modifier_shovel_treasure_cross.____file_path = "scripts/vscripts/triggers/shovel_treasure_cross.lua" +__TS__ClassExtends(modifier_shovel_treasure_cross, BaseModifier) +function modifier_shovel_treasure_cross.prototype.IsHidden(self) + return true +end +function modifier_shovel_treasure_cross.prototype.IsPurgable(self) + return false +end +function modifier_shovel_treasure_cross.prototype.RemoveOnDeath(self) + return true +end +modifier_shovel_treasure_cross = __TS__Decorate( + modifier_shovel_treasure_cross, + modifier_shovel_treasure_cross, + {registerModifier(nil)}, + {kind = "class", name = "modifier_shovel_treasure_cross"} +) +____exports.modifier_shovel_treasure_cross = modifier_shovel_treasure_cross +local function handleCrossStart(self, trigger) + local ent = trigger.activator + if ent == nil or ent:IsNull() or not ent:IsRealHero() then + return + end + ent:AddNewModifier( + ent, + getModifierSourceAbility(nil, ent), + ____exports.modifier_shovel_treasure_cross.name, + {} + ) +end +local function handleCrossEnd(self, trigger) + local ent = trigger.activator + if ent == nil or ent:IsNull() then + return + end + ent:RemoveModifierByName(____exports.modifier_shovel_treasure_cross.name) +end +registerEntityFunction(nil, "StartTouch", handleCrossStart) +registerEntityFunction(nil, "EndTouch", handleCrossEnd) +registerEntityFunction(nil, "OnStartTouch", handleCrossStart) +registerEntityFunction(nil, "OnEndTouch", handleCrossEnd) +registerEntityFunction(nil, "StartModif", handleCrossStart) +return ____exports diff --git a/scripts/vscripts/triggers/trigger_button.lua b/scripts/vscripts/triggers/trigger_button.lua new file mode 100644 index 0000000..0a03429 --- /dev/null +++ b/scripts/vscripts/triggers/trigger_button.lua @@ -0,0 +1,175 @@ +--[[ Generated with https://github.com/TypeScriptToLua/TypeScriptToLua ]] +local ____exports = {} +local ____dota_ts_adapter = require("lib.dota_ts_adapter") +local registerEntityFunction = ____dota_ts_adapter.registerEntityFunction +local LOG = "[trigger_button]" +--- Основной targetname пропа (prop_dynamic). +local BUTTON_PROP_TARGETNAME = "trigger_button" +--- Запасной targetname, если триггер тоже назван `trigger_button`. +local BUTTON_PROP_TARGETNAME_FALLBACK = "trigger_button_prop" +local function getThisTriggerIndex(self) + do + local ____try, ____hasReturned, ____returnValue = pcall(function() + if thisEntity ~= nil and thisEntity ~= nil and not thisEntity:IsNull() then + return true, thisEntity:entindex() + end + end) + if ____try and ____hasReturned then + return ____returnValue + end + end + return -1 +end +local function isVolumeTriggerEntity(self, ent) + local cn = "" + do + local function ____catch(_) + return true, false + end + local ____try, ____hasReturned, ____returnValue = pcall(function() + cn = ent:GetClassname() + end) + if not ____try then + ____hasReturned, ____returnValue = ____catch(____hasReturned) + end + if ____hasReturned then + return ____returnValue + end + end + return cn == "trigger_multiple" or cn == "trigger_once" or cn == "trigger_look" or cn == "trigger_push" or cn == "trigger_teleport" or cn == "trigger_hurt" +end +--- Все сущности с targetname; пропускаем сам триггер и объёмные триггеры. +local function findButtonPropDynamic(self) + local myIdx = getThisTriggerIndex(nil) + local list = Entities:FindAllByName(BUTTON_PROP_TARGETNAME) + if list ~= nil then + do + local i = 0 + while i < #list do + do + local e = list[i + 1] + if e == nil or not IsValidEntity(e) then + goto __continue10 + end + if myIdx >= 0 and e:entindex() == myIdx then + goto __continue10 + end + if isVolumeTriggerEntity(nil, e) then + goto __continue10 + end + print((((((LOG .. " findProp: \"") .. BUTTON_PROP_TARGETNAME) .. "\" → ent=") .. tostring(e:entindex())) .. " class=") .. e:GetClassname()) + return e + end + ::__continue10:: + i = i + 1 + end + end + end + local fb = Entities:FindByName(nil, BUTTON_PROP_TARGETNAME_FALLBACK) + if fb ~= nil and fb ~= nil and IsValidEntity(fb) and not isVolumeTriggerEntity(nil, fb) then + print((((LOG .. " findProp: fallback \"") .. BUTTON_PROP_TARGETNAME_FALLBACK) .. "\" ent=") .. tostring(fb:entindex())) + return fb + end + print(((((((LOG .. " findProp: не найден подходящий prop (имена \"") .. BUTTON_PROP_TARGETNAME) .. "\" / \"") .. BUTTON_PROP_TARGETNAME_FALLBACK) .. "\", исключены trigger_* и thisEntity=") .. tostring(myIdx)) .. ")") + return nil +end +--- Жесты — у CDOTA_BaseNPC / некоторых пропов; иначе SetSequence по имени секвенции. +local function playButtonPropDotAttack(self) + local ent = findButtonPropDynamic(nil) + if ent == nil then + return + end + local asNpc = ent + local anyEnt = ent + if type(anyEnt.StartGesture) == "function" then + local startG = anyEnt.StartGesture + local fadeG = anyEnt.FadeGesture + do + local function ____catch(e) + print((LOG .. " StartGesture ошибка: ") .. tostring(e)) + end + local ____try, ____hasReturned = pcall(function() + if type(fadeG) == "function" then + fadeG(nil, ACT_DOTA_ATTACK) + end + startG(nil, ACT_DOTA_ATTACK) + print(((LOG .. " prop ent=") .. tostring(ent:entindex())) .. " → StartGesture(DOTA_ATTACK)") + end) + if not ____try then + ____catch(____hasReturned) + end + end + return + end + if type(anyEnt.SetSequence) == "function" then + local setSeq = anyEnt.SetSequence + local candidates = { + "ACT_DOTA_ATTACK", + "attack", + "dota_attack", + "press", + "down" + } + for ____, seq in ipairs(candidates) do + do + local ____try, ____hasReturned, ____returnValue = pcall(function() + setSeq(nil, seq) + print(((((LOG .. " prop ent=") .. tostring(ent:entindex())) .. " → SetSequence(\"") .. seq) .. "\")") + return true + end) + if ____try and ____hasReturned then + return ____returnValue + end + end + end + print(((((LOG .. " prop ent=") .. tostring(ent:entindex())) .. ": SetSequence не принял кандидатов ") .. table.concat(candidates, ", ")) .. " — проверь имя секвенции в модели") + return + end + print(((((LOG .. " prop ent=") .. tostring(ent:entindex())) .. " class=") .. ent:GetClassname()) .. ": нет StartGesture/SetSequence — тип сущности не поддерживает анимацию как у героя") +end +local function handleStartTouch(self, trigger) + local act = trigger.activator + if act ~= nil and IsValidEntity(act) then + print((LOG .. " StartTouch / OnStartTouch: activator=") .. tostring(act:entindex())) + else + print(LOG .. " StartTouch / OnStartTouch: нет activator (I/O — только анимация пропа)") + end + playButtonPropDotAttack(nil) +end +local function handleEndTouch(self, trigger) + local act = trigger.activator + if act ~= nil and IsValidEntity(act) then + print((LOG .. " EndTouch / OnEndTouch: activator=") .. tostring(act:entindex())) + else + print(LOG .. " EndTouch / OnEndTouch: нет activator") + end +end +registerEntityFunction( + nil, + "StartTouch", + function(____, trigger) + handleStartTouch(nil, trigger) + end +) +registerEntityFunction( + nil, + "EndTouch", + function(____, trigger) + handleEndTouch(nil, trigger) + end +) +registerEntityFunction( + nil, + "OnStartTouch", + function(____, trigger) + handleStartTouch(nil, trigger) + end +) +registerEntityFunction( + nil, + "OnEndTouch", + function(____, trigger) + handleEndTouch(nil, trigger) + end +) +return ____exports diff --git a/scripts/vscripts/triggers/trigger_swamp.lua b/scripts/vscripts/triggers/trigger_swamp.lua new file mode 100644 index 0000000..d5a4387 --- /dev/null +++ b/scripts/vscripts/triggers/trigger_swamp.lua @@ -0,0 +1,32 @@ +--[[ Generated with https://github.com/TypeScriptToLua/TypeScriptToLua ]] +local ____exports = {} +local ____dota_ts_adapter = require("lib.dota_ts_adapter") +local registerEntityFunction = ____dota_ts_adapter.registerEntityFunction +local ____modifier_swamp_slow = require("abilities.modifiers.modifier_swamp_slow") +local modifier_swamp_slow = ____modifier_swamp_slow.modifier_swamp_slow +registerEntityFunction( + nil, + "StartTouch", + function(____, trigger) + local ent = trigger.activator + if ent ~= nil then + ent:AddNewModifier( + ent, + getModifierSourceAbility(nil, ent), + modifier_swamp_slow.name, + {} + ) + end + end +) +registerEntityFunction( + nil, + "EndTouch", + function(____, trigger) + local ent = trigger.activator + if ent ~= nil then + ent:RemoveModifierByName(modifier_swamp_slow.name) + end + end +) +return ____exports diff --git a/scripts/vscripts/utils/creep_render_color.lua b/scripts/vscripts/utils/creep_render_color.lua new file mode 100644 index 0000000..a7cdc2c --- /dev/null +++ b/scripts/vscripts/utils/creep_render_color.lua @@ -0,0 +1,29 @@ +--[[ Generated with https://github.com/TypeScriptToLua/TypeScriptToLua ]] +local ____exports = {} +local DEFAULT_TINT = 255 +local TINT_EPS = 1.5 +local function isAlreadyRenderTinted(self, unit) + local u = unit + if u.GetRenderColor == nil then + return false + end + local c = u:GetRenderColor() + if c == nil then + return false + end + local dr = DEFAULT_TINT + return math.abs(c.x - dr) > TINT_EPS or math.abs(c.y - dr) > TINT_EPS or math.abs(c.z - dr) > TINT_EPS +end +--- +-- @returns true если цвет реально выставили +function ____exports.trySetIntrinsicCreepRenderColor(self, unit, r, g, b) + if not IsServer() or not IsValidEntity(unit) then + return false + end + if isAlreadyRenderTinted(nil, unit) then + return false + end + unit:SetRenderColor(r, g, b) + return true +end +return ____exports diff --git a/scripts/vscripts/utils/crit_mult.lua b/scripts/vscripts/utils/crit_mult.lua new file mode 100644 index 0000000..7f1b730 --- /dev/null +++ b/scripts/vscripts/utils/crit_mult.lua @@ -0,0 +1,68 @@ +--[[ Generated with https://github.com/TypeScriptToLua/TypeScriptToLua ]] +local ____exports = {} +local CRIT_MULT_KEY_PREFIX +require("lib.dota_ts_adapter") +function ____exports.getCritMult(self, hero) + if not hero or not IsValidEntity(hero) then + return 100 + end + local playerId = hero:GetPlayerOwnerID() + if playerId == nil or playerId == nil or playerId < 0 then + return 100 + end + local key = CRIT_MULT_KEY_PREFIX .. tostring(playerId) + local data = CustomNetTables:GetTableValue("custom_stats", key) + local ____data_0 + if data then + ____data_0 = data.value + else + ____data_0 = 100 + end + return ____data_0 +end +CRIT_MULT_KEY_PREFIX = "crit_mult_" +function ____exports.setCritMult(self, hero, critMult) + if not IsServer() then + return + end + local playerId = hero:GetPlayerOwnerID() + if playerId == nil or playerId == nil or playerId < 0 then + return + end + local key = CRIT_MULT_KEY_PREFIX .. tostring(playerId) + CustomNetTables:SetTableValue("custom_stats", key, {value = critMult}) +end +function ____exports.addCritMult(self, hero, delta) + if not IsServer() then + return + end + local current = ____exports.getCritMult(nil, hero) + ____exports.setCritMult(nil, hero, current + delta) +end +function ____exports.reduceCritMult(self, hero, delta) + if not IsServer() then + return + end + ____exports.addCritMult( + nil, + hero, + -math.abs(delta) + ) +end +--- Итоговый множитель урона крита (физика и магия): sum(mult/100 из источников) × (крит_мульт героя / 100). +-- +-- @param stackedCritMultSum сумма mult/100 по сработавшим источникам (например 1.75 + 1.6) +function ____exports.getFinalStackingCritMultiplier(self, unit, stackedCritMultSum) + if stackedCritMultSum <= 0 then + return 0 + end + local critMultStat = unit:IsHero() and ____exports.getCritMult(nil, unit) or 100 + return stackedCritMultSum * (critMultStat / 100) +end +local g = _G +g.setCritMult = ____exports.setCritMult +g.addCritMult = ____exports.addCritMult +g.reduceCritMult = ____exports.reduceCritMult +g.getCritMult = ____exports.getCritMult +g.getFinalStackingCritMultiplier = ____exports.getFinalStackingCritMultiplier +return ____exports diff --git a/scripts/vscripts/utils/damage_event_attribution.lua b/scripts/vscripts/utils/damage_event_attribution.lua new file mode 100644 index 0000000..ab0c818 --- /dev/null +++ b/scripts/vscripts/utils/damage_event_attribution.lua @@ -0,0 +1,63 @@ +--[[ Generated with https://github.com/TypeScriptToLua/TypeScriptToLua ]] +local ____exports = {} +--- Привязка entity_hurt к игроку для статистики урона. +-- GetRawPlayerDamage в кастомке часто пустой; BP раньше смотрел только IsRealHero + GetOwner — терялся урон с иллюзий/призывов. +local function resolvePlayerIdFromUnitChain(self, start) + if not start or not IsValidEntity(start) then + return nil + end + local current = start + do + local depth = 0 + while depth < 8 and current and IsValidEntity(current) do + local pid = current:GetPlayerOwnerID() + if pid ~= nil and pid >= 0 then + return pid + end + local anyC = current + local owner = type(anyC.GetOwnerEntity) == "function" and anyC:GetOwnerEntity() or type(anyC.GetOwner) == "function" and anyC:GetOwner() + if not owner or not IsValidEntity(owner) then + break + end + current = owner + depth = depth + 1 + end + end + return nil +end +--- Атакующий юнит (меч, иллюзия, призванное). +function ____exports.resolvePlayerIdFromDamageAttacker(self, attacker) + return resolvePlayerIdFromUnitChain(nil, attacker) +end +--- Источник нанесённого урона из события: сначала attacker, если пусто/без владельца — inflictor (способность/предмет) → кастер. +function ____exports.resolvePlayerIdFromEntityHurtForOutgoing(self, event) + local aIdx = event.entindex_attacker + if aIdx ~= nil and aIdx ~= nil and tonumber(tostring(aIdx)) > 0 then + local ent = EntIndexToHScript(aIdx) + if ent and IsValidEntity(ent) then + local p = ____exports.resolvePlayerIdFromDamageAttacker(nil, ent) + if p ~= nil and p >= 0 then + return p + end + end + end + local iIdx = event.entindex_inflictor + if iIdx ~= nil and iIdx ~= nil and tonumber(tostring(iIdx)) > 0 then + local inf = EntIndexToHScript(iIdx) + if not inf or not IsValidEntity(inf) then + return nil + end + local caster + if type(inf.GetCaster) == "function" then + caster = inf:GetCaster() + end + if (not caster or not IsValidEntity(caster)) and type(inf.GetOwner) == "function" then + caster = inf:GetOwner() + end + if caster and IsValidEntity(caster) then + return ____exports.resolvePlayerIdFromDamageAttacker(nil, caster) + end + end + return nil +end +return ____exports diff --git a/scripts/vscripts/utils/dev_cheat_session.lua b/scripts/vscripts/utils/dev_cheat_session.lua new file mode 100644 index 0000000..a71ad93 --- /dev/null +++ b/scripts/vscripts/utils/dev_cheat_session.lua @@ -0,0 +1,23 @@ +--[[ Generated with https://github.com/TypeScriptToLua/TypeScriptToLua ]] +local ____exports = {} +--- Tools / `-cheats`: `GameRules.IsCheatMode()` или `sv_cheats 1`. +function ____exports.isDevCheatSession(self) + if GameRules:IsCheatMode() then + return true + end + do + local function ____catch(_error) + return true, false + end + local ____try, ____hasReturned, ____returnValue = pcall(function() + return true, Convars:GetBool("sv_cheats") == true + end) + if not ____try then + ____hasReturned, ____returnValue = ____catch(____hasReturned) + end + if ____hasReturned then + return ____returnValue + end + end +end +return ____exports diff --git a/scripts/vscripts/utils/entity_radius.lua b/scripts/vscripts/utils/entity_radius.lua new file mode 100644 index 0000000..e546c12 --- /dev/null +++ b/scripts/vscripts/utils/entity_radius.lua @@ -0,0 +1,44 @@ +--[[ Generated with https://github.com/TypeScriptToLua/TypeScriptToLua ]] +local ____exports = {} +--- Аналог Entities.FindAllByClassnameWithin без VectorWS: обход через FindAllByClassname + дистанция. +function ____exports.findAllByClassnameInRadius(classname, center, radius) + local all = Entities:FindAllByClassname(classname) + local result = {} + local r2 = radius * radius + for ____, ent in ipairs(all) do + do + if not ent or ent:IsNull() then + goto __continue3 + end + local pos = ent:GetAbsOrigin() + local dx = pos.x - center.x + local dy = pos.y - center.y + if dx * dx + dy * dy <= r2 then + result[#result + 1] = ent + end + end + ::__continue3:: + end + return result +end +--- Аналог Entities.FindByClassnameNearest: ближайшая сущность класса в пределах maxRadius. +function ____exports.findNearestByClassname(classname, origin, maxRadius) + local all = Entities:FindAllByClassname(classname) + local best + local bestDist = maxRadius + 1 + for ____, ent in ipairs(all) do + do + if not ent or ent:IsNull() then + goto __continue8 + end + local d = (origin - ent:GetAbsOrigin()):Length2D() + if d <= maxRadius and d < bestDist then + bestDist = d + best = ent + end + end + ::__continue8:: + end + return best +end +return ____exports diff --git a/scripts/vscripts/utils/find_homer.lua b/scripts/vscripts/utils/find_homer.lua new file mode 100644 index 0000000..31b49bd --- /dev/null +++ b/scripts/vscripts/utils/find_homer.lua @@ -0,0 +1,17 @@ +--[[ Generated with https://github.com/TypeScriptToLua/TypeScriptToLua ]] +local ____exports = {} +--- Поиск npc_homer без FindUnitsInRadius на весь мир (тяжёлый запрос при каждом тике карт 55/56). +function ____exports.findHomerNpc(self) + local byName = Entities:FindByName(nil, "npc_homer") + if byName and IsValidEntity(byName) and not byName:IsNull() and byName:GetUnitName() == "npc_homer" then + return byName + end + local allUnits = Entities:FindAllByClassname("npc_dota_creature") + for ____, unit in ipairs(allUnits) do + if unit and IsValidEntity(unit) and not unit:IsNull() and unit:GetUnitName() == "npc_homer" and unit:IsAlive() then + return unit + end + end + return nil +end +return ____exports diff --git a/scripts/vscripts/utils/get_true_hero_from_entity.lua b/scripts/vscripts/utils/get_true_hero_from_entity.lua new file mode 100644 index 0000000..28bc1f4 --- /dev/null +++ b/scripts/vscripts/utils/get_true_hero_from_entity.lua @@ -0,0 +1,30 @@ +--[[ Generated with https://github.com/TypeScriptToLua/TypeScriptToLua ]] +local ____exports = {} +--- Как в defension (lib/utils.ts + lib/Filters.ts): от юнита к «настоящему» герою игрока для учёта урона. +function ____exports.getTrueHeroFromEntity(self, ent) + if not ent then + return nil + end + if ent:IsBaseNPC() then + local npc = ent + if npc:IsRealHero() then + return npc + end + local ownerEnt = npc:GetOwner() + if ownerEnt and ownerEnt:IsBaseNPC() then + local owner = ownerEnt + if owner:IsRealHero() then + return owner + end + end + local player = npc:GetPlayerOwner() + if player ~= nil then + local hero = player:GetAssignedHero() + if hero and hero:IsRealHero() then + return hero + end + end + end + return nil +end +return ____exports diff --git a/scripts/vscripts/utils/heal_tracker.lua b/scripts/vscripts/utils/heal_tracker.lua new file mode 100644 index 0000000..e3ea14e --- /dev/null +++ b/scripts/vscripts/utils/heal_tracker.lua @@ -0,0 +1,40 @@ +--[[ Generated with https://github.com/TypeScriptToLua/TypeScriptToLua ]] +local ____exports = {} +local ____battle_pass_server = require("battle_pass_server") +local BattlePassServer = ____battle_pass_server.BattlePassServer +--- Лечит юнита и учитывает лечение союзников для Battle Pass. +-- Вызывайте вместо HealWithParams когда healer — герой игрока, а target — союзник (не сам healer). +function ____exports.HealWithBattlePass(self, target, amount, ability, healer, showOverhead) + if showOverhead == nil then + showOverhead = true + end + target:HealWithParams( + amount, + ability, + false, + showOverhead, + healer, + false + ) + local ____temp_2 = amount > 0 and healer + if ____temp_2 then + local ____this_1 + ____this_1 = healer + local ____opt_0 = ____this_1.IsRealHero + if ____opt_0 ~= nil then + ____opt_0 = ____opt_0(____this_1) + end + ____temp_2 = ____opt_0 + end + if ____temp_2 and target ~= healer then + do + pcall(function() + local playerId = healer:GetPlayerOwnerID() + if playerId >= 0 then + BattlePassServer:getInstance():onHealAlly(playerId, amount) + end + end) + end + end +end +return ____exports diff --git a/scripts/vscripts/utils/incoming_damage_reduction_combine.lua b/scripts/vscripts/utils/incoming_damage_reduction_combine.lua new file mode 100644 index 0000000..6854000 --- /dev/null +++ b/scripts/vscripts/utils/incoming_damage_reduction_combine.lua @@ -0,0 +1,162 @@ +local ____lualib = require("lualib_bundle") +local __TS__NumberIsFinite = ____lualib.__TS__NumberIsFinite +local __TS__ObjectKeys = ____lualib.__TS__ObjectKeys +local __TS__ObjectValues = ____lualib.__TS__ObjectValues +local __TS__Delete = ____lualib.__TS__Delete +local __TS__Class = ____lualib.__TS__Class +local __TS__ClassExtends = ____lualib.__TS__ClassExtends +local __TS__Decorate = ____lualib.__TS__Decorate +local ____exports = {} +local ____dota_ts_adapter = require("lib.dota_ts_adapter") +local BaseAbility = ____dota_ts_adapter.BaseAbility +local BaseModifier = ____dota_ts_adapter.BaseModifier +local registerAbility = ____dota_ts_adapter.registerAbility +local registerModifier = ____dota_ts_adapter.registerModifier +--- Верхний предел после убывания (синхрон с `src/panorama/arsenal.ts`). +____exports.MAX_EFFECTIVE_INCOMING_REDUCTION_FROM_LINEAR_SUM_PCT = 92 +--- «Бумажная» сумма % снижения входящего → эффективный % в бою (убывающая отдача). +-- +-- @param linearSumPositivePct сумма вкладов из KV/арсенала (+20+20+…) +function ____exports.resolveIncomingDamageReductionPctFromLinearSum(self, linearSumPositivePct) + if not __TS__NumberIsFinite(linearSumPositivePct) or linearSumPositivePct <= 0 then + return 0 + end + local s = linearSumPositivePct + local hyperbolic = 100 * s / (100 + s) + return math.min(____exports.MAX_EFFECTIVE_INCOMING_REDUCTION_FROM_LINEAR_SUM_PCT, hyperbolic) +end +--- Значение для MODIFIER_PROPERTY_INCOMING_DAMAGE_PERCENTAGE (отрицательное = меньше урона). +function ____exports.incomingDamageReductionModifierValue(self, linearSumPositivePct) + local effective = ____exports.resolveIncomingDamageReductionPctFromLinearSum(nil, linearSumPositivePct) + return effective > 0 and -effective or 0 +end +____exports.MODIFIER_INCOMING_DAMAGE_REDUCTION = "modifier_incoming_damage_reduction" +local STATS_MULTIPLIER_INCOMING_PREFIX = "stats_multiplier:" +local function getStorage(self, unit) + local holder = unit + if not holder.__incomingDamageReductionStorage then + holder.__incomingDamageReductionStorage = {sources = {}, eventSources = {}} + end + return holder.__incomingDamageReductionStorage +end +local function hasAnySources(self, unit) + local storage = getStorage(nil, unit) + return #__TS__ObjectKeys(storage.sources) > 0 or #__TS__ObjectKeys(storage.eventSources) > 0 +end +local function resolveSourceAbility(self, unit) + return unit:FindAbilityByName("ability_incoming_damage_reduction") or unit:FindAbilityByName("ability_stats_multiplier") or unit:FindAbilityByName("ability_stacking_crit") or nil +end +local function ensureCombineModifier(self, unit) + if unit:FindModifierByName(____exports.MODIFIER_INCOMING_DAMAGE_REDUCTION) then + return + end + local sourceAbility = resolveSourceAbility(nil, unit) + unit:AddNewModifier(unit, sourceAbility, ____exports.MODIFIER_INCOMING_DAMAGE_REDUCTION, {}) +end +local function removeCombineModifierIfUnused(self, unit) + if hasAnySources(nil, unit) then + return + end + local mod = unit:FindModifierByName(____exports.MODIFIER_INCOMING_DAMAGE_REDUCTION) + if mod then + mod:Destroy() + end +end +--- Сумма «бумажных» % снижения (до формулы убывания). +function ____exports.computeIncomingDamageReductionLinearSum(self, unit, event) + local storage = getStorage(nil, unit) + local sum = 0 + for ____, entry in ipairs(__TS__ObjectValues(storage.sources)) do + local value = entry:resolver() + if __TS__NumberIsFinite(value) and value > 0 then + sum = sum + value + end + end + if event then + for ____, entry in ipairs(__TS__ObjectValues(storage.eventSources)) do + local value = entry:resolver(event) + if __TS__NumberIsFinite(value) and value > 0 then + sum = sum + value + end + end + end + return sum +end +--- Статический/динамический источник: resolver возвращает положительный % снижения. +function ____exports.setIncomingDamageReductionSource(self, unit, sourceId, resolver) + if not unit or not IsValidEntity(unit) then + return + end + getStorage(nil, unit).sources[sourceId] = {resolver = resolver} + ensureCombineModifier(nil, unit) +end +--- Источник, зависящий от удара (угол, золото, мана и т.д.). +function ____exports.setIncomingDamageReductionEventSource(self, unit, sourceId, resolver) + if not unit or not IsValidEntity(unit) then + return + end + getStorage(nil, unit).eventSources[sourceId] = {resolver = resolver} + ensureCombineModifier(nil, unit) +end +function ____exports.removeIncomingDamageReductionSource(self, unit, sourceId) + if not unit or not IsValidEntity(unit) then + return + end + local storage = getStorage(nil, unit) + __TS__Delete(storage.sources, sourceId) + __TS__Delete(storage.eventSources, sourceId) + removeCombineModifierIfUnused(nil, unit) +end +function ____exports.setStatsMultiplierIncomingDamageReductionSource(self, hero, sourceId, resolver) + ____exports.setIncomingDamageReductionSource(nil, hero, STATS_MULTIPLIER_INCOMING_PREFIX .. sourceId, resolver) +end +function ____exports.removeStatsMultiplierIncomingDamageReductionSource(self, hero, sourceId) + ____exports.removeIncomingDamageReductionSource(nil, hero, STATS_MULTIPLIER_INCOMING_PREFIX .. sourceId) +end +local ability_incoming_damage_reduction = __TS__Class() +ability_incoming_damage_reduction.name = "ability_incoming_damage_reduction" +ability_incoming_damage_reduction.____file_path = "scripts/vscripts/utils/incoming_damage_reduction_combine.lua" +__TS__ClassExtends(ability_incoming_damage_reduction, BaseAbility) +function ability_incoming_damage_reduction.prototype.GetIntrinsicModifierName(self) + return ____exports.MODIFIER_INCOMING_DAMAGE_REDUCTION +end +function ability_incoming_damage_reduction.prototype.IsHidden(self) + return true +end +ability_incoming_damage_reduction = __TS__Decorate( + ability_incoming_damage_reduction, + ability_incoming_damage_reduction, + {registerAbility(nil)}, + {kind = "class", name = "ability_incoming_damage_reduction"} +) +local modifier_incoming_damage_reduction = __TS__Class() +modifier_incoming_damage_reduction.name = "modifier_incoming_damage_reduction" +modifier_incoming_damage_reduction.____file_path = "scripts/vscripts/utils/incoming_damage_reduction_combine.lua" +__TS__ClassExtends(modifier_incoming_damage_reduction, BaseModifier) +function modifier_incoming_damage_reduction.prototype.IsHidden(self) + return true +end +function modifier_incoming_damage_reduction.prototype.IsPurgable(self) + return false +end +function modifier_incoming_damage_reduction.prototype.RemoveOnDeath(self) + return false +end +function modifier_incoming_damage_reduction.prototype.DeclareFunctions(self) + return {MODIFIER_PROPERTY_INCOMING_DAMAGE_PERCENTAGE} +end +function modifier_incoming_damage_reduction.prototype.GetModifierIncomingDamage_Percentage(self, event) + local unit = self:GetParent() + if not unit or not IsValidEntity(unit) then + return 0 + end + local linearSum = ____exports.computeIncomingDamageReductionLinearSum(nil, unit, event) + return ____exports.incomingDamageReductionModifierValue(nil, linearSum) +end +modifier_incoming_damage_reduction = __TS__Decorate( + modifier_incoming_damage_reduction, + modifier_incoming_damage_reduction, + {registerModifier(nil)}, + {kind = "class", name = "modifier_incoming_damage_reduction"} +) +return ____exports diff --git a/scripts/vscripts/utils/light_fix.lua b/scripts/vscripts/utils/light_fix.lua new file mode 100644 index 0000000..b09b010 --- /dev/null +++ b/scripts/vscripts/utils/light_fix.lua @@ -0,0 +1,48 @@ +--[[ Generated with https://github.com/TypeScriptToLua/TypeScriptToLua ]] +local ____exports = {} +--- Серверный спавн карты `light_fix` в центре мира по X/Y, якорь Z = LIGHT_FIX_SPAWN_Z под землёй. +-- Один экземпляр на матч: повторные вызовы снимают предыдущий spawn group и спавнят снова. +local function ceilMapNumber(____, value) + return value + (64 - value % 64) % 64 +end +--- Опциональная точка на карте маппера (если есть — X/Y, со снапом по сетке; Z задаётся отдельно). +local LIGHT_FIX_WORLD_ENTITY = "point_light_fix_world_center" +--- Высота якоря под картой: не видно игрокам, логика освещения может подтягиваться. +local LIGHT_FIX_SPAWN_Z = -1000 +local lightFixSpawnGroupHandle +--- Центр мира по границам карты + снап 64 по X/Y; Z под землёй. +local function getLightFixSpawnOrigin(self) + local named = Entities:FindByName(nil, LIGHT_FIX_WORLD_ENTITY) + if named and named.GetAbsOrigin then + local o = named:GetAbsOrigin() + return Vector( + ceilMapNumber(nil, o.x), + ceilMapNumber(nil, o.y), + LIGHT_FIX_SPAWN_Z + ) + end + local cx = (GetWorldMinX() + GetWorldMaxX()) * 0.5 + local cy = (GetWorldMinY() + GetWorldMaxY()) * 0.5 + local x = ceilMapNumber(nil, cx) + local y = ceilMapNumber(nil, cy) + return Vector(x, y, LIGHT_FIX_SPAWN_Z) +end +--- Спавнит `light_fix` в центре мира (коллбеки пустые — полный цикл загрузки). +-- `playerId` оставлен в сигнатуре для вызывающего кода; позиция не зависит от игрока. +function ____exports.spawnLightFixForPlayer(self, _playerId) + if not IsServer() then + return + end + local origin = getLightFixSpawnOrigin(nil) + lightFixSpawnGroupHandle = DOTA_SpawnMapAtPosition( + "light_fix", + origin, + false, + function() + end, + function() + end, + nil + ) +end +return ____exports diff --git a/scripts/vscripts/utils/luck.lua b/scripts/vscripts/utils/luck.lua new file mode 100644 index 0000000..c6601cb --- /dev/null +++ b/scripts/vscripts/utils/luck.lua @@ -0,0 +1,119 @@ +local ____lualib = require("lualib_bundle") +local __TS__NumberIsFinite = ____lualib.__TS__NumberIsFinite +local ____exports = {} +require("lib.dota_ts_adapter") +--- Удача только из NetTable (без бонуса арсенала) — для setLuck/addLuck. +local function getLuckBaseFromTable(self, hero) + if not hero or not IsValidEntity(hero) then + return 0 + end + local playerId = hero:GetPlayerOwnerID() + if playerId == nil or playerId == nil or playerId < 0 then + return 0 + end + local key = "luck_" .. tostring(playerId) + local data = CustomNetTables:GetTableValue("custom_stats", key) + local ____data_0 + if data then + ____data_0 = data.value + else + ____data_0 = 0 + end + return ____data_0 +end +function ____exports.setLuck(self, hero, luck) + if not IsServer() then + return + end + local playerId = hero:GetPlayerOwnerID() + if playerId == nil or playerId == nil or playerId < 0 then + return + end + local key = "luck_" .. tostring(playerId) + CustomNetTables:SetTableValue("custom_stats", key, {value = luck}) +end +function ____exports.addLuck(self, hero, delta) + if not IsServer() then + return + end + local current = getLuckBaseFromTable(nil, hero) + ____exports.setLuck(nil, hero, current + delta) +end +function ____exports.reduceLuck(self, hero, delta) + if not IsServer() then + return + end + ____exports.addLuck( + nil, + hero, + -math.abs(delta) + ) +end +function ____exports.getLuck(self, hero) + if not hero or not IsValidEntity(hero) then + return 0 + end + local playerId = hero:GetPlayerOwnerID() + if playerId == nil or playerId == nil or playerId < 0 then + return 0 + end + return getLuckBaseFromTable(nil, hero) +end +--- По умолчанию удача не может поднять шанс выше этого множителя от базы (15% → макс. 30%). +local DEFAULT_MAX_CHANCE_VS_BASE = 2 +--- Рассчитывает шанс с учетом удачи героя и базового значения +-- +-- @param hero - герой для которого рассчитывается шанс +-- @param baseValue - базовое значение шанса (0-1) +-- @param luckMultiplier - аддитивный бонус за 1 удачу (в долях, по умолчанию 0.01 = +1 п.п.) +-- @param maxChanceVsBaseMult - потолок: итог не выше baseValue * этого числа (по умолчанию 2). Для baseValue <= 0 потолок не применяется. +-- @returns итоговый шанс (0-1) +function ____exports.calculateLuckChance(self, hero, baseValue, luckMultiplier, maxChanceVsBaseMult) + if luckMultiplier == nil then + luckMultiplier = 0.01 + end + if maxChanceVsBaseMult == nil then + maxChanceVsBaseMult = DEFAULT_MAX_CHANCE_VS_BASE + end + local luck = ____exports.getLuck(nil, hero) + local finalChance = baseValue + luck * luckMultiplier + if baseValue > 0 and __TS__NumberIsFinite(maxChanceVsBaseMult) and maxChanceVsBaseMult > 0 and maxChanceVsBaseMult < 1000000000 then + local ceiling = baseValue * maxChanceVsBaseMult + finalChance = math.min(finalChance, ceiling) + end + return math.max( + 0, + math.min(1, finalChance) + ) +end +--- Проверяет, сработал ли шанс с учетом удачи +-- +-- @param hero - герой для которого проверяется шанс +-- @param baseValue - базовое значение шанса (0-1) +-- @param luckMultiplier - аддитивный бонус за 1 удачу (по умолчанию 0.01) +-- @param maxChanceVsBaseMult - см. calculateLuckChance (по умолчанию ×2 от базы) +-- @returns true если шанс сработал +function ____exports.rollLuckChance(self, hero, baseValue, luckMultiplier, maxChanceVsBaseMult) + if luckMultiplier == nil then + luckMultiplier = 0.01 + end + if maxChanceVsBaseMult == nil then + maxChanceVsBaseMult = DEFAULT_MAX_CHANCE_VS_BASE + end + local chance = ____exports.calculateLuckChance( + nil, + hero, + baseValue, + luckMultiplier, + maxChanceVsBaseMult + ) + return math.random() < chance +end +local g = _G +g.setLuck = ____exports.setLuck +g.addLuck = ____exports.addLuck +g.reduceLuck = ____exports.reduceLuck +g.getLuck = ____exports.getLuck +g.calculateLuckChance = ____exports.calculateLuckChance +g.rollLuckChance = ____exports.rollLuckChance +return ____exports diff --git a/scripts/vscripts/utils/player_connection_state.lua b/scripts/vscripts/utils/player_connection_state.lua new file mode 100644 index 0000000..d1012c3 --- /dev/null +++ b/scripts/vscripts/utils/player_connection_state.lua @@ -0,0 +1,28 @@ +--[[ Generated with https://github.com/TypeScriptToLua/TypeScriptToLua ]] +local ____exports = {} +--- Значения GetConnectionState(playerId) (сервер) / DOTA_CONNECTION_STATE. +-- UNKNOWN 0, NOT_YET_CONNECTED 1, CONNECTED 2, DISCONNECTED 3, ABANDONED 4, LOADING 5, FAILED 6 +____exports.DOTA_CONNECTION_STATE = { + UNKNOWN = 0, + NOT_YET_CONNECTED = 1, + CONNECTED = 2, + DISCONNECTED = 3, + ABANDONED = 4, + LOADING = 5, + FAILED = 6 +} +--- Ещё в сессии / может вернуться (в т.ч. DISCONNECTED). Лив = ABANDONED; FAILED = сессия мёртва. +function ____exports.isConnectionStateEffectivelyInGame(self, cs) + return cs ~= ____exports.DOTA_CONNECTION_STATE.ABANDONED and cs ~= ____exports.DOTA_CONNECTION_STATE.FAILED +end +--- Игрок на связи с сервером матча (heartbeat, таймер лива). DISCONNECTED = уже вышел. +function ____exports.isConnectionStateActivelyConnected(self, cs) + return cs == ____exports.DOTA_CONNECTION_STATE.CONNECTED +end +function ____exports.isConnectionStateAbandoned(self, cs) + return cs == ____exports.DOTA_CONNECTION_STATE.ABANDONED +end +function ____exports.isConnectionStateDropped(self, cs) + return cs == ____exports.DOTA_CONNECTION_STATE.ABANDONED or cs == ____exports.DOTA_CONNECTION_STATE.FAILED +end +return ____exports diff --git a/scripts/vscripts/utils/precache_all_npc_units.lua b/scripts/vscripts/utils/precache_all_npc_units.lua new file mode 100644 index 0000000..b18ffa1 --- /dev/null +++ b/scripts/vscripts/utils/precache_all_npc_units.lua @@ -0,0 +1,56 @@ +local ____lualib = require("lualib_bundle") +local __TS__ArraySort = ____lualib.__TS__ArraySort +local ____exports = {} +--- Прекеш юнитов по имени (PrecacheUnitByNameAsync) из объединённого npc_units_custom. +-- Вызывать только из GameMode.Precache — иначе движок ругается: «PrecacheResource must be passed a valid precache context». +-- Волны/spawn/event/quest/summons — через #base в npc_units_custom.txt. Герои из npc_heroes_custom сюда не входят. +local LOG_PFX = "[precache_all_npc_units]" +--- Единый источник, как у precache_models_from_kv: движок подмешивает все #base. +local NPC_UNITS_CUSTOM_KV = "scripts/npc/npc_units_custom.txt" +--- Таблица определений юнитов: обычно kv["DOTAUnits"], но LoadKeyValues в части сборок +-- отдаёт те же пары имя→блок сразу в корне (см. admin_menu: перебор верхнего уровня kv). +local function getUnitDefinitionsRoot(self, kv) + local wrapped = kv.DOTAUnits + if wrapped ~= nil and type(wrapped) == "table" then + return wrapped + end + return kv +end +local function collectUnitNamesFromNpcUnitsCustomKv(self, kvPath) + local kv = LoadKeyValues(kvPath) + if kv == nil then + print((LOG_PFX .. " не удалось загрузить ") .. kvPath) + return {} + end + local block = getUnitDefinitionsRoot(nil, kv) + if block == nil then + print((LOG_PFX .. " пустой KV ") .. kvPath) + return {} + end + local names = {} + for key in pairs(block) do + local child = block[key] + if child ~= nil and child ~= nil and type(child) == "table" then + names[#names + 1] = key + end + end + if #names == 0 then + print(((LOG_PFX .. " не найдено ни одного юнита в ") .. kvPath) .. " (ожидан DOTAUnits или плоский корень)") + end + return names +end +--- Имена кастомных юнитов из npc_units_custom.txt (+ #base), без npc_heroes. +function ____exports.getWaveAndSpawnManagerUnitNamesFromKv(self) + local names = collectUnitNamesFromNpcUnitsCustomKv(nil, NPC_UNITS_CUSTOM_KV) + __TS__ArraySort(names) + return names +end +--- Запрашивает прекеш полного набора ресурсов юнита по определению в KV. +function ____exports.precacheAllCustomUnitsByNameAsync(self, context) + local names = ____exports.getWaveAndSpawnManagerUnitNamesFromKv(nil) + print((LOG_PFX .. " PrecacheUnitByNameSync (npc_units_custom + #base) × ") .. tostring(#names)) + for ____, name in ipairs(names) do + PrecacheUnitByNameSync(name, context) + end +end +return ____exports diff --git a/scripts/vscripts/utils/precache_misc_particles.lua b/scripts/vscripts/utils/precache_misc_particles.lua new file mode 100644 index 0000000..b4db8b2 --- /dev/null +++ b/scripts/vscripts/utils/precache_misc_particles.lua @@ -0,0 +1,195 @@ +--[[ Generated with https://github.com/TypeScriptToLua/TypeScriptToLua ]] +local ____exports = {} +local ____blackshop = require("blackshop") +local precacheBlackshopParticles = ____blackshop.precacheBlackshopParticles +local ____QuestEventHandlers = require("quests.QuestEventHandlers") +local precacheQuestBountyHunterParticles = ____QuestEventHandlers.precacheQuestBountyHunterParticles +local ____SpawnManager = require("SpawnManager") +local precacheSpawnManagerEffectParticles = ____SpawnManager.precacheSpawnManagerEffectParticles +local ____SoundSystem = require("SoundSystem") +local precacheWeatherParticles = ____SoundSystem.precacheWeatherParticles +local precacheKittyFlexResources = ____SoundSystem.precacheKittyFlexResources +local ____vampirism = require("utils.vampirism") +local precacheVampirismParticle = ____vampirism.precacheVampirismParticle +local ____CutsceneFunctions = require("cutscenes.CutsceneFunctions") +local precacheCutsceneParticles = ____CutsceneFunctions.precacheCutsceneParticles +--- Полный набор частиц из бывшего GameMode.Precache (способности/предметы/резерв). +-- Дубликаты с precache* ниже безопасны. Постепенно переносить в Precache() конкретных ability/item. +local LEGACY_GAMEMODE_PARTICLES = { + "particles/econ/items/monkey_king/mk_ti9_immortal/arcana_death/mk_ti9_immortal_arcana_death_weapon_ambient_fire.vpcf", + "particles/econ/items/monkey_king/mk_ti9_immortal/mk_ti9_immortal_weapon_ambient_fire.vpcf", + "particles/red_diff.vpcf", + "particles/impossible.vpcf", + "particles/econ/items/phoenix/phoenix_ti10_immortal/phoenix_ti10_fire_spirit_launch_elements.vpcf", + "particles/econ/items/queen_of_pain/qop_arcana/qop_arcana_tgt_death_fire.vpcf", + "particles/units/heroes/hero_skeletonking/wraith_king_curse_overhead_skull.vpcf", + "particles/econ/items/bounty_hunter/bounty_hunter_hunters_hoard/bounty_hunter_hoard_shield_mark.vpcf", + "particles/econ/items/axe/ti9_jungle_axe/ti9_jungle_axe_culling_blade_sprint_fire.vpcf", + "particles/generic_gameplay/generic_has_quest.vpcf", + "particles/units/heroes/hero_kunkka/kunkka_spell_torrent_splash.vpcf", + "particles/units/heroes/hero_morphling/morphling_adaptive_strike_agi_proj.vpcf", + "particles/econ/items/crystal_maiden/crystal_maiden_maiden_of_icewrack/maiden_freezing_field_explosion_c_arcana1.vpcf", + "particles/units/heroes/hero_skeletonking/skeletonking_hellfireblast.vpcf", + "particles/econ/items/wraith_king/wraith_king_arcana/wk_arc_weapon_blur_critical.vpcf", + "particles/econ/events/fall_2022/maelstrom/maelstrom_arcs_fall2022.vpcf", + "particles/units/heroes/hero_bounty_hunter/bounty_hunter_track_trail_circle.vpcf", + "particles/units/heroes/hero_bounty_hunter/bounty_hunter_track_shield_mark.vpcf", + "particles/units/heroes/hero_phantom_assassin/phantom_assassin_stifling_dagger_explosion.vpcf", + "particles/units/heroes/hero_primal_beast/primal_beast_onslaught_chargeup.vpcf", + "particles/units/heroes/hero_abaddon/abaddon_death_coil.vpcf", + "particles/econ/items/huskar/huskar_2021_immortal/huskar_2021_immortal_burning_spear_debuff.vpcf", + "particles/units/heroes/hero_axe/axe_culling_blade.vpcf", + "particles/units/heroes/hero_phoenix/phoenix_supernova_reborn.vpcf", + "particles/bloodstone_full_screen_effect.vpcf", + "particles/econ/items/bloodseeker/bloodseeker_eztzhok_weapon/bloodseeker_bloodbath_eztzhok.vpcf", + "particles/units/heroes/hero_muerta/muerta_ultimate_form_screen_effect.vpcf", + "particles/units/heroes/hero_huskar/huskar_burning_spear_debuff.vpcf", + "particles/units/heroes/hero_skeletonking/wraith_king_curse_debuff_slash.vpcf", + "particles/econ/items/shadow_fiend/sf_desolation/sf_base_attack_desolation.vpcf", + "particles/econ/items/templar_assassin/ta_2022_immortal/ta_2022_immortal_trap_crimson.vpcf", + "particles/econ/items/templar_assassin/ta_2022_immortal/ta_2022_immortal_trap_gold.vpcf", + "particles/econ/items/lanaya/lanaya_epit_trap/templar_assassin_epit_trap.vpcf", + "particles/units/heroes/hero_ancient_apparition/ancient_apparition_freeze_stacks_smoke_b.vpcf", + "particles/units/heroes/hero_ancient_apparition/ancient_apparition_ice_blast_main.vpcf", + "particles/units/heroes/hero_jakiro/jakiro_liquid_fire_explosion.vpcf", + "particles/econ/items/faceless_void/faceless_void_arcana/faceless_void_arcana_deny_v2_symbol_question.vpcf", + "particles/units/heroes/hero_largo/largo_catchy_lick.vpcf", + "particles/ui_mouseactions/range_finder_cone_dual.vpcf", + "particles/units/heroes/hero_invoker/invoker_forged_spirit_projectile.vpcf", + "particles/msg_fx/msg_mana_add.vpcf", + "particles/units/heroes/hero_morphling/morphling_adaptive_strike.vpcf", + "particles/items3_fx/mango_active.vpcf", + "particles/units/heroes/hero_brewmaster/brewmaster_drunken_haze_debuff.vpcf", + "models/heroes/phantom_assassin_persona/debut/particles/pa_debutdash/pa_debutdash_fragments.vpcf", + "particles/items_fx/chain_lightning.vpcf", + "particles/items_fx/phylactery_target.vpcf", + "particles/items_fx/phylactery.vpcf", + "particles/units/heroes/hero_huskar/huskar_inner_fire_debuff_flame.vpcf", + "particles/items_fx/battlefury_cleave.vpcf", + "particles/econ/items/queen_of_pain/qop_2022_immortal/queen_2022_scream_of_pain_owner_blue.vpcf", + "particles/darkmoon_creep_warning.vpcf", + "particles/crystal_scepter_shield_ring.vpcf", + "particles/crystal_scepter_shield.vpcf", + "particles/fish_screen_effect.vpcf", + "particles/econ/items/lifestealer/lifestealer_immortal_backbone_gold/lifestealer_immortal_backbone_gold_rage.vpcf", + "particles/units/heroes/hero_omniknight/omniknight_heavenly_grace_buff.vpcf", + "particles/econ/items/omniknight/omniknight_fall20_immortal/omniknight_fall20_immortal_degen_aura_debuff.vpcf", + "particles/econ/items/omniknight/omni_crimson_witness_2021/omniknight_crimson_witness_2021_degen_aura_debuff.vpcf", + "particles/econ/events/fall_2021/fall_2021_emblem_game_effect.vpcf", + "particles/blue_gems_effect.vpcf", + "particles/sponsor_effect.vpcf", + "particles/lotus_effect.vpcf", + "particles/effect_battlepass_red.vpcf", + "particles/battlepass_garden_effect.vpcf", + "particles/battlepass_golden_effect.vpcf", + "particles/units/heroes/hero_witchdoctor/witchdoctor_maledict_projectile.vpcf", + "particles/econ/items/ember_spirit/ember_ti9/ember_ti9_flameguard.vpcf", + "particles/units/heroes/hero_ember_spirit/ember_spirit_fire_remnant_flames.vpcf", + "particles/world_environmental_fx/map_riverflow.vpcf", + "particles/econ/events/diretide_2020/emblem/fall20_emblem_effect.vpcf", + "particles/econ/events/diretide_2020/emblem/fall20_emblem_v1_effect.vpcf", + "particles/econ/events/diretide_2020/emblem/fall20_emblem_v2_effect.vpcf", + "particles/econ/events/diretide_2020/emblem/fall20_emblem_v3_effect.vpcf", + "particles/econ/items/kunkka/divine_anchor/hero_kunkka_dafx_skills/kunkka_spell_x_spot_mark_red_fxset.vpcf", + "particles/econ/events/ti9/shovel_dig.vpcf", + "particles/econ/events/ti9/shovel_revealed_baby_roshan.vpcf", + "particles/econ/events/darkmoon_2017/darkmoon_calldown_marker_arrows.vpcf", + "particles/econ/items/void_spirit/void_spirit_immortal_2021/void_spirit_immortal_2021_astral_step_debuff.vpcf", + "particles/econ/items/viper/viper_ti7_immortal/viper_poison_debuff_ti7.vpcf", + "particles/econ/events/darkmoon_2017/darkmoon_generic_aoe.vpcf", + "particles/econ/items/clinkz/clinkz_maraxiform/clinkz_maraxiform_searing_arrow_deso.vpcf", + "particles/econ/items/centaur/centaur_crownfall_belt/centaur_crownfall_belt_retaliate.vpcf", + "particles/units/heroes/hero_medusa/medusa_mana_shield_buff.vpcf", + "particles/ui_mouseactions/range_finder_tower_aoe_target_ring.vpcf", + "particles/units/heroes/hero_omniknight/omniknight_guardian_angel_wings.vpcf", + "particles/econ/items/bristleback/ti7_head_nasal_goo/bristleback_ti7_crimson_nasal_goo_proj.vpcf", + "particles/units/heroes/hero_axe/axe_beserkers_call_owner.vpcf", + "particles/econ/items/bloodseeker/bloodseeker_eztzhok_weapon/bloodseeker_bloodrage_eztzhok.vpcf", + "particles/units/heroes/hero_chaos_knight/chaos_knight_phantasm.vpcf", + "particles/units/heroes/hero_bloodseeker/bloodseeker_bloodritual_ring_lv.vpcf", + "particles/units/heroes/hero_bloodseeker/bloodseeker_spell_bloodbath_bubbles_lv.vpcf", + "particles/econ/items/lifestealer/ls_ti10_immortal/ls_ti10_immortal_infest_gold.vpcf", + "particles/units/heroes/hero_ursa/ursa_enrage_buff_2.vpcf", + "particles/units/heroes/heroes_underlord/abbysal_underlord_portal_ambient.vpcf", + "particles/bloodbath_circle.vpcf", + "particles/units/heroes/hero_pudge/pudge_meathook.vpcf", + "particles/econ/items/pudge/pudge_ti6_immortal/pudge_meathook_impact_ti6.vpcf", + "particles/units/heroes/hero_wisp/wisp_ambient.vpcf", + "particles/econ/events/fall_2021/fountain_regen_fall_2021_lvl3.vpcf", + "particles/crystal_maiden_aspect_3.vpcf", + "particles/econ/items/crystal_maiden/ti7_immortal_shoulder/cm_ti7_immortal_frostbite.vpcf", + "particles/econ/courier/courier_golden_doomling/courier_golden_doomling_ambient.vpcf", + "particles/units/heroes/hero_terrorblade/terrorblade_metamorphosis.vpcf", + "particles/econ/items/sven/sven_ti7_sword/sven_ti7_sword_spell_great_cleave_gods_strength_crit_b.vpcf", + "particles/econ/items/huskar/huskar_2021_immortal/huskar_2021_immortal_burning_spear_debuff_flame_circulate.vpcf", + "particles/units/heroes/hero_doom_bringer/doom_scorched_earth.vpcf", + "particles/econ/items/wraith_king/wraith_king_ti6_bracer/wraith_king_ti6_ambient_fireball_lava.vpcf", + "particles/units/heroes/hero_doom_bringer/doom_bringer_devour.vpcf", + "particles/econ/items/zeus/arcana_chariot/zeus_arcana_kill_explosion.vpcf", + "particles/econ/items/warlock/warlock_ti9/warlock_ti9_shadow_word_buff.vpcf", + "particles/units/heroes/hero_centaur/centaur_shard_buff_strength_counter_stack.vpcf", + "particles/units/heroes/hero_dragon_knight/dragon_knight_shard_fireball.vpcf", + "particles/nagash_world_full.vpcf", + "particles/events/crownfall/survivors/status/status_effect_burn.vpcf", + "particles/creeps/lane_creeps/creep_radiant_hulk_swipe_left.vpcf", + "particles/creeps/lane_creeps/creep_radiant_hulk_swipe_right.vpcf", + "particles/econ/items/bloodseeker/bloodseeker_crownfall_immortal/bloodseeker_crownfall_immortal_splash_ring.vpcf", + "particles/units/heroes/hero_witchdoctor/witchdoctor_voodoo_restoration.vpcf", + "particles/econ/items/winter_wyvern/winter_wyvern_ti7/wyvern_cold_embrace_ti7buff.vpcf", + "particles/econ/items/ancient_apparition/ancient_apparation_ti8/ancient_ice_vortex_ti8.vpcf", + "particles/generic_gameplay/generic_slowed_cold.vpcf", + "particles/econ/items/crystal_maiden/crystal_maiden_maiden_of_icewrack/maiden_freezing_field_snow_arcana1.vpcf", + "particles/units/heroes/hero_crystalmaiden/maiden_frostbite_buff.vpcf", + "particles/econ/items/crystal_maiden/ti9_immortal_staff/cm_ti9_staff_lvlup_globe.vpcf", + "particles/econ/events/snowball/snowball_projectile.vpcf", + "particles/units/heroes/hero_ancient_apparition/ancient_apparition_ice_blast_debuff.vpcf", + "particles/rain_fx/econ_weather_sirocco.vpcf", + "particles/rain_fx/econ_weather_pestilence.vpcf", + "particles/winter_fx/weather_plateau_snow.vpcf", + "particles/rain_fx/econ_rain.vpcf", + "particles/units/heroes/hero_clinkz/clinkz_searing_arrow_linear_proj.vpcf", + "particles/econ/items/grimstroke/gs_fall20_immortal/gs_fall20_immortal_soul_debuff.vpcf", + "particles/econ/items/grimstroke/gs_fall20_immortal/gs_fall20_immortal_soul_dragon_model.vpcf", + "particles/econ/items/legion/legion_fallen/legion_fallen_press_buff.vpcf", + "particles/heroes/dragon_knight_breathe_fire_meta.vpcf", + "particles/units/heroes/hero_dragon_knight/dragon_knight_breathe_fire.vpcf", + "particles/generic_gameplay/generic_lifesteal.vpcf", + "particles/econ/items/medusa/medusa_daughters/medusa_daughters_mana_shield.vpcf", + "particles/econ/items/gyrocopter/hero_gyrocopter_gyrotechnics/gyro_base_attack.vpcf", + "particles/units/heroes/hero_jakiro/jakiro_liquid_fire_debuff.vpcf", + "particles/econ/items/jakiro/jakiro_ti10_immortal/jakiro_ti10_macropyre.vpcf", + "particles/units/heroes/hero_warlock/warlock_rain_of_chaos_start.vpcf", + "particles/juggernaut_step.vpcf", + "particles/econ/items/warlock/warlock_ti10_head/warlock_ti_10_fatal_bonds_icon.vpcf", + "particles/units/heroes/hero_bloodseeker/bloodseeker_bloodbath.vpcf", + "particles/ping_player_clown.vpcf", + "particles/store.vpcf", + "particles/boss_tinker_laser_preview_vector.vpcf", + "particles/econ/items/axe/ti9_jungle_axe/ti9_jungle_axe_culling_blade_sprint_fire.vpcf", + "particles/econ/items/templar_assassin/templar_assassin_butterfly/templar_assassin_meld_attack_butterfly.vpcf" +} +--- Число записей PrecacheResource("particle") по модулям (для лога; совпадает с телами функций ниже). +local PARTICLE_PRECACHE_COUNTS = { + weather = 4, + spawnManager = 3, + blackshop = 6, + questBountyHunter = 2, + vampirism = 1, + cutscenes = 2 +} +function ____exports.precacheMiscDistributedAndOrphanParticles(self, context) + precacheWeatherParticles(nil, context) + precacheKittyFlexResources(nil, context) + precacheSpawnManagerEffectParticles(nil, context) + precacheBlackshopParticles(nil, context) + precacheQuestBountyHunterParticles(nil, context) + precacheVampirismParticle(nil, context) + precacheCutsceneParticles(nil, context) + for ____, p in ipairs(LEGACY_GAMEMODE_PARTICLES) do + PrecacheResource("particle", p, context) + end + local legacyN = #LEGACY_GAMEMODE_PARTICLES + local sum = PARTICLE_PRECACHE_COUNTS.weather + PARTICLE_PRECACHE_COUNTS.spawnManager + PARTICLE_PRECACHE_COUNTS.blackshop + PARTICLE_PRECACHE_COUNTS.questBountyHunter + PARTICLE_PRECACHE_COUNTS.vampirism + PARTICLE_PRECACHE_COUNTS.cutscenes + legacyN + print(((((((((((((((("[Precache] Частицы: SoundSystem/погода=" .. tostring(PARTICLE_PRECACHE_COUNTS.weather)) .. ", SpawnManager=") .. tostring(PARTICLE_PRECACHE_COUNTS.spawnManager)) .. ", BlackShop=") .. tostring(PARTICLE_PRECACHE_COUNTS.blackshop)) .. ", Quest(BH)=") .. tostring(PARTICLE_PRECACHE_COUNTS.questBountyHunter)) .. ", vampirism=") .. tostring(PARTICLE_PRECACHE_COUNTS.vampirism)) .. ", cutscenes=") .. tostring(PARTICLE_PRECACHE_COUNTS.cutscenes)) .. ", legacy-список=") .. tostring(legacyN)) .. " → сумма вызовов Precache(particle) в этом блоке: ") .. tostring(sum)) .. " (дубликаты путей между модулями и legacy возможны)") +end +return ____exports diff --git a/scripts/vscripts/utils/precache_models_from_kv.lua b/scripts/vscripts/utils/precache_models_from_kv.lua new file mode 100644 index 0000000..4fc6e84 --- /dev/null +++ b/scripts/vscripts/utils/precache_models_from_kv.lua @@ -0,0 +1,135 @@ +local ____lualib = require("lualib_bundle") +local __TS__TypeOf = ____lualib.__TS__TypeOf +local __TS__StringEndsWith = ____lualib.__TS__StringEndsWith +local __TS__ArraySort = ____lualib.__TS__ArraySort +local ____exports = {} +--- Модели, которых нет в npc_units_custom / #base, но нужны коду (дроп, эффекты). +local EXTRA_PRECACHE_MODELS = { + "models/props_gameplay/roshans_banner.vmdl", + "models/development/invisiblebox.vmdl", + "models/props_gameplay/lantern/lantern_of_sight.vmdl", + "models/heroes/undying/undying_tower.vmdl", + "models/items/lifestealer/promo_bloody_ripper_belt/promo_bloody_ripper_belt.vmdl", + "models/items/warlock/golem/puppet_summoner_golem/puppet_summoner_golem.vmdl", + "models/items/lifestealer/promo_bloody_ripper_head/promo_bloody_ripper_head.vmdl", + "models/items/lifestealer/promo_bloody_ripper_back/promo_bloody_ripper_back.vmdl", + "models/items/lifestealer/promo_bloody_ripper_arms/promo_bloody_ripper_arms.vmdl", + "models/items/kunkka/spiritoftide_gloves/spiritoftide_gloves.vmdl", + "models/items/kunkka/kunkka_bandana.vmdl", + "models/items/kunkka/claddish_shoulder/claddish_shoulder.vmdl", + "models/items/kunkka/arm_lev_pipe/arm_lev_pipe.vmdl", + "models/heroes/kunkka/kunkka_shoulders.vmdl", + "models/items/kunkka/kunkka_shadow_blade/kunkka_shadow_blade.vmdl", + "models/heroes/dragon_knight_persona/dk_persona_armor.vmdl", + "models/heroes/dragon_knight_persona/dk_persona_head_hair.vmdl", + "models/items/omniknight/stalwart_back/stalwart_back.vmdl", + "models/heroes/omniknight/head.vmdl", + "models/items/omniknight/grey_night_head/grey_night_head.vmdl", + "models/items/lina/magnificentflame_head/magnificentflame_head.vmdl", + "models/items/lina/pw_fire_lotus/lina_belt.vmdl", + "models/items/lina/everlastingheat_arms/everlastingheat_arms.vmdl", + "models/items/lina/ember_crane_neck/ember_crane_neck.vmdl", + "models/items/crystal_maiden/esl_frozen_lotus_head/esl_frozen_lotus_head.vmdl", + "models/items/crystal_maiden/esl_frozen_lotus_shoulder/esl_frozen_lotus_shoulder.vmdl", + "models/items/crystal_maiden/esl_frozen_lotus_arms/esl_frozen_lotus_arms.vmdl", + "models/items/crystal_maiden/esl_frozen_lotus_back/esl_frozen_lotus_back.vmdl", + "models/heroes/bard/bard_frog_upperbody.vmdl", + "models/heroes/bard/bard_frog_lowerbody.vmdl", + "models/heroes/bard/bard_frog_weapon.vmdl", + "models/items/witchdoctor/bonkers_the_mad/bonkers_the_mad.vmdl", + "models/items/witchdoctor/teller_of_the_auspice_belt/teller_of_the_auspice_belt.vmdl", + "models/items/witchdoctor/teller_of_the_auspice_head/teller_of_the_auspice_head.vmdl", + "models/items/witchdoctor/teller_of_the_auspice_weapon/teller_of_the_auspice_weapon.vmdl", + "models/items/grimstroke/grimreaver_hood/grimreaver_hood.vmdl", + "models/items/grimstroke/the_chained_scribe_head/the_chained_scribe_head.vmdl", + "models/items/grimstroke/the_chained_scribe_belt/the_chained_scribe_belt.vmdl", + "models/items/grimstroke/the_chained_scribe_armor/the_chained_scribe_armor.vmdl", + "models/items/grimstroke/grimreaver_scythe/grimreaver_scythe.vmdl", + "models/items/death_prophet/woman_of_the_snow_belt/woman_of_the_snow_belt.vmdl", + "models/items/death_prophet/woman_of_the_snow_legs/woman_of_the_snow_legs.vmdl", + "models/items/death_prophet/woman_of_the_snow_misc/woman_of_the_snow_misc.vmdl", + "models/items/death_prophet/woman_of_the_snow_head/woman_of_the_snow_head.vmdl", + "models/items/death_prophet/woman_of_the_snow_armor/woman_of_the_snow_armor.vmdl" +} +local function collectVmdlStringsFromKv(self, value, bucket) + if value == nil or value == nil then + return + end + local t = __TS__TypeOf(value) + if t == "string" then + local s = value + if __TS__StringEndsWith(s, ".vmdl") then + bucket[s] = true + end + return + end + if t == "object" then + local o = value + for key in pairs(o) do + collectVmdlStringsFromKv(nil, o[key], bucket) + end + end +end +--- KV, из которых LoadKeyValues подмешивает #base и собирает дерево как у движка. +local NPC_KV_MODEL_SOURCES = {"scripts/npc/npc_units_custom.txt", "scripts/npc/npc_items_custom.txt"} +local LOG_PFX = "[precache_models_from_kv]" +local function sortedModelPaths(self, bucket) + local out = {} + for p in pairs(bucket) do + out[#out + 1] = p + end + __TS__ArraySort(out) + return out +end +local function printModelList(self, title, paths) + print(((((LOG_PFX .. " --- ") .. title) .. " (") .. tostring(#paths)) .. ") ---") + for ____, p in ipairs(paths) do + print((LOG_PFX .. " model: ") .. p) + end +end +--- Прекеширует все .vmdl из npc_units_custom и npc_items_custom (с их #base). +function ____exports.precacheModelsFromNpcUnitsCustomKv(self, context) + local bucket = {} + --- Модели только из этого корневого KV (для лога; дубликаты между файлами возможны). + local perSource = {} + for ____, path in ipairs(NPC_KV_MODEL_SOURCES) do + local kv = LoadKeyValues(path) + if kv == nil then + print((LOG_PFX .. " Не удалось загрузить ") .. path) + else + local ____local = {} + collectVmdlStringsFromKv(nil, kv, ____local) + perSource[path] = ____local + for m in pairs(____local) do + bucket[m] = true + end + end + end + local extraBucket = {} + for ____, path in ipairs(EXTRA_PRECACHE_MODELS) do + bucket[path] = true + extraBucket[path] = true + end + print(LOG_PFX .. " Список моделей к прекешу:") + for ____, path in ipairs(NPC_KV_MODEL_SOURCES) do + local ____local = perSource[path] + if ____local ~= nil then + printModelList( + nil, + path, + sortedModelPaths(nil, ____local) + ) + end + end + printModelList( + nil, + "EXTRA_PRECACHE_MODELS (не из KV)", + sortedModelPaths(nil, extraBucket) + ) + local allPaths = sortedModelPaths(nil, bucket) + for ____, modelPath in ipairs(allPaths) do + PrecacheResource("model", modelPath, context) + end + print((LOG_PFX .. " Итого запрошено PrecacheResource(model): ") .. tostring(#allPaths)) +end +return ____exports diff --git a/scripts/vscripts/utils/real_lobby_player.lua b/scripts/vscripts/utils/real_lobby_player.lua new file mode 100644 index 0000000..2449acd --- /dev/null +++ b/scripts/vscripts/utils/real_lobby_player.lua @@ -0,0 +1,66 @@ +local ____lualib = require("lualib_bundle") +local __TS__ArraySort = ____lualib.__TS__ArraySort +local ____exports = {} +--- Кто считается «реальным» игроком в лобби (совпадает с бывшей логикой GameMode). +-- Без проверки контроллера в список попадают пустые/«невидимые» слоты на CUSTOM_GAME_SETUP. +function ____exports.isRealLobbyPlayer(self, playerId) + if not PlayerResource:IsValidPlayerID(playerId) then + return false + end + if not PlayerResource:IsValidPlayer(playerId) then + return false + end + if PlayerResource:IsFakeClient(playerId) then + return false + end + return PlayerResource:GetPlayer(playerId) ~= nil +end +function ____exports.countRealLobbyPlayers(self) + local n = 0 + do + local playerId = 0 + while playerId < DOTA_MAX_PLAYERS do + if ____exports.isRealLobbyPlayer(nil, playerId) then + n = n + 1 + end + playerId = playerId + 1 + end + end + return n +end +--- Игроки, участвующие в PvE-статистике матча: реальное лобби + команда +-- (если слотов Dire нет — только GOODGUYS). +function ____exports.collectStatsEligiblePlayerIds(self) + local ids = {} + local badMax = GameRules:GetCustomGameTeamMaxPlayers(DOTA_TEAM_BADGUYS) + do + local i = 0 + while i < DOTA_MAX_PLAYERS do + do + local pid = i + if not ____exports.isRealLobbyPlayer(nil, pid) then + goto __continue10 + end + local team = PlayerResource:GetTeam(pid) + if badMax == 0 then + if team ~= DOTA_TEAM_GOODGUYS then + goto __continue10 + end + else + if team ~= DOTA_TEAM_GOODGUYS and team ~= DOTA_TEAM_BADGUYS then + goto __continue10 + end + end + ids[#ids + 1] = pid + end + ::__continue10:: + i = i + 1 + end + end + __TS__ArraySort( + ids, + function(____, a, b) return a - b end + ) + return ids +end +return ____exports diff --git a/scripts/vscripts/utils/store_effects.lua b/scripts/vscripts/utils/store_effects.lua new file mode 100644 index 0000000..6aee7c7 --- /dev/null +++ b/scripts/vscripts/utils/store_effects.lua @@ -0,0 +1,75 @@ +--[[ Generated with https://github.com/TypeScriptToLua/TypeScriptToLua ]] +local ____exports = {} +local ____store_manager = require("store_manager") +local StoreManager = ____store_manager.StoreManager +--- Микрокэш на один вызов GetGameTime() — снижает давление CustomNetTables при частых GetEffectName. +local clientGetEffectCacheKey = "" +local clientGetEffectCacheTime = -999999 +local clientGetEffectCacheValue = nil +--- Получить все активные эффекты героя +-- +-- @param hero - Герой игрока +-- @returns Объект с активными эффектами по типам: { "effect": "skin_effect_fire", "wings": "wings_demon", "model": null } +function ____exports.GetAllEffects(self, hero) + local playerId = hero:GetPlayerOwnerID() + if playerId < 0 then + return {} + end + local storeManager = StoreManager:getInstance() + return storeManager:getPlayerActiveEffects(playerId) +end +--- Получить активный эффект конкретного типа для героя +-- +-- @param hero - Герой игрока +-- @param effectType - Тип эффекта ("effect", "wings", "model") +-- @returns ID эффекта или null +function ____exports.GetEffect(self, hero, effectType) + local playerId = hero:GetPlayerOwnerID() + if playerId < 0 then + return nil + end + if IsClient() then + local now = GameRules:GetGameTime() + local cacheKey = (tostring(playerId) .. ":") .. effectType + if clientGetEffectCacheKey == cacheKey and clientGetEffectCacheTime == now then + return clientGetEffectCacheValue + end + local playerInfo = CustomNetTables:GetTableValue( + "player_info", + tostring(playerId) + ) + local value = nil + if playerInfo and playerInfo.active_effects and playerInfo.active_effects[effectType] then + value = playerInfo.active_effects[effectType] + end + clientGetEffectCacheKey = cacheKey + clientGetEffectCacheTime = now + clientGetEffectCacheValue = value + return value + end + local storeManager = StoreManager:getInstance() + return storeManager:getPlayerActiveEffect(playerId, effectType) +end +--- Проверить, надёт ли конкретный эффект на героя +-- +-- @param hero - Герой игрока +-- @param effectId - ID эффекта +-- @param effectType - Опционально: тип эффекта для проверки конкретного типа +-- @returns true если эффект надёт, false если нет +function ____exports.HasEffect(self, hero, effectId, effectType) + local playerId = hero:GetPlayerOwnerID() + if playerId < 0 then + return false + end + local storeManager = StoreManager:getInstance() + return storeManager:isEffectEquipped(playerId, effectId, effectType) +end +local g = _G +g.GetAllEffects = ____exports.GetAllEffects +g.GetEffect = ____exports.GetEffect +g.HasEffect = ____exports.HasEffect +local env = getfenv(nil, 0) +env.GetAllEffects = ____exports.GetAllEffects +env.GetEffect = ____exports.GetEffect +env.HasEffect = ____exports.HasEffect +return ____exports diff --git a/scripts/vscripts/utils/summon_owner_attack_proxy.lua b/scripts/vscripts/utils/summon_owner_attack_proxy.lua new file mode 100644 index 0000000..988f062 --- /dev/null +++ b/scripts/vscripts/utils/summon_owner_attack_proxy.lua @@ -0,0 +1,115 @@ +local ____lualib = require("lualib_bundle") +local Set = ____lualib.Set +local __TS__New = ____lualib.__TS__New +local __TS__ObjectAssign = ____lualib.__TS__ObjectAssign +local __TS__Class = ____lualib.__TS__Class +local __TS__ClassExtends = ____lualib.__TS__ClassExtends +local __TS__Decorate = ____lualib.__TS__Decorate +local ____exports = {} +local ____dota_ts_adapter = require("lib.dota_ts_adapter") +local BaseModifier = ____dota_ts_adapter.BaseModifier +local registerModifier = ____dota_ts_adapter.registerModifier +--- Модификаторы владельца, которые не пробрасываем (крит обрабатывается на самом призыве). +local OWNER_ATTACK_FORWARD_EXCLUDED_MODIFIERS = __TS__New(Set, {"modifier_stacking_crit", "modifier_summon_owner_attack_proxy"}) +--- Герой-владелец для призыва или illusions-like юнита с SetOwner. +function ____exports.getRealOwnerHeroFromUnit(self, unit) + if not unit or not IsValidEntity(unit) then + return nil + end + if unit:IsRealHero() then + return unit + end + local owner = unit:GetOwner() + if not owner or not IsValidEntity(owner) or not owner:IsRealHero() then + return nil + end + return owner +end +local function forEachOwnerAttackModifier(self, owner, visitor) + local seenNames = __TS__New(Set) + do + local i = 0 + while i < owner:GetModifierCount() do + do + local modifierName = owner:GetModifierNameByIndex(i) + if not modifierName or OWNER_ATTACK_FORWARD_EXCLUDED_MODIFIERS:has(modifierName) or seenNames:has(modifierName) then + goto __continue7 + end + seenNames:add(modifierName) + local modifier = owner:FindModifierByName(modifierName) + if not modifier or not IsValidEntity(modifier) then + goto __continue7 + end + visitor(nil, modifier, modifierName) + end + ::__continue7:: + i = i + 1 + end + end +end +--- Пробрасывает атаку призыва в on-hit/on-attack модификаторы героя-владельца. +function ____exports.forwardOwnerAttackModifiersFromSummon(self, owner, summonEvent, hook) + if not IsServer() then + return + end + if not owner or not IsValidEntity(owner) or not owner:IsAlive() then + return + end + local ownerEvent = __TS__ObjectAssign({}, summonEvent, {attacker = owner}) + forEachOwnerAttackModifier( + nil, + owner, + function(____, modifier) + local hookFn = modifier[hook] + if type(hookFn) ~= "function" then + return + end + hookFn(modifier, ownerEvent) + end + ) +end +____exports.modifier_summon_owner_attack_proxy = __TS__Class() +local modifier_summon_owner_attack_proxy = ____exports.modifier_summon_owner_attack_proxy +modifier_summon_owner_attack_proxy.name = "modifier_summon_owner_attack_proxy" +modifier_summon_owner_attack_proxy.____file_path = "scripts/vscripts/utils/summon_owner_attack_proxy.lua" +__TS__ClassExtends(modifier_summon_owner_attack_proxy, BaseModifier) +function modifier_summon_owner_attack_proxy.prototype.IsHidden(self) + return true +end +function modifier_summon_owner_attack_proxy.prototype.IsPurgable(self) + return false +end +function modifier_summon_owner_attack_proxy.prototype.RemoveOnDeath(self) + return false +end +function modifier_summon_owner_attack_proxy.prototype.DeclareFunctions(self) + return {MODIFIER_EVENT_ON_ATTACK, MODIFIER_EVENT_ON_ATTACK_LANDED} +end +function modifier_summon_owner_attack_proxy.prototype.forwardToOwner(self, event, hook) + if not IsServer() then + return + end + local parent = self:GetParent() + if not parent or not IsValidEntity(parent) or event.attacker ~= parent then + return + end + local owner = ____exports.getRealOwnerHeroFromUnit(nil, parent) + if not owner then + return + end + ____exports.forwardOwnerAttackModifiersFromSummon(nil, owner, event, hook) +end +function modifier_summon_owner_attack_proxy.prototype.OnAttack(self, event) + self:forwardToOwner(event, "OnAttack") +end +function modifier_summon_owner_attack_proxy.prototype.OnAttackLanded(self, event) + self:forwardToOwner(event, "OnAttackLanded") +end +modifier_summon_owner_attack_proxy = __TS__Decorate( + modifier_summon_owner_attack_proxy, + modifier_summon_owner_attack_proxy, + {registerModifier(nil)}, + {kind = "class", name = "modifier_summon_owner_attack_proxy"} +) +____exports.modifier_summon_owner_attack_proxy = modifier_summon_owner_attack_proxy +return ____exports diff --git a/scripts/vscripts/utils/sync_owner_modifiers_to_summon.lua b/scripts/vscripts/utils/sync_owner_modifiers_to_summon.lua new file mode 100644 index 0000000..29e7498 --- /dev/null +++ b/scripts/vscripts/utils/sync_owner_modifiers_to_summon.lua @@ -0,0 +1,125 @@ +local ____lualib = require("lualib_bundle") +local __TS__ArrayFilter = ____lualib.__TS__ArrayFilter +local ____exports = {} +--- Модификаторы призыва, которые нельзя снимать при синхронизации. +local SUMMON_ONLY_MODIFIER_NAMES = {modifier_dark_friends = true, modifier_stacking_crit = true} +--- Модификаторы владельца, которые не копируем на призыв. +local OWNER_MODIFIER_SYNC_EXCLUDED = {modifier_dark_friends = true, modifier_dark_friends_create = true} +local function isOwnerModifierEligibleForSummonSync(self, mod) + local name = mod:GetName() + if OWNER_MODIFIER_SYNC_EXCLUDED[name] then + return false + end + if mod.IsDebuff and mod:IsDebuff() then + return false + end + return true +end +local function groupModifiersByName(self, modifiers) + local groups = {} + for ____, mod in ipairs(modifiers) do + local name = mod:GetName() + if not groups[name] then + groups[name] = {} + end + local ____groups_name_0 = groups[name] + ____groups_name_0[#____groups_name_0 + 1] = mod + end + return groups +end +local function syncStackingCritState(self, ownerMod, summonMod) + summonMod.critModifiers = ownerMod.critModifiers + local ____summonMod_2 = summonMod + local ____ownerMod_guaranteedCritCount_1 = ownerMod.guaranteedCritCount + if ____ownerMod_guaranteedCritCount_1 == nil then + ____ownerMod_guaranteedCritCount_1 = 0 + end + ____summonMod_2.guaranteedCritCount = ____ownerMod_guaranteedCritCount_1 + local ____summonMod_4 = summonMod + local ____ownerMod_guaranteedCritUntilDestroy_3 = ownerMod.guaranteedCritUntilDestroy + if ____ownerMod_guaranteedCritUntilDestroy_3 == nil then + ____ownerMod_guaranteedCritUntilDestroy_3 = false + end + ____summonMod_4.guaranteedCritUntilDestroy = ____ownerMod_guaranteedCritUntilDestroy_3 +end +local function syncModifierInstance(self, ownerMod, summonMod) + local ownerStacks = ownerMod:GetStackCount() + if summonMod:GetStackCount() ~= ownerStacks then + summonMod:SetStackCount(ownerStacks) + end + local ownerDuration = ownerMod:GetDuration() + if ownerDuration > 0 then + local remaining = ownerMod:GetRemainingTime() + if remaining > 0 and math.abs(summonMod:GetRemainingTime() - remaining) > 0.15 then + summonMod:SetDuration(remaining, false) + end + end + if ownerMod:GetName() == "modifier_stacking_crit" then + syncStackingCritState(nil, ownerMod, summonMod) + end +end +--- Копирует все бафф-модификаторы владельца на призыв (стаки, длительность, криты). +function ____exports.syncOwnerModifiersToSummon(self, owner, summon) + if not IsServer() then + return + end + if not owner or not summon or not IsValidEntity(owner) or not IsValidEntity(summon) then + return + end + local ownerEligible = {} + for ____, mod in ipairs(owner:FindAllModifiers()) do + if isOwnerModifierEligibleForSummonSync(nil, mod) then + ownerEligible[#ownerEligible + 1] = mod + end + end + local ownerGroups = groupModifiersByName(nil, ownerEligible) + local summonGroups = groupModifiersByName( + nil, + summon:FindAllModifiers() + ) + for name in pairs(summonGroups) do + do + if SUMMON_ONLY_MODIFIER_NAMES[name] then + goto __continue21 + end + if not ownerGroups[name] then + for ____, mod in ipairs(summonGroups[name]) do + mod:Destroy() + end + end + end + ::__continue21:: + end + for name in pairs(ownerGroups) do + local ownerInstances = ownerGroups[name] + local summonInstances = __TS__ArrayFilter( + summon:FindAllModifiers(), + function(____, mod) return mod:GetName() == name end + ) + while #summonInstances < #ownerInstances do + local source = ownerInstances[#summonInstances + 1] + local caster = source:GetCaster() or owner + local ability = source:GetAbility() or nil + local duration = source:GetDuration() > 0 and source:GetRemainingTime() or -1 + local added = summon:AddNewModifier(caster, ability, name, {duration = duration}) + if not added then + break + end + summonInstances[#summonInstances + 1] = added + end + while #summonInstances > #ownerInstances do + local extra = table.remove(summonInstances) + if extra ~= nil then + extra:Destroy() + end + end + do + local i = 0 + while i < #ownerInstances do + syncModifierInstance(nil, ownerInstances[i + 1], summonInstances[i + 1]) + i = i + 1 + end + end + end +end +return ____exports diff --git a/scripts/vscripts/utils/utils.lua b/scripts/vscripts/utils/utils.lua new file mode 100644 index 0000000..e4add72 --- /dev/null +++ b/scripts/vscripts/utils/utils.lua @@ -0,0 +1,123 @@ +--[[ Generated with https://github.com/TypeScriptToLua/TypeScriptToLua ]] +local ____exports = {} +function ____exports.SetGoldUsually(self, unit) + local difficulty_multiplier = 1 + local baseMinGold = unit:GetMinimumGoldBounty() + local baseMaxGold = unit:GetMaximumGoldBounty() + local expirience = unit:GetDeathXP() + local expirience_full = math.floor(expirience * difficulty_multiplier) + local minGold = math.floor(baseMinGold * difficulty_multiplier) + local maxGold = math.floor(baseMaxGold * difficulty_multiplier) + unit:SetDeathXP(expirience_full) + unit:SetMinimumGoldBounty(minGold) + unit:SetMaximumGoldBounty(maxGold) +end +function ____exports.GiveGoldPlayers(self, amount) + local players = PlayerResource:GetPlayerCountForTeam(DOTA_TEAM_GOODGUYS) + do + local i = 0 + while i < players do + local player = PlayerResource:GetPlayer(i) + if player then + local hero = player:GetAssignedHero() + if hero ~= nil and hero ~= nil then + hero:ModifyGold(amount, true, DOTA_ModifyGold_Unspecified) + end + end + i = i + 1 + end + end +end +_G.HasShard = function(____, unit) + if unit:HasModifier("modifier_item_aghanims_shard") then + return true + end + return false +end +_G.HasScepter = function(____, unit) + if not unit or unit:IsNull() then + return false + end + if unit:HasModifier("modifier_item_aghanims_scepter") then + return true + end + if unit:HasModifier("modifier_item_ultimate_scepter") then + return true + end + if unit:HasModifier("modifier_item_ultimate_scepter_consumed") then + return true + end + return unit:HasScepter() +end +_G.HasTalent = function(____, unit, talentName) + local talent = unit:FindAbilityByName(talentName) + return talent ~= nil and talent:GetLevel() > 0 +end +____exports.SpawnUnitsAndAttack = function(____, name, location, count, data, team) + local ____data_0 = data + local attackPoint = ____data_0.attackPoint + local target = ____data_0.target + local units = {} + do + local index = 0 + while index < count do + local unit = CreateUnitByName( + name, + location, + true, + nil, + nil, + team + ) + units[#units + 1] = unit + if attackPoint then + Timers:CreateTimer( + 0.1, + function() + unit:Stop() + ExecuteOrderFromTable({ + UnitIndex = unit:entindex(), + Queue = false, + OrderType = DOTA_UNIT_ORDER_ATTACK_MOVE, + Position = attackPoint + }) + end + ) + elseif target then + unit:SetContextThink( + unit:GetUnitName(), + function() + ExecuteOrderFromTable({ + UnitIndex = unit:entindex(), + OrderType = DOTA_UNIT_ORDER_ATTACK_TARGET, + TargetIndex = target:entindex() + }) + return 0.25 + end, + 0.2 + ) + end + index = index + 1 + end + end + return units +end +____exports.GetAllHeroes = function() + local heroes = {} + do + local index = 0 + while index < PlayerResource:GetPlayerCount() do + do + local hero = PlayerResource:GetSelectedHeroEntity(index) + if not hero then + goto __continue22 + end + heroes[#heroes + 1] = hero + end + ::__continue22:: + index = index + 1 + end + end + return heroes +end +return ____exports diff --git a/scripts/vscripts/utils/vampirism.lua b/scripts/vscripts/utils/vampirism.lua new file mode 100644 index 0000000..b1e398a --- /dev/null +++ b/scripts/vscripts/utils/vampirism.lua @@ -0,0 +1,147 @@ +--[[ Generated with https://github.com/TypeScriptToLua/TypeScriptToLua ]] +local ____exports = {} +local manageVampirismModifier +require("lib.dota_ts_adapter") +local ____modifier_vampirism = require("abilities.modifiers.modifier_vampirism") +local modifier_vampirism = ____modifier_vampirism.modifier_vampirism +function ____exports.getPhysicalVampirism(self, hero) + if not hero or not IsValidEntity(hero) then + return 0 + end + local playerId = hero:GetPlayerOwnerID() + if playerId == nil or playerId == nil or playerId < 0 then + return 0 + end + local key = "physical_vampirism_" .. tostring(playerId) + local data = CustomNetTables:GetTableValue("custom_stats", key) + local ____data_0 + if data then + ____data_0 = data.value + else + ____data_0 = 0 + end + local value = ____data_0 + return value +end +function ____exports.getMagicalVampirism(self, hero) + if not hero or not IsValidEntity(hero) then + return 0 + end + local playerId = hero:GetPlayerOwnerID() + if playerId == nil or playerId == nil or playerId < 0 then + return 0 + end + local key = "magical_vampirism_" .. tostring(playerId) + local data = CustomNetTables:GetTableValue("custom_stats", key) + local ____data_1 + if data then + ____data_1 = data.value + else + ____data_1 = 0 + end + local value = ____data_1 + return value +end +function manageVampirismModifier(self, hero) + if not IsServer() then + return + end + local physicalVampirism = ____exports.getPhysicalVampirism(nil, hero) + local magicalVampirism = ____exports.getMagicalVampirism(nil, hero) + local hasVampirism = physicalVampirism > 0 or magicalVampirism > 0 + local hasModifier = hero:HasModifier("modifier_vampirism") + if hasVampirism and not hasModifier then + hero:AddNewModifier( + hero, + getModifierSourceAbility(nil, hero), + modifier_vampirism.name, + {} + ) + elseif not hasVampirism and hasModifier then + hero:RemoveModifierByName("modifier_vampirism") + end +end +--- Партикл эффекта вампиризма (modifier без своего Precache в KV) +function ____exports.precacheVampirismParticle(self, context) + PrecacheResource("particle", "particles/units/heroes/hero_bloodseeker/bloodseeker_bloodbath.vpcf", context) +end +local playerIdToPhysicalVampirism = {} +local playerIdToMagicalVampirism = {} +if not playerIdToPhysicalVampirism then + playerIdToPhysicalVampirism = {} +end +if not playerIdToMagicalVampirism then + playerIdToMagicalVampirism = {} +end +local function getPlayerKey(self, hero, ____type) + local playerId = hero:GetPlayerOwnerID() + if playerId == nil or playerId == nil or playerId < 0 then + return nil + end + return (____type .. "_") .. tostring(playerId) +end +function ____exports.setPhysicalVampirism(self, hero, vampirism) + if not IsServer() then + return + end + local playerId = hero:GetPlayerOwnerID() + local key = "physical_vampirism_" .. tostring(playerId) + CustomNetTables:SetTableValue("custom_stats", key, {value = vampirism}) +end +function ____exports.addPhysicalVampirism(self, hero, delta) + if not IsServer() then + return + end + local current = ____exports.getPhysicalVampirism(nil, hero) + local newValue = current + delta + ____exports.setPhysicalVampirism(nil, hero, newValue) + manageVampirismModifier(nil, hero) +end +function ____exports.reducePhysicalVampirism(self, hero, delta) + if not IsServer() then + return + end + local current = ____exports.getPhysicalVampirism(nil, hero) + local newValue = math.max(0, current - delta) + ____exports.setPhysicalVampirism(nil, hero, newValue) + manageVampirismModifier(nil, hero) +end +function ____exports.setMagicalVampirism(self, hero, vampirism) + if not IsServer() then + return + end + local playerId = hero:GetPlayerOwnerID() + if playerId == nil or playerId == nil or playerId < 0 then + return + end + local key = "magical_vampirism_" .. tostring(playerId) + CustomNetTables:SetTableValue("custom_stats", key, {value = vampirism}) +end +function ____exports.addMagicalVampirism(self, hero, delta) + if not IsServer() then + return + end + local current = ____exports.getMagicalVampirism(nil, hero) + local newValue = current + delta + ____exports.setMagicalVampirism(nil, hero, newValue) + manageVampirismModifier(nil, hero) +end +function ____exports.reduceMagicalVampirism(self, hero, delta) + if not IsServer() then + return + end + local current = ____exports.getMagicalVampirism(nil, hero) + local newValue = math.max(0, current - delta) + ____exports.setMagicalVampirism(nil, hero, newValue) + manageVampirismModifier(nil, hero) +end +local g = _G +g.setPhysicalVampirism = ____exports.setPhysicalVampirism +g.addPhysicalVampirism = ____exports.addPhysicalVampirism +g.reducePhysicalVampirism = ____exports.reducePhysicalVampirism +g.getPhysicalVampirism = ____exports.getPhysicalVampirism +g.setMagicalVampirism = ____exports.setMagicalVampirism +g.addMagicalVampirism = ____exports.addMagicalVampirism +g.reduceMagicalVampirism = ____exports.reduceMagicalVampirism +g.getMagicalVampirism = ____exports.getMagicalVampirism +return ____exports diff --git a/scripts/vscripts/utils/vector_ws.lua b/scripts/vscripts/utils/vector_ws.lua new file mode 100644 index 0000000..5bd0d43 --- /dev/null +++ b/scripts/vscripts/utils/vector_ws.lua @@ -0,0 +1,23 @@ +--[[ Generated with https://github.com/TypeScriptToLua/TypeScriptToLua ]] +local ____exports = {} +local g = _G +local function toVectorWSImpl(_self, v) + local ctor = g.VectorWS + if type(ctor) == "function" then + return ctor(v.x, v.y, v.z) + end + return v +end +--- Поворот точки вокруг origin только по yaw (град), эквивалент RotatePosition(origin, QAngle(0, yaw, 0), point) без нативного API. +local function rotatePositionYawImpl(_self, origin, yawDegrees, point) + local rad = yawDegrees * (math.pi / 180) + local off = point - origin + local c = math.cos(rad) + local s = math.sin(rad) + local rx = off.x * c - off.y * s + local ry = off.x * s + off.y * c + return Vector(origin.x + rx, origin.y + ry, origin.z + off.z) +end +g.toVectorWS = toVectorWSImpl +g.rotatePositionYaw = rotatePositionYawImpl +return ____exports diff --git a/scripts/vscripts/vector_target_cast_manager.lua b/scripts/vscripts/vector_target_cast_manager.lua new file mode 100644 index 0000000..b195fbf --- /dev/null +++ b/scripts/vscripts/vector_target_cast_manager.lua @@ -0,0 +1,74 @@ +local ____lualib = require("lualib_bundle") +local __TS__Class = ____lualib.__TS__Class +local Map = ____lualib.Map +local __TS__New = ____lualib.__TS__New +local ____exports = {} +--- Короткоживущий буфер вектор-каста с клиента. +-- Храним данные только на одно ближайшее применение способности. +____exports.VectorTargetCastManager = __TS__Class() +local VectorTargetCastManager = ____exports.VectorTargetCastManager +VectorTargetCastManager.name = "VectorTargetCastManager" +VectorTargetCastManager.____file_path = "scripts/vscripts/vector_target_cast_manager.lua" +function VectorTargetCastManager.prototype.____constructor(self) + self.casts = __TS__New(Map) + self.ttl = 1.25 + CustomGameEventManager:RegisterListener( + "vector_target_cast_update", + function(_, rawEvent) + self:onVectorCast(rawEvent) + end + ) +end +function VectorTargetCastManager.getInstance(self) + if not ____exports.VectorTargetCastManager.instance then + ____exports.VectorTargetCastManager.instance = __TS__New(____exports.VectorTargetCastManager) + end + return ____exports.VectorTargetCastManager.instance +end +function VectorTargetCastManager.prototype.getKey(self, playerId, abilityName) + return (tostring(playerId) .. ":") .. abilityName +end +function VectorTargetCastManager.prototype.onVectorCast(self, event) + local playerId = event.PlayerID or event.playerId + local abilityName = event.abilityName + if playerId == nil or playerId < 0 or not abilityName then + return + end + if event.startX == nil or event.startY == nil or event.endX == nil or event.endY == nil then + return + end + local key = self:getKey(playerId, abilityName) + self.casts:set( + key, + { + start = Vector(event.startX, event.startY, 0), + ["end"] = Vector(event.endX, event.endY, 0), + expireAt = GameRules:GetGameTime() + self.ttl + } + ) +end +function VectorTargetCastManager.prototype.consumeCast(self, playerId, abilityName) + local key = self:getKey(playerId, abilityName) + local stored = self.casts:get(key) + if not stored then + return nil + end + self.casts:delete(key) + if stored.expireAt < GameRules:GetGameTime() then + return nil + end + return {start = stored.start, ["end"] = stored["end"]} +end +function VectorTargetCastManager.prototype.hasFreshCast(self, playerId, abilityName) + local key = self:getKey(playerId, abilityName) + local stored = self.casts:get(key) + if not stored then + return false + end + if stored.expireAt < GameRules:GetGameTime() then + self.casts:delete(key) + return false + end + return true +end +return ____exports diff --git a/scripts/vscripts/wavemanager.lua b/scripts/vscripts/wavemanager.lua new file mode 100644 index 0000000..71c7e51 --- /dev/null +++ b/scripts/vscripts/wavemanager.lua @@ -0,0 +1,1777 @@ +local ____lualib = require("lualib_bundle") +local __TS__Class = ____lualib.__TS__Class +local Map = ____lualib.Map +local __TS__New = ____lualib.__TS__New +local __TS__Iterator = ____lualib.__TS__Iterator +local __TS__ArraySplice = ____lualib.__TS__ArraySplice +local __TS__StringIncludes = ____lualib.__TS__StringIncludes +local __TS__Number = ____lualib.__TS__Number +local __TS__NumberIsNaN = ____lualib.__TS__NumberIsNaN +local __TS__ParseInt = ____lualib.__TS__ParseInt +local Set = ____lualib.Set +local __TS__ArrayFilter = ____lualib.__TS__ArrayFilter +local __TS__ObjectAssign = ____lualib.__TS__ObjectAssign +local __TS__ArraySome = ____lualib.__TS__ArraySome +local __TS__ArraySetLength = ____lualib.__TS__ArraySetLength +local ____exports = {} +local ____modifier_boss_hud_health_bar = require("abilities.modifiers.modifier_boss_hud_health_bar") +local applyBossHudHealthBar = ____modifier_boss_hud_health_bar.applyBossHudHealthBar +local isBossHudHealthBarUnit = ____modifier_boss_hud_health_bar.isBossHudHealthBarUnit +local ____difficulty_manager = require("difficulty_manager") +local Difficulty = ____difficulty_manager.Difficulty +local ____utils = require("utils.utils") +local SetGoldUsually = ____utils.SetGoldUsually +local ____player_connection_state = require("utils.player_connection_state") +local DOTA_CONNECTION_STATE = ____player_connection_state.DOTA_CONNECTION_STATE +local ____SpawnManager = require("SpawnManager") +local SpawnManager = ____SpawnManager.SpawnManager +local function rawPrintFn(____, ...) + _G:print(...) +end +local ENABLE_VERBOSE_WAVE_MANAGER_LOGS = false +local ____print = ENABLE_VERBOSE_WAVE_MANAGER_LOGS and rawPrintFn or (function(____, ...) return nil end) +local GOLD_REWARD_RADIUS = 1500 +--- Кэш списка героев для AI волны — не вызывать FindUnitsInRadius с каждого крипа каждую секунду. +local WAVE_HERO_TARGET_CACHE_TTL = 0.22 +local WAVE_CREEP_HORDE_THRESHOLD = 60 +local WAVE_CREEP_MEGA_HORDE_THRESHOLD = 100 +--- Шанс 1..100 на каждую итерацию: пока выпадает успех и в пуле есть неприкреплённые способности — +-- зомби может получить сразу несколько разных бонус-абилок подряд (нейтрал, без Luck). +local WAVE_MOB_RANDOM_BONUS_ABILITY_CHANCE = 7 +--- Пул способностей для случайной выдачи волновым мобам (уже используются на npc_wave_* в KV). +-- Не включаем zombie_virus / ability_unit_less_laggy — они обычно уже есть в юните. +local WAVE_MOB_BONUS_ABILITY_POOL = { + "toxin", + "wave_full_brutality", + "ghost_evasive", + "wave_phasing_march", + "zombie_armor_decress" +} +local WAVE_SETTINGS = { + [1] = {waves = {{maxZombies = 25, spawnInterval = {min = 0.5, max = 1}, zombieTypes = {{unitName = "npc_wave_zombie", goldPerKill = 2, maxCount = 8, chance = 70}, {unitName = "npc_wave_half_zombie", goldPerKill = 4, maxCount = 6, chance = 30}}, onEndSpawns = {{unitName = "npc_wave_bearst_zombie", count = 3, goldPerKill = 2}}}, {maxZombies = 25, spawnInterval = {min = 0.6, max = 1}, zombieTypes = {{unitName = "npc_wave_zombie", goldPerKill = 2, maxCount = 8, chance = 60}, {unitName = "npc_wave_half_zombie", goldPerKill = 2, maxCount = 6, chance = 35}, {unitName = "npc_wave_toxin_zombie", goldPerKill = 4, maxCount = 3, chance = 5}}, SpawnBossOnEnd = true}, {maxZombies = 25, spawnInterval = {min = 0.5, max = 1.2}, zombieTypes = {{unitName = "npc_wave_zombie", goldPerKill = 2, maxCount = 12, chance = 60}, {unitName = "npc_wave_half_zombie", goldPerKill = 2, maxCount = 6, chance = 20}, {unitName = "npc_wave_toxin_zombie", goldPerKill = 4, maxCount = 2, chance = 5}}}}}, + [2] = {waves = {{maxZombies = 20, spawnInterval = {min = 0.5, max = 1.2}, zombieTypes = { + {unitName = "npc_wave_toxin_zombie", goldPerKill = 4, maxCount = 25, chance = 70}, + {unitName = "npc_wave_bearst_zombie", goldPerKill = 4, maxCount = 15, chance = 30}, + {unitName = "npc_wave_skeleton_zombie", goldPerKill = 3, maxCount = 25, chance = 70}, + {unitName = "npc_wave_skeleton_zombie_half", goldPerKill = 3, maxCount = 15, chance = 30}, + {unitName = "npc_wave_dead_skeleton_archer", goldPerKill = 3, maxCount = 5, chance = 20} + }}, {maxZombies = 25, spawnInterval = {min = 0.5, max = 1.2}, zombieTypes = { + {unitName = "npc_wave_toxin_zombie", goldPerKill = 4, maxCount = 25, chance = 5}, + {unitName = "npc_wave_bearst_zombie", goldPerKill = 4, maxCount = 15, chance = 5}, + {unitName = "npc_wave_skeleton_zombie", goldPerKill = 3, maxCount = 5, chance = 20}, + {unitName = "npc_wave_dead_skeleton", goldPerKill = 3, maxCount = 25, chance = 10}, + {unitName = "npc_wave_skeleton_zombie_half", goldPerKill = 2, maxCount = 15, chance = 20}, + {unitName = "npc_wave_dead_skeleton_archer", goldPerKill = 2, maxCount = 5, chance = 20} + }, SpawnBossOnEnd = true}}}, + [3] = {waves = {{maxZombies = 20, spawnInterval = {min = 0.5, max = 1.2}, zombieTypes = {{unitName = "npc_wave_dead_harpy", goldPerKill = 4, maxCount = 25, chance = 60}, {unitName = "npc_wave_toxin_2_zombie", goldPerKill = 6, maxCount = 3, chance = 30}, {unitName = "npc_wave_ghost_ranged", goldPerKill = 3, maxCount = 25, chance = 70}, {unitName = "npc_wave_bearst_zombie", goldPerKill = 4, maxCount = 15, chance = 30}}}, {maxZombies = 25, spawnInterval = {min = 0.5, max = 1.2}, zombieTypes = {{unitName = "npc_wave_dead_harpy", goldPerKill = 4, maxCount = 25, chance = 60}, {unitName = "npc_wave_toxin_2_zombie", goldPerKill = 4, maxCount = 15, chance = 30}, {unitName = "npc_wave_ghost", goldPerKill = 15, maxCount = 6, chance = 70}, {unitName = "npc_wave_ghost_ranged", goldPerKill = 5, maxCount = 25, chance = 70}}, SpawnBossOnEnd = true}}}, + [4] = {waves = {{maxZombies = 20, spawnInterval = {min = 0.5, max = 1.2}, zombieTypes = {{unitName = "npc_wave_ghoul", goldPerKill = 7, maxCount = 25, chance = 70}, {unitName = "npc_wave_toxin_2_zombie", goldPerKill = 7, maxCount = 3, chance = 30}}}, {maxZombies = 25, spawnInterval = {min = 0.5, max = 1.2}, zombieTypes = {{unitName = "npc_wave_ghoul", goldPerKill = 7, maxCount = 25, chance = 70}, {unitName = "npc_wave_toxin_2_zombie", goldPerKill = 6, maxCount = 3, chance = 30}, {unitName = "npc_wave_skeleton_assassin", goldPerKill = 5, maxCount = 25, chance = 70}, {unitName = "npc_wave_skeleton_warrior", goldPerKill = 4, maxCount = 15, chance = 30}}, SpawnBossOnEnd = true}}}, + [5] = {waves = {{maxZombies = 28, spawnInterval = {min = 0.45, max = 0.95}, zombieTypes = { + {unitName = "npc_wave_ghoul", goldPerKill = 5, maxCount = 30, chance = 30}, + {unitName = "npc_wave_ghost", goldPerKill = 6, maxCount = 20, chance = 20}, + {unitName = "npc_wave_dead_harpy", goldPerKill = 5, maxCount = 14, chance = 15}, + {unitName = "npc_wave_skeleton_assassin", goldPerKill = 5, maxCount = 18, chance = 20}, + {unitName = "npc_wave_toxin_2_zombie", goldPerKill = 5, maxCount = 10, chance = 15} + }}, {maxZombies = 32, spawnInterval = {min = 0.4, max = 0.85}, zombieTypes = {{unitName = "npc_wave_skeleton_warrior", goldPerKill = 5, maxCount = 22, chance = 25}, {unitName = "npc_wave_ghost_ranged", goldPerKill = 6, maxCount = 22, chance = 20}, {unitName = "npc_wave_toxin_2_zombie", goldPerKill = 5, maxCount = 12, chance = 15}, {unitName = "npc_wave_dead_skeleton_archer", goldPerKill = 5, maxCount = 10, chance = 20}}}, {maxZombies = 36, spawnInterval = {min = 0.35, max = 0.75}, zombieTypes = { + {unitName = "npc_wave_ghoul", goldPerKill = 6, maxCount = 34, chance = 20}, + {unitName = "npc_wave_ghost", goldPerKill = 7, maxCount = 24, chance = 20}, + {unitName = "npc_wave_skeleton_assassin", goldPerKill = 7, maxCount = 22, chance = 20}, + {unitName = "npc_wave_dead_harpy", goldPerKill = 8, maxCount = 16, chance = 20}, + {unitName = "npc_wave_dead_skeleton_archer", goldPerKill = 6, maxCount = 12, chance = 10} + }, SpawnBossOnEnd = true}}}, + [6] = {waves = {{maxZombies = 38, spawnInterval = {min = 0.32, max = 0.7}, zombieTypes = { + {unitName = "npc_wave_toxin_2_zombie", goldPerKill = 6, maxCount = 18, chance = 20}, + {unitName = "npc_wave_ghost", goldPerKill = 7, maxCount = 24, chance = 20}, + {unitName = "npc_wave_ghoul", goldPerKill = 7, maxCount = 30, chance = 20}, + {unitName = "npc_wave_skeleton_warrior", goldPerKill = 8, maxCount = 22, chance = 20}, + {unitName = "npc_wave_dead_harpy", goldPerKill = 8, maxCount = 16, chance = 20} + }}, {maxZombies = 42, spawnInterval = {min = 0.28, max = 0.64}, zombieTypes = {{unitName = "npc_wave_skeleton_assassin", goldPerKill = 7, maxCount = 24, chance = 20}, {unitName = "npc_wave_dead_skeleton_archer", goldPerKill = 6, maxCount = 16, chance = 18}, {unitName = "npc_wave_ghost_ranged", goldPerKill = 8, maxCount = 24, chance = 18}, {unitName = "npc_wave_toxin_2_zombie", goldPerKill = 5, maxCount = 16, chance = 14}}}, {maxZombies = 46, spawnInterval = {min = 0.24, max = 0.55}, zombieTypes = { + {unitName = "npc_wave_ghoul", goldPerKill = 5, maxCount = 36, chance = 15}, + {unitName = "npc_wave_ghost", goldPerKill = 6, maxCount = 30, chance = 15}, + {unitName = "npc_wave_skeleton_warrior", goldPerKill = 7, maxCount = 26, chance = 15}, + {unitName = "npc_wave_skeleton_assassin", goldPerKill = 8, maxCount = 24, chance = 15}, + {unitName = "npc_wave_dead_harpy", goldPerKill = 6, maxCount = 18, chance = 15}, + {unitName = "npc_wave_dead_skeleton_archer", goldPerKill = 5, maxCount = 14, chance = 15} + }, SpawnBossOnEnd = true}}}, + [7] = {waves = { + {maxZombies = 40, spawnInterval = {min = 0.3, max = 0.6}, zombieTypes = {{unitName = "npc_wave_zombie", goldPerKill = 6, maxCount = 12, chance = 60}, {unitName = "npc_wave_half_zombie", goldPerKill = 7, maxCount = 6, chance = 20}, {unitName = "npc_wave_toxin_zombie", goldPerKill = 8, maxCount = 2, chance = 5}}, SpawnBossOnEnd = true}, + {maxZombies = 40, spawnInterval = {min = 0.3, max = 0.6}, zombieTypes = { + {unitName = "npc_wave_toxin_zombie", goldPerKill = 9, maxCount = 25, chance = 5}, + {unitName = "npc_wave_bearst_zombie", goldPerKill = 9, maxCount = 15, chance = 5}, + {unitName = "npc_wave_skeleton_zombie", goldPerKill = 9, maxCount = 5, chance = 20}, + {unitName = "npc_wave_dead_skeleton", goldPerKill = 9, maxCount = 25, chance = 10}, + {unitName = "npc_wave_skeleton_zombie_half", goldPerKill = 9, maxCount = 15, chance = 20}, + {unitName = "npc_wave_dead_skeleton_archer", goldPerKill = 9, maxCount = 5, chance = 20} + }, SpawnBossOnEnd = true}, + {maxZombies = 40, spawnInterval = {min = 0.3, max = 0.6}, zombieTypes = {{unitName = "npc_wave_dead_harpy", goldPerKill = 9, maxCount = 25, chance = 60}, {unitName = "npc_wave_toxin_2_zombie", goldPerKill = 9, maxCount = 15, chance = 30}, {unitName = "npc_wave_ghost", goldPerKill = 9, maxCount = 25, chance = 70}, {unitName = "npc_wave_ghost_ranged", goldPerKill = 9, maxCount = 25, chance = 70}}, SpawnBossOnEnd = true}, + {maxZombies = 40, spawnInterval = {min = 0.3, max = 0.6}, zombieTypes = {{unitName = "npc_wave_ghoul", goldPerKill = 9, maxCount = 25, chance = 70}, {unitName = "npc_wave_toxin_2_zombie", goldPerKill = 9, maxCount = 3, chance = 30}, {unitName = "npc_wave_skeleton_assassin", goldPerKill = 9, maxCount = 25, chance = 70}, {unitName = "npc_wave_skeleton_warrior", goldPerKill = 9, maxCount = 15, chance = 30}}, SpawnBossOnEnd = true}, + {maxZombies = 40, spawnInterval = {min = 0.3, max = 0.6}, zombieTypes = {{unitName = "npc_wave_ghoul", goldPerKill = 9, maxCount = 25, chance = 70}, {unitName = "npc_wave_toxin_2_zombie", goldPerKill = 9, maxCount = 3, chance = 30}, {unitName = "npc_wave_skeleton_assassin", goldPerKill = 9, maxCount = 25, chance = 70}, {unitName = "npc_wave_skeleton_warrior", goldPerKill = 9, maxCount = 15, chance = 30}}, SpawnBossOnEnd = true}, + {maxZombies = 40, spawnInterval = {min = 0.3, max = 0.6}, zombieTypes = {{unitName = "npc_wave_ghoul", goldPerKill = 9, maxCount = 25, chance = 70}, {unitName = "npc_wave_toxin_2_zombie", goldPerKill = 9, maxCount = 3, chance = 30}, {unitName = "npc_wave_skeleton_assassin", goldPerKill = 9, maxCount = 25, chance = 70}, {unitName = "npc_wave_skeleton_warrior", goldPerKill = 9, maxCount = 15, chance = 30}}, SpawnBossOnEnd = true}, + { + maxZombies = 0, + spawnInterval = {min = 0, max = 0}, + zombieTypes = {}, + cutsceneOnEnd = "camera_start_ending", + stopWavesAfter = true + } + }} +} +local LOG_PREFIX = "[WaveManager]" +local RANDOM_WAVE_BOSSES = { + {unitName = "npc_wave_boss_zombie", displayName = "#npc_wave_boss_zombie"}, + {unitName = "npc_wave_boss_skeleton", displayName = "#npc_wave_boss_skeleton"}, + {unitName = "npc_wave_boss_lifestealer", displayName = "#npc_wave_boss_lifestealer"}, + {unitName = "npc_wave_boss_death_prophet", displayName = "#npc_wave_boss_death_prophet"}, + {unitName = "npc_wave_boss_zombie_king", displayName = "#npc_wave_boss_zombie_king"}, + {unitName = "npc_wave_boss_suicide_zombie", displayName = "#npc_wave_boss_suicide_zombie"} +} +local MUSIC_DURATION_BY_EVENT = { + wave_start_yaskar_laning_01 = 120, + wave_start_yaskar_laning_02 = 120, + wave_start_yaskar_laning_03 = 120, + wave_boss_spawn_yaskar_battle_01 = 60, + wave_boss_spawn_yaskar_battle_02 = 60, + wave_boss_spawn_yaskar_battle_03 = 60, + wave_boss_spawn_yaskar_killed = 92, + wave_start_deadmau5_laning_01 = 159, + wave_start_deadmau5_laning_02 = 127, + wave_start_deadmau5_laning_03 = 124, + wave_boss_spawn_deadmau5_battle_01 = 48, + wave_boss_spawn_deadmau5_battle_02 = 73, + wave_boss_spawn_deadmau5_battle_03 = 67, + wave_boss_spawn_deadmau5_killed = 173 +} +local WAVE_MUSIC_GROUPS = {{name = "yaskar_laning", waveStartEvents = {"wave_start_yaskar_laning_01", "wave_start_yaskar_laning_02", "wave_start_yaskar_laning_03"}}, {name = "deadmau5_laning", waveStartEvents = {"wave_start_deadmau5_laning_01", "wave_start_deadmau5_laning_02", "wave_start_deadmau5_laning_03"}}, {name = "yaskar_battle", waveStartEvents = {"wave_boss_spawn_yaskar_battle_01", "wave_boss_spawn_yaskar_battle_02", "wave_boss_spawn_yaskar_battle_03", "wave_boss_spawn_yaskar_killed"}}, {name = "deadmau5_battle", waveStartEvents = {"wave_boss_spawn_deadmau5_battle_01", "wave_boss_spawn_deadmau5_battle_02", "wave_boss_spawn_deadmau5_battle_03", "wave_boss_spawn_deadmau5_killed"}}} +____exports.WaveManager = __TS__Class() +local WaveManager = ____exports.WaveManager +WaveManager.name = "WaveManager" +WaveManager.____file_path = "scripts/vscripts/WaveManager.lua" +function WaveManager.prototype.____constructor(self) + self.waveNumber = 0 + self.nextWaveTime = 60 + self.isWaveInProgress = false + self.zombieCount = 0 + self.currentNight = 0 + self.maxWavesInNight = 3 + self.spawnedZombies = {} + self.maxAliveZombies = 100 + self.zombieGoldMap = __TS__New(Map) + self.isRepeatingLastWave = false + self.waveEndCallbacks = {} + self.isNextWaveScheduled = false + self.isGameStarted = false + self.waveZombieSilentRemoval = false + self.availableRandomBosses = {} + self.night5RandomBossKillsSinceCycleStart = 0 + self.nightWavesStoppedForNight = false + self.currentBattleMusicEvent = nil + self.currentMusicPool = nil + self.battleMusicTimer = nil + self.activeMusicGroupIndex = nil + self.nightEndMusicPlayed = false + self.playerMusicEnabled = __TS__New(Map) + self.pendingEndingCutsceneSceneId = nil + self.endingCutsceneReadyPlayers = __TS__New(Map) + self.endingCutsceneReadyDeadline = nil + self.endingCutsceneReadyCountdownTimer = nil + self.waveHeroTargetCache = {} + self.waveHeroTargetCacheTime = -1000000000 + self.lastWaveCreepAbilityTime = __TS__New(Map) + ____print(nil, LOG_PREFIX .. " Инициализация") + ListenToGameEvent( + "entity_killed", + function(event) + local killedUnit = EntIndexToHScript(event.entindex_killed) + if killedUnit and killedUnit:GetTeamNumber() == DOTA_TEAM_NEUTRALS then + self:onZombieKilled(killedUnit) + end + end, + nil + ) + CustomGameEventManager:RegisterListener( + "toggle_music", + function(_userId, event) + local playerId = event.PlayerID + local ____temp_0 = self.playerMusicEnabled:get(playerId) + if ____temp_0 == nil then + ____temp_0 = true + end + local currentState = ____temp_0 + local nextState = not currentState + self.playerMusicEnabled:set(playerId, nextState) + if self.currentBattleMusicEvent then + if nextState then + self:emitSoundForPlayer(playerId, self.currentBattleMusicEvent) + else + self:stopSoundForPlayer(playerId, self.currentBattleMusicEvent) + end + end + end + ) + CustomGameEventManager:RegisterListener( + "ending_cutscene_toggle_ready", + function(eventSourceIndex, data) + local playerController = EntIndexToHScript(eventSourceIndex) + if not playerController then + return + end + local playerId = playerController:GetPlayerID() + if not PlayerResource:IsValidPlayerID(playerId) then + return + end + if not self.pendingEndingCutsceneSceneId then + return + end + if not self.endingCutsceneReadyPlayers:has(playerId) then + return + end + local isReady = (data and data.ready) == 1 or (data and data.ready) == true + self.endingCutsceneReadyPlayers:set(playerId, isReady) + if not self:hasAnyEndingCutsceneReadyPlayer() then + self:clearEndingCutsceneCountdown() + end + self:startEndingCutsceneCountdownIfNeeded() + self:broadcastEndingCutsceneReadyState() + self:tryStartPendingEndingCutsceneIfEveryoneReady() + end + ) +end +function WaveManager.getInstance(self) + if not ____exports.WaveManager.instance then + ____exports.WaveManager.instance = __TS__New(____exports.WaveManager) + end + return ____exports.WaveManager.instance +end +function WaveManager.prototype.clearEndingCutsceneCountdown(self) + if self.endingCutsceneReadyCountdownTimer then + Timers:RemoveTimer(self.endingCutsceneReadyCountdownTimer) + self.endingCutsceneReadyCountdownTimer = nil + end + self.endingCutsceneReadyDeadline = nil +end +function WaveManager.prototype.startEndingCutsceneCountdownIfNeeded(self) + if not self.pendingEndingCutsceneSceneId then + return + end + if self.endingCutsceneReadyDeadline ~= nil then + return + end + local hasAnyReady = false + for ____, ____value in __TS__Iterator(self.endingCutsceneReadyPlayers) do + local isReady = ____value[2] + if isReady then + hasAnyReady = true + break + end + end + if not hasAnyReady then + return + end + self.endingCutsceneReadyDeadline = GameRules:GetGameTime() + 30 + self.endingCutsceneReadyCountdownTimer = "ending_cutscene_countdown_" .. DoUniqueString("timer") + local timerName = self.endingCutsceneReadyCountdownTimer + Timers:CreateTimer( + timerName, + { + endTime = 0.25, + callback = function() + if not self.pendingEndingCutsceneSceneId then + self:clearEndingCutsceneCountdown() + return nil + end + self:broadcastEndingCutsceneReadyState() + local deadline = self.endingCutsceneReadyDeadline + if deadline ~= nil and GameRules:GetGameTime() >= deadline then + self:tryStartPendingEndingCutsceneIfEveryoneReady(true) + return nil + end + return 0.25 + end + } + ) +end +function WaveManager.prototype.hasAnyEndingCutsceneReadyPlayer(self) + for ____, ____value in __TS__Iterator(self.endingCutsceneReadyPlayers) do + local isReady = ____value[2] + if isReady then + return true + end + end + return false +end +function WaveManager.prototype.getConnectedLobbyPlayerIds(self) + local result = {} + do + local playerId = 0 + while playerId < DOTA_MAX_PLAYERS do + do + local pid = playerId + if not PlayerResource:IsValidPlayerID(pid) then + goto __continue36 + end + local player = PlayerResource:GetPlayer(pid) + if not player then + goto __continue36 + end + local cs = PlayerResource:GetConnectionState(pid) + if cs ~= DOTA_CONNECTION_STATE.CONNECTED then + goto __continue36 + end + result[#result + 1] = pid + end + ::__continue36:: + playerId = playerId + 1 + end + end + return result +end +function WaveManager.prototype.buildEndingCutsceneReadyPayload(self) + if not self.pendingEndingCutsceneSceneId then + return nil + end + local ready = {} + local readyPlayerNames = {} + local readyPlayerIds = {} + local readyCount = 0 + local totalCount = 0 + for ____, ____value in __TS__Iterator(self.endingCutsceneReadyPlayers) do + local playerId = ____value[1] + local isReady = ____value[2] + totalCount = totalCount + 1 + if isReady then + readyCount = readyCount + 1 + local playerName = PlayerResource:GetPlayerName(playerId) + readyPlayerNames[#readyPlayerNames + 1] = playerName and #playerName > 0 and playerName or "Player " .. tostring(playerId) + readyPlayerIds[#readyPlayerIds + 1] = playerId + end + ready[playerId] = isReady and 1 or 0 + end + if readyCount > 0 and self.endingCutsceneReadyDeadline == nil then + self.endingCutsceneReadyDeadline = GameRules:GetGameTime() + 30 + end + local secondsLeft = -1 + if self.endingCutsceneReadyDeadline ~= nil then + secondsLeft = math.max( + 0, + math.ceil(self.endingCutsceneReadyDeadline - GameRules:GetGameTime()) + ) + end + return { + sceneId = self.pendingEndingCutsceneSceneId, + ready = ready, + readyCount = readyCount, + totalCount = totalCount, + readyPlayerNames = readyPlayerNames, + readyPlayerIds = readyPlayerIds, + secondsLeft = secondsLeft + } +end +function WaveManager.prototype.broadcastEndingCutsceneReadyState(self) + self:startEndingCutsceneCountdownIfNeeded() + local payload = self:buildEndingCutsceneReadyPayload() + if not payload then + return + end + CustomGameEventManager:Send_ServerToAllClients("ending_cutscene_ready_update", payload) +end +function WaveManager.prototype.closeEndingCutsceneReadyUi(self) + CustomGameEventManager:Send_ServerToAllClients("ending_cutscene_ready_close", {}) +end +function WaveManager.prototype.isEndingCutsceneReadyVoteOpen(self) + return self.pendingEndingCutsceneSceneId ~= nil +end +function WaveManager.prototype.openEndingCutsceneReadyCheck(self, sceneId) + self.pendingEndingCutsceneSceneId = sceneId + self.endingCutsceneReadyPlayers:clear() + self:clearEndingCutsceneCountdown() + self:removeAllTrackedWaveZombiesSilent() + SpawnManager:getInstance():RemoveAllSpawnZones() + local players = self:getConnectedLobbyPlayerIds() + do + local i = 0 + while i < #players do + do + local playerId = players[i + 1] + local point = Entities:FindByName( + nil, + "point_player_" .. tostring(i) + ) + local player = PlayerResource:GetPlayer(playerId) + if not player or not point then + goto __continue52 + end + local hero = player.GetAssignedHero and player:GetAssignedHero() + if hero == nil then + goto __continue52 + end + if not hero:IsAlive() then + hero:RespawnHero(false, false) + end + hero:SetAbsOrigin(point:GetAbsOrigin()) + FindClearSpaceForUnit( + hero, + point:GetAbsOrigin(), + true + ) + end + ::__continue52:: + i = i + 1 + end + end + for ____, playerId in ipairs(players) do + self.endingCutsceneReadyPlayers:set(playerId, false) + end + if #players == 0 then + self:tryStartPendingEndingCutsceneIfEveryoneReady() + return + end + CustomGameEventManager:Send_ServerToAllClients("ending_cutscene_ready_open", {sceneId = sceneId}) + self:broadcastEndingCutsceneReadyState() +end +function WaveManager.prototype.tryStartPendingEndingCutsceneIfEveryoneReady(self, forceByTimer) + if forceByTimer == nil then + forceByTimer = false + end + local sceneId = self.pendingEndingCutsceneSceneId + if not sceneId then + return + end + local total = 0 + local ready = 0 + for ____, ____value in __TS__Iterator(self.endingCutsceneReadyPlayers) do + local isReady = ____value[2] + total = total + 1 + if isReady then + ready = ready + 1 + end + end + if total > 0 and ready < total and not forceByTimer then + return + end + local mgr = GameRules.CutsceneManager + if not mgr or mgr:isCutsceneActive() then + return + end + mgr:startScene(sceneId, {force = true}) + self.pendingEndingCutsceneSceneId = nil + self.endingCutsceneReadyPlayers:clear() + self:clearEndingCutsceneCountdown() + self:closeEndingCutsceneReadyUi() +end +function WaveManager.prototype.isMusicEnabledForPlayer(self, playerID) + local ____temp_5 = self.playerMusicEnabled:get(playerID) + if ____temp_5 == nil then + ____temp_5 = true + end + return ____temp_5 +end +function WaveManager.prototype.emitSoundForPlayer(self, playerID, soundEventName) + local hero = PlayerResource:GetSelectedHeroEntity(playerID) + if hero then + EmitSoundOnEntityForPlayer(soundEventName, hero, playerID) + return + end + local player = PlayerResource:GetPlayer(playerID) + if not player then + return + end + EmitAnnouncerSoundForPlayer(soundEventName, playerID) +end +function WaveManager.prototype.stopSoundForPlayer(self, playerID, soundEventName) + local hero = PlayerResource:GetSelectedHeroEntity(playerID) + if hero then + StopSoundOn(soundEventName, hero) + end + local player = PlayerResource:GetPlayer(playerID) + if player then + StopSoundOn(soundEventName, player) + end +end +function WaveManager.prototype.isRandomWaveBossUnitName(self, unitName) + for ____, b in ipairs(RANDOM_WAVE_BOSSES) do + if b.unitName == unitName then + return true + end + end + return false +end +function WaveManager.prototype.getNextRandomBossNoRepeat(self) + if #self.availableRandomBosses == 0 then + self.availableRandomBosses = {unpack(RANDOM_WAVE_BOSSES)} + end + local randomIndex = RandomInt(0, #self.availableRandomBosses - 1) + local selectedBoss = self.availableRandomBosses[randomIndex + 1] + __TS__ArraySplice(self.availableRandomBosses, randomIndex, 1) + return selectedBoss +end +function WaveManager.prototype.announceWaveStarted(self, totalWaves) + local group = self:getOrPickActiveMusicGroup() + if self.currentMusicPool == nil or #self.currentMusicPool == 0 then + self:startMusicPool(group.waveStartEvents) + end + self.nightEndMusicPlayed = false +end +function WaveManager.prototype.getOrPickActiveMusicGroup(self) + if self.activeMusicGroupIndex == nil then + self.activeMusicGroupIndex = RandomInt(0, #WAVE_MUSIC_GROUPS - 1) + local selectedGroup = WAVE_MUSIC_GROUPS[self.activeMusicGroupIndex + 1] + ____print(nil, (LOG_PREFIX .. " 🎵 Выбрана музыкальная группа: ") .. selectedGroup.name) + end + return WAVE_MUSIC_GROUPS[self.activeMusicGroupIndex + 1] +end +function WaveManager.prototype.getEventDuration(self, soundEventName) + return MUSIC_DURATION_BY_EVENT[soundEventName] or 60 +end +function WaveManager.prototype.scheduleNextMusicFromPool(self, afterEvent) + if self.battleMusicTimer then + Timers:RemoveTimer(self.battleMusicTimer) + self.battleMusicTimer = nil + end + if not self.currentMusicPool or #self.currentMusicPool == 0 then + return + end + local duration = self:getEventDuration(afterEvent) + self.battleMusicTimer = Timers:CreateTimer( + duration, + function() + if GameRules:IsDaytime() then + self:handleNightEndedMusic() + return nil + end + if not self.currentMusicPool or #self.currentMusicPool == 0 then + return nil + end + local nextEvent = self.currentMusicPool[RandomInt(0, #self.currentMusicPool - 1) + 1] + if #self.currentMusicPool > 1 then + while nextEvent == self.currentBattleMusicEvent do + nextEvent = self.currentMusicPool[RandomInt(0, #self.currentMusicPool - 1) + 1] + end + end + self:playBattleMusic(nextEvent) + return nil + end + ) +end +function WaveManager.prototype.startMusicPool(self, events) + self.currentMusicPool = events + local firstEvent = events[RandomInt(0, #events - 1) + 1] + self:playBattleMusic(firstEvent) +end +function WaveManager.prototype.playBattleMusic(self, soundEventName) + if self.currentBattleMusicEvent then + self:stopSoundForAllPlayers(self.currentBattleMusicEvent) + end + self.currentBattleMusicEvent = soundEventName + self:emitSoundForAllPlayers(soundEventName) + self:scheduleNextMusicFromPool(soundEventName) +end +function WaveManager.prototype.stopBattleMusic(self) + if self.battleMusicTimer then + Timers:RemoveTimer(self.battleMusicTimer) + self.battleMusicTimer = nil + end + self.currentMusicPool = nil + if not self.currentBattleMusicEvent then + return + end + self:stopSoundForAllPlayers(self.currentBattleMusicEvent) + self.currentBattleMusicEvent = nil +end +function WaveManager.prototype.emitSoundForAllPlayers(self, soundEventName) + do + local playerID = 0 + while playerID < DOTA_MAX_PLAYERS do + do + if not PlayerResource:IsValidPlayerID(playerID) then + goto __continue99 + end + if not self:isMusicEnabledForPlayer(playerID) then + goto __continue99 + end + self:emitSoundForPlayer(playerID, soundEventName) + end + ::__continue99:: + playerID = playerID + 1 + end + end +end +function WaveManager.prototype.stopSoundForAllPlayers(self, soundEventName) + do + local playerID = 0 + while playerID < DOTA_MAX_PLAYERS do + do + if not PlayerResource:IsValidPlayerID(playerID) then + goto __continue103 + end + self:stopSoundForPlayer(playerID, soundEventName) + end + ::__continue103:: + playerID = playerID + 1 + end + end +end +function WaveManager.prototype.handleNightEndedMusic(self) + if self.nightEndMusicPlayed then + return + end + self.nightEndMusicPlayed = true + self:stopBattleMusic() +end +function WaveManager.prototype.isValidZombieHeroTarget(self, hero) + return hero:IsAlive() and hero:IsRealHero() and not hero:IsInvulnerable() and not hero:IsAttackImmune() +end +function WaveManager.prototype.shouldKeepZombieAttackTarget(self, target) + if not target:IsAlive() then + return false + end + if target:IsHero() and target:IsRealHero() then + return self:isValidZombieHeroTarget(target) + end + return true +end +function WaveManager.prototype.refreshWaveHeroTargetCache(self) + local now = GameRules:GetGameTime() + if now - self.waveHeroTargetCacheTime < WAVE_HERO_TARGET_CACHE_TTL then + return + end + self.waveHeroTargetCacheTime = now + local list = {} + do + local i = 0 + while i < DOTA_MAX_PLAYERS do + do + local pid = i + if not PlayerResource:IsValidPlayerID(pid) then + goto __continue113 + end + local player = PlayerResource:GetPlayer(pid) + local hero + if player and player.GetAssignedHero and player:GetAssignedHero() then + hero = player:GetAssignedHero() + end + if not hero or not IsValidEntity(hero) then + hero = PlayerResource:GetSelectedHeroEntity(pid) or nil + end + if hero and IsValidEntity(hero) and hero:IsRealHero() and hero:IsAlive() then + list[#list + 1] = hero + end + end + ::__continue113:: + i = i + 1 + end + end + self.waveHeroTargetCache = list +end +function WaveManager.prototype.getWaveCreepThinkInterval(self) + local n = #self.spawnedZombies + if n > WAVE_CREEP_MEGA_HORDE_THRESHOLD then + return 0.45 + end + if n > WAVE_CREEP_HORDE_THRESHOLD then + return 0.32 + end + return 0.2 +end +function WaveManager.prototype.findNearestHero(self, zombie, radius) + if radius == nil then + radius = 450 + end + self:refreshWaveHeroTargetCache() + local zpos = zombie:GetAbsOrigin() + local best = nil + local bestDist = radius + 1 + for ____, raw in ipairs(self.waveHeroTargetCache) do + do + local hero = raw + if not hero or not IsValidEntity(hero) or not self:isValidZombieHeroTarget(hero) then + goto __continue122 + end + local d = (hero:GetAbsOrigin() - zpos):Length2D() + if d <= radius and d < bestDist then + bestDist = d + best = hero + end + end + ::__continue122:: + end + if best then + return best + end + local nearbyHeroes = FindUnitsInRadius( + zombie:GetTeamNumber(), + zpos, + nil, + radius, + DOTA_UNIT_TARGET_TEAM_ENEMY, + DOTA_UNIT_TARGET_HERO, + DOTA_UNIT_TARGET_FLAG_NONE, + FIND_CLOSEST, + false + ) + for ____, hero in ipairs(nearbyHeroes) do + if hero and self:isValidZombieHeroTarget(hero) then + return hero + end + end + return nil +end +function WaveManager.prototype.findHomerTarget(self) + local homer = Entities:FindByName(nil, "npc_homer") + if homer and homer:IsAlive() and homer:GetUnitName() == "npc_homer" then + return homer + end + local allUnits = Entities:FindAllByClassname("npc_dota_creature") + for ____, unit in ipairs(allUnits) do + if unit and unit:GetUnitName() == "npc_homer" and unit:IsAlive() then + return unit + end + end + return nil +end +function WaveManager.prototype.updateZombieBehavior(self, zombie, homePoint) + local currentTarget = zombie:GetAttackTarget() + if currentTarget and self:shouldKeepZombieAttackTarget(currentTarget) then + return + end + local canAttack = zombie:GetAttackCapability() ~= DOTA_UNIT_CAP_NO_ATTACK and not zombie:IsDisarmed() + if not canAttack then + ExecuteOrderFromTable({ + UnitIndex = zombie:entindex(), + Queue = false, + OrderType = DOTA_UNIT_ORDER_MOVE_TO_POSITION, + Position = homePoint:GetAbsOrigin() + }) + return + end + local nearestHero = self:findNearestHero(zombie, 1000) + if nearestHero then + ExecuteOrderFromTable({ + UnitIndex = zombie:entindex(), + Queue = false, + OrderType = DOTA_UNIT_ORDER_ATTACK_TARGET, + TargetIndex = nearestHero:entindex() + }) + else + local homerTarget = self:findHomerTarget() + if homerTarget then + ExecuteOrderFromTable({ + UnitIndex = zombie:entindex(), + Queue = false, + OrderType = DOTA_UNIT_ORDER_ATTACK_TARGET, + TargetIndex = homerTarget:entindex() + }) + else + ExecuteOrderFromTable({ + UnitIndex = zombie:entindex(), + Queue = false, + OrderType = DOTA_UNIT_ORDER_ATTACK_MOVE, + Position = homePoint:GetAbsOrigin() + }) + end + end +end +function WaveManager.prototype.assignBehavior(self, unit, homePoint) + local stagger = math.abs(unit:entindex()) % 8 * 0.04 + local behaviorInterval = #self.spawnedZombies > WAVE_CREEP_MEGA_HORDE_THRESHOLD and 1.25 or 1 + Timers:CreateTimer( + 0.1 + stagger, + function() + if unit and unit:IsAlive() then + unit:Stop() + self:updateZombieBehavior(unit, homePoint) + self:startWaveCreepAbilityThink(unit) + Timers:CreateTimer({ + endTime = behaviorInterval, + callback = function() + if unit and unit:IsAlive() then + self:updateZombieBehavior(unit, homePoint) + return #self.spawnedZombies > WAVE_CREEP_MEGA_HORDE_THRESHOLD and 1.25 or 1 + end + return nil + end + }) + end + end + ) +end +function WaveManager.prototype.getAbilityBehavior(self, ability) + local behavior = 0 + do + local function ____catch(_) + local raw = ability:GetBehavior() + if type(raw) == "number" and not __TS__NumberIsNaN(__TS__Number(raw)) then + behavior = raw + end + end + local ____try, ____hasReturned = pcall(function() + local kv = ability:GetAbilityKeyValues() + if kv and kv.AbilityBehavior then + local s = kv.AbilityBehavior or "" + if __TS__StringIncludes(s, "POINT") then + behavior = bit.bor(behavior, ____exports.WaveManager.ABILITY_POINT) + end + if __TS__StringIncludes(s, "UNIT_TARGET") then + behavior = bit.bor(behavior, ____exports.WaveManager.ABILITY_UNIT_TARGET) + end + if __TS__StringIncludes(s, "NO_TARGET") then + behavior = bit.bor(behavior, ____exports.WaveManager.ABILITY_NO_TARGET) + end + end + end) + if not ____try then + ____catch(____hasReturned) + end + end + local byName = {sheep_coil = ____exports.WaveManager.ABILITY_POINT, black_dragon_fireball = ____exports.WaveManager.ABILITY_POINT, frogmen_acid_jump = ____exports.WaveManager.ABILITY_POINT} + local nameOverride = byName[ability:GetAbilityName()] + if nameOverride ~= nil then + return nameOverride + end + return behavior +end +function WaveManager.prototype.tryUseWaveCreepAbilities(self, unit, target) + if not unit:IsAlive() or unit:IsStunned() or unit:IsHexed() then + return false + end + local unitPos = unit:GetAbsOrigin() + local targetPos = target:GetAbsOrigin() + do + local i = 0 + while i < unit:GetAbilityCount() do + do + local ability = unit:GetAbilityByIndex(i) + if not ability or ability:IsNull() or ability:IsPassive() or ability:IsHidden() then + goto __continue158 + end + if not ability:IsCooldownReady() then + goto __continue158 + end + local manaCost = ability:GetManaCost(ability:GetLevel()) + if manaCost > unit:GetMana() then + goto __continue158 + end + local behavior = self:getAbilityBehavior(ability) + local castRange = ability:GetCastRange(unitPos, target) + local dx = targetPos.x - unitPos.x + local dy = targetPos.y - unitPos.y + local distance = math.sqrt(dx * dx + dy * dy) + if bit.band(behavior, ____exports.WaveManager.ABILITY_UNIT_TARGET) ~= 0 and distance <= castRange and target:IsAlive() then + ExecuteOrderFromTable({ + UnitIndex = unit:entindex(), + OrderType = DOTA_UNIT_ORDER_CAST_TARGET, + TargetIndex = target:entindex(), + AbilityIndex = ability:GetEntityIndex(), + Queue = false + }) + return true + end + if bit.band(behavior, ____exports.WaveManager.ABILITY_POINT) ~= 0 and distance <= castRange and target:IsAlive() then + ExecuteOrderFromTable({ + UnitIndex = unit:entindex(), + OrderType = DOTA_UNIT_ORDER_CAST_POSITION, + Position = targetPos, + AbilityIndex = ability:GetEntityIndex(), + Queue = false + }) + return true + end + if bit.band(behavior, ____exports.WaveManager.ABILITY_NO_TARGET) ~= 0 and distance <= 400 then + ExecuteOrderFromTable({ + UnitIndex = unit:entindex(), + OrderType = DOTA_UNIT_ORDER_CAST_NO_TARGET, + AbilityIndex = ability:GetEntityIndex(), + Queue = false + }) + return true + end + end + ::__continue158:: + i = i + 1 + end + end + return false +end +function WaveManager.prototype.waveCreepAbilityThink(self, unit) + if not unit or not unit:IsAlive() then + return nil + end + local thinkInterval = self:getWaveCreepThinkInterval() + local now = GameRules:GetGameTime() + local last = self.lastWaveCreepAbilityTime:get(unit:entindex()) or 0 + if now - last < ____exports.WaveManager.WAVE_CREEP_ABILITY_COOLDOWN then + return thinkInterval + end + local radius = 700 + local uPos = unit:GetAbsOrigin() + self:refreshWaveHeroTargetCache() + local best + local bestD = radius + 1 + for ____, raw in ipairs(self.waveHeroTargetCache) do + do + if not raw or not IsValidEntity(raw) or not raw:IsAlive() then + goto __continue168 + end + local d = (raw:GetAbsOrigin() - uPos):Length2D() + if d <= radius and d < bestD then + bestD = d + best = raw + end + end + ::__continue168:: + end + local target = best + if target == nil then + local enemies = FindUnitsInRadius( + unit:GetTeamNumber(), + uPos, + nil, + radius, + DOTA_UNIT_TARGET_TEAM_ENEMY, + bit.bor(DOTA_UNIT_TARGET_HERO, DOTA_UNIT_TARGET_BASIC), + DOTA_UNIT_TARGET_FLAG_NONE, + FIND_CLOSEST, + false + ) + if #enemies == 0 then + return thinkInterval + end + target = enemies[1] + end + if not target or not target:IsAlive() then + return thinkInterval + end + local used = self:tryUseWaveCreepAbilities(unit, target) + if used then + self.lastWaveCreepAbilityTime:set( + unit:entindex(), + now + ) + end + return thinkInterval +end +function WaveManager.prototype.startWaveCreepAbilityThink(self, unit) + local initial = self:getWaveCreepThinkInterval() + unit:SetContextThink( + "WaveCreepAbilities", + function() return self:waveCreepAbilityThink(unit) end, + initial + ) +end +function WaveManager.prototype.getLastConfiguredNight(self) + local lastNight = 1 + for nightKey in pairs(WAVE_SETTINGS) do + local n = __TS__ParseInt(nightKey) + if n > lastNight then + lastNight = n + end + end + return lastNight +end +function WaveManager.prototype.getNightMultiplier(self) + local lastConfiguredNight = self:getLastConfiguredNight() + if self.currentNight <= lastConfiguredNight then + return 1 + end + return 1 + (self.currentNight - lastConfiguredNight) * 0.2 +end +function WaveManager.prototype.getWaves(self) + local actualNight = self.currentNight + if WAVE_SETTINGS[actualNight] ~= nil and WAVE_SETTINGS[actualNight].waves ~= nil then + return WAVE_SETTINGS[actualNight].waves + end + local lastNight = self:getLastConfiguredNight() + ____print( + nil, + (((LOG_PREFIX .. " Нет конфига для ночи ") .. tostring(actualNight)) .. ", используем ночь ") .. tostring(lastNight) + ) + if WAVE_SETTINGS[lastNight] ~= nil and WAVE_SETTINGS[lastNight].waves ~= nil then + return WAVE_SETTINGS[lastNight].waves + end + ____print(nil, LOG_PREFIX .. " ⚠️ Нет конфигурации волн! Возвращаем пустой массив") + return {} +end +function WaveManager.prototype.applyNightScaling(self, unit) + local multiplier = self:getNightMultiplier() + if multiplier <= 1 then + return + end + local oldDamage = unit:GetBaseDamageMin() + local oldHealth = unit:GetMaxHealth() + local newDamage = math.floor(oldDamage * multiplier) + unit:SetBaseDamageMin(newDamage) + unit:SetBaseDamageMax(newDamage) + local newHealth = math.floor(oldHealth * multiplier) + unit:SetMaxHealth(newHealth) + unit:SetBaseMaxHealth(newHealth) + unit:SetHealth(newHealth) + ____print( + nil, + ((((((((((((LOG_PREFIX .. " Скейлинг ") .. unit:GetUnitName()) .. ": HP ") .. tostring(oldHealth)) .. " -> ") .. tostring(newHealth)) .. ", DMG ") .. tostring(oldDamage)) .. " -> ") .. tostring(newDamage)) .. " (x") .. tostring(multiplier)) .. ")" + ) +end +function WaveManager.prototype.setUnitRewards(self, unit) + SetGoldUsually(nil, unit) + unit:SetDeathXP(unit:GetDeathXP()) +end +function WaveManager.prototype.tryGrantRandomBonusWaveAbility(self, unit) + if not IsServer() then + return + end + if not unit or not IsValidEntity(unit) or not unit:IsAlive() then + return + end + if Difficulty.leader ~= "death_sentence" then + return + end + local skipThisSpawn = __TS__New(Set) + while true do + if RandomInt(1, 100) > WAVE_MOB_RANDOM_BONUS_ABILITY_CHANCE then + break + end + local candidates = __TS__ArrayFilter( + WAVE_MOB_BONUS_ABILITY_POOL, + function(____, name) return not unit:FindAbilityByName(name) and not skipThisSpawn:has(name) end + ) + if #candidates == 0 then + break + end + local abilityName = candidates[RandomInt(0, #candidates - 1) + 1] + do + local function ____catch(_) + skipThisSpawn:add(abilityName) + end + local ____try, ____hasReturned = pcall(function() + local ab = unit:AddAbility(abilityName) + if ab ~= nil and ab ~= nil then + local maxLevel = ab:GetMaxLevel() + ab:SetLevel(maxLevel > 0 and maxLevel or 1) + else + skipThisSpawn:add(abilityName) + end + end) + if not ____try then + ____catch(____hasReturned) + end + end + end +end +function WaveManager.prototype.OnWaveEnd(self, callback) + local ____self_waveEndCallbacks_6 = self.waveEndCallbacks + ____self_waveEndCallbacks_6[#____self_waveEndCallbacks_6 + 1] = callback + ____print( + nil, + ((LOG_PREFIX .. " OnWaveEnd: зарегистрирован колбэк (всего: ") .. tostring(#self.waveEndCallbacks)) .. ")" + ) +end +function WaveManager.prototype.removeAllTrackedWaveZombiesSilent(self) + self.waveZombieSilentRemoval = true + do + pcall(function() + local snapshot = {unpack(self.spawnedZombies)} + local removed = 0 + for ____, z in ipairs(snapshot) do + do + if not z or not IsValidEntity(z) or not z:IsAlive() then + goto __continue205 + end + self.zombieGoldMap:delete(z:entindex()) + z:RemoveSelf() + removed = removed + 1 + end + ::__continue205:: + end + self.spawnedZombies = __TS__ArrayFilter( + self.spawnedZombies, + function(____, u) return u and IsValidEntity(u) and u:IsAlive() end + ) + self.zombieCount = #self.spawnedZombies + if removed > 0 then + ____print( + nil, + (((LOG_PREFIX .. " Ready-check: удалено оставшихся зомби волны: ") .. tostring(removed)) .. ", в учёте осталось: ") .. tostring(self.zombieCount) + ) + end + end) + do + self.waveZombieSilentRemoval = false + end + end +end +function WaveManager.prototype.scheduleWaveEndFollowup(self, waveConfig, waveIndexForEvent) + if not self.isRepeatingLastWave and not GameRules:IsDaytime() then + self:executeWaveEndEvent(waveConfig, waveIndexForEvent) + end + if waveConfig.stopWavesAfter then + self.nightWavesStoppedForNight = true + ____print(nil, LOG_PREFIX .. " 🛑 Волны ночи остановлены (stopWavesAfter), следующих волн не будет") + return + end + ____print(nil, LOG_PREFIX .. " Следующая волна через 3 сек...") + self.isNextWaveScheduled = true + Timers:CreateTimer( + 3, + function() + self.isNextWaveScheduled = false + if not GameRules:IsDaytime() then + self:SpawnNextWave() + else + self:handleNightEndedMusic() + ____print(nil, LOG_PREFIX .. " Наступил день — следующая волна отменена") + end + end + ) +end +function WaveManager.prototype.executeWaveEndEvent(self, waveConfig, waveIndex) + local waves = self:getWaves() + local info = { + night = self.currentNight, + waveIndex = waveIndex, + totalWaves = #waves, + isLastWave = waveIndex >= #waves, + waveConfig = waveConfig + } + ____print( + nil, + (((((((LOG_PREFIX .. " 🎯 WaveEndEvent: ночь ") .. tostring(info.night)) .. ", волна ") .. tostring(info.waveIndex)) .. "/") .. tostring(info.totalWaves)) .. ", isLastWave=") .. tostring(info.isLastWave) + ) + if waveConfig.SpawnBossOnEnd then + local randomBossSpawn = {unitName = "npc_wave_boss_zombie", count = 1, goldPerKill = 300} + self:spawnWaveEndUnits(__TS__ObjectAssign({}, waveConfig, {onEndSpawns = {randomBossSpawn}})) + end + if waveConfig.onEndSpawns and #waveConfig.onEndSpawns > 0 then + self:spawnWaveEndUnits(waveConfig) + end + if waveConfig.cutsceneOnEnd then + local sceneId = waveConfig.cutsceneOnEnd + Timers:CreateTimer( + 0.12, + function() + local mgr = GameRules.CutsceneManager + if not mgr or mgr:isCutsceneActive() then + return nil + end + self:openEndingCutsceneReadyCheck(sceneId) + return nil + end + ) + end + for ____, callback in ipairs(self.waveEndCallbacks) do + do + local function ____catch(err) + ____print( + nil, + (LOG_PREFIX .. " ⚠️ Ошибка в колбэке OnWaveEnd: ") .. tostring(err) + ) + end + local ____try, ____hasReturned = pcall(function() + callback(nil, info) + end) + if not ____try then + ____catch(____hasReturned) + end + end + end +end +function WaveManager.prototype.SpawnBossOnEnd(self, endSpawn, spawnPoints, homePoint) + local selectedBossName = "" + local selectedBossUnit = "" + local spawnUnitName = endSpawn.unitName + if endSpawn.unitName == "npc_wave_boss_zombie" and #RANDOM_WAVE_BOSSES > 0 then + local randomBoss = self:getNextRandomBossNoRepeat() + spawnUnitName = randomBoss.unitName + selectedBossUnit = randomBoss.unitName + selectedBossName = randomBoss.displayName + end + if selectedBossUnit ~= "" then + CustomGameEventManager:Send_ServerToAllClients("boss_spawned", {bossName = selectedBossName, bossUnitName = selectedBossUnit}) + ____print(nil, ((((LOG_PREFIX .. " 👑 Выбран случайный босс: ") .. selectedBossUnit) .. " (") .. selectedBossName) .. ")") + GameRules:SendCustomMessage("Босс вышел!", 0, 0) + end + do + local i = 0 + while i < endSpawn.count do + do + local spawnPoint = spawnPoints[RandomInt(0, #spawnPoints - 1) + 1] + if not spawnPoint then + goto __continue230 + end + local unit = CreateUnitByName( + spawnUnitName, + spawnPoint:GetAbsOrigin(), + true, + nil, + nil, + DOTA_TEAM_NEUTRALS + ) + if unit ~= nil and unit ~= nil then + self:applyNightScaling(unit) + self.zombieCount = self.zombieCount + 1 + local ____self_spawnedZombies_7 = self.spawnedZombies + ____self_spawnedZombies_7[#____self_spawnedZombies_7 + 1] = unit + self.zombieGoldMap:set( + unit:entindex(), + endSpawn.goldPerKill + ) + self:setUnitRewards(unit) + self:assignBehavior(unit, homePoint) + self:tryGrantRandomBonusWaveAbility(unit) + local bossBarToken = selectedBossName ~= "" and selectedBossName or (isBossHudHealthBarUnit(nil, spawnUnitName) and "#" .. spawnUnitName or nil) + if bossBarToken ~= nil then + applyBossHudHealthBar(nil, unit, bossBarToken) + end + ____print( + nil, + (((((((LOG_PREFIX .. " 🎯 WaveEndSpawn: заспавнен ") .. spawnUnitName) .. " (") .. tostring(i + 1)) .. "/") .. tostring(endSpawn.count)) .. "), gold=") .. tostring(endSpawn.goldPerKill) + ) + end + end + ::__continue230:: + i = i + 1 + end + end +end +function WaveManager.prototype.spawnWaveEndUnits(self, waveConfig) + if not waveConfig.onEndSpawns or #waveConfig.onEndSpawns == 0 then + return + end + local spawnPoints = Entities:FindAllByName("wave_point") + local homePoint = Entities:FindByName(nil, "homer_point") + if not spawnPoints or #spawnPoints == 0 or not homePoint then + ____print(nil, LOG_PREFIX .. " ⚠️ WaveEndEvent: не найдены точки спавна или homer_point") + return + end + for ____, endSpawn in ipairs(waveConfig.onEndSpawns) do + self:SpawnBossOnEnd(endSpawn, spawnPoints, homePoint) + end +end +function WaveManager.prototype.onZombieKilled(self, killedUnit) + if not killedUnit or not IsValidEntity(killedUnit) then + return + end + local killedIndex = killedUnit:entindex() + local unitName = "" + do + pcall(function() + unitName = killedUnit:GetUnitName() + end) + end + local wasTracked = __TS__ArraySome( + self.spawnedZombies, + function(____, z) return z and IsValidEntity(z) and z:entindex() == killedIndex end + ) + if wasTracked then + self.spawnedZombies = __TS__ArrayFilter( + self.spawnedZombies, + function(____, z) return z and IsValidEntity(z) and z:entindex() ~= killedIndex end + ) + self.zombieCount = #__TS__ArrayFilter( + self.spawnedZombies, + function(____, z) return z and IsValidEntity(z) and z:IsAlive() end + ) + ____print( + nil, + (((((LOG_PREFIX .. " Зомби убит: ") .. unitName) .. " (entindex: ") .. tostring(killedIndex)) .. "). Осталось живых: ") .. tostring(self.zombieCount) + ) + else + ____print( + nil, + ((((LOG_PREFIX .. " Убит нейтрал (не из волны): ") .. unitName) .. " (entindex: ") .. tostring(killedIndex)) .. ")" + ) + end + local goldPerKill = self.zombieGoldMap:get(killedIndex) + self.zombieGoldMap:delete(killedIndex) + if goldPerKill ~= nil and goldPerKill > 0 then + local killPosition = killedUnit:GetAbsOrigin() + local players = {} + local heroes = HeroList:GetAllHeroes() + for ____, hero in ipairs(heroes) do + do + if not hero or not hero:IsRealHero() or hero:IsIllusion() or hero:GetTeamNumber() ~= DOTA_TEAM_GOODGUYS or not hero:IsAlive() then + goto __continue248 + end + local distance = (hero:GetAbsOrigin() - killPosition):Length2D() + if distance <= GOLD_REWARD_RADIUS then + players[#players + 1] = hero + end + end + ::__continue248:: + end + if #players > 0 then + local baseGoldPerPlayer = math.floor(goldPerKill / #players) + local remainder = goldPerKill - baseGoldPerPlayer * #players + ____print( + nil, + ((((((((((((LOG_PREFIX .. " 💰 Выдаём золото за ") .. unitName) .. ": ") .. tostring(goldPerKill)) .. " всего, ") .. tostring(baseGoldPerPlayer)) .. " (+остаток ") .. tostring(remainder)) .. ") на игрока в радиусе ") .. tostring(GOLD_REWARD_RADIUS)) .. " (") .. tostring(#players)) .. " игроков)" + ) + for ____, hero in ipairs(players) do + do + pcall(function() + local bonusFromRemainder = remainder > 0 and 1 or 0 + local finalGold = baseGoldPerPlayer + bonusFromRemainder + if remainder > 0 then + remainder = remainder - 1 + end + hero:ModifyGold(finalGold, true, DOTA_ModifyGold_CreepKill) + SendOverheadEventMessage( + nil, + OVERHEAD_ALERT_GOLD, + hero, + finalGold, + hero:GetPlayerOwner() + ) + end) + end + end + else + ____print( + nil, + (((LOG_PREFIX .. " ⚠️ Золото не выдано за ") .. unitName) .. ": нет героев в радиусе ") .. tostring(GOLD_REWARD_RADIUS) + ) + end + else + if wasTracked and not self.waveZombieSilentRemoval then + ____print( + nil, + ((((LOG_PREFIX .. " ⚠️ Золото не найдено в кеше для ") .. unitName) .. " (entindex: ") .. tostring(killedIndex)) .. ")" + ) + end + end + if wasTracked then + local wavesForBossLoop = self:getWaves() + local lastWaveCfg = #wavesForBossLoop > 0 and wavesForBossLoop[#wavesForBossLoop] or nil + if self.currentNight == 5 and unitName ~= "" and self:isRandomWaveBossUnitName(unitName) and not (lastWaveCfg and lastWaveCfg.stopWavesAfter) then + self.night5RandomBossKillsSinceCycleStart = self.night5RandomBossKillsSinceCycleStart + 1 + if self.night5RandomBossKillsSinceCycleStart >= #RANDOM_WAVE_BOSSES then + self.night5RandomBossKillsSinceCycleStart = 0 + self.availableRandomBosses = {unpack(RANDOM_WAVE_BOSSES)} + self.waveNumber = 0 + self.isRepeatingLastWave = false + ____print( + nil, + ((LOG_PREFIX .. " 🔁 Ночь 5: убит ") .. tostring(#RANDOM_WAVE_BOSSES)) .. "-й босс цикла — сброс пула боссов и волн, цикл с начала" + ) + self.isNextWaveScheduled = false + if not GameRules:IsDaytime() then + self.isNextWaveScheduled = true + Timers:CreateTimer( + 3, + function() + self.isNextWaveScheduled = false + if not GameRules:IsDaytime() then + self:SpawnNextWave() + else + self:handleNightEndedMusic() + ____print(nil, LOG_PREFIX .. " Наступил день — перезапуск волн ночи 5 отменён") + end + end + ) + end + return + end + end + local waves = self:getWaves() + local waveConfig + if self.isRepeatingLastWave then + waveConfig = #waves > 0 and waves[#waves] or nil + else + local currentWaveIdx = self.waveNumber - 1 + if currentWaveIdx >= 0 and currentWaveIdx < #waves then + waveConfig = waves[currentWaveIdx + 1] + end + end + if waveConfig ~= nil and not self.isWaveInProgress then + local maxAllowedZombies = math.min(waveConfig.maxZombies * 0.5, 5) + if self.zombieCount <= maxAllowedZombies then + if self.isNextWaveScheduled then + return + end + ____print( + nil, + ((((((LOG_PREFIX .. " Зомби <= ") .. tostring(maxAllowedZombies)) .. " (") .. tostring(self.zombieCount)) .. "/") .. tostring(waveConfig.maxZombies)) .. "). Обработка конца волны..." + ) + self:scheduleWaveEndFollowup(waveConfig, self.waveNumber) + end + end + end +end +function WaveManager.prototype.spawnZombiesFromConfig(self, waveConfig, spawnPointName) + local points = Entities:FindAllByName(spawnPointName) + local homePoint = Entities:FindByName(nil, "homer_point") + if not points or #points == 0 then + ____print(nil, ((LOG_PREFIX .. " ⚠️ Не найдены точки спавна \"") .. spawnPointName) .. "\"") + self.isWaveInProgress = false + return + end + if not homePoint then + ____print(nil, LOG_PREFIX .. " ⚠️ Не найдена точка \"homer_point\"") + self.isWaveInProgress = false + return + end + ____print( + nil, + (((((((LOG_PREFIX .. " Начинаем спавн: maxZombies=") .. tostring(waveConfig.maxZombies)) .. ", точки=\"") .. spawnPointName) .. "\" (") .. tostring(#points)) .. " шт), типов зомби: ") .. tostring(#waveConfig.zombieTypes) + ) + local spawnedCount = 0 + Timers:CreateTimer({ + endTime = 0, + callback = function() + if GameRules:IsDaytime() then + self:handleNightEndedMusic() + ____print( + nil, + (((LOG_PREFIX .. " Наступил день — спавн остановлен. Заспавнено ") .. tostring(spawnedCount)) .. "/") .. tostring(waveConfig.maxZombies) + ) + self.isWaveInProgress = false + return nil + end + if self.zombieCount >= self.maxAliveZombies then + return RandomFloat(waveConfig.spawnInterval.min, waveConfig.spawnInterval.max) + end + if spawnedCount >= waveConfig.maxZombies then + ____print( + nil, + (((LOG_PREFIX .. " Волна завершена: заспавнено ") .. tostring(spawnedCount)) .. "/") .. tostring(waveConfig.maxZombies) + ) + self.isWaveInProgress = false + local maxAllowedZombies = math.min(waveConfig.maxZombies * 0.5, 5) + if self.zombieCount <= maxAllowedZombies and not self.isNextWaveScheduled then + ____print( + nil, + ((LOG_PREFIX .. " Все зомби убиты (") .. tostring(self.zombieCount)) .. "), обработка конца волны..." + ) + self:scheduleWaveEndFollowup(waveConfig, self.waveNumber) + end + return nil + end + local spawnPoint = points[RandomInt(0, #points - 1) + 1] + if not spawnPoint then + return RandomFloat(waveConfig.spawnInterval.min, waveConfig.spawnInterval.max) + end + local unit + local selectedGold = 0 + local totalChance = 0 + local roll = RandomInt(1, 100) + for ____, zombieType in ipairs(waveConfig.zombieTypes) do + totalChance = totalChance + zombieType.chance + if roll <= totalChance then + unit = CreateUnitByName( + zombieType.unitName, + spawnPoint:GetAbsOrigin(), + true, + nil, + nil, + DOTA_TEAM_NEUTRALS + ) + selectedGold = zombieType.goldPerKill + break + end + end + if unit then + self:applyNightScaling(unit) + self.zombieCount = self.zombieCount + 1 + spawnedCount = spawnedCount + 1 + local ____self_spawnedZombies_10 = self.spawnedZombies + ____self_spawnedZombies_10[#____self_spawnedZombies_10 + 1] = unit + self.zombieGoldMap:set( + unit:entindex(), + selectedGold + ) + self:setUnitRewards(unit) + self:assignBehavior(unit, homePoint) + self:tryGrantRandomBonusWaveAbility(unit) + else + ____print( + nil, + (((LOG_PREFIX .. " ⚠️ Зомби не создан: roll=") .. tostring(roll)) .. ", totalChance=") .. tostring(totalChance) + ) + end + return RandomFloat(waveConfig.spawnInterval.min, waveConfig.spawnInterval.max) + end + }) +end +function WaveManager.prototype.Initialize(self) + ____print( + nil, + ((LOG_PREFIX .. " Initialize: таймер волн запущен, интервал ") .. tostring(self.nextWaveTime)) .. " сек" + ) + self:StartWaveTimer() +end +function WaveManager.prototype.StartWaveTimer(self) + Timers:CreateTimer( + "WaveTimer", + { + useGameTime = true, + endTime = self.nextWaveTime, + callback = function() + self:SpawnWave() + return self.nextWaveTime + end + } + ) +end +function WaveManager.prototype.SpawnWave(self) + local gameState = GameRules:State_Get() + if gameState ~= DOTA_GAMERULES_STATE_GAME_IN_PROGRESS then + ____print( + nil, + ((LOG_PREFIX .. " SpawnWave: пропуск — игра еще не началась (state: ") .. tostring(gameState)) .. ")" + ) + self.isNextWaveScheduled = false + return + end + if not self.isGameStarted then + ____print(nil, LOG_PREFIX .. " SpawnWave: пропуск — игра еще не началась (isGameStarted: false)") + self.isNextWaveScheduled = false + return + end + if self.isWaveInProgress then + ____print(nil, LOG_PREFIX .. " SpawnWave: пропуск — волна уже идёт") + self.isNextWaveScheduled = false + return + end + if GameRules:IsDaytime() then + self:handleNightEndedMusic() + self.isNextWaveScheduled = false + return + end + if self.currentNight == 0 then + ____print(nil, LOG_PREFIX .. " SpawnWave: пропуск — ночь не установлена (currentNight: 0)") + self.isNextWaveScheduled = false + return + end + if self.nightWavesStoppedForNight then + ____print(nil, LOG_PREFIX .. " SpawnWave: пропуск — волны ночи завершены (stopWavesAfter)") + self.isNextWaveScheduled = false + return + end + local waves = self:getWaves() + if #waves == 0 then + ____print( + nil, + (LOG_PREFIX .. " SpawnWave: нет волн для ночи ") .. tostring(self.currentNight) + ) + self.isNextWaveScheduled = false + return + end + self.isNextWaveScheduled = false + self.spawnedZombies = __TS__ArrayFilter( + self.spawnedZombies, + function(____, z) return z and IsValidEntity(z) and z:IsAlive() end + ) + self.zombieCount = #self.spawnedZombies + if self.waveNumber >= #waves then + local lastWaveConfigPre = waves[#waves] + if lastWaveConfigPre and lastWaveConfigPre.stopWavesAfter then + self.nightWavesStoppedForNight = true + ____print(nil, LOG_PREFIX .. " SpawnWave: пропуск — последняя волна с stopWavesAfter, повтора нет") + return + end + self.isRepeatingLastWave = true + local lastWaveConfig = waves[#waves] + self.isWaveInProgress = true + ____print( + nil, + (((((LOG_PREFIX .. " 🔁 SpawnWave: все волны пройдены, повтор последней волны (ночь ") .. tostring(self.currentNight)) .. "), maxZombies=") .. tostring(lastWaveConfig.maxZombies)) .. ", осталось зомби от предыдущих волн: ") .. tostring(self.zombieCount) + ) + self:spawnZombiesFromConfig(lastWaveConfig, "wave_point") + return + end + local waveConfig = waves[self.waveNumber + 1] + self.waveNumber = self.waveNumber + 1 + self.isWaveInProgress = true + self:announceWaveStarted(#waves) + ____print( + nil, + (((((((LOG_PREFIX .. " Волна старт: night=") .. tostring(self.currentNight)) .. " wave=") .. tostring(self.waveNumber)) .. "/") .. tostring(#waves)) .. " max=") .. tostring(waveConfig.maxZombies) + ) + self:spawnZombiesFromConfig(waveConfig, "wave_point") +end +function WaveManager.prototype.SpawnNextWave(self) + local gameState = GameRules:State_Get() + if gameState ~= DOTA_GAMERULES_STATE_GAME_IN_PROGRESS then + ____print( + nil, + ((LOG_PREFIX .. " SpawnNextWave: пропуск — игра еще не началась (state: ") .. tostring(gameState)) .. ")" + ) + self.isNextWaveScheduled = false + return + end + if not self.isGameStarted then + ____print(nil, LOG_PREFIX .. " SpawnNextWave: пропуск — игра еще не началась (isGameStarted: false)") + self.isNextWaveScheduled = false + return + end + if GameRules:IsDaytime() then + self:handleNightEndedMusic() + ____print(nil, LOG_PREFIX .. " SpawnNextWave: пропуск — сейчас день") + self.isNextWaveScheduled = false + return + end + if self.isWaveInProgress then + ____print(nil, LOG_PREFIX .. " SpawnNextWave: пропуск — волна уже идёт") + self.isNextWaveScheduled = false + return + end + if self.currentNight == 0 then + ____print(nil, LOG_PREFIX .. " SpawnNextWave: пропуск — ночь не установлена (currentNight: 0)") + self.isNextWaveScheduled = false + return + end + if self.nightWavesStoppedForNight then + ____print(nil, LOG_PREFIX .. " SpawnNextWave: пропуск — волны ночи завершены (stopWavesAfter)") + self.isNextWaveScheduled = false + return + end + local waves = self:getWaves() + if #waves == 0 then + ____print( + nil, + (LOG_PREFIX .. " SpawnNextWave: нет волн для ночи ") .. tostring(self.currentNight) + ) + self.isNextWaveScheduled = false + return + end + self.isNextWaveScheduled = false + self.spawnedZombies = __TS__ArrayFilter( + self.spawnedZombies, + function(____, z) return z and IsValidEntity(z) and z:IsAlive() end + ) + self.zombieCount = #self.spawnedZombies + if self.waveNumber >= #waves then + local lastWaveConfigPre = waves[#waves] + if lastWaveConfigPre and lastWaveConfigPre.stopWavesAfter then + self.nightWavesStoppedForNight = true + ____print(nil, LOG_PREFIX .. " SpawnNextWave: пропуск — последняя волна с stopWavesAfter, повтора нет") + return + end + self.isRepeatingLastWave = true + local lastWaveConfig = waves[#waves] + self.isWaveInProgress = true + ____print( + nil, + (((((LOG_PREFIX .. " 🔁 SpawnNextWave: все волны пройдены, повтор последней волны (ночь ") .. tostring(self.currentNight)) .. "), maxZombies=") .. tostring(lastWaveConfig.maxZombies)) .. ", осталось зомби от предыдущих волн: ") .. tostring(self.zombieCount) + ) + self:spawnZombiesFromConfig(lastWaveConfig, "wave_point") + return + end + local waveConfig = waves[self.waveNumber + 1] + self.waveNumber = self.waveNumber + 1 + self.isWaveInProgress = true + self:announceWaveStarted(#waves) + ____print( + nil, + (((((((LOG_PREFIX .. " Волна старт: night=") .. tostring(self.currentNight)) .. " wave=") .. tostring(self.waveNumber)) .. "/") .. tostring(#waves)) .. " max=") .. tostring(waveConfig.maxZombies) + ) + self:spawnZombiesFromConfig(waveConfig, "wave_point") +end +function WaveManager.prototype.SetCurrentNight(self, night) + local oldNight = self.currentNight + self:stopBattleMusic() + self.activeMusicGroupIndex = nil + self.nightEndMusicPlayed = false + self.currentNight = math.max(1, night) + self.waveNumber = 0 + self.isRepeatingLastWave = false + self.night5RandomBossKillsSinceCycleStart = 0 + self.nightWavesStoppedForNight = false + self.isNextWaveScheduled = false + self.isWaveInProgress = false + ____print( + nil, + ((((LOG_PREFIX .. " SetCurrentNight: ") .. tostring(oldNight)) .. " -> ") .. tostring(self.currentNight)) .. ", waveNumber сброшен на 0, isWaveInProgress сброшен" + ) +end +function WaveManager.prototype.SetGameStarted(self) + if not self.isGameStarted then + self.isGameStarted = true + ____print(nil, LOG_PREFIX .. " SetGameStarted: игра началась, волны могут спавниться") + end +end +function WaveManager.prototype.SetNextWaveTime(self, seconds) + self.nextWaveTime = math.max(10, seconds) + Timers:RemoveTimer("WaveTimer") + self:StartWaveTimer() + ____print( + nil, + ((LOG_PREFIX .. " SetNextWaveTime: ") .. tostring(self.nextWaveTime)) .. " сек" + ) +end +function WaveManager.prototype.GetCurrentWave(self) + return self.waveNumber +end +function WaveManager.prototype.GetNextWaveTime(self) + return self.nextWaveTime +end +function WaveManager.prototype.SetNextWaveIndex(self, waveIndex) + self.isRepeatingLastWave = false + self.isNextWaveScheduled = false + if waveIndex <= 0 then + self.waveNumber = 0 + ____print( + nil, + ((LOG_PREFIX .. " SetNextWaveIndex: сброшен на 0 (входной индекс: ") .. tostring(waveIndex)) .. ")" + ) + return + end + local waves = self:getWaves() + if #waves == 0 then + self.waveNumber = 0 + ____print(nil, LOG_PREFIX .. " SetNextWaveIndex: нет волн, сброшен на 0") + return + end + self.waveNumber = math.min(waveIndex, #waves) - 1 + ____print( + nil, + ((((((LOG_PREFIX .. " SetNextWaveIndex: установлен на ") .. tostring(self.waveNumber)) .. " (входной индекс: ") .. tostring(waveIndex)) .. ", всего волн: ") .. tostring(#waves)) .. ")" + ) +end +function WaveManager.prototype.SetMaxWavesForNight(self, night, maxWaves) + if WAVE_SETTINGS[night] then + __TS__ArraySetLength(WAVE_SETTINGS[night].waves, maxWaves) + else + local lastNight = self:getLastConfiguredNight() + WAVE_SETTINGS[night] = {waves = WAVE_SETTINGS[lastNight].waves} + end + ____print( + nil, + (((LOG_PREFIX .. " SetMaxWavesForNight: ночь ") .. tostring(night)) .. ", maxWaves=") .. tostring(maxWaves) + ) +end +function WaveManager.prototype.GetMaxWavesForCurrentNight(self) + local ____opt_15 = WAVE_SETTINGS[self.currentNight] + return ____opt_15 and #____opt_15.waves or self.maxWavesInNight +end +function WaveManager.prototype.GetRemainingWaves(self) + local maxWaves = self:GetMaxWavesForCurrentNight() + return math.max(0, maxWaves - self.waveNumber) +end +function WaveManager.prototype.GetCurrentNight(self) + return self.currentNight +end +function WaveManager.prototype.getAliveSpawnedWaveUnits(self) + local out = {} + for ____, z in ipairs(self.spawnedZombies) do + if z and IsValidEntity(z) and z:IsAlive() then + out[#out + 1] = z + end + end + return out +end +function WaveManager.prototype.StartCutsceneReadyCheck(self, sceneId) + if not IsServer() then + return false + end + if not sceneId or #sceneId == 0 then + return false + end + local mgr = GameRules.CutsceneManager + if not mgr or mgr:isCutsceneActive() then + return false + end + self:openEndingCutsceneReadyCheck(sceneId) + return true +end +WaveManager.ABILITY_POINT = 16 +WaveManager.ABILITY_UNIT_TARGET = 32 +WaveManager.ABILITY_NO_TARGET = 4 +WaveManager.WAVE_CREEP_ABILITY_COOLDOWN = 2 +return ____exports diff --git a/soundevents/invasion_sounds.vsndevts_c b/soundevents/invasion_sounds.vsndevts_c new file mode 100644 index 0000000..d3f6181 Binary files /dev/null and b/soundevents/invasion_sounds.vsndevts_c differ diff --git a/soundevents/wave_music.vsndevts_c b/soundevents/wave_music.vsndevts_c new file mode 100644 index 0000000..999f154 Binary files /dev/null and b/soundevents/wave_music.vsndevts_c differ diff --git a/sounds/creeps/balah_babar.vsnd_c b/sounds/creeps/balah_babar.vsnd_c new file mode 100644 index 0000000..9fcb348 Binary files /dev/null and b/sounds/creeps/balah_babar.vsnd_c differ diff --git a/sounds/items/bloodstone_magical_sound.vsnd_c b/sounds/items/bloodstone_magical_sound.vsnd_c new file mode 100644 index 0000000..3c69e7a Binary files /dev/null and b/sounds/items/bloodstone_magical_sound.vsnd_c differ diff --git a/sounds/music/agent_gabena.vsnd_c b/sounds/music/agent_gabena.vsnd_c new file mode 100644 index 0000000..e387d0f Binary files /dev/null and b/sounds/music/agent_gabena.vsnd_c differ diff --git a/sounds/music/bruh.vsnd_c b/sounds/music/bruh.vsnd_c new file mode 100644 index 0000000..f702573 Binary files /dev/null and b/sounds/music/bruh.vsnd_c differ diff --git a/sounds/music/byd_dobr_idi.vsnd_c b/sounds/music/byd_dobr_idi.vsnd_c new file mode 100644 index 0000000..a60f06e Binary files /dev/null and b/sounds/music/byd_dobr_idi.vsnd_c differ diff --git a/sounds/music/cat_shnapy.vsnd_c b/sounds/music/cat_shnapy.vsnd_c new file mode 100644 index 0000000..d99bef4 Binary files /dev/null and b/sounds/music/cat_shnapy.vsnd_c differ diff --git a/sounds/music/dobro_pozhalovat_na_server_shizofreniya.vsnd_c b/sounds/music/dobro_pozhalovat_na_server_shizofreniya.vsnd_c new file mode 100644 index 0000000..1a0f300 Binary files /dev/null and b/sounds/music/dobro_pozhalovat_na_server_shizofreniya.vsnd_c differ diff --git a/sounds/music/dobro_pozhalovat_v_club.vsnd_c b/sounds/music/dobro_pozhalovat_v_club.vsnd_c new file mode 100644 index 0000000..ad4ca3b Binary files /dev/null and b/sounds/music/dobro_pozhalovat_v_club.vsnd_c differ diff --git a/sounds/music/eblo_razraba.vsnd_c b/sounds/music/eblo_razraba.vsnd_c new file mode 100644 index 0000000..27626c1 Binary files /dev/null and b/sounds/music/eblo_razraba.vsnd_c differ diff --git a/sounds/music/eto_prosto_okhueno.vsnd_c b/sounds/music/eto_prosto_okhueno.vsnd_c new file mode 100644 index 0000000..3a72cfd Binary files /dev/null and b/sounds/music/eto_prosto_okhueno.vsnd_c differ diff --git a/sounds/music/fbi_open_up.vsnd_c b/sounds/music/fbi_open_up.vsnd_c new file mode 100644 index 0000000..01128bc Binary files /dev/null and b/sounds/music/fbi_open_up.vsnd_c differ diff --git a/sounds/music/get_out.vsnd_c b/sounds/music/get_out.vsnd_c new file mode 100644 index 0000000..ad835dc Binary files /dev/null and b/sounds/music/get_out.vsnd_c differ diff --git a/sounds/music/i_am_sad.vsnd_c b/sounds/music/i_am_sad.vsnd_c new file mode 100644 index 0000000..f23533c Binary files /dev/null and b/sounds/music/i_am_sad.vsnd_c differ diff --git a/sounds/music/ia_vas_unichtozhu.vsnd_c b/sounds/music/ia_vas_unichtozhu.vsnd_c new file mode 100644 index 0000000..ec2aa7e Binary files /dev/null and b/sounds/music/ia_vas_unichtozhu.vsnd_c differ diff --git a/sounds/music/jump.vsnd_c b/sounds/music/jump.vsnd_c new file mode 100644 index 0000000..5e63a9a Binary files /dev/null and b/sounds/music/jump.vsnd_c differ diff --git a/sounds/music/kak_rulit.vsnd_c b/sounds/music/kak_rulit.vsnd_c new file mode 100644 index 0000000..909f418 Binary files /dev/null and b/sounds/music/kak_rulit.vsnd_c differ diff --git a/sounds/music/kak_zhit_to_a.vsnd_c b/sounds/music/kak_zhit_to_a.vsnd_c new file mode 100644 index 0000000..77d5931 Binary files /dev/null and b/sounds/music/kak_zhit_to_a.vsnd_c differ diff --git a/sounds/music/kitty_flex.vsnd_c b/sounds/music/kitty_flex.vsnd_c new file mode 100644 index 0000000..e44eec7 Binary files /dev/null and b/sounds/music/kitty_flex.vsnd_c differ diff --git a/sounds/music/kto_myaukaet.vsnd_c b/sounds/music/kto_myaukaet.vsnd_c new file mode 100644 index 0000000..c1978ba Binary files /dev/null and b/sounds/music/kto_myaukaet.vsnd_c differ diff --git a/sounds/music/kuda.vsnd_c b/sounds/music/kuda.vsnd_c new file mode 100644 index 0000000..57cf9a0 Binary files /dev/null and b/sounds/music/kuda.vsnd_c differ diff --git a/sounds/music/muhehehehe.vsnd_c b/sounds/music/muhehehehe.vsnd_c new file mode 100644 index 0000000..2ff28f1 Binary files /dev/null and b/sounds/music/muhehehehe.vsnd_c differ diff --git a/sounds/music/murlok.vsnd_c b/sounds/music/murlok.vsnd_c new file mode 100644 index 0000000..e83edc8 Binary files /dev/null and b/sounds/music/murlok.vsnd_c differ diff --git a/sounds/music/na_nas_napali.vsnd_c b/sounds/music/na_nas_napali.vsnd_c new file mode 100644 index 0000000..391fd45 Binary files /dev/null and b/sounds/music/na_nas_napali.vsnd_c differ diff --git a/sounds/music/ne_ponimaiu_karina_strimersha_slozhno_slozhno.vsnd_c b/sounds/music/ne_ponimaiu_karina_strimersha_slozhno_slozhno.vsnd_c new file mode 100644 index 0000000..40239e7 Binary files /dev/null and b/sounds/music/ne_ponimaiu_karina_strimersha_slozhno_slozhno.vsnd_c differ diff --git a/sounds/music/ne_tvoy_uroven_dorogoy.vsnd_c b/sounds/music/ne_tvoy_uroven_dorogoy.vsnd_c new file mode 100644 index 0000000..4cce63a Binary files /dev/null and b/sounds/music/ne_tvoy_uroven_dorogoy.vsnd_c differ diff --git a/sounds/music/nepravilno_poprobuy_esche_raz.vsnd_c b/sounds/music/nepravilno_poprobuy_esche_raz.vsnd_c new file mode 100644 index 0000000..75b6677 Binary files /dev/null and b/sounds/music/nepravilno_poprobuy_esche_raz.vsnd_c differ diff --git a/sounds/music/nikhuia_ne_ponial_no_ochen_interesno.vsnd_c b/sounds/music/nikhuia_ne_ponial_no_ochen_interesno.vsnd_c new file mode 100644 index 0000000..a78a425 Binary files /dev/null and b/sounds/music/nikhuia_ne_ponial_no_ochen_interesno.vsnd_c differ diff --git a/sounds/music/nya.vsnd_c b/sounds/music/nya.vsnd_c new file mode 100644 index 0000000..5a6ed55 Binary files /dev/null and b/sounds/music/nya.vsnd_c differ diff --git a/sounds/music/oi_tak_nravitsa.vsnd_c b/sounds/music/oi_tak_nravitsa.vsnd_c new file mode 100644 index 0000000..b503d79 Binary files /dev/null and b/sounds/music/oi_tak_nravitsa.vsnd_c differ diff --git a/sounds/music/olyhi_bezdari_ogyzki.vsnd_c b/sounds/music/olyhi_bezdari_ogyzki.vsnd_c new file mode 100644 index 0000000..39c513b Binary files /dev/null and b/sounds/music/olyhi_bezdari_ogyzki.vsnd_c differ diff --git a/sounds/music/ou_mai.vsnd_c b/sounds/music/ou_mai.vsnd_c new file mode 100644 index 0000000..5b79f8d Binary files /dev/null and b/sounds/music/ou_mai.vsnd_c differ diff --git a/sounds/music/po_syobam.vsnd_c b/sounds/music/po_syobam.vsnd_c new file mode 100644 index 0000000..34b54e7 Binary files /dev/null and b/sounds/music/po_syobam.vsnd_c differ diff --git a/sounds/music/poshel_process.vsnd_c b/sounds/music/poshel_process.vsnd_c new file mode 100644 index 0000000..75ae7f3 Binary files /dev/null and b/sounds/music/poshel_process.vsnd_c differ diff --git a/sounds/music/posledniy_ponedelnik_zivesh.vsnd_c b/sounds/music/posledniy_ponedelnik_zivesh.vsnd_c new file mode 100644 index 0000000..2fe37e4 Binary files /dev/null and b/sounds/music/posledniy_ponedelnik_zivesh.vsnd_c differ diff --git a/sounds/music/rot_etogo_kazino.vsnd_c b/sounds/music/rot_etogo_kazino.vsnd_c new file mode 100644 index 0000000..6ed3e18 Binary files /dev/null and b/sounds/music/rot_etogo_kazino.vsnd_c differ diff --git a/sounds/music/s_kakoy_stati.vsnd_c b/sounds/music/s_kakoy_stati.vsnd_c new file mode 100644 index 0000000..3fb72b6 Binary files /dev/null and b/sounds/music/s_kakoy_stati.vsnd_c differ diff --git a/sounds/music/shizofreniya.vsnd_c b/sounds/music/shizofreniya.vsnd_c new file mode 100644 index 0000000..a0a8f03 Binary files /dev/null and b/sounds/music/shizofreniya.vsnd_c differ diff --git a/sounds/music/sir_no_sir.vsnd_c b/sounds/music/sir_no_sir.vsnd_c new file mode 100644 index 0000000..e4a7e3a Binary files /dev/null and b/sounds/music/sir_no_sir.vsnd_c differ diff --git a/sounds/music/sir_yes_sir.vsnd_c b/sounds/music/sir_yes_sir.vsnd_c new file mode 100644 index 0000000..239d199 Binary files /dev/null and b/sounds/music/sir_yes_sir.vsnd_c differ diff --git a/sounds/music/stop_mne_ne_priyatno.vsnd_c b/sounds/music/stop_mne_ne_priyatno.vsnd_c new file mode 100644 index 0000000..77b79ed Binary files /dev/null and b/sounds/music/stop_mne_ne_priyatno.vsnd_c differ diff --git a/sounds/music/stoyat_ya_yzhe_eto_sosal.vsnd_c b/sounds/music/stoyat_ya_yzhe_eto_sosal.vsnd_c new file mode 100644 index 0000000..8cc2832 Binary files /dev/null and b/sounds/music/stoyat_ya_yzhe_eto_sosal.vsnd_c differ diff --git a/sounds/music/ura_pobeda.vsnd_c b/sounds/music/ura_pobeda.vsnd_c new file mode 100644 index 0000000..250ef08 Binary files /dev/null and b/sounds/music/ura_pobeda.vsnd_c differ diff --git a/sounds/music/uvorot_ot_spelov.vsnd_c b/sounds/music/uvorot_ot_spelov.vsnd_c new file mode 100644 index 0000000..877bf25 Binary files /dev/null and b/sounds/music/uvorot_ot_spelov.vsnd_c differ diff --git a/sounds/music/v_komp_igri_igral.vsnd_c b/sounds/music/v_komp_igri_igral.vsnd_c new file mode 100644 index 0000000..c62bb6e Binary files /dev/null and b/sounds/music/v_komp_igri_igral.vsnd_c differ diff --git a/sounds/music/vot_eto_nikhuia_sebe.vsnd_c b/sounds/music/vot_eto_nikhuia_sebe.vsnd_c new file mode 100644 index 0000000..10a780f Binary files /dev/null and b/sounds/music/vot_eto_nikhuia_sebe.vsnd_c differ diff --git a/sounds/music/vot_eto_povorot.vsnd_c b/sounds/music/vot_eto_povorot.vsnd_c new file mode 100644 index 0000000..77b96c2 Binary files /dev/null and b/sounds/music/vot_eto_povorot.vsnd_c differ diff --git a/sounds/music/zachem_ya_suda_prishel.vsnd_c b/sounds/music/zachem_ya_suda_prishel.vsnd_c new file mode 100644 index 0000000..b400268 Binary files /dev/null and b/sounds/music/zachem_ya_suda_prishel.vsnd_c differ diff --git a/sounds/music/zaika.vsnd_c b/sounds/music/zaika.vsnd_c new file mode 100644 index 0000000..0b9569b Binary files /dev/null and b/sounds/music/zaika.vsnd_c differ