HomeUser Control Panel (unavailable in archive)ForumsTutorialsArt GalleryResourcesMaps

Stupid Penguin

07-08-2008, 02:24 PM#1
Flame_Phoenix
Well, now that my Apocalypse spell got approved I am very happy.
When making that spell, I also gained some experience, which I am now using to make another spell that many people will like: Stupid Penguin

So you guys are now wondering, what is this spell about ?
Well, is simple, It is a channeling spell.
When the caster starts the channel, he creates a Stupid Penguin that can only move (has no attack). Each second (or cicle you choose) the Penguin grows and gets bigger and bigger until ... BOOMMMM it excplodes and damages enemies !!
I like this spell, when I am angry I just pretend the Penguin is some stupid guy I hate, and I just blow him up ! It can be kinda relaxing =P
Anyway, I made this spell with dynamic triggers some time ago ... (now you are all cursing me, like, OMG didn't you learn a thing ? Well, but I did, this spell was done before submitting my Apocalypse spell here, so I am now updating it)

The mechanic of the spell is again, very simple and intuitive.
I bet you will all understand how it works after looking 10 seconds to the code. It is very similar to the Apocalypse spell.

Collapse JASS:
    globals 
        private group StupidPenguinCasters = CreateGroup()
        private HandleTable activeTable //your private Table's global variable
    endglobals
    
    private struct MyStruct
        unit caster
        integer level
        unit penguin
        real currentScale 
        timer growPeriod
        real grow
        real damage
        
        static method create takes unit caster, real spellX, real spellY returns MyStruct
            local MyStruct data = MyStruct.allocate()
            
            //set variables about the caster
            set data.caster = caster
            set data.level = GetUnitAbilityLevel(data.caster, AID)
            
            //set variables about the Stupid Penguin
            set data.penguin = CreateUnit(GetOwningPlayer(data.caster), UNITID, spellX, spellY, 0.)
            set data.currentScale = 1
            set data.growPeriod = NewTimer()
            set data.grow = Growth(data.level)
            set data.damage = Damage(data.level)
        
            return data
        endmethod
        
        method onDestroy takes nothing returns nothing
            //since the spell is not active anymore, we clean the Table      
            call activeTable.flush(.caster) 
            
            //the unit is not anymore in the active units group.
            call GroupRemoveUnit(StupidPenguinCasters, .caster) 
            
            //releasing the timer for CSSafety to use it one day later =D
            call ReleaseTimer(.growPeriod)
        endmethod
        
    endstruct
//===========================================================================
    private function onStop takes nothing returns boolean
        local MyStruct data
        local unit u = GetTriggerUnit()
        
        //variables for the explosion!
        local group g 
        local unit f
        local boolexpr b
        local texttag text
        local effect explosionPenguin 
        local effect deathPenguin 
        local effect blood
        
        //this will save you unnecessary gamecache calls during units' deaths. 
        //It also prevents conflicts with units getting the same handle id as a ghost, 
        //however, since you flush when the spell ends that's most likely not an issue.
        if(IsUnitInGroup(u, StupidPenguinCasters)) then
            set data = activeTable[u] //recover that data (the struct) from the caster
            
            //we blow with everything up !! xD
            set g = NewGroup()
            
            //I want to use a boolean expresion, so I can place a function up in the SETUP
            //part that will allow the user to choose the targets of the explosion, but I don't
            //know how to do that without 
            // 1. Placing the entire structure before the SETUP (which will be very ugly)
            // 2. out of ideas ...
            set b = Condition(function Targets) 
            
            //we make an explosion and add a death effect to give the ilusion the Penguin exploded
            set explosionPenguin = AddSpecialEffect(EXPLOSION_EFFECT, GetUnitX(data.penguin), GetUnitY(data.penguin))
            set deathPenguin = AddSpecialEffect(DEATH_EFFECT, GetUnitX(data.penguin), GetUnitY(data.penguin))
            
            //remove the Penguin
            call ShowUnit(data.penguin, false)
            call KillUnit(data.penguin)
        
            call GroupEnumUnitsInRange(g, GetUnitX(data.penguin), GetUnitY(data.penguin), Radius(data.level), b)
            
            loop
                set f = FirstOfGroup(g)
                exitwhen (f == null)
                call GroupRemoveUnit(g, f)
                
                // the effect when the unit is damaged
                set blood = AddSpecialEffect(BLOOD_EFFECT, GetUnitX(f), GetUnitY(f))
            
                call UnitDamageTarget(data.penguin, f, data.damage, true, false, ATTACK_TYPE_NORMAL, DAMAGE_TYPE_UNIVERSAL, null)
            
                //sets the text tag if the flag is true
                if(TEXT) then
                    set text = CreateTextTag()
                    call SetTextTagText(text, I2S(R2I(data.damage)), .023 )   
                    call SetTextTagPosUnit( text, f, 0 )
                    call SetTextTagColor( text, RED, GREEN, BLUE, 255 )
                    call SetTextTagPermanent(text, false)
                    call SetTextTagVelocity( text, 0, .0277 )
                    call SetTextTagLifespan(text, 2.0)
                endif
            
                call DestroyEffect(blood)
            endloop
        
            call DestroyBoolExpr(b)
            call DestroyEffect(explosionPenguin)
            call DestroyEffect(deathPenguin)
            call ReleaseGroup(g)
        
            call data.destroy() //now, time to clean the mess once again
        endif
        
        set b = null //PS cant we recycle bolleexpressions ?? 
        set text = null
        set explosionPenguin = null
        set deathPenguin = null
        set g = null
        set u = null
            
        return false
    endfunction
//===========================================================================
    private function Size takes nothing returns nothing
        //code to make the penguin grow bigger and bigger !
        //we also set the damage variables and all that stuff here
    endfunction
//===========================================================================
    private function Conditions takes nothing returns boolean
        local MyStruct data 
        
        //I don't like creating this 2 variables everytime I go for a check =/
        local effect ef 
        local location spellLoc
        
        if (GetSpellAbilityId() == AID) then
            //setting variables for the struct
            set spellLoc = GetSpellTargetLoc()
            set data = MyStruct.create(GetTriggerUnit(), GetLocationX(spellLoc), GetLocationY(spellLoc))
            
            //this is just an eye candy, when the Stupid Penguin borns =)
            //If you  could find an animation with an egg, that could be cute xD
            set ef = AddSpecialEffect(BIRTH_EFFECT, GetUnitX(data.penguin), GetUnitY(data.penguin))
            
            //put the struct in the Table, we just use the caster's handle adress as 
            //the key which tells us where in the Table the struct is stored
            set activeTable[data.caster] = data 

            //we attach the struct to the timer and we start it
            call SetCSData(data.growPeriod, integer(data))
            call TimerStart(data.growPeriod, GrowTime(data.level), true, function Size)
            
            //we add the casting unit to some sort of "pool" with all other casters 
            //that are using this spell
            call GroupAddUnit(StupidPenguinCasters, data.caster)
            
            //cleaning up the mess
            call RemoveLocation(spellLoc)
            call DestroyEffect(ef)
        endif
        
        set ef = null
        set spellLoc = null
        
        return false
    endfunction
//===========================================================================
    private function Init takes nothing returns nothing
        local trigger StupidPenguinTrigger =CreateTrigger()
        call TriggerRegisterAnyUnitEventBJ(StupidPenguinTrigger, EVENT_PLAYER_UNIT_SPELL_EFFECT )
        call TriggerAddCondition(StupidPenguinTrigger, Condition( function Conditions ) )

        set StupidPenguinTrigger = CreateTrigger()
        call TriggerRegisterAnyUnitEventBJ(StupidPenguinTrigger, EVENT_PLAYER_UNIT_SPELL_ENDCAST)
        call TriggerAddCondition(StupidPenguinTrigger,  Condition(function onStop))
    
        //Create your spell's private table for the casters     
        set activeTable = HandleTable.create()
    endfunction
endscope

However I have some problems, as you can see, mainly on the boolexpression. I'd hate to place the whole struct before the SETUP part and to make it worse, I also have a few problems with the local variables, namely because I don't like some of the stuff I do with them.
The Size function is empty because it is not needed for now, I like to clear stuff by sections.

Also, one question, every time I create a local variable, is that local variable a pointer, just like when I create triggers ?

I hope you people can help me out with this spell as well, I would also like to submit it to this community after finishing it =)
Hope one day my submission number can be bigger =)

