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

[vJASS] Meat Mash v1.1.0.1

Thank you Skycraft for the idea and inspiration ^_^

I think the documentation of the code will suffice:

JASS:
library MeatMash requires SpellEffectEvent, Tt, Table, RegisterPlayerUnitEvent, Dummy
/*****************************************************
*
*   MeatMash
*   v1.1.0.1
*   By Magtheridon96
*
*   - This spell creates a Barbeque ward at the target
*     point that calls down chunks of meat from the sky 
*     to damage nearby enemy units.
*
*   Requires:
*   ---------
*
*       TimerTools by Nestharus
*           - hiveworkshop.com/forums/jass-resources-412/system-timer-tools-201165/
*       SpellEffectEvent by Bribe
*           - hiveworkshop.com/forums/jass-resources-412/snippet-spelleffectevent-187193/
*       RegisterPlayerUnitEvent by Magtheridon96
*           - hiveworkshop.com/forums/jass-resources-412/snippet-registerplayerunitevent-203338/
*       Table by Bribe
*           - hiveworkshop.com/forums/jass-resources-412/snippet-new-table-188084/
*       Dummy by Nestharus
*           - hiveworkshop.com/forums/jass-resources-412/system-dummy-213908/
*
*   Implementation Instructions:
*   ----------------------------
*
*       1. Import all the required systems to your map.
*       2. Import this trigger into your map.
*       3. Import the object editor data to your map.
*          (1 Ability, 2 Units)
*       4. Import dummy.mdx into your map.
*       5. Configure the spell any way you like.
*       6. Enjoy!
*
*   Configuration:
*   --------------
*
*****************************************************/
    globals
/*****************************************************
*
*       Object Raw Code Configuration
*       -----------------------------
*
*       This is where you should input all the 
*       raw codes of the objects of this spell.
*
*           ABIL_CODE
*           o The raw code of the ability.
*
*           WARD_CODE
*           o The raw code of the barbeque ward.
*
*****************************************************/
        private constant integer ABIL_CODE = 'A000'
        private constant integer WARD_CODE = 'o000'
/*****************************************************
*
*       Projectile Speed Configuration
*       ------------------------------
*
*       This is where you could change how fast 
*       the meat chunks are falling from the sky.
*
*           CHUNK_INITIAL_SPEED
*           o The initial speed of the meat chunk.
*
*           CHUNK_HEIGHT
*           o The height at which the meat chunk is created.
*
*           ACCELERATION
*           o The acceleration of the falling speed of
*             the meat chunk.
*
*****************************************************/
        private constant real CHUNK_INITIAL_SPEED = 1
        private constant real CHUNK_HEIGHT = 1300
        private constant real ACCELERATION = 0.7
/*****************************************************
*
*       Damage and Attack-type Configuration
*       ------------------------------------
*
*       This is where you would configure the attack 
*       type and the damage type of the damage that is 
*       dealt to units.
*
*           ATTACK_TYPE
*           o The attack type.
*
*           DAMAGE_TYPE
*           o The damage type.
*
*****************************************************/
        private constant attacktype ATTACK_TYPE = ATTACK_TYPE_NORMAL
        private constant damagetype DAMAGE_TYPE = DAMAGE_TYPE_NORMAL
/*****************************************************
*
*       Damage Radius Configuration
*       ---------------------------
*
*       This is where you would configure 
*       the damage radius of one meat chunk.
*
*           MEAT_DAMAGE_RADIUS
*           o Damage radius of one meat chunk.
*
*****************************************************/
        private constant real MEAT_DAMAGE_RADIUS = 120
/*****************************************************
*
*       Special Effect Configuration
*       ----------------------------
*
*       This is where you would configure
*       the special effects created by the spell.
*
*           MEAT_EFFECT_PATH
*           o This is the actual meat chunk.
*
*           WARD_EFFECT_PATH
*           o The effect created when a ward dies.
*
*****************************************************/
        private constant string MEAT_EFFECT_PATH = "Abilities\\Weapons\\MeatwagonMissile\\MeatwagonMissile.mdl"
        private constant string WARD_EFFECT_PATH = "Abilities\\Weapons\\SerpentWardMissile\\SerpentWardMissile.mdl"
    endglobals
