• 🏆 Texturing Contest #33 is OPEN! Contestants must re-texture a SD unit model found in-game (Warcraft 3 Classic), recreating the unit into a peaceful NPC version. 🔗Click here to enter!
  • It's time for the first HD Modeling Contest of 2024. Join the theme discussion for Hive's HD Modeling Contest #6! Click here to post your idea!

Wandering Plague v1.1

This bundle is marked as useful / simple. Simplicity is bliss, low effort and/or may contain minor bugs.
Wandering Plague
Morphs the targeted unit into a harmless critter, being unable to attack or cast. Enemy units next to the morphed one have a chance of 50% per second to get morphed for half the duration. Units that got morphed passivly have a chance of 30% to morph other units too.

Level 1: 3.0/1.5 seconds morphed
Level 2: 4.0/2.0 seconds morphed
Level 3: 5.0/2.5 seconds morphed

15 seconds cooldown
JASS:
/*  INTRODUCTIONS

    To use this spell, copy all self-made spells, buffs and units into your own map.
    Afterwards adjust all the IDs in the trigger to ensure they're correct.
    If you are unsure if you got all stuff just check the globals, all names are mentioned there.
    It is recommended to start with the buffs, followed by the spells and the units at last
    (although its only important that the buffs are first. If not you have to reassign the buff in the objecteditor to the spell).
    
    Also, make sure to always save the map befor testing it. If not the map wont work and a test/game would send you into the
    Warcraft III menu instead of playing it.
    
    Feel free to use and/or change this spell as much as you want, no credits necessary.
    
    Greetins, Justify */
    
scope WanderingPlague initializer OnInit

    globals
        //------------------------------Wandering Plague------------------------------//
        
        //-- IDs --//
        private constant integer WANDERING_PLAGUE_ID = 'A000'            //ID of the spell 'Wandering Plague'
        private constant integer WANDERING_PLAGUE_SECOND_ID = 'A001'     //ID of the spell 'Wandering Plague (Second)'
        private constant integer WANDERING_PLAGUE_BUFF_ID = 'B000'       //ID of the buff 'Wandering Plague'
        private constant integer WANDERING_PLAGUE_SECOND_BUFF_ID = 'B001'//ID of the buff 'Wandering Plague (Second)'
        private constant integer WANDERING_PLAGUE_DUMMY_ID = 'h001'      //ID of the unit 'dummy'. Feel free to use your own dummy/XE-dummy
        private constant string WANDERING_PLAGUE_ORDER = "hex"           //Order string of the spell 'Wandering Plague'
        
        //--Options--//
        private constant real WANDERING_PLAGUE_PRIM_CHAIN_CHANCE = 0.5   //Chance of hexing other units around the main target, each second
        private constant real WANDERING_PLAGUE_SEC_CHAIN_CHANCE = 0.3    //Chance of hexing other units around new-hexed targets, each second
        private constant real WANDERING_PLAGUE_CHAIN_RANGE = 500.        //Area of affected units
        
        //---------------------------End of Wandering Plague--------------------------//
    endglobals
    
    private struct Morphed
        unit tar
        integer level
        player orig
        
        static Morphed AFFECTED
        static boolean IS_PRIMARY
        
        static method getEnemys takes nothing returns boolean
            local unit u = GetFilterUnit()
            local unit dummy
            local timer t
            local Morphed this
            
            if GetWidgetLife(u) > 0.405 and /* Alive
            */ IsUnitEnemy(u, AFFECTED.orig) and /* Affects enemies
            */ GetUnitAbilityLevel(u, WANDERING_PLAGUE_BUFF_ID) == 0 and /* Not hexed primary
            */ GetUnitAbilityLevel(u, WANDERING_PLAGUE_SECOND_BUFF_ID) == 0 and /* Not infected secondary 
            */ (IS_PRIMARY and GetRandomReal(0., 1.) <= WANDERING_PLAGUE_PRIM_CHAIN_CHANCE or /*
            */ not IS_PRIMARY and GetRandomReal(0., 1.) <= WANDERING_PLAGUE_SEC_CHAIN_CHANCE) then
                set dummy = CreateUnit(AFFECTED.orig, WANDERING_PLAGUE_DUMMY_ID, GetUnitX(u), GetUnitY(u), 0.)
                call UnitAddAbility(dummy, WANDERING_PLAGUE_SECOND_ID)
                call SetUnitAbilityLevel(dummy, WANDERING_PLAGUE_SECOND_ID, AFFECTED.level)
                call IssueTargetOrder(dummy, WANDERING_PLAGUE_ORDER, u)
                call UnitApplyTimedLife(dummy, 'BTLF', 1.0)
                set dummy = null
                
                set t = NewTimer()
                set this = Morphed.allocate()
                set .tar = u
                set .level = AFFECTED.level
                set .orig = AFFECTED.orig
                call SetTimerData(t, this)
                call TimerStart(t, 1., true, function Morphed.check)
            endif
            set u = null
            
            return false
        endmethod
        
        static method check takes nothing returns nothing
            local timer t = GetExpiredTimer()
            local Morphed this = GetTimerData(t)
            
            set AFFECTED = this
            
            if GetUnitAbilityLevel(.tar, WANDERING_PLAGUE_BUFF_ID) > 0 then
                set IS_PRIMARY = true
            elseif GetUnitAbilityLevel(.tar, WANDERING_PLAGUE_SECOND_BUFF_ID) > 0 then
                set IS_PRIMARY = false
            else
                call ReleaseTimer(t)
                call .destroy()
                return
            endif
            
            call GroupEnumUnitsInArea(ENUM_GROUP, GetUnitX(.tar), GetUnitY(.tar), WANDERING_PLAGUE_CHAIN_RANGE, Condition(function Morphed.getEnemys))
        endmethod
        
        static method new takes nothing returns boolean
            local timer t
            local Morphed this
            
            if GetSpellAbilityId() == WANDERING_PLAGUE_ID then
                set t = NewTimer()
                set this = Morphed.allocate()
                set .tar = GetSpellTargetUnit()
                set .level = GetUnitAbilityLevel(.tar, WANDERING_PLAGUE_ID)
                set .orig = GetOwningPlayer(GetTriggerUnit())
                call SetTimerData(t, this)
                call TimerStart(t, 1., true, function Morphed.check)
            endif
            
            return false
        endmethod
        
        method destroy takes nothing returns nothing
            set .tar = null
            call .deallocate()
        endmethod
    endstruct

    
    private function OnInit takes nothing returns nothing
        local trigger t = CreateTrigger()
        
        call TriggerRegisterAnyUnitEventBJ(t, EVENT_PLAYER_UNIT_SPELL_EFFECT)
        call TriggerAddCondition(t, Condition(function Morphed.new))
    endfunction

