Modifying Silencers Int Steal

edited December 2015 in Questions

I'm guessing this ability is hardcoded and completely behind the scenes as its an innate ability. I did see a reference to it in the wisdom glaives ability, however.

Should it be modifiable, is it technically possible to modify it so that when someone dies around him he gets 1 int steal (instead of 2) but when silencer lands the killing blow, he gets 2 int steal?

Comments

  • edited December 2015 Posts: 1,670
    1. Remove it from Silencer if you are going to use the same hero.
    2. Rewrite it

    The remove part:

    https://github.com/MNoya/PMP/blob/master/game/dota_addons/pmp/scripts/vscripts/pmp.lua#L618-L620

    The concept of Modding Community doesn't go well together with Competitive Business
    My Project Page || My GitHub Profile ||

  • edited December 2015 Posts: 146

    Rewrite it

    Sounds good to me; In order to rewrite it i'd appreciate pointers in the right direction for the following:

    1. Which lua files should I edit / create in order to recreate the function?
    2. Which lua functions should I override / create?
  • Posts: 858

    in your game logic's OnHeroDeath (if its not there, make it, that name probably isnt right htough but you get the point of the function), enum over all heroes within 925 of death and if unit has silencer int steal buff than grant +2 int.

    MAKE CUSTOM GAMES GREAT AGAIN

    Finished-
    kv checker - https://arhowk.github.io
    panorama video series - https://moddota.com/forums/discussion/2021/inclusive-panorama-tutorial-quest-box

    My pet project This Cursed World/Crestfallen (name pending)
    https://moddota.com/forums/discussion/986/this-cursed-world-rpg#latest

  • in your game logic's OnHeroDeath (if its not there, make it, that name probably isnt right htough but you get the point of the function), enum over all heroes within 925 of death and if unit has silencer int steal buff than grant +2 int.

    Thanks for pointing me in the right direction. I'll go ahead and write up some code in the next couple of days, try it, and if there are issues, post it here.

  • edited December 2015 Posts: 146

    @Noya

    Since I can't publish, I decided to continue working on my mod slowly - sorry if its been a while since I posted. I tried the code that you suggested earlier, it seems that modifier_silencer_int_steal is not valid - the if statement never returns true even when I pick silencer. Perhaps the modifier name has changed? I tried searching for it but could not find a reference.

  • Posts: 1,670

    write dota_modifier_dump in the console after picking silencer and find it

    The concept of Modding Community doesn't go well together with Competitive Business
    My Project Page || My GitHub Profile ||

  • edited December 2015 Posts: 146

    write dota_modifier_dump in the console after picking silencer and find it

    Thanks for the tip; modifier_silencer_int_steal actually showed up so it was correct. For some reason though it never enters that if statement. Here is my code, same as yours:

    function CMegaDotaGameMode:OnNPCSpawned( keys )
    
        local npc = EntIndexToHScript(keys.entindex)
    
        if npc:IsRealHero() and npc.bFirstSpawned == nil then
    
            npc.bFirstSpawned = true
    
            --  This does not work
            if npc:HasModifier("modifier_silencer_int_steal") then
                npc:RemoveModifierByName("modifier_silencer_int_steal")
                print("silencer int steal removed") 
            end
        end 
    end
    
  • Posts: 1,670

    Check the script I linked you earlier, it has a 1 sec Timer. The modifier isn't applied until a couple of frames later, so a small timer to wait for the hero to be fully ready is required.

    The concept of Modding Community doesn't go well together with Competitive Business
    My Project Page || My GitHub Profile ||

  • edited December 2015 Posts: 146

    Check the script I linked you earlier, it has a 1 sec Timer. The modifier isn't applied until a couple of frames later, so a small timer to wait for the hero to be fully ready is required.

    Thanks for the feedback - I implemented the timer you mentioned; however when the script hit the Timers:CreateTimer line I got the following error:

    Script Runtime Error: scripts/vscripts/events.lua:17: attempt to index global 'Timers' (a nil value) scripts/vscripts/events.lua:17: in function 'OnHeroInGame'

    Here is the code that I used - I checked to see if I need to include some sort of library for the timer function but I did not see any reference to that in your code.

    EDIT: I took more time to browse your project and noticed a timers.lua file in your libraries subdirectory. Am working on figuring out how to include it in my events.lua file

    function CMegaDotaGameMode:OnNPCSpawned( keys )
    
        local npc = EntIndexToHScript(keys.entindex)
    
        if npc:IsRealHero() and npc.bFirstSpawned == nil then
    
            npc.bFirstSpawned = true
    
            CMegaDotaGameMode:OnHeroInGame(npc)
    
        end 
    
    end
    
    function CMegaDotaGameMode:OnHeroInGame( hero )
    
        --  THIS CAUSES AN ERROR
        Timers:CreateTimer(1, function()
            if hero:HasModifier("modifier_silencer_int_steal") then
                hero:RemoveModifierByName("modifier_silencer_int_steal")
                print("silencer int steal removed") 
            end
        end)
    end
    
  • Posts: 213

    It's BMD's timer library from barebones. It does, however, work on it's own, so just grab timers.lua and you're good to go.

  • Posts: 1,670

    and require('timers') it

    The concept of Modding Community doesn't go well together with Competitive Business
    My Project Page || My GitHub Profile ||

  • Posts: 146

    @vyrus

    Thanks for clarifying that - there are certainly other library files in barebones that I can see will be useful.

    @Noya

    When I was writing my previous post, I looked for a require('timers') in your code but it was not in the file that you quoted your source from. As for myself I inserted require('libraries/timers') and its all working good now.

  • edited January 2016 Posts: 146

    I have implemented a routine that does what I intended which steals 1 int in 925 AOE if silencer does not land the killing blow and steals 2 int when he does land the killing blow. I tested it and its working (stats are modified correctly etc).

    Even though the code below works, I am not sure if my approach was the best approach so feedback on that would be appreciated:

    local silencerTeam = nil
    
    function CMegaDotaGameMode:OnHeroInGame( hero )
        Timers:CreateTimer(1, function()
            if hero:HasModifier("modifier_silencer_int_steal") then
                silencerTeam = hero:GetTeam()           
                hero:RemoveModifierByName("modifier_silencer_int_steal")
                print("silencer int steal removed") 
            end
        end)
    end
    
    function CMegaDotaGameMode:OnEntityKilled( event )  
        local killedUnit = EntIndexToHScript( event.entindex_killed )
        local killedTeam = killedUnit:GetTeam()
        local killingHero = EntIndexToHScript( event.entindex_attacker )
        local killingTeam = killingHero:GetTeam()
    
        if killedUnit:IsRealHero() then
            self.allSpawned = true
    
            print("Hero has been killed")   
    
            --  Silencer is in game
            if silencerTeam ~= nil and silencerTeam == killingTeam then
                --  Silencer landed the killing blow, steal 2 int
                if killingHero:GetUnitName() == "npc_dota_hero_silencer" then
                    killingHero:ModifyIntellect(2.0) 
                    killedUnit:ModifyIntellect(-2.0) 
                    print("2 int stolen")
                --  Check to see if silencer was within 925 of killed unit and award 1 int to silencer
                else
                    local nearbyUnits = FindUnitsInRadius(  killedTeam,
                                                            killedUnit:GetAbsOrigin(),
                                                            nil,
                                                            925,
                                                            DOTA_UNIT_TARGET_TEAM_ENEMY,
                                                            DOTA_UNIT_TARGET_HERO,
                                                            DOTA_UNIT_TARGET_FLAG_NONE,
                                                            FIND_ANY_ORDER,
                                                            false)          
    
                    for key,unit in ipairs(nearbyUnits) do                   
                        print(unit:GetUnitName())
                        if unit:GetUnitName() == "npc_dota_hero_silencer" then
                            unit:ModifyIntellect(1.0) 
                            killedUnit:ModifyIntellect(-1.0) 
                            print("1 int stolen")
                            break
                        end                 
                    end
                end
            end
        end
    end
    

    I am however missing the following:

    1. The visual pop up of the number of intellegence stolen (both on the dying hero and on silencer)
    2. The permanent buff that appears on silencer once he has some int stolen displaying how much stolen int he has.

    Can someone point me in the right direction for the above 2?

  • A modifier lua handles your case perfectly, I think. At least MUCH better than you handled it.

    https://developer.valvesoftware.com/wiki/Dota_2_Workshop_Tools/Lua_Abilities_and_Modifiers

    So the way you do it:

    make a modifier_my_int_steal, on OnHeroInGame you do hero:AddNewModifier(hero, nil, "modifier_my_int_steal", {})

    This modifier contains:

    MODIFIER_EVENT_ON_HERO_KILLED 
    

    Then in OnHeroKilled(event) you do

    if (self:GetParent():GetAbsOrigin() - event.unit:GetAbsOrigin()):Length2D() <= 925 then
        self:SetStackCount(self:GetStackCount() + 2)
        self:GetParent():CalculateStatBonus()
    

    And finally you have MODIFIER_PROPERTY_STATS_INTELLECT_BONUS which results in GetModifierBonusStats_Intellect() return self:GetStackCount()

    It will be like 20 lines in total.

    As for the popup it's a particle, though I'm not sure which one exactly.

  • Posts: 146

    @DoctorGester

    Will setting up the ability your suggested way allow for that permanent buff icon? I'm guessing yes as it seems the proper way to recreate new abilities. As for the pop up display for int stolen I think I remember seeing it somewhere, just don't remember where.

  • This way is actually based on a buff icon.

  • edited January 2016 Posts: 188
    if dev_test_modifier == nil then
        dev_test_modifier = class({})
    end
    
    
    function dev_test_modifier:GetAttributes()
        return MODIFIER_ATTRIBUTE_IGNORE_INVULNERABLE
    end
    
    
    function dev_test_modifier:OnCreated(kv)
        if IsServer() then
            self.distance = kv.maxdistance or 925
            self.steal = kv.steal or 5
        end
    end
    
    function dev_test_modifier:IsPurgable()
        return false
    end
    
    function dev_test_modifier:DeclareFunctions()
        local funcs = {
        MODIFIER_EVENT_ON_DEATH
        }
    
        return funcs
    end
    
    function dev_test_modifier:OnDeath(kv)
        if IsServer() then
            if kv.unit:IsRealHero() and self:GetParent():IsRealHero() and kv.unit ~= self:GetParent() then
                if kv.unit:GetTeam() ~= self:GetParent():GetTeam() then
                    if (self:GetParent():GetAbsOrigin() - kv.unit:GetAbsOrigin()):Length2D() <= self.distance or kv.attacker == self:GetParent() then
                        local stolen = 0
                        if kv.unit:GetBaseIntellect() > 1 then
                            if kv.unit:GetBaseIntellect() > self.steal + 1 then
                                kv.unit:SetBaseIntellect(kv.unit:GetBaseIntellect() - self.steal)
                                stolen = self.steal
                                kv.unit:CalculateStatBonus()
                            else
                                local old = kv.unit:GetBaseIntellect()
                                kv.unit:SetBaseIntellect(1)
                                local new = kv.unit:GetBaseIntellect()
                                stolen = old - new
                            end
                        end
                        self:GetParent():SetBaseIntellect(self:GetParent():GetBaseIntellect() + stolen)
                        self:SetStackCount(self:GetStackCount() + stolen)
                        self:GetParent():CalculateStatBonus()
                        --particle
                        local life_time = 2.0
                        local digits = string.len( math.floor( stolen ) ) + 1
                        local numParticle = ParticleManager:CreateParticle( "particles/msg_fx/msg_miss.vpcf", PATTACH_OVERHEAD_FOLLOW, self:GetParent() )
                        ParticleManager:SetParticleControl( numParticle, 1, Vector( 10, stolen, 0 ) )
                        ParticleManager:SetParticleControl( numParticle, 2, Vector( life_time, digits, 0 ) )
                        ParticleManager:SetParticleControl( numParticle, 3, Vector( 100, 100, 255 ) )
                    end
                end
            end
        end
    end
    
    
  • edited January 2016 Posts: 146

    @Everyone

    Sorry I have not been responsive; been held up these past few days.

    @DrTeaSpoon

    Thanks for the code snippet! I'm gonna study it and try it when I get some free time over the next couple of days - from what I see it looks like it belongs in my events.lua file. Your suggested method does appear somewhat different from how DoctorGester proposed to implement it. I'll try both and repost.

  • edited January 2016 Posts: 188

    Well its a lua modifier that mimics the int steal of silencer. Rename the modifier from dev_test_modifier how you like ( int this example we use custom_silencer_int_steal)
    Then link it anywhere in gamemode file:

    LinkLuaModifier( "custom_silencer_int_steal", "lua_modifiers/custom_int_steal.lua", LUA_MODIFIER_MOTION_NONE )
    

    I like to put generic modifiers in their own folder at vscripts.
    Then remove modifier_silencer_int_steal when silencer is first spawned and then apply this custom modifier with:

    hHero:AddNewModifier( hHero, nil, "custom_silencer_int_steal", { maxdistance = 925, steal = 1 } )
    

    (hHero is just he handle of the hero in your first spawned function)
    And you should be set. You might want to change the attribute to MODIFIER_ATTRIBUTE_IGNORE_INVULNERABLE + MODIFIER_ATTRIBUTE_PERMANENT to it as well

  • edited January 2016 Posts: 146

    @DrTeaSpoon

    Thanks for the clarifications. I am assuming the code in your first post would belong in custom_int_steal.lua

    Once I get some time, i'll try implementing this and get back to you if I encounter any issues that i'm unable to resolve.

  • edited January 2016 Posts: 188

    yes, In the example it should be file in path <addon>/scripts/vscripts/lua_modifiers/custom_int_steal.lua
    Inspired by this post I made item for my mod that adds random stat to the holder when nearby enemy hero dies(not steal because I think thats just nasty). The example may help :3
    Git Hub Links:
    https://github.com/DrTeaSpoon/Dota2Overflow/blob/master/game/dota_addons/dota2overflow/scripts/npc/npc_items_custom.txt#L657-L699
    https://github.com/DrTeaSpoon/Dota2Overflow/blob/master/game/dota_addons/dota2overflow/scripts/vscripts/lua_items/overflow/blessed/ring/item.lua
    https://github.com/DrTeaSpoon/Dota2Overflow/blob/master/game/dota_addons/dota2overflow/scripts/vscripts/lua_items/overflow/blessed/ring/modifier.lua

  • Posts: 146

    I'm glad my idea inspired you :) Personally i'm thinking in the future of setting the int steal up to 3 if the hero that's killed is 5 levels higher than silencer. Lots of food for thought here!

    I went ahead and implemented your solution - worked like a charm the first time round (i'm new to lua so most of the time I mess up on some small caveat that I was unaware of).

    Right now i'm looking into resolving the following:

    1. right now +1 is displayed on silencer upon int steal, not +1 int
    2. no -1 int is displayed on dying hero
    3. the buff is there by default even when no int is stolen; not sure if thats the behavior in the original
    4. I need to find the original graphic for the buff

    For items 1 and 2 i'm looking at SetParticleControl - i'll continue looking into solutions for these items and repost later. Thanks again for all the feedback.

  • edited January 2016 Posts: 188

    1: Original behaviour didn't read "int" in the particle. Only had blue colour (cp3 controls the colour).
    2: Create another particle at the target. Change the cp1's first vector to 1 for minus symbol: (There are bunch other symbols in there as well.)

    local targetParticle = ParticleManager:CreateParticle( "particles/msg_fx/msg_miss.vpcf", PATTACH_OVERHEAD_FOLLOW, kv.unit )
    ParticleManager:SetParticleControl( targetParticle, 1, Vector( 11, 1, 0 ) )
    ParticleManager:SetParticleControl( targetParticle, 2, Vector( life_time, digits, 0 ) )
    ParticleManager:SetParticleControl( targetParticle, 3, Vector(100,100,255) )
    

    3: Create condition in the IsHidden function:

    function custom_silencer_int_steal:IsHidden()
        if self:GetStackCount() == 0 then
            return true
        else
            return false
        end
    end
    

    4: Just use the name of the ability:

    function custom_silencer_int_steal:GetTexture()
        return "silencer_glaives_of_wisdom"
    end
    
  • function custom_silencer_int_steal:IsHidden()
        return self:GetStackCount() == 0
    end
    
  • Ye, that works too. :3

  • Posts: 146

    @DrTeaSpoon / DoctorGester

    Sorry for the abysmally late response; thanks again for the feedback - I have read your post and will be trying out your changes in the next week or so. Right now I have been focused on actually publishing my mod - I had serious issues with that thanks to what turned out to be a bug on valve's part. Now its working :)

    What i'll be doing over the next few days is focusing on updating my release notes and doing a quick fix on something I missed out on when I published. Once i'm done with that, I will get back to working on customizing silencers int steal for the next patch as its something I definitely want to include.