• 🏆 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!

Aquatic Blast v.2.0

  • Like
Reactions: X-OMG-X
This is a simple Spell requested by X-OMG-X

For documentation look at the code

Aquatic Blast

The Hydromancer creates a hole of water at the target area wich pulls nearby enemy units into it. When a enemy unit reaches the center, the hole implodes and damages nearby enemies. Because of the unstable power the hole will dissappear after some time of no enemy contact.
this map contains 2 versions of this spell:
a lightning version which has more eye-candy, but i had to change the code for this version in many ways. it is not that fast and may cause lagg at very very many instances.
the NoLightning version is like the old one only with the newest bug fixes.
it will (maybe) never cause lagg but is a little boring^^
I think you can always use the Lightning version cause there will never be that many instances that it laggs, but who knows^^

UPDATE v.2.0: THX to TriggerHappy187, X-OMG-X and Hanky for reviewing this spell and giving tips for improving the script etc.
i fixed all bugs and added a more eye candy version and a "how to import" trigger


JASS:
scope AquaticBlastNoLightning initializer Init
// Aquatic Blast by The_Witcher
//
// well not much to say look through/edit the setup part so they fit your wishes
//
// This NoLightning version looks not as good as the lightning but will run faster/ won't cause lagg
//  Because I think there will never be that many instances that it laggs i suggest
//  useing the Lightning version, but who knows^^
//
// The SETUP part
globals 
    // The rawcode of your ability
    private constant integer AbiID = 'A000'                  
    // The effect created at the target loc
    private constant string GroundSFX = "Abilities\\Spells\\Human\\Brilliance\\Brilliance.mdl"
    // The seconde effect created at the target loc
    private constant string MainSFX = "Abilities\\Spells\\Other\\Drain\\ManaDrainTarget.mdl"
    // The effect created when the whole thing explodes
    private constant string ExplodeSFX = "Objects\\Spawnmodels\\Naga\\NagaDeath\\NagaDeath.mdl"
    // The effect shown on damaged units
    private constant string DamageSFX = "Abilities\\Weapons\\FrostWyrmMissile\\FrostWyrmMissile.mdl"
    // The attachment point where the DamageSFX appears
    private constant string DamagePoint = "chest"
    // The interval (0.01 is smoothest but can lagg in huge maps)
    private constant real interval = 0.01
    // The boolean whether the spell affects only enemies or all units
    private constant boolean EnemyOnly = true
endglobals

private function GetDamage takes integer level returns real
    return 120.00 + 50 * (I2R(level) - 1)       //120 on level 1, 170 on level 2, 220 on level 3,...
endfunction

private function GetMaxTime takes integer level returns real
    return 3 + 2 * I2R(level)         //5 on level 1, 7 on level 2, 9 on level 3,...
endfunction

private function GetRange takes integer level returns real
    return 200 + 100 * I2R(level)            //300 on level 1, 400 level 2, 500 level 3,...
endfunction

private function GetPullSpeed takes integer level returns real
    return 2 + 0.5 * I2R(level)            //2 on level 1, 2.5 level 2, 3 level 3,...
endfunction

// END OF SETUP PART

private struct data
real x
real y
real t = 0
real mt
real dmg
real r
real d
player pl
integer level
effect sfx1
effect sfx2
integer temp = 0
endstruct

globals
    private timer tim = CreateTimer()
    private data array DATAS
    private integer total = 0
    private boolexpr BoolExpr
    private group g = CreateGroup()
    private player Temp
endglobals

private function EnemyFilter takes nothing returns boolean
    return IsUnitEnemy(GetFilterUnit(), Temp)
endfunction

private function True takes nothing returns boolean
    return true
endfunction

private function AngleBetweenCoords takes real x, real y, real xx, real yy returns real
    return bj_RADTODEG * Atan2(yy - y, xx - x)
endfunction

private function DistanceBetweenCoords takes real x , real y , real xx , real yy returns real
    return SquareRoot((x-xx)*(x-xx)+(y-yy)*(y-yy))+0.5
endfunction
                                                                        