/*****************************************************
*
*       Damage Configuration
*       --------------------
*
*       This is where you could configure 
*       the amount of damage that is dealt
*       per chunk of meat.
*
*           integer level
*           o Level of the ability.
*
*****************************************************/
    private function GetDamagePerChunk takes integer level returns real
        return 30. + level * 15
    endfunction
/*****************************************************
*
*       Meat Chunk Amounts Configuration
*       --------------------------------
*
*       This is where you would configure
*       the number of meat chunks spawned
*       per second.
*
*           integer level
*           o Level of the ability.
*
*****************************************************/
    private function GetChunksPerSecond takes integer level returns real
        return 6. + level * 2
    endfunction
/*****************************************************
*
*       Spell Radius Configuration
*       --------------------------
*
*       This is where you would configure
*       the radius of the chunk spawning area.
*
*           integer level
*           o Level of the ability.
*
*****************************************************/
    private function GetRadius takes integer level returns real
        return 400 + level * 50.
    endfunction
/*****************************************************
*
*       Spell Duration Configuration
*       ----------------------------
*
*       This is where you would configure
*       the duration of the spell effect.
*
*           integer level
*           o Level of the ability.
*
*****************************************************/
    private function GetDuration takes integer level returns real
        return 6. + level * 2
    endfunction
/*****************************************************
*
*       Target Filter Configuration
*       ---------------------------
*
*       This is where you would configure
*       the filter that decides whether a 
*       unit should take damage or not.
*
*           player source
*           o The owner of the caster.
*
*           unit target
*           o The unit we are filtering.
*
*****************************************************/
    private function TargetFilter takes player source, unit target returns boolean
        return not IsUnitType(target, UNIT_TYPE_MAGIC_IMMUNE) and not IsUnitType(target, UNIT_TYPE_STRUCTURE) and not IsUnitType(target, UNIT_TYPE_DEAD) and IsUnitEnemy(target, source)
    endfunction
