• Listen to a special audio message from Bill Roper to the Hive Workshop community (Bill is a former Vice President of Blizzard Entertainment, Producer, Designer, Musician, Voice Actor) 🔗Click here to hear his message!
  • Read Evilhog's interview with Gregory Alper, the original composer of the music for WarCraft: Orcs & Humans 🔗Click here to read the full interview.

Fireblaze [1.2b]

Hello, I haven't been touching the world editor for a long time, but recently I got bored so I tried to make a simple (and hopefully original) spell with my *not so creative* mind.
Hopefully I did not become really rusty with coding. Nevertheless, the spell is MUI and allows for extensive configurations.

I mostly use camelCase for naming my private functions and variables. I hope that's okay.

Description

Summons a phoenix that soars with blazing speed towards the target point, dealing 65/90/115 damage to enemy units in its wake and leaving a trail of blazing fire that only appears after a while for 5 seconds, dealing 25/30/35 damage per second to enemy units within the fire. The phoenix transforms into pure heat energy upon reaching its destination, dealing 175/220/265 damage to enemy units close enough to witness its transformation.

Units can be hit by both the phoenix and the explosion.
JASS:
library Fireblaze initializer init uses TimerUtils
    
//====================================================================================
/*                       FIREBLAZE   by F1ashB0nd                           */
//
/*       Credits to:    Vexorian (dummy.mdx, TimerUtils)                    
                        Bribe (NewTable/Table)                              */
//
// How to implement:
//
//      Step 1:
//    Create a new trigger and name it whatever you want (preferably "Fireblaze")
//    and then go to [Edit -> "Convert to Custom Text"]. Replace everything in the
//    newly created trigger with this code.
//
//      Step 2:
//    Now you have to implement TimerUtils and Table into your map. If you already have 
//    them, just skip this step. If you don't have it, copy and paste both triggers
//    from the test map into your map.
//    (Note: TimerUtils is made by Vexorian and Table by Bribe. Give them credits if you
//    use these systems.)
//
//      Step 3:
//    Import dummy.mdx into your map. You can export the dummy.mdx file and then
//    import it into your map.
//    (Again, dummy.mdx is also by Vexorian, so give credits when used.)
//
//      Step 4:
//    If you don't have any dummy units that uses Vexorian's dummy.mdx, you can copy
//    and paste the dummy from the test map.
//
//      Step 5:
//    Create a new ability and configure the settings below (including the raw code of
//    the dummy unit and the raw code of the ability.) Feel free to copy and paste
//    the ability in the test map but you still have to configure the dummy raw code
//    and ability raw code in the settings below.
//
//  Note: The only ability you will need if you decide to copy it from my map, is the
//        channel-based ability "Fireblaze". The other abilities classified under
//        specials are part of the test map itself.