EDIT EDIT

Btw, can I (should I) replace this:
Collapse JASS:
    globals 
        private group StupidPenguinCasters = CreateGroup()
        private HandleTable activeTable //your private Table's global variable
    endglobals

by this ???
Collapse JASS:
globals 
        private group StupidPenguinCasters = NewGroup() //maybe using group recycle system ?
        private HandleTable activeTable //your private Table's global variable
    endglobals
07-08-2008, 02:35 PM#2
Anitarf
Why not use Tornado? It's already a channeling summon spell.
07-08-2008, 03:18 PM#3
chobibo
Quote:
Why not use Tornado? It's already a channeling summon spell.

He's right, just setup a trigger that detects the summoned units death and then do AoE damage, and about the growing penguin part you can also trigger it.
07-08-2008, 07:19 PM#4
Flame_Phoenix
Mmmmmmm although it wouldn't be a bad idea, the only thing that would change would be the fact that I create the unit in the "create" method. Other things would remain equal or would be little changed. Besides, I am looking for the way to learn more, not the easiest way - the easiest way would be using the spell with the dynamic triggers I made.

I also like to make my spells very independent from the WE, if for some reason i want to add this spell to naga hero, they will conflict unless I screw with the ID orders, and there is no need for that.

Besides the spell is nearly made, I just need some guide lines and answer from you guys, before submitting the spell =)

