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

Chicken Burst v0.2

attachment.php

JASS:
//===========================================================================
//                        Chicken Burst spell
//                          by Garfield1337
//===========================================================================
scope ChickenBurst initializer Init

    globals
        private constant integer CHICKEN_ID               =   'h001'  //Raw code of chicken
        private constant integer DUMMY_ID                 =   'h002'  //Raw code of dummy
        private constant integer EGG_ID                   =   'h000'  //Raw code of egg
        private constant integer ABILITY_ID               =   'A000'  //Raw code of ability
        private constant real EGG_SPEED                   =   24.00   //Egg's flying speed
        private constant real CHICKEN_SPEED               =   4.00    //Chickens' flying speed
        private constant real CHICKEN_HEIGHT              =   600.0   //Chickens' max height when flying
        private constant real CHICKEN_EXPLOSION_AOE       =   160.0   //Radius of area which chickens damage
        private constant real CHICKEN_RANGE_MIN           =   150.0   //Minimum range chickens fly from egg
        private constant real CHICKEN_RANGE_MAX           =   350.0   //Maximum range chickens fly from egg
        private constant string CHICKEN_EXPLOSION_SFX     =   "Abilities\\Spells\\Other\\Incinerate\\FireLordDeathExplode.mdl"   //Effect created upon chicken explosion
        private constant boolean DAMAGE_ALLIED            =   false   //Should the spell damage allied units?
        private constant boolean DAMAGE_ENEMY             =   true    //Should the spell damage enemy units?
        private constant attacktype ATTACK_TYPE           =   ATTACK_TYPE_NORMAL   //Explosion attack type
        private constant damagetype DAMAGE_TYPE           =   DAMAGE_TYPE_NORMAL   //Explosion damage type
        private constant weapontype WEAPON_TYPE           =   WEAPON_TYPE_WHOKNOWS //Explosion weapon type
        private constant boolean SHOW_ERROR               =   true    //Should an error message appear if target point of spell is invalid?
        private constant boolean AFFECT_DESTRUCTABLES     =   true    //Should the explosion affect destructables at all?
        private constant boolean DESTROY_DESTRUCTABLES    =   false   //Should the explosion destroy destructables?
        private constant boolean TREES_ONLY               =   false   //Should the explosion only affect trees as destructables?
        private constant real DESTRUCTABLE_DAMAGE         =   40.0    //Damage to destructables if DESTROY_DESTRUCTABLES is false
    endglobals
    
    private keyword INVALID_TARGETS //Don't change this line
    
    private function InvalidTargets takes nothing returns nothing
        //Which unit types shouldn't be affected by spell
        set INVALID_TARGETS[0]                            =   UNIT_TYPE_FLYING
        set INVALID_TARGETS[1]                            =   UNIT_TYPE_STRUCTURE
        set INVALID_TARGETS[2]                            =   UNIT_TYPE_MAGIC_IMMUNE
    endfunction
    
    private function Damage takes integer lvl returns real
        //Damage per explosion per level
        return 50.0 + 20.0 * lvl
    endfunction
    
    private function ChickenAmount takes integer lvl returns integer
        //Number of chickens per level
        return 5 + 5 * lvl
    endfunction
    
    private function ChickenDuration takes nothing returns real
        //Time before each chicken explodes
        return GetRandomReal(1.00,2.00)
    endfunction
    
    //End of configuration part
    //===========================================================================
    
    globals
        private hashtable Hash = InitHashtable()
        private group g = CreateGroup()
        private location l = Location(0.00,0.00)
        private unittype array INVALID_TARGETS
        private group EGGS = CreateGroup()
        private group CHICKENS = CreateGroup()
        private rect r
        private unit t
        private real minX
        private real minY
        private real maxX
        private real maxY
    endglobals

    private function Parabola takes real y0, real y1, real h, real d, real x returns real
        local real A = (2*(y0+y1)-4*h)/(d*d)
        local real B = (y1-y0-A*d*d)/d        //Credits to moyack for the parabola function
        return A*x*x + B*x + y0
    endfunction
    
    private function Check takes real x,real y returns boolean
        return (x >= minX and x <= maxX) and (y >= minY and y <= maxY)
    endfunction

    private function Cast takes nothing returns boolean
        local unit u
        local real r1
        local real r2
        local real r3
        local real r4
        local real x1
        local real y1
        local real x2
        local real y2
        local integer i
        if GetSpellAbilityId() == ABILITY_ID then
            set x2 = GetSpellTargetX()
            set y2 = GetSpellTargetY()
            if Check(x2,y2) then
                set x1 = GetUnitX(GetTriggerUnit())
                set y1 = GetUnitY(GetTriggerUnit())
                //Creating egg
                set u = CreateUnit(GetOwningPlayer(GetTriggerUnit()),EGG_ID,x1,y1,GetRandomReal(0.00,360.0))
                set i = GetHandleId(u)
                //Moving the egg on caster's position since it is created beside
                call SetUnitX(u,x1)
                call SetUnitY(u,y1)
                //Enabling the egg to fly by adding and removing crow form
                call UnitAddAbility(u,'Amrf')
                call UnitRemoveAbility(u,'Amrf')
                //Adding a slight height to egg so it doesn't fly off the ground
                call SetUnitFlyHeight(u,30.0,0.00)
                //Saving angle between caster and target
                set r1 = Atan2(y2 - y1,x2 - x1)
                call SaveReal(Hash,i,0,r1)
                //To prevent the egg from hitting cliff while in air before reaching it's target
                //i check height of terrain on egg's way and find the peak (if any)
                //then later i add the peak's height to max height of parabola in loop
                set r2 = 0
                set r3 = SquareRoot(Pow(x2 - x1,2) + Pow(y2 - y1,2))
                set r4 = 0
                loop
                    call MoveLocation(l,x1 + r2 * Cos(r1),y1 + r2 * Sin(r1))
                    if r2 == 0 then
                        //Saving caster's height for parabola
                        call SaveReal(Hash,i,1,GetLocationZ(l) + 30.0)
                    elseif r2 >= r3 then
                        //Saving target's height for parabola
                        call SaveReal(Hash,i,2,GetLocationZ(l))
                        exitwhen true
                    endif
                    if GetLocationZ(l) > r4 then
                        set r4 = GetLocationZ(l)
                    endif
                    set r2 = r2 + 32.0
                endloop
                //Saving peak's height
                call SaveReal(Hash,i,3,r4)
                //Saving total distance
                call SaveReal(Hash,i,4,r3)
                //Saving passed distance which is 0
                call SaveReal(Hash,i,5,0.00)
                //Saving spell level
                call SaveInteger(Hash,i,6,GetUnitAbilityLevel(GetTriggerUnit(),ABILITY_ID))
                //Saving a custom integer which will be used in loop
                call SaveInteger(Hash,i,7,0)
                call GroupAddUnit(EGGS,u)
            elseif SHOW_ERROR then
                call DisplayTimedTextToPlayer(GetTriggerPlayer(),0.00,0.00,5.00,"|c00FF0000Invalid Target!|r")
            endif
        endif
        return false
    endfunction

    private function Loop1 takes nothing returns nothing
        //Egg loop
        local unit u1 = GetEnumUnit()
        local unit u2
        local real r
        local real x1
        local real y1
        local real x2
        local real y2
        local integer i1 = GetHandleId(u1)
        local integer i2 = LoadInteger(Hash,i1,7)
        local integer i3 = 0
        if i2 == 0 then //At first, the egg is just fly1ing
            //Egg movement
            set x1 = GetUnitX(u1) + EGG_SPEED * Cos(LoadReal(Hash,i1,0))
            set y1 = GetUnitY(u1) + EGG_SPEED * Sin(LoadReal(Hash,i1,0))
            call SetUnitX(u1,x1)
            call SetUnitY(u1,y1)
            call SaveReal(Hash,i1,5,LoadReal(Hash,i1,5) + EGG_SPEED)
            call MoveLocation(l,x1,y1)
            //Setting egg's fly1ing height with parabola
            call SetUnitFlyHeight(u1,Parabola(LoadReal(Hash,i1,1),LoadReal(Hash,i1,2),LoadReal(Hash,i1,4) / 3 + LoadReal(Hash,i1,3),LoadReal(Hash,i1,4),LoadReal(Hash,i1,5)) - GetLocationZ(l),0.00)
            if LoadReal(Hash,i1,5) >= LoadReal(Hash,i1,4) then
                //Play1ing egg's birth animation to make it look as if it jumped off the ground
                call SetUnitAnimation(u1,"birth")
                //If the egg hits ground,the custom integer is set to 1 to prevent egg from moving any1more
                call SaveInteger(Hash,i1,7,1)
            endif
        else
            set x1 = GetUnitX(u1)
            set y1 = GetUnitY(u1)
            //The custom integer is now used as a counter to simulate wait function
            call SaveInteger(Hash,i1,7,i2 + 1)
            if i2 == 20 then //Upon reaching 20 the egg ex1plodes...
                set u2 = CreateUnit(GetOwningPlayer(u1),DUMMY_ID,x1,y1,GetRandomReal(0.00,360.0))
                call SetUnitX(u2,x1)
                call SetUnitY(u2,y1)
                //Mine's death spell animation is used as a small ex1plosion for egg
                call SetUnitAnimation(u2,"death spell")
                call UnitApplyTimedLife(u2,'BTLF',2.00)
            elseif i2 == 25 then //...and after a short delay1,chickens burst
                loop
                    set i3 = i3 + 1
                    //Creating chickens until it hits wanted amount
                    exitwhen i3 > ChickenAmount(LoadInteger(Hash,i1,6))
                    //Defining the facing angle of each chicken to make them form a circle
                    set r = 6.28318 / ChickenAmount(LoadInteger(Hash,i1,6)) *  (i3 - 1) + GetRandomReal(0.00,6.28318 / ChickenAmount(LoadInteger(Hash,i1,6)))
                    set u2 = CreateUnit(GetOwningPlayer(u1),CHICKEN_ID,x1,y1,r * bj_RADTODEG)
                    call SetUnitX(u2,x1)
                    call SetUnitY(u2,y1)
                    set i2 = GetHandleId(u2)
                    //Enabling the chickens to fly1
                    call UnitAddAbility(u2,'Amrf')
                    call UnitRemoveAbility(u2,'Amrf')
                    //Defining the coordinates for each chicken to fall on
                    set x2 = x1 + GetRandomReal(CHICKEN_RANGE_MIN,CHICKEN_RANGE_MAX) * Cos(r)
                    set y2 = y1 + GetRandomReal(CHICKEN_RANGE_MIN,CHICKEN_RANGE_MAX) * Sin(r)
                    //Saving the egg's height for parabola
                    call MoveLocation(l,x1,y1)
                    call SaveReal(Hash,i2,0,GetLocationZ(l))
                    //Saving target coordinates' height
                    call MoveLocation(l,x2,y2)
                    call SaveReal(Hash,i2,1,GetLocationZ(l))
                    //Saving total distance
                    call SaveReal(Hash,i2,2,SquareRoot(Pow(x2 - x1,2) + Pow(y2 - y1,2)))
                    //Saving passed distance
                    call SaveReal(Hash,i2,3,0.00)
                    //Saving spell level, inherited from the egg
                    call SaveInteger(Hash,i2,4,LoadInteger(Hash,i1,6))
                    call GroupAddUnit(CHICKENS,u2)
                endloop
                //After chickens are created, the egg is destroy1ed
                call KillUnit(u1)
                call GroupRemoveUnit(EGGS,u1)
                call FlushChildHashtable(Hash,i1)
            endif
        endif
    endfunction

    private function Loop2 takes nothing returns nothing
        //Chicken loop
        local unit u = GetEnumUnit()
        local real r1
        local real r2
        local real x = GetUnitX(u) + CHICKEN_SPEED * Cos(GetUnitFacing(u) * bj_DEGTORAD)
        local real y = GetUnitY(u) + CHICKEN_SPEED * Sin(GetUnitFacing(u) * bj_DEGTORAD)
        local integer i = GetHandleId(u)
        //Chicken movement
        call SetUnitX(u,x)
        call SetUnitY(u,y)
        call MoveLocation(l,x,y)
        call SaveReal(Hash,i,3,LoadReal(Hash,i,3) + CHICKEN_SPEED)
        //Setting chickens' flying height with parabola
        call SetUnitFlyHeight(u,Parabola(LoadReal(Hash,i,0),LoadReal(Hash,i,1),CHICKEN_HEIGHT + RMaxBJ(LoadReal(Hash,i,0),LoadReal(Hash,i,1)),LoadReal(Hash,i,2),LoadReal(Hash,i,3)) - GetLocationZ(l),0.00)
        if LoadReal(Hash,i,3) >= LoadReal(Hash,i,2) then
            //Upon hitting ground,each chicken is ordered to move to a random point
            //The distance is defined by chickens' movement speed and time before they explode
            set r1 = ChickenDuration()
            set r2 = GetUnitMoveSpeed(u) * r1 * 3
            set x = GetUnitX(u) + r2 * Cos(GetRandomReal(0.00,6.28318))
            set y = GetUnitY(u) + r2 * Sin(GetRandomReal(0.00,6.28318))
            call IssuePointOrder(u,"smart",x,y)
            //Applying timed life to chickens to make them explode after the set time
            call UnitApplyTimedLife(u,'BTLF',r1)
            call GroupRemoveUnit(CHICKENS,u)
        endif
    endfunction

    private function LoopInit takes nothing returns nothing
        call ForGroup(EGGS,function Loop1)
        call ForGroup(CHICKENS,function Loop2)
    endfunction

    private function ExplosionFilter takes nothing returns boolean
        //Explosion's filter
        local unit u1 = GetTriggerUnit()
        local unit u2 = GetFilterUnit()
        local integer i = 0
        local boolean b = true
        //Checking whether filter unit is enemy or ally
        //whether the corresponding  booleans are true or false
        //and whether the filter unit is alive
        if ((DAMAGE_ENEMY and IsUnitEnemy(u2,GetOwningPlayer(u1))) or (DAMAGE_ALLIED and IsUnitAlly(u2,GetOwningPlayer(u1)))) and IsUnitType(u2, UNIT_TYPE_DEAD) == false then
            loop
                //Looping through all invalid target types
                //If the filter unit belongs to any,it's invalid
                exitwhen INVALID_TARGETS[i] == null
                if IsUnitType(u2,INVALID_TARGETS[i]) then
                    set b = false
                endif
                set i = i + 1
            endloop
            if b then
                //If the filter unit passes all checks, it's damaged
                call UnitDamageTarget(u1,u2,Damage(LoadInteger(Hash,GetHandleId(u1),4)),true,false,ATTACK_TYPE,DAMAGE_TYPE,WEAPON_TYPE)
            endif
        endif
        return false
    endfunction
    
    static if AFFECT_DESTRUCTABLES then
        private function DestructableEnum takes nothing returns nothing
            local destructable d = GetFilterDestructable()
            local real x
            local real y
            //Checking destructable life and filtering out dead ones
            if GetDestructableLife(d) > 0 then
                set x = GetDestructableX(d)
                set y = GetDestructableX(d)
                //If only trees should be damaged or destroyed, a tree-check is applied
                if TREES_ONLY then
                    call IssueTargetOrder(t,"harvest",d)
                    if GetUnitCurrentOrder(t) != OrderId("harvest") then
                        //In case destructable is not a tree, the function ends
                        call IssueImmediateOrder(t,"stop")
                        set d = null
                        return
                    endif
                    call IssueImmediateOrder(t,"stop")
                endif
                if DESTROY_DESTRUCTABLES then
                    //Destroying destructable if the configurable boolean is true...
                    call KillDestructable(d)
                else
                    //...if not, it's damaged instead
                    if DESTRUCTABLE_DAMAGE < GetDestructableLife(d) then
                        call SetDestructableAnimation(d,"stand hit")
                    endif
                    call SetDestructableLife(d,GetDestructableLife(d) - DESTRUCTABLE_DAMAGE)
                endif
            endif
            set d = null
        endfunction
    endif

    private function Death takes nothing returns boolean
        local unit u = GetTriggerUnit()
        local real x
        local real y
        if GetUnitTypeId(u) == CHICKEN_ID then
            //When a chicken dies, it creates the SFX and enumerates
            //the surrounding units and destructables for damage
            set x = GetUnitX(u)
            set y = GetUnitY(u)
            call DestroyEffect(AddSpecialEffect(CHICKEN_EXPLOSION_SFX,GetUnitX(u),GetUnitY(u)))
            call GroupEnumUnitsInRange(g,x,y,CHICKEN_EXPLOSION_AOE,Filter(function ExplosionFilter))
            //Checking if explosion should affect destructables
            static if AFFECT_DESTRUCTABLES then
                call MoveRectTo(r,x,y)
                call EnumDestructablesInRect(r,null,function DestructableEnum)
            endif
            call FlushChildHashtable(Hash,GetHandleId(u))
        elseif GetUnitTypeId(u) == DUMMY_ID then
            //When a dummy dies,it's instantly removed to hide the dying animation
            call RemoveUnit(u)
        endif
        return false
    endfunction

    private function Init takes nothing returns nothing
        local trigger t1 = CreateTrigger()
        local trigger t2 = CreateTrigger()
        local integer i = 0
        call InvalidTargets()
        set t = CreateUnit(Player(15),DUMMY_ID,0.00,0.00,0.00)
        call ShowUnit(t,false)
        set r = Rect(-CHICKEN_EXPLOSION_AOE,-CHICKEN_EXPLOSION_AOE,CHICKEN_EXPLOSION_AOE,CHICKEN_EXPLOSION_AOE)
        set minX = GetRectMinX(bj_mapInitialPlayableArea)
        set minY = GetRectMinY(bj_mapInitialPlayableArea)
        set maxX = GetRectMaxX(bj_mapInitialPlayableArea)
        set maxY = GetRectMaxY(bj_mapInitialPlayableArea)
        loop
            call TriggerRegisterPlayerUnitEvent(t1,Player(i),EVENT_PLAYER_UNIT_SPELL_EFFECT,null)
            call TriggerRegisterPlayerUnitEvent(t2,Player(i),EVENT_PLAYER_UNIT_DEATH,null)
            set i = i + 1
            exitwhen i == 16
        endloop
        call TriggerAddCondition(t1,Condition(function Cast))
        call TriggerAddCondition(t2,Condition(function Death))
        call TimerStart(CreateTimer(),0.03,true,function LoopInit)
        set t1 = null
        set t2 = null
    endfunction