//====================================================================================
    native UnitAlive takes unit id returns boolean
    
    globals
    
        //The rawcode of the ability that will trigger this spell (ability-type should be point-target or unit-target)
        private constant integer ABILITY_ID = 'A000'
        
        //The rawcode of the dummy unit
        private constant integer DUMMY_ID = 'dumy'
        
        //TOTAL VALUE = BASE + (INCREMENT x ABILITY LEVEL)
        //-
        //The damage of the explosion at the end.
        private constant real EXPLOSION_DAMAGE_BASE = 130
        private constant real EXPLOSION_DAMAGE_INCREMENT = 45
        //The radius of the explosion at the end.
        private constant real EXPLOSION_RADIUS_BASE = 325
        private constant real EXPLOSION_RADIUS_INCREMENT = 0
        
        //The damage of the projectile (or phoenix) as it touches units.
        private constant real PROJECTILE_DAMAGE_BASE = 40
        private constant real PROJECTILE_DAMAGE_INCREMENT = 25
        //The radius of the projectile (or phoenix) where units around it will be detected.
        private constant real PROJECTILE_RADIUS_BASE = 190
        private constant real PROJECTILE_RADIUS_INCREMENT = 0
        
        //The damage per second of the afterburn.
        private constant real FIRE_DAMAGE_BASE = 20
        private constant real FIRE_DAMAGE_INCREMENT = 5
        //The radius of the fire's damaging zone.
        private constant real FIRE_RADIUS_BASE = 150
        private constant real FIRE_RADIUS_INCREMENT = 0
        //The duration of the fire/afterburn.
        private constant real FIRE_DURATION_BASE = 5
        private constant real FIRE_DURATION_INCREMENT = 0
        
        //The speed of the projectile (or phoenix) in units per second.
        private constant real SPEED_BASE = 1500
        private constant real SPEED_INCREMENT = 0
        
        //The time elapsed before the afterburn starts.
        private constant real FIRE_DELAY_BASE = 1.4
        private constant real FIRE_DELAY_INCREMENT = 0
        
        
        
        //------------------
        //Additional Options
        //------------------
        
        
        //Choose to enable/disable the fire from taking place in water.
        //However, while disabled, the radius of the fire on land may reach still reach the nearby plot of water.
        private constant boolean FIRE_IN_WATER = false
        
        //Choose whether to allow filtered units of the projectile and explosion to be able to take damage from both sources.
        //If set to FALSE, the damage units take from the projectile and explosion will not go beyond the damage of the explosion.
        private constant boolean PROJECTILE_EXPLOSION_DAMAGE = true
        
        //The attack-type and damage-type of the damage done with this spell.
        private constant attacktype ATK_TYPE = ATTACK_TYPE_NORMAL
        private constant damagetype DMG_TYPE = DAMAGE_TYPE_UNIVERSAL
        
        //The interval where the spell effects happen (excluding the fire's damage interval)
        private constant real INTERVAL = 0.02
        
        //The interval where units inside the fire gets damaged. Preset at a moderate value to avoid lag.
        private constant real FIRE_DAMAGE_INTERVAL = 0.20
        
        //The size of the fire model.
        private constant real FIRE_SCALE = 2.10
        
        //The height of the projectile (or phoenix) above ground.
        private constant real PROJECTILE_HEIGHT = 85
        //The size of the projectile (or phoenix).
        private constant real PROJECTILE_SCALE = 1.80
        
        //The model path for the projectile
        private constant string PROJECTILE_MODEL = "units\\human\\phoenix\\phoenix.mdl"
        //The model path for the afterburn
        private constant string FIRE_MODEL = "Environment\\LargeBuildingFire\\LargeBuildingFire1.mdl"
        //Additional on-target special effects can be edited below.
        
        //Specify whether or not to preload the models of the projectile and fire.
        private constant boolean PRELOAD = true
        
    endglobals
    
    globals //These are globals used throughout this spell that shouldn't be configurated/changed by external functions.
        private group G = CreateGroup()
        private integer fb_inst = 0
        private constant integer PRIMARY_ID = 0 // ID for projectile
        private constant integer SEC_ID = 1 // ID for fire
        private constant integer FD_ID = 2 //FIRE_DUMMY_ID
        private constant integer FD_SFX_ID = 3 //FIRE_DUMMY_EFFECT_ID
        private constant integer FD_DUR_ID = 4 //FIRE_DUMMY_DURATION_ID
    endglobals
    
    public constant function GetInstances takes nothing returns integer //Returns the amount of instances of this spell.
        return fb_inst
    endfunction
    
    //Unit damage filter for the fire's damage per second.
    // unit u = the unit about to be damaged, player p = owner of the caster
    private function fireFilter takes unit u, player p returns boolean
        return not IsUnitType(u, UNIT_TYPE_STRUCTURE) and UnitAlive(u) and GetUnitTypeId(u) != DUMMY_ID and IsUnitEnemy(u, p) and not IsUnitType(u, UNIT_TYPE_MAGIC_IMMUNE)
    endfunction
    
    //Unit damage filter for the projectile (or phoenix).
    // unit u = the unit about to be damaged, player p = owner of the caster
    private function projectileFilter takes unit u, player p returns boolean
        return not IsUnitType(u, UNIT_TYPE_STRUCTURE) and UnitAlive(u) and GetUnitTypeId(u) != DUMMY_ID and IsUnitEnemy(u, p) and not IsUnitType(u, UNIT_TYPE_MAGIC_IMMUNE)
    endfunction
    
    //Unit damage filter for the explosion.
    // unit u = the unit about to be damaged, player p = owner of the caster
    private function explosionFilter takes unit u, player p returns boolean
        return not IsUnitType(u, UNIT_TYPE_STRUCTURE) and UnitAlive(u) and GetUnitTypeId(u) != DUMMY_ID and IsUnitEnemy(u, p)
    endfunction
    
    //Edit the special effect that appears on a unit that is about to be damaged by the projectile.
    //unit u = unit about to be damaged, player p = owner of the caster.
    private function projectileEffectsTarget takes unit u, player p returns nothing
        //Insert custom special effects here.
    endfunction
    
    //Edit the explosion special effects here.
    //unit dummy = the dummy unit at the center of the explosion.
    private function explosionEffects takes unit caster, unit dummy returns nothing
        call SetUnitScale(dummy, 1.4, 1.4, 1.4)
        call DestroyEffect(AddSpecialEffectTarget("Objects\\Spawnmodels\\Human\\HCancelDeath\\HCancelDeath.mdl", dummy, "origin"))
        call DestroyEffect(AddSpecialEffectTarget("Abilities\\Spells\\Other\\Doom\\DoomDeath.mdl", dummy, "origin"))
    endfunction
    
    //Edit the explosion special effects.
    //unit u = the unit about to be damaged, player p = owner of the caster.
    private function explosionEffectsTarget takes unit u, player p returns nothing
        //Insert custom special effects here.
    endfunction
    
    
