// Loljuvination -- v1.02. -- by Weep
//
// It's Rejuvination, plus some side-effects: the periodic spawning of flowers,
// fuzzy critters, a chance to hex the target, and a chance to summon a demonic Infernal.
// Gotta be careful when weilding those powerful life-based spells...
//
// Requires:
// Systems:
// TimerUtils by Vexorian
// DummyCaster by Jesus4Lyf
// Any one of the following: AutoIndex by grim001, AIDS by Jesus4Lyf, UnitIndexer by Nestharus
// Object Editor abilities:
// Rejuvination (LOL)
// Inferno (LOL)
// Hex (LOL)
//
// Much of the level data is handled in the Object Editor abilities; furthermore, they must all have
// the same amount of levels, if you wish to change that.
library Loljuvination initializer config requires TimerUtils, DummyCaster, optional AIDS, optional AutoIndex, optional UnitIndexer //I don't know how to make a library require any one of multiple compatible libraries...
//===================================================================
// Rawcodes and boring data.
// Abilities
globals
//The base ability used by the hero, and its buff.
private constant integer BASE_ABILITY = 'A000' //Rawcode of "Rejuvination (LOL)"
private constant integer BASE_BUFF = 'Brej'
//A point-targeted ability with a chance to be cast at the position of the target.
private constant integer POINT_ABILITY = 'A001' //Rawcode of "Inferno (LOL)"
private constant string POINT_ABILITY_ORDER = "dreadlordinferno"
//A unit-targeted ability with a chance to be cast on the target.
private constant integer TARGET_ABILITY = 'A002' //Rawcode of "Hex (LOL)"
private constant string TARGET_ABILITY_ORDER = "hex"
endglobals
//Spell level information
private function ProcForCritter takes integer level returns boolean //This is checked every PERIOD
return GetRandomInt(1, 100) <= 30
endfunction
private function ProcForEffect takes integer level returns boolean //This is checked every PERIOD
return GetRandomInt(1, 100) <= 60
endfunction
private function ProcForPointSpell takes integer level returns boolean //This is checked every PERIOD
return GetRandomInt(1, 100) <= 2+level/2
endfunction
private function ProcForTargetSpell takes integer level returns boolean //This is checked every PERIOD
return GetRandomInt(1, 100) <= 8-level
endfunction
// Constant Data
globals
//Period on which to run this spell's checks
private constant real PERIOD = 0.5
//Effects with duration made periodically on the ground
private string array DOODAD_EFFECTS_TYPE
private integer DOODAD_EFFECTS_TYPE_COUNT
private constant real DOODAD_EFFECTS_MAX_OFFSET = 100. //So they will be made near the target
private constant real DOODAD_EFFECT_EXPIRATION_TIME = 40. //Duration before removing effects
//An effect played when a critter is spawned
private constant string CRITTER_SUMMON_EFFECT = "Abilities\\Spells\\Orc\\FeralSpirit\\feralspiritdone.mdl"
private constant string CRITTER_SUMMON_EFFECT_ATTACHMENT = "origin"
private constant real CRITTER_EXPIRATION_TIME = 120. //Duration of life of the spawned critters
private constant integer CRITTER_EXPIRATION_BUFF = 'Btdg' //Timed life buff
private integer array CRITTER_TYPES_GROUND
private integer CRITTER_TYPE_GROUND_COUNT
private integer array CRITTER_TYPES_FLYING
private integer CRITTER_TYPE_FLYING_COUNT
endglobals
private function config takes nothing returns nothing
//Preferably cute and fuzzy animals
set CRITTER_TYPES_GROUND[0] = 'nech' //Chicken
set CRITTER_TYPES_GROUND[1] = 'necr' //Rabbit
set CRITTER_TYPES_GROUND[2] = 'nrac' //Raccoon
set CRITTER_TYPES_GROUND[3] = 'nsea' //Seal
set CRITTER_TYPES_GROUND[4] = 'nshe' //Sheep
set CRITTER_TYPE_GROUND_COUNT = 4
set CRITTER_TYPES_FLYING[0] = 'nalb' //Albatross
set CRITTER_TYPES_FLYING[1] = 'nsno' //Snowy Owl
set CRITTER_TYPES_FLYING[2] = 'nvul' //Vulture
set CRITTER_TYPES_FLYING[3] = 'nshf' //Flying Sheep
set CRITTER_TYPE_FLYING_COUNT = 3
//Flowers!
set DOODAD_EFFECTS_TYPE[0] = "Doodads\\Ruins\\Plants\\Ruins_Flower\\Ruins_Flower0.mdl"
set DOODAD_EFFECTS_TYPE[1] = "Doodads\\Ruins\\Plants\\Ruins_Flower\\Ruins_Flower1.mdl"
set DOODAD_EFFECTS_TYPE[2] = "Doodads\\Ruins\\Plants\\Ruins_Flower\\Ruins_Flower2.mdl"
set DOODAD_EFFECTS_TYPE[3] = "Doodads\\Ruins\\Plants\\Ruins_Flower\\Ruins_Flower3.mdl"
set DOODAD_EFFECTS_TYPE[4] = "Doodads\\Ruins\\Plants\\Ruins_Flower\\Ruins_Flower4.mdl"
set DOODAD_EFFECTS_TYPE_COUNT = 4
//Preloading
call UnitAddAbility(DUMMY, POINT_ABILITY)
call UnitRemoveAbility(DUMMY, POINT_ABILITY)
call UnitAddAbility(DUMMY, TARGET_ABILITY)
call UnitRemoveAbility(DUMMY, TARGET_ABILITY)
endfunction
//===================================================================
// Spell code begins here. Don't edit unless you're confident!
//===================================================================
private struct TimedEffect
private effect theEffect
private static method expire takes nothing returns nothing
local thistype this = thistype(GetTimerData(GetExpiredTimer()))
call ReleaseTimer(GetExpiredTimer())
call DestroyEffect(.theEffect)
call this.destroy()
endmethod
static method create takes effect e, real expiration returns thistype
local thistype this = thistype.allocate()
local timer t = NewTimer()
set .theEffect = e
call SetTimerData(t, this)
call TimerStart(t, expiration, false, function thistype.expire)
set t = null
return this
endmethod
endstruct
private struct Loljuvination extends array //WC3-style buffs are one-instance-per-target!
private unit target
private player casterOwner
private integer level
private boolean pointTargetProcced
private timer period
private static method periodicActions takes nothing returns nothing
local thistype this = thistype[GetTimerData(GetExpiredTimer())]
local real targetX = GetUnitX(.target)
local real targetY = GetUnitY(.target)
if GetUnitAbilityLevel(.target, BASE_BUFF) > 0 then
if ProcForCritter(.level) then
//Create a cute critter of the proper movement type for the target
if IsUnitType(.target, UNIT_TYPE_FLYING) then
//Why declare a new local or global when there's already one for this purpose?
set bj_lastCreatedUnit = CreateUnit(Player(PLAYER_NEUTRAL_PASSIVE), CRITTER_TYPES_FLYING[GetRandomInt(0, CRITTER_TYPE_FLYING_COUNT)], targetX, targetY, GetRandomReal(0, 360))
else
set bj_lastCreatedUnit = CreateUnit(Player(PLAYER_NEUTRAL_PASSIVE), CRITTER_TYPES_GROUND[GetRandomInt(0, CRITTER_TYPE_GROUND_COUNT)], targetX, targetY, GetRandomReal(0, 360))
endif
call DestroyEffect(AddSpecialEffectTarget(CRITTER_SUMMON_EFFECT, bj_lastCreatedUnit, CRITTER_SUMMON_EFFECT_ATTACHMENT))
call UnitApplyTimedLife(bj_lastCreatedUnit, CRITTER_EXPIRATION_BUFF, CRITTER_EXPIRATION_TIME)
endif
if ProcForEffect(.level) then
//Make a timed effect
call TimedEffect.create(AddSpecialEffect(DOODAD_EFFECTS_TYPE[GetRandomInt(0, DOODAD_EFFECTS_TYPE_COUNT)], targetX+GetRandomReal(-1, 1)*DOODAD_EFFECTS_MAX_OFFSET, targetY+GetRandomReal(-1, 1)*DOODAD_EFFECTS_MAX_OFFSET), DOODAD_EFFECT_EXPIRATION_TIME)
endif
if ProcForPointSpell(.level) and not .pointTargetProcced then
//Cast the point-target ability using DummyCaster for the spell's caster's player
call SetUnitOwner(DUMMY, .casterOwner, false)
call UnitAddAbility(DUMMY, POINT_ABILITY)
call SetUnitAbilityLevel(DUMMY, POINT_ABILITY, .level)
call IssuePointOrder(DUMMY, POINT_ABILITY_ORDER, targetX, targetY)
//Be a good citizen and reset the dummy
call UnitRemoveAbility(DUMMY, POINT_ABILITY)
call SetUnitOwner(DUMMY, Player(PLAYER_NEUTRAL_PASSIVE), false)
set .pointTargetProcced = true //It would be a bit imba to cast more than once per buffing
endif
if ProcForTargetSpell(.level) then
//Cast the unit-target ability using DummyCaster
call SetUnitOwner(DUMMY, Player(PLAYER_NEUTRAL_PASSIVE), false) //In case somebody else was careless
call UnitAddAbility(DUMMY, TARGET_ABILITY)
call SetUnitAbilityLevel(DUMMY, TARGET_ABILITY, .level)
call IssueTargetOrder(DUMMY, TARGET_ABILITY_ORDER, .target)
//Be a good citizen and reset the dummy
call UnitRemoveAbility(DUMMY, TARGET_ABILITY)
endif
else
//IT'S OVER!
call ReleaseTimer(.period)
set .period = null
endif
endmethod
private static method onCast takes nothing returns boolean
local thistype this
if GetSpellAbilityId() != BASE_ABILITY then
return false
endif
set this = thistype[GetUnitId(GetSpellTargetUnit())] //One instance per target is the WC3 way.
set .target = GetSpellTargetUnit()
set .casterOwner = GetTriggerPlayer()
set .level = GetUnitAbilityLevel(GetTriggerUnit(), BASE_ABILITY)
set .pointTargetProcced = false
if .period == null then //No need to restart things if they're already going from a previous cast
set .period = NewTimer()
call SetTimerData(.period, this)
call TimerStart(.period, PERIOD, true, function thistype.periodicActions)
endif
return false
endmethod
private static method onInit takes nothing returns nothing
local trigger t = CreateTrigger()
call TriggerRegisterAnyUnitEventBJ(t, EVENT_PLAYER_UNIT_SPELL_EFFECT)
call TriggerAddCondition(t, Condition(function thistype.onCast))
endmethod
endstruct
endlibrary