private function execute takes nothing returns nothing
    local integer i = 0
    local data dat
    local unit b
    local real x
    local real y
    local real r
    loop
        exitwhen i >= total
        set dat = DATAS[i]
        set Temp = dat.pl
        set dat.t = dat.t + interval
        call GroupEnumUnitsInRange(g, dat.x, dat.y, dat.r, BoolExpr)
        loop                                                                                                         
            set b = FirstOfGroup(g)
            exitwhen b == null
            if not(IsUnitType(b, UNIT_TYPE_DEAD) or GetUnitTypeId(b) == 0) then
            set r = AngleBetweenCoords(GetUnitX(b), GetUnitY(b), dat.x, dat.y) * bj_DEGTORAD
            set x = GetUnitX(b) + dat.d * Cos(r)
            set y = GetUnitY(b) + dat.d * Sin(r)   
            call SetUnitX(b,x)
            call SetUnitY(b,y)
            if DistanceBetweenCoords(x,y,dat.x,dat.y) < dat.d+1 then
                call GroupClear(g)
                call GroupEnumUnitsInRange(g, dat.x, dat.y, dat.r, BoolExpr)
                loop
                    set b = FirstOfGroup(g)
                    exitwhen b == null
                    call SetWidgetLife(b, GetWidgetLife(b) - dat.dmg)
                    call DestroyEffect(AddSpecialEffectTarget(DamageSFX,b,DamagePoint))
                    call GroupRemoveUnit(g,b)
                endloop
                set dat.t = dat.mt+1
            endif
            endif
            call GroupRemoveUnit(g,b)
        endloop
        
        if dat.t > dat.mt then
            call DestroyEffect(AddSpecialEffect(ExplodeSFX,dat.x,dat.y))
            call DestroyEffect(dat.sfx1)
            call DestroyEffect(dat.sfx2)
            set DATAS[i] = DATAS[total-1]
            set total = total - 1
            call dat.destroy()
        endif
        set i = i + 1
    endloop
    if total == 0 then
        call PauseTimer(tim)
    endif
    set b = null
endfunction

private function cast takes nothing returns nothing
    local data dat = data.create()
    local unit u = GetTriggerUnit()
    local unit b
    set dat.x = GetSpellTargetX()
    set dat.y = GetSpellTargetY()
    set dat.sfx1 = AddSpecialEffect(MainSFX,dat.x,dat.y) 
    set dat.sfx2 = AddSpecialEffect(GroundSFX,dat.x,dat.y)  
    set dat.level = GetUnitAbilityLevel(u,AbiID)
    set dat.mt = GetMaxTime(dat.level)
    set dat.r = GetRange(dat.level)
    set dat.dmg = GetDamage(dat.level)
    set dat.d = GetPullSpeed(dat.level)
    set dat.pl = GetOwningPlayer(u)
    set DATAS[total] = dat
    set total = total + 1
    if total == 1 then
        call TimerStart(tim, interval, true, function execute)
    endif
    call GroupEnumUnitsInRange(g, dat.x, dat.y, dat.r, BoolExpr)
    loop
        set b = FirstOfGroup(g)
        exitwhen b == null        
        call IssueTargetOrder(b,"attack",u)
        call GroupRemoveUnit(g,b)
    endloop
    set u = null
    set b = null
endfunction

private function check takes nothing returns boolean
    return GetSpellAbilityId() == AbiID
endfunction

private function Init takes nothing returns nothing
    local trigger t = CreateTrigger()
    call TriggerAddAction(t, function cast)
    call TriggerRegisterAnyUnitEventBJ( t, EVENT_PLAYER_UNIT_SPELL_CAST )
    call TriggerAddCondition(t,Condition(function check))
    if EnemyOnly then
        set BoolExpr = Condition(function EnemyFilter)
    else 
        set BoolExpr = Filter(function True)
    endif
endfunction

endscope

JASS:
scope AquaticBlastLightning initializer Init
// Aquatic Blast by The_Witcher
//
// well not much to say look through/edit the setup part so they fit your wishes
//
// This lightning version has more eye candy but a coding which can cause lagg
//  I think you can always use the Lightning version cause there will never be that many instances that it laggs but who knows^^
//
// The SETUP part
globals 
    // The rawcode of your ability
    private constant integer AbiID = 'A001'                  
    // The effect created at the target loc
    private constant string GroundSFX = "Abilities\\Spells\\Human\\Brilliance\\Brilliance.mdl"
    // The seconde effect created at the target loc
    private constant string MainSFX = "Abilities\\Spells\\Other\\Drain\\ManaDrainTarget.mdl"
    // The effect created when the whole thing explodes
    private constant string ExplodeSFX = "Objects\\Spawnmodels\\Naga\\NagaDeath\\NagaDeath.mdl"
    // The effect shown on damaged units
    private constant string DamageSFX = "Abilities\\Weapons\\FrostWyrmMissile\\FrostWyrmMissile.mdl"
    // The attachment point where the DamageSFX appears
    private constant string DamagePoint = "chest"
    // The lightning effect appearing on each pulled unit
    private constant string Lightning = "DRAM"
    // The interval (0.01 is smoothest but can lagg in huge maps)
    private constant real interval = 0.01
    // The boolean whether the spell affects only enemies or all units
    private constant boolean EnemyOnly = true
