Listen to a special audio message from Bill Roper to the Hive Workshop community (Bill is a former Vice President of Blizzard Entertainment, Producer, Designer, Musician, Voice Actor) 🔗Click here to hear his message!
Today I upload a spell that uses my FearSystem.
This spell is Terror Rising (thanks to kakuzu for the name !).
The description of the spell : The caster create a electricity bolt that travels 425/600/775 units and every 175 units the bolt launch a explosion that deal 25/50/75 damages to enemy units and fear those units during 1.2/1.4/1.6 second.
The fear and the damage stacks if the unit take many explosion of the bolt meaning that a unit taking two explosions will be feared during (1.2-0.5)+1.2 = 1.9 second and will take 25+25 = 50 damages for the level 1 of the spell.
Note : There is a -0.5 because there is an explosion every 0.5 second.
This system requires the FearSystem, it is not optional.
Here is the code of the spell :
JASS:
library TerrorRising requires FearSystem/*By me*/, BoundSentinel //By Vex
/*
FearSystem : http://www.hiveworkshop.com/forums/spells-569/fear-system-v-2-7-a-243755/
BoundSentinel : http://www.wc3c.net/showthread.php?t=102576
*/
//CONFIGURATION
globals
private constant real FPS = 0.0312500
//The distance between two explosions.
private constant real BETWEEN_UNITS = 175.
//The time between two explosions.
private constant real TIME_BETWEEN = 0.5
//It means that the speed of the orb is BETWEEN_UNITS/TIME_BETWEEN
private constant integer SPELL_ID = 'U000'
private constant integer DUMMY_ID = 'd000'
private constant real AOE = 175.
private constant attacktype ATTACK_TYPE = ATTACK_TYPE_MAGIC
private constant damagetype DAMAGE_TYPE = DAMAGE_TYPE_MAGIC
//The path of the special effect attached to fear status
private constant string FEAR_PATH = "Abilities\\Spells\\Undead\\Curse\\CurseTarget.mdl"
private constant string FEAR_ATTACH = "overhead"
//The path of the effect that will spawn on the units when it takes damage
private constant string UNIT_PATH = "Abilities\\Spells\\Human\\Feedback\\ArcaneTowerAttack.mdl"
private constant string UNIT_ATTACH = "origin"
private constant string EXPLOSION_PATH = "Abilities\\Weapons\\Bolt\\BoltImpact.mdl"
endglobals
private constant function Damage takes integer level returns real
return 25.*level
endfunction
private constant function Explosion_Number takes integer level returns integer
return 2+level
endfunction
private constant function Time_Fear takes integer level returns real
return 1. + 0.2*level
endfunction
private function Unit_Filter takes player source, unit targ returns boolean
return (not IsUnitType(targ, UNIT_TYPE_MAGIC_IMMUNE)) and (UnitAlive(targ)) and IsUnitEnemy(targ, source)
endfunction
//END CONFIGURATION -> DONUT TOUCH ANYTHING BELOW
private struct Explosion extends array
unit u
unit caster
player owner
integer steps
real temp
real fear
real X
real Y
real dmg
thistype prev
thistype next
static integer count
static timer period
static group g
method destroy takes nothing returns nothing
if this.next != 0 then
set this.next.prev = this.prev
endif
set this.prev.next = this.next
set this.prev = thistype(0).prev
set thistype(0).prev = this
if thistype(0).next == 0 then
call PauseTimer(period)
endif
call UnitApplyTimedLife(u, 'BTLF', 0.01)
set this.u = null
set this.caster = null
set this.owner = null
endmethod
static method periodic takes nothing returns nothing
local Fear F
local thistype this = thistype(0).next
local unit v
local real x
local real y
loop
exitwhen this == 0
set this.temp = this.temp - FPS
set x = GetUnitX(this.u) + this.X
set y = GetUnitY(this.u) + this.Y
call SetUnitX(this.u, x)
call SetUnitY(this.u, y)
if this.temp <= 0 then
call DestroyEffect( AddSpecialEffect(EXPLOSION_PATH, x, y) )
call GroupEnumUnitsInRange(g, x, y, AOE, null)
loop
set v = FirstOfGroup(g)
exitwhen v==null
call GroupRemoveUnit(g, v)
if Unit_Filter(this.owner, v) then
call DestroyEffect( AddSpecialEffectTarget(UNIT_PATH,v,UNIT_ATTACH) )
if Fear.isFeared(v) then
set F = Fear.get(v)
set F.time = this.fear + F.time
else
set F = Fear.create()
set F.targ = v
set F.path = FEAR_PATH
set F.attach = FEAR_ATTACH
set F.time = this.fear
call F.start()
call F.destroy()
endif
call UnitDamageTarget(this.caster, v, this.dmg, true, false, ATTACK_TYPE, DAMAGE_TYPE, null)
endif
endloop
set this.steps = this.steps - 1
set this.temp = TIME_BETWEEN
endif
if this.steps == 0 then
call this.destroy()
endif
set this = this.next
endloop
endmethod
static method cond takes nothing returns boolean
local thistype this
local real angle
local real tx
local real ty
local integer i
if GetSpellAbilityId() == SPELL_ID then
//Allocate
if thistype(0).prev == 0 then
set count = count + 1
set this = count
else
set this = thistype(0).prev
set thistype(0).prev = thistype(0).prev.prev
endif
if thistype(0).next == 0 then
call TimerStart(period, FPS, true, function thistype.periodic)
else
set thistype(0).next.prev = this
endif
set this.next = thistype(0).next
set thistype(0).next = this
set this.prev = thistype(0)
//End Allocate
set this.caster = GetTriggerUnit()
set i = GetUnitAbilityLevel(this.caster,SPELL_ID)
set tx = GetSpellTargetX()
set ty = GetSpellTargetY()
set angle = Atan2(ty-GetUnitY(this.caster), tx-GetUnitX(this.caster))
set this.owner = GetTriggerPlayer()
set this.u = CreateUnit(this.owner, DUMMY_ID, tx, ty, angle*bj_RADTODEG)
set this.steps = Explosion_Number(i)
set this.temp = TIME_BETWEEN
set this.fear = Time_Fear(i)
set this.X = BETWEEN_UNITS*Cos(angle)*FPS
set this.Y = BETWEEN_UNITS*Sin(angle)*FPS
set this.dmg = Damage(i)
endif
return false
endmethod
static method onInit takes nothing returns nothing
local trigger t = CreateTrigger()
call TriggerRegisterAnyUnitEventBJ(t, EVENT_PLAYER_UNIT_SPELL_EFFECT)
call TriggerAddCondition(t, Condition(function thistype.cond))
call Preload(FEAR_PATH)
call Preload(EXPLOSION_PATH)
call Preload(UNIT_PATH)
set period = CreateTimer()
set count = 0
set g = CreateGroup()
set t = null
endmethod
endstruct
endlibrary
How to import :
- You will first need JNGP.
- Copy the three library inside the folder Requirements.
Note : You can use Table by Bribe or no Table at all too (even if I recommend to use one).
Actually I have trouble with Table by Bribe just use Table by Vex and nothing more if you can at the moment.
- Copy the trigger called TerrorRising
- Copy the abilities linked to the FearSystem called 'BEAR FORM FEAR' and 'MORPH FEAR' and 'DISABLE_ATTACK'. Then change the id inside the code of the FearSystem.
- Copy the dummy unit called 'DUMMY_EXPLOSION' and change the id inside the spell.
- Copy the ability FearExplosion. Then change the id inside the code of the spell.
- You're ready to go
Credits to :
- Vexorian -> Table, JassHelper, BoundSentinels
- Bribe -> Table
- Maker -> Help in FearSystem
- Chobibo -> Help in FearSystem
- Kakuzu -> Help for the name :3
Give me credits and to the other if you use
v1.0 :
- Initial Release
v1.1 :
- Implemented BoundSentinels
- Struct members optimized.
v1.2 :
- Trigger optimization.
v1.3 :
- Added the Unit_Filter function
- Trigger optimization.
v1.4 :
- I don't remember what I've done sorry
v1.5 :
- A good name finally !
Keywords:
Explosion, vJASS, JESP, Fear, Terror, Rising, Terror Rising, Malhorne
20:49, 3rd Jan 2014
BPower: Changes made, approved.
One minor thing: The dummy unit still uses upgrades.
(old)review: http://www.hiveworkshop.com/forums/spells-569/fearexplosion-v1-3-a-244427/index4.html#post2466426
set angle = Atan2(ty-GetUnitY(this.caster), tx-GetUnitX(this.caster)) instead of set x = GetUnitX(this.caster) and set y = GetUnitY(this.caster) since x and y are only used once.
Add some explanation in the global settings block, to make it more user friendly.
Especially for those two: private constant real BETWEEN_UNITS and private constant real TIME_BETWEEN
call SetUnitPosition(this.u, x + this.X, y + this.Y) could be SetUnitX/Y, but then you have to use something like WorldBounds
set p = GetOwningPlayer(this.caster) could be done in your cond method. --> this.owner = GetTriggerPlayer()
Which also allows you to set this.u = CreateUnit(GetOwningPlayer(this.caster),... --> set this.u = CreateUnit(this.owner, ...
GetWidgetLife(v)>0.405 --> not (IsUnitType(v, UNIT_TYPE_DEAD)) or native UnitAlive
null this.caster in the end
not IsUnitAlly(v, p) --> IsUnitEnemy(v, p) I don't know if here is a difference, it just looks better.
Optional:
You could use xe, Dummy or Missile for Dummy recycling. (Probably you won't have to call CreateUnit more than 10 times for this spell with one of those)
SpellEffectEvent
WorldBounds
CTL (You did a good job with your timer so w/e)
After all these 3 are pretty standart resources and having them does hurt noone.
private struct Explosion extends array
unit u
unit caster
integer steps
real temp
real fear
real X
real Y
real dmg
thistype prev
thistype next
static integer count
static timer period
static group g
into
JASS:
private struct Explosion extends array
private unit u
private unit caster
private integer steps
private real temp
private real fear
private real X
private real Y
private real dmg
private thistype prev
private thistype next
private static integer count
private static timer period
private static group g
JASS:
call SetUnitPosition(this.u, x + this.X, y + this.Y)
into
JASS:
call SetUnitX(this.u, x + this.X)
call SetUnitY(this.u, y + this.Y)
you are creating TriggerExecute, place destroy method above create
SetUnitX is always superior, and moreso for missiles, you dont need to stop orders or check pathing, you just need to move the missile
also calling SetUnitPosition has potential to lagg after several casts because of how it works
GetOwningPlayer(this.caster)could be stored in the struct.
GetWidgetLife(v)>0.405could be replaced with UnitAlive. Make sure to declare native UnitAlive takes unit id returns booleanin your script somewhere or else you will get errors. The reasons are minor (small performance, readability) to implement this, but I think it's worth it.
I really think you should be using a timer system, like TimerUtils if you plan on using dynamic timers instead of one. If you insist on using one timer, I know there are some single timer loops around. The reason being that people generally want one of two things in their maps when it comes to utilizing timers. One, timer recycling (dynamic timers), or two a single timer for their map. Your spell currently fits into neither Not a big deal, but something to consider.
BoundsSentinel different to WorldBounds. While WorldBounds only gives information about map bounds, BoundsSentinel moves units back in if they leave those. If you plan to use one timer per instance, I wouldn't use TimerUtils since your timer timeout is 0.03125000, therefore I would go with CTL.
It's not recommended to change this timeout to higher values anyways for instance 0.1, because it would look stupid.
BoundsSentinel different to WorldBounds. While WorldBounds only gives information about map bounds BoundsSentinel moves units back in if they leave those.
No I don't think I'll use a missile library since it is kinda simple in this spell.
I don't really think I have to use TimerUtils and don't know for CTL.
yes, test it yourself, force error just after endstruct(put a behind that) and check how the code is called.
I dont think its that big of a deal that he uses one Timer, its one timer, even with 0.00325 s period, is still better than using TimerUtils here, because if you start new instance every cast, and you spam it moderatelly, it will lagg out, because you will have dozens of Timers running.
CTL is not bad, but its quite complicated and will generate a looot of here useless code
I dont think its that big of a deal that he uses one Timer, its one timer, even with 0.00325 s period, is still better than using TimerUtils here, because if you start new instance every cast, and you spam it moderatelly, it will lagg out, because you will have dozens of Timers running.
Yeah, but that wasn't the reason why I told him to use TimerUtils, I thought that he should use an external library for the periodic movement, most probably an approved projectile library in the hive. The fear pulses are configurable so doing something like fearing every 4 seconds is better handled by a single timer per projectile.
Honestly nothing is as easy to use as CTL or the old Tt is. It does everything for you.
You just have to call this = create() and it handles the allocation itself.
This june i coded my very first spell and I used CTT (because I didn't know anything about dynamic indexing )
There is nothing wrong with locals, you just don't have to call GetUnitX/Y.
My way the information for x,y does already exist.
Honestly I can't argue about which is better, because I don't know it. Its' just how I would do it.
You can always test it. I agree though on caching the initial coordinates, it would make the projectile trajectory smoother since it will ignore any inconsistencies (I doubt it, since he's already switched to SetUnitXY) due to pathing correction. Cached data incremented per period with the offset delta is a good solution, I bow to you sir.
The Fear system is pretty cool and I love Fear.isFeared .
I didn't find anything wrong inside the code, but some things I would like to mention:
You don't have to create these members in onInit, just do it right away. static timer period = CreateTimer() and static group g = CreateGroup and static integer count = 0
As I said before you could use an x and y member for the missile instead of calling GetUnitX/Y each loop.
unit v will be nulled when the group is empty because of the FirstOfGroup. You don't have to do it yourselfset v = null
Imo the TargetFilter should be configurable. --> private function FilterUnits takes takes player, unit returns boolean instead of
if IsUnitEnemy(v, this.owner) and not IsUnitType(v, UNIT_TYPE_DEAD) then Also when using IsUnitType, you have to check GetUnitTypeId != 0 aswell.
Time_Fear the _ doesn't follow the JPAG, but actually I like it aswell.
This site uses cookies to help personalise content, tailor your experience and to keep you logged in if you register.
By continuing to use this site, you are consenting to our use of cookies.