//--------------------------------------------------------------------------------
    private struct Fireblaze
        player owner
        unit dummy
        unit caster
        effect projectile
        integer level
        integer fireDummyCount
        real targetX
        real targetY
        real x
        real x2
        real y
        real y2
        real speed
        real fireDuration
        real burnDuration
        real delay
        real explosionDamage
        real fireDamage
        real burnDamage
        real projectileDamage
        real fireDistance
        real angle
        real explosionRadius
        real fireRadius
        real projectileRadius
        real sinA
        real cosA
        TableArray tbArray
        
        boolean isFireDone
        boolean isExplosionDone
        
        private method clear takes nothing returns nothing
            call tbArray.flush()
            set owner = null
            set dummy = null
            set caster = null
            set projectile = null
            set fb_inst = fb_inst - 1
            call this.destroy()
        endmethod
        
        private static method fireDamageOverTime takes nothing returns nothing
            local thistype this = GetTimerData(GetExpiredTimer())
            local unit u
            local integer i = 1
            local integer deadDummies = 0
            local real dur
            
            loop //Here comes the dangerous UnitGroup loops
                exitwhen i > fireDummyCount
                set dur = tbArray[FD_DUR_ID].real[i] - FIRE_DAMAGE_INTERVAL
                set tbArray[FD_DUR_ID].real[i] = dur
                if dur > 0 then
                    //fire damage
                    call GroupEnumUnitsInRange(G, GetUnitX(tbArray[FD_ID].unit[i]), GetUnitY(tbArray[FD_ID].unit[i]), fireRadius, null)
                    
                    loop //Another loop
                        set u = FirstOfGroup(G)
                        exitwhen u == null
                        call GroupRemoveUnit(G, u)
                    
                        //the fires' area overlaps with each other so we need a table to determine which unit has already been damaged.
                        //the reason for the overlap is simple: to improve the accuracy of the linear unit detection. (empty space between the circular areas reduced)
                        if fireFilter(u, owner) and not tbArray[SEC_ID].has(GetHandleId(u)) then
                            call UnitDamageTarget(caster, u, fireDamage * FIRE_DAMAGE_INTERVAL, true, false, ATK_TYPE, DMG_TYPE, null)
                            set tbArray[SEC_ID][GetHandleId(u)] = 1
                        endif
                    endloop
                    
                    if dur - FIRE_DAMAGE_INTERVAL <= 0 then
                        call DestroyEffect(tbArray[FD_SFX_ID].effect[i])
                        //Add expiration timer so that the effect finishes playing its death animation instead of poofing just like that.
                        call UnitApplyTimedLife(tbArray[FD_ID].unit[i], 'BTLF', 0.1)
                    endif
                    
                else
                    set deadDummies = deadDummies + 1 //Mark the fire as 'dead'.
                endif
                set i = i + 1
            endloop
            
            if deadDummies == fireDummyCount and isFireDone and isExplosionDone then
                call ReleaseTimer(GetExpiredTimer())
                call this.clear()
                return
            endif
            call tbArray[SEC_ID].flush()
        endmethod
        
        private static method fireCallback takes nothing returns nothing
            local thistype this = GetTimerData(GetExpiredTimer())
            local unit u
            local integer i = 1
            local real X
            local real Y
            
            if delay > 0. then
                set delay = delay - INTERVAL
                return
            endif
            
            set x2 = x2 + speed * cosA
            set y2 = y2 + speed * sinA
            set fireDistance = fireDistance + speed
            
            if fireDistance >= fireRadius then
                set fireDistance = fireDistance - fireRadius
                
                if FIRE_IN_WATER or (not FIRE_IN_WATER and IsTerrainPathable(x2, y2, PATHING_TYPE_FLOATABILITY)) then
                    //offset the position back by the difference left by fireDistance, so that the fires will be uniformly distributed.
                    set u = CreateUnit(owner, DUMMY_ID, x2 - fireDistance * cosA, y2 - fireDistance * sinA, angle * bj_RADTODEG)
                    set fireDummyCount = fireDummyCount + 1
                    if fireDummyCount == 1 then
                        call TimerStart(NewTimerEx(this), FIRE_DAMAGE_INTERVAL, true, function thistype.fireDamageOverTime)
                    endif
                    call SetUnitScale(u, FIRE_SCALE, 0, 0)
                    set tbArray[FD_ID].unit[fireDummyCount] = u
                    set tbArray[FD_SFX_ID].effect[fireDummyCount] = AddSpecialEffectTarget(FIRE_MODEL, u, "origin")
                    set tbArray[FD_DUR_ID].real[fireDummyCount] = fireDuration
                endif
            endif
            
            set X = targetX - x2
            set Y = targetY - y2
            if X*X + Y*Y < fireRadius*fireRadius then
                call ReleaseTimer(GetExpiredTimer())
                set isFireDone = true
            endif
            
            set u = null
        endmethod
        
        private static method projectileCallback takes nothing returns nothing
            local thistype this = GetTimerData(GetExpiredTimer())
            local unit u
            
            set x = x + speed * cosA
            set y = y + speed * sinA
            
            //I had to do speed * 1.001 because the projectile would go on infinitely if the caster targeted himself.
            //A 0.1% uncertainty won't really affect anything.
            if not IsUnitInRangeXY(dummy, targetX, targetY, speed * 1.001) then
                call SetUnitX(dummy, x)
                call SetUnitY(dummy, y)
                //projectile damage
                call GroupEnumUnitsInRange(G, x, y, projectileRadius, null)
                
                loop
                    set u = FirstOfGroup(G)
                    exitwhen u == null
                    call GroupRemoveUnit(G, u)
                    
                    if projectileFilter(u, owner) and not tbArray[PRIMARY_ID].has(GetHandleId(u)) then
                        call UnitDamageTarget(caster, u, projectileDamage, true, false, ATK_TYPE, DMG_TYPE, null)
                        set tbArray[PRIMARY_ID][GetHandleId(u)] = 1
                        call projectileEffectsTarget(u, owner)
                    endif
                endloop
                
            else
                call SetUnitX(dummy, targetX)
                call SetUnitY(dummy, targetY)
                //explode
                call DestroyEffect(projectile)
                call UnitApplyTimedLife(dummy, 'BTLF', 0.5)
                call ReleaseTimer(GetExpiredTimer())
                
                call explosionEffects(caster, dummy)
                
                call GroupEnumUnitsInRange(G, targetX, targetY, explosionRadius, null)
                
                loop
                    set u = FirstOfGroup(G)
                    exitwhen u == null
                    call GroupRemoveUnit(G, u)
                    
                    if explosionFilter(u, owner) then
                    
                        static if PROJECTILE_EXPLOSION_DAMAGE then
                            call UnitDamageTarget(caster, u, explosionDamage, true, false, ATK_TYPE, DMG_TYPE, null)
                        else
                            if tbArray[PRIMARY_ID].has(GetHandleId(u)) then
                                call UnitDamageTarget(caster, u, explosionDamage - projectileDamage, true, false, ATK_TYPE, DMG_TYPE, null)
                            else
                                call UnitDamageTarget(caster, u, explosionDamage, true, false, ATK_TYPE, DMG_TYPE, null)
                            endif
                        endif
                        
                        call explosionEffectsTarget(u, owner)
                    endif
                endloop
                
                set isExplosionDone = true
                
            endif
            
        endmethod
        
        method startEffect takes nothing returns nothing
            set tbArray = TableArray[5]
            set angle = Atan2(targetY - y, targetX - x)
            set sinA = Sin(angle)
            set cosA = Cos(angle)
            set dummy = CreateUnit(owner, DUMMY_ID, x, y, angle * bj_RADTODEG)
            set fireDummyCount = 0
            
            set isFireDone = false
            set isExplosionDone = false
            
            //Move the unit to the position of the caster again because the dummy was created with collision taken into account
            call SetUnitX(dummy, x)
            call SetUnitY(dummy, y)
            set projectile = AddSpecialEffectTarget(PROJECTILE_MODEL, dummy, "origin")
            
            //Add crow form so that the dummy's height can be adjusted
            if UnitAddAbility(dummy, 'Arav') then
                call UnitRemoveAbility(dummy, 'Arav')
            endif
            
            call SetUnitFlyHeight(dummy, PROJECTILE_HEIGHT, 0)
            call SetUnitScale(dummy, PROJECTILE_SCALE, 0, 0)
            call TimerStart(NewTimerEx(this), INTERVAL, true, function thistype.projectileCallback)
            call TimerStart(NewTimerEx(this), INTERVAL, true, function thistype.fireCallback)
        endmethod
        
    endstruct