endglobals

private function GetDamage takes integer level returns real
    return 120.00 + 50 * (I2R(level) - 1)       //120 on level 1, 170 on level 2, 220 on level 3,...
endfunction

private function GetMaxTime takes integer level returns real
    return 3 + 2 * I2R(level)         //5 on level 1, 7 on level 2, 9 on level 3,...
endfunction

private function GetRange takes integer level returns real
    return 200 + 100 * I2R(level)            //300 on level 1, 400 level 2, 500 level 3,...
endfunction

private function GetPullSpeed takes integer level returns real
    return 2 + 0.5 * I2R(level)            //2 on level 1, 2.5 level 2, 3 level 3,...
endfunction

// END OF SETUP PART

private struct data
real x
real y
real t = 0
real mt
real dmg
real r
real d
player pl
integer level
effect sfx1
effect sfx2
integer temp = 0
lightning array l[30]
unit array u[30]
integer a = 0
endstruct

globals
    private timer tim = CreateTimer()
    private data array DATAS
    private integer total = 0
    private boolexpr BoolExpr
    private group g = CreateGroup()
    private player Temp
endglobals

private function EnemyFilter takes nothing returns boolean
    return IsUnitEnemy(GetFilterUnit(), Temp)
endfunction

private function True takes nothing returns boolean
    return true
endfunction

private function AngleBetweenCoords takes real x, real y, real xx, real yy returns real
    return bj_RADTODEG * Atan2(yy - y, xx - x)
endfunction

private function DistanceBetweenCoords takes real x , real y , real xx , real yy returns real
    return SquareRoot((x-xx)*(x-xx)+(y-yy)*(y-yy))+0.5
endfunction

private function FilterDeadUnits takes nothing returns nothing
    if IsUnitType(GetEnumUnit(), UNIT_TYPE_DEAD) or GetUnitTypeId(GetEnumUnit()) == 0 then
        call GroupRemoveUnit(g,GetEnumUnit())
    endif
endfunction
                                                                        
private function execute takes nothing returns nothing
    local integer i = 0
    local integer ii = 0
    local data dat
    local unit b
    local real x
    local real y
    local real r
    loop
        exitwhen i >= total
        set dat = DATAS[i]
        set Temp = dat.pl
        set dat.t = dat.t + interval
        call GroupEnumUnitsInRange(g, dat.x, dat.y, dat.r, BoolExpr)
        call ForGroup(g, function FilterDeadUnits)
        loop
            exitwhen ii >= dat.a
            if IsUnitInGroup(dat.u[ii],g) then
                call MoveLightning(dat.l[ii], true,dat.x,dat.y,GetUnitX(dat.u[ii]),GetUnitY(dat.u[ii]))
                call GroupRemoveUnit(g,dat.u[ii])
            else
                call GroupRemoveUnit(g,dat.u[ii])
                set dat.a = dat.a - 1
                set dat.u[ii] = dat.u[dat.a]
                call DestroyLightning(dat.l[ii])
                set dat.l[ii] = dat.l[dat.a]
                set dat.l[ii] = null
            endif
            set ii = ii + 1
        endloop
        loop                                                                                                         
            set b = FirstOfGroup(g)
            exitwhen b == null
            set dat.u[dat.a] = b
            set dat.l[dat.a] = AddLightningEx( Lightning,true, dat.x,dat.y,50,GetUnitX(b),GetUnitY(b),50)
            set dat.a = dat.a + 1
            call GroupRemoveUnit(g,b)
        endloop
        set ii = 0
        loop   
            exitwhen ii >= dat.a
            set b = dat.u[ii]
            set r = AngleBetweenCoords(GetUnitX(b), GetUnitY(b), dat.x, dat.y) * bj_DEGTORAD
            set x = GetUnitX(b) + dat.d * Cos(r)
            set y = GetUnitY(b) + dat.d * Sin(r)   
            call SetUnitX(b,x)
            call SetUnitY(b,y)
            if DistanceBetweenCoords(x,y,dat.x,dat.y) < dat.d+1 then
                call GroupClear(g)
                call GroupEnumUnitsInRange(g, dat.x, dat.y, dat.r, BoolExpr)
                loop
                    set b = FirstOfGroup(g)
                    exitwhen b == null
                    call SetWidgetLife(b, GetWidgetLife(b) - dat.dmg)
                    call DestroyEffect(AddSpecialEffectTarget(DamageSFX,b,DamagePoint))
                    call GroupRemoveUnit(g,b)
                endloop
                set dat.t = dat.mt+1
            endif
            set ii = ii + 1
        endloop
        
        if dat.t > dat.mt then
            call DestroyEffect(AddSpecialEffect(ExplodeSFX,dat.x,dat.y))
            call DestroyEffect(dat.sfx1)
            call DestroyEffect(dat.sfx2)
            set ii = 0
            loop
                exitwhen ii >= dat.a
                call DestroyLightning(dat.l[ii])
                set ii = ii + 1
            endloop
            set DATAS[i] = DATAS[total-1]
            set total = total - 1
            call dat.destroy()
        endif
        set i = i + 1
    endloop
    if total == 0 then
        call PauseTimer(tim)
    endif
    set b = null
