library OvermindSpellOne initializer Init requires /*
* ---------------
*/ RealGroup, /*
* ---------------
* - Allows outsourcing of dynamic data attachment.
*
* ---------------
*/ TextTag, /*
* ---------------
* - A library requirement for displaying game information related to the spell.
*
* ------------------
*/ TimerUtils, /*
* ------------------
* - An abstraction library for timers. It allows requesting of to-be recycled timers,
* in an O[1] fashion.
*
* ---------------------
*/ AllocationAndLinks /*
* ---------------------
* - Updated allocator module that considers the new maximum count of array instances.
*
*/
scope OvermindSpellUno
globals
/*
A flag that basically allows debugging of functions
*/
private constant boolean ALLOW_DEBUG = false
endglobals
/*
Configurable functions:
These are the functions which are okay to modify:
RAW_ID(integer request)
DATA(integer request, integer level)
MDL_FILE(integer request)
SPELL_DM(int req)
- A separate function that stores certain rawcodes to be used in the scope
By default, it returns 0. (not a float)
DATA(int req, int lev)
- The most versatile function, this function can essentially serve as
the softcoded Object Editor fields which can be tinkered with.
Defaults to 0f.
Some requests indicate a default value for a requested instance. In
that case, if you need to modify, you can do so. However, those
that are not defined as defaulting to a certain value will not work
with your modifications.
- This is somewhat considered as a magic numbers' function.
MDL_FILE(int req)
- Another versatile function, this function stores the appropriate strings
to be used later on.
Defaults to "".
*/
// Spell raw code
private constant function SPELL_ID takes nothing returns integer
return 'A000'
endfunction
private constant function RAW_ID takes integer request returns integer
if request == 0 then
// The illusion ability to be used
return '0000'
elseif request == 1 then
// The illusion's timed buff
return 'B001'
endif
return 0
endfunction
private constant function DATA takes integer request, integer level returns real
if request == 0 then
// Returns damage dealt per tick in puddle
if level == 1 then
return 25.
elseif level == 2 then
return 35.
elseif level == 3 then
return 50.
endif
elseif request == 1 then
// Returns damage dealt on puddle evaporation
if level == 1 then
return 50.
elseif level == 2 then
return 70.
elseif level == 3 then
return 90.
endif
elseif request == 2 then
// Returns the tick rate of the puddle.
// Defaults to 1.
return 1.
elseif request == 3 then
// Returns the duration of the illusion
if level == 1 then
return 10.
elseif level == 2 then
return 15.
elseif level == 3 then
return 20.
endif
elseif request == 4 then
// Returns damage received by the illusion
if level == 1 then
return 1.25
elseif level == 2 then
return 1.25
elseif level == 3 then
return 1.25
endif
elseif request == 5 then
// Returns the speed of the bile projectile
// Defaults to 700.
return 700.
elseif request == 6 then
// Returns the tick rate of the movement
// Also functions as the transition rate of visibility.
return 1/50.
elseif request == 7 then
// Returns the duration of transition of visibility.
return 1.5
elseif request == 8 then
// Returns the range check for damaging nearby units
// Defaults to 250.
return 250.
endif
return 0.
endfunction
private constant function MDL_FILE takes integer request returns string
if request == 0 then
// Returns the model string of the projectile
return "Abilities\\Weapons\\ChimaeraAcidMissile\\ChimaeraAcidMissile.mdl"
elseif request == 1 then
// Returns the puddle model
// For now, it defaults to the above string
return MDL_FILE(0)
endif
return ""
endfunction
/*
Spell code:
This section deals with the actual mechanics of the spell. It is not recommended
to modify the spell code other than inserting debug messages.
The class is named Data since the class is encapsulated and does not need a detailed
name.
class Data implements AllocLinkBundle
- Importing the coder's own allocation and linked list bundle
private static code exec_code
- A variable generated to prevent a trigger evaluation and duplication of functions
private static timer EVAL
- A timer specifically for handling the list in the class.
Should not be destroyed.
private static boolean EVAL_ACT
- A flag to determine if the timer is running or not.
private integer flag
- A flag to determine the current phase of the spell.
private unit dummy
- Stores a newly-created dummy unit for effect purposes.
private effect missile_fx
- Only used for the dummy unit as a projectile
model.
private effect missile_fx2
private effect missile_fx3
- Reserved as pool model. Change MDL_FILE(1) to the model file of your choice.
readonly unit unit
- The actual casting unit.
readonly unit illusion
- The current illusion unit.
readonly unit target
- The target of the ability.
readonly integer level
- An integer storing the spell level of the casting unit. (SPELL_ID()
readonly RealGroup reals
- A variable that allows dynamic mapping of variables as they are requested.
For more info, see Essential Libraries/Real Group
class functions
-function destroy()
-> Does exactly what it is defined, destroying and clearing the instance.
-static function get_illu(unit id) returns Data
-> Searches for the illusion that dealt damage.
-static function get(unit id) returns Data
-> Searches for the caster.
-static function create(unit u) returns Data
-> Creates an instance mapped to the unit.
-private static function onManipulateDamage()
-> Search illusion, and apply necessary handler changes.
-private static function onSummon()
-> Detects a summon event by the dummy.
-
*/
//! runtextmacro link_module("search", "private")
private struct Data extends array
implement AllocLinkBundle
private static code exec_code = null
private static timer EVAL = null
private static boolean EVAL_ACT = false
private integer flag
private integer flag_data
private real fade
private unit dummy
private effect missile_fx
private effect missile_fx2
private effect missile_fx3
readonly unit unit
readonly unit illusion
readonly unit target
readonly integer level
readonly RealGroup reals
private method destroy takes nothing returns nothing
call reals.destroy()
set unit = null
set target = null
set dummy = null
set reals = 0
set level = 0
set flag = 0
set fade = 0
set flag_data = 0
call pop()
call deallocate()
endmethod
//! runtextmacro search_list("", "get_illu", "unit", "next", "illusion == id")
//! runtextmacro search_list("", "get", "unit", "next", "unit == id")
static method create takes unit u, unit targ returns thistype
local thistype this = get(u)
if this == 0 then
set this = allocate()
set unit = u
set target = targ
set level = GetUnitAbilityLevel(u, SPELL_ID())
set fade = DATA(7, 0)
set flag = 1
set flag_data = 255
call IssueImmediateOrderById(unit, 851972)
call PauseUnit(unit, true)
set reals = RealGroup.create()
set reals.value = DATA(3, level)
call reals.add(GetUnitX(unit))
call reals.add(GetUnitY(unit))
call push()
if not EVAL_ACT then
call TimerStart(EVAL, DATA(6, 0), true, exec_code)
set EVAL_ACT = true
endif
endif
return this
endmethod
private static unit damaged = null
private static thistype damaged_this = 0
private static method onManipulateDamage takes nothing returns nothing
local thistype this
if damaged != Damage.target then
set this = get_illu(Damage.target)
set damaged_this = this
set damaged = Damage.target
else
set this = damaged_this
endif
if this != 0 then
set Damage.amount = Damage.amount * DATA(4, level)
endif
endmethod
implement DoubleLink_search
private unit summoned
private unit summoned_illu
private thistype summoned_this
private static thistype summoned_count = 0
private static method onSummon takes nothing returns nothing
local thistype this = thistype(0).search_next.summoned_this
call DestroyTrigger(GetTriggeringTrigger())
set thistype(0).search_next.summoned = GetSummonedUnit()
call UnitRemoveAbility(thistype(0).search_next.summoned_illu, RAW_ID(0))
call RecycleDummy(thistype(0).search_next.summoned_illu)
call SelectUnit(unit, false)
call ShowUnit(unit, false)
call SetUnitInvulnerable(unit, true)
call SetUnitX(thistype(0).search_next.summoned, GetUnitX(unit))
call SetUnitY(thistype(0).search_next.summoned, GetUnitY(unit))
call PauseUnit(thistype(0).search_next.summoned, true)
if GetLocalPlayer() == GetOwningPlayer(unit) then
call SelectUnit(thistype(0).search_next.summoned, true)
endif
set illusion = thistype(0).search_next.summoned
call UnitApplyTimedLife(illusion, 'BTLF', DATA(3, level) - 0.5)
set summoned_count = summoned_count - 1
set thistype(0).search_next.summoned = null
set thistype(0).search_next.summoned_illu = null
set thistype(0).search_next.summoned_this = 0
call thistype(0).search_next.search_pop()
endmethod
private method timer_onTick_enum takes integer i, integer i2 returns nothing
local player p = GetOwningPlayer(unit)
call GroupEnumUnitsInRange(ENUM_GROUP, reals[i].value, reals[i + 1].value, DATA(8, level), null)
//! runtextmacro FoG_start()
if IsUnitEnemy(enum_unit, p) and UnitAlive(enum_unit) then
call UnitDamageTarget(unit, enum_unit, DATA(i2, level), true, false, ATTACK_TYPE_NORMAL, DAMAGE_TYPE_UNIVERSAL, null)
endif
//! runtextmacro FoG_end()
set p = null
endmethod
private static method timer_onTick takes nothing returns nothing
local thistype this = GetTimerData(GetExpiredTimer())
local integer i = 1
set reals.value = reals.value - DATA(2, level)
if reals.value <= 0 or not UnitAlive(illusion) then
call ReleaseTimer(GetExpiredTimer())
set i = i + 2
loop
exitwhen i >= reals.number_count
call DestroyEffect(AddSpecialEffect(MDL_FILE(0), reals[i].value, reals[i + 1].value))
call timer_onTick_enum(i, 1)
set i = i + 2
endloop
set fade = 1.5
set flag = 4
call UnitDamageTarget(unit, illusion, 0, true, false, ATTACK_TYPE_CHAOS, DAMAGE_TYPE_UNIVERSAL, null)
call KillUnit(illusion)
set illusion = null
call PauseUnit(unit, false)
call ShowUnit(unit, true)
call SetUnitInvulnerable(unit, false)
call SetUnitX(unit, reals[3].value)
call SetUnitY(unit, reals[4].value)
call SelectUnit(unit, true)
call DestroyEffect(missile_fx2)
call DestroyEffect(missile_fx3)
set missile_fx2 = null
set missile_fx3 = null
else
loop
exitwhen i >= reals.number_count
call DestroyEffect(AddSpecialEffect(MDL_FILE(0), reals[i].value, reals[i + 1].value))
call timer_onTick_enum(i, 0)
set i = i + 2
endloop
endif
endmethod
private method actions takes nothing returns nothing
local trigger detector
local unit illu
local real r
local real r1
local real r2
local real r3
local real r4
if flag == 1 then
set flag_data = Math.int_max(R2I(flag_data - (flag_data - 0)/fade*DATA(6, 0)), 0)
call SetUnitVertexColor(unit, 255, 255, 255, flag_data)
set fade = fade - DATA(6, 0)
call DestroyEffect(AddSpecialEffect(MDL_FILE(0), reals[1].value, reals[2].value))
if fade <= 0 then
set illu = GetRecycledDummyAnyAngle(GetUnitX(unit), GetUnitY(unit), GetUnitFlyHeight(unit))
set summoned_count = summoned_count + 1
call summoned_count.search_push()
set summoned_count.summoned_illu = illu
call UnitAddAbility(illu, RAW_ID(0))
call SetUnitOwner(illu, GetOwningPlayer(unit), true)
set detector = CreateTrigger()
call TriggerRegisterUnitEvent(detector, illu, EVENT_UNIT_SUMMON)
call TriggerAddCondition(detector, function thistype.onSummon)
set detector = null
set summoned_count.summoned_this = this
call IssueTargetOrderById(illu, 852274, unit)
set illu = null
set fade = 0.5
set flag = 2
set flag_data = 0
set summoned_count.summoned = null
set missile_fx2 = AddSpecialEffect(MDL_FILE(1), GetUnitX(unit), GetUnitY(unit))
call TimerStart(NewTimerEx(this), DATA(2, 0), true, function thistype.timer_onTick)
endif
elseif flag == 2 then
if fade == 0.5 then
set illu = illusion
call SetUnitAnimation(illu, "attack")
call QueueUnitAnimation(illu, "stand")
set illu = null
elseif fade <= 0 then
set illu = illusion
call PauseUnit(illu, false)
set illu = GetRecycledDummy(reals[1].real, reals[2].real, GetUnitFlyHeight(illu), Math.radA(reals[1].real, reals[2].real, GetUnitX(target), GetUnitY(target)))
set missile_fx = AddSpecialEffectTarget(MDL_FILE(0), illu, "origin")
set dummy = illu
set flag = 3
set fade = 0
set illu = null
return
endif
set fade = fade - DATA(6, 0)
elseif flag == 3 then
set illu = dummy
set r1 = GetUnitX(target)
set r2 = GetUnitY(target)
set r3 = GetUnitX(illu)
set r4 = GetUnitY(illu)
set r = Math.rad(GetUnitX(illu), GetUnitY(illu), r1, r2)
if Math.dist(GetUnitX(illu), GetUnitY(illu), r1, r2) <= DATA(5, level)*DATA(6, 0) then
call reals.add(r1)
call reals.add(r2)
call SetUnitX(illu, reals[3].real)
call SetUnitY(illu, reals[4].real)
call DestroyEffect(missile_fx)
set missile_fx = null
call DummyAddRecycleTimer(illu, 1.)
set flag = 0
set missile_fx3 = AddSpecialEffect(MDL_FILE(1), reals[3].real, reals[4].real)
else
call SetUnitX(illu, GetUnitX(illu) + DATA(5, level)*DATA(6, 0)*Cos(r))
call SetUnitY(illu, GetUnitY(illu) + DATA(5, level)*DATA(6, 0)*Sin(r))
call SetUnitFacing(illu, r*bj_RADTODEG)
endif
set illu = null
elseif flag == 4 then
set flag_data = (R2I(flag_data - (flag_data - 255)/fade*DATA(6, 0)))
call SetUnitVertexColor(unit, 255, 255, 255, flag_data)
set fade = fade - DATA(6, 0)
call DestroyEffect(AddSpecialEffect(MDL_FILE(0), reals[3].value, reals[4].value))
if fade <= 0 then
set flag_data = 255
call SetUnitVertexColor(unit, 255, 255, 255, flag_data)
call destroy()
endif
endif
endmethod
private static method timer_onLoop takes nothing returns nothing
local thistype this = thistype(0).next
local integer i = 0
loop
exitwhen this == 0
// On timer loop
call actions()
set this = next
set i = i + 1
endloop
if i == 0 then
call PauseTimer(EVAL)
set EVAL_ACT = false
endif
endmethod
private static method onInit takes nothing returns nothing
set EVAL = CreateTimer()
set exec_code = function thistype.timer_onLoop
call Damage.registerModifier(function thistype.onManipulateDamage)
endmethod
endstruct
private function OnSpellEffect takes nothing returns nothing
if GetSpellAbilityId() == SPELL_ID() then
call Data.create(GetTriggerUnit(), GetSpellTargetUnit())
endif
endfunction
public function Init takes nothing returns nothing
local trigger t = CreateTrigger()
call TriggerRegisterAnyUnitEventBJ(t, EVENT_PLAYER_UNIT_SPELL_EFFECT)
call TriggerAddCondition(t, function OnSpellEffect)
set t = null
endfunction
endscope
scope OvermindSpellTwo
private constant function SPELL_ID takes nothing returns integer
return 'A001'
endfunction
private constant function RAW_ID takes integer request returns integer
if request == 0 then
return '1000'
endif
return 0
endfunction
private constant function DATA takes integer request, integer level returns real
if request == 0 then
// Returns the amount of damage absorbed as life-force
if level == 1 then
return 0.06
elseif level == 2 then
return 0.08
elseif level == 3 then
return 0.10
endif
elseif request == 1 then
// Returns a flag for allowing Manabreak
// If return type is not zero, flag is true; else false.
if level == 1 then
return 0.
elseif level == 2 then
return 0.
elseif level == 3 then
return 0.
endif
elseif request == 2 then
// Returns ratio of manabreak based on damage dealt.
// Depends on the above flag in order to work.
if level == 1 then
return 0.5
elseif level == 2 then
return 1.25
elseif level == 3 then
return 2.
endif
elseif request == 3 then
// Returns the number of stacks stored.
// Has a maximum of 64 stacks.
// Negative value will allow instantaneous restoration of mana per blow.
if level == 1 then
return 8.
elseif level == 2 then
return 14.
elseif level == 3 then
return 20.
endif
elseif request == 4 then
// Returns the ratio of damage dealt based on stored stacks.
if level == 1 then
return 0.5
elseif level == 2 then
return 1.25
elseif level == 3 then
return 2.
endif
elseif request == 5 then
// Returns a flag for allowing Manaburst
// If return type is not zero, the flag is true; else false.
if level == 1 then
return 0.
elseif level == 2 then
return 1.
elseif level == 3 then
return 1.
endif
elseif request == 6 then
// Returns the Area of effect for Manaburst.
if level == 1 then
return 200.
elseif level == 2 then
return 200.
elseif level == 3 then
return 200.
endif
elseif request == 7 then
// Returns the flag for considering unit's max mana
// If return type is zero; flag is true; else false
if level == 1 then
return 0.
elseif level == 2 then
return 0.
elseif level == 3 then
return 1.
endif
elseif request == 8 then
// Results into the maximum number of stacks.
// Cannot be modified via assigning of level.
return 64.
endif
return 0.
endfunction
private constant function MDL_FILE takes integer request returns string
if request == 0 then
return "Objects\\Spawnmodels\\NightElf\\NECancelDeath\\NECancelDeath.mdl"
elseif request == 1 then
return "Abilities\\Spells\\Human\\Feedback\\ArcaneTowerAttack.mdl"
elseif request == 2 then
return "Abilities\\Spells\\Human\\Polymorph\\PolyMorphDoneGround.mdl"
endif
return ""
endfunction
private struct Data extends array
implement AllocLinkBundle
readonly unit unit
readonly integer count
readonly integer level
readonly real amount
// See AllocationAndLinks for the textmacro implementation
//! runtextmacro search_list("", "get", "unit", "next", "unit == id")
static method create takes unit u returns thistype
local thistype this = get(u)
if this == 0 then
set this = allocate()
set unit = u
call push()
endif
set level = GetUnitAbilityLevel(unit, SPELL_ID())
if DATA(3, level) <= 0 then
call UnitRemoveAbility(unit, RAW_ID(0))
else
call UnitAddAbility(unit, RAW_ID(0))
call SetUnitAbilityLevel(unit, RAW_ID(0), level)
endif
return this
endmethod
private method erupt_damage takes nothing returns nothing
local player p = GetOwningPlayer(unit)
call CreateTextTagBJ(p, GetUnitX(unit) - 40, GetUnitY(unit), 70, 0, 255, 255, I2S(R2I(amount*DATA(4, level))))
set vj_lastCreatedTextTag.duration = 3.5
set vj_lastCreatedTextTag.fade = 2.75
set vj_lastCreatedTextTag.height = 13
call GroupEnumUnitsInRange(ENUM_GROUP, GetUnitX(unit), GetUnitY(unit), DATA(6, level), null)
//! runtextmacro FoG_start()
if IsUnitEnemy(enum_unit, p) and UnitAlive(enum_unit) then
call DestroyEffect(AddSpecialEffectTarget(MDL_FILE(2), enum_unit, "chest"))
call UnitDamageTarget(unit, enum_unit, amount*DATA(4, level), true, false, ATTACK_TYPE_CHAOS, DAMAGE_TYPE_UNIVERSAL, null)
endif
//! runtextmacro FoG_end()
set p = null
endmethod
static method erupt takes unit u returns nothing
local thistype this = get(u)
if this == 0 then
debug call stdLib.throwError("Error; data was not initialized.")
debug return
set this = create(u)
endif
if amount == 0 then
call stdLib.throwError("Amount to be restored is zero.")
call IssueImmediateOrderById(unit, 851972)
call UnitRemoveAbility(unit, RAW_ID(0))
call UnitAddAbility(unit, RAW_ID(0))
return
endif
if DATA(3, level) > 0 then
call DestroyEffect(AddSpecialEffectTarget(MDL_FILE(0), unit, "chest"))
call SetUnitMana(unit, GetUnitMana(unit) + amount)
if DATA(5, level) != 0 then
call erupt_damage()
endif
set amount = 0.
set count = 0
endif
endmethod
private static method onDamage takes nothing returns nothing
local thistype this = get(Damage.source)
local trigger t = GetTriggeringTrigger()
if this == 0 and GetUnitAbilityLevel(Damage.source, SPELL_ID()) != 0 then
set this = create(Damage.source)
endif
if IsUnitEnemy(Damage.target, GetOwningPlayer(Damage.source)) and this != 0 then
// If the number of stacks is infinite
if DATA(3, level) > 0 then
// Stores the maximum amount of life force
if count < Math.real_min(DATA(3, level), DATA(8, 0)) then
set amount = amount + Damage.amount*DATA(0, level)
set count = count + 1
// Keeping the debug stuff just in case it breaks.
endif
else
// Sets unit's mana (acts as a wrapper)
call SetUnitMana(unit, GetUnitMana(unit) + Damage.amount*DATA(0, level))
endif
if DATA(1, level) != 0 then
if (DATA(7, level) == 0) or (DATA(7, level) != 0 and GetUnitMaxMana(Damage.target) != 0.) then
// Disables the current trigger which fires on Damage event.
call DisableTrigger(t)
// Apply special effect
call DestroyEffect(AddSpecialEffectTarget(MDL_FILE(1), Damage.target, "chest"))
call UnitDamageTarget(unit, Damage.target, Damage.amount*DATA(0, level)*DATA(2, level), true, false, ATTACK_TYPE_CHAOS, DAMAGE_TYPE_UNIVERSAL, null)
call EnableTrigger(t)
endif
endif
endif
set t = null
endmethod
private static method onInit takes nothing returns nothing
local trigger t = CreateTrigger()
call Damage.registerTrigger(t)
call TriggerAddCondition(t, function thistype.onDamage)
set t = null
endmethod
endstruct
private function OnHeroLevel takes nothing returns nothing
if GetTriggerEventId() == EVENT_PLAYER_HERO_SKILL then
if GetLearnedSkill() == SPELL_ID() then
call Data.create(GetTriggerUnit())
endif
elseif GetTriggerEventId() == EVENT_PLAYER_UNIT_SPELL_EFFECT then
if GetSpellAbilityId() == RAW_ID(0) then
call Data.erupt(GetTriggerUnit())
endif
endif
endfunction
public function Init takes nothing returns nothing
local trigger t = CreateTrigger()
call TriggerRegisterAnyUnitEventBJ(t, EVENT_PLAYER_HERO_SKILL)
call TriggerRegisterAnyUnitEventBJ(t, EVENT_PLAYER_UNIT_SPELL_EFFECT)
call TriggerAddCondition(t, function OnHeroLevel)
set t = null
endfunction
endscope
private function Init takes nothing returns nothing
call OvermindSpellUno_Init()
call OvermindSpellTwo_Init()
endfunction
endlibrary