//------------------------------------------------------------------------------------------
    
    private function Actions takes nothing returns boolean //The Action part of the trigger. Sets up the nesessary configurations for the spell.
        local Fireblaze fb
        
        if GetSpellAbilityId() == ABILITY_ID then
            
            set fb = Fireblaze.create()
            set fb.owner = GetTriggerPlayer()
            set fb.caster = GetTriggerUnit()
            set fb.targetX = GetSpellTargetX()
            set fb.targetY = GetSpellTargetY()
            set fb.x = GetUnitX(fb.caster)
            set fb.y = GetUnitY(fb.caster)
            set fb.x2 = fb.x
            set fb.y2 = fb.y
            set fb.level = GetUnitAbilityLevel(fb.caster, ABILITY_ID)
            
            //A minimum speed of 100. With 0 or negative speed, the spell will go on forever!
            set fb.speed = (SPEED_BASE + (SPEED_INCREMENT * fb.level) ) * INTERVAL
            if fb.speed < (100 * INTERVAL) then
                set fb.speed = (100 * INTERVAL)
            endif
            
            set fb.fireDuration = FIRE_DURATION_BASE + (FIRE_DURATION_INCREMENT * fb.level)
            if fb.fireDuration < 0 then
                set fb.fireDuration = 0
            endif
            
            set fb.delay = FIRE_DELAY_BASE + (FIRE_DELAY_INCREMENT * fb.level)
            if fb.delay < 0 then
                set fb.delay = 0
            endif
            
            set fb.explosionDamage = EXPLOSION_DAMAGE_BASE + (EXPLOSION_DAMAGE_INCREMENT * fb.level)
            set fb.fireDamage = FIRE_DAMAGE_BASE + (FIRE_DAMAGE_INCREMENT * fb.level)
            set fb.projectileDamage = PROJECTILE_DAMAGE_BASE + (PROJECTILE_DAMAGE_INCREMENT * fb.level)
            
            set fb.explosionRadius = EXPLOSION_RADIUS_BASE + (EXPLOSION_RADIUS_INCREMENT * fb.level)
            set fb.fireRadius = FIRE_RADIUS_BASE + (FIRE_RADIUS_INCREMENT * fb.level)
            set fb.projectileRadius = PROJECTILE_RADIUS_BASE + (PROJECTILE_RADIUS_INCREMENT * fb.level)
            
            set fb_inst = fb_inst + 1
            call fb.startEffect()
        endif
        
        return false
    endfunction
    
    private function init takes nothing returns nothing //The initializing function for the library. Creates a trigger that detects the spell effect event.
        local trigger t = CreateTrigger()
        
        call TriggerRegisterAnyUnitEventBJ(t, EVENT_PLAYER_UNIT_SPELL_EFFECT)
        call TriggerAddCondition(t, function Actions)
        
        static if PRELOAD then
            call Preload( PROJECTILE_MODEL )
            call Preload( FIRE_MODEL )
        endif
    endfunction
    