endfunction

private function cast takes nothing returns nothing
    local data dat = data.create()
    local unit u = GetTriggerUnit()
    local unit b
    set dat.x = GetSpellTargetX()
    set dat.y = GetSpellTargetY()
    set dat.sfx1 = AddSpecialEffect(MainSFX,dat.x,dat.y) 
    set dat.sfx2 = AddSpecialEffect(GroundSFX,dat.x,dat.y)  
    set dat.level = GetUnitAbilityLevel(u,AbiID)
    set dat.mt = GetMaxTime(dat.level)
    set dat.r = GetRange(dat.level)
    set dat.dmg = GetDamage(dat.level)
    set dat.d = GetPullSpeed(dat.level)
    set dat.pl = GetOwningPlayer(u)
    set DATAS[total] = dat
    set total = total + 1
    if total == 1 then
        call TimerStart(tim, interval, true, function execute)
    endif
    call GroupEnumUnitsInRange(g, dat.x, dat.y, dat.r, BoolExpr)
    loop
        set b = FirstOfGroup(g)
        exitwhen b == null        
        call IssueTargetOrder(b,"attack",u)
        call GroupRemoveUnit(g,b)
    endloop
    set u = null
    set b = null
endfunction

private function check takes nothing returns boolean
    return GetSpellAbilityId() == AbiID
endfunction

private function Init takes nothing returns nothing
    local trigger t = CreateTrigger()
    call TriggerAddAction(t, function cast)
    call TriggerRegisterAnyUnitEventBJ( t, EVENT_PLAYER_UNIT_SPELL_CAST )
    call TriggerAddCondition(t,Condition(function check))
    if EnemyOnly then
        set BoolExpr = Condition(function EnemyFilter)
    else 
        set BoolExpr = Filter(function True)
    endif
endfunction

endscope

Keywords:
spell, water, blast, aqua, hole, implosion, pull, vjass, lightning
Contents

Aquatic Blast (Map)

Reviews
11:26, 4th Oct 2009 Hanky: Good job The_Witcher. The effects were ok and the script is pretty much ok too. But there are some points that have to be improved: - the boolexpr thingy TriggerHappy said in his review - the detection if a unit is...

Moderator

M

Moderator

11:26, 4th Oct 2009
Hanky:
Good job The_Witcher. The effects were ok and the script is pretty much ok too. But there are some points that have to be improved:
- the boolexpr thingy TriggerHappy said in his review
- the detection if a unit is alive should be fixed like TriggerHappy already said
- those distance/angle functions aren't needed, just write the formulas into you script
- fix the tooltip, since I guess you meant "which" and not "wich"
- add for the newbies a documentation part how to import your spell

That's all I found for now. Fix those points above to increase the rating of your resource. Use the pm function if you want to have a new review.
 
Hey thanks the witcher!
I will download it and give a review.

edit: Tried the spell and here is my review:
Aquatic Blast
The spell did what i requested but maybe you could make it pull a little slower. Also, that brilliance aura doesnt disappear when the spell is finished. Doesnt know much about special effects, but do you think you can make it more eyecandy? Maybe making the effects bigger if possible. Other than that, the spell seems to be well made and quite useful for my map. Thanks.
 
  • Like
Reactions: Rmx
Visually the spell is nothing special, but it's nice.

