- Joined
- Mar 29, 2012
- Messages
- 542
Holy shit Ofel, I can watch this all day, the effects are so good!
Lol, thanks. Me too.

Holy shit Ofel, I can watch this all day, the effects are so good!
WIP #2 (Concept)
![]()
That begin the summoning. =D
Ancient of Power Config
Events
Map initialization
Conditions
Actions
-------- --------------------------- --------
-------- =========================== --------
Set AoP_Ability = Ancient of Power
Set AoP_Dummy = Ancient of Power Dummy
Set AoP_AbilityOrder = (Order(forceofnature))
Set AoP_AbilityLevels = 3
-------- =========================== --------
Set AoP_SummoningDuration[1] = 10.00
Set AoP_SummoningDuration[2] = 10.00
Set AoP_SummoningDuration[3] = 10.00
Set AoP_SummoningPowerThreshold[1] = 2600.00
Set AoP_SummoningPowerThreshold[2] = 3400.00
Set AoP_SummoningPowerThreshold[3] = 4000.00
Set AoP_SummoningStartSFX[1] = war3mapImported\GreenRing.mdl
Set AoP_SummoningStartSFX[2] = war3mapImported\PoisonStream.mdl
Set AoP_SummoningStartSFX[3] = war3mapImported\Ragingslam.mdl
Set AoP_SummoningSFX = war3mapImported\HarvestLife.mdl
Set AoP_sSFXFinalSize[1] = 1.00
Set AoP_sSFXFinalSize[2] = 1.15
Set AoP_sSFXFinalSize[3] = 1.30
Set AoP_sSFXHeight = 0.00
Set AoP_sGroundSFX = Abilities\Spells\NightElf\EntanglingRoots\EntanglingRootsTarget.mdl
Set AoP_sGroundSFXInterval = 0.05
Set AoP_sGroundSFXStart = 100.00
Set AoP_sGroundSFXSpeed = 480.00
Set AoP_sGroundSFXDensity = 2.00
Set AoP_sGroundSFXArea[1] = 300.00
Set AoP_sGroundSFXArea[2] = 300.00
Set AoP_sGroundSFXArea[3] = 300.00
Set AoP_sEdgeSFX = Objects\Spawnmodels\NightElf\EntBirthTarget\EntBirthTarget.mdl
Set AoP_sEdgeSFXInterval = 0.08
Set AoP_sEdgeSFXStart[1] = 200.00
Set AoP_sEdgeSFXStart[2] = 200.00
Set AoP_sEdgeSFXStart[3] = 200.00
Set AoP_sEdgeSFXSpeed = 480.00
Set AoP_sEdgeSFXSpinSpeed = 160.00
Set AoP_sEdgeSFXDensity = 0.75
Set AoP_sEdgeSFXArea[1] = 500.00
Set AoP_sEdgeSFXArea[2] = 500.00
Set AoP_sEdgeSFXArea[3] = 500.00
Set AoP_sEdgeEndSFX = war3mapImported\GreenHeal.mdx
-------- =========================== --------
Set AoP_SummonedUnit[1] = Tree of Life
Set AoP_SummonedUnit[2] = Tree of Ages
Set AoP_SummonedUnit[3] = Tree of Eternity
Set AoP_suFacingAngle = 270.00
Set AoP_suBirthAnimation = Stand
-------- =========================== --------
Set AoP_AbsorbInterval = 1.00
Set AoP_LifeDrain = 30.00
Set AoP_ManaDrain = 15.00
Set AoP_LifePower = 1.00
Set AoP_ManaPower = 2.00
-------- =========================== --------
Set AoP_ManaOrbModel = war3mapImported\Power_Orb.mdl
Set AoP_mOrbSize = 0.50
Set AoP_mOrbSpawnHeight = 150.00
Set AoP_mOrbSpawnOffset = 100.00
Set AoP_mOrbTravelTime = 1.00
Set AoP_mOrbArc = 0.75
Set AoP_mOrbCancelSFX = war3mapImported\Unleash the power.mdx
Set AoP_mOrbCancelSFXSize = 1.00
Set AoP_mOrbCancelSFXDeath = 1.00
-------- =========================== --------
Custom script: call ExecuteFunc("AoP_Initialize")
JASS:constant function AoP_DummiesOwner takes nothing returns player return Player(PLAYER_NEUTRAL_PASSIVE) endfunction constant function AoP_FlyAbilityID takes nothing returns integer return 'Amrf' endfunction constant function AoP_FrameUpdate takes nothing returns real return 1 / 32. endfunction function AoP_IsUnitAlive takes unit u returns boolean return GetUnitTypeId(u) != 0 and not IsUnitType(u, UNIT_TYPE_DEAD) endfunction function AoP_IsUnitChanneling takes unit u returns boolean return AoP_IsUnitAlive(u) and GetUnitCurrentOrder(u) == udg_AoP_AbilityOrder endfunction function AoP_GetMagnitude2D takes real x, real y returns real return SquareRoot(x*x+y*y) endfunction function AoP_GetMagnitude3D takes real x, real y, real z returns real return SquareRoot(x*x+y*y+z*z) endfunction function AoP_GetAngle takes real x, real y returns real return Atan2(y, x) endfunction function AoP_GetAngleZ takes real distance2D, real z returns real return Atan2(z, distance2D) endfunction function AoP_GetParabolaHeight takes real y0, real y1, real h, real d, real x returns real local real A = (2*(y0+y1)-4*h)/(d*d) local real B = (y1-y0-A*d*d)/d return A*x*x + B*x + y0 endfunction function AoP_GetTerrainZ takes real x, real y returns real call MoveLocation(udg_AoP_ZLocator, x, y) return GetLocationZ(udg_AoP_ZLocator) endfunction function AoP_GetUnitZAlt takes unit u, real x, real y returns real return GetUnitFlyHeight(u) + AoP_GetTerrainZ(x, y) endfunction function AoP_UnitApplyFly takes unit u returns nothing if UnitAddAbility(u, AoP_FlyAbilityID()) and UnitRemoveAbility(u, AoP_FlyAbilityID()) then endif endfunction function AoP_SetUnitZAlt takes unit u, real z, real x, real y returns nothing call SetUnitFlyHeight(u, z - AoP_GetTerrainZ(x, y), 0) endfunction function AoP_SetUnitPosition takes unit u, real x, real y returns nothing call SetUnitX(u, x) call SetUnitY(u, y) endfunction function AoP_Destroy takes integer index returns nothing set udg_AoP_Recycle[udg_AoP_Recyclable] = index set udg_AoP_Recyclable = udg_AoP_Recyclable + 1 set udg_AoP_Next[udg_AoP_Prev[index]] = udg_AoP_Next[index] set udg_AoP_Prev[udg_AoP_Next[index]] = udg_AoP_Prev[index] if udg_AoP_Next[0] == 0 then call PauseTimer(udg_AoP_Timer) endif endfunction function AoP_Create takes nothing returns integer local integer newIndex if udg_AoP_Recyclable == 0 then set udg_AoP_MaxIndex = udg_AoP_MaxIndex + 1 set newIndex = udg_AoP_MaxIndex else set udg_AoP_Recyclable = udg_AoP_Recyclable - 1 set newIndex = udg_AoP_Recycle[udg_AoP_Recyclable] endif set udg_AoP_Next[newIndex] = 0 set udg_AoP_Next[udg_AoP_Prev[0]] = newIndex set udg_AoP_Prev[newIndex] = udg_AoP_Prev[0] set udg_AoP_Prev[0] = newIndex return newIndex endfunction function AoP_CreateParticle takes string model, real x, real y, real z, real size, real dur returns nothing local integer pIndex = AoP_Create() set udg_AoP_Particle[pIndex] = CreateUnit(AoP_DummiesOwner(), udg_AoP_Dummy, x, y, 0) call AoP_SetUnitPosition(udg_AoP_Particle[pIndex], x, y) call AoP_UnitApplyFly(udg_AoP_Particle[pIndex]) call AoP_SetUnitZAlt(udg_AoP_Particle[pIndex], z, x, y) call SetUnitScale(udg_AoP_Particle[pIndex], size, 0, 0) set udg_AoP_AttachedModel[pIndex] = AddSpecialEffectTarget(model, udg_AoP_Particle[pIndex], "origin") set udg_AoP_realTimer[pIndex] = dur set udg_AoP_Stage[pIndex] = 5 endfunction function AoP_CreateParticleTarget takes string model, unit u, real size, real dur returns nothing local real x = GetUnitX(u) local real y = GetUnitY(u) call AoP_CreateParticle(model, x, y, AoP_GetUnitZAlt(u, x, y), size, dur) endfunction function AoP_StartSummoningEffect takes real x, real y, integer lvl returns nothing local integer eIndex = AoP_Create() local integer i = 0 loop set i = i + 1 exitwhen udg_AoP_SummoningStartSFX[i] == null call DestroyEffect(AddSpecialEffect(udg_AoP_SummoningStartSFX[i], x, y)) endloop set udg_AoP_TargetX[eIndex] = x set udg_AoP_TargetY[eIndex] = y set udg_AoP_Level[eIndex] = lvl set udg_AoP_Dist[eIndex] = udg_AoP_sGroundSFXStart set udg_AoP_Dist2[eIndex] = udg_AoP_sEdgeSFXStart[udg_AoP_Level[eIndex]] set udg_AoP_Angle[eIndex] = GetRandomReal(-bj_PI, bj_PI) set udg_AoP_realTimer[eIndex] = 0 set udg_AoP_realTimer2[eIndex] = 0 set udg_AoP_Stage[eIndex] = 4 endfunction function AoP_Periodic takes nothing returns nothing local real x local real y local real z local integer i local integer index = 0 loop set index = udg_AoP_Next[index] exitwhen index == 0 if udg_AoP_Stage[index] == 1 then if AoP_IsUnitChanneling(udg_AoP_Caster[index]) then if udg_AoP_Dist2[index] < udg_AoP_Dist[index] then set udg_AoP_Dist2[index] = udg_AoP_Dist2[index] + udg_AoP_Speed[index] set udg_AoP_Dist[0] = udg_AoP_Dist2[index] - udg_AoP_Dist[index] // Opposite to angle set x = udg_AoP_TargetX[index] + udg_AoP_Dist[0] * Cos(udg_AoP_Angle[index]) set y = udg_AoP_TargetY[index] + udg_AoP_Dist[0] * Sin(udg_AoP_Angle[index]) set z = AoP_GetParabolaHeight(udg_AoP_SourceZ[index], udg_AoP_TargetZ[index], udg_AoP_MaxHeight[index], udg_AoP_Dist[index], udg_AoP_Dist2[index]) call SetUnitX(udg_AoP_Missile[index], x) call SetUnitY(udg_AoP_Missile[index], y) call AoP_SetUnitZAlt(udg_AoP_Missile[index], z, x, y) else call DestroyEffect(udg_AoP_AttachedModel[index]) call KillUnit(udg_AoP_Missile[index]) call AoP_StartSummoningEffect(udg_AoP_TargetX[index], udg_AoP_TargetY[index], udg_AoP_Level[index]) set udg_AoP_SummoningEffect[index] = CreateUnit(udg_AoP_Owner[index], udg_AoP_Dummy, udg_AoP_TargetX[index], udg_AoP_TargetY[index], 0) call AoP_SetUnitPosition(udg_AoP_SummoningEffect[index], udg_AoP_TargetX[index], udg_AoP_TargetY[index]) call AoP_UnitApplyFly(udg_AoP_SummoningEffect[index]) call SetUnitFlyHeight(udg_AoP_SummoningEffect[index], udg_AoP_sSFXHeight, 0) call SetUnitScale(udg_AoP_SummoningEffect[index], 0, 0, 0) set udg_AoP_AttachedModel[index] = AddSpecialEffectTarget(udg_AoP_SummoningSFX, udg_AoP_SummoningEffect[index], "origin") set udg_AoP_Dist[index] = 0 set udg_AoP_Stage[index] = 2 endif else // --- When caster stopped channeling call DestroyEffect(udg_AoP_AttachedModel[index]) call KillUnit(udg_AoP_Missile[index]) call AoP_CreateParticleTarget(udg_AoP_mOrbCancelSFX, udg_AoP_Missile[index], udg_AoP_mOrbCancelSFXSize, udg_AoP_mOrbCancelSFXDeath) call AoP_Destroy(index) endif elseif udg_AoP_Stage[index] == 2 then if AoP_IsUnitChanneling(udg_AoP_Caster[index]) then if udg_AoP_Dist[index] < udg_AoP_sSFXFinalSize[udg_AoP_Level[index]] then else endif else // --- When caster stopped channeling call AoP_Destroy(index) endif elseif udg_AoP_Stage[index] == 4 then if udg_AoP_Dist[index] < udg_AoP_sGroundSFXArea[udg_AoP_Level[index]] then set udg_AoP_Dist[index] = udg_AoP_Dist[index] + udg_AoP_sGroundSFXSpeed set udg_AoP_realTimer[index] = udg_AoP_realTimer[index] - AoP_FrameUpdate() if udg_AoP_realTimer[index] <= 0 then set udg_AoP_Angle[0] = GetRandomReal(-bj_PI, bj_PI) set i = udg_AoP_sGroundSFXCount loop exitwhen i <= 0 set udg_AoP_Angle[0] = udg_AoP_Angle[0] + udg_AoP_sGroundSFXGap set x = udg_AoP_TargetX[index] + udg_AoP_Dist[index] * Cos(udg_AoP_Angle[0]) set y = udg_AoP_TargetY[index] + udg_AoP_Dist[index] * Sin(udg_AoP_Angle[0]) call DestroyEffect(AddSpecialEffect(udg_AoP_sGroundSFX, x, y)) set i = i - 1 endloop set udg_AoP_realTimer[index] = udg_AoP_sGroundSFXInterval endif endif if udg_AoP_Dist2[index] < udg_AoP_sEdgeSFXArea[udg_AoP_Level[index]] then set udg_AoP_Dist2[index] = udg_AoP_Dist2[index] + udg_AoP_sEdgeSFXSpeed else set udg_AoP_Dist2[index] = udg_AoP_sEdgeSFXArea[udg_AoP_Level[index]] endif set udg_AoP_Angle[index] = udg_AoP_Angle[index] + udg_AoP_sEdgeSFXSpinSpeed set udg_AoP_realTimer2[index] = udg_AoP_realTimer2[index] - AoP_FrameUpdate() if udg_AoP_realTimer2[index] <= 0 then set udg_AoP_Angle[0] = udg_AoP_Angle[index] set i = udg_AoP_sEdgeSFXCount loop exitwhen i <= 0 set udg_AoP_Angle[0] = udg_AoP_Angle[0] + udg_AoP_sEdgeSFXGap set x = udg_AoP_TargetX[index] + udg_AoP_Dist2[index] * Cos(udg_AoP_Angle[0]) set y = udg_AoP_TargetY[index] + udg_AoP_Dist2[index] * Sin(udg_AoP_Angle[0]) call DestroyEffect(AddSpecialEffect(udg_AoP_sEdgeSFX, x, y)) if udg_AoP_Dist2[index] >= udg_AoP_sEdgeSFXArea[udg_AoP_Level[index]] then endif set i = i - 1 endloop set udg_AoP_realTimer2[index] = udg_AoP_sEdgeSFXInterval endif elseif udg_AoP_Stage[index] == 5 then set udg_AoP_realTimer[index] = udg_AoP_realTimer[index] - AoP_FrameUpdate() if udg_AoP_realTimer[index] <= 0 then call DestroyEffect(udg_AoP_AttachedModel[index]) call KillUnit(udg_AoP_Particle[index]) call AoP_Destroy(index) endif endif endloop endfunction function AoP_onEffect takes nothing returns boolean local real sx local real sy local real stx local real sty local integer cIndex if GetSpellAbilityId() == udg_AoP_Ability then set cIndex = AoP_Create() set udg_AoP_Caster[cIndex] = GetTriggerUnit() set udg_AoP_Owner[cIndex] = GetTriggerPlayer() set udg_AoP_Level[cIndex] = GetUnitAbilityLevel(udg_AoP_Caster[cIndex], udg_AoP_Ability) set sx = GetUnitX(udg_AoP_Caster[cIndex]) set sy = GetUnitY(udg_AoP_Caster[cIndex]) set udg_AoP_SourceZ[cIndex] = AoP_GetTerrainZ(sx, sy) + udg_AoP_mOrbSpawnHeight set udg_AoP_TargetX[cIndex] = GetSpellTargetX() set udg_AoP_TargetY[cIndex] = GetSpellTargetY() set udg_AoP_TargetZ[cIndex] = AoP_GetTerrainZ(udg_AoP_TargetX[cIndex], udg_AoP_TargetY[cIndex]) set stx = udg_AoP_TargetX[cIndex] - sx set sty = udg_AoP_TargetY[cIndex] - sy set udg_AoP_Angle[cIndex] = AoP_GetAngle(stx, sty) set udg_AoP_TargetX[0] = sx + udg_AoP_mOrbSpawnOffset * Cos(udg_AoP_Angle[cIndex]) set udg_AoP_TargetY[0] = sy + udg_AoP_mOrbSpawnOffset * Sin(udg_AoP_Angle[cIndex]) set udg_AoP_Dist[cIndex] = AoP_GetMagnitude2D(stx, sty) - udg_AoP_mOrbSpawnOffset set udg_AoP_Dist2[cIndex] = 0 set udg_AoP_Speed[cIndex] = udg_AoP_Dist[cIndex] / udg_AoP_mOrbTravelTime set udg_AoP_MaxHeight[cIndex] = udg_AoP_Dist[cIndex] * udg_AoP_mOrbArc + udg_AoP_TargetZ[cIndex] set udg_AoP_Missile[cIndex] = CreateUnit(AoP_DummiesOwner(), udg_AoP_Dummy, udg_AoP_TargetX[0], udg_AoP_TargetY[0], udg_AoP_Angle[cIndex]*bj_RADTODEG) call AoP_UnitApplyFly(udg_AoP_Missile[cIndex]) call AoP_SetUnitZAlt(udg_AoP_Missile[cIndex], udg_AoP_SourceZ[cIndex], udg_AoP_TargetX[0], udg_AoP_TargetY[0]) call SetUnitScale(udg_AoP_Missile[cIndex], udg_AoP_mOrbSize, 0, 0) set udg_AoP_AttachedModel[cIndex] = AddSpecialEffectTarget(udg_AoP_ManaOrbModel, udg_AoP_Missile[cIndex], "origin") set udg_AoP_Stage[cIndex] = 1 if udg_AoP_Prev[cIndex] == 0 then call TimerStart(udg_AoP_Timer, AoP_FrameUpdate(), true, function AoP_Periodic) endif endif return false endfunction function AoP_Initialize takes nothing returns nothing local integer i = 0 loop set i = i + 1 exitwhen i > udg_AoP_AbilityLevels endloop set udg_AoP_mOrbTravelTime = udg_AoP_mOrbTravelTime / AoP_FrameUpdate() set udg_AoP_sGroundSFXCount = R2I(4*udg_AoP_sGroundSFXDensity) set udg_AoP_sGroundSFXGap = 360 / udg_AoP_sGroundSFXCount * bj_DEGTORAD set udg_AoP_sGroundSFXSpeed = udg_AoP_sGroundSFXSpeed * AoP_FrameUpdate() set udg_AoP_sEdgeSFXCount = R2I(4*udg_AoP_sEdgeSFXDensity) set udg_AoP_sEdgeSFXGap = 360 / udg_AoP_sEdgeSFXCount * bj_DEGTORAD set udg_AoP_sEdgeSFXSpeed = udg_AoP_sEdgeSFXSpeed * AoP_FrameUpdate() set udg_AoP_sEdgeSFXSpinSpeed = udg_AoP_sEdgeSFXSpinSpeed * AoP_FrameUpdate() * bj_DEGTORAD if udg_AoP_ZLocator == null then set udg_AoP_ZLocator = Location(0, 0) endif endfunction //=========================================================================== function InitTrig_Ancient_of_Power takes nothing returns nothing set gg_trg_Ancient_of_Power = CreateTrigger() call TriggerRegisterAnyUnitEventBJ(gg_trg_Ancient_of_Power, EVENT_PLAYER_UNIT_SPELL_EFFECT) call TriggerAddCondition(gg_trg_Ancient_of_Power, Condition(function AoP_onEffect)) endfunction
I know dat feel yo. Will have a shitload of constants as well, lol.That's because the actual code.isn't even at 25%![]()
Still far from finish. I'm facing the most tough part where I never did before. Maybe my expectation is a little too big. -_-'
A bloodlust buff with a green vertex color?![]()
scope VoidUndertow initializer Init
/* *********************************************************************
* VOID UNTERTOW by xxdingo93xx
*
* created for HWS-ZephyrChallange #14
*
* Icon credits:
* - BlizzardEntertainment ([url]http://www.hiveworkshop.com/forums/icons-541/btnspell_shadow_summonvoidwalker-54578/?prev=search%3Dvoid%26d%3Dlist%26r%3D20[/url])
*
* Model credits:
* - PeeKay ([url]http://www.hiveworkshop.com/forums/models-530/peekays-punishmissle-model-202088/?prev=search%3Ddarkness%26r%3D20%26d%3Dlist%26page%3D2[/url])
* - alfredx_sotn ([url]http://www.hiveworkshop.com/forums/models-530/waterelemental-47544/?prev=search%3Dvoid%26d%3Dlist%26r%3D20[/url])
* - JesusHipster ([url]http://www.hiveworkshop.com/forums/models-530/guardian-wisp-228249/?prev=search%3Dwisp%26d%3Dlist%26r%3D20[/url])
* - Vortigon ([url]http://www.hiveworkshop.com/forums/models-530/dark-orb-257313/?prev=search%3Ddark%2520orb%26d%3Dlist%26r%3D20[/url])
* - ILH ([url]http://www.hiveworkshop.com/forums/models-530/twilight-sparkle-250103/?prev=d%3Dlist%26r%3D20%26t%3D8[/url])
* - Vexorian (for the dummy.mdx imported model)
*
* Required libraries:
* - TimerUtils (by Vexorian)
* - Table (by Bribe)
* - RegisterPlayerUnitEvent (by Magtheridon96)
*
* If you want to use this spell in your map, you will need to undergo a
* a few steps in order to make it work properly and customized to your
* wishes. Just follow the steps mentioned below, but keep in mind that I
* used a bunch of imported models for this spell. You might want to copy
* them, as well.
*
* Step 1: give credits if you use this ;-)
* Step 2: copy the necessary units into your map:
* - Netherworld Creature(Soul Undertow Level 1,2,3)
* - Dummy (don't forget to copy the imported dummy.mdl as well)
* - Dummy (rotated)
* Step 3: copy the ability "Void Undertow" into your map.
* Step 4: customize the globals to your likings:
*
***********************************************************************/
globals
// ========= RAWCODE SETTINGS =========
//Default: 'n000'
private constant integer DUMMYID = 'n000' //the rawcode of the regular dummy unit
//Default: 'n001'
private constant integer SOUL_ID = 'n001' //rawcode of the rotated dummy unit
//Default: 'VoUn'
private constant integer AID = 'VoUn' //the ability's rawcode
// ========= GENERAL SETTINGS =========
//Default: 1./32.
private constant real TICK = 1./32. //The periodic time to run a trigger
//Default: WEAPON_TYPE_WHOKNOWS
private constant weapontype WT = WEAPON_TYPE_WHOKNOWS //weapontype of damage dealt
//Default: ATTACK_TYPE_MAGIC
private constant attacktype AT = ATTACK_TYPE_MAGIC
//Default: DAMAGE_TYPE_UNKNOWN
private constant damagetype DT = DAMAGE_TYPE_UNKNOWN
////////////////////////////////////////////////
// ========= VISUAL SETTINGS =========
// I tried to keep the order of the visual
// settings just like the order of the spell's
// components, to make it easier to follow what
// you're doing.
////////////////////////////////////////////////
// *** CIRCULAR SPAWN ***
//
//Default: 400.
private constant real SPAWN_DISTANCE = 400.
//Default: 3
private constant integer SPAWN_PART_CNT = 3 //amount of particles being spawned
//Default: 7.
private constant real SPAWN_SPINSPEED = 7. //radian-mode, so keep this value -2*PI <= x <= 2*PI
//Default: 45.
private constant real SPAWN_START_ANGLE = 45. //degree-mode
//Default: 1.
private constant real SPAWN_DURATION = 1. //duration of this whole process. Will determine the movespeed
//Default: "Abilities\\Spells\\NightElf\\FaerieDragonInvis\\FaerieDragon_Invis.mdl"
private constant string SPAWN_MODEL = "Abilities\\Spells\\NightElf\\FaerieDragonInvis\\FaerieDragon_Invis.mdl" //"Abilities\\Spells\\NightElf\\FaerieDragonInvis\\FaerieDragon_Invis.mdl" //the particles modelpath
//Default: "Abilities\\Spells\\Demon\\DarkPortal\\DarkPortalTarget.mdl"
private constant string SPAWN_COL_MODEL = "war3mapImported\\TwilightSparkle.mdx"//"Abilities\\Spells\\Demon\\DarkPortal\\DarkPortalTarget.mdl" //the collision modelpath
//Default: 300.
private constant real SPAWN_START_Z = 300. //starting height, will sink to SPAWN_END_Z over time
//Default: 50.
private constant real SPAWN_END_Z = 50.
// *** UNIT IMAGE ***
//
//Default: "Abilities\\Spells\\Undead\\RegenerationAura\\ObsidianRegenAura.mdl"
private constant string IMAGE_MODELPATH = "Abilities\\Spells\\Undead\\RegenerationAura\\ObsidianRegenAura.mdl" //unit image
//Default: 1.
private constant real IMAGE_GROWTH_DURA = 1. //duration of the effect
//Default: 0.
private constant real IMAGE_HEIGHT = 0. //the effect's height
//Default: 3.
private constant real IMAGE_MAX_SIZE = 2. //the scaling value the effect will grow up to overtime
// *** VOID ORB ***
//
//Default: "war3mapImported\\DarkOrb.mdl"
private constant string VOIDORB_MODEL = "war3mapImported\\DarkOrb.mdl" //ID of the dummy unit representing the voidorb
//Default: 1.
private constant real VOIDORB_SIZE = 0.1 //scaling value of orb. WARNING:
//if you use the default for VOIDORB_MODEL, increasing the size won't
//make the orb bigger but it will tremble instead. Kinda interesting,
//but it's up to you if go for it
//Default: 4.
private constant real VOIDORB_SPEED = 4. //orb's speed
//Default: 1.05
private constant real VOIDORB_ACC = 1.02 //orb's accumulation. Value will be multiplied with the voidorb_speed every TICK
//Default 60.
private constant real VOIDORB_COL = 60. //collision range between caster and orb
//Default: 50.
private constant real VOIDORB_END_Z = 50. //orb's height. Also DrainOrb's.
//Default: 2.
private constant real VOIDORB_DELAY = 0.4 // delay until the orb starts moving
// *** DRAIN ORB ***
//
//Default: "war3mapImported\\Wisp.mdl"
private constant string DRAINORB_MODEL = "war3mapImported\\Wisp.mdl" //
//Default: 0.8
private constant real DRAINORB_SIZE = 0.8
//Default: 255
private constant integer DRAINORB_RED = 255 //RGB value
//Default: 255
private constant integer DRAINORB_BLUE = 255 //RGB value
//Default: 0
private constant integer DRAINORB_GREEN = 0 //RGB value
//Default: 255
private constant integer DRAINORB_ALPHA = 255 //transparency
//Default: 28.
private constant real DRAINORB_MAX_SPEED = 24. //the maximum movespeed...
//Default: 26.
private constant real DRAINORB_MIN_SPEED = 28. //...and minimum movespeed, so the movespeed will vary a little.
//Default: 1.
private constant real DRAINORB_ACC = 1. //accumulation for drainorbs
//Default: 60.
private constant real DRAINORB_COL = 60. //collision range for drainorb <-> voidorb
//Default 50.
private constant real DRAINORB_Z = 50.
//Default: 16.
private constant real DRAINORB_ZOFFSET = 16. //fixing value to spawn the orb in the
//center of the target's body
// *** LOCKED SUMMON ***
//
//Default: 60.
private constant real SUMMON_BACK_RNG = 60. //the distance the summoned unit is behind the summoner
//Default: 60.
private constant real SUMMON_BACK_HGH = 60. //the flying height for the summoned unit
//Default: "birth"
private constant string SUMMON_ANI = "birth" //the summoned unit's animation on summoning
//Default: 1.
private constant real SUMMON_ANI_RESET = 1. //after this amount of seconds the unit's animation will be reset
//Default: "war3mapImported\\VoidWalker.mdl"
private constant string SUMMON_MODELPATH = "war3mapImported\\VoidWalker.mdl"//for the unit-image effect spawned on the actual summoned unit.
//Default: 4
private constant real SUMMON_MAX_SIZE = 4. //size of unit-image effect on summoned unit
//Default: 1
private constant real SUMMON_GROWTH_DURA = 1.
// *** SOUL LIFETIME ***
//
//Default: 6
private constant integer SOUL_AMT = 5 //amount of shards being spawned over the summoned unit
//Default: 1.5
private constant real SOUL_MAX_SCALE = 1.5//starting scaling value for the soul
//Default: 0.5
private constant real SOUL_MIN_SCALE = 0.5 //threshold for shrinking value.
//Once the scaling value has fallen below this threhsold, the soul vanishes.
//If you don't want any shrinking-effect whatsoever, give it the same value
//that SOUL_MAX_SCALE has.
//Default: '75.
private constant real SOUL_RADIUS = 100. //the radius determining the distance between souls and summoned unit
//Default: 16.
private constant real SOUL_XOFFSET = 16. //fixing value, since the souls apparently aren't spawned
//in the very center X-value of the summoned unit
//I suggest you leave this untouched, unless you know what you're doing
//Default: 75.
private constant real SOUL_HEIGHT = 100. //the base height for the souls
//Default: 135.
private constant real SOUL_SLOPE = 135.//the souls are spawned on a rotated plane in the XY-axis. This determines its degree
////////////////////////////////////////////////
// ========= GAMEPLAY SETTINGS =========
// In this part you must determine gameplay-
// specific values.
////////////////////////////////////////////////
//Default: 3
private constant integer ABILITY_LEVELS = 3 //The amount of levels the spell can have
//check the function Settings down below
//to change spell-specific values.
private integer array SUMMON_ID[ABILITY_LEVELS]
private real array DRAINORB_RNG[ABILITY_LEVELS]
private real array DRAINORB_DMG[ABILITY_LEVELS]
private real array AVG_DMG_PER_UNIT[ABILITY_LEVELS]
private real array SUMMON_LIFE_FAC[ABILITY_LEVELS]
private real array SUMMON_HP_THRESHOLD[ABILITY_LEVELS]
private real array SUMMON_ATK_RANGE[ABILITY_LEVELS]
endglobals
private function Settings takes nothing returns nothing
//the id of the summoned unit, just
//in case you want a stronger or another unit to be summoned each level.
set SUMMON_ID[0] = 'nVO1'//Level 1
set SUMMON_ID[1] = 'nVO2'//Level 2
set SUMMON_ID[2] = 'nVO3'//Level 3
//must be the same attack range the corresponding summoned unit has.
set SUMMON_ATK_RANGE[0] = 500.
set SUMMON_ATK_RANGE[1] = 500.
set SUMMON_ATK_RANGE[2] = 500.
//the hitpoints one drainorb will steal
set DRAINORB_DMG[0] = 15. //Level 1...
set DRAINORB_DMG[1] = 15. //...you get the idea.
set DRAINORB_DMG[2] = 15.
//the average damage dealt to a unit by drainorbs
//remember: it's AVERAGE, so it can vary around this value
set AVG_DMG_PER_UNIT[0] = 50.
set AVG_DMG_PER_UNIT[1] = 60.
set AVG_DMG_PER_UNIT[2] = 70.
//the detection range for the voidorb to spawn drainorbs
set DRAINORB_RNG[0] = 400.
set DRAINORB_RNG[1] = 450.
set DRAINORB_RNG[2] = 500.
// for every SUMMON_LIFE_FAC[i] drained hitpoints add 1 second lifetime
set SUMMON_LIFE_FAC[0] = 20
set SUMMON_LIFE_FAC[1] = 15
set SUMMON_LIFE_FAC[2] = 10
//at least SUMMON_HP_THRESHOLD[i] hitpoints need to be drained in order to summon
set SUMMON_HP_THRESHOLD[0] = 30.
set SUMMON_HP_THRESHOLD[1] = 40.
set SUMMON_HP_THRESHOLD[2] = 50.
endfunction
//===========================================================================
//===========================================================================
//===========================================================================
globals
private group TempGroup = CreateGroup()
private location TempLoc = Location(0.,0.)
private unit TempUnit = null
//we use a table so that we can check whether
//the casting unit has summoned a creature already.
private Table LockedSummonTab
private Table VoidOrbTab
endglobals
native UnitAlive takes unit u returns boolean
//drainorb-targets must be: alive, non-structure, non-magic-immune, non-mechanical, enemied
private function Enum takes nothing returns boolean
local boolean result = UnitAlive(GetFilterUnit()) and not IsUnitType(GetFilterUnit(), UNIT_TYPE_STRUCTURE)
set result = result and not IsUnitType(GetFilterUnit(), UNIT_TYPE_MAGIC_IMMUNE) and not IsUnitType(GetFilterUnit(), UNIT_TYPE_MECHANICAL)
set result = result and IsUnitEnemy(GetFilterUnit(), GetOwningPlayer(TempUnit))
return result
endfunction
private function A2PXY takes real x,real y,real xt,real yt returns real
local real angle = bj_RADTODEG*Atan2(yt-y,xt-x)
local real modulus = angle - I2R(R2I(angle / 360.)) * 360.
if modulus < 0 then
set modulus = modulus + 360.
endif
return modulus
endfunction
private function D2PXY takes real x1, real y1, real x2, real y2 returns real
return SquareRoot(((x2 - x1) * (x2 - x1)) + ((y2 - y1) * (y2 - y1)))
endfunction
private function UnitAllowFly takes unit u returns boolean
return UnitAddAbility(u, 'Amrf') and UnitRemoveAbility(u, 'Amrf')
endfunction
//Rounding is actually important, because I'm using precise values here and there
private function RoundInteger takes real r returns integer
local integer temp = R2I(r)
local integer temp2 = R2I(r + 0.5)
if temp == temp2 then
return temp
else
return temp2
endif
endfunction
private keyword VoidOrb
private keyword LockedSummon
private keyword UnitImage
private keyword DrainOrb
//USE OF STRUCT:
//spawns an amount of particles around the target point,
//coming closer in a rotating motion until they collide.
//This is phase 1 (of 3) for the spell.
private struct CircularSpawn
unit caster
unit array dummies[SPAWN_PART_CNT]
real angle
real centerX
real centerY
real z
real distance
real distanceStep
timer t
effect array spx[SPAWN_PART_CNT]
private static real zStep
private static method onTick takes nothing returns nothing
local thistype data = thistype(GetTimerData(GetExpiredTimer()))
local integer i = 0
local real x
local real y
local real angle
if data.distance > 1. then
loop
exitwhen i >= SPAWN_PART_CNT
set angle = data.angle + SPAWN_START_ANGLE + i * (360. / (SPAWN_PART_CNT))
set x = data.centerX + data.distance * Cos(angle * bj_DEGTORAD)
set y = data.centerY + data.distance * Sin(angle * bj_DEGTORAD)
set data.z = data.z + zStep
call SetUnitX(data.dummies[i], x)
call SetUnitY(data.dummies[i], y)
call SetUnitFlyHeight(data.dummies[i], data.z, 0.)
set i = i + 1
endloop
set data.distance = data.distance - data.distanceStep
set data.angle = data.angle + SPAWN_SPINSPEED
else
//starting phase 2: creating a voidorb!
call VoidOrb.create(data.caster, data.centerX, data.centerY)
//the collision effect will be spawned at this point
call DestroyEffect(AddSpecialEffect(SPAWN_COL_MODEL, data.centerX, data.centerY))
call data.destroy()
endif
endmethod
public static method create takes unit caster, real x, real y, real angle returns thistype
local thistype data = thistype.allocate()
local integer i = 0
set data.centerX = x
set data.centerY = y
set data.z = SPAWN_START_Z
set data.caster = caster
set data.angle = angle
set data.distance = SPAWN_DISTANCE
set data.distanceStep = (SPAWN_DISTANCE * TICK) / SPAWN_DURATION
loop
exitwhen i >= SPAWN_PART_CNT
set angle = data.angle + SPAWN_START_ANGLE + i * (360. / (SPAWN_PART_CNT))
set x = data.centerX + data.distance * Cos(angle * bj_DEGTORAD)
set y = data.centerY + data.distance * Sin(angle * bj_DEGTORAD)
set data.dummies[i] = CreateUnit(GetOwningPlayer(caster), DUMMYID, x, y, 0.)
call UnitAllowFly(data.dummies[i])
set data.spx[i] = AddSpecialEffectTarget(SPAWN_MODEL, data.dummies[i], "origin")
call SetUnitFlyHeight(data.dummies[i], data.z, 0.)
set i = i + 1
endloop
set data.t = NewTimer()
call SetTimerData(data.t, integer(data))
call TimerStart(data.t, TICK, true, function thistype.onTick)
return data
endmethod
public method onDestroy takes nothing returns nothing
local integer i = 0
call ReleaseTimer(.t)
loop
exitwhen i >= SPAWN_PART_CNT
call UnitApplyTimedLife(this.dummies[i], 'BTLK' ,0.01)
call DestroyEffect(this.spx[i])
set this.dummies[i] = null
set this.spx[i] = null
set i = i + 1
endloop
set this.caster = null
endmethod
private static method onInit takes nothing returns nothing
set zStep = (SPAWN_END_Z - SPAWN_START_Z)*TICK / SPAWN_DURATION
endmethod
endstruct
//USE OF struct:
//will be used as parent class,
//as we will have 2 different types
//of homing missiles. So let's just sum
//up the whole moving part into a
//separate struct and make the special
//missiles inherit from this one.
private struct HomingMissile
real x
real y
real z
real minZ
real collisionRange
unit target
unit missile
real speed
real speedAcc
boolean timeout
timer t
effect spx
private method onCollision takes nothing returns nothing
set this.timeout = true
endmethod
static method onMove takes nothing returns nothing
local thistype data = thistype(GetTimerData(GetExpiredTimer()))
local real angle = A2PXY(GetUnitX(data.missile), GetUnitY(data.missile), GetUnitX(data.target), GetUnitY(data.target))
local real distance = D2PXY(GetUnitX(data.missile), GetUnitY(data.missile), GetUnitX(data.target), GetUnitY(data.target))
local unit u = null
local real zStep
if (distance > data.collisionRange) then
set data.speed = data.speed * data.speedAcc
set data.x = GetUnitX(data.missile) + data.speed * Cos(angle * bj_DEGTORAD)
set data.y = GetUnitY(data.missile) + data.speed * Sin(angle * bj_DEGTORAD)
//we limit the height downwards, so the missile must
//have at least a height of minZ.
if data.z > data.minZ then
set zStep = (data.z-GetUnitFlyHeight(data.target)) / (distance / data.speed)
set data.z = data.z - zStep
else
set data.z = data.minZ
endif
call SetUnitX(data.missile, data.x)
call SetUnitY(data.missile, data.y)
call SetUnitFlyHeight(data.missile, data.z, 0.)
call SetUnitFacing(data.missile, angle)
else
call data.onCollision()
endif
endmethod
private static method onDelay takes nothing returns nothing
local thistype data = thistype(GetTimerData(GetExpiredTimer()))
call TimerStart(data.t, TICK, true, function thistype.onMove)
endmethod
public static method create takes real startX, real startY, real startZ, real minZ, real zOffset, unit target, real speed, real speedAcc, real collisionRange, string missileModel, real scalingValue, real delay returns thistype
local thistype data = thistype.allocate()
local real angle = A2PXY(startX, startY, GetUnitX(target), GetUnitY(target))
set data.x = startX
set data.y = startY
set data.z = startZ + zOffset
set data.minZ = minZ //must have at least this height
set data.target = target
set data.speed = speed
set data.speedAcc = speedAcc
set data.collisionRange = collisionRange
set data.timeout = FALSE
set data.missile = CreateUnit(GetOwningPlayer(target), DUMMYID, startX, startY, angle)
set data.spx = AddSpecialEffectTarget(missileModel, data.missile, "origin")
call UnitAllowFly(data.missile)
call SetUnitScale(data.missile, scalingValue,scalingValue,scalingValue)
call SetUnitFlyHeight(data.missile, startZ, 0.)
set data.t = NewTimer()
call TimerStart(data.t, delay, false, function thistype.onDelay)
call SetTimerData(data.t, integer(data))
return data
endmethod
public method onDestroy takes nothing returns nothing
call ReleaseTimer(this.t)
call UnitApplyTimedLife(this.missile, 'BTLK', 0.5)
call DestroyEffect(this.spx)
set this.missile = null
set this.target = null
set this.spx = null
endmethod
endstruct
//USE OF STRUCT:
//spawns an orb which will float back to the target unit
//(so basically a homing missile). On it's way, it will
//spawn several DrainOrbs on nearby enemies.
//Phase 2 (of 3) for the spell.
private struct VoidOrb extends HomingMissile
readonly unit caster
readonly integer level //current ability lvl
integer dosc //DrainOrb spawn chance
real drainedLife //the sum of all drained life so far
timer VOtimer //separate timer for this special HomingMissile's matter
public method addDrainedLife takes real value returns nothing
set this.drainedLife = this.drainedLife + value
endmethod
private method onCollision takes nothing returns nothing
local LockedSummon temp
local real lifetime
local integer index = GetHandleId(this.caster)
//at least the threshold-amount of hitpoints must have been drained
//to summon a netherworld creature
if this.drainedLife >= SUMMON_HP_THRESHOLD[this.level] then
set temp = LockedSummon(VoidOrbTab[index])
//if the caster hasn't got a sidekick already...
if (temp == 0) then
//...create him a new one with the calculated amount of lifetime
set VoidOrbTab[index] = integer(LockedSummon.create(this.caster, this.drainedLife / SUMMON_LIFE_FAC[this.level]))
call UnitImage.create(this.caster, IMAGE_MODELPATH, IMAGE_GROWTH_DURA, IMAGE_MAX_SIZE, IMAGE_HEIGHT)
call UnitImage.create(LockedSummon(VoidOrbTab[index]).u, SUMMON_MODELPATH, SUMMON_GROWTH_DURA, SUMMON_MAX_SIZE, SUMMON_BACK_HGH)
else
//...otherwise create him a new sidekick adding the remaining lifetime
//of the previous creature to the new one's.
//So basically, the lifetime does stack.
set lifetime = temp.slt.remTick * TICK + this.drainedLife / SUMMON_LIFE_FAC[this.level]
call temp.destroy()
set VoidOrbTab[index] = integer(LockedSummon.create(this.caster, lifetime))
call UnitImage.create(this.caster, IMAGE_MODELPATH, IMAGE_GROWTH_DURA, IMAGE_MAX_SIZE, IMAGE_HEIGHT)
call UnitImage.create(LockedSummon(VoidOrbTab[index]).u, SUMMON_MODELPATH, SUMMON_GROWTH_DURA, SUMMON_MAX_SIZE, SUMMON_BACK_HGH)
endif
endif
call this.destroy()
endmethod
private static method onTick takes nothing returns nothing
local thistype data = thistype(GetTimerData(GetExpiredTimer()))
local real angle = A2PXY(GetUnitX(data.missile), GetUnitY(data.missile), GetUnitX(data.caster), GetUnitY(data.caster))
local real distance = D2PXY(GetUnitX(data.missile), GetUnitY(data.missile), GetUnitX(data.caster), GetUnitY(data.caster))
local unit u = null
if not data.timeout then
set TempUnit = data.caster//to make filtering for enemies easier...
call GroupEnumUnitsInRange(TempGroup, data.x, data.y, DRAINORB_RNG[data.level], Condition(function Enum))
loop
set u = FirstOfGroup(TempGroup)
exitwhen u == null
//everytime this method runs, each enemied unit nearby the voidorb
//will have a data.dosc chance to be a victim of a drainorb.
if GetRandomInt(0, 100) < data.dosc then
call DrainOrb.create(data, u)
endif
call GroupRemoveUnit(TempGroup, u)
endloop
else
call data.onCollision()
endif
endmethod
public static method onDelay takes nothing returns nothing
local thistype data = thistype(GetTimerData(GetExpiredTimer()))
call TimerStart(data.VOtimer, TICK, true, function thistype.onTick)
endmethod
public static method create takes unit caster, real x, real y returns thistype
local thistype data = thistype.allocate(x, y, VOIDORB_END_Z, VOIDORB_END_Z, 0., caster, VOIDORB_SPEED, VOIDORB_ACC, VOIDORB_COL, VOIDORB_MODEL, VOIDORB_SIZE, VOIDORB_DELAY)
set data.caster = caster
set data.level = GetUnitAbilityLevel(caster, AID) -1
set data.drainedLife = 0.
// this formula will calculate the chance on whether to spawn a drainorb
// on a nearby enemy or not. About the 40: I guessed that a unit will
// be approximately 40 * TICKS seconds in the orb's range (so the onTick
// method will be run 40 times). The rest of the formula is just algebra.
set data.dosc = RoundInteger(AVG_DMG_PER_UNIT[data.level] / (DRAINORB_DMG[data.level] * 40)* 100)
set data.VOtimer = NewTimer()
call SetTimerData(data.VOtimer, integer(data))
call TimerStart(data.VOtimer, VOIDORB_DELAY, false, function thistype.onDelay)
return data
endmethod
private method onDestroy takes nothing returns nothing
call DestroyEffect(this.spx)
call UnitApplyTimedLife(this.missile, 'BTLK', 0.5)
set this.spx = null
set this.missile = null
set this.caster = null
call ReleaseTimer(this.t)
call ReleaseTimer(this.VOtimer)
endmethod
endstruct
//USE OF STRUCT:
//spawns an effect on the target which will grow in size
//and fade over time (just like the effect from the ability avatar)
//will be used twice when the voidorb has reached the caster:
//once for the caster himself and once for the summoned unit
private struct UnitImage
unit u //target
unit dummy //dummyunit with the effect spawned on it
integer fade //current alpha value
real size //current scaling value
timer t
effect spx
integer fadeStep
real sizeStep
private static method onTick takes nothing returns nothing
local thistype data = thistype(GetTimerData(GetExpiredTimer()))
local real x
local real y
if not (data.fade <= 0) then
set data.fade = data.fade - data.fadeStep
set data.size = data.size + data.sizeStep
set x = GetUnitX(data.u)
set y = GetUnitY(data.u)
call SetUnitPosition(data.dummy, x, y)
call SetUnitFacing(data.dummy, GetUnitFacing(data.u))
call SetUnitVertexColor(data.dummy, 255,255,255,data.fade)
call SetUnitScale(data.dummy, data.size, data.size,data.size)
else
call data.destroy()
endif
endmethod
public static method create takes unit target, string modelpath, real duration, real size, real z returns thistype
local thistype data = thistype.allocate()
local real x = GetUnitX(target)
local real y = GetUnitY(target)
set data.u = target
set data.dummy = CreateUnit(Player(PLAYER_NEUTRAL_PASSIVE), DUMMYID, GetUnitX(data.u), GetUnitY(data.u), GetUnitFacing(data.u))
set data.spx = AddSpecialEffectTarget(modelpath, data.dummy,"origin")
set data.fadeStep = R2I((255. * TICK) / duration)
set data.sizeStep = (size * TICK) / duration
call SetUnitFlyHeight(data.dummy, z, 0.)
call SetUnitPosition(data.dummy, x, y)
set data.fade = 255
set data.size = 1.
set data.t = NewTimer()
call SetTimerData(data.t, integer(data))
call TimerStart(data.t, TICK, true, function thistype.onTick)
return data
endmethod
private method onDestroy takes nothing returns nothing
set this.u = null
call RemoveUnit(this.dummy)
call DestroyEffect(this.spx)
call ReleaseTimer(this.t)
set this.spx = null
set this.dummy = null
endmethod
endstruct
//USE OF STRUCT:
//a homing missile, just like VoidOrb, however this one
//will damage the unit it is spawned on. Also, the drained
//hitpoints will only convert into lifetime for the summoned
//unit, when the drainOrb has reached the voidOrb.
private struct DrainOrb extends HomingMissile
VoidOrb vo //the voidOrb it is homing to
real drainedLife
timer DOtimer
private static method onTick takes nothing returns nothing
local thistype data = thistype(GetTimerData(GetExpiredTimer()))
//there are only 2 possibilities: the drainOrb will either
//successfully reach the voidOrb or the voidOrb will vanish
//before the drainOrb could reach it. In both cases,
//we must destroy this drainOrb and check for further conditions.
if data.timeout or data.vo.missile == null then
call data.destroy()
endif
endmethod
//public static method create takes real startX, real startY, real startZ, unit target, real speed, real speedAcc, real collisionRange, string missileModel, real scalingValue, real delay returns thistype
public static method create takes VoidOrb vo, unit enemy returns thistype
local real speed = GetRandomReal(DRAINORB_MIN_SPEED, DRAINORB_MAX_SPEED)
local thistype data = thistype.allocate(GetUnitX(enemy), GetUnitY(enemy), GetUnitFlyHeight(enemy), VOIDORB_END_Z, DRAINORB_ZOFFSET, vo.missile, speed, DRAINORB_ACC, DRAINORB_COL, DRAINORB_MODEL,DRAINORB_SIZE, 0.)
set data.vo = vo
call SetUnitVertexColor(data.missile, DRAINORB_RED, DRAINORB_GREEN, DRAINORB_BLUE, DRAINORB_ALPHA)
// DAMAGE DEALT HERE
set data.drainedLife = DRAINORB_DMG[vo.level]
call UnitDamageTarget(data.vo.caster, enemy, DRAINORB_DMG[vo.level], true, true, AT, DT, WT)
set data.DOtimer = NewTimer()
call SetTimerData(data.DOtimer, integer(data))
call TimerStart(data.DOtimer, TICK, true, function thistype.onTick)
return data
endmethod
private method onDestroy takes nothing returns nothing
//if the voidOrb has vanished before the drainOrb did...
if (this.vo.missile == null) then
call UnitApplyTimedLife(this.missile, 'BTLK', 0.5) //make the drainOrb disappear smoothly
else
//otherwise remove it. Because black holes don't disappear slowly, y'know.
//or at least that's what Albert Einstein came up with.
call RemoveUnit(this.missile)
//only add drained life when DrainOrb has reached VoidOrb
call this.vo.addDrainedLife(this.drainedLife)
endif
call ReleaseTimer(this.DOtimer)
endmethod
endstruct
//USE OF STRUCT:
//alternative for timed life (like summoned units have).
//Spawns an amount of shards over the target unit which will
//shrink and disappear over time one by one.
private struct SoulLifetime
unit u //affected unit
unit array souls[SOUL_AMT] //I called the dummies "souls" here. Just to be fancy.
real array radians[SOUL_AMT]
integer current //counter, used for indexing
real soulSize //current soul scaling value
real shrinkStep //shrinking step per soul
integer tickAmt //amount of ticks per soul
readonly integer remTick //remaining ticks
timer t
boolean timeout
static real radDistance //radian-distance between each soul
static real cosS//cos-value for the slope plane
static real sinS//sin ---""---
private static method onTick takes nothing returns nothing
local thistype data = thistype(GetTimerData(GetExpiredTimer()))
local integer i = 0
// Positioning the souls behind the unit.
// this is basically just maths...
local real angle = GetUnitFacing(data.u) * bj_PI/180.
local real cosA = Cos(angle)
local real sinA = Sin(angle)
local real x
local real y
local real z
local real x1
loop
exitwhen i >= SOUL_AMT
set x = Cos(data.radians[i]) * SOUL_RADIUS
set y = Sin(data.radians[i]) * SOUL_RADIUS
set z = 0
set x1 = x * cosS
set z = x * sinS
set x = x1*cosA - y*sinA
set y = x1*sinA + y*cosA
call SetUnitX(data.souls[i], GetUnitX(data.u)+SOUL_XOFFSET+x)
call SetUnitY(data.souls[i], GetUnitY(data.u)+ y)
call SetUnitFlyHeight(data.souls[i], SOUL_HEIGHT + z + GetUnitFlyHeight(data.u), 0.)
set i = i+1
endloop
//Shrinking souls and checking timeout-conditions.
//If there are no ticks left for the current soul...
if data.remTick < 0 then
//...make it disappear and decrement the current-counter...
call UnitApplyTimedLife(data.souls[data.current], 'BTLF', 0.1)
set data.souls[data.current] = null //nullifying the variable, just to be sure...
set data.current = data.current - 1
//... and declare timeout when there is no other soul left.
if data.current < 0 then
set data.timeout = true
endif
set data.soulSize = SOUL_MAX_SCALE
set data.remTick = data.tickAmt
else //But if there are still ticks left for the current soul...
//...just decrement the remaining ticks and shrink the soul.
set data.soulSize = data.soulSize - data.shrinkStep
set data.remTick = data.remTick - 1
call SetUnitScale(data.souls[data.current], data.soulSize,data.soulSize,data.soulSize)
endif
endmethod
public static method create takes unit target, real time returns thistype
local thistype data = thistype.allocate()
local integer i = 0
local real angle = (GetUnitFacing(target)) * bj_PI/180.
local real cosA = Cos(angle)
local real sinA = Sin(angle)
local real x
local real y
local real z
local real x1
set data.u = target
set data.current = (SOUL_AMT-1) //the current soul that is is being used
set data.tickAmt = R2I((time/TICK)/SOUL_AMT) //amount of ticks for each individual soul
set data.remTick = data.tickAmt //total remaining ticks
set data.shrinkStep = (SOUL_MAX_SCALE - SOUL_MIN_SCALE) / data.tickAmt
set data.soulSize = SOUL_MAX_SCALE
set data.timeout = false
loop
exitwhen i >= SOUL_AMT
set data.radians[i] = -bj_PI/2 + i * radDistance
set x = Cos(data.radians[i]) * SOUL_RADIUS
set y = Sin(data.radians[i]) * SOUL_RADIUS
set x1 = x * cosS
set z = x * sinS
set x = x1*cosA - y*sinA
set y = x1*sinA + y*cosA
set data.souls[i] = CreateUnit(GetOwningPlayer(data.u), SOUL_ID, GetUnitX(data.u) + x + SOUL_XOFFSET, GetUnitY(data.u) + y, GetUnitFacing(data.u))
call SetUnitScale(data.souls[i], SOUL_MAX_SCALE, SOUL_MAX_SCALE, SOUL_MAX_SCALE)
call UnitAllowFly(data.souls[i])
call SetUnitFlyHeight(data.souls[i], SOUL_HEIGHT + z + GetUnitFlyHeight(data.u), 0.)
set i = i+1
endloop
set data.t = NewTimer()
call SetTimerData(data.t, integer(data))
call TimerStart(data.t, TICK, true, function SoulLifetime.onTick)
return data
endmethod
public method onDestroy takes nothing returns nothing
local integer i = 0
call ReleaseTimer(this.t)
set this.u = null
loop
exitwhen i >= SOUL_AMT
if this.souls[i] != null then
call UnitApplyTimedLife(this.souls[i], 'BTLF', 0.5)
set this.souls[i] = null
endif
set i = i+1
endloop
endmethod
private static method onInit takes nothing returns nothing
local real slope = SOUL_SLOPE * bj_PI/180.
//this values will be the same all the time,
//so we just calculate them once.
set cosS = Cos(slope)
set sinS = Sin(slope)
set radDistance = bj_PI/(SOUL_AMT-1)
endmethod
endstruct
//USE OF STRUCT:
//summons a unit behind the casters back. This unit
//won't be able to move freely, but will be attached
//to the target's back. Whenever the caster attacks,
//the summoned unit will attack the same target.
private struct LockedSummon
SoulLifetime slt //each lockedSummon should have soulLifeTime.
readonly unit u //the summoned unit
unit lock //the unit it is locked onto
real x
real y
integer level //ability level
timer t
boolean inFight //used for summoned unit's behaviour.
//if the caster attacks a enemied unit, we order the summoned
//to attack the same target.
private static method onAttack takes nothing returns boolean
//local thistype data = thistype(GetTriggerData(GetTriggeringTrigger()))
local thistype data = thistype(LockedSummonTab[GetHandleId(GetAttacker())])
local real distance = D2PXY(GetUnitX(GetTriggerUnit()), GetUnitY(GetTriggerUnit()), data.x, data.y)
//attacker must be caster
if IsUnit(GetAttacker(), data.lock) then
set TempUnit = data.lock //to make filtering for enemies easier.
call GroupEnumUnitsInRange(TempGroup,data.x, data.y,SUMMON_ATK_RANGE[data.level] - SUMMON_BACK_HGH, Condition(function Enum))
//attacked unit must fit the enumeration-criteria.
if IsUnitInGroup(GetTriggerUnit(), TempGroup) then
call IssueTargetOrder(data.u, "attack", GetTriggerUnit())
set data.inFight = true //so now we know, the summoned unit is in a fight.
endif
call GroupClear(TempGroup)
endif
return false
endmethod
private static method onDeath takes nothing returns boolean
local thistype data = thistype(LockedSummonTab[GetHandleId(GetDyingUnit())])
if IsUnit(GetDyingUnit(), data.lock) then
call data.destroy()
endif
return false
endmethod
private static method onTick takes nothing returns nothing
local thistype data = thistype(GetTimerData(GetExpiredTimer()))
local real angle = GetUnitFacing(data.lock)
local unit temp = null
//as long as the summoned unit has lifetime left...
if not data.slt.timeout then
set data.x = GetUnitX(data.lock) - SUMMON_BACK_RNG * Cos(angle * bj_DEGTORAD)
set data.y = GetUnitY(data.lock) - SUMMON_BACK_RNG * Sin(angle * bj_DEGTORAD)
call SetUnitX(data.u, data.x)
call SetUnitY(data.u, data.y)
call SetUnitFlyHeight(data.u, SUMMON_BACK_HGH, 0.)
set TempUnit = data.lock //to make filtering for enemies easier.
call GroupEnumUnitsInRange(TempGroup,data.x, data.y,SUMMON_ATK_RANGE[data.level] - SUMMON_BACK_HGH, Condition(function Enum))
//if there are no enemies around...
if FirstOfGroup(TempGroup) == null then
if data.inFight then //...and we memorized the unit to fight...
set data.inFight = false //...we overthink that, because our unit can't
//fight when there's no enemy around...
endif
else //... but if there are enemies...
if not data.inFight then //...and our unit is not fighting ...
//...make it fight...
call IssueTargetOrder(data.u, "attack", FirstOfGroup(TempGroup))
set data.inFight = true //...and remember that...
endif
call GroupClear(TempGroup)
endif
//if the summoned unit is not fighting, we can make it face the same
//direction as its summoner does and stop all other orders.
if not data.inFight then
call IssueImmediateOrder(data.u, "stop")
call SetUnitFacing(data.u, angle)
endif
else
call data.destroy()
endif
endmethod
public static method create takes unit lock, real lifetime returns thistype
local thistype data = thistype.allocate()
local real angle = GetUnitFacing(lock)
local integer level = GetUnitAbilityLevel(lock, AID) - 1
set data.lock = lock
set data.level = level
set data.x = GetUnitX(data.lock) - SUMMON_BACK_RNG * Cos(angle * bj_DEGTORAD)
set data.y = GetUnitY(data.lock) - SUMMON_BACK_RNG * Sin(angle * bj_DEGTORAD)
set data.u = CreateUnit(GetOwningPlayer(data.lock), SUMMON_ID[level], data.x, data.y, angle)
set data.slt = SoulLifetime.create(data.u, lifetime)
set data.inFight = false
call SetUnitAnimation(data.u, SUMMON_ANI)
call UnitAllowFly(data.u)
call SetUnitFlyHeight(data.u, SUMMON_BACK_HGH, 0.)
//saving the data via a table, so that the non-periodic methods
//onDeath and onAttack will have the data that way.
set LockedSummonTab[GetHandleId(data.lock)] = integer(data)
set data.t = NewTimer()
call SetTimerData(data.t, integer(data))
call TimerStart(data.t, TICK, true, function thistype.onTick)
return data
endmethod
public method onDestroy takes nothing returns nothing
call ReleaseTimer(this.t)
call UnitApplyTimedLife(this.u, 'BTLK', 0.1)
call this.slt.destroy()
set this.u = null
set this.lock = null
endmethod
private static method onInit takes nothing returns nothing
//we need to trigger actions whenever the summoner attacks or dies.
call RegisterPlayerUnitEvent(EVENT_PLAYER_UNIT_ATTACKED, function thistype.onAttack)
call RegisterPlayerUnitEvent(EVENT_PLAYER_UNIT_DEATH, function thistype.onDeath)
endmethod
endstruct
private function Conditions takes nothing returns boolean
local unit caster = null
local unit target = null
local real x
local real y
if (GetSpellAbilityId() == AID) then
set caster = GetSpellAbilityUnit()
set x = GetSpellTargetX()
set y = GetSpellTargetY()
//the whole spell starts with a circularSpawn. So let the fun begin...
call CircularSpawn.create(caster, x, y, A2PXY(GetUnitX(caster), GetUnitY(caster), x, y))
set caster = null
endif
return false
endfunction
private function Init takes nothing returns nothing
call RegisterPlayerUnitEvent(EVENT_PLAYER_UNIT_SPELL_CAST, function Conditions)
set VoidOrbTab = Table.create()
set LockedSummonTab = Table.create()
call Settings()
//Preloading the strings containing a file
call Preload(SPAWN_MODEL)
call Preload(SPAWN_COL_MODEL)
call Preload(IMAGE_MODELPATH)
call Preload(VOIDORB_MODEL)
call Preload(DRAINORB_MODEL)
call Preload(SUMMON_MODELPATH)
endfunction
endscope
24.05.2016 | - Preloaded files in the Init function - scratched out unnecessary comments |
11.05.2016 | - fixed summoned unit's behaviour - got rid of the library TriggerUtils and used Table instead for this matter - added method onDeath for LockedSummon, now the summoned unit disappears if caster dies |
10.05.2016 | - added UnitImage effect on summoned unit - changed A2PXY function so that it doesn't use ModuloReal - added customizable height settings to CircularSpawn effect - increased test-map quality |
06.05.2016 | - added struct "HomingMissile" and used it for inheritance - added documentation at certain code fragments - changed tooltip - made summoned unit have different stats per level |
06.05.2016 | First upload |
No reason not to.
In fact I don't see any system that wouldn't be allowed. Unless it's more systems than your own code.
![]() |
@Almia - The effects at the start of your spell sort of reminds me of Tank's BUSlooks great though!
On a side note, boop:
![]()
Still missing a few more mechanics, including a whole phase after the player can start moving the orb, and a couple of needed special effects. I actually banged my head for a really long time figuring out why the smaller orbs weren't always landing in the center of the big orb... I finally figured out that it was because General Frank made his models "slightly" float *facepalm* Sitting at SEVEN friken GUI triggers![]()
]
Can I ask for a request, dingo? can you usetags, because that stretches the page (not all users have large monitors ;_; )
what's the BB Code for putting components in clickable tabs like you did with your gifs? :3
Not entirely convinced with the name, especially as there already is a spell that happens to have a similar one, but I cannot think of a better idea.
tags please As I have said before "not all users have large monitors" |
@Loner-Magixxar
tags or
tags please ![]()
As I have said before "not all users have large monitors"
I know, I just mistakenly pushed the wrong button and then I was searching for a comment that had used a hidden trigger to find out how to hide them. Yes, that's how noob I am! |
@Loner-Magixxar
Never use 0.01s timers. It's too much on the performance.
100 is intensive. You can just use 0.03 (because that's the standard). If it is smooth, I think you can do 1/60
I can't use 0.03 but I can use 0.04. Is 0.04 good enough?
33 frames > 25 frames :V
Maker used to hate 25 frames, it is not that smooth.
1 cannot be divided by 3 but it can be divided by 1, 2 or 4. Because my computations are based on an integer value of that division I cannot use 3.
But still I will try to see if I can change it. Ahh this is going to affect all my codes.
Loner-Magixxar, a little tip. Check out the other entries for naming conventions of variables.(It's supposed to follow standard submission guidelines.)
![]()
(Note : edited some parts. Not running smooth when I have Google Chrome open)
currently at 1300~ lines ;_; have not yet written the 3(4) cores of the spell.
I might as well just change the some of the mechanics of the spell so that it would be simpler.
Gotta finish this tomorrow(if I can)
I actually intended the spell to be ironic to the night elvesI really like it, it kinda looks like a different force of nature though.
Just with disgusting spiders and effect-p0rn.
ThanksI didn't even notice the small spiders actually. Really neat effect.
I actually intended the spell to be ironic to the night elves
Lol, that's opposite to mine. Night elves are even powerful with Ancient of Power, mwaha!
I have question (this might not an exact place to ask but): Is variables from Trigger Editor (Ctrl+B) have limitation in term of number?
I have question (this might not an exact place to ask but): Is variables from Trigger Editor (Ctrl+B) have limitation in term of number?
nope it doesnt
Hell no.
I see. Gonna continue the spell.
Edit
Here's another question: About variables naming convention, I have my variables named like this:
Is the 2 last variables okay with those names?
Set AoP_SummoningSFX = war3mapImported\HarvestLife.mdl
Set AoP_sSFXSize[1] = 0.50
Set AoP_sSFXSize[2] = 0.65
Set AoP_sSFXSize[3] = 0.80
Set AoP_sSFXHeight = 0.00