/*****************************************************
*
*   Notes:
*   ------
*
*       - If you are facing performance problems, go 
*         to the top of the MeatMash struct and read 
*         the given text on top of the TIMEOUT constant.
*         You may need to modify the TIMEOUT.
*
*   Credits:
*   --------
*
*       - Skycraft (Inspiration and idea)
*       - Nestharus (TimerTools and Dummy)
*       - Bribe (SpellEffectEvent and Table)
*
*****************************************************/
    
    /*
    *   This struct represents one chunk of meat.
    *   This struct can be avoided completely, but 
    *   it would require me to make multiple tables
    *   of data inside the MeatMash struct which could 
    *   get ugly. This is the more efficient method
    *   because I would only rely on one hashtable read 
    *   to get the MeatChunk struct instance and the rest
    *   of the reads would be array reads.
    */
    private struct MeatChunk extends array
        /*
        *   These will be used for the 
        *   dynamic indexing.
        */
        private static integer array rn
        private static integer ic = 0
        
        /*
        *   The dummy unit and his 
        *   current falling speed.
        */
        Dummy dummy
        real speed
        
        /*
        *   The model of the dummy.
        */
        effect model
        
        /*
        *   The coordinates of the 
        *   dummy unit.
        */
        real x
        real y
        real z
        
        /*
        *   This function creates an instance
        *   of the MeatChunk struct.
        */
        static method create takes real centerX, real centerY, real distance, real angle returns thistype
            /*
            *   First, we use dynamic indexing
            *   to allocate an instance.
            */
            local thistype this = rn[0]
            if this == 0 then
                set ic = ic + 1
                set this = ic
            else
                set rn[0] = rn[this]
            endif
            
            /*
            *   Next, we create the dummy unit
            *   and configure his coordinates and 
            *   speed.
            */
            set this.x = centerX + distance * Cos(angle)
            set this.y = centerY + distance * Sin(angle)
            set this.dummy = Dummy.create(this.x, this.y, 0)
            set this.speed = CHUNK_INITIAL_SPEED
            set this.z = CHUNK_HEIGHT
            
            /*
            *   Creating the meat chunk effect on the dummy unit.
            */
            set this.model = AddSpecialEffectTarget(MEAT_EFFECT_PATH, this.dummy.unit, "origin")
            
            /*
            *   Finally, we put our meat in the sky.
            */
            call SetUnitFlyHeight(this.dummy.unit, CHUNK_HEIGHT, 0)
            
            return this
        endmethod
        
        method destroy takes nothing returns nothing
            /*
            *   We deallocate the instance.
            */
            set rn[this] = rn[0]
            set rn[0] = this
            /*
            *   We destroy the model.
            */
            call DestroyEffect(this.model)
            /*
            *   Then we destroy the dummy.
            */
            call this.dummy.destroy()
        endmethod
    endstruct
    
    /*
    *   This is the main spell struct
    *   in which I keep all my data.
    */
    private struct MeatMash extends array
        /*
        *   The timeout can be changed. The only 
        *   timeouts I would recommend are as 
        *   follows:
        *
        *       0.02        (50 iterations per second)
        *       0.03125     (32 iterations per second)
        *       0.04        (25 iterations per second)
        *       0.05        (20 iterations per second)
        *       0.0625      (16 iterations per second)
        *
        *   0.0625 is only for cases in which you are
        *   facing performance problems. 0.03125 is the
        *   most optimal because it allows you to maintain
        *   realism and is not too fast.
        */
        private static constant real TIMEOUT = 0.03125
        
        /*
        *   This is the global group that we will
        *   use to enumerate over units in range.
        */
        private static group enumGroup = CreateGroup()
        
        /*
        *   This is the table that will link
        *   wards to their instances.
        */
        private static Table cache
        
        /*
        *   These are all the spell 
        *   data arrays.
        */
        private static unit array caster
        private static player array owner
        private static unit array ward
        private static real array wardX
        private static real array wardY
        private static real array damage
        private static real array range
        private static real array time
        private static real array interval
        private static integer array ticks
        
        /*
        *   Each struct instance has one 
        *   Table and an integer to act 
        *   as a MEAT STACK.
        */
        private static Table array stack
        private static integer array count
        
        /*
        *   This is where all the 
        *   timer code begins.
        */
        implement CTTC
            /*
            *   Local declarations.
            */
            local MeatChunk current
            local integer index
            local unit u
        implement CTTCExpire
            /*
            *   Checking if there is at least 
            *   one tick left.
            */
            if ticks[this] >= 1 then
            
                /*
                *   Decrease the number of remaining
                *   ticks by 1.
                */
                set ticks[this] = ticks[this] - 1
            
                /*
                *   We increase the time member by the 
                *   TIMEOUT. This time member is important
                *   because it allows us to have this spell 
                *   function for any number of chunks per
                *   second. Without it, this spell would 
                *   only function for up to (1/TIMEOUT) 
                *   chunks per second and only certain 
                *   numbers within the range
                *   [0 ... (1/TIMEOUT)]
                */
                set time[this] = time[this] + TIMEOUT
                
                /*
                *   Now we loop to see how many chunks 
                *   of meat we need to create this time.
                */
                loop
                    exitwhen time[this] < interval[this]
                    
                    /*
                    *   We create the meat chunk at a random 
                    *   point in range of the ward and push it 
                    *   to the MEAT STACK.
                    */
                    set stack[this][count[this]] = MeatChunk.create(wardX[this], wardY[this], GetRandomReal(0, range[this]), GetRandomReal(0, 6.28))
                    set count[this] = count[this] + 1
                    
                    /*
                    *   We decrease the time passed by the 
                    *   interval at which we need to create 
                    *   a meat chunk.
                    */
                    set time[this] = time[this] - interval[this]
                endloop
            else
                /*
                *   If there are no more chunks of meat left, 
                *   then we can destroy the current instance 
                *   of the spell.
                */
                if count[this] == 0 then
                    /*
                    *   Removing the current instance
                    *   from the timer list.
                    */
                    call this.destroy()
                    /*
                    *   Destroying the meat chunk stack.
                    */
                    call stack[this].flush()
                    /*
                    *   Killing the barbeque ward.
                    */
                    call UnitApplyTimedLife(ward[this], 'BTLF', 0.01)
                    /*
                    *   Unlinking the ward from this instance
                    */
                    set cache[GetHandleId(ward[this])] = 0
                    /*
                    *   Nulling the members of this instance.
                    */
                    set caster[this] = null
                    set ward[this] = null
                    set owner[this] = null
                    set time[this] = 0
                endif
            endif
            
            /*
            *   We reset our loop index.
            */
            set index = 0
            
            /*
            *   Now we are iterating over all 
            *   the chunks of meat for the current 
            *   instance.
            */
            loop
                exitwhen index == count[this]
                set current = stack[this][index]
                
                /*
                *   We change the height of the meat
                *   chunk and increase the speed.
                */
                set current.z = current.z - current.speed
                set current.speed = current.speed + ACCELERATION
                call SetUnitFlyHeight(current.dummy.unit, current.z, 0)
                
                /*
                *   Checking if the dummy unit
                *   is close to the ground.
                */
                if current.z < 8 then
                
                    /*
                    *   We pick all units in range of 
                    *   the meat chunk and iterate.
                    */
                    call GroupEnumUnitsInRange(enumGroup, current.x, current.y, MEAT_DAMAGE_RADIUS, null)
                    
                    loop
                        set u = FirstOfGroup(enumGroup)
                        exitwhen u == null
                        call GroupRemoveUnit(enumGroup, u)
                        
                        /*
                        *   We check if the picked unit passes 
                        *   the given target filter.
                        */
                        if TargetFilter(owner[this], u) then
                            /*
                            *   We deal damage to the picked unit.
                            */
                            call UnitDamageTarget(caster[this], u, damage[this], false, false, ATTACK_TYPE, DAMAGE_TYPE, null)
                        endif
                    endloop
                    
                    /*
                    *   The current chunk has fell to the
                    *   ground, so we can destroy it.
                    */
                    call current.destroy()
                    
                    /*
                    *   Then we remove it from 
                    *   the current MEAT STACK.
                    */
                    set count[this] = count[this] - 1
                    set stack[this][index] = stack[this][count[this]]
                    set index = index - 1
                endif
                
                /*
                *   Go to the next chunk of meat.
                */
                set index = index + 1
            endloop
        implement CTTCEnd
        
        private static method run takes nothing returns nothing
            local thistype this = create()
            local integer level
            
            /*
            *   We first store the caster, his 
            *   owner and the target coordinates.
            */
            set caster[this] = GetTriggerUnit()
            set owner[this] = GetTriggerPlayer()
            set wardX[this] = GetSpellTargetX()
            set wardY[this] = GetSpellTargetY()
            
            /*
            *   Next, we create the barbeque ward.
            *   If this ward dies, the spell ends.
            */
            set ward[this] = CreateUnit(owner[this], WARD_CODE, wardX[this], wardY[this], 270)
            
            /*
            *   Now we cache the level which
            *   will be used in the formulas
            *   for damage, range, ticks 
            *   and chunks per second.
            */
            set level = GetUnitAbilityLevel(caster[this], ABIL_CODE)
            
            set damage[this] = GetDamagePerChunk(level)
            set range[this] = GetRadius(level)
            set ticks[this] = R2I(GetDuration(level) / TIMEOUT)
            set interval[this] = 1 / GetChunksPerSecond(level)
            
            /*
            *   We apply timed life to the ward 
            *   only to show the player how much 
            *   time is left before the spell ends.
            */
            call UnitApplyTimedLife(ward[this], 'BTLF', GetDuration(level))
            
            /*
            *   We reset the stack for 
            *   the current instance.
            */
            if stack[this] == 0 then
                set stack[this] = Table.create()
            endif
            
            /*
            *   And finally, we link this instance
            *   with the ward so that we can end it 
            *   upon the death of this ward.
            */
            set cache[GetHandleId(ward[this])] = this
        endmethod
        
        private static method onDeath takes nothing returns nothing
            local integer handleId = GetHandleId(GetTriggerUnit())
            local thistype this = thistype(cache[handleId])
            
            /*
            *   If there was actually an instance of this associated with 
            *   the dying unit, then it was in fact a Barbeque ward, and so, 
            *   we ... the comments below detail what we are going to do.
            */
            if this != 0 then
                
                /*
                *   We set the ticks to 0 to disable
                *   meat chunk summoning and we unlink the
                *   ward from the current instance in case
                *   the handle id of the ward is not 
                *   retrievable after all the meat chunks 
                *   hit the ground when we want to destroy 
                *   the spell instance.
                */
                set ticks[this] = 0
                set cache[handleId] = 0
                
                /*
                *   We remove the ward from the 
                *   game because we do not need it.
                */
                call RemoveUnit(ward[this])
                
                /*
                *   Finally, we create the special effect
                *   at the position of the ward.
                */
                call DestroyEffect(AddSpecialEffect(WARD_EFFECT_PATH, wardX[this], wardY[this]))
            endif
        endmethod
        
        private static method onInit takes nothing returns nothing
            /*
            *   We register the spell effect event 
            *   to our run function and we register 
            *   the death event so we can detect 
            *   the death of a Barbeque ward.
            */
            call RegisterSpellEffectEvent(ABIL_CODE, function thistype.run)
            call RegisterPlayerUnitEvent(EVENT_PLAYER_UNIT_DEATH, function thistype.onDeath)
            /*
            *   We create our instance table.
            *   This will be used to link wards to 
            *   their respective instances.
            */
            set cache = Table.create()
        endmethod
    endstruct
    