endscope

Info
This spell isn't too complicated at all. I havent't coded for a long time and just wanted to do something and I decided to share it. Have fun with it, no credits required.

This spell requires vJass and TimerUtils/GroupUtils from Vexorian. They are added to the map if you don't already have them
changelog
v1.0Release
v1.1Minor code fixes

Keywords:
Wandering, Plague, Pestilence, Pest, Hex, Black Death, Justify
Contents

Just another Warcraft III map (Map)

Reviews
12th Dec 2015 IcemanBo: Too long as NeedsFix. Rejected. 11 Nov 2011 Bribe: Even if you are using a scope you need to list the requirements in the in-code comments so the user knows what to import. GetWidgetLife for checking if a unit is...

Moderator

M

Moderator

12th Dec 2015
IcemanBo: Too long as NeedsFix. Rejected.


11 Nov 2011
Bribe: Even if you are using a scope you need to list the requirements in the in-code comments so the user knows what to import.

GetWidgetLife for checking if a unit is alive is unsafe. The unit's life may have changed after death. You need IsUnitType, UNIT_TYPE_DEAD.

Use a FirstOfGroup instead of a group filter.

You do not require GroupUtils and it adds a lot of overhead to this script which does not need to be there. It is easy to inline "IsUnitInRangeXY" yourself.

"GetOwningPlayer(GetTriggerUnit())" -> GetTriggerPlayer()

SpellEffectEvent would be a good choice for a library requirement.
 
1- Don't use onDestroy
2- Null the player variables as well (in the destroy method)
3- Use GetWidgetLife (much faster at the C++ level since it only returns one float within a class)
4- Use shorter names for the variables :p
5- Make the variables "private constant"
6- Make the struct extend an array so that it doesn't generate unnecessary code in the background. (Then you don't need to use allocate and deallocate)
7- Inside the struct, it could be better to use "thistype" instead of the name of the struct.
It's exactly the same, but the only difference is that it allows you to change the struct
name to anything you want without having to pay attention to whatever is inside the
struct. Personally, I like it because it looks good ^^ thistype :p
8- Null the local trigger in OnInit
9- Try using a library instead of a scope for this spell to make the requirements explicit :p

Good Job ^^
 
Level 13
Joined
Mar 16, 2008
Messages
941
Okay, first feedback. I was sure there will be some mistakes due to the big brake I made.
I always used onDestroy but I'm sure the call can be saved. GetWidgetLife and thistype will be added soon, just forgot them. However, I can't guarantee that they are correct, but if the tests a friend made are correct nulling never-destroyed handles has no effect. Even with thousands of not-nulled references nothing changed.
Oh, and for sure I will add the private constants. For a test I added them to another trigger, a library. When re-copying them I forgot to change it. Thanks.
More feedback would be nice ;)
 
Level 17
Joined
Jun 17, 2007
Messages
1,433
Is there any reason why you're doing this:
JASS:
local Morphed this = GetTimerData(t)
set AFFECTED = this
rather than just this:
JASS:
set AFFECTED = GetTimerData(t)
?
Since your constants are private, you can shorten their names by a fair amount by avoiding the 'WANDERING_PLAGUE' prefix (ie, 'ID' instead of 'WANDERING_PLAGUE_ID').
Also, GroupUtils is by Rising_Dusk, not Vexorian.
 
Level 13
Joined
Mar 16, 2008
Messages
941
There is the reason that I want the syntax. AFFECTED.xyz everywhere is just annoying, the "this"-syntax is much cleaner imo, although I have an useless line due to this.
Will shorten the names, it's true. Another relict from a copy.
Credits will be fixed here with the next upload. In the map the librarie has Rising_Dusks introduction, I think it's clear enough.
 
Top