endlibrary

Credits to Vexorian and Bribe
Special thanks to: BPower, TriggerHappy, Malhorne, Cokemonkey11, deathismyfriend and PurgeandFire


1.2b
- Moved the clear method to the top of the struct.
- Changed SetUnitPosition function into SetUnitX/Y.
- Moved the square root of the conditional inequality SquareRoot(X*X + Y*Y) < fireRadius to the other side.
- Made GetInstances() a public constant function.
- Made the condition function of the detection trigger to return false.

1.2
- Reduced sine and cosine calculations in the spell to the minimum.
- Fixed a small leak and some minor changes to SetUnitScale.
- Replaced one of the SquareRoot function with IsUnitInRangeXY function.
- Added another configuration: FIRE_SCALE
- Added some extra stuff to the test map.

1.1
- Replaced the usage of hashtable with Bribe's Table.
- Used UnitAlive native and added static if's to preloading projectile and fire models.
- Implemented an extra boolean configuration: PROJECTILE_EXPLOSION_DAMAGE

1.0d
- Added the missing 'not' to the isUnitDead filter.

1.0c
- Stored the StringHash function's returning value in integer variables at the beginning. Further functions that use the hashtable now only has to refer to the integer variables instead of using StringHash repeatedly.
- Increased base projectile radius and speed, base fire delay, and casting range. Reduced the mana cost for the test map.

