DrEvil
FCRI Associate!
- Reaction score
- 111
Grenade + Bouncy Grenade <updated>
I have re-coded my grenade spell to work with terrain heights,
I have created two, one that flies straight to the point, and another which bounces to the target point.
vJASS = yes
MUI = yes
Leakless = yes
Lagless = yes
Description of Grenade :
Description of Bouncy Grenade : ( Same stats of Grenade, but it bounces... )
ScreenShots...
I didn't want my old grenade here.. So I changed it to the new and improved version(s)
Changes -
New code,
Now works with terrain heights,
The bounce height + speed + distance depend on how far it travelled first time,
Options to hurt allies and player units,
Most things on the spell are configurable,
Added documentation for both spells ( Although it's just a brief task of how to use in another map ).
Special thx to Romek & TriggerHappy for helping me along the way
I have re-coded my grenade spell to work with terrain heights,
I have created two, one that flies straight to the point, and another which bounces to the target point.
vJASS = yes
MUI = yes
Leakless = yes
Lagless = yes
Description of Grenade :
The caster throws a grenade towards the target point, as soon as it hits the ground it will explode, possibly damaging player units, allies and enemies.
Level 1 = 200 Aoe / Minimum range = 400 / Maximum range = 1200 / Damage = 10-25
Level 2 = 250 Aoe / Minimum range = 400 / Maximum range = 1200 / Damage = 25-40
Level 3 = 300 Aoe / Minimum range = 400 / Maximum range = 1200 / Damage = 40-55
Level 1 = 200 Aoe / Minimum range = 400 / Maximum range = 1200 / Damage = 10-25
Level 2 = 250 Aoe / Minimum range = 400 / Maximum range = 1200 / Damage = 25-40
Level 3 = 300 Aoe / Minimum range = 400 / Maximum range = 1200 / Damage = 40-55
The caster determines where to throw the grenade that it will bounce towards the target ( Results vary if launched from lower/higher terrain)
Level 1 = 200 Aoe / Minimum range = 400 / Maximum range = 1200 / Damage = 10-25
Level 2 = 250 Aoe / Minimum range = 400 / Maximum range = 1200 / Damage = 25-40
Level 3 = 300 Aoe / Minimum range = 400 / Maximum range = 1200 / Damage = 40-55
Level 1 = 200 Aoe / Minimum range = 400 / Maximum range = 1200 / Damage = 10-25
Level 2 = 250 Aoe / Minimum range = 400 / Maximum range = 1200 / Damage = 25-40
Level 3 = 300 Aoe / Minimum range = 400 / Maximum range = 1200 / Damage = 40-55
JASS:
scope Grenade initializer Init
private keyword Data
globals
// Editable Globals
private constant integer GRENADE_ID = 039;gdum039; // Grenade unit
private constant integer GRENADE_SFX1 = 039;xdum039; // SFX 1
private constant integer GRENADE_SFX2 = 039;xsfx039; // SFX 2
private constant integer SPELL_ID = 039;GAId039; // Ability ID
private constant real MAX_RANGE = 1000
private constant real MIN_RANGE = 400
private constant real MAX_HEIGHT = 400
private constant attacktype AT = ATTACK_TYPE_NORMAL
private constant damagetype DT = DAMAGE_TYPE_NORMAL
private constant weapontype WT = WEAPON_TYPE_WHOKNOWS
private constant boolean DAMAGE_SELF = false// Can it hurt player units ?
private constant boolean DAMAGE_ALLY = false// Can it hurt ally units ?
// Editable Globals
// Don't change...
private constant real PERIOD = 0.04 // Depends on what period you like ... don't moan at me if your .0001 period lags though...
private constant real SPEED = 700 * PERIOD// How fast the grenade travels per second
private Data DATA //Temp Globals
private real X //Temp Globals
private real Y //Temp Globals
private real DIST //Temp Globals
private integer COUNT = 0
private Data array GData
private timer TIMER = CreateTimer()
private location LOC = Location(0,0)
//Don't change
endglobals
private constant function AOE_Range takes integer lvl returns real
return 150 + lvl*50 *1.0
endfunction
private function MinMax takes real val,real min,real max returns real
if(min>val) then
return min
elseif(val>max) then
return max
else
return val
endif
endfunction
private function GetParabolaZ takes real x,real d,real h returns real
return 4 * h * x * (d - x) / (d * d)
endfunction // By AceHart
private function GetDamage takes integer level returns real
return GetRandomReal (10,20) + 15 * (level-1)
endfunction// I guess Flare @ TH.net helped me with this <img src="data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7" class="smilie smilie--sprite smilie--sprite12" alt="o_O" title="Er... what? o_O" loading="lazy" data-shortname="o_O" />...
private struct Data
unit Grenade
unit caster
player p
real x
real y
real DistCur
real DistMax
real baseH
real aoe
integer level
real cos
real sin
static group GROUP = CreateGroup()
static method Damage takes nothing returns boolean
local unit u = GetFilterUnit()
local player p = GetOwningPlayer(u)
local Data d = DATA
local real damage = GetDamage(d.level)
local boolean dmg = false
if(DAMAGE_SELF and p==d.p)then
set dmg = true
elseif(DAMAGE_ALLY and IsPlayerAlly(p,d.p))then
set dmg = true
elseif(IsPlayerEnemy(p,d.p))then
set dmg = true
endif
if(dmg) then
call UnitDamageTarget(d.caster,u,damage,true,true,AT,DT,WT)
endif
set u = null
return false
endmethod
static method InCircle takes nothing returns boolean
local destructable d = GetFilterDestructable()
local real x = DATA.x - GetDestructableX(d)
local real y = DATA.y - GetDestructableY(d)
local real dist = x*x+y*y
set d = null
if(dist>DATA.aoe*DATA.aoe)then// or SquareRoot(dist) > AOE_RANGE either workz...
return false
else
return true
endif
endmethod
static method TreeDestroy takes nothing returns nothing
local destructable d = GetEnumDestructable()
if IsDestructableTree(d) then
call KillDestructable(d)
endif
set d = null
endmethod
method KillTrees takes real x,real y returns nothing
local rect r = Rect(x-.aoe,y-.aoe,x+.aoe,y+.aoe)
call EnumDestructablesInRect(r,Filter(function Data.InCircle),function Data.TreeDestroy)
call RemoveRect(r)
set r = null
endmethod
method Explode takes nothing returns nothing
call UnitApplyTimedLife(CreateUnit(.p,GRENADE_SFX1,.x,.y,0),0,1)
call UnitApplyTimedLife(CreateUnit(.p,GRENADE_SFX2,.x,.y,0),0,1)
set DATA = this
call GroupEnumUnitsInRange(.GROUP,.x,.y,this.aoe,Filter(function Data.Damage))
call .KillTrees(.x,.y)
call .destroy()
endmethod
method onDestroy takes nothing returns nothing
call ShowUnit(.Grenade,false)// Don't know wether this is neccesary ?
call RemoveUnit(.Grenade)
endmethod
endstruct
private function Execute takes nothing returns nothing
local integer i = 0
local Data d
local unit u
local boolean zhit
loop
exitwhen i == COUNT
set zhit = false
set d = GData<i>
set u = d.Grenade
set d.DistCur = d.DistCur + SPEED
set d.x = d.x + SPEED * d.cos
set d.y = d.y + SPEED * d.sin
if IsInMapXY(d.x,d.y) == true then
call SetUnitX(u,d.x)
call SetUnitY(u,d.y)
call SetUnitZ(u,d.baseH+GetParabolaZ(d.DistCur,d.DistMax,MAX_HEIGHT))
call MoveLocation(LOC,d.x,d.y)
if(R2I(GetUnitZ(u))==R2I(GetLocationZ(LOC)))then
// R2I because it gives me '0.001' and stuff ... so it isn't 'equal' unit height
set zhit=true
endif
if zhit==true then
set COUNT = COUNT - 1
set GData<i> = GData[COUNT]
call d.Explode()
else
set i = i + 1
endif
else
set COUNT = COUNT - 1
set GData<i> = GData[COUNT]
call d.destroy()
endif
endloop
set u = null
if COUNT == 0 then
call PauseTimer(TIMER)
endif
endfunction
private function Run takes nothing returns nothing
local Data d = Data.create()
local location l = GetSpellTargetLoc()
local real x = GetLocationX(l)
local real y = GetLocationY(l)
local real dist = 0
local real facing
call RemoveLocation(l)// do I need to 'set l = null' ?
set d.caster = GetTriggerUnit()
set l = GetUnitLoc(d.caster)
set d.baseH = GetLocationZ(l)
call RemoveLocation(l)
set l = null
set d.p = GetOwningPlayer(d.caster)
set d.x = GetUnitX(d.caster)
set d.y = GetUnitY(d.caster)
set d.level = GetUnitAbilityLevel(d.caster,SPELL_ID)
set d.aoe = AOE_Range(d.level)
set x = x - d.x
set y = y - d.y
set facing = Atan2(y,x)
set d.cos = Cos(facing)
set d.sin = Sin(facing)
set d.Grenade = CreateUnit(d.p,GRENADE_ID,d.x,d.y,facing)
call UnitAddAbility(d.Grenade,039;Amrf039;)// I have to do this or else it goes buggy over raised terrain
call UnitRemoveAbility(d.Grenade,039;Amrf039;)
set d.DistMax = MinMax(SquareRoot(x*x+y*y),MIN_RANGE,MAX_RANGE)
set d.DistCur = 0
set GData[COUNT] = d
set COUNT = COUNT + 1
if COUNT == 1 then
call TimerStart(TIMER,PERIOD,true,function Execute)
endif
endfunction
private function Cond takes nothing returns boolean
if GetSpellAbilityId() == SPELL_ID then
call Run()
endif
return false
endfunction
private function Init takes nothing returns nothing
local trigger t = CreateTrigger()
call TriggerRegisterAnyUnitEventBJ(t,EVENT_PLAYER_UNIT_SPELL_CAST)
call TriggerAddCondition(t,Condition(function Cond))
endfunction
endscope</i></i></i>
JASS:
scope BouncyGrenade initializer Init
private keyword Data
globals
// Editable Globals
private constant integer GRENADE_ID = 039;gdum039;
private constant integer GRENADE_SFX1 = 039;xdum039;
private constant integer GRENADE_SFX2 = 039;xsfx039;
private constant integer SPELL_ID = 039;BGid039;
private constant integer MAX_BOUNCES = 2// How many bounces ?
private constant real BOUNCE_DECREASE = 1.5// How much the range divises by per bounce
private constant real MAX_RANGE = 1200// max range of grenade
private constant real MIN_RANGE = 400// min range of grenade
private constant real MAX_HEIGHT = 400// maximum height reachable
private constant attacktype AT = ATTACK_TYPE_NORMAL // attack type..
private constant damagetype DT = DAMAGE_TYPE_NORMAL// damage type..
private constant weapontype WT = WEAPON_TYPE_WHOKNOWS// Weapon type..
private constant boolean DAMAGE_SELF = false// Can it hurt player units ?
private constant boolean DAMAGE_ALLY = false// Can it hurt ally units ?
//
// Don't change...
private constant real PERIOD = 0.04 // Depends on what period you like ... don't moan at me if your .0001 period lags though...
private constant real SPEED = 700 *PERIOD // How fast the grenade travels per second
private Data DATA// Temp Globals
private real X //Temp Globals
private real Y //Temp Globals
private real DIST //Temp Globals
private integer COUNT = 0
private Data array GData
private timer TIMER = CreateTimer()
//You can now continue to the rest of the code...
endglobals
private constant function AOE_Range takes integer lvl returns real
return 150 + lvl*50 *1.0// Find AoE for Grenade explosion
endfunction
private function MinMax takes real val,real min,real max returns real
if(min>val) then
return min
elseif(val>max) then
return max
else
return val
endif
endfunction
private function GetRange takes real range returns real
return range/BOUNCE_DECREASE// devise the range
endfunction
private function GetSpeed takes real range returns real
return (range/MAX_RANGE)*SPEED*2// find speed with this 'formula'
endfunction
private function GetHeight takes real range returns real// find the max height for that bounce
return MinMax((range/MAX_RANGE)*MAX_HEIGHT*3,0,MAX_HEIGHT)
endfunction
private function GetParabolaZ takes real x,real d,real h returns real
return 4 * h * x * (d - x) / (d * d)
endfunction // By AceHart
private function GetDamage takes integer level returns real
return GetRandomReal (10,20) + 15 * (level-1)
endfunction// I guess Flare @ TH.net helped me with this <img src="data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7" class="smilie smilie--sprite smilie--sprite12" alt="o_O" title="Er... what? o_O" loading="lazy" data-shortname="o_O" />...
private struct Data
unit Grenade
unit caster
player p
real x
real y
real DistCur
real DistMax
real HeightMax
real heightBase
real baseH
real aoe
real facing
integer level
real speed
real cos
real sin
integer bounce
boolean justBounced
static group GROUP = CreateGroup()
static method Damage takes nothing returns boolean// To damage the targets near grenade
local unit u = GetFilterUnit()
local player p = GetOwningPlayer(u)
local Data d = DATA
local real damage = GetDamage(d.level)
local boolean dmg = false
if(DAMAGE_SELF and p==d.p)then// damage the caster's and player units ... if possible ?
set dmg = true
elseif(DAMAGE_ALLY and IsPlayerAlly(p,d.p))then// hurt allies ?
set dmg = true
elseif(IsPlayerEnemy(p,d.p))then// or just enemies...
set dmg = true
endif
if(dmg) then
call UnitDamageTarget(d.caster,u,damage,true,true,AT,DT,WT)// the damage..
endif
set u = null
return false// return false to not add to global group..
endmethod
static method InCircle takes nothing returns boolean
local destructable d = GetFilterDestructable()
local real x = DATA.x - GetDestructableX(d)
local real y = DATA.y - GetDestructableY(d)
local real dist = x*x+y*y
// Check if the destructable is in range of the grenade AoE
set d = null
if(dist>DATA.aoe*DATA.aoe)then// or SquareRoot(dist) > AOE_RANGE either workz...
return false
else
return true
endif
endmethod
static method TreeDestroy takes nothing returns nothing
local destructable d = GetEnumDestructable()
if IsDestructableTree(d) then
call KillDestructable(d)// Destroy the tree if its a tree...
endif
set d = null//nulls...
endmethod
method KillTrees takes real x,real y returns nothing
local rect r = Rect(x-.aoe,y-.aoe,x+.aoe,y+.aoe)
// find trees within a rect then find within a circle...
call EnumDestructablesInRect(r,Filter(function Data.InCircle),function Data.TreeDestroy)
call RemoveRect(r)// nulls...
set r = null
endmethod
method Explode takes nothing returns nothing
call UnitApplyTimedLife(CreateUnit(.p,GRENADE_SFX1,.x,.y,0),0,1)
call UnitApplyTimedLife(CreateUnit(.p,GRENADE_SFX2,.x,.y,0),0,1)// Add each SFX for a second
set DATA = this
// global data for enums ...
call GroupEnumUnitsInRange(.GROUP,.x,.y,this.aoe,Filter(function Data.Damage))
call .KillTrees(.x,.y)
call .destroy()// kill trees then destroy self..
endmethod
method onDestroy takes nothing returns nothing
call ShowUnit(.Grenade,false)// Don't know wether this is neccesary ?
call RemoveUnit(.Grenade)
endmethod
method NewBounce takes nothing returns nothing// set new data for that bounce
local location l = GetUnitLoc(.Grenade)
set .bounce = .bounce + 1// increase bounce
set .DistMax = .DistCur/BOUNCE_DECREASE// find the distmax from the distance traveled / decrement
set .DistCur = 0// reset dist traveled
set .HeightMax = GetHeight(.DistMax)// formula for height
set .speed = GetSpeed(.DistMax)// find the speed for new bounce
set .baseH = GetLocationZ(l)//find 'this' bounce height again..
call RemoveLocation(l)// leaks
set l = null
endmethod
endstruct
private function Execute takes nothing returns nothing
local integer i = 0
local Data d
local unit u
local boolean zhit // Z - Hit if anyone has a weird font like me which looks like an s ...
local location l = Location(0,0)
loop
exitwhen i == COUNT
set zhit = false
set d = GData<i>
set u = d.Grenade
set d.DistCur = d.DistCur + d.speed
set d.x = d.x + d.speed * d.cos// moving data...
set d.y = d.y + d.speed * d.sin
if IsInMapXY(d.x,d.y) == true then// grenade in map
call SetUnitX(u,d.x)
call SetUnitY(u,d.y)// move the grenade
call SetUnitZ(u,d.baseH+GetParabolaZ(d.DistCur,d.DistMax,d.HeightMax))
// set unit Z
call MoveLocation(l,d.x,d.y)
if(R2I(GetUnitZ(u))==R2I(GetLocationZ(l)))then
// R2I because it gives me '0.001' and stuff ... so it isn't 'equal' unit height
set zhit=true
endif
// I dont think the dist current can go higher than the max... but just in case
if zhit==true then
if d.bounce==2 then
set COUNT = COUNT - 1
set GData<i> = GData[COUNT]// next instance
call d.Explode()
else
call d.NewBounce()// new bounce because of zhit
endif
else
set i = i + 1// just increase the value to the next instance
endif
else
set COUNT = COUNT - 1// change to next instance
set GData<i> = GData[COUNT]
call d.destroy()
endif
endloop
call RemoveLocation(l)
set u = null// leaks
set l = null
if COUNT == 0 then
call PauseTimer(TIMER)// pause
endif
endfunction
private function Run takes nothing returns nothing
local Data d = Data.create()
local location l = GetSpellTargetLoc()
local real x = GetLocationX(l)
local real y = GetLocationY(l)
local real dist = 0// locals for data
local real facing
call RemoveLocation(l)// do I need to 'set l = null' ?
set d.caster = GetTriggerUnit()
set l = GetUnitLoc(d.caster)
set d.baseH = GetLocationZ(l)
call RemoveLocation(l)
set l = null// leaks
set d.caster = GetTriggerUnit()
set d.p = GetOwningPlayer(d.caster)
set d.x = GetUnitX(d.caster)// caster data
set d.y = GetUnitY(d.caster)
set d.level = GetUnitAbilityLevel(d.caster,SPELL_ID)
set d.aoe = AOE_Range(d.level)
set d.bounce = 0
set x = x - d.x
set y = y - d.y
set facing = Atan2(y,x)
set d.cos = Cos(facing)
set d.sin = Sin(facing)// facing,,,
set d.Grenade = CreateUnit(d.p,GRENADE_ID,d.x,d.y,facing)
call UnitAddAbility(d.Grenade,039;Amrf039;)// I have to do this or else it goes buggy over raised terrain
call UnitRemoveAbility(d.Grenade,039;Amrf039;)
set d.DistMax = MinMax(SquareRoot(x*x+y*y)/Pow(BOUNCE_DECREASE,MAX_BOUNCES),0,MAX_RANGE)
set d.HeightMax = GetHeight(d.DistMax)// height, dist , speed data
set d.speed = GetSpeed(d.DistMax)
//MinMax(SquareRoot(x*x+y*y),MIN_RANGE,MAX_RANGE)
set d.DistCur = 0
set GData[COUNT] = d
set COUNT = COUNT + 1
if COUNT == 1 then
call TimerStart(TIMER,PERIOD,true,function Execute)
endif
endfunction
private function Cond takes nothing returns boolean
if GetSpellAbilityId() == SPELL_ID then
call Run()
endif
return false
endfunction
private function Init takes nothing returns nothing
local trigger t = CreateTrigger()
call TriggerRegisterAnyUnitEventBJ(t,EVENT_PLAYER_UNIT_SPELL_CAST)
call TriggerAddCondition(t,Condition(function Cond))
endfunction
endscope</i></i></i>
ScreenShots...
I didn't want my old grenade here.. So I changed it to the new and improved version(s)
Changes -
New code,
Now works with terrain heights,
The bounce height + speed + distance depend on how far it travelled first time,
Options to hurt allies and player units,
Most things on the spell are configurable,
Added documentation for both spells ( Although it's just a brief task of how to use in another map ).
Special thx to Romek & TriggerHappy for helping me along the way