Though whenever I casted it the units never attacked me (no aggro).

  • Use this method to accurately check if a units dead.
  • Make the attachment point configurable for DamageSFX.
  • Instead of using arrays in your structs you could simply use multiple variables to increase your max struct instances, though I doubt that many instances of your spell would be running at once.
  • Your boolexpr could leak if EnemyOnly was set to false, instead you should be using a filter that returns true.
    JASS:
    private function True takes nothing returns boolean
        return true
    endfunction
    
            if EnemyOnly then
                set BoolExpr = Condition(function EnemyFilter)
            else 
                set BoolExpr = Filter(function True)
            endif
 
  • Like
Reactions: Rmx
Level 17
Joined
Mar 17, 2009
Messages
1,349
TriggerHappy187 said:
Instead of using arrays in your structs you could simply use multiple variables to increase your max struct instances, though I doubt that many instances of your spell would be running at once.
There would be NEVER 8191/3 instances of the spelll running, so as you shouldn't doubt that :p

TriggerHappy187 said:
Use this method to accurately check if a units dead.
No need to use that method "exactly", simply replace and GetWidgetLife(GetFilterUnit()) > 0.405 with and not IsUnitType(GetFilterUnit(), UNIT_TYPE_DEAD).


Also, your way of looping through groups using FirstOfGroup is inefficient...
you should use ForGroup instead.

Btw, as I notice you don't check in case it's not EnemyOnly if allied units are alive, do you?
 
  • Like
Reactions: Rmx
Level 5
Joined
Oct 9, 2008
Messages
112
Hi, i tested this map, and i found an bug. If a unit runs fast enough, like the knight, he doesnt move in this area, to get damage. (Only if hes alone)
Solution: Make the units in the area paused.

i hope it will help, have fun.
 
Level 14
Joined
Jan 15, 2007
Messages
349
Yes ForGroup is maybe faster. But a loop with FirstOfGroup looks better and it won't have such a big speed difference. Also you'll need at least one temporary global variable. Well since vJASS that's easier but still in my opinion ForGroup looks abit messy.
 
ok plz stop that "which of them is more efficient"

i used both of them and the speed difference can't be that huge that it is important in a spell!!

Atami" said:
Hi, i tested this map, and i found an bug. If a unit runs fast enough, like the knight, he doesnt move in this area, to get damage. (Only if hes alone)
Solution: Make the units in the area paused.

i hope it will help, have fun.
this isn't a bug!! units can try to walk against the pull.
if someone would try to pull you, you would try to escape, too!!
 
Level 16
Joined
Jul 4, 2008
Messages
1,106

Review
Hello! I just reviewed your map and well, I liked the eye-candy of it BUT! I found leaks out of it. The leaks weren't the only thing I found, but the lightning also bugged. It sometimes just didnt get removed. So, to give you a number, I'd give you... 3.2/5

Other...
Reputation: FALSE
Vote for Approve: FALSE


Vote for Approval Requirement! Info
Under 3.5 - No Approve
Over 3.5 - Approve
 

Review
Hello! I just reviewed your map and well, I liked the eye-candy of it BUT! I found leaks out of it. The leaks weren't the only thing I found, but the lightning also bugged. It sometimes just didnt get removed. So, to give you a number, I'd give you... 3.2/5

Other...
Reputation: FALSE
Vote for Approve: FALSE


Vote for Approval Requirement! Info
Under 3.5 - No Approve
Over 3.5 - Approve

well one of the worst reviews i ever saw...
where have you found leaks???
in addition the lightnings are ALWAYS removed!!
i tested the map 3 times and always all lightnings were removed!
 
Level 17
Joined
Mar 17, 2009
Messages
1,349
Hanky said:
Yes ForGroup is maybe faster. But a loop with FirstOfGroup looks better and it won't have such a big speed difference. Also you'll need at least one temporary global variable. Well since vJASS that's easier but still in my opinion ForGroup looks abit messy.
Yes I understand what you say... :) when I said inefficient I didn't mean it's bad to use, i just meant inefficient w.r.t. ForGroup (should have made that clearer).

@ OffGraphic:
Huh...!? I must also agree that this is the worse review I've ever seen too...
 
Level 12
Joined
Jul 27, 2008
Messages
1,181
Yup, me too. Plus, I'm 95% sure he doesn't know (v)JASS.

Spell is nice, but 2 complaints (both small, and about how code looks)
A) Indent the inside of your struct.
B) Break the code in a few places, so that it doesn't look like a big block. (You ain't writing ASM :gg:)

EDIT::xxd::xxd::xxd::xxd: 1000 posts :thumbs_up:.

EDIT 2: One more thing, private function True takes nothing returns boolean should be constant.
 
Top