1.0b
- Made the changes as suggested by deathismyfriend.

1.0
- First release


I'm grateful to those who helped me optimize my spell.

Do let me know if you have encountered any bugs or have any suggestions for me. I am also open to ways for further optimizing my code or adding additional minor effects. Thanks!

Keywords:
fire, blazing, blaze, fireblaze, flame, blast, explosion, incinerate, burn, bomb, flare, phoenix, red, yellow, vJass, doom, hell, heat, bird.
Contents

Just another Warcraft III map (Map)

Reviews
13:22, 10th Mar 2014 BPower: You made all required changes. Approved. 23:17, 8th Mar 2014 PurgeandFire: Review: http://www.hiveworkshop.com/forums/2497029-post22.html

Moderator

M

Moderator

13:22, 10th Mar 2014
BPower:
You made all required changes. Approved.

23:17, 8th Mar 2014
PurgeandFire: Review:
http://www.hiveworkshop.com/forums/2497029-post22.html
 
Don't use GetWidgetLife(u) > 0.405 it is inaccurate as you can heal dead units and removed units will still be counted as alive.
Use this.
JASS:
IsUnitType( u, UNIT_TYPE_DEAD) or GetUnitTypeId( u) == 0

Get rid of this. bj_RADTODEG and that way you can get rid of all of the bj_DEGTORAD and make it a little faster.
 
Last edited:
Level 5
Joined
Sep 28, 2010
Messages
75
Made the suggested changes.