endscope

Copy the Chicken Burst trigger and object editor data to your map and configure variables within the configuration section of trigger to your needs.
You also need JNGP to use the spell.


Keywords:
Chicken, Burst, Egg, Lol, Lolz, Lulz, Rofl, Lmao, Funny, Explosion, Chaos, Random.
Contents

Chicken Burst (Map)

Reviews
Bribe: Winner of the Spells / Systems contest #19. My review from the contest:

Moderator

M

Moderator

Bribe:

Winner of the Spells / Systems contest #19.

My review from the contest:

VERY smooth casting, the effects really fit. I think anyone would find this quite amusing :) . I can see this becoming a very popular spell. The fact that it doesn't lag even with multiple casts is also a great plus. The sound effects are great.

Some static-ifs for constant booleans would be great. It would be more efficient with a loop instead of a group-enumeration.
 

Dr Super Good

Spell Reviewer
Level 63
Joined
Jan 18, 2005
Messages
27,180
As far as I am aware the JASS compiler in WC3 is extreemly retarded and will not automatically perform optimizations. As such GetTriggerUnit() will be called each time you specify in your script which is a lot slower than caching the value into a local and using that. This is even worse for hashtable functions as hashtable lookups are probably slower.

Equally well a global variable interaction is slower than a local variable interaction.

