Name | Type | is_array | initial_value |
Real | real | No |
//TESH.scrollpos=181
//TESH.alwaysfold=0
////////////////////////////////////////////////////\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\
// My first own spell in vJass \\
// by: Dr. Boom \\
// Special thanks to: \\
// watermelon_1234: Without him I never would use vJass. He teach me nearly all of it!!! \\
// Inferior : He teach me a lot of things with vJass too!! \\
// Bribe: He helped me to get into vJass - Thanks! \\
// Vexorian: Thanks for vJass ofc^^ and for TimerUtils \\
// How to use this spell:
// 1) Copy this trigger
// 2) Open your map, create a new trigger with the name "Deathball" and paste the trigger into it
// 3) Go back to this map and copy%past the Ability "Deathball" and the unit "D_WaveBall"
// 4) Make sure you also c%p the trigger "TimerUtils" which is needed for this spell!!
////////////////////////////////////////////////////\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\
library Deathball initializer Init requires TimerUtils
globals
private constant integer SPELL_ID = 'A003' // The RawCode of the casted spell
private constant integer DUMMY_BALL_ID = 'dum1' // The RawCode of the dummy (the ball)
private constant string AOE_SFX = "Objects\\Spawnmodels\\Undead\\UndeadDissipate\\UndeadDissipate.mdl" // The Area of Effect SFX
private constant string TARGET_SFX = "Abilities\\Spells\\Undead\\AnimateDead\\AnimateDeadTarget.mdl"// The SFX for the targets, that get damage
private constant attacktype A_TYPE = ATTACK_TYPE_NORMAL // The attacktype ( is used to deal damage to the targets )
private constant damagetype D_TYPE = DAMAGE_TYPE_NORMAL // The damagetype ( is used to deal damage to the targets )
private constant weapontype W_TYPE = WEAPON_TYPE_WHOKNOWS // The weapontype ( is used to deal damaghe to the targets)
private constant real WAVE_RELEASE = 30. // How often the AOE_SFX is spawned and the targets get damage
private constant boolean DESTROY_TREE = true // If true, trees in range of the ball will be destroyed
private constant real DESTROY_TREE_RANGE = 100. // If DESTROY_TREE, this is the range
private constant real AOE_SFX_NUMBER = 10. // Change this value, to chance the number of AOE_SFX,that are spawned
private constant real BALL_SPEED = 17. // The speed of the ball ( the ball moves 17 every 0.04 )
private constant real SPELL_DURATION = 10. // The duration of the full spell
endglobals
globals // Don't change something here
private real array DAMAGE_LIFE // This is for the normal damage
private real array DAMAGE_MANA // This is for the drained mana
private real array DAMAGE_AREA // The damage area
endglobals
// Here you can set the damage, the drained mana and the area of effect
// The current spell has 4 levels.
// If you do this spell with only two levels, you can delete any array variable above ([3] + [4]
// If you do this spell with ten levels, you must add the arrays here ( [5]to[10] )
// Make sure you use the correct variable names, without any typo.
private function Setup takes nothing returns nothing // In the Init trigger must be: call Setup()
set DAMAGE_LIFE[1] = 50.
set DAMAGE_LIFE[2] = 100.
set DAMAGE_LIFE[3] = 150.
set DAMAGE_LIFE[4] = 200.
set DAMAGE_MANA[1] = 25.
set DAMAGE_MANA[2] = 50.
set DAMAGE_MANA[3] = 75.
set DAMAGE_MANA[4] = 100.
set DAMAGE_AREA[1] = 250.
set DAMAGE_AREA[2] = 275.
set DAMAGE_AREA[3] = 300.
set DAMAGE_AREA[4] = 325.
endfunction
/////////////////////////////////////////\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\
// The configuration ends here. Don't change something under this line \\
////////////////////////////////////////\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\
globals
private constant real LOOP_TIME = 0.04 // The loop time (Like GUI: Every 0.04 seconds)
private constant group CONTAINER = CreateGroup() // The group where we filter the targets
private constant integer HARVEST_ORDER = 852018 // The order number of harvest ( to remove the trees )
private integer TEMP_INSTANCE
private unit TREE_CHECKER
private rect ENUM_RECT
private real MIN_X
private real MAX_X
private real MIN_Y
private real MAX_Y
endglobals
private struct Deathball
unit caster = null // the caster
unit ball = null // the ball
real time = 0. // HOW LONG the spell should be
real x1 = 0. // x for the creation point of the ball and where the ball should move
real y1 = 0. // y for the creation point of the ball and where the ball should move
real x2 = 0. // x for the AOE_SFX around the ball
real y2 = 0. // y for the AOE_SFX around the ball
real face = 0. // facing of the caster, so the ball can move one line
real counter = 0. // The counter ( in the loop the counter increased by 1. If it match WAVE_RELEASE, resett to 0 ( for the damage and effect )
private static method areafilter takes nothing returns boolean
local real life
local real mana
local unit enum = GetFilterUnit()
local Deathball d = TEMP_INSTANCE
if GetWidgetLife(enum) > 0.405 and IsUnitEnemy(enum,GetOwningPlayer(d.caster)) and not IsUnitType(enum,UNIT_TYPE_MAGIC_IMMUNE) and not IsUnitType(enum,UNIT_TYPE_STRUCTURE) then
set life = DAMAGE_LIFE[GetUnitAbilityLevel(d.caster,SPELL_ID)]
set mana = DAMAGE_MANA[GetUnitAbilityLevel(d.caster,SPELL_ID)]
call UnitDamageTarget(d.caster,enum,life,true,false,A_TYPE,D_TYPE,W_TYPE)
call SetUnitState(enum,UNIT_STATE_MANA,(GetUnitState(enum,UNIT_STATE_MANA) - mana ))
call DestroyEffect(AddSpecialEffect(TARGET_SFX,GetUnitX(enum),GetUnitY(enum)))
endif
return false
endmethod
private static method IsDestructableTree takes destructable d returns boolean
local boolean b
if GetWidgetLife(d)>0.405 then
call PauseUnit(TREE_CHECKER,false)
set b = IssueTargetOrderById(TREE_CHECKER,HARVEST_ORDER,d)
call PauseUnit(TREE_CHECKER,true)
return b
endif
return false
endmethod
private static method TreeFilter takes nothing returns boolean
if IsDestructableTree(GetFilterDestructable()) then
call KillDestructable(GetFilterDestructable())
endif
return false
endmethod
private static method Loop takes nothing returns nothing
local timer t = GetExpiredTimer()
local Deathball d = GetTimerData(t)
local integer i = 0
local real effectdistance = DAMAGE_AREA[GetUnitAbilityLevel(d.caster,SPELL_ID)]
local real cosinus = Cos(d.face)
local real sinus = Sin(d.face)
local real cosinus2
local real sinus2
set TEMP_INSTANCE = d
set d.counter = d.counter + 1.
set d.x1 = d.x1 + BALL_SPEED * cosinus
set d.y1 = d.y1 + BALL_SPEED * sinus
call SetUnitX(d.ball,d.x1)
call SetUnitY(d.ball,d.y1)
if d.counter == WAVE_RELEASE then
set d.counter = 0.
call GroupEnumUnitsInRange(CONTAINER,GetUnitX(d.ball),GetUnitY(d.ball),DAMAGE_AREA[GetUnitAbilityLevel(d.caster,SPELL_ID)],Condition(function Deathball.areafilter))
loop
exitwhen i == AOE_SFX_NUMBER
set cosinus2 = Cos(2 * bj_PI / AOE_SFX_NUMBER * i)
set sinus2 = Sin(2 * bj_PI / AOE_SFX_NUMBER * i)
set d.x2 = d.x1 + (DAMAGE_AREA[GetUnitAbilityLevel(d.caster,SPELL_ID)] - 25.) * cosinus2
set d.y2 = d.y1 + (DAMAGE_AREA[GetUnitAbilityLevel(d.caster,SPELL_ID)] -25.) * sinus2
call DestroyEffect(AddSpecialEffect(AOE_SFX,d.x2,d.y2))
set i = i + 1
endloop
endif
static if DESTROY_TREE then
call MoveRectTo(ENUM_RECT,d.x1,d.y1)
call EnumDestructablesInRect(ENUM_RECT,Condition(function Deathball.TreeFilter),null)
endif
set d.time = d.time - LOOP_TIME
if d.time <= 0. or d.x1 < MIN_X or d.x1 > MAX_X or d.y1 < MIN_Y or d.y1 > MAX_Y then
call ReleaseTimer(t)
call d.destroy()
call RemoveUnit(d.ball)
set d.ball = null
endif
endmethod
static method create takes unit caster, real x, real y returns Deathball
local thistype d = thistype.allocate()
local timer t = NewTimer()
set d.caster = caster
set d.time = SPELL_DURATION
call SetTimerData(t,d)
call TimerStart(t,LOOP_TIME,true,function Deathball.Loop)
set d.x1 = GetUnitX(d.caster)
set d.y1 = GetUnitY(d.caster)
set d.face = Atan2(y-d.y1,x-d.x1)
set d.ball = CreateUnit(GetOwningPlayer(d.caster),DUMMY_BALL_ID,d.x1,d.y1,d.face)
return d
endmethod
endstruct
private function DeathballCast takes nothing returns boolean
if GetSpellAbilityId() == SPELL_ID then
call Deathball.create(GetTriggerUnit(),GetSpellTargetX(),GetSpellTargetY())
endif
return false
endfunction
private function Init takes nothing returns nothing
local trigger t = CreateTrigger()
local integer i = 0
loop
exitwhen i == bj_MAX_PLAYER_SLOTS
call TriggerRegisterPlayerUnitEvent(t,Player(i),EVENT_PLAYER_UNIT_SPELL_EFFECT,null)
set i = i + 1
endloop
call TriggerAddCondition(t,Condition(function DeathballCast))
call Setup()
call Preload(AOE_SFX)
call Preload(TARGET_SFX)
set TREE_CHECKER = CreateUnit(Player(15),'hpea',0.,0.,0.)
set ENUM_RECT = Rect(-DESTROY_TREE_RANGE,-DESTROY_TREE_RANGE,DESTROY_TREE_RANGE,DESTROY_TREE_RANGE)
call ShowUnit(TREE_CHECKER,false)
set MIN_X = GetRectMinX(bj_mapInitialPlayableArea)
set MAX_X = GetRectMaxX(bj_mapInitialPlayableArea)
set MIN_Y = GetRectMinY(bj_mapInitialPlayableArea)
set MAX_Y = GetRectMaxY(bj_mapInitialPlayableArea)
set t = null
endfunction
endlibrary
//TESH.scrollpos=0
//TESH.alwaysfold=0
library TimerUtils initializer init
//*********************************************************************
//* TimerUtils (red+blue+orange flavors for 1.24b+)
//* ----------
//*
//* To implement it , create a custom text trigger called TimerUtils
//* and paste the contents of this script there.
//*
//* To copy from a map to another, copy the trigger holding this
//* library to your map.
//*
//* (requires vJass) More scripts: htt://www.wc3c.net
//*
//* For your timer needs:
//* * Attaching
//* * Recycling (with double-free protection)
//*
//* set t=NewTimer() : Get a timer (alternative to CreateTimer)
//* ReleaseTimer(t) : Relese a timer (alt to DestroyTimer)
//* SetTimerData(t,2) : Attach value 2 to timer
//* GetTimerData(t) : Get the timer's value.
//* You can assume a timer's value is 0
//* after NewTimer.
//*
//* Multi-flavor:
//* Set USE_HASH_TABLE to true if you don't want to complicate your life.
//*
//* If you like speed and giberish try learning about the other flavors.
//*
//********************************************************************
//================================================================
globals
//How to tweak timer utils:
// USE_HASH_TABLE = true (new blue)
// * SAFEST
// * SLOWEST (though hash tables are kind of fast)
//
// USE_HASH_TABLE = false, USE_FLEXIBLE_OFFSET = true (orange)
// * kinda safe (except there is a limit in the number of timers)
// * ALMOST FAST
//
// USE_HASH_TABLE = false, USE_FLEXIBLE_OFFSET = false (red)
// * THE FASTEST (though is only faster than the previous method
// after using the optimizer on the map)
// * THE LEAST SAFE ( you may have to tweak OFSSET manually for it to
// work)
//
private constant boolean USE_HASH_TABLE = true
private constant boolean USE_FLEXIBLE_OFFSET = false
private constant integer OFFSET = 0x100000
private integer VOFFSET = OFFSET
//Timers to preload at map init:
private constant integer QUANTITY = 256
//Changing this to something big will allow you to keep recycling
// timers even when there are already AN INCREDIBLE AMOUNT of timers in
// the stack. But it will make things far slower so that's probably a bad idea...
private constant integer ARRAY_SIZE = 8190
endglobals
//==================================================================================================
globals
private integer array data[ARRAY_SIZE]
private hashtable ht
endglobals
//It is dependent on jasshelper's recent inlining optimization in order to perform correctly.
function SetTimerData takes timer t, integer value returns nothing
static if(USE_HASH_TABLE) then
// new blue
call SaveInteger(ht,0,GetHandleId(t), value)
elseif (USE_FLEXIBLE_OFFSET) then
// orange
static if (DEBUG_MODE) then
if(GetHandleId(t)-VOFFSET<0) then
call BJDebugMsg("SetTimerData: Wrong handle id, only use SetTimerData on timers created by NewTimer")
endif
endif
set data[GetHandleId(t)-VOFFSET]=value
else
// new red
static if (DEBUG_MODE) then
if(GetHandleId(t)-OFFSET<0) then
call BJDebugMsg("SetTimerData: Wrong handle id, only use SetTimerData on timers created by NewTimer")
endif
endif
set data[GetHandleId(t)-OFFSET]=value
endif
endfunction
function GetTimerData takes timer t returns integer
static if(USE_HASH_TABLE) then
// new blue
return LoadInteger(ht,0,GetHandleId(t) )
elseif (USE_FLEXIBLE_OFFSET) then
// orange
static if (DEBUG_MODE) then
if(GetHandleId(t)-VOFFSET<0) then
call BJDebugMsg("SetTimerData: Wrong handle id, only use SetTimerData on timers created by NewTimer")
endif
endif
return data[GetHandleId(t)-VOFFSET]
else
// new red
static if (DEBUG_MODE) then
if(GetHandleId(t)-OFFSET<0) then
call BJDebugMsg("SetTimerData: Wrong handle id, only use SetTimerData on timers created by NewTimer")
endif
endif
return data[GetHandleId(t)-OFFSET]
endif
endfunction
//==========================================================================================
globals
private timer array tT[ARRAY_SIZE]
private integer tN = 0
private constant integer HELD=0x28829022
//use a totally random number here, the more improbable someone uses it, the better.
endglobals
//==========================================================================================
function NewTimer takes nothing returns timer
if (tN==0) then
//If this happens then the QUANTITY rule has already been broken, try to fix the
// issue, else fail.
debug call BJDebugMsg("NewTimer: Warning, Exceeding TimerUtils_QUANTITY, make sure all timers are getting recycled correctly")
static if( not USE_HASH_TABLE) then
debug call BJDebugMsg("In case of errors, please increase it accordingly, or set TimerUtils_USE_HASH_TABLE to true")
set tT[0]=CreateTimer()
static if( USE_FLEXIBLE_OFFSET) then
if (GetHandleId(tT[0])-VOFFSET<0) or (GetHandleId(tT[0])-VOFFSET>=ARRAY_SIZE) then
//all right, couldn't fix it
call BJDebugMsg("NewTimer: Unable to allocate a timer, you should probably set TimerUtils_USE_HASH_TABLE to true or fix timer leaks.")
return null
endif
else
if (GetHandleId(tT[0])-OFFSET<0) or (GetHandleId(tT[0])-OFFSET>=ARRAY_SIZE) then
//all right, couldn't fix it
call BJDebugMsg("NewTimer: Unable to allocate a timer, you should probably set TimerUtils_USE_HASH_TABLE to true or fix timer leaks.")
return null
endif
endif
endif
else
set tN=tN-1
endif
call SetTimerData(tT[tN],0)
return tT[tN]
endfunction
//==========================================================================================
function ReleaseTimer takes timer t returns nothing
if(t==null) then
debug call BJDebugMsg("Warning: attempt to release a null timer")
return
endif
if (tN==ARRAY_SIZE) then
debug call BJDebugMsg("Warning: Timer stack is full, destroying timer!!")
//stack is full, the map already has much more troubles than the chance of bug
call DestroyTimer(t)
else
call PauseTimer(t)
if(GetTimerData(t)==HELD) then
debug call BJDebugMsg("Warning: ReleaseTimer: Double free!")
return
endif
call SetTimerData(t,HELD)
set tT[tN]=t
set tN=tN+1
endif
endfunction
private function init takes nothing returns nothing
local integer i=0
local integer o=-1
local boolean oops = false
static if( USE_HASH_TABLE ) then
set ht = InitHashtable()
loop
exitwhen(i==QUANTITY)
set tT[i]=CreateTimer()
call SetTimerData(tT[i], HELD)
set i=i+1
endloop
set tN = QUANTITY
else
loop
set i=0
loop
exitwhen (i==QUANTITY)
set tT[i] = CreateTimer()
if(i==0) then
set VOFFSET = GetHandleId(tT[i])
static if(USE_FLEXIBLE_OFFSET) then
set o=VOFFSET
else
set o=OFFSET
endif
endif
if (GetHandleId(tT[i])-o>=ARRAY_SIZE) then
exitwhen true
endif
if (GetHandleId(tT[i])-o>=0) then
set i=i+1
endif
endloop
set tN = i
exitwhen(tN == QUANTITY)
set oops = true
exitwhen not USE_FLEXIBLE_OFFSET
debug call BJDebugMsg("TimerUtils_init: Failed a initialization attempt, will try again")
endloop
if(oops) then
static if ( USE_FLEXIBLE_OFFSET) then
debug call BJDebugMsg("The problem has been fixed.")
//If this message doesn't appear then there is so much
//handle id fragmentation that it was impossible to preload
//so many timers and the thread crashed! Therefore this
//debug message is useful.
elseif(DEBUG_MODE) then
call BJDebugMsg("There were problems and the new timer limit is "+I2S(i))
call BJDebugMsg("This is a rare ocurrence, if the timer limit is too low:")
call BJDebugMsg("a) Change USE_FLEXIBLE_OFFSET to true (reduces performance a little)")
call BJDebugMsg("b) or try changing OFFSET to "+I2S(VOFFSET) )
endif
endif
endif
endfunction
endlibrary