I hope I did the IsUnitDead check right.
JASS:
... and (IsUnitType( u, UNIT_TYPE_DEAD) or GetUnitTypeId( u) == 0) and ...

Left the main angle variable in radian form. The spell should have lesser calculations per tick now.

Thanks!
 
Level 19
Joined
Mar 18, 2012
Messages
1,716
You made a mistake in all Filters: return not IsUnitType(u, UNIT_TYPE_STRUCTURE) and (IsUnitType( u, UNIT_TYPE_DEAD) or GetUnitTypeId( u) == 0) and GetUnitTypeId(u) != DUMMY_ID and IsUnitEnemy(u, p)

it has to be not (IsUnitType( u, UNIT_TYPE_DEAD) or GetUnitTypeId( u) == 0).

I haven't seen this line for a long time StringHash. ^^
 
You made a mistake in all Filters: return not IsUnitType(u, UNIT_TYPE_STRUCTURE) and (IsUnitType( u, UNIT_TYPE_DEAD) or GetUnitTypeId( u) == 0) and GetUnitTypeId(u) != DUMMY_ID and IsUnitEnemy(u, p)

it has to be not (IsUnitType( u, UNIT_TYPE_DEAD) or GetUnitTypeId( u) == 0).

I haven't seen this line for a long time StringHash. ^^

It actually has to be not (IsUnitType( u, UNIT_TYPE_DEAD) or GetUnitTypeId( u) != 0)

@Malhorne
I'm not sure what cases as I haven't tested them to see.
 
Level 5
Joined
Sep 28, 2010
Messages
75
Added the not in all filters.

BPower said:
it has to be not (IsUnitType( u, UNIT_TYPE_DEAD) or GetUnitTypeId( u) == 0).
deathismyfriend said:
It actually has to be not (IsUnitType( u, UNIT_TYPE_DEAD) or GetUnitTypeId( u) != 0)
I have tested with both and all I can say is,
not (IsUnitType( u, UNIT_TYPE_DEAD) or GetUnitTypeId(u) == 0)
works, and
not (IsUnitType( u, UNIT_TYPE_DEAD) or GetUnitTypeId(u) != 0)
doesn't.

I have yet to use UnitAlive native because I am more familiar with the above filter. However, I will indeed use it if it is proven to be significantly better than the above filter.

I really appreciate everyone's help. Thanks!
 
I have yet to use UnitAlive native because I am more familiar with the above filter. However, I will indeed use it if it is proven to be significantly better than the above filter.

Here's some quick critique. I haven't actually tested the spell yet so I'll come back with something positive later.
  • What's there to be familiar with? It's also faster than the alternative checks.
    JASS:
    native UnitAlive takes unit id returns boolean
    function FilterFunc takes nothing returns boolean
        return UnitAlive(GetFilterUnit())
    endfunction
  • I also dislike how this spell uses it's own hashtable, considering there's a limit of 250 per map.
  • Make Preloading optional with static if's.
 
Level 5
Joined
Sep 28, 2010
Messages
75
Alright, I've noted all the changes I should make.
I will start on it soon. Thanks again!

EDIT: Spell updated with the following changes: Used the UnitAlive native, replaced the hashtable with Table (although I don't know if that is the right way to use TableArrays) and added a static if to the preloading.
 
Last edited:
Level 19
Joined
Mar 18, 2012
Messages
1,716
The problem with TableArray is that unlike Table you can't flush without destroying them.
I never faced a case like this one before, if I come up with a better solution I'll let you know.

In general TableArray can be used the way you did it.

I think it's up to the user to do a proper configuration. In case he does,
these lines are redundant, but still its a nice move against incompetence :grin:
JASS:
            if fb.speed < (100 * INTERVAL) then
                set fb.speed = (100 * INTERVAL)
            endif

call SetUnitScale(dummy, PROJECTILE_SCALE, PROJECTILE_SCALE, PROJECTILE_SCALE) --> You can only scale a unit in x-axis. y and z will automatically adjusted no matter what you put in there.

Cos(angle) and Sin(angle) are constant per struct instance and you need them quite often. --> You could use a cos and sin member and set them on spell cast. iirc its about ~15-20% faster.

if SquareRoot(X*X + Y*Y) > (speed * 1.001) then. SqareRoot is slow and could also be IsUnitInRangeXY(dummy, X, Y, speed*1.001)

Null the effect projectile after destroying it.

Make a constant variable for 'dumy'.
set u = CreateUnit(owner, 'dumy', x2 - fireDistance * Cos(angle), y2 - fireDistance * Sin(angle), angle * bj_RADTODEG)
call SetUnitScale(u, 2.1, 2.1, 2.1) could be configurable.

The configuration part is well documanted :) and I love your demo map.