Overall your script needs 2 major improvements.
1. Caching function results that are needed multiple times (especially those from hashtables which are reasonably expensive calls).
2. Utalizing locals where possible instead of globals (currently you are seting globals to various data elements used only within a specific function).

You also have some redundant negation.
JASS:
if not(GetUnitCurrentOrder(t) == OrderId("harvest")) then

Surly this is the same as
JASS:
if GetUnitCurrentOrder(t) != OrderId("harvest") then
 
Level 16
Joined
May 1, 2008
Messages
1,605
Moin moin =)

So now i was able to look at the code, but after Dr Super Good say it already what I found, only thing I want point out, are your chosen keywords -_-
I think, hive should get a rule for keywords at all!

Else I like this spell and the idea to blow up some chickens =)
5/5

Greetings and Peace
Dr. Boom
 
Level 20
Joined
Jul 6, 2009
Messages
1,885
@DSG
Thanks for tips. I thought of using globals instead of locals if some variables are used in more than one function...oh well,i'll set 'em back to locals.
I might redo spell using structs instead of hashtables since they're faster afaik. I learned structs after making this spell else i would do it in start.
only thing I want point out, are your chosen keywords -_-
I think, hive should get a rule for keywords at all!
Uh...i'm not sure what you mean..
 

Dr Super Good

