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