That is all I got on the first impression. It's a lot of code covered in one struct, so I guess there are a few further other issues which I didn't mention (yet???) :) .
 
ljass]call SetUnitScale(dummy, PROJECTILE_SCALE, PROJECTILE_SCALE, PROJECTILE_SCALE)[/icode] --> You can only scale a unit in x-axis. y and z will automatically adjusted no matter what you put in there.

I actually didn't know that was the behavior. Thanks.

if SquareRoot(X*X + Y*Y) > (speed * 1.001) then. SqareRoot is slow and could also be IsUnitInRangeXY(dummy, X, Y, speed*1.001)

What the fuck, I've literally never used this native before. Is it new?

Null the effect projectile after destroying it.

Not necessary since struct member indices are recycled
 
Cokemonkey11 said:
Globals can leak yes, but struct instances are recycled.

I would still null them because there's a chance it won't be used again.

For example say like 10 instances are created at once, but then for the rest of the game only the first 3 indices are recycled.

I also don't think that's the point. I think a new leak occurs when the handle is created and destroyed but never nulled, regardless of if the global gets a new value.

Cokemonkey11 said:
has an unnecessary intermediate nulling

Come to think of it I actually think that is necessary, I'll test later.

I'm probably just confusing things though.
 
Level 5
Joined
Sep 28, 2010
Messages
75
Updated the spell these changes:

Nulled the effect variable in the clear() method, to make things safe.
Added sinA and cosA variables to reduce the calculations per tick by alot.
I also changed SetUnitScale(dummy, PROJECTILE_SCALE, PROJECTILE_SCALE, PROJECTILE_SCALE) to SetUnitScale(dummy, PROJECTILE_SCALE, 0, 0), but I don't really know the difference.
Added the configuration for FIRE_SCALE.

BPower said:
Make a constant variable for 'dumy'.
set u = CreateUnit(owner, 'dumy', x2 - fireDistance * Cos(angle), y2 - fireDistance * Sin(angle), angle * bj_RADTODEG)
Lol, I can't believe i forgot that line. Changed 'dumy' to DUMMY_ID. Thanks.

The configuration part is well documanted :) and I love your demo map.
Appreciate your compliment :thumbs_up:
 
Review:
  • It is a fun spell. :)
  • In the method startEffect, I recommend using SetUnitX/Y instead of SetUnitPosition. SetUnitPosition performs pathing checks and issues a stop order on the unit (which may fire an event). SetUnitX/Y will ignore pathing, so it should get the correct position.
  • You should move the method clear above the other ones. By default, when you issue a call to a method below another method, vJASS will create a prototype function (along with a trigger and an action) that is essentially a copy of the original, so you end up with a lot of superfluous code.
  • if SquareRoot(X*X + Y*Y) < fireRadius then
    ->
    if X*X + Y*Y < fireRadius * fireRadius then

Fix #2 (unless you have a good reason) and #3 and I'll approve. Great job with the configurability and the documentation/code neatness, btw. :)
 
Level 18
Joined
Sep 14, 2012
Messages
3,413
JASS:
public function GetInstances takes nothing returns integer //Returns the amount of instances of this spell.
        return fb_inst
    endfunction
-> Make this constant func.

return true IIRC it is better to return false otherwise it will check actions and make you loose some performance. But I'm not sure so till nobody thinks the same don't take care of this one.
 
Top