So, any more suggestions please ?

EDIT EDIT EDIT

However, I will consider this solution if this get messy.
Btw, you should know that the penguin can get attacked and can die =)

EDIT EDIT EDIT
I can't use tornado, if i do so, the unit will be invulnerable and I don't want that =S
07-08-2008, 07:55 PM#5
Feroc1ty
Can't the tornado spawn a different unit, which may have or may not have the invulnerability?
07-08-2008, 07:56 PM#6
chobibo
I haven't reviewed the entire code yet, the only suggestion I could offer you now is to use a global boolexpr to store Condition(Target) so you wouldn't need to create a boolexpr handle each time the function is called.
07-08-2008, 08:21 PM#7
Flame_Phoenix
Quote:
Can't the tornado spawn a different unit, which may have or may not have the invulnerability?
All units Tornado spawns are always invulnerable.

Quote:
I haven't reviewed the entire code yet, the only suggestion I could offer you now is to use a global boolexpr to store Condition(Target) so you wouldn't need to create a boolexpr handle each time the function is called.
What for ? I would still have the same problem =S
Besides its not in every iteration, it runs 1 time when the Penguin dies ...
07-08-2008, 08:29 PM#8
Anitarf
It's not a new handle every time, boolexpressions work in mysterios ways.
07-08-2008, 08:37 PM#9
Flame_Phoenix
Any suggestions on ow to improve the spell ?
Any answers to my many questions ???
Some one ? please ?
07-08-2008, 09:51 PM#10
Flame_Phoenix
Ok guys, I made some updates, and now I leave code here for more suggestions =P

Collapse JASS:
scope StupidPenguinRmk initializer Init

    globals 
        private unit tmpCaster = null
    endglobals
//===========================================================================
//=============================SETUP START===================================
//===========================================================================
    globals
        private constant integer AID = 'A000'  //The rawcode of the Apocalypse ability 
        private constant integer UNITID = 'npng' //The Penguin's ID
        private constant string EXPLOSION_EFFECT = "Objects\\Spawnmodels\\NightElf\\NEDeathMedium\\NEDeath.mdl" //explosion effect when penguin dies
        private constant string DEATH_EFFECT = "Objects\\Spawnmodels\\Orc\\OrcSmallDeathExplode\\OrcSmallDeathExplode.mdl" //death's effect of penguin
        private constant string BLOOD_EFFECT = "Objects\\Spawnmodels\\Critters\\Albatross\\CritterBloodAlbatross.mdl" //effect shown when units take damage
        private constant string BIRTH_EFFECT = "Objects\\Spawnmodels\\Other\\ToonBoom\\ToonBoom.mdl" //effect shown when pinguin is created
        private constant integer RED = 0 //the red RGB color for the text tag
        private constant integer GREEN = 0 //the green RGB color for the text tag
        private constant integer BLUE = 255 //the blue RGB color for the text tag
        private constant boolean TEXT = true //if true, shows text saying damage abve units, if false, it doesn't
    endglobals
    
    private constant function GrowTime takes integer level returns real time
        return 1. + (level * 0) //time intervail between each time the unit grows (is a cicle)
    endfunction
    
    private constant function Growth takes integer level returns real grow
        return 0.1 * level //How much the unit will grow. It grows 10% * level in this case
    endfunction
    
    private constant function Radius takes integer level returns integer radius
        return 200 + (50 * (level - 1)) //the area of damage
    endfunction
        
    private constant function Damage takes integer level returns real damage
        return 10. * level //Damage increase per cicle
    endfunction
    
    private function Targets takes nothing returns boolean
        //these are the targets the explosion will affect !
        return IsUnitEnemy(GetFilterUnit(), GetOwningPlayer(tmpCaster)) and (IsUnitType(GetFilterUnit(), UNIT_TYPE_MECHANICAL) == false) and (IsUnitType(GetFilterUnit(), UNIT_TYPE_MAGIC_IMMUNE) == false) and (GetWidgetLife(GetFilterUnit()) > 0.405)
    endfunction
    