Spell Reviewer
Level 63
Joined
Jan 18, 2005
Messages
27,180
Locals allocate space onto the stack when the function is called which only exists while the function is executing. Once the function returns (eithor specified via a return statement or automatically at the endfunction statement) it will remove all traces of that function call from the stack. The result is no static memory usage and slightly faster execution.
 
Level 22
Joined
Nov 14, 2008
Messages
3,256
I would make a filter function among the other functions instead of the InvalidTargets function as now you loop thru all the unit types. Also then you can make it more customizeable with having the choice of choosing if you want it to hit allies/enemies.

DAMAGE_ENEMY == true useless. No need for == true.

Locals should be used. As DSG said above.

if i2 == 20 then //Upon reaching 20 the egg explodes...
SHouldn't that value be customizeable too?

However overall I agree about the winning part yet I dislike creating spells with hash in vJASS. Great spell.
 
Level 20
Joined
Jul 6, 2009
Messages
1,885
I would make a filter function among the other functions instead of the InvalidTargets function as now you loop thru all the unit types. Also then you can make it more customizeable with having the choice of choosing if you want it to hit allies/enemies.
I loop only through given invalid unit types o_O
DAMAGE_ENEMY == true useless. No need for == true.
I don't think it's useless,who knows what some people want their spell to do,maybe just damage allies lol
Locals should be used. As DSG said above.
Yeah,will do.
if i2 == 20 then //Upon reaching 20 the egg explodes...
SHouldn't that value be customizeable too?
Hm...i guess i'll make it configurable.
However overall I agree about the winning part yet I dislike creating spells with hash in vJASS. Great spell.
I learned structs after i made the spell =[
 
Top