endlibrary

I hope you find the code readable.
If you spot any errors or have issues, notify me.
If you have problems with implementation, please read the documentation.
Always read the documentation first.


v1.0.0.0
  • Uploaded.

v1.0.0.1
  • Gave dummy units 'Amrf' in the Object Editor. (Optimization)

v1.1.0.0
  • Removed some constants
  • Changed default configuration
  • Added unit recycling via Dummy by Nestharus

v1.1.0.1
  • Removed some useless local variables
  • Changed the Timer module used
  • Removed some unwanted code

Happy Mapping!

Keywords:
meat, chunks, meatmash, mash, potato, nestharus, bribe, spell, naruto, spell packs, packs, spellpacks, luffy, magtheridon96.
Contents

Just another Warcraft III map (Map)

Reviews
25th Jul 2012 Bribe: Coding looks quite good and I can't find any bugs/faults. The visual treat is sadistic. I recommend for a next spell that you join the Spells Mini Contest ;) Approved 4.49/5

Moderator

M

Moderator

25th Jul 2012
Bribe: Coding looks quite good and I can't find any bugs/faults. The visual treat is sadistic. I recommend for a next spell that you join the Spells Mini Contest ;)

Approved 4.49/5
 
Level 31
Joined
Jul 10, 2007
Messages
6,306
This is designed using the old crappy method where you have 1 constant instance for the spell and copies of that instance..