//===========================================================================
//=============================SETUP END=====================================
//===========================================================================
    globals 
        private group StupidPenguinCasters = CreateGroup()
        private HandleTable activeTable //your private Table's global variable
    endglobals
    
    private struct MyStruct
        unit caster
        integer level
        unit penguin
        real currentScale 
        timer growPeriod
        real grow
        real damage
        
        static method create takes unit caster, real spellX, real spellY returns MyStruct
            local MyStruct data = MyStruct.allocate()
            
            //set variables about the caster
            set data.caster = caster
            set data.level = GetUnitAbilityLevel(data.caster, AID)
            
            //set variables about the Stupid Penguin
            set data.penguin = CreateUnit(GetOwningPlayer(data.caster), UNITID, spellX, spellY, 0.)
            set data.currentScale = 1
            set data.growPeriod = NewTimer()
            set data.grow = Growth(data.level)
            set data.damage = Damage(data.level)
        
            return data
        endmethod
        
        method onDestroy takes nothing returns nothing
            //since the spell is not active anymore, we clean the Table      
            call activeTable.flush(.caster) 
            
            //the unit is not anymore in the active units group.
            call GroupRemoveUnit(StupidPenguinCasters, .caster) 
            
            //releasing the timer for CSSafety to use it one day later =D
            call ReleaseTimer(.growPeriod)
        endmethod
        
    endstruct
//===========================================================================
    private function explosion takes MyStruct structure returns nothing
        //variables for the explosion!
        local MyStruct data = structure
        local group g = NewGroup()
        local unit f
        local boolexpr b
        local texttag text
        local effect explosionPenguin = AddSpecialEffect(EXPLOSION_EFFECT, GetUnitX(data.penguin), GetUnitY(data.penguin))
        local effect deathPenguin = AddSpecialEffect(DEATH_EFFECT, GetUnitX(data.penguin), GetUnitY(data.penguin))
        local effect blood
            
        //remove the Penguin
        call ShowUnit(data.penguin, false)
        call KillUnit(data.penguin)
        
        set tmpCaster = data.caster
        set b = Condition(function Targets) 
        call GroupEnumUnitsInRange(g, GetUnitX(data.penguin), GetUnitY(data.penguin), Radius(data.level), b)
            
        loop
            set f = FirstOfGroup(g)
            exitwhen (f == null)
            call GroupRemoveUnit(g, f)
                
            // the effect when the unit is damaged and the damage
            set blood = AddSpecialEffect(BLOOD_EFFECT, GetUnitX(f), GetUnitY(f))
            call UnitDamageTarget(data.penguin, f, data.damage, true, false, ATTACK_TYPE_NORMAL, DAMAGE_TYPE_UNIVERSAL, null)
            
            //sets the text tag if the flag is true
            if(TEXT) then
                set text = CreateTextTag()
                call SetTextTagText(text, I2S(R2I(data.damage)), .023 )   
                call SetTextTagPosUnit( text, f, 0 )
                call SetTextTagColor( text, RED, GREEN, BLUE, 255 )
                call SetTextTagPermanent(text, false)
                call SetTextTagVelocity( text, 0, .0277 )
                call SetTextTagLifespan(text, 2.0)
            endif
            call DestroyEffect(blood)
        endloop
        
        call DestroyBoolExpr(b)
        call DestroyEffect(explosionPenguin)
        call DestroyEffect(deathPenguin)
        call ReleaseGroup(g)
        
        set b = null //PS cant we recycle bolleexpressions ?? 
        set text = null
        set explosionPenguin = null
        set deathPenguin = null
        set g = null
    endfunction
//===========================================================================
    private function onStop takes nothing returns boolean
        local MyStruct data
        local unit u = GetTriggerUnit()
        
        //this will save you unnecessary gamecache calls during units' deaths. 
        //It also prevents conflicts with units getting the same handle id as a ghost, 
        //however, since you flush when the spell ends that's most likely not an issue.
        if(IsUnitInGroup(u, StupidPenguinCasters)) then
            set data = activeTable[u] //recover that data (the struct) from the caster
            call explosion(data) //here we make things blow up !
            call data.destroy() //now, time to clean the mess once again
        endif

        set u = null
        
        return false
    endfunction
//===========================================================================
    private function onDeath takes nothing returns boolean
        local MyStruct data
        local unit u = GetTriggerUnit()
        
        //PS HELP, HOW DO I KNOW THE PENGUIN THAT DIED ?!
        //CAN I HAVE 1 TABLE FOR 2 UNITS ?
        if(IsUnitInGroup(u, StupidPenguinCasters)) then
            set data = activeTable[u] //recover that data (the struct) from the caster
            call explosion(data) //here we make things blow up !
            call data.destroy() //now, time to clean the mess once again
        endif

        set u = null
        
        return false
    endfunction
//===========================================================================
    private function Size takes nothing returns nothing
        //code to make the penguin grow bigger and bigger !
        //we also set the damage variables and all that stuff here
    endfunction
//===========================================================================
    private function Conditions takes nothing returns boolean
        local MyStruct data 
        local effect ef 
        local location spellLoc
        
        if (GetSpellAbilityId() == AID) then
            //setting variables for the struct
            set spellLoc = GetSpellTargetLoc()
            set data = MyStruct.create(GetTriggerUnit(), GetLocationX(spellLoc), GetLocationY(spellLoc))
            
            //this is just an eye candy, when the Stupid Penguin borns =)
            //If you  could find an animation with an egg, that could be cute xD
            set ef = AddSpecialEffect(BIRTH_EFFECT, GetUnitX(data.penguin), GetUnitY(data.penguin))
            
            //put the struct in the Table, we just use the caster's handle adress as 
            //the key which tells us where in the Table the struct is stored
            set activeTable[data.caster] = data 

            //we attach the struct to the timer and we start it
            call SetCSData(data.growPeriod, integer(data))
            call TimerStart(data.growPeriod, GrowTime(data.level), true, function Size)
            
            //we add the casting unit to some sort of "pool" with all other casters 
            //that are using this spell
            call GroupAddUnit(StupidPenguinCasters, data.caster)
            
            //cleaning up the mess
            call RemoveLocation(spellLoc)
            call DestroyEffect(ef)
        endif
        
        set ef = null
        set spellLoc = null
        
        return false
    endfunction
//===========================================================================
    private function Init takes nothing returns nothing
        local trigger StupidPenguinTrigger =CreateTrigger()
        call TriggerRegisterAnyUnitEventBJ(StupidPenguinTrigger, EVENT_PLAYER_UNIT_SPELL_EFFECT )
        call TriggerAddCondition(StupidPenguinTrigger, Condition( function Conditions ) )

        set StupidPenguinTrigger = CreateTrigger()
        call TriggerRegisterAnyUnitEventBJ(StupidPenguinTrigger, EVENT_PLAYER_UNIT_SPELL_ENDCAST)
        call TriggerAddCondition(StupidPenguinTrigger,  Condition(function onStop))
        
        set StupidPenguinTrigger = CreateTrigger()
        call TriggerRegisterAnyUnitEventBJ(StupidPenguinTrigger, EVENT_PLAYER_UNIT_DEATH)
        call TriggerAddCondition(StupidPenguinTrigger,  Condition(function onDeath))
        
        //Create your spell's private table for the casters     
        set activeTable = HandleTable.create()
    endfunction
endscope

Any suggestions ?
07-09-2008, 03:04 AM#11
chobibo
Quote:
It's not a new handle every time, boolexpressions work in mysterios ways.
I stand corrected, just use Condition(Target) directly, It always points to the same boolexpr. You also don't need to destroy the boolexpr dude.
07-09-2008, 03:07 AM#12
PenguinEmperor
. . .

Can you use something OTHER than a penguin?
07-09-2008, 03:28 AM#13
Squally425
I always thought exploding mini abominations were somewhat a sight to behold...
07-09-2008, 09:08 AM#14
Flame_Phoenix
Quote:
Can you use something OTHER than a penguin?
Quote:
I always thought exploding mini abominations were somewhat a sight to behold...
Yes I will soon change the spell so it can have a different unit every level =)
And you can choose what kind of unit you can blow in the SETUP part.
Quote:
It's not a new handle every time, boolexpressions work in mysterios ways.
I stand corrected, just use Condition(Target) directly, It always points to the same boolexpr. You also don't need to destroy the boolexpr dude.
Well, BladeDK disagrees with you, he destroys boolexpre in his tutorials about how to make spells.

About the code, can any1 tell me how I can solve the Penguin thing ?? when he dies ? I already know a solution, I just need to know how to use table with him =S
07-09-2008, 09:53 AM#15
Pyrogasm
Quote:
Originally Posted by Flame_Phoenix
Well, BladeDK disagrees with you, he destroys boolexpre in his tutorials about how to make spells.
Well, he wrote that before this.