Think of this instead...
local mash = MeatMash.create(options, abilityId, etc)
set mash[3].damagePerChunk = 50


Then when a unit uses the ability, it automatically runs perfectly.


This doesn't support things like +%magic damage, which some maps like GoH have. Simply having damage per ability isn't very dynamic.

It would be better if you abstracted the base code for this (stuff that handles falling w/e and etc) so that people could use that and create a variety of spells from it.

It's just way too focused on one thing, not modular, and so on. I know this is the way people generally do spells, but I'm saying now that it's a bad practice : \. If a user wanted to have 2 dif types of this spell with dif effects and damage types, they'd have to cnp ur entire lib 2x. If they wanted to upgrade the damage for one unit for this spell, then they'd have to recode ur entire thing or do other weird things >.<.


I give this a 2/5 for effort. You'd have to spend a lot of thought on design and what not to pull this off correctly, and I'm sure that the new designs you come up with would be able to be applied to future spells ^_^.

edit
Actually, just doing instancing with ability ids and abstracting the damage would be brilliant. You could even do a module with an onDamage that way it can be plugged into any map and fit any theme =). That design change would likely earn you a 4/5 from me, possibly 5/5 if your code is brilliant ^_^. If everything is super perfect, an above and beyond from me : D.
 
This is designed using the old crappy method where you have 1 constant instance for the spell and copies of that instance..

Think of this instead...
local mash = MeatMash.create(options, abilityId, etc)
set mash[3].damagePerChunk = 50


Then when a unit uses the ability, it automatically runs perfectly.


This doesn't support things like +%magic damage, which some maps like GoH have. Simply having damage per ability isn't very dynamic.

It would be better if you abstracted the base code for this (stuff that handles falling w/e and etc) so that people could use that and create a variety of spells from it.

It's just way too focused on one thing, not modular, and so on. I know this is the way people generally do spells, but I'm saying now that it's a bad practice : \. If a user wanted to have 2 dif types of this spell with dif effects and damage types, they'd have to cnp ur entire lib 2x. If they wanted to upgrade the damage for one unit for this spell, then they'd have to recode ur entire thing or do other weird things >.<.


I give this a 2/5 for effort. You'd have to spend a lot of thought on design and what not to pull this off correctly, and I'm sure that the new designs you come up with would be able to be applied to future spells ^_^.

edit
Actually, just doing instancing with ability ids and abstracting the damage would be brilliant. You could even do a module with an onDamage that way it can be plugged into any map and fit any theme =). That design change would likely earn you a 4/5 from me, possibly 5/5 if your code is brilliant ^_^. If everything is super perfect, an above and beyond from me : D.

This is just how I code public spells because I really don't feel like including a whole lot of other systems to make it modular :p

If I wanted this spell for my map, I'd have used a projectile system so I can reduce the spell code really.

edit
You know, this gives me a really good idea for a spell system :D
It'd be a collection of subsystems that work together to make up a spell.
 
Level 25
Joined
Jun 5, 2008
Messages
2,572
@Mag

Shouldn't this use a system to recycle units? The amount of units per cast is quite big so it would make sense.

This question comes a bit of also my need to know should we recycle units and if so what are the system available to use for doing so except Bribe's.

The spell is quite nice, not too original.
Not gonna rate it but it is 4/5, lacking that 1 point on originality.
 
Level 29
Joined
Mar 10, 2009
Messages
5,016
OK I just tested it and found out that it has a huge AOE, I suggest to 'level' the AOE maximum of say 1200...

I didnt touch anyting but it says it requires CTTC so I changed it to that...

It lags if casted 4 times at the same time even if I made it to 0.0625, I think due to many falling BBQs XD...
nevermind, chunks per second
 
What do you mean by "reach"?
It already has Damage AoE. It's in the documentation:

JASS:
/*****************************************************
*
*       Spell Radius Configuration
*       --------------------------
*
*       This is where you would configure
*       the radius of the chunk spawning area.
*
*           integer level
*           o Level of the ability.
*
*****************************************************/
    private function GetRadius takes integer level returns real
        return 400 + level * 50